diff --git a/previews/PR1096/.documenter-siteinfo.json b/previews/PR1096/.documenter-siteinfo.json index 86e15d84e9..97844642bf 100644 --- a/previews/PR1096/.documenter-siteinfo.json +++ b/previews/PR1096/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.11.1","generation_timestamp":"2024-10-29T12:10:20","documenter_version":"1.7.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.11.1","generation_timestamp":"2024-10-31T12:08:54","documenter_version":"1.7.0"}} \ No newline at end of file diff --git a/previews/PR1096/changelog/index.html b/previews/PR1096/changelog/index.html index cf3b9bd1ee..73ad1cb1fc 100644 --- a/previews/PR1096/changelog/index.html +++ b/previews/PR1096/changelog/index.html @@ -111,4 +111,4 @@ + K = allocate_matrix(dh, ch)

Added

Changed

Deprecated

Removed

Fixed

Other improvements

v0.3.14 - 2023-04-03

Added

Other improvements

Internal changes

Changes listed here should not affect regular usage, but listed here in case you have been poking into Ferrite internals:

v0.3.13 - 2023-03-23

Added

Fixed

Other improvements

Internal changes

v0.3.12 - 2023-02-28

Added

Fixed

Other improvements

v0.3.11 - 2023-01-17

Added

Changed

Deprecated

Fixed

Other improvements

v0.3.10 - 2022-12-11

Added

Changed

Fixed

Other improvements

v0.3.9 - 2022-10-19

Added

Changed

v0.3.8 - 2022-10-05

Added

Fixed

Other improvements

v0.3.7 - 2022-07-05

Fixed

Other improvements

v0.3.6 - 2022-06-30

Fixed

Other improvements

v0.3.5 - 2022-05-30

Added

Fixed

v0.3.4 - 2022-02-25

Added

Changed

v0.3.3 - 2022-02-04

Changed

v0.3.2 - 2022-01-18

Added

Changed

Fixed

+ end

Fixed

Other improvements

v0.3.14 - 2023-04-03

Added

Other improvements

Internal changes

Changes listed here should not affect regular usage, but listed here in case you have been poking into Ferrite internals:

v0.3.13 - 2023-03-23

Added

Fixed

Other improvements

Internal changes

v0.3.12 - 2023-02-28

Added

Fixed

Other improvements

v0.3.11 - 2023-01-17

Added

Changed

Deprecated

Fixed

Other improvements

v0.3.10 - 2022-12-11

Added

Changed

Fixed

Other improvements

v0.3.9 - 2022-10-19

Added

Changed

v0.3.8 - 2022-10-05

Added

Fixed

Other improvements

v0.3.7 - 2022-07-05

Fixed

Other improvements

v0.3.6 - 2022-06-30

Fixed

Other improvements

v0.3.5 - 2022-05-30

Added

Fixed

v0.3.4 - 2022-02-25

Added

Changed

v0.3.3 - 2022-02-04

Changed

v0.3.2 - 2022-01-18

Added

Changed

Fixed

diff --git a/previews/PR1096/cited-literature/index.html b/previews/PR1096/cited-literature/index.html index 894f37aefd..bb2ec5e229 100644 --- a/previews/PR1096/cited-literature/index.html +++ b/previews/PR1096/cited-literature/index.html @@ -1,2 +1,2 @@ -Cited literature · Ferrite.jl

Cited literature

[1]
G. A. Holzapfel. Nonlinear Solid Mechanics: A Continuum Approach for Engineering (Wiley, Chichester ; New York, 2000).
[2]
[3]
[4]
D. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.
[5]
[6]
[7]
[8]
F. D. Witherden and P. E. Vincent. On the identification of symmetric quadrature rules for finite element methods. Computers & Mathematics with Applications 69, 1232–1241 (2015).
[9]
M. Crouzeix and P.-A. Raviart. Conforming and nonconforming finite element methods for solving the stationary Stokes equations I. Revue française d'automatique informatique recherche opérationnelle. Mathématique 7, 33–75 (1973).
[10]
R. Rannacher and S. Turek. Simple nonconforming quadrilateral Stokes element. Numerical Methods for Partial Differential Equations 8, 97–111 (1992).
[11]
[12]
M. Cenanovic. Finite element methods for surface problems. Ph.D. Thesis, Jönköping University, School of Engineering (2017).
[13]
[14]
[15]
+Cited literature · Ferrite.jl

Cited literature

[1]
G. A. Holzapfel. Nonlinear Solid Mechanics: A Continuum Approach for Engineering (Wiley, Chichester ; New York, 2000).
[2]
[3]
[4]
D. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.
[5]
[6]
[7]
[8]
F. D. Witherden and P. E. Vincent. On the identification of symmetric quadrature rules for finite element methods. Computers & Mathematics with Applications 69, 1232–1241 (2015).
[9]
M. Crouzeix and P.-A. Raviart. Conforming and nonconforming finite element methods for solving the stationary Stokes equations I. Revue française d'automatique informatique recherche opérationnelle. Mathématique 7, 33–75 (1973).
[10]
R. Rannacher and S. Turek. Simple nonconforming quadrilateral Stokes element. Numerical Methods for Partial Differential Equations 8, 97–111 (1992).
[11]
[12]
M. Cenanovic. Finite element methods for surface problems. Ph.D. Thesis, Jönköping University, School of Engineering (2017).
[13]
[14]
[15]
diff --git a/previews/PR1096/devdocs/FEValues/index.html b/previews/PR1096/devdocs/FEValues/index.html index a610b73f2a..97fa5d0753 100644 --- a/previews/PR1096/devdocs/FEValues/index.html +++ b/previews/PR1096/devdocs/FEValues/index.html @@ -1,2 +1,2 @@ -FEValues · Ferrite.jl

FEValues

Type definitions

Internal types

Ferrite.GeometryMappingType
GeometryMapping{DiffOrder}(::Type{T}, ip_geo, qr::QuadratureRule)

Create a GeometryMapping object which contains the geometric

  • shape values
  • gradient values (if DiffOrder ≥ 1)
  • hessians values (if DiffOrder ≥ 2)

T<:AbstractFloat gives the numeric type of the values.

source
Ferrite.MappingValuesType
MappingValues(J, H)

The mapping values are calculated based on a geometric_mapping::GeometryMapping along with the cell coordinates, and the stored jacobian, J, and potentially hessian, H, are used when mapping the FunctionValues to the current cell during reinit!.

source
Ferrite.FunctionValuesType
FunctionValues{DiffOrder}(::Type{T}, ip_fun, qr::QuadratureRule, ip_geo::VectorizedInterpolation)

Create a FunctionValues object containing the shape values and gradients (up to order DiffOrder) for both the reference cell (precalculated) and the real cell (updated in reinit!).

source
Ferrite.BCValuesType
BCValues(func_interpol::Interpolation, geom_interpol::Interpolation, boundary_type::Union{Type{<:BoundaryIndex}})

BCValues stores the shape values at all facet/faces/edges/vertices (depending on boundary_type) for the geometric interpolation (geom_interpol), for each dof-position determined by the func_interpol. Used mainly by the ConstraintHandler.

source

Internal utilities

Ferrite.embedding_detFunction
embedding_det(J::SMatrix{3, 2})

Embedding determinant for surfaces in 3D.

TLDR: "det(J) =" ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂

The transformation theorem for some function f on a 2D surface in 3D space leads to ∫ f ⋅ dS = ∫ f ⋅ (∂x/∂ξ₁ × ∂x/∂ξ₂) dξ₁dξ₂ = ∫ f ⋅ n ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ dξ₁dξ₂ where ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ is "detJ" and n is the unit normal. See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation. For more details see e.g. the doctoral thesis by Mirza Cenanovic Tangential Calculus [12].

source
embedding_det(J::Union{SMatrix{2, 1}, SMatrix{3, 1}})

Embedding determinant for curves in 2D and 3D.

TLDR: "det(J) =" ||∂x/∂ξ||₂

The transformation theorem for some function f on a 1D curve in 2D and 3D space leads to ∫ f ⋅ dE = ∫ f ⋅ ∂x/∂ξ dξ = ∫ f ⋅ t ||∂x/∂ξ||₂ dξ where ||∂x/∂ξ||₂ is "detJ" and t is "the unit tangent". See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation.

source
Ferrite.ValuesUpdateFlagsType
ValuesUpdateFlags(ip_fun::Interpolation; update_gradients = Val(true), update_hessians = Val(false), update_detJdV = Val(true))

Creates a singelton type for specifying what parts of the AbstractValues should be updated. Note that this is internal API used to get type-stable construction. Keyword arguments in AbstractValues constructors are forwarded, and the public API is passing these as Bool, while the ValuesUpdateFlags method supports both boolean and Val(::Bool) keyword args.

source

Custom FEValues

Custom FEValues, fe_v::AbstractValues, should normally implement the reinit! method. Subtypes of AbstractValues have default implementations for some functions, but require some lower-level access functions, specifically

Array bounds

  • Asking for the nth quadrature point must be inside array bounds if 1 <= n <= getnquadpoints(fe_v). (checkquadpoint can, alternatively, be dispatched to check that n is inbounds.)
  • Asking for the ith shape value or gradient must be inside array bounds if 1 <= i <= getnbasefunctions(fe_v)
  • Asking for the ith geometric value must be inside array bounds if 1 <= i <= getngeobasefunctions(fe_v)
+FEValues · Ferrite.jl

FEValues

Type definitions

Internal types

Ferrite.GeometryMappingType
GeometryMapping{DiffOrder}(::Type{T}, ip_geo, qr::QuadratureRule)

Create a GeometryMapping object which contains the geometric

  • shape values
  • gradient values (if DiffOrder ≥ 1)
  • hessians values (if DiffOrder ≥ 2)

T<:AbstractFloat gives the numeric type of the values.

source
Ferrite.MappingValuesType
MappingValues(J, H)

The mapping values are calculated based on a geometric_mapping::GeometryMapping along with the cell coordinates, and the stored jacobian, J, and potentially hessian, H, are used when mapping the FunctionValues to the current cell during reinit!.

source
Ferrite.FunctionValuesType
FunctionValues{DiffOrder}(::Type{T}, ip_fun, qr::QuadratureRule, ip_geo::VectorizedInterpolation)

Create a FunctionValues object containing the shape values and gradients (up to order DiffOrder) for both the reference cell (precalculated) and the real cell (updated in reinit!).

source
Ferrite.BCValuesType
BCValues(func_interpol::Interpolation, geom_interpol::Interpolation, boundary_type::Union{Type{<:BoundaryIndex}})

BCValues stores the shape values at all facet/faces/edges/vertices (depending on boundary_type) for the geometric interpolation (geom_interpol), for each dof-position determined by the func_interpol. Used mainly by the ConstraintHandler.

source

Internal utilities

Ferrite.embedding_detFunction
embedding_det(J::SMatrix{3, 2})

Embedding determinant for surfaces in 3D.

TLDR: "det(J) =" ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂

The transformation theorem for some function f on a 2D surface in 3D space leads to ∫ f ⋅ dS = ∫ f ⋅ (∂x/∂ξ₁ × ∂x/∂ξ₂) dξ₁dξ₂ = ∫ f ⋅ n ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ dξ₁dξ₂ where ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ is "detJ" and n is the unit normal. See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation. For more details see e.g. the doctoral thesis by Mirza Cenanovic Tangential Calculus [12].

source
embedding_det(J::Union{SMatrix{2, 1}, SMatrix{3, 1}})

Embedding determinant for curves in 2D and 3D.

TLDR: "det(J) =" ||∂x/∂ξ||₂

The transformation theorem for some function f on a 1D curve in 2D and 3D space leads to ∫ f ⋅ dE = ∫ f ⋅ ∂x/∂ξ dξ = ∫ f ⋅ t ||∂x/∂ξ||₂ dξ where ||∂x/∂ξ||₂ is "detJ" and t is "the unit tangent". See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation.

source
Ferrite.ValuesUpdateFlagsType
ValuesUpdateFlags(ip_fun::Interpolation; update_gradients = Val(true), update_hessians = Val(false), update_detJdV = Val(true))

Creates a singelton type for specifying what parts of the AbstractValues should be updated. Note that this is internal API used to get type-stable construction. Keyword arguments in AbstractValues constructors are forwarded, and the public API is passing these as Bool, while the ValuesUpdateFlags method supports both boolean and Val(::Bool) keyword args.

source

Custom FEValues

Custom FEValues, fe_v::AbstractValues, should normally implement the reinit! method. Subtypes of AbstractValues have default implementations for some functions, but require some lower-level access functions, specifically

Array bounds

  • Asking for the nth quadrature point must be inside array bounds if 1 <= n <= getnquadpoints(fe_v). (checkquadpoint can, alternatively, be dispatched to check that n is inbounds.)
  • Asking for the ith shape value or gradient must be inside array bounds if 1 <= i <= getnbasefunctions(fe_v)
  • Asking for the ith geometric value must be inside array bounds if 1 <= i <= getngeobasefunctions(fe_v)
diff --git a/previews/PR1096/devdocs/assembly/index.html b/previews/PR1096/devdocs/assembly/index.html index 34e74a4cc1..e05d89d45a 100644 --- a/previews/PR1096/devdocs/assembly/index.html +++ b/previews/PR1096/devdocs/assembly/index.html @@ -1,4 +1,4 @@ -Assembly · Ferrite.jl

Assembly

Type definitions

Ferrite.COOAssemblerType
struct COOAssembler{Tv, Ti}

This assembler creates a COO (coordinate format) representation of a sparse matrix during assembly and converts it into a SparseMatrixCSC{Tv, Ti} on finalization.

source

Utility functions

Ferrite.matrix_handleFunction
matrix_handle(a::AbstractAssembler)
-vector_handle(a::AbstractAssembler)

Return a reference to the underlying matrix/vector of the assembler used during assembly operations.

source
Ferrite.vector_handleFunction
matrix_handle(a::AbstractAssembler)
-vector_handle(a::AbstractAssembler)

Return a reference to the underlying matrix/vector of the assembler used during assembly operations.

source
Ferrite._sortdofs_for_assembly!Function
_sortdofs_for_assembly!(permutation::Vector{Int}, sorteddofs::Vector{Int}, dofs::AbstractVector)

Sorts the dofs into a separate buffer and returns it together with a permutation vector.

source
Ferrite.sortperm2!Function
sortperm2!(data::AbstractVector, permutation::AbstractVector)

Sort the input vector inplace and compute the corresponding permutation.

source
+Assembly · Ferrite.jl

Assembly

Type definitions

Ferrite.COOAssemblerType
struct COOAssembler{Tv, Ti}

This assembler creates a COO (coordinate format) representation of a sparse matrix during assembly and converts it into a SparseMatrixCSC{Tv, Ti} on finalization.

source

Utility functions

Ferrite.matrix_handleFunction
matrix_handle(a::AbstractAssembler)
+vector_handle(a::AbstractAssembler)

Return a reference to the underlying matrix/vector of the assembler used during assembly operations.

source
Ferrite.vector_handleFunction
matrix_handle(a::AbstractAssembler)
+vector_handle(a::AbstractAssembler)

Return a reference to the underlying matrix/vector of the assembler used during assembly operations.

source
Ferrite._sortdofs_for_assembly!Function
_sortdofs_for_assembly!(permutation::Vector{Int}, sorteddofs::Vector{Int}, dofs::AbstractVector)

Sorts the dofs into a separate buffer and returns it together with a permutation vector.

source
Ferrite.sortperm2!Function
sortperm2!(data::AbstractVector, permutation::AbstractVector)

Sort the input vector inplace and compute the corresponding permutation.

source
diff --git a/previews/PR1096/devdocs/dofhandler/index.html b/previews/PR1096/devdocs/dofhandler/index.html index c7e7b3323f..49f7aeef24 100644 --- a/previews/PR1096/devdocs/dofhandler/index.html +++ b/previews/PR1096/devdocs/dofhandler/index.html @@ -1,9 +1,9 @@ -Dof Handler · Ferrite.jl

Dof Handler

Type definitions

Dof handlers are subtypes of AbstractDofhandler{sdim}, i.e. they are parametrized by the spatial dimension. Internally a helper struct InterpolationInfo is utilized to enforce type stability during dof distribution, because the interpolations are not available as concrete types.

Ferrite.InterpolationInfoType
InterpolationInfo

Gathers all the information needed to distribute dofs for a given interpolation. Note that this cache is of the same type no matter the interpolation: the purpose is to make dof-distribution type-stable.

source
Ferrite.PathOrientationInfoType
PathOrientationInfo

Orientation information for 1D entities.

The orientation for 1D entities is defined by the indices of the grid nodes associated to the vertices. To give an example, the oriented path

1 ---> 2

is called regular, indicated by regular=true, while the oriented path

2 ---> 1

is called inverted, indicated by regular=false.

source
Ferrite.SurfaceOrientationInfoType
SurfaceOrientationInfo

Orientation information for 2D entities. Such an entity can be possibly flipped (i.e. the defining vertex order is reverse to the spanning vertex order) and the vertices can be rotated against each other. Take for example the faces

1---2 2---3
+Dof Handler · Ferrite.jl

Dof Handler

Type definitions

Dof handlers are subtypes of AbstractDofhandler{sdim}, i.e. they are parametrized by the spatial dimension. Internally a helper struct InterpolationInfo is utilized to enforce type stability during dof distribution, because the interpolations are not available as concrete types.

Ferrite.InterpolationInfoType
InterpolationInfo

Gathers all the information needed to distribute dofs for a given interpolation. Note that this cache is of the same type no matter the interpolation: the purpose is to make dof-distribution type-stable.

source
Ferrite.PathOrientationInfoType
PathOrientationInfo

Orientation information for 1D entities.

The orientation for 1D entities is defined by the indices of the grid nodes associated to the vertices. To give an example, the oriented path

1 ---> 2

is called regular, indicated by regular=true, while the oriented path

2 ---> 1

is called inverted, indicated by regular=false.

source
Ferrite.SurfaceOrientationInfoType
SurfaceOrientationInfo

Orientation information for 2D entities. Such an entity can be possibly flipped (i.e. the defining vertex order is reverse to the spanning vertex order) and the vertices can be rotated against each other. Take for example the faces

1---2 2---3
 | A | | B |
 4---3 1---4

which are rotated against each other by 90° (shift index is 1) or the faces

1---2 2---1
 | A | | B |
-4---3 3---4

which are flipped against each other. Any combination of these can happen. The combination to map this local face to the defining face is encoded with this data structure via $rotate \circ flip$ where the rotation is indiced by the shift index. !!!NOTE TODO implement me.

source

Internal API

The main entry point for dof distribution is __close!.

Ferrite.__close!Function
__close!(dh::DofHandler)

Internal entry point for dof distribution.

Dofs are distributed as follows: For the DofHandler each SubDofHandler is visited in the order they were added. For each field in the SubDofHandler create dofs for the cell. This means that dofs on a particular cell will be numbered in groups for each field, so first the dofs for field 1 are distributed, then field 2, etc. For each cell dofs are first distributed on its vertices, then on the interior of edges (if applicable), then on the interior of faces (if applicable), and finally on the cell interior. The entity ordering follows the geometrical ordering found in vertices, faces and edges.

source
Ferrite.get_gridFunction
get_grid(dh::AbstractDofHandler)

Access some grid representation for the dof handler.

Note

This API function is currently not well-defined. It acts as the interface between distributed assembly and assembly on a single process, because most parts of the functionality can be handled by only acting on the locally owned cell set.

source
Ferrite.find_fieldFunction
find_field(dh::DofHandler, field_name::Symbol)::NTuple{2,Int}

Return the index of the field with name field_name in a DofHandler. The index is a NTuple{2,Int}, where the 1st entry is the index of the SubDofHandler within which the field was found and the 2nd entry is the index of the field within the SubDofHandler.

Note

Always finds the 1st occurrence of a field within DofHandler.

See also: find_field(sdh::SubDofHandler, field_name::Symbol), Ferrite._find_field(sdh::SubDofHandler, field_name::Symbol).

source
find_field(sdh::SubDofHandler, field_name::Symbol)::Int

Return the index of the field with name field_name in a SubDofHandler. Throw an error if the field is not found.

See also: find_field(dh::DofHandler, field_name::Symbol), _find_field(sdh::SubDofHandler, field_name::Symbol).

source
Ferrite._close_subdofhandler!Function
_close_subdofhandler!(dh::DofHandler{sdim}, sdh::SubDofHandler, sdh_index::Int, nextdof::Int, vertexdicts, edgedicts, facedicts) where {sdim}

Main entry point to distribute dofs for a single SubDofHandler on its subdomain.

source
Ferrite._distribute_dofs_for_cell!Function
_distribute_dofs_for_cell!(dh::DofHandler{sdim}, cell::AbstractCell, ip_info::InterpolationInfo, nextdof::Int, vertexdict, edgedict, facedict) where {sdim}

Main entry point to distribute dofs for a single cell.

source
Ferrite.permute_and_push!Function
permute_and_push!

For interpolations with more than one interior dof per edge it may be necessary to adjust the dofs. Since dofs are (initially) enumerated according to the local edge direction there can be a direction mismatch with the neighboring element. For example, in the following nodal interpolation example, with three interior dofs on each edge, the initial pass have distributed dofs 4, 5, 6 according to the local edge directions:

+-----------+
+4---3 3---4

which are flipped against each other. Any combination of these can happen. The combination to map this local face to the defining face is encoded with this data structure via $rotate \circ flip$ where the rotation is indiced by the shift index. !!!NOTE TODO implement me.

source

Internal API

The main entry point for dof distribution is __close!.

Ferrite.__close!Function
__close!(dh::DofHandler)

Internal entry point for dof distribution.

Dofs are distributed as follows: For the DofHandler each SubDofHandler is visited in the order they were added. For each field in the SubDofHandler create dofs for the cell. This means that dofs on a particular cell will be numbered in groups for each field, so first the dofs for field 1 are distributed, then field 2, etc. For each cell dofs are first distributed on its vertices, then on the interior of edges (if applicable), then on the interior of faces (if applicable), and finally on the cell interior. The entity ordering follows the geometrical ordering found in vertices, faces and edges.

source
Ferrite.get_gridFunction
get_grid(dh::AbstractDofHandler)

Access some grid representation for the dof handler.

Note

This API function is currently not well-defined. It acts as the interface between distributed assembly and assembly on a single process, because most parts of the functionality can be handled by only acting on the locally owned cell set.

source
Ferrite.find_fieldFunction
find_field(dh::DofHandler, field_name::Symbol)::NTuple{2,Int}

Return the index of the field with name field_name in a DofHandler. The index is a NTuple{2,Int}, where the 1st entry is the index of the SubDofHandler within which the field was found and the 2nd entry is the index of the field within the SubDofHandler.

Note

Always finds the 1st occurrence of a field within DofHandler.

See also: find_field(sdh::SubDofHandler, field_name::Symbol), Ferrite._find_field(sdh::SubDofHandler, field_name::Symbol).

source
find_field(sdh::SubDofHandler, field_name::Symbol)::Int

Return the index of the field with name field_name in a SubDofHandler. Throw an error if the field is not found.

See also: find_field(dh::DofHandler, field_name::Symbol), _find_field(sdh::SubDofHandler, field_name::Symbol).

source
Ferrite._close_subdofhandler!Function
_close_subdofhandler!(dh::DofHandler{sdim}, sdh::SubDofHandler, sdh_index::Int, nextdof::Int, vertexdicts, edgedicts, facedicts) where {sdim}

Main entry point to distribute dofs for a single SubDofHandler on its subdomain.

source
Ferrite._distribute_dofs_for_cell!Function
_distribute_dofs_for_cell!(dh::DofHandler{sdim}, cell::AbstractCell, ip_info::InterpolationInfo, nextdof::Int, vertexdict, edgedict, facedict) where {sdim}

Main entry point to distribute dofs for a single cell.

source
Ferrite.permute_and_push!Function
permute_and_push!

For interpolations with more than one interior dof per edge it may be necessary to adjust the dofs. Since dofs are (initially) enumerated according to the local edge direction there can be a direction mismatch with the neighboring element. For example, in the following nodal interpolation example, with three interior dofs on each edge, the initial pass have distributed dofs 4, 5, 6 according to the local edge directions:

+-----------+
 |     A     |
 +--4--5--6->+    local edge on element A
 
@@ -11,6 +11,6 @@
 
 +<-6--5--4--+    local edge on element B
 |     B     |
-+-----------+

For most scalar-valued interpolations we can simply compensate for this by reversing the numbering on all edges that do not match the global edge direction, i.e. for the edge on element B in the example.

In addition, we also have to preserve the ordering at each dof location.

For more details we refer to Scroggs et al. [13] as we follow the methodology described therein.

References

  • [13] Scroggs et al. ACM Trans. Math. Softw. 48 (2022).
source
!!!NOTE TODO implement me.

For more details we refer to [1] as we follow the methodology described therein.

[1] Scroggs, M. W., Dokken, J. S., Richardson, C. N., & Wells, G. N. (2022). Construction of arbitrary order finite element degree-of-freedom maps on polygonal and polyhedral cell meshes. ACM Transactions on Mathematical Software (TOMS), 48(2), 1-23.

!!!TODO citation via software.
++-----------+

For most scalar-valued interpolations we can simply compensate for this by reversing the numbering on all edges that do not match the global edge direction, i.e. for the edge on element B in the example.

In addition, we also have to preserve the ordering at each dof location.

For more details we refer to Scroggs et al. [13] as we follow the methodology described therein.

References

  • [13] Scroggs et al. ACM Trans. Math. Softw. 48 (2022).
source
!!!NOTE TODO implement me.

For more details we refer to [1] as we follow the methodology described therein.

[1] Scroggs, M. W., Dokken, J. S., Richardson, C. N., & Wells, G. N. (2022). Construction of arbitrary order finite element degree-of-freedom maps on polygonal and polyhedral cell meshes. ACM Transactions on Mathematical Software (TOMS), 48(2), 1-23.

!!!TODO citation via software.
 
-!!!TODO Investigate if we can somehow pass the interpolation into this function in a typestable way.
source
+!!!TODO Investigate if we can somehow pass the interpolation into this function in a typestable way.
source
diff --git a/previews/PR1096/devdocs/elements/index.html b/previews/PR1096/devdocs/elements/index.html index 974e2d1e99..1e14bbd63a 100644 --- a/previews/PR1096/devdocs/elements/index.html +++ b/previews/PR1096/devdocs/elements/index.html @@ -1,15 +1,15 @@ -Elements and cells · Ferrite.jl

Elements and cells

Type definitions

Elements or cells are subtypes of AbstractCell{<:AbstractRefShape}. As shown, they are parametrized by the associated reference element.

Required methods to implement for all subtypes of AbstractCell to define a new element

Ferrite.get_node_idsFunction
Ferrite.get_node_ids(c::AbstractCell)

Return the node id's for cell c in the order determined by the cell's reference cell.

Default implementation: c.nodes.

source

Common utilities and definitions when working with grids internally.

First we have some topological queries on the element

Ferrite.verticesMethod
Ferrite.vertices(::AbstractCell)

Returns a tuple with the node indices (of the nodes in a grid) for each vertex in a given cell. This function induces the VertexIndex, where the second index corresponds to the local index into this tuple.

source
Ferrite.edgesMethod
Ferrite.edges(::AbstractCell)

Returns a tuple of 2-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented edge. This function induces the EdgeIndex, where the second index corresponds to the local index into this tuple.

Note that the vertices are sufficient to define an edge uniquely.

source
Ferrite.facesMethod
Ferrite.faces(::AbstractCell)

Returns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented face. This function induces the FaceIndex, where the second index corresponds to the local index into this tuple.

An oriented face is a face with the first node having the local index and the other nodes spanning such that the normal to the face is pointing outwards.

Note that the vertices are sufficient to define a face uniquely.

source
Ferrite.facetsMethod
Ferrite.facets(::AbstractCell)

Returns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented facet. This function induces the FacetIndex, where the second index corresponds to the local index into this tuple.

See also vertices, edges, and faces

source
Ferrite.boundaryfunctionMethod
boundaryfunction(::Type{<:BoundaryIndex})

Helper function to dispatch on the correct entity from a given boundary index.

source
Ferrite.reference_verticesMethod
reference_vertices(::Type{<:AbstractRefShape})
-reference_vertices(::AbstractCell)

Returns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.

source
Ferrite.reference_edgesMethod
reference_edges(::Type{<:AbstractRefShape})
-reference_edges(::AbstractCell)

Returns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.

source
Ferrite.reference_facesMethod
reference_faces(::Type{<:AbstractRefShape})
-reference_faces(::AbstractCell)

Returns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.

source

and some generic utils which are commonly found in finite element codes

Ferrite.toglobalFunction
toglobal(grid::AbstractGrid, vertexidx::VertexIndex) -> Int
-toglobal(grid::AbstractGrid, vertexidx::Vector{VertexIndex}) -> Vector{Int}

This function takes the local vertex representation (a VertexIndex) and looks up the unique global id (an Int).

source
Ferrite.sortfaceFunction
sortface(face::Tuple{Int})
+Elements and cells · Ferrite.jl

Elements and cells

Type definitions

Elements or cells are subtypes of AbstractCell{<:AbstractRefShape}. As shown, they are parametrized by the associated reference element.

Required methods to implement for all subtypes of AbstractCell to define a new element

Ferrite.get_node_idsFunction
Ferrite.get_node_ids(c::AbstractCell)

Return the node id's for cell c in the order determined by the cell's reference cell.

Default implementation: c.nodes.

source

Common utilities and definitions when working with grids internally.

First we have some topological queries on the element

Ferrite.verticesMethod
Ferrite.vertices(::AbstractCell)

Returns a tuple with the node indices (of the nodes in a grid) for each vertex in a given cell. This function induces the VertexIndex, where the second index corresponds to the local index into this tuple.

source
Ferrite.edgesMethod
Ferrite.edges(::AbstractCell)

Returns a tuple of 2-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented edge. This function induces the EdgeIndex, where the second index corresponds to the local index into this tuple.

Note that the vertices are sufficient to define an edge uniquely.

source
Ferrite.facesMethod
Ferrite.faces(::AbstractCell)

Returns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented face. This function induces the FaceIndex, where the second index corresponds to the local index into this tuple.

An oriented face is a face with the first node having the local index and the other nodes spanning such that the normal to the face is pointing outwards.

Note that the vertices are sufficient to define a face uniquely.

source
Ferrite.facetsMethod
Ferrite.facets(::AbstractCell)

Returns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented facet. This function induces the FacetIndex, where the second index corresponds to the local index into this tuple.

See also vertices, edges, and faces

source
Ferrite.boundaryfunctionMethod
boundaryfunction(::Type{<:BoundaryIndex})

Helper function to dispatch on the correct entity from a given boundary index.

source
Ferrite.reference_verticesMethod
reference_vertices(::Type{<:AbstractRefShape})
+reference_vertices(::AbstractCell)

Returns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.

source
Ferrite.reference_edgesMethod
reference_edges(::Type{<:AbstractRefShape})
+reference_edges(::AbstractCell)

Returns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.

source
Ferrite.reference_facesMethod
reference_faces(::Type{<:AbstractRefShape})
+reference_faces(::AbstractCell)

Returns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.

source

and some generic utils which are commonly found in finite element codes

Ferrite.toglobalFunction
toglobal(grid::AbstractGrid, vertexidx::VertexIndex) -> Int
+toglobal(grid::AbstractGrid, vertexidx::Vector{VertexIndex}) -> Vector{Int}

This function takes the local vertex representation (a VertexIndex) and looks up the unique global id (an Int).

source
Ferrite.sortfaceFunction
sortface(face::Tuple{Int})
 sortface(face::Tuple{Int,Int})
 sortface(face::Tuple{Int,Int,Int})
-sortface(face::Tuple{Int,Int,Int,Int})

Returns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.

source
Ferrite.sortface_fastFunction
sortface_fast(face::Tuple{Int})
+sortface(face::Tuple{Int,Int,Int,Int})

Returns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.

source
Ferrite.sortface_fastFunction
sortface_fast(face::Tuple{Int})
 sortface_fast(face::Tuple{Int,Int})
 sortface_fast(face::Tuple{Int,Int,Int})
-sortface_fast(face::Tuple{Int,Int,Int,Int})

Returns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.

source
Ferrite.sortedgeFunction
sortedge(edge::Tuple{Int,Int})

Returns the unique representation of an edge and its orientation. Here the unique representation is the sorted node index tuple. The orientation is true if the edge is not flipped, where it is false if the edge is flipped.

source
Ferrite.sortedge_fastFunction

sortedge_fast(edge::Tuple{Int,Int})

Returns the unique representation of an edge. Here the unique representation is the sorted node index tuple.

source
Ferrite.element_to_facet_transformationFunction
element_to_facet_transformation(point::AbstractVector, ::Type{<:AbstractRefShape}, facet::Int)

Transform quadrature point from the cell's coordinates to the facet's reference coordinates, decreasing the number of dimensions by one. This is the inverse of facet_to_element_transformation.

source
Ferrite.facet_to_element_transformationFunction
facet_to_element_transformation(point::Vec, ::Type{<:AbstractRefShape}, facet::Int)

Transform quadrature point from the facet's reference coordinates to coordinates on the cell's facet, increasing the number of dimensions by one.

source
Ferrite.InterfaceOrientationInfoType
InterfaceOrientationInfo

Relative orientation information for 1D and 2D interfaces in 2D and 3D elements respectively. This information is used to construct the transformation matrix to transform the quadrature points from faceta to facetb achieving synced spatial coordinates. Face B's orientation relative to Face A's can possibly be flipped (i.e. the vertices indices order is reversed) and the vertices can be rotated against each other. The reference orientation of face B is such that the first node has the lowest vertex index. Thus, this structure also stores the shift of the lowest vertex index which is used to reorient the face in case of flipping transform_interface_points!.

source
Ferrite.transform_interface_points!Function
transform_interface_points!(dst::AbstractVector{Vec{3, Float64}}, points::AbstractVector{Vec{3, Float64}}, interface_transformation::InterfaceOrientationInfo)

Transform the points from face A to face B using the orientation information of the interface and store it in the vector dst. For 3D, the faces are transformed into regular polygons such that the rotation angle is the shift in reference node index × 2π ÷ number of edges in face. If the face is flipped then the flipping is about the axis that preserves the position of the first node (which is the reference node after being rotated to be in the first position, it's rotated back in the opposite direction after flipping). Take for example the interface

        2           3
+sortface_fast(face::Tuple{Int,Int,Int,Int})

Returns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.

source
Ferrite.sortedgeFunction
sortedge(edge::Tuple{Int,Int})

Returns the unique representation of an edge and its orientation. Here the unique representation is the sorted node index tuple. The orientation is true if the edge is not flipped, where it is false if the edge is flipped.

source
Ferrite.sortedge_fastFunction

sortedge_fast(edge::Tuple{Int,Int})

Returns the unique representation of an edge. Here the unique representation is the sorted node index tuple.

source
Ferrite.element_to_facet_transformationFunction
element_to_facet_transformation(point::AbstractVector, ::Type{<:AbstractRefShape}, facet::Int)

Transform quadrature point from the cell's coordinates to the facet's reference coordinates, decreasing the number of dimensions by one. This is the inverse of facet_to_element_transformation.

source
Ferrite.facet_to_element_transformationFunction
facet_to_element_transformation(point::Vec, ::Type{<:AbstractRefShape}, facet::Int)

Transform quadrature point from the facet's reference coordinates to coordinates on the cell's facet, increasing the number of dimensions by one.

source
Ferrite.InterfaceOrientationInfoType
InterfaceOrientationInfo

Relative orientation information for 1D and 2D interfaces in 2D and 3D elements respectively. This information is used to construct the transformation matrix to transform the quadrature points from faceta to facetb achieving synced spatial coordinates. Face B's orientation relative to Face A's can possibly be flipped (i.e. the vertices indices order is reversed) and the vertices can be rotated against each other. The reference orientation of face B is such that the first node has the lowest vertex index. Thus, this structure also stores the shift of the lowest vertex index which is used to reorient the face in case of flipping transform_interface_points!.

source
Ferrite.transform_interface_points!Function
transform_interface_points!(dst::AbstractVector{Vec{3, Float64}}, points::AbstractVector{Vec{3, Float64}}, interface_transformation::InterfaceOrientationInfo)

Transform the points from face A to face B using the orientation information of the interface and store it in the vector dst. For 3D, the faces are transformed into regular polygons such that the rotation angle is the shift in reference node index × 2π ÷ number of edges in face. If the face is flipped then the flipping is about the axis that preserves the position of the first node (which is the reference node after being rotated to be in the first position, it's rotated back in the opposite direction after flipping). Take for example the interface

        2           3
         | \         | \
         |  \        |  \
 y       | A \       | B \
@@ -43,4 +43,4 @@
        |  \
 y      |   \
 ↑      |    \
-→ x    1-----2
source
Ferrite.get_transformation_matrixFunction
get_transformation_matrix(interface_transformation::InterfaceOrientationInfo)

Returns the transformation matrix corresponding to the interface orientation information stored in InterfaceOrientationInfo. The transformation matrix is constructed using a combination of affine transformations defined for each interface reference shape. The transformation for a flipped face is a function of both relative orientation and the orientation of the second face. If the face is not flipped then the transformation is a function of relative orientation only.

source
+→ x 1-----2
source
Ferrite.get_transformation_matrixFunction
get_transformation_matrix(interface_transformation::InterfaceOrientationInfo)

Returns the transformation matrix corresponding to the interface orientation information stored in InterfaceOrientationInfo. The transformation matrix is constructed using a combination of affine transformations defined for each interface reference shape. The transformation for a flipped face is a function of both relative orientation and the orientation of the second face. If the face is not flipped then the transformation is a function of relative orientation only.

source
diff --git a/previews/PR1096/devdocs/index.html b/previews/PR1096/devdocs/index.html index 430f9ce5cc..91f662480c 100644 --- a/previews/PR1096/devdocs/index.html +++ b/previews/PR1096/devdocs/index.html @@ -1,2 +1,2 @@ -Developer documentation · Ferrite.jl
+Developer documentation · Ferrite.jl
diff --git a/previews/PR1096/devdocs/interpolations/index.html b/previews/PR1096/devdocs/interpolations/index.html index 67c18d7ba8..ef0dc63713 100644 --- a/previews/PR1096/devdocs/interpolations/index.html +++ b/previews/PR1096/devdocs/interpolations/index.html @@ -1,5 +1,5 @@ -Interpolations · Ferrite.jl

Interpolations

Type definitions

Interpolations are subtypes of Interpolation{shape, order}, i.e. they are parametrized by the reference element and its characteristic order.

Fallback methods applicable for all subtypes of Interpolation

Ferrite.getrefshapeMethod
Ferrite.getrefshape(::Interpolation)::AbstractRefShape

Return the reference element shape of the interpolation.

source
Ferrite.reference_shape_gradientMethod
reference_shape_gradient(ip::Interpolation, ξ::Vec, i::Int)

Evaluate the gradient of the ith shape function of the interpolation ip in reference coordinate ξ.

source
Ferrite.boundarydof_indicesFunction
boundarydof_indices(::Type{<:BoundaryIndex})

Helper function to generically dispatch on the correct dof sets of a boundary entity.

source
Ferrite.reference_shape_values!Function
reference_shape_values!(values::AbstractArray{T}, ip::Interpolation, ξ::Vec)

Evaluate all shape functions of ip at once at the reference point ξ and store them in values.

source
Ferrite.reference_shape_gradients!Function
reference_shape_gradients!(gradients::AbstractArray, ip::Interpolation, ξ::Vec)

Evaluate all shape function gradients of ip at once at the reference point ξ and store them in gradients.

source
Ferrite.reference_shape_gradients_and_values!Function
reference_shape_gradients_and_values!(gradients::AbstractArray, values::AbstractArray, ip::Interpolation, ξ::Vec)

Evaluate all shape function gradients and values of ip at once at the reference point ξ and store them in values.

source
Ferrite.reference_shape_hessians_gradients_and_values!Function
reference_shape_hessians_gradients_and_values!(hessians::AbstractVector, gradients::AbstractVector, values::AbstractVector, ip::Interpolation, ξ::Vec)

Evaluate all shape function hessians, gradients and values of ip at once at the reference point ξ and store them in hessians, gradients, and values.

source

Required methods to implement for all subtypes of Interpolation to define a new finite element

Depending on the dimension of the reference element the following functions have to be implemented

Ferrite.vertexdof_indicesMethod
vertexdof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.

source
Ferrite.dirichlet_vertexdof_indicesMethod
dirichlet_vertexdof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to vertexdof_indices(ip::Interpolation) for continuous interpolation.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.

source
Ferrite.facedof_indicesMethod
facedof_indices(ip::Interpolation)

A tuple containing tuples of all local dof indices for the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell.

source
Ferrite.facedof_interior_indicesMethod
facedof_interior_indices(ip::Interpolation)

A tuple containing tuples of the local dof indices on the interior of the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell. Note that the vertex and edge dofs are included here.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be the computed via "last edge interior dof index + 1", if face dofs exist.

source
Ferrite.edgedof_indicesMethod
edgedof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell.

The dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.

source
Ferrite.dirichlet_edgedof_indicesMethod
dirichlet_edgedof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to edgedof_indices(ip::Interpolation) for continuous interpolation.

The dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.

source
Ferrite.edgedof_interior_indicesMethod
edgedof_interior_indices(ip::Interpolation)

A tuple containing tuples of the local dof indices on the interior of the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Note that the vertex dofs are included here.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be computed via "last vertex dof index + 1", if edge dofs exist.

source
Ferrite.volumedof_interior_indicesMethod
volumedof_interior_indices(ip::Interpolation)

Tuple containing the dof indices associated with the interior of a volume.

Note

The dofs appearing in the tuple must be continuous and increasing, volumedofs are enumerated last.

source
Ferrite.reference_coordinatesMethod
reference_coordinates(ip::Interpolation)

Returns a vector of coordinates with length getnbasefunctions(::Interpolation) and indices corresponding to the indices of a dof in vertices, faces and edges.

Only required for nodal interpolations.
+Interpolations · Ferrite.jl

Interpolations

Type definitions

Interpolations are subtypes of Interpolation{shape, order}, i.e. they are parametrized by the reference element and its characteristic order.

Fallback methods applicable for all subtypes of Interpolation

Ferrite.getrefshapeMethod
Ferrite.getrefshape(::Interpolation)::AbstractRefShape

Return the reference element shape of the interpolation.

source
Ferrite.reference_shape_gradientMethod
reference_shape_gradient(ip::Interpolation, ξ::Vec, i::Int)

Evaluate the gradient of the ith shape function of the interpolation ip in reference coordinate ξ.

source
Ferrite.boundarydof_indicesFunction
boundarydof_indices(::Type{<:BoundaryIndex})

Helper function to generically dispatch on the correct dof sets of a boundary entity.

source
Ferrite.reference_shape_values!Function
reference_shape_values!(values::AbstractArray{T}, ip::Interpolation, ξ::Vec)

Evaluate all shape functions of ip at once at the reference point ξ and store them in values.

source
Ferrite.reference_shape_gradients!Function
reference_shape_gradients!(gradients::AbstractArray, ip::Interpolation, ξ::Vec)

Evaluate all shape function gradients of ip at once at the reference point ξ and store them in gradients.

source
Ferrite.reference_shape_gradients_and_values!Function
reference_shape_gradients_and_values!(gradients::AbstractArray, values::AbstractArray, ip::Interpolation, ξ::Vec)

Evaluate all shape function gradients and values of ip at once at the reference point ξ and store them in values.

source
Ferrite.reference_shape_hessians_gradients_and_values!Function
reference_shape_hessians_gradients_and_values!(hessians::AbstractVector, gradients::AbstractVector, values::AbstractVector, ip::Interpolation, ξ::Vec)

Evaluate all shape function hessians, gradients and values of ip at once at the reference point ξ and store them in hessians, gradients, and values.

source

Required methods to implement for all subtypes of Interpolation to define a new finite element

Depending on the dimension of the reference element the following functions have to be implemented

Ferrite.vertexdof_indicesMethod
vertexdof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.

source
Ferrite.dirichlet_vertexdof_indicesMethod
dirichlet_vertexdof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to vertexdof_indices(ip::Interpolation) for continuous interpolation.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.

source
Ferrite.facedof_indicesMethod
facedof_indices(ip::Interpolation)

A tuple containing tuples of all local dof indices for the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell.

source
Ferrite.facedof_interior_indicesMethod
facedof_interior_indices(ip::Interpolation)

A tuple containing tuples of the local dof indices on the interior of the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell. Note that the vertex and edge dofs are included here.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be the computed via "last edge interior dof index + 1", if face dofs exist.

source
Ferrite.edgedof_indicesMethod
edgedof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell.

The dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.

source
Ferrite.dirichlet_edgedof_indicesMethod
dirichlet_edgedof_indices(ip::Interpolation)

A tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to edgedof_indices(ip::Interpolation) for continuous interpolation.

The dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.

source
Ferrite.edgedof_interior_indicesMethod
edgedof_interior_indices(ip::Interpolation)

A tuple containing tuples of the local dof indices on the interior of the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Note that the vertex dofs are included here.

Note

The dofs appearing in the tuple must be continuous and increasing! The first dof must be computed via "last vertex dof index + 1", if edge dofs exist.

source
Ferrite.volumedof_interior_indicesMethod
volumedof_interior_indices(ip::Interpolation)

Tuple containing the dof indices associated with the interior of a volume.

Note

The dofs appearing in the tuple must be continuous and increasing, volumedofs are enumerated last.

source
Ferrite.is_discontinuousMethod
is_discontinuous(::Interpolation)
-is_discontinuous(::Type{<:Interpolation})

Checks whether the interpolation is discontinuous (i.e. DiscontinuousLagrange)

source
Ferrite.adjust_dofs_during_distributionMethod
adjust_dofs_during_distribution(::Interpolation)

This function must return true if the dofs should be adjusted (i.e. permuted) during dof distribution. This is in contrast to i) adjusting the dofs during reinit! in the assembly loop, or ii) not adjusting at all (which is not needed for low order interpolations, generally).

source
Ferrite.mapping_typeFunction
mapping_type(ip::Interpolation)

Get the type of mapping from the reference cell to the real cell for an interpolation ip. Subtypes of ScalarInterpolation and VectorizedInterpolation return IdentityMapping(), but other non-scalar interpolations may request different mapping types.

source

for all entities which exist on that reference element. The dof functions default to having no dofs defined on a specific entity. Hence, not overloading of the dof functions will result in an element with zero dofs. Also, it should always be double checked that everything is consistent as specified in the docstring of the corresponding function, as inconsistent implementations can lead to bugs which are really difficult to track down.

+TODO: Separate nodal and non-nodal interpolations.
source
Ferrite.is_discontinuousMethod
is_discontinuous(::Interpolation)
+is_discontinuous(::Type{<:Interpolation})

Checks whether the interpolation is discontinuous (i.e. DiscontinuousLagrange)

source
Ferrite.adjust_dofs_during_distributionMethod
adjust_dofs_during_distribution(::Interpolation)

This function must return true if the dofs should be adjusted (i.e. permuted) during dof distribution. This is in contrast to i) adjusting the dofs during reinit! in the assembly loop, or ii) not adjusting at all (which is not needed for low order interpolations, generally).

source
Ferrite.mapping_typeFunction
mapping_type(ip::Interpolation)

Get the type of mapping from the reference cell to the real cell for an interpolation ip. Subtypes of ScalarInterpolation and VectorizedInterpolation return IdentityMapping(), but other non-scalar interpolations may request different mapping types.

source

for all entities which exist on that reference element. The dof functions default to having no dofs defined on a specific entity. Hence, not overloading of the dof functions will result in an element with zero dofs. Also, it should always be double checked that everything is consistent as specified in the docstring of the corresponding function, as inconsistent implementations can lead to bugs which are really difficult to track down.

diff --git a/previews/PR1096/devdocs/performance/index.html b/previews/PR1096/devdocs/performance/index.html index 1efd103368..ddaaea8395 100644 --- a/previews/PR1096/devdocs/performance/index.html +++ b/previews/PR1096/devdocs/performance/index.html @@ -1,2 +1,2 @@ -Performance analysis · Ferrite.jl

Performance analysis

In the benchmark folder we provide basic infrastructure to analyze the performance of Ferrite to help tracking down performance regression issues. Two basic tools can be directly executed via make: A basic benchmark for the current branch and a comparison between two commits. To execute the benchmark on the current branch only open a shell in the benchmark folder and call

make benchmark

whereas for the comparison of two commits simply call

make compare target=<target-commit> baseline=<baseline-commit>

If you have a custom julia executable that is not accessible via the julia command, then you can pass the executable via

JULIA_CMD=<path-to-julia-executable> make compare target=<target-commit> baseline=<baseline-commit>
Note

For the performance comparison between two commits you must not have any uncommitted or untracked files in your Ferrite.jl folder! Otherwise the PkgBenchmark.jl will fail to setup the comparison.

For more fine grained control you can run subsets of the benchmarks via by appending -<subset> to compare or benchmark, e.g.

make benchmark-mesh

to benchmark only the mesh functionality. The following subsets are currently available:

  • assembly
  • boundary-conditions
  • dofs
  • mesh
Note

It is recommended to run all benchmarks before running subsets to get the correct tuning parameters for each benchmark.

+Performance analysis · Ferrite.jl

Performance analysis

In the benchmark folder we provide basic infrastructure to analyze the performance of Ferrite to help tracking down performance regression issues. Two basic tools can be directly executed via make: A basic benchmark for the current branch and a comparison between two commits. To execute the benchmark on the current branch only open a shell in the benchmark folder and call

make benchmark

whereas for the comparison of two commits simply call

make compare target=<target-commit> baseline=<baseline-commit>

If you have a custom julia executable that is not accessible via the julia command, then you can pass the executable via

JULIA_CMD=<path-to-julia-executable> make compare target=<target-commit> baseline=<baseline-commit>
Note

For the performance comparison between two commits you must not have any uncommitted or untracked files in your Ferrite.jl folder! Otherwise the PkgBenchmark.jl will fail to setup the comparison.

For more fine grained control you can run subsets of the benchmarks via by appending -<subset> to compare or benchmark, e.g.

make benchmark-mesh

to benchmark only the mesh functionality. The following subsets are currently available:

  • assembly
  • boundary-conditions
  • dofs
  • mesh
Note

It is recommended to run all benchmarks before running subsets to get the correct tuning parameters for each benchmark.

diff --git a/previews/PR1096/devdocs/reference_cells/index.html b/previews/PR1096/devdocs/reference_cells/index.html index 83004d0d8d..21d26d045e 100644 --- a/previews/PR1096/devdocs/reference_cells/index.html +++ b/previews/PR1096/devdocs/reference_cells/index.html @@ -1,5 +1,5 @@ -Reference cells · Ferrite.jl

Reference cells

The reference cells are used to i) define grid cells, ii) define shape functions, and iii) define quadrature rules. The numbering of vertices, edges, faces are visualized below. See also FerriteViz.elementinfo.

AbstractRefShape subtypes

Ferrite.RefLineType
RefLine <: AbstractRefShape{1}

Reference line/interval, reference dimension 1.

----------------+--------------------
+Reference cells · Ferrite.jl

Reference cells

The reference cells are used to i) define grid cells, ii) define shape functions, and iii) define quadrature rules. The numbering of vertices, edges, faces are visualized below. See also FerriteViz.elementinfo.

AbstractRefShape subtypes

Ferrite.RefLineType
RefLine <: AbstractRefShape{1}

Reference line/interval, reference dimension 1.

----------------+--------------------
 Vertex numbers: | Vertex coordinates:
   1-------2     | v1: 𝛏 = (-1.0,)
     --> ξ₁      | v2: 𝛏 = ( 1.0,)
@@ -7,7 +7,7 @@
 Face numbers:   | Face identifiers:
   1-------2     | f1: (v1,)
                 | f2: (v2,)
-----------------+--------------------
source
Ferrite.RefTriangleType
RefTriangle <: AbstractRefShape{2}

Reference triangle, reference dimension 2.

----------------+--------------------
+----------------+--------------------
source
Ferrite.RefTriangleType
RefTriangle <: AbstractRefShape{2}

Reference triangle, reference dimension 2.

----------------+--------------------
 Vertex numbers: | Vertex coordinates:
     2           |
     | \         | v1: 𝛏 = (1.0, 0.0)
@@ -22,7 +22,7 @@
     2   1       | f2: (v2, v3)
     |     \     | f3: (v3, v1)
     +---3---+   |
-----------------+--------------------
source
Ferrite.RefQuadrilateralType
RefQuadrilateral <: AbstractRefShape{2}

Reference quadrilateral, reference dimension 2.

----------------+---------------------
+----------------+--------------------
source
Ferrite.RefQuadrilateralType
RefQuadrilateral <: AbstractRefShape{2}

Reference quadrilateral, reference dimension 2.

----------------+---------------------
 Vertex numbers: | Vertex coordinates:
     4-------3   |
     |       |   | v1: 𝛏 = (-1.0, -1.0)
@@ -37,7 +37,7 @@
     4       2   | f3: (v3, v4)
     |       |   | f4: (v4, v1)
     +---1---+   |
-----------------+---------------------
source
Ferrite.RefTetrahedronType
RefTetrahedron <: AbstractRefShape{3}

Reference tetrahedron, reference dimension 3.

---------------------------------------+-------------------------
+----------------+---------------------
source
Ferrite.RefTetrahedronType
RefTetrahedron <: AbstractRefShape{3}

Reference tetrahedron, reference dimension 3.

---------------------------------------+-------------------------
 Vertex numbers:                        | Vertex coordinates:
              4                4        |
   ^ ξ₃      /  \             /| \      |  v1: 𝛏 = (0.0, 0.0, 0.0)
@@ -61,7 +61,7 @@
           /   3    \       /2 +___  \  | f3: (v2, v3, v4)
          /      __--+     / /  1 __‾-+ | f4: (v1, v4, v3)
         + __--‾‾         +/__--‾‾      |
----------------------------------------+-------------------------
source
Ferrite.RefHexahedronType
RefHexahedron <: AbstractRefShape{3}

Reference hexahedron, reference dimension 3.

-----------------------------------------+----------------------------
+---------------------------------------+-------------------------
source
Ferrite.RefHexahedronType
RefHexahedron <: AbstractRefShape{3}

Reference hexahedron, reference dimension 3.

-----------------------------------------+----------------------------
 Vertex numbers:                          | Vertex coordinates:
             5--------8        5--------8 | v1: 𝛏 = (-1.0, -1.0, -1.0)
            /        /|       /|        | | v2: 𝛏 = ( 1.0, -1.0, -1.0)
@@ -91,7 +91,7 @@
          |    3   | /      | /        /  |  f5: (v1, v5, v8, v4)
          |        |/       |/    1   /   |  f6: (v5, v6, v7, v8)
          +--------+        +--------+    |
------------------------------------------+-----------------------------
source
Ferrite.RefPrismType
RefPrism <: AbstractRefShape{3}

Reference prism, reference dimension 3.

-----------------------------------------+----------------------------
+-----------------------------------------+-----------------------------
source
Ferrite.RefPrismType
RefPrism <: AbstractRefShape{3}

Reference prism, reference dimension 3.

-----------------------------------------+----------------------------
 Vertex numbers:                          | Vertex coordinates:
             4-------/6       4--------6  |
            /     /   |      /|        |  |  v1: 𝛏 = (0.0, 0.0, 0.0)
@@ -121,8 +121,8 @@
          |       /        | /  1  /      | f5: (v4, v5, v6)
          |    /           |/   /         |
          + /              + /            |
------------------------------------------+----------------------------
source

Required methods to implement for all subtypes of AbstractRefShape to define a new reference shape

Ferrite.reference_verticesMethod
reference_vertices(::Type{<:AbstractRefShape})
-reference_vertices(::AbstractCell)

Returns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.

source
Ferrite.reference_edgesMethod
reference_edges(::Type{<:AbstractRefShape})
-reference_edges(::AbstractCell)

Returns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.

source
Ferrite.reference_facesMethod
reference_faces(::Type{<:AbstractRefShape})
-reference_faces(::AbstractCell)

Returns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.

source

which automatically defines

Applicable methods to AbstractRefShapes

Ferrite.getrefdimMethod
Ferrite.getrefdim(RefShape::Type{<:AbstractRefShape})

Get the dimension of the reference shape

source
+-----------------------------------------+----------------------------
source

Required methods to implement for all subtypes of AbstractRefShape to define a new reference shape

Ferrite.reference_verticesMethod
reference_vertices(::Type{<:AbstractRefShape})
+reference_vertices(::AbstractCell)

Returns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.

source
Ferrite.reference_edgesMethod
reference_edges(::Type{<:AbstractRefShape})
+reference_edges(::AbstractCell)

Returns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.

source
Ferrite.reference_facesMethod
reference_faces(::Type{<:AbstractRefShape})
+reference_faces(::AbstractCell)

Returns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.

source

which automatically defines

Applicable methods to AbstractRefShapes

Ferrite.getrefdimMethod
Ferrite.getrefdim(RefShape::Type{<:AbstractRefShape})

Get the dimension of the reference shape

source
diff --git a/previews/PR1096/devdocs/special_datastructures/index.html b/previews/PR1096/devdocs/special_datastructures/index.html index 115fef8305..62de880f68 100644 --- a/previews/PR1096/devdocs/special_datastructures/index.html +++ b/previews/PR1096/devdocs/special_datastructures/index.html @@ -3,4 +3,4 @@ for (ind, val) in some_data push_at_index!(buffer, val, ind) end -end

sizehint tells how much space to allocate for the index ind if no val has been added to that index before, or how much more space to allocate in case all previously allocated space for ind has been used up.

source
ArrayOfVectorViews(b::CollectionsOfViews.ConstructionBuffer)

Creates the ArrayOfVectorViews directly from the ConstructionBuffer that was manually created and filled.

source
ArrayOfVectorViews(indices::Vector{Int}, data::Vector{T}, lin_idx::LinearIndices{N}; checkargs = true)

Creates the ArrayOfVectorViews directly where the user is responsible for having the correct input data. Checking of the argument dimensions can be elided by setting checkargs = false, but incorrect dimensions may lead to illegal out of bounds access later.

data is indexed by indices[i]:indices[i+1], where i = lin_idx[idx...] and idx... are the user-provided indices to the ArrayOfVectorViews.

source
Ferrite.CollectionsOfViews.ConstructionBufferType
ConstructionBuffer(data::Vector, dims::NTuple{N, Int}, sizehint)

Create a buffer for creating an ArrayOfVectorViews, representing an array with N axes. sizehint sets the number of elements in data allocated when a new index is added via push_at_index!, or when the current storage for the index is full, how much many additional elements are reserved for that index. Any content in data is overwritten, but performance is improved by pre-allocating it to a reasonable size or by sizehint!ing it.

source
Ferrite.CollectionsOfViews.push_at_index!Function
push_at_index!(b::ConstructionBuffer, val, indices::Int...)

push! the value val to the Vector view at the index given by indices, typically called inside the ArrayOfVectorViews constructor do-block. But can also be used when manually creating a ConstructionBuffer.

source
+end

sizehint tells how much space to allocate for the index ind if no val has been added to that index before, or how much more space to allocate in case all previously allocated space for ind has been used up.

source
ArrayOfVectorViews(b::CollectionsOfViews.ConstructionBuffer)

Creates the ArrayOfVectorViews directly from the ConstructionBuffer that was manually created and filled.

source
ArrayOfVectorViews(indices::Vector{Int}, data::Vector{T}, lin_idx::LinearIndices{N}; checkargs = true)

Creates the ArrayOfVectorViews directly where the user is responsible for having the correct input data. Checking of the argument dimensions can be elided by setting checkargs = false, but incorrect dimensions may lead to illegal out of bounds access later.

data is indexed by indices[i]:indices[i+1], where i = lin_idx[idx...] and idx... are the user-provided indices to the ArrayOfVectorViews.

source
Ferrite.CollectionsOfViews.ConstructionBufferType
ConstructionBuffer(data::Vector, dims::NTuple{N, Int}, sizehint)

Create a buffer for creating an ArrayOfVectorViews, representing an array with N axes. sizehint sets the number of elements in data allocated when a new index is added via push_at_index!, or when the current storage for the index is full, how much many additional elements are reserved for that index. Any content in data is overwritten, but performance is improved by pre-allocating it to a reasonable size or by sizehint!ing it.

source
Ferrite.CollectionsOfViews.push_at_index!Function
push_at_index!(b::ConstructionBuffer, val, indices::Int...)

push! the value val to the Vector view at the index given by indices, typically called inside the ArrayOfVectorViews constructor do-block. But can also be used when manually creating a ConstructionBuffer.

source
diff --git a/previews/PR1096/gallery/helmholtz.ipynb b/previews/PR1096/gallery/helmholtz.ipynb index 3f3c262eb3..f58c1ef2b0 100644 --- a/previews/PR1096/gallery/helmholtz.ipynb +++ b/previews/PR1096/gallery/helmholtz.ipynb @@ -153,8 +153,7 @@ "K = allocate_matrix(dh);\n", "\n", "function doassemble(\n", - " cellvalues::CellValues, facetvalues::FacetValues,\n", - " K::SparseMatrixCSC, dh::DofHandler\n", + " cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler\n", " )\n", " b = 1.0\n", " f = zeros(ndofs(dh))\n", diff --git a/previews/PR1096/gallery/helmholtz.jl b/previews/PR1096/gallery/helmholtz.jl index 2b0de2c20d..b2d4e31d1f 100644 --- a/previews/PR1096/gallery/helmholtz.jl +++ b/previews/PR1096/gallery/helmholtz.jl @@ -42,8 +42,7 @@ update!(dbcs, 0.0) K = allocate_matrix(dh); function doassemble( - cellvalues::CellValues, facetvalues::FacetValues, - K::SparseMatrixCSC, dh::DofHandler + cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler ) b = 1.0 f = zeros(ndofs(dh)) diff --git a/previews/PR1096/gallery/helmholtz/index.html b/previews/PR1096/gallery/helmholtz/index.html index 580112efae..3c83d0c689 100644 --- a/previews/PR1096/gallery/helmholtz/index.html +++ b/previews/PR1096/gallery/helmholtz/index.html @@ -47,8 +47,7 @@ K = allocate_matrix(dh); function doassemble( - cellvalues::CellValues, facetvalues::FacetValues, - K::SparseMatrixCSC, dh::DofHandler + cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler ) b = 1.0 f = zeros(ndofs(dh)) @@ -109,4 +108,4 @@ vtk = VTKGridFile("helmholtz", dh) write_solution(vtk, dh, u) close(vtk) -println("Helmholtz successful")
Helmholtz successful

This page was generated using Literate.jl.

+println("Helmholtz successful")
Helmholtz successful

This page was generated using Literate.jl.

diff --git a/previews/PR1096/gallery/index.html b/previews/PR1096/gallery/index.html index 3763506400..7afb066c6d 100644 --- a/previews/PR1096/gallery/index.html +++ b/previews/PR1096/gallery/index.html @@ -1,2 +1,2 @@ -Code gallery · Ferrite.jl

Code gallery

This page gives an overview of the code gallery. Compared to the tutorials, these programs do not focus on teaching Ferrite, but rather focus on showing how Ferrite can be used "in the wild".

Contribute to the gallery!

Most of the gallery is user contributed. If you use Ferrite, and have something you want to share, please contribute to the gallery! This could, for example, be your research code for a published paper, some interesting application, or just some nice trick.


Helmholtz equation

Solves the Helmholtz equation on the unit square using a combination of Dirichlet and Neumann boundary conditions and the method of manufactured solutions.

Contributed by: Kristoffer Carlsson (@KristofferC).


Nearly incompressible hyperelasticity

This program combines the ideas from Tutorial 3: Incompressible elasticity and Tutorial 4: Hyperelasticity to construct a mixed element solving three-dimensional displacement-pressure equations.

Contributed by: Bhavesh Shrimali (@bhaveshshrimali).


Ginzburg-Landau model energy minimization

A basic Ginzburg-Landau model is solved. ForwardDiff.jl is used to compute the gradient and hessian of the energy function. Multi-threading is used to parallelize the assembly procedure.

Contributed by: Louis Ponet (@louisponet).


Topology optimization

Topology optimization is shown for the bending problem by using a SIMP material model. To avoid numerical instabilities, a regularization scheme requiring the calculation of the Laplacian is imposed, which is done by using the grid topology functionalities.

Contributed by: Mischa Blaszczyk (@blaszm).

+Code gallery · Ferrite.jl

Code gallery

This page gives an overview of the code gallery. Compared to the tutorials, these programs do not focus on teaching Ferrite, but rather focus on showing how Ferrite can be used "in the wild".

Contribute to the gallery!

Most of the gallery is user contributed. If you use Ferrite, and have something you want to share, please contribute to the gallery! This could, for example, be your research code for a published paper, some interesting application, or just some nice trick.


Helmholtz equation

Solves the Helmholtz equation on the unit square using a combination of Dirichlet and Neumann boundary conditions and the method of manufactured solutions.

Contributed by: Kristoffer Carlsson (@KristofferC).


Nearly incompressible hyperelasticity

This program combines the ideas from Tutorial 3: Incompressible elasticity and Tutorial 4: Hyperelasticity to construct a mixed element solving three-dimensional displacement-pressure equations.

Contributed by: Bhavesh Shrimali (@bhaveshshrimali).


Ginzburg-Landau model energy minimization

A basic Ginzburg-Landau model is solved. ForwardDiff.jl is used to compute the gradient and hessian of the energy function. Multi-threading is used to parallelize the assembly procedure.

Contributed by: Louis Ponet (@louisponet).


Topology optimization

Topology optimization is shown for the bending problem by using a SIMP material model. To avoid numerical instabilities, a regularization scheme requiring the calculation of the Laplacian is imposed, which is done by using the grid topology functionalities.

Contributed by: Mischa Blaszczyk (@blaszm).

diff --git a/previews/PR1096/gallery/landau.ipynb b/previews/PR1096/gallery/landau.ipynb index 9fffa52f45..b8903dd970 100644 --- a/previews/PR1096/gallery/landau.ipynb +++ b/previews/PR1096/gallery/landau.ipynb @@ -96,10 +96,8 @@ "source": [ "function Fl(P::Vec{3, T}, α::Vec{3}) where {T}\n", " P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2))\n", - " return (\n", - " α[1] * sum(P2) +\n", - " α[2] * (P[1]^4 + P[2]^4 + P[3]^4)\n", - " ) +\n", + " return α[1] * sum(P2) +\n", + " α[2] * (P[1]^4 + P[2]^4 + P[3]^4) +\n", " α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3])\n", "end" ], @@ -305,9 +303,10 @@ "cell_type": "code", "source": [ "function save_landau(path, model, dofs = model.dofs)\n", - " return VTKGridFile(path, model.dofhandler) do vtk\n", + " VTKGridFile(path, model.dofhandler) do vtk\n", " write_solution(vtk, model.dofhandler, dofs)\n", " end\n", + " return\n", "end" ], "metadata": {}, @@ -416,10 +415,11 @@ "source": [ "function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n", " fill!(∇f, zero(T))\n", - " return @assemble! begin\n", + " @assemble! begin\n", " ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)\n", " @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient)\n", " end\n", + " return\n", "end" ], "metadata": {}, @@ -447,10 +447,11 @@ "source": [ "function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n", " assemblers = [start_assemble(∇²f) for t in 1:nthreads()]\n", - " return @assemble! begin\n", + " @assemble! begin\n", " ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)\n", " @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian)\n", " end\n", + " return\n", "end" ], "metadata": {}, @@ -525,7 +526,7 @@ " end\n", " function h!(storage, x)\n", " return ∇²F!(storage, x, model)\n", - " #apply!(storage, model.boundaryconds)\n", + " # apply!(storage, model.boundaryconds)\n", " end\n", " f(x) = F(x, model)\n", "\n", @@ -595,41 +596,33 @@ "text": [ "Iter Function value Gradient norm \n", " 0 2.127588e+06 3.597094e+02\n", - " * time: 0.028549909591674805\n", + " * time: 0.028679847717285156\n", " 1 3.786155e+05 1.047687e+02\n", - " * time: 3.7921929359436035\n", + " * time: 3.832444906234741\n", " 2 5.306125e+04 2.978953e+01\n", - " * time: 6.764747858047485\n", + " * time: 6.87027382850647\n", " 3 -2.642320e+03 7.943136e+00\n", - " * time: 9.68120789527893\n", + " * time: 9.848543882369995\n", " 4 -1.027484e+04 1.752693e+00\n", - " * time: 12.6286039352417\n", + " * time: 12.847023963928223\n", " 5 -1.084925e+04 2.157295e-01\n", - " * time: 15.589504957199097\n", + " * time: 15.840385913848877\n", " 6 -1.085880e+04 5.288877e-03\n", - " * time: 18.5224130153656\n", + " * time: 18.81114888191223\n", " 7 -1.085881e+04 3.478176e-06\n", - " * time: 21.469243049621582\n", + " * time: 21.77446484565735\n", " 8 -1.085881e+04 1.596091e-12\n", - " * time: 24.414417028427124\n", + " * time: 24.724785804748535\n", " 9 -1.085881e+04 1.444882e-12\n", - " * time: 27.39769983291626\n", + " * time: 27.709916830062866\n", " 10 -1.085881e+04 1.548831e-13\n", - " * time: 30.331682920455933\n", + " * time: 30.68674087524414\n", " 11 -1.085881e+04 1.548831e-13\n", - " * time: 33.36940789222717\n", + " * time: 33.767337799072266\n", " 12 -1.085881e+04 1.508030e-13\n", - " * time: 33.81774592399597\n", - " 40.960759 seconds (14.85 M allocations: 4.057 GiB, 1.27% gc time, 12.82% compilation time)\n" + " * time: 34.225353956222534\n", + " 41.541840 seconds (14.84 M allocations: 4.057 GiB, 1.28% gc time, 12.96% compilation time)\n" ] - }, - { - "output_type": "execute_result", - "data": { - "text/plain": "VTKGridFile for the closed file \"landaufinal.vtu\"." - }, - "metadata": {}, - "execution_count": 16 } ], "cell_type": "code", diff --git a/previews/PR1096/gallery/landau.jl b/previews/PR1096/gallery/landau.jl index fcfade641c..7ffc65c2e9 100644 --- a/previews/PR1096/gallery/landau.jl +++ b/previews/PR1096/gallery/landau.jl @@ -8,10 +8,8 @@ using Base.Threads function Fl(P::Vec{3, T}, α::Vec{3}) where {T} P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2)) - return ( - α[1] * sum(P2) + - α[2] * (P[1]^4 + P[2]^4 + P[3]^4) - ) + + return α[1] * sum(P2) + + α[2] * (P[1]^4 + P[2]^4 + P[3]^4) + α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3]) end @@ -86,9 +84,10 @@ function LandauModel(α, G, gridsize, left::Vec{DIM, T}, right::Vec{DIM, T}, elp end function save_landau(path, model, dofs = model.dofs) - return VTKGridFile(path, model.dofhandler) do vtk + VTKGridFile(path, model.dofhandler) do vtk write_solution(vtk, model.dofhandler, dofs) end + return end macro assemble!(innerbody) @@ -126,18 +125,20 @@ end function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T} fill!(∇f, zero(T)) - return @assemble! begin + @assemble! begin ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf) @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient) end + return end function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T} assemblers = [start_assemble(∇²f) for t in 1:nthreads()] - return @assemble! begin + @assemble! begin ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf) @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian) end + return end function calcall(∇²f::SparseMatrixCSC, ∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T} @@ -164,7 +165,7 @@ function minimize!(model; kwargs...) end function h!(storage, x) return ∇²F!(storage, x, model) - #apply!(storage, model.boundaryconds) + # apply!(storage, model.boundaryconds) end f(x) = F(x, model) diff --git a/previews/PR1096/gallery/landau/index.html b/previews/PR1096/gallery/landau/index.html index 704f22f21c..a82714cf31 100644 --- a/previews/PR1096/gallery/landau/index.html +++ b/previews/PR1096/gallery/landau/index.html @@ -7,10 +7,8 @@ using Tensors using Base.Threads

Energy terms

4th order Landau free energy

function Fl(P::Vec{3, T}, α::Vec{3}) where {T}
     P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2))
-    return (
-        α[1] * sum(P2) +
-            α[2] * (P[1]^4 + P[2]^4 + P[3]^4)
-    ) +
+    return α[1] * sum(P2) +
+        α[2] * (P[1]^4 + P[2]^4 + P[3]^4) +
         α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3])
 end
Fl (generic function with 1 method)

Ginzburg free energy

@inline Fg(∇P, G) = 0.5(∇P ⊡ G) ⊡ ∇P
Fg (generic function with 1 method)

GL free energy

F(P, ∇P, params) = Fl(P, params.α) + Fg(∇P, params.G)
F (generic function with 1 method)

Parameters that characterize the model

struct ModelParams{V, T}
     α::V
@@ -73,9 +71,10 @@
     caches = [ThreadCache(dpc, cpc, copy(cvP), ModelParams(α, G), elpotential) for t in 1:nthreads()]
     return LandauModel(dofvector, dofhandler, boundaryconds, threadindices, caches)
 end
Main.LandauModel

utility to quickly save a model

function save_landau(path, model, dofs = model.dofs)
-    return VTKGridFile(path, model.dofhandler) do vtk
+    VTKGridFile(path, model.dofhandler) do vtk
         write_solution(vtk, model.dofhandler, dofs)
     end
+    return
 end
save_landau (generic function with 2 methods)

Assembly

This macro defines most of the assembly step, since the structure is the same for the energy, gradient and Hessian calculations.

macro assemble!(innerbody)
     return esc(
         quote
@@ -107,16 +106,18 @@
     return sum(outs)
 end
F (generic function with 2 methods)

The gradient calculation for each dof

function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}
     fill!(∇f, zero(T))
-    return @assemble! begin
+    @assemble! begin
         ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)
         @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient)
     end
+    return
 end
∇F! (generic function with 1 method)

The Hessian calculation for the whole grid

function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T}
     assemblers = [start_assemble(∇²f) for t in 1:nthreads()]
-    return @assemble! begin
+    @assemble! begin
         ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)
         @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian)
     end
+    return
 end
∇²F! (generic function with 1 method)

We can also calculate all things in one go!

function calcall(∇²f::SparseMatrixCSC, ∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}
     outs = fill(zero(T), nthreads())
     fill!(∇f, zero(T))
@@ -139,7 +140,7 @@
     end
     function h!(storage, x)
         return ∇²F!(storage, x, model)
-        #apply!(storage, model.boundaryconds)
+        # apply!(storage, model.boundaryconds)
     end
     f(x) = F(x, model)
 
@@ -187,4 +188,31 @@
 
 save_landau("landauorig", model)
 @time minimize!(model)
-save_landau("landaufinal", model)
VTKGridFile for the closed file "landaufinal.vtu".

as we can see this runs very quickly even for relatively large gridsizes. The key to get high performance like this is to minimize the allocations inside the threaded loops, ideally to 0.


This page was generated using Literate.jl.

+save_landau("landaufinal", model)
Iter     Function value   Gradient norm
+     0     2.127588e+06     3.597094e+02
+ * time: 8.893013000488281e-5
+     1     3.786155e+05     1.047687e+02
+ * time: 3.3736979961395264
+     2     5.306125e+04     2.978953e+01
+ * time: 6.358555793762207
+     3    -2.642320e+03     7.943136e+00
+ * time: 9.308557987213135
+     4    -1.027484e+04     1.752693e+00
+ * time: 12.248273849487305
+     5    -1.084925e+04     2.157295e-01
+ * time: 15.229424953460693
+     6    -1.085880e+04     5.288877e-03
+ * time: 18.1901638507843
+     7    -1.085881e+04     3.478176e-06
+ * time: 21.136974811553955
+     8    -1.085881e+04     1.596091e-12
+ * time: 24.103304862976074
+     9    -1.085881e+04     1.444882e-12
+ * time: 27.063945770263672
+    10    -1.085881e+04     1.548831e-13
+ * time: 30.02093195915222
+    11    -1.085881e+04     1.548831e-13
+ * time: 33.09149980545044
+    12    -1.085881e+04     1.508030e-13
+ * time: 33.53430700302124
+ 39.623621 seconds (11.20 M allocations: 3.879 GiB, 1.43% gc time, 8.25% compilation time)

as we can see this runs very quickly even for relatively large gridsizes. The key to get high performance like this is to minimize the allocations inside the threaded loops, ideally to 0.


This page was generated using Literate.jl.

diff --git a/previews/PR1096/gallery/quasi_incompressible_hyperelasticity.ipynb b/previews/PR1096/gallery/quasi_incompressible_hyperelasticity.ipynb index c3987bdbad..892635e17c 100644 --- a/previews/PR1096/gallery/quasi_incompressible_hyperelasticity.ipynb +++ b/previews/PR1096/gallery/quasi_incompressible_hyperelasticity.ipynb @@ -419,8 +419,8 @@ " # Loop over all cells in the grid\n", " for cell in CellIterator(dh)\n", " global_dofs = celldofs(cell)\n", - " global_dofsu = global_dofs[1:nu] # first nu dofs are displacement\n", - " global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure\n", + " global_dofsu = global_dofs[1:nu] # first nu dofs are displacement\n", + " global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure\n", " @assert size(global_dofs, 1) == nu + np # sanity check\n", " ue = w[global_dofsu] # displacement dofs for the current cell\n", " pe = w[global_dofsp] # pressure dofs for the current cell\n", diff --git a/previews/PR1096/gallery/quasi_incompressible_hyperelasticity.jl b/previews/PR1096/gallery/quasi_incompressible_hyperelasticity.jl index 401bf481d4..3cfb5ff3b5 100644 --- a/previews/PR1096/gallery/quasi_incompressible_hyperelasticity.jl +++ b/previews/PR1096/gallery/quasi_incompressible_hyperelasticity.jl @@ -167,8 +167,8 @@ function assemble_global!( # Loop over all cells in the grid for cell in CellIterator(dh) global_dofs = celldofs(cell) - global_dofsu = global_dofs[1:nu] # first nu dofs are displacement - global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure + global_dofsu = global_dofs[1:nu] # first nu dofs are displacement + global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure @assert size(global_dofs, 1) == nu + np # sanity check ue = w[global_dofsu] # displacement dofs for the current cell pe = w[global_dofsp] # pressure dofs for the current cell diff --git a/previews/PR1096/gallery/quasi_incompressible_hyperelasticity/index.html b/previews/PR1096/gallery/quasi_incompressible_hyperelasticity/index.html index 6532996bcc..9815edfe58 100644 --- a/previews/PR1096/gallery/quasi_incompressible_hyperelasticity/index.html +++ b/previews/PR1096/gallery/quasi_incompressible_hyperelasticity/index.html @@ -146,8 +146,8 @@ # Loop over all cells in the grid for cell in CellIterator(dh) global_dofs = celldofs(cell) - global_dofsu = global_dofs[1:nu] # first nu dofs are displacement - global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure + global_dofsu = global_dofs[1:nu] # first nu dofs are displacement + global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure @assert size(global_dofs, 1) == nu + np # sanity check ue = w[global_dofsu] # displacement dofs for the current cell pe = w[global_dofsp] # pressure dofs for the current cell @@ -398,8 +398,8 @@ # Loop over all cells in the grid for cell in CellIterator(dh) global_dofs = celldofs(cell) - global_dofsu = global_dofs[1:nu] # first nu dofs are displacement - global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure + global_dofsu = global_dofs[1:nu] # first nu dofs are displacement + global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure @assert size(global_dofs, 1) == nu + np # sanity check ue = w[global_dofsu] # displacement dofs for the current cell pe = w[global_dofsp] # pressure dofs for the current cell @@ -485,4 +485,4 @@ quadratic_u = Lagrange{RefTetrahedron, 2}()^3 linear_p = Lagrange{RefTetrahedron, 1}() -vol_def = solve(quadratic_u, linear_p)

This page was generated using Literate.jl.

+vol_def = solve(quadratic_u, linear_p)

This page was generated using Literate.jl.

diff --git a/previews/PR1096/gallery/topology_optimization.ipynb b/previews/PR1096/gallery/topology_optimization.ipynb index a1badb61e7..0bd0a026c5 100644 --- a/previews/PR1096/gallery/topology_optimization.ipynb +++ b/previews/PR1096/gallery/topology_optimization.ipynb @@ -335,7 +335,7 @@ " i = cellid(element)\n", " for j in 1:_nfacets\n", " nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))\n", - " if (!isempty(nbg_cellid))\n", + " if !isempty(nbg_cellid)\n", " nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell\n", " else # boundary facet\n", " nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]\n", @@ -418,7 +418,7 @@ " λ_upper = maximum(Δχ) + ηs\n", " λ_trial = 0.0\n", "\n", - " while (abs(ρ - ρ_trial) > 1.0e-7)\n", + " while abs(ρ - ρ_trial) > 1.0e-7\n", " for i in 1:n_el\n", " Δχt = 1 / ηs * (Δχ[i] - λ_trial)\n", " χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))\n", @@ -429,9 +429,9 @@ " ρ_trial += χ_trial[i] / n_el\n", " end\n", "\n", - " if (ρ_trial > ρ)\n", + " if ρ_trial > ρ\n", " λ_lower = λ_trial\n", - " elseif (ρ_trial < ρ)\n", + " elseif ρ_trial < ρ\n", " λ_upper = λ_trial\n", " end\n", " λ_trial = 1 / 2 * (λ_upper + λ_lower)\n", @@ -516,7 +516,7 @@ "\n", " χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)\n", "\n", - " if (j < n_j)\n", + " if j < n_j\n", " χn[:] = χn1[:]\n", " end\n", " end\n", @@ -616,7 +616,7 @@ "\n", " symmetrize_lower!(Ke)\n", "\n", - " return @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))\n", + " @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))\n", " if (cellid(element), facet) ∈ getfacetset(grid, \"traction\")\n", " reinit!(facetvalues, element, facet)\n", " t = Vec((0.0, -1.0)) # force pointing downwards\n", @@ -629,7 +629,7 @@ " end\n", " end\n", " end\n", - "\n", + " return\n", "end\n", "\n", "function symmetrize_lower!(K)\n", @@ -751,13 +751,13 @@ " # calculate compliance\n", " compliance = 1 / 2 * u' * K * u\n", "\n", - " if (it == 1)\n", + " if it == 1\n", " compliance_0 = compliance\n", " end\n", "\n", " # check convergence criterium (twice!)\n", - " if (abs(compliance - compliance_n) / compliance < tol)\n", - " if (conv)\n", + " if abs(compliance - compliance_n) / compliance < tol\n", + " if conv\n", " println(\"Converged at iteration number: \", it)\n", " break\n", " else\n", @@ -777,7 +777,7 @@ " compliance_n = compliance\n", "\n", " # output during calculation\n", - " if (output)\n", + " if output\n", " i = @sprintf(\"%3.3i\", it)\n", " filename_it = string(filename, \"_\", i)\n", "\n", @@ -788,7 +788,7 @@ " end\n", "\n", " # export converged results\n", - " if (!output)\n", + " if !output\n", " VTKGridFile(filename, grid) do vtk\n", " write_cell_data(vtk, χ, \"density\")\n", " end\n", @@ -827,7 +827,7 @@ " Starting Newton iterations\n", "Converged at iteration number: 65\n", "Rel. stiffness: 4.8466 \n", - " 4.585859 seconds (2.44 M allocations: 2.015 GiB, 2.04% gc time, 6.60% compilation time)\n" + " 4.685160 seconds (2.44 M allocations: 2.015 GiB, 2.09% gc time, 6.78% compilation time)\n" ] } ], diff --git a/previews/PR1096/gallery/topology_optimization.jl b/previews/PR1096/gallery/topology_optimization.jl index 9deed36843..30252d38b8 100644 --- a/previews/PR1096/gallery/topology_optimization.jl +++ b/previews/PR1096/gallery/topology_optimization.jl @@ -107,7 +107,7 @@ function cache_neighborhood(dh, topology) i = cellid(element) for j in 1:_nfacets nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j)) - if (!isempty(nbg_cellid)) + if !isempty(nbg_cellid) nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell else # boundary facet nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1] @@ -140,7 +140,7 @@ function compute_χn1(χn, Δχ, ρ, ηs, χ_min) λ_upper = maximum(Δχ) + ηs λ_trial = 0.0 - while (abs(ρ - ρ_trial) > 1.0e-7) + while abs(ρ - ρ_trial) > 1.0e-7 for i in 1:n_el Δχt = 1 / ηs * (Δχ[i] - λ_trial) χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt)) @@ -151,9 +151,9 @@ function compute_χn1(χn, Δχ, ρ, ηs, χ_min) ρ_trial += χ_trial[i] / n_el end - if (ρ_trial > ρ) + if ρ_trial > ρ λ_lower = λ_trial - elseif (ρ_trial < ρ) + elseif ρ_trial < ρ λ_upper = λ_trial end λ_trial = 1 / 2 * (λ_upper + λ_lower) @@ -189,7 +189,7 @@ function update_density(dh, states, mp, ρ, neighboorhoods, Δh) χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min) - if (j < n_j) + if j < n_j χn[:] = χn1[:] end end @@ -241,7 +241,7 @@ function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state) symmetrize_lower!(Ke) - return @inbounds for facet in 1:nfacets(getcells(grid, cellid(element))) + @inbounds for facet in 1:nfacets(getcells(grid, cellid(element))) if (cellid(element), facet) ∈ getfacetset(grid, "traction") reinit!(facetvalues, element, facet) t = Vec((0.0, -1.0)) # force pointing downwards @@ -254,7 +254,7 @@ function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state) end end end - + return end function symmetrize_lower!(K) @@ -340,13 +340,13 @@ function topopt(ra, ρ, n, filename; output = :false) # calculate compliance compliance = 1 / 2 * u' * K * u - if (it == 1) + if it == 1 compliance_0 = compliance end # check convergence criterium (twice!) - if (abs(compliance - compliance_n) / compliance < tol) - if (conv) + if abs(compliance - compliance_n) / compliance < tol + if conv println("Converged at iteration number: ", it) break else @@ -366,7 +366,7 @@ function topopt(ra, ρ, n, filename; output = :false) compliance_n = compliance # output during calculation - if (output) + if output i = @sprintf("%3.3i", it) filename_it = string(filename, "_", i) @@ -377,7 +377,7 @@ function topopt(ra, ρ, n, filename; output = :false) end # export converged results - if (!output) + if !output VTKGridFile(filename, grid) do vtk write_cell_data(vtk, χ, "density") end diff --git a/previews/PR1096/gallery/topology_optimization/index.html b/previews/PR1096/gallery/topology_optimization/index.html index b74149502b..4dd60767ac 100644 --- a/previews/PR1096/gallery/topology_optimization/index.html +++ b/previews/PR1096/gallery/topology_optimization/index.html @@ -96,7 +96,7 @@ i = cellid(element) for j in 1:_nfacets nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j)) - if (!isempty(nbg_cellid)) + if !isempty(nbg_cellid) nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell else # boundary facet nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1] @@ -125,7 +125,7 @@ λ_upper = maximum(Δχ) + ηs λ_trial = 0.0 - while (abs(ρ - ρ_trial) > 1.0e-7) + while abs(ρ - ρ_trial) > 1.0e-7 for i in 1:n_el Δχt = 1 / ηs * (Δχ[i] - λ_trial) χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt)) @@ -136,9 +136,9 @@ ρ_trial += χ_trial[i] / n_el end - if (ρ_trial > ρ) + if ρ_trial > ρ λ_lower = λ_trial - elseif (ρ_trial < ρ) + elseif ρ_trial < ρ λ_upper = λ_trial end λ_trial = 1 / 2 * (λ_upper + λ_lower) @@ -170,7 +170,7 @@ χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min) - if (j < n_j) + if j < n_j χn[:] = χn1[:] end end @@ -218,7 +218,7 @@ symmetrize_lower!(Ke) - return @inbounds for facet in 1:nfacets(getcells(grid, cellid(element))) + @inbounds for facet in 1:nfacets(getcells(grid, cellid(element))) if (cellid(element), facet) ∈ getfacetset(grid, "traction") reinit!(facetvalues, element, facet) t = Vec((0.0, -1.0)) # force pointing downwards @@ -231,7 +231,7 @@ end end end - + return end function symmetrize_lower!(K) @@ -315,13 +315,13 @@ # calculate compliance compliance = 1 / 2 * u' * K * u - if (it == 1) + if it == 1 compliance_0 = compliance end # check convergence criterium (twice!) - if (abs(compliance - compliance_n) / compliance < tol) - if (conv) + if abs(compliance - compliance_n) / compliance < tol + if conv println("Converged at iteration number: ", it) break else @@ -341,7 +341,7 @@ compliance_n = compliance # output during calculation - if (output) + if output i = @sprintf("%3.3i", it) filename_it = string(filename, "_", i) @@ -352,7 +352,7 @@ end # export converged results - if (!output) + if !output VTKGridFile(filename, grid) do vtk write_cell_data(vtk, χ, "density") end @@ -365,7 +365,7 @@ Starting Newton iterations Converged at iteration number: 65 Rel. stiffness: 4.8466 - 4.473742 seconds (2.24 M allocations: 2.005 GiB, 1.88% gc time, 1.85% compilation time)

We observe, that the stiffness for the lower value of $ra$ is higher, but also requires more iterations until convergence and finer structures to be manufactured, as can be seen in Figure 2:

Figure 2: Optimization results of the bending beam for smaller (left) and larger (right) value of the regularization parameter $\beta$.

To prove mesh independence, the user could vary the mesh resolution and compare the results.

References

[14]
D. R. Jantos, K. Hackl and P. Junker. An accurate and fast regularization approach to thermodynamic topology optimization. International Journal for Numerical Methods in Engineering 117, 991–1017 (2019).
[15]
M. Blaszczyk, D. R. Jantos and P. Junker. Application of Taylor series combined with the weighted least square method to thermodynamic topology optimization. Computer Methods in Applied Mechanics and Engineering 393, 114698 (2022).

Plain program

Here follows a version of the program without any comments. The file is also available here: topology_optimization.jl.

using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf
+  4.402401 seconds (2.24 M allocations: 2.005 GiB, 2.26% gc time, 1.99% compilation time)

We observe, that the stiffness for the lower value of $ra$ is higher, but also requires more iterations until convergence and finer structures to be manufactured, as can be seen in Figure 2:

Figure 2: Optimization results of the bending beam for smaller (left) and larger (right) value of the regularization parameter $\beta$.

To prove mesh independence, the user could vary the mesh resolution and compare the results.

References

[14]
D. R. Jantos, K. Hackl and P. Junker. An accurate and fast regularization approach to thermodynamic topology optimization. International Journal for Numerical Methods in Engineering 117, 991–1017 (2019).
[15]
M. Blaszczyk, D. R. Jantos and P. Junker. Application of Taylor series combined with the weighted least square method to thermodynamic topology optimization. Computer Methods in Applied Mechanics and Engineering 393, 114698 (2022).

Plain program

Here follows a version of the program without any comments. The file is also available here: topology_optimization.jl.

using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf
 
 function create_grid(n)
     corners = [
@@ -474,7 +474,7 @@
         i = cellid(element)
         for j in 1:_nfacets
             nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))
-            if (!isempty(nbg_cellid))
+            if !isempty(nbg_cellid)
                 nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell
             else # boundary facet
                 nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]
@@ -507,7 +507,7 @@
     λ_upper = maximum(Δχ) + ηs
     λ_trial = 0.0
 
-    while (abs(ρ - ρ_trial) > 1.0e-7)
+    while abs(ρ - ρ_trial) > 1.0e-7
         for i in 1:n_el
             Δχt = 1 / ηs * (Δχ[i] - λ_trial)
             χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))
@@ -518,9 +518,9 @@
             ρ_trial += χ_trial[i] / n_el
         end
 
-        if (ρ_trial > ρ)
+        if ρ_trial > ρ
             λ_lower = λ_trial
-        elseif (ρ_trial < ρ)
+        elseif ρ_trial < ρ
             λ_upper = λ_trial
         end
         λ_trial = 1 / 2 * (λ_upper + λ_lower)
@@ -556,7 +556,7 @@
 
         χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)
 
-        if (j < n_j)
+        if j < n_j
             χn[:] = χn1[:]
         end
     end
@@ -608,7 +608,7 @@
 
     symmetrize_lower!(Ke)
 
-    return @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))
+    @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))
         if (cellid(element), facet) ∈ getfacetset(grid, "traction")
             reinit!(facetvalues, element, facet)
             t = Vec((0.0, -1.0)) # force pointing downwards
@@ -621,7 +621,7 @@
             end
         end
     end
-
+    return
 end
 
 function symmetrize_lower!(K)
@@ -707,13 +707,13 @@
         # calculate compliance
         compliance = 1 / 2 * u' * K * u
 
-        if (it == 1)
+        if it == 1
             compliance_0 = compliance
         end
 
         # check convergence criterium (twice!)
-        if (abs(compliance - compliance_n) / compliance < tol)
-            if (conv)
+        if abs(compliance - compliance_n) / compliance < tol
+            if conv
                 println("Converged at iteration number: ", it)
                 break
             else
@@ -733,7 +733,7 @@
         compliance_n = compliance
 
         # output during calculation
-        if (output)
+        if output
             i = @sprintf("%3.3i", it)
             filename_it = string(filename, "_", i)
 
@@ -744,7 +744,7 @@
     end
 
     # export converged results
-    if (!output)
+    if !output
         VTKGridFile(filename, grid) do vtk
             write_cell_data(vtk, χ, "density")
         end
@@ -755,4 +755,4 @@
 end
 
 @time topopt(0.03, 0.5, 60, "large_radius"; output = :false);
-#topopt(0.02, 0.5, 60, "topopt_animation"; output=:true); # can be used to create animations

This page was generated using Literate.jl.

+#topopt(0.02, 0.5, 60, "topopt_animation"; output=:true); # can be used to create animations

This page was generated using Literate.jl.

diff --git a/previews/PR1096/howto/index.html b/previews/PR1096/howto/index.html index 2b042910c5..6f8aca09ee 100644 --- a/previews/PR1096/howto/index.html +++ b/previews/PR1096/howto/index.html @@ -1,2 +1,2 @@ -How-to guide overview · Ferrite.jl

How-to guides

This page gives an overview of the how-to guides. How-to guides address various common tasks one might want to do in a finite element program. Many of the guides are extensions, or build on top of, the tutorials and, therefore, some familiarity with Ferrite is assumed.


Post processing and visualization

This guide builds on top of Tutorial 1: Heat equation and discusses various post processsing techniques with the goal of visualizing primary fields (the finite element solution) and secondary quantities (e.g. fluxes, stresses, etc.). Concretely, this guide answers:

  • How to visualize data from quadrature points?
  • How to evaluate the finite element solution, or secondary quantities, in arbitrary points of the domain?

Multi-threaded assembly

This guide modifies Tutorial 2: Linear elasticity such that the program is using multi-threading to parallelize the assembly procedure. Concretely this shows how to use grid coloring and "scratch values" in order to use multi-threading without running into race-conditions.


+How-to guide overview · Ferrite.jl

How-to guides

This page gives an overview of the how-to guides. How-to guides address various common tasks one might want to do in a finite element program. Many of the guides are extensions, or build on top of, the tutorials and, therefore, some familiarity with Ferrite is assumed.


Post processing and visualization

This guide builds on top of Tutorial 1: Heat equation and discusses various post processsing techniques with the goal of visualizing primary fields (the finite element solution) and secondary quantities (e.g. fluxes, stresses, etc.). Concretely, this guide answers:

  • How to visualize data from quadrature points?
  • How to evaluate the finite element solution, or secondary quantities, in arbitrary points of the domain?

Multi-threaded assembly

This guide modifies Tutorial 2: Linear elasticity such that the program is using multi-threading to parallelize the assembly procedure. Concretely this shows how to use grid coloring and "scratch values" in order to use multi-threading without running into race-conditions.


diff --git a/previews/PR1096/howto/postprocessing.ipynb b/previews/PR1096/howto/postprocessing.ipynb index fbb2a02b8a..365a666e67 100644 --- a/previews/PR1096/howto/postprocessing.ipynb +++ b/previews/PR1096/howto/postprocessing.ipynb @@ -301,94 +301,94 @@ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n" ], "image/svg+xml": [ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n" ] }, @@ -428,98 +428,98 @@ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n" ], "image/svg+xml": [ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n" ] }, diff --git a/previews/PR1096/howto/postprocessing/e4afafc5.svg b/previews/PR1096/howto/postprocessing/9e8364b1.svg similarity index 85% rename from previews/PR1096/howto/postprocessing/e4afafc5.svg rename to previews/PR1096/howto/postprocessing/9e8364b1.svg index 6c1be67bdd..526811814f 100644 --- a/previews/PR1096/howto/postprocessing/e4afafc5.svg +++ b/previews/PR1096/howto/postprocessing/9e8364b1.svg @@ -1,45 +1,45 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR1096/howto/postprocessing/aa20c5ed.svg b/previews/PR1096/howto/postprocessing/ef7fadb3.svg similarity index 83% rename from previews/PR1096/howto/postprocessing/aa20c5ed.svg rename to previews/PR1096/howto/postprocessing/ef7fadb3.svg index 302e93327c..e4a37532f0 100644 --- a/previews/PR1096/howto/postprocessing/aa20c5ed.svg +++ b/previews/PR1096/howto/postprocessing/ef7fadb3.svg @@ -1,47 +1,47 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR1096/howto/postprocessing/index.html b/previews/PR1096/howto/postprocessing/index.html index 3868f3c90d..d720388876 100644 --- a/previews/PR1096/howto/postprocessing/index.html +++ b/previews/PR1096/howto/postprocessing/index.html @@ -22,7 +22,7 @@ return q end

Now call the function to get all the fluxes.

q_gp = compute_heat_fluxes(cellvalues, dh, u);

Next, create an L2Projector using the same interpolation as was used to approximate the temperature field. On instantiation, the projector assembles the coefficient matrix M and computes the Cholesky factorization of it. By doing so, the projector can be reused without having to invert M every time.

projector = L2Projector(ip, grid);

Project the integration point values to the nodal values

q_projected = project(projector, q_gp, qr);

Exporting to VTK

To visualize the heat flux, we export the projected field q_projected to a VTK-file, which can be viewed in e.g. ParaView. The result is also visualized in Figure 1.

VTKGridFile("heat_equation_flux", grid) do vtk
     write_projection(vtk, projector, q_projected, "q")
-end;

Point evaluation

Figure 2: Visualization of the cut line where we want to compute the temperature and heat flux.

Consider a cut-line through the domain like the black line in Figure 2 above. We will evaluate the temperature and the heat flux distribution along a horizontal line.

points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];

First, we need to generate a PointEvalHandler. This will find and store the cells containing the input points.

ph = PointEvalHandler(grid, points);

After the L2-Projection, the heat fluxes q_projected are stored in the DoF-ordering determined by the projector's internal DoFHandler, so to evaluate the flux q at our points we give the PointEvalHandler, the L2Projector and the values q_projected.

q_points = evaluate_at_points(ph, projector, q_projected);

We can also extract the field values, here the temperature, right away from the result vector of the simulation, that is stored in u. These values are stored in the order of our initial DofHandler so the input is not the PointEvalHandler, the original DofHandler, the dof-vector u, and (optionally for single-field problems) the name of the field. From the L2Projection, the values are stored in the order of the degrees of freedom.

u_points = evaluate_at_points(ph, dh, u, :u);

Now, we can plot the temperature and flux values with the help of any plotting library, e.g. Plots.jl. To do this, we need to import the package:

import Plots

Firstly, we are going to plot the temperature values along the given line.

Plots.plot(getindex.(points, 1), u_points, xlabel = "x (coordinate)", ylabel = "u (temperature)", label = nothing)
Example block output

Figure 3: Temperature along the cut line from Figure 2.

Secondly, the horizontal heat flux (i.e. the first component of the heat flux vector) is plotted.

Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = "x (coordinate)", ylabel = "q_x (flux in x-direction)", label = nothing)
Example block output

Figure 4: $x$-component of the flux along the cut line from Figure 2.

Plain program

Here follows a version of the program without any comments. The file is also available here: postprocessing.jl.

include("../tutorials/heat_equation.jl");
+end;

Point evaluation

Figure 2: Visualization of the cut line where we want to compute the temperature and heat flux.

Consider a cut-line through the domain like the black line in Figure 2 above. We will evaluate the temperature and the heat flux distribution along a horizontal line.

points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];

First, we need to generate a PointEvalHandler. This will find and store the cells containing the input points.

ph = PointEvalHandler(grid, points);

After the L2-Projection, the heat fluxes q_projected are stored in the DoF-ordering determined by the projector's internal DoFHandler, so to evaluate the flux q at our points we give the PointEvalHandler, the L2Projector and the values q_projected.

q_points = evaluate_at_points(ph, projector, q_projected);

We can also extract the field values, here the temperature, right away from the result vector of the simulation, that is stored in u. These values are stored in the order of our initial DofHandler so the input is not the PointEvalHandler, the original DofHandler, the dof-vector u, and (optionally for single-field problems) the name of the field. From the L2Projection, the values are stored in the order of the degrees of freedom.

u_points = evaluate_at_points(ph, dh, u, :u);

Now, we can plot the temperature and flux values with the help of any plotting library, e.g. Plots.jl. To do this, we need to import the package:

import Plots

Firstly, we are going to plot the temperature values along the given line.

Plots.plot(getindex.(points, 1), u_points, xlabel = "x (coordinate)", ylabel = "u (temperature)", label = nothing)
Example block output

Figure 3: Temperature along the cut line from Figure 2.

Secondly, the horizontal heat flux (i.e. the first component of the heat flux vector) is plotted.

Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = "x (coordinate)", ylabel = "q_x (flux in x-direction)", label = nothing)
Example block output

Figure 4: $x$-component of the flux along the cut line from Figure 2.

Plain program

Here follows a version of the program without any comments. The file is also available here: postprocessing.jl.

include("../tutorials/heat_equation.jl");
 
 function compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}
 
@@ -69,4 +69,4 @@
 
 Plots.plot(getindex.(points, 1), u_points, xlabel = "x (coordinate)", ylabel = "u (temperature)", label = nothing)
 
-Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = "x (coordinate)", ylabel = "q_x (flux in x-direction)", label = nothing)

This page was generated using Literate.jl.

+Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = "x (coordinate)", ylabel = "q_x (flux in x-direction)", label = nothing)

This page was generated using Literate.jl.

diff --git a/previews/PR1096/howto/threaded_assembly.ipynb b/previews/PR1096/howto/threaded_assembly.ipynb index a934f79939..291a7a2295 100644 --- a/previews/PR1096/howto/threaded_assembly.ipynb +++ b/previews/PR1096/howto/threaded_assembly.ipynb @@ -74,16 +74,7 @@ "metadata": {} }, { - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": "VTKGridFile for the closed file \"colored.vtu\"." - }, - "metadata": {}, - "execution_count": 1 - } - ], + "outputs": [], "cell_type": "code", "source": [ "using Ferrite, SparseArrays\n", @@ -92,10 +83,11 @@ " grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))\n", " colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)\n", " colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)\n", - " return VTKGridFile(\"colored\", grid) do vtk\n", + " VTKGridFile(\"colored\", grid) do vtk\n", " Ferrite.write_cell_colors(vtk, grid, colors_workstream, \"workstream-coloring\")\n", " Ferrite.write_cell_colors(vtk, grid, colors_greedy, \"greedy-coloring\")\n", " end\n", + " return\n", "end\n", "\n", "create_example_2d_grid()" @@ -277,14 +269,15 @@ "slightly more convenient when using task local values since they can be defined with the\n", "`@local` macro.\n", "\n", - "!!! note \"Schedulers and load balancing\"\n", - " OhMyThreads provides a number of different\n", - " [schedulers](https://juliafolds2.github.io/OhMyThreads.jl/stable/refs/api/#Schedulers).\n", - " In this example we use the `DynamicScheduler` (which is the default one). The\n", - " `DynamicScheduler` will spawn `ntasks` tasks where each task will process a chunk of\n", - " (roughly) equal number of cells (i.e. `length(color) ÷ ntasks`). This should be a good\n", - " choice for this example because we expect all cells to take the same time to process\n", - " and we don't need any load balancing.\n", + "> **Schedulers and load balancing**\n", + ">\n", + "> OhMyThreads provides a number of different\n", + "> [schedulers](https://juliafolds2.github.io/OhMyThreads.jl/stable/refs/api/#Schedulers).\n", + "> In this example we use the `DynamicScheduler` (which is the default one). The\n", + "> `DynamicScheduler` will spawn `ntasks` tasks where each task will process a chunk of\n", + "> (roughly) equal number of cells (i.e. `length(color) ÷ ntasks`). This should be a good\n", + "> choice for this example because we expect all cells to take the same time to process\n", + "> and we don't need any load balancing.\n", "\n", " For a different problem setup where some cells might take longer to process (perhaps\n", " they experience plastic deformation and we need to solve a local problem) we might\n", @@ -346,28 +339,29 @@ { "cell_type": "markdown", "source": [ - "!!! details \"OhMyThreads functional API: OhMyThreads.tforeach\"\n", - " The `OhMyThreads.@tasks` block above corresponds to a call to `OhMyThreads.tforeach`.\n", - " Using the functional API directly would look like below. The main difference is that\n", - " we need to manually create a `TaskLocalValue` for the scratch data.\n", - " ```julia\n", - " # using TaskLocalValues\n", - " scratches = TaskLocalValue() do\n", - " ScratchData(dh, K, f, cellvalues)\n", - " end\n", - " OhMyThreads.tforeach(color; scheduler) do cellidx\n", - " # Obtain a task local scratch and unpack it\n", - " scratch = scratches[]\n", - " (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n", - " # Reinitialize the cell cache and then the cellvalues\n", - " reinit!(cell_cache, cellidx)\n", - " reinit!(cellvalues, cell_cache)\n", - " # Compute the local contribution of the cell\n", - " assemble_cell!(Ke, fe, cellvalues, C, b)\n", - " # Assemble local contribution\n", - " assemble!(assembler, celldofs(cell_cache), Ke, fe)\n", - " end\n", - " ```" + "> **OhMyThreads functional API: OhMyThreads.tforeach**\n", + ">\n", + "> The `OhMyThreads.@tasks` block above corresponds to a call to `OhMyThreads.tforeach`.\n", + "> Using the functional API directly would look like below. The main difference is that\n", + "> we need to manually create a `TaskLocalValue` for the scratch data.\n", + "> ```julia\n", + "> # using TaskLocalValues\n", + "> scratches = TaskLocalValue() do\n", + "> ScratchData(dh, K, f, cellvalues)\n", + "> end\n", + "> OhMyThreads.tforeach(color; scheduler) do cellidx\n", + "> # Obtain a task local scratch and unpack it\n", + "> scratch = scratches[]\n", + "> (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n", + "> # Reinitialize the cell cache and then the cellvalues\n", + "> reinit!(cell_cache, cellidx)\n", + "> reinit!(cellvalues, cell_cache)\n", + "> # Compute the local contribution of the cell\n", + "> assemble_cell!(Ke, fe, cellvalues, C, b)\n", + "> # Assemble local contribution\n", + "> assemble!(assembler, celldofs(cell_cache), Ke, fe)\n", + "> end\n", + "> ```" ], "metadata": {} }, diff --git a/previews/PR1096/howto/threaded_assembly.jl b/previews/PR1096/howto/threaded_assembly.jl index 233860db30..1887bb8dc0 100644 --- a/previews/PR1096/howto/threaded_assembly.jl +++ b/previews/PR1096/howto/threaded_assembly.jl @@ -4,10 +4,11 @@ function create_example_2d_grid() grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0))) colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream) colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy) - return VTKGridFile("colored", grid) do vtk + VTKGridFile("colored", grid) do vtk Ferrite.write_cell_colors(vtk, grid, colors_workstream, "workstream-coloring") Ferrite.write_cell_colors(vtk, grid, colors_greedy, "greedy-coloring") end + return end create_example_2d_grid() diff --git a/previews/PR1096/howto/threaded_assembly/index.html b/previews/PR1096/howto/threaded_assembly/index.html index f641b85b33..a5d173704c 100644 --- a/previews/PR1096/howto/threaded_assembly/index.html +++ b/previews/PR1096/howto/threaded_assembly/index.html @@ -5,13 +5,14 @@ grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0))) colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream) colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy) - return VTKGridFile("colored", grid) do vtk + VTKGridFile("colored", grid) do vtk Ferrite.write_cell_colors(vtk, grid, colors_workstream, "workstream-coloring") Ferrite.write_cell_colors(vtk, grid, colors_greedy, "greedy-coloring") end + return end -create_example_2d_grid()
VTKGridFile for the closed file "colored.vtu".

Figure 1: Element coloring using the "workstream"-algorithm (left) and the "greedy"- algorithm (right).

Multithreaded assembly of a cantilever beam in 3D

We will now look at an example where we assemble the stiffness matrix and right hand side using multiple threads. The problem setup is a cantilever beam in 3D with a linear elastic material behavior. For this exercise we only focus on the multithreading and are not bothered with boundary conditions. For more details refer to the tutorial on linear elasticity.

Setup

We define the element routine, material stiffness, grid and DofHandler just like in the tutorial on linear elasticity without discussing it further here.

# Element routine
+create_example_2d_grid()

Figure 1: Element coloring using the "workstream"-algorithm (left) and the "greedy"- algorithm (right).

Multithreaded assembly of a cantilever beam in 3D

We will now look at an example where we assemble the stiffness matrix and right hand side using multiple threads. The problem setup is a cantilever beam in 3D with a linear elastic material behavior. For this exercise we only focus on the multithreading and are not bothered with boundary conditions. For more details refer to the tutorial on linear elasticity.

Setup

We define the element routine, material stiffness, grid and DofHandler just like in the tutorial on linear elasticity without discussing it further here.

# Element routine
 function assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)
     fill!(Ke, 0)
     fill!(fe, 0)
@@ -144,10 +145,11 @@
     grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))
     colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)
     colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)
-    return VTKGridFile("colored", grid) do vtk
+    VTKGridFile("colored", grid) do vtk
         Ferrite.write_cell_colors(vtk, grid, colors_workstream, "workstream-coloring")
         Ferrite.write_cell_colors(vtk, grid, colors_greedy, "greedy-coloring")
     end
+    return
 end
 
 create_example_2d_grid()
@@ -273,4 +275,4 @@
     @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)
     return
 end
-nothing # hide

This page was generated using Literate.jl.

+nothing # hide

This page was generated using Literate.jl.

diff --git a/previews/PR1096/index.html b/previews/PR1096/index.html index 661269698a..a63b64ab88 100644 --- a/previews/PR1096/index.html +++ b/previews/PR1096/index.html @@ -1,2 +1,2 @@ -Home · Ferrite.jl

Ferrite.jl

Welcome to the documentation for Ferrite.jl! Ferrite is a finite element toolbox that provides functionalities to implement finite element analysis in Julia. The aim is to be i) general, ii) performant, and iii) to keep mathematical abstractions.

Upgrading code from version 0.3.x to version 1.0

Ferrite version 1.0 contains a number of breaking changes compared to version 0.3.x. The Changelog documents all changes and there is also a section specifically for Upgrading code from Ferrite 0.3 to 1.0.

Note

Please help improve this documentation – if something confuses you, chances are you're not alone. It's easy to do as you read along: just click on the "Edit on GitHub" link at the top of each page, and then edit the files directly in your browser. Your changes will be vetted by developers before becoming permanent, so don't worry about whether you might say something wrong. See also Contributing to Ferrite for more details.

How the documentation is organized

This high level view of the documentation structure will help you find what you are looking for. The document is organized as follows[1]:

  • Tutorials are thoroughly documented examples which guides you through the process of solving partial differential equations using Ferrite.
  • Topic guides contains more in-depth explanations and discussions about finite element programming concepts and ideas, and specifically how these are realized in Ferrite.
  • Reference contains the technical API reference of functions and methods (e.g. the documentation strings).
  • How-to guides will guide you through the steps involved in addressing common tasks and use-cases. These usually build on top of the tutorials and thus assume basic knowledge of how Ferrite works.

The four sections above form the main user-facing parts of the documentation. In addition, the document also contain the following sections:

  • Code gallery contain user contributed example programs showcasing what can be done with Ferrite.
  • Changelog contain release notes and information about how to upgrade between releases.
  • Developer documentation contain documentation of Ferrite internal code and is mainly targeted at developers of Ferrite.

Getting started

As a new user of Ferrite it is suggested to start working with the tutorials before using Ferrite to tackle the specific equation you ultimately want to solve. The tutorials start with explaining the basic concepts and then increase in complexity. Understanding the first tutorial program, solving the heat equation, is essential in order to understand how Ferrite works. Already this rather simple program discusses many of the important concepts. See the tutorials overview for suggestion on how to progress to more advanced usage.

Getting help

If you have questions about Ferrite it is suggested to use the #ferrite-fem channel on the Julia Slack, or the #Ferrite.jl stream on Zulip. Alternatively you can use the discussion forum on the GitHub repository.

Installation

To use Ferrite you first need to install Julia, see https://julialang.org/ for details. Installing Ferrite can then be done from the Pkg REPL; press ] at the julia> promp to enter pkg> mode:

pkg> add Ferrite

This will install Ferrite and all necessary dependencies. Press backspace to get back to the julia> prompt. (See the documentation for Pkg, Julia's package manager, for more help regarding package installation and project management.)

Finally, to load Ferrite, use

using Ferrite

You are now all set to start using Ferrite!

Contributing to Ferrite

Ferrite is still under active development. If you find a bug, or have ideas for improvements, you are encouraged to interact with the developers on the Ferrite GitHub repository. There is also a thorough contributor guide which can be found in CONTRIBUTING.md.

+Home · Ferrite.jl

Ferrite.jl

Welcome to the documentation for Ferrite.jl! Ferrite is a finite element toolbox that provides functionalities to implement finite element analysis in Julia. The aim is to be i) general, ii) performant, and iii) to keep mathematical abstractions.

Upgrading code from version 0.3.x to version 1.0

Ferrite version 1.0 contains a number of breaking changes compared to version 0.3.x. The Changelog documents all changes and there is also a section specifically for Upgrading code from Ferrite 0.3 to 1.0.

Note

Please help improve this documentation – if something confuses you, chances are you're not alone. It's easy to do as you read along: just click on the "Edit on GitHub" link at the top of each page, and then edit the files directly in your browser. Your changes will be vetted by developers before becoming permanent, so don't worry about whether you might say something wrong. See also Contributing to Ferrite for more details.

How the documentation is organized

This high level view of the documentation structure will help you find what you are looking for. The document is organized as follows[1]:

  • Tutorials are thoroughly documented examples which guides you through the process of solving partial differential equations using Ferrite.
  • Topic guides contains more in-depth explanations and discussions about finite element programming concepts and ideas, and specifically how these are realized in Ferrite.
  • Reference contains the technical API reference of functions and methods (e.g. the documentation strings).
  • How-to guides will guide you through the steps involved in addressing common tasks and use-cases. These usually build on top of the tutorials and thus assume basic knowledge of how Ferrite works.

The four sections above form the main user-facing parts of the documentation. In addition, the document also contain the following sections:

  • Code gallery contain user contributed example programs showcasing what can be done with Ferrite.
  • Changelog contain release notes and information about how to upgrade between releases.
  • Developer documentation contain documentation of Ferrite internal code and is mainly targeted at developers of Ferrite.

Getting started

As a new user of Ferrite it is suggested to start working with the tutorials before using Ferrite to tackle the specific equation you ultimately want to solve. The tutorials start with explaining the basic concepts and then increase in complexity. Understanding the first tutorial program, solving the heat equation, is essential in order to understand how Ferrite works. Already this rather simple program discusses many of the important concepts. See the tutorials overview for suggestion on how to progress to more advanced usage.

Getting help

If you have questions about Ferrite it is suggested to use the #ferrite-fem channel on the Julia Slack, or the #Ferrite.jl stream on Zulip. Alternatively you can use the discussion forum on the GitHub repository.

Installation

To use Ferrite you first need to install Julia, see https://julialang.org/ for details. Installing Ferrite can then be done from the Pkg REPL; press ] at the julia> promp to enter pkg> mode:

pkg> add Ferrite

This will install Ferrite and all necessary dependencies. Press backspace to get back to the julia> prompt. (See the documentation for Pkg, Julia's package manager, for more help regarding package installation and project management.)

Finally, to load Ferrite, use

using Ferrite

You are now all set to start using Ferrite!

Contributing to Ferrite

Ferrite is still under active development. If you find a bug, or have ideas for improvements, you are encouraged to interact with the developers on the Ferrite GitHub repository. There is also a thorough contributor guide which can be found in CONTRIBUTING.md.

diff --git a/previews/PR1096/literate-gallery/helmholtz.jl b/previews/PR1096/literate-gallery/helmholtz.jl index 56837750a0..8729c55426 100644 --- a/previews/PR1096/literate-gallery/helmholtz.jl +++ b/previews/PR1096/literate-gallery/helmholtz.jl @@ -90,8 +90,7 @@ update!(dbcs, 0.0) K = allocate_matrix(dh); function doassemble( - cellvalues::CellValues, facetvalues::FacetValues, - K::SparseMatrixCSC, dh::DofHandler + cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler ) b = 1.0 f = zeros(ndofs(dh)) @@ -171,6 +170,6 @@ using Test #src #src the true maximum is slightly bigger then 1.0 @test maximum(u) ≈ 0.9952772469054607 #src @test u_ana(Vec{2}((-0.5, -0.5))) ≈ 1 #src -@test u_ana(Vec{2}((0.5, -0.5))) ≈ 1 #src -@test u_ana(Vec{2}((-0.5, 0.5))) ≈ 1 #src +@test u_ana(Vec{2}((0.5, -0.5))) ≈ 1 #src +@test u_ana(Vec{2}((-0.5, 0.5))) ≈ 1 #src println("Helmholtz successful") diff --git a/previews/PR1096/literate-gallery/landau.jl b/previews/PR1096/literate-gallery/landau.jl index d976e96332..e16ead420e 100644 --- a/previews/PR1096/literate-gallery/landau.jl +++ b/previews/PR1096/literate-gallery/landau.jl @@ -32,10 +32,8 @@ using Base.Threads # ### 4th order Landau free energy function Fl(P::Vec{3, T}, α::Vec{3}) where {T} P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2)) - return ( - α[1] * sum(P2) + - α[2] * (P[1]^4 + P[2]^4 + P[3]^4) - ) + + return α[1] * sum(P2) + + α[2] * (P[1]^4 + P[2]^4 + P[3]^4) + α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3]) end # ### Ginzburg free energy @@ -116,9 +114,10 @@ end # utility to quickly save a model function save_landau(path, model, dofs = model.dofs) - return VTKGridFile(path, model.dofhandler) do vtk + VTKGridFile(path, model.dofhandler) do vtk write_solution(vtk, model.dofhandler, dofs) end + return end # ## Assembly @@ -161,19 +160,21 @@ end # The gradient calculation for each dof function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T} fill!(∇f, zero(T)) - return @assemble! begin + @assemble! begin ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf) @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient) end + return end # The Hessian calculation for the whole grid function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T} assemblers = [start_assemble(∇²f) for t in 1:nthreads()] - return @assemble! begin + @assemble! begin ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf) @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian) end + return end # We can also calculate all things in one go! @@ -204,7 +205,7 @@ function minimize!(model; kwargs...) end function h!(storage, x) return ∇²F!(storage, x, model) - #apply!(storage, model.boundaryconds) + ## apply!(storage, model.boundaryconds) end f(x) = F(x, model) diff --git a/previews/PR1096/literate-gallery/quasi_incompressible_hyperelasticity.jl b/previews/PR1096/literate-gallery/quasi_incompressible_hyperelasticity.jl index 0365282b09..2f19d4ada9 100644 --- a/previews/PR1096/literate-gallery/quasi_incompressible_hyperelasticity.jl +++ b/previews/PR1096/literate-gallery/quasi_incompressible_hyperelasticity.jl @@ -262,8 +262,8 @@ function assemble_global!( ## Loop over all cells in the grid for cell in CellIterator(dh) global_dofs = celldofs(cell) - global_dofsu = global_dofs[1:nu] # first nu dofs are displacement - global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure + global_dofsu = global_dofs[1:nu] # first nu dofs are displacement + global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure @assert size(global_dofs, 1) == nu + np # sanity check ue = w[global_dofsu] # displacement dofs for the current cell pe = w[global_dofsp] # pressure dofs for the current cell diff --git a/previews/PR1096/literate-gallery/topology_optimization.jl b/previews/PR1096/literate-gallery/topology_optimization.jl index e8c7f6e13c..37e99e00d7 100644 --- a/previews/PR1096/literate-gallery/topology_optimization.jl +++ b/previews/PR1096/literate-gallery/topology_optimization.jl @@ -204,7 +204,7 @@ function cache_neighborhood(dh, topology) i = cellid(element) for j in 1:_nfacets nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j)) - if (!isempty(nbg_cellid)) + if !isempty(nbg_cellid) nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell else # boundary facet nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1] @@ -246,7 +246,7 @@ function compute_χn1(χn, Δχ, ρ, ηs, χ_min) λ_upper = maximum(Δχ) + ηs λ_trial = 0.0 - while (abs(ρ - ρ_trial) > 1.0e-7) + while abs(ρ - ρ_trial) > 1.0e-7 for i in 1:n_el Δχt = 1 / ηs * (Δχ[i] - λ_trial) χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt)) @@ -257,9 +257,9 @@ function compute_χn1(χn, Δχ, ρ, ηs, χ_min) ρ_trial += χ_trial[i] / n_el end - if (ρ_trial > ρ) + if ρ_trial > ρ λ_lower = λ_trial - elseif (ρ_trial < ρ) + elseif ρ_trial < ρ λ_upper = λ_trial end λ_trial = 1 / 2 * (λ_upper + λ_lower) @@ -304,7 +304,7 @@ function update_density(dh, states, mp, ρ, neighboorhoods, Δh) χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min) - if (j < n_j) + if j < n_j χn[:] = χn1[:] end end @@ -364,7 +364,7 @@ function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state) symmetrize_lower!(Ke) - return @inbounds for facet in 1:nfacets(getcells(grid, cellid(element))) + @inbounds for facet in 1:nfacets(getcells(grid, cellid(element))) if (cellid(element), facet) ∈ getfacetset(grid, "traction") reinit!(facetvalues, element, facet) t = Vec((0.0, -1.0)) # force pointing downwards @@ -377,7 +377,7 @@ function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state) end end end - + return end function symmetrize_lower!(K) @@ -479,13 +479,13 @@ function topopt(ra, ρ, n, filename; output = :false) ## calculate compliance compliance = 1 / 2 * u' * K * u - if (it == 1) + if it == 1 compliance_0 = compliance end ## check convergence criterium (twice!) - if (abs(compliance - compliance_n) / compliance < tol) - if (conv) + if abs(compliance - compliance_n) / compliance < tol + if conv println("Converged at iteration number: ", it) break else @@ -505,7 +505,7 @@ function topopt(ra, ρ, n, filename; output = :false) compliance_n = compliance ## output during calculation - if (output) + if output i = @sprintf("%3.3i", it) filename_it = string(filename, "_", i) @@ -516,7 +516,7 @@ function topopt(ra, ρ, n, filename; output = :false) end ## export converged results - if (!output) + if !output VTKGridFile(filename, grid) do vtk write_cell_data(vtk, χ, "density") end diff --git a/previews/PR1096/literate-howto/threaded_assembly.jl b/previews/PR1096/literate-howto/threaded_assembly.jl index 48d778f137..47cbd32297 100644 --- a/previews/PR1096/literate-howto/threaded_assembly.jl +++ b/previews/PR1096/literate-howto/threaded_assembly.jl @@ -68,10 +68,11 @@ function create_example_2d_grid() grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0))) colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream) colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy) - return VTKGridFile("colored", grid) do vtk + VTKGridFile("colored", grid) do vtk Ferrite.write_cell_colors(vtk, grid, colors_workstream, "workstream-coloring") Ferrite.write_cell_colors(vtk, grid, colors_greedy, "greedy-coloring") end + return end create_example_2d_grid() diff --git a/previews/PR1096/literate-tutorials/computational_homogenization.jl b/previews/PR1096/literate-tutorials/computational_homogenization.jl index 748e8dbb47..ee7b6486e4 100644 --- a/previews/PR1096/literate-tutorials/computational_homogenization.jl +++ b/previews/PR1096/literate-tutorials/computational_homogenization.jl @@ -458,29 +458,25 @@ end # So we have now already computed all the components, and just need to gather the data in # a fourth order tensor: -E_dirichlet = SymmetricTensor{4, 2}( - (i, j, k, l) -> begin - if k == l == 1 - σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11 - elseif k == l == 2 - σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22 - else - σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21 - end +E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l + if k == l == 1 + σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11 + elseif k == l == 2 + σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22 + else + σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21 end -) +end -E_periodic = SymmetricTensor{4, 2}( - (i, j, k, l) -> begin - if k == l == 1 - σ̄.periodic[1][i, j] - elseif k == l == 2 - σ̄.periodic[2][i, j] - else - σ̄.periodic[3][i, j] - end +E_periodic = SymmetricTensor{4, 2}() do i, j, k, l + if k == l == 1 + σ̄.periodic[1][i, j] + elseif k == l == 2 + σ̄.periodic[2][i, j] + else + σ̄.periodic[3][i, j] end -); +end # We can check that the result are what we expect, namely that the stiffness with Dirichlet # boundary conditions is higher than when using periodic boundary conditions, and that @@ -538,7 +534,7 @@ end; # Just another way to compute the stiffness for testing purposes #src function homogenize_test(u::Matrix, dh, cv, E_incl, E_mat) #src - ĒΩ = zero(SymmetricTensor{4, 2}) #src + ĒΩ = zero(SymmetricTensor{4, 2}) #src Ω = 0.0 #src ue = zeros(ndofs_per_cell(dh), 3) #src for cell in CellIterator(dh) #src @@ -553,17 +549,15 @@ function homogenize_test(u::Matrix, dh, cv, E_incl, E_mat) # dΩ = getdetJdV(cv, qp) #src Ω += dΩ #src ## compute u^ij and u^kl #src - Ē′ = SymmetricTensor{4, 2}( #src - (i, j, k, l) -> begin #src - ij = i == j == 1 ? 1 : i == j == 2 ? 2 : 3 #src - kl = k == l == 1 ? 1 : k == l == 2 ? 2 : 3 #src - εij = function_symmetric_gradient(cv, qp, view(ue, :, ij)) + #src - symmetric((basevec(Vec{2}, i) ⊗ basevec(Vec{2}, j))) #src - εkl = function_symmetric_gradient(cv, qp, view(ue, :, kl)) + #src - symmetric((basevec(Vec{2}, k) ⊗ basevec(Vec{2}, l))) #src - return (εij ⊡ E ⊡ εkl) * dΩ #src - end #src - ) #src + Ē′ = SymmetricTensor{4, 2}() do i, j, k, l #src + ij = i == j == 1 ? 1 : i == j == 2 ? 2 : 3 #src + kl = k == l == 1 ? 1 : k == l == 2 ? 2 : 3 #src + εij = function_symmetric_gradient(cv, qp, view(ue, :, ij)) + #src + symmetric((basevec(Vec{2}, i) ⊗ basevec(Vec{2}, j))) #src + εkl = function_symmetric_gradient(cv, qp, view(ue, :, kl)) + #src + symmetric((basevec(Vec{2}, k) ⊗ basevec(Vec{2}, l))) #src + return (εij ⊡ E ⊡ εkl) * dΩ #src + end #src ĒΩ += Ē′ #src end #src end #src diff --git a/previews/PR1096/literate-tutorials/hyperelasticity.jl b/previews/PR1096/literate-tutorials/hyperelasticity.jl index 6bad61a93c..6ab05b3138 100644 --- a/previews/PR1096/literate-tutorials/hyperelasticity.jl +++ b/previews/PR1096/literate-tutorials/hyperelasticity.jl @@ -69,6 +69,7 @@ using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers # # where ``I_1 = \mathrm{tr}(\mathbf{C})`` is the first invariant, ``J = \sqrt{\det(\mathbf{C})}`` # and ``\mu`` and ``\lambda`` material parameters. + # !!! details "Extra details on compressible neo-Hookean formulations" # The Neo-Hooke model is only a well defined terminology in the incompressible case. # Thus, only $W(\mathbf{C})$ specifies the neo-Hookean behavior, the volume penalty $U(J)$ can vary in different formulations. @@ -80,6 +81,7 @@ using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers # where [SimMie:1992:act; Eq. (2.37)](@cite) published a non-generalized version with $\beta=-2$. # This shows the possible variety of $U(J)$ while all of them refer to compressible neo-Hookean models. # Sometimes the modified first invariant $\overline{I}_1=\frac{I_1}{I_3^{1/3}}$ is used in $W(\mathbf{C})$ instead of $I_1$. + # From the potential we obtain the second Piola-Kirchoff stress ``\mathbf{S}`` as # # ```math @@ -99,60 +101,53 @@ using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers # ```math # \begin{align*} # \mathbf{P} &= \mathbf{F} \cdot \mathbf{S},\\ -# \frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= \mathbf{I} \bar{\otimes} \mathbf{S} + 2\, \mathbf{F} \bar{\otimes} \mathbf{I} : +# \frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= \mathbf{I} \bar{\otimes} \mathbf{S} + 2\, \mathbf{F} \cdot # \frac{\partial \mathbf{S}}{\partial \mathbf{C}} : \mathbf{F}^\mathrm{T} \bar{\otimes} \mathbf{I}. # \end{align*} # ``` -#md # ```@raw html -#md #
-#md # -#md # Derivation of $\partial \mathbf{P} / \partial \mathbf{F}$ -#md # -#md #
-#md # ``` -#nb # ### Derivation of ``\partial \mathbf{P} / \partial \mathbf{F}`` -# Using the product rule, the chain rule, and the relations ``\mathbf{P} = \mathbf{F} \cdot -# \mathbf{S}`` and ``\mathbf{C} = \mathbf{F}^\mathrm{T} \cdot \mathbf{F}``, we obtain the -# following: -# ```math -# \begin{aligned} -# \frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= -# \frac{\partial P_{ij}}{\partial F_{kl}} \\ &= -# \frac{\partial (F_{im}S_{mj})}{\partial F_{kl}} \\ &= -# \frac{\partial F_{im}}{\partial F_{kl}}S_{mj} + -# F_{im}\frac{\partial S_{mj}}{\partial F_{kl}} \\ &= -# \delta_{ik}\delta_{ml} S_{mj} + -# F_{im}\frac{\partial S_{mj}}{\partial C_{no}}\frac{\partial C_{no}}{\partial F_{kl}} \\ &= -# \delta_{ik}S_{lj} + -# F_{im}\frac{\partial S_{mj}}{\partial C_{no}} -# \frac{\partial (F^\mathrm{T}_{np}F_{po})}{\partial F_{kl}} \\ &= -# \delta_{ik}S^\mathrm{T}_{jl} + -# F_{im}\delta_{jq}\frac{\partial S_{mq}}{\partial C_{no}} -# \left( -# \frac{\partial F^\mathrm{T}_{np}}{\partial F_{kl}}F_{po} + -# F^\mathrm{T}_{np}\frac{\partial F_{po}}{\partial F_{kl}} -# \right) \\ &= -# \delta_{ik}S_{jl} + -# F_{im}\delta_{jq}\frac{\partial S_{mq}}{\partial C_{no}} -# (\delta_{nl} \delta_{pk} F_{po} + F^\mathrm{T}_{np}\delta_{pk} \delta_{ol}) \\ &= -# \delta_{ik}S_{lj} + -# F_{im}\delta_{jq}\frac{\partial S_{mq}}{\partial C_{no}} -# (F^\mathrm{T}_{ok} \delta_{nl} + F^\mathrm{T}_{nk} \delta_{ol}) \\ &= -# \delta_{ik}S_{jl} + -# 2\, F_{im}\delta_{jq} \frac{\partial S_{mq}}{\partial C_{no}} -# F^\mathrm{T}_{nk} \delta_{ol} \\ &= -# \mathbf{I}\bar{\otimes}\mathbf{S} + -# 2\, \mathbf{F}\bar{\otimes}\mathbf{I} : \frac{\partial \mathbf{S}}{\partial \mathbf{C}} -# : \mathbf{F}^\mathrm{T} \bar{\otimes} \mathbf{I}, -# \end{aligned} -# ``` -# where we used the fact that ``\mathbf{S}`` is symmetric (``S_{lj} = S_{jl}``) and that -# ``\frac{\partial \mathbf{S}}{\partial \mathbf{C}}`` is *minor* symmetric (``\frac{\partial -# S_{mq}}{\partial C_{no}} = \frac{\partial S_{mq}}{\partial C_{on}}``). -#md # ```@raw html -#md #
-#md # ``` +# !!! details "Derivation of $\partial \mathbf{P} / \partial \mathbf{F}$" +# *Tip:* See [knutam.github.io/tensors](https://knutam.github.io/tensors/Theory/IndexNotation/) for +# an explanation of the index notation used in this derivation. +#md # +# Using the product rule, the chain rule, and the relations ``\mathbf{P} = \mathbf{F} \cdot +# \mathbf{S}`` and ``\mathbf{C} = \mathbf{F}^\mathrm{T} \cdot \mathbf{F}``, we obtain the +# following: +# ```math +# \begin{aligned} +# \frac{\partial P_{ij}}{\partial F_{kl}} &= +# \frac{\partial (F_{im}S_{mj})}{\partial F_{kl}} \\ &= +# \frac{\partial F_{im}}{\partial F_{kl}}S_{mj} + +# F_{im}\frac{\partial S_{mj}}{\partial F_{kl}} \\ &= +# \delta_{ik}\delta_{ml} S_{mj} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}}\frac{\partial C_{no}}{\partial F_{kl}} \\ &= +# \delta_{ik}S_{lj} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +# \frac{\partial (F^\mathrm{T}_{np}F_{po})}{\partial F_{kl}} \\ &= +# \delta_{ik}S^\mathrm{T}_{jl} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +# \left( +# \frac{\partial F^\mathrm{T}_{np}}{\partial F_{kl}}F_{po} + +# F^\mathrm{T}_{np}\frac{\partial F_{po}}{\partial F_{kl}} +# \right) \\ &= +# \delta_{ik}S_{jl} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +# (\delta_{nl} \delta_{pk} F_{po} + F^\mathrm{T}_{np}\delta_{pk} \delta_{ol}) \\ &= +# \delta_{ik}S_{lj} + +# F_{im}\frac{\partial S_{mj}}{\partial C_{no}} +# (F^\mathrm{T}_{ok} \delta_{nl} + F^\mathrm{T}_{nk} \delta_{ol}) \\ &= +# \delta_{ik}S_{jl} + +# 2\, F_{im} \frac{\partial S_{mj}}{\partial C_{no}} +# F^\mathrm{T}_{nk} \delta_{ol} \\ +# \frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= +# \mathbf{I}\bar{\otimes}\mathbf{S} + +# 2\, \mathbf{F} \cdot \frac{\partial \mathbf{S}}{\partial \mathbf{C}} +# : \mathbf{F}^\mathrm{T} \bar{\otimes} \mathbf{I}, +# \end{aligned} +# ``` +# where we used the fact that ``\mathbf{S}`` is symmetric (``S_{lj} = S_{jl}``) and that +# ``\frac{\partial \mathbf{S}}{\partial \mathbf{C}}`` is *minor* symmetric (``\frac{\partial +# S_{mj}}{\partial C_{no}} = \frac{\partial S_{mj}}{\partial C_{on}}``). # ### Implementation of material model using automatic differentiation # We can implement the material model as follows, where we utilize automatic differentiation @@ -179,6 +174,19 @@ function constitutive_driver(C, mp::NeoHooke) return S, ∂S∂C end; +## Test the derivation #src +using Test #src +F = rand(Tensor{2, 3}) #src +mp = NeoHooke(rand(2)...) #src +S, ∂S∂C = constitutive_driver(tdot(F), mp) #src +P = F ⋅ S #src +I = one(S) #src +∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I) #src +∂P∂F_ad, P_ad = Tensors.hessian(x -> Ψ(tdot(x), mp), F, :all) #src +@test P ≈ P_ad #src +@test ∂P∂F ≈ ∂P∂F_ad #src +nothing #src + # ## Newton's method # # As mentioned above, to deal with the non-linear weak form we first linearize @@ -249,7 +257,7 @@ function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN) S, ∂S∂C = constitutive_driver(C, mp) P = F ⋅ S I = one(S) - ∂P∂F = otimesu(I, S) + 2 * otimesu(F, I) ⊡ ∂S∂C ⊡ otimesu(F', I) + ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I) ## Loop over test functions for i in 1:ndofs @@ -298,12 +306,13 @@ function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN) assembler = start_assemble(K, g) ## Loop over all cells in the grid - return @timeit "assemble" for cell in CellIterator(dh) + @timeit "assemble" for cell in CellIterator(dh) global_dofs = celldofs(cell) ue = u[global_dofs] # element dofs @timeit "element assemble" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN) assemble!(assembler, global_dofs, ke, ge) end + return end; # Finally, we define a main function which sets up everything and then performs Newton diff --git a/previews/PR1096/literate-tutorials/incompressible_elasticity.jl b/previews/PR1096/literate-tutorials/incompressible_elasticity.jl index 1b29797911..eda174b964 100644 --- a/previews/PR1096/literate-tutorials/incompressible_elasticity.jl +++ b/previews/PR1096/literate-tutorials/incompressible_elasticity.jl @@ -274,7 +274,8 @@ function solve(ν, interpolation_u, interpolation_p) σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress ## Export the solution and the stress - filename = "cook_" * (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * + filename = "cook_" * + (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * "_linear" VTKGridFile(filename, grid) do vtk diff --git a/previews/PR1096/literate-tutorials/linear_elasticity.jl b/previews/PR1096/literate-tutorials/linear_elasticity.jl index 21f20b4cf6..49181ee8b5 100644 --- a/previews/PR1096/literate-tutorials/linear_elasticity.jl +++ b/previews/PR1096/literate-tutorials/linear_elasticity.jl @@ -223,7 +223,7 @@ end # G = \frac{E}{2(1 + \nu)}, \quad K = \frac{E}{3(1 - 2\nu)} # ``` Emod = 200.0e3 # Young's modulus [MPa] -ν = 0.3 # Poisson's ratio [-] +ν = 0.3 # Poisson's ratio [-] Gmod = Emod / (2(1 + ν)) # Shear modulus Kmod = Emod / (3(1 - 2ν)) # Bulk modulus @@ -358,8 +358,8 @@ colors = [ #hide "1" => 1, "5" => 1, # purple #hide "2" => 2, "3" => 2, # red #hide "4" => 3, # blue #hide - "6" => 4, # green #hide -] #hide + "6" => 4, # green #hide +] #hide for (key, color) in colors #hide for i in getcellset(grid, key) #hide color_data[i] = color #hide diff --git a/previews/PR1096/literate-tutorials/linear_shell.jl b/previews/PR1096/literate-tutorials/linear_shell.jl index fa79b387bd..71a74c79c1 100644 --- a/previews/PR1096/literate-tutorials/linear_shell.jl +++ b/previews/PR1096/literate-tutorials/linear_shell.jl @@ -1,3 +1,4 @@ +# runic: off # # [Linear shell](@id tutorial-linear-shell) # # ![](linear_shell.png) @@ -14,117 +15,115 @@ using ForwardDiff function main() #wrap everything in a function... - # First we generate a flat rectangular mesh. There is currently no built-in function for generating - # shell meshes in Ferrite, so we have to create our own simple mesh generator (see the - # function `generate_shell_grid` further down in this file). - #+ - nels = (10, 10) - size = (10.0, 10.0) - grid = generate_shell_grid(nels, size) - - # Here we define the bi-linear interpolation used for the geometrical description of the shell. - # We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use - # under integration for the inplane integration, to avoid shear locking. - #+ - ip = Lagrange{RefQuadrilateral, 1}() - qr_inplane = QuadratureRule{RefQuadrilateral}(1) - qr_ooplane = QuadratureRule{RefLine}(2) - cv = CellValues(qr_inplane, ip, ip^3) - - # Next we distribute displacement dofs,`:u = (x,y,z)` and rotational dofs, `:θ = (θ₁, θ₂)`. - #+ - dh = DofHandler(grid) - add!(dh, :u, ip^3) - add!(dh, :θ, ip^2) - close!(dh) - - # In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This - # is done with `addfacetset!` and `addvertexset!` - #+ - addfacetset!(grid, "left", (x) -> x[1] ≈ 0.0) - addfacetset!(grid, "right", (x) -> x[1] ≈ size[1]) - addvertexset!(grid, "corner", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0) - - # Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations. - #+ - ch = ConstraintHandler(dh) - add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1, 3])) - add!(ch, Dirichlet(:θ, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1, 2])) - - # On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation. - #+ - add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> (0.0, 0.0), [1, 3])) - add!(ch, Dirichlet(:θ, getfacetset(grid, "right"), (x, t) -> (0.0, pi / 10), [1, 2])) - - # In order to not get rigid body motion, we lock the y-displacement in one of the corners. - #+ - add!(ch, Dirichlet(:θ, getvertexset(grid, "corner"), (x, t) -> (0.0), [2])) - - close!(ch) - update!(ch, 0.0) - - # Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material. - # In this linear shell, plane stress is assumed, ie $\\sigma_{zz} = 0$. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6). - #+ - κ = 5 / 6 # Shear correction factor - E = 210.0 - ν = 0.3 - a = (1 - ν) / 2 - C = E / (1 - ν^2) * [ - 1 ν 0 0 0; - ν 1 0 0 0; - 0 0 a * κ 0 0; - 0 0 0 a * κ 0; - 0 0 0 0 a * κ - ] - - - data = (thickness = 1.0, C = C) #Named tuple - - # We now assemble the problem in standard finite element fashion - #+ - nnodes = getnbasefunctions(ip) - ndofs_shell = ndofs_per_cell(dh) - - K = allocate_matrix(dh) - f = zeros(Float64, ndofs(dh)) - - ke = zeros(ndofs_shell, ndofs_shell) - fe = zeros(ndofs_shell) - - celldofs = zeros(Int, ndofs_shell) - cellcoords = zeros(Vec{3, Float64}, nnodes) - - assembler = start_assemble(K, f) - for cell in CellIterator(grid) - fill!(ke, 0.0) - reinit!(cv, cell) - celldofs!(celldofs, dh, cellid(cell)) - getcoordinates!(cellcoords, grid, cellid(cell)) - - #Call the element routine - integrate_shell!(ke, cv, qr_ooplane, cellcoords, data) - - assemble!(assembler, celldofs, ke, fe) - end - - # Apply BC and solve. - #+ - apply!(K, f, ch) - a = K \ f - - # Output results. - #+ - return VTKGridFile("linear_shell", dh) do vtk - write_solution(vtk, dh, a) - end +# First we generate a flat rectangular mesh. There is currently no built-in function for generating +# shell meshes in Ferrite, so we have to create our own simple mesh generator (see the +# function `generate_shell_grid` further down in this file). +#+ +nels = (10,10) +size = (10.0, 10.0) +grid = generate_shell_grid(nels, size) + +# Here we define the bi-linear interpolation used for the geometrical description of the shell. +# We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use +# under integration for the inplane integration, to avoid shear locking. +#+ +ip = Lagrange{RefQuadrilateral,1}() +qr_inplane = QuadratureRule{RefQuadrilateral}(1) +qr_ooplane = QuadratureRule{RefLine}(2) +cv = CellValues(qr_inplane, ip, ip^3) + +# Next we distribute displacement dofs,`:u = (x,y,z)` and rotational dofs, `:θ = (θ₁, θ₂)`. +#+ +dh = DofHandler(grid) +add!(dh, :u, ip^3) +add!(dh, :θ, ip^2) +close!(dh) + +# In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This +# is done with `addfacetset!` and `addvertexset!` +#+ +addfacetset!(grid, "left", (x) -> x[1] ≈ 0.0) +addfacetset!(grid, "right", (x) -> x[1] ≈ size[1]) +addvertexset!(grid, "corner", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0) + +# Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations. +#+ +ch = ConstraintHandler(dh) +add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,3]) ) +add!(ch, Dirichlet(:θ, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,2]) ) + +# On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation. +#+ +add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> (0.0, 0.0), [1,3]) ) +add!(ch, Dirichlet(:θ, getfacetset(grid, "right"), (x, t) -> (0.0, pi/10), [1,2]) ) + +# In order to not get rigid body motion, we lock the y-displacement in one of the corners. +#+ +add!(ch, Dirichlet(:θ, getvertexset(grid, "corner"), (x, t) -> (0.0), [2]) ) + +close!(ch) +update!(ch, 0.0) + +# Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material. +# In this linear shell, plane stress is assumed, ie $\\sigma_{zz} = 0$. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6). +#+ +κ = 5/6 # Shear correction factor +E = 210.0 +ν = 0.3 +a = (1-ν)/2 +C = E/(1-ν^2) * [1 ν 0 0 0; + ν 1 0 0 0; + 0 0 a*κ 0 0; + 0 0 0 a*κ 0; + 0 0 0 0 a*κ] + + +data = (thickness = 1.0, C = C); #Named tuple + +# We now assemble the problem in standard finite element fashion +#+ +nnodes = getnbasefunctions(ip) +ndofs_shell = ndofs_per_cell(dh) + +K = allocate_matrix(dh) +f = zeros(Float64, ndofs(dh)) + +ke = zeros(ndofs_shell, ndofs_shell) +fe = zeros(ndofs_shell) + +celldofs = zeros(Int, ndofs_shell) +cellcoords = zeros(Vec{3,Float64}, nnodes) + +assembler = start_assemble(K, f) +for cell in CellIterator(grid) + fill!(ke, 0.0) + reinit!(cv, cell) + celldofs!(celldofs, dh, cellid(cell)) + getcoordinates!(cellcoords, grid, cellid(cell)) + + #Call the element routine + integrate_shell!(ke, cv, qr_ooplane, cellcoords, data) + + assemble!(assembler, celldofs, ke, fe) +end + +# Apply BC and solve. +#+ +apply!(K, f, ch) +a = K\f + +# Output results. +#+ +VTKGridFile("linear_shell", dh) do vtk + write_solution(vtk, dh, a) +end end; #end main functions # Below is the function that creates the shell mesh. It simply generates a 2d-quadrature mesh, and appends # a third coordinate (z-direction) to the node-positions. function generate_shell_grid(nels, size) - _grid = generate_grid(Quadrilateral, nels, Vec((0.0, 0.0)), Vec(size)) + _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size)) nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node for n in _grid.nodes] grid = Grid(_grid.cells, nodes) @@ -145,20 +144,16 @@ end; # The element uses two coordinate systems. The first coordianate system, called the fiber system, is created for each # element node, and is used as a reference frame for the rotations. The function below implements an algorithm that return the # fiber directions, $\boldsymbol{e}^{f}_{a1}$, $\boldsymbol{e}^{f}_{a2}$ and $\boldsymbol{e}^{f}_{a3}$, at each node $a$. -function fiber_coordsys(Ps::Vector{Vec{3, Float64}}) +function fiber_coordsys(Ps::Vector{Vec{3,Float64}}) - ef1 = Vec{3, Float64}[] - ef2 = Vec{3, Float64}[] - ef3 = Vec{3, Float64}[] + ef1 = Vec{3,Float64}[] + ef2 = Vec{3,Float64}[] + ef3 = Vec{3,Float64}[] for P in Ps a = abs.(P) j = 1 - if a[1] > a[3] - a[3] = a[1]; j = 2 - end - if a[2] > a[3] - j = 3 - end + if a[1] > a[3]; a[3] = a[1]; j = 2; end + if a[2] > a[3]; j = 3; end e3 = P e2 = Tensors.cross(P, basevec(Vec{3}, j)) @@ -184,26 +179,26 @@ function lamina_coordsys(dNdξ, ζ, x, p, h) e2 = zero(Vec{3}) for i in 1:length(dNdξ) - e1 += dNdξ[i][1] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i] - e2 += dNdξ[i][2] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i] + e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i] + e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i] end e1 /= norm(e1) e2 /= norm(e2) - ez = Tensors.cross(e1, e2) + ez = Tensors.cross(e1,e2) ez /= norm(ez) - a = 0.5 * (e1 + e2) + a = 0.5*(e1 + e2) a /= norm(a) - b = Tensors.cross(ez, a) + b = Tensors.cross(ez,a) b /= norm(b) - ex = sqrt(2) / 2 * (a - b) - ey = sqrt(2) / 2 * (a + b) + ex = sqrt(2)/2 * (a - b) + ey = sqrt(2)/2 * (a + b) - return Tensor{2, 3}(hcat(ex, ey, ez)) + return Tensor{2,3}(hcat(ex,ey,ez)) end; @@ -220,18 +215,18 @@ end; # J_{ij} = \frac{\partial x_i}{\partial \xi_j}, # ``` function getjacobian(q, N, dNdξ, ζ, X, p, h) - J = zeros(3, 3) + J = zeros(3,3) for a in 1:length(N) for i in 1:3, j in 1:3 - _dNdξ = (j == 3) ? 0.0 : dNdξ[a][j] - _dζdξ = (j == 3) ? 1.0 : 0.0 + _dNdξ = (j==3) ? 0.0 : dNdξ[a][j] + _dζdξ = (j==3) ? 1.0 : 0.0 _N = N[a] - J[i, j] += _dNdξ * X[a][i] + (_dNdξ * ζ + _N * _dζdξ) * h / 2 * p[a][i] + J[i,j] += _dNdξ * X[a][i] + (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i] end end - return (q' * J) |> Tensor{2, 3, Float64} + return (q' * J) |> Tensor{2,3,Float64} end; # ##### Strains @@ -250,20 +245,20 @@ end; # \frac{\partial u_{i}}{\partial x_j} = \sum_{m=1}^3 q_{im} \sum_{a=1}^{N_{\text{nodes}}} \frac{\partial N_a}{\partial x_j} \bar{u}_{am} + # \frac{\partial(N_a ζ)}{\partial x_j} \frac{h}{2} (\theta_{a2} e^{f}_{am1} - \theta_{a1} e^{f}_{am2}) # ``` -function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where {T} +function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T - u = reinterpret(Vec{3, T}, dofvec[1:12]) - θ = reinterpret(Vec{2, T}, dofvec[13:20]) + u = reinterpret(Vec{3,T}, dofvec[1:12]) + θ = reinterpret(Vec{2,T}, dofvec[13:20]) dudx = zeros(T, 3, 3) for m in 1:3, j in 1:3 for a in 1:length(N) - dudx[m, j] += dNdx[a][j] * u[a][m] + h / 2 * (dNdx[a][j] * ζ + N[a] * dζdx[j]) * (θ[a][2] * ef1[a][m] - θ[a][1] * ef2[a][m]) + dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m]) end end - dudx = q * dudx - ε = [dudx[1, 1], dudx[2, 2], dudx[1, 2] + dudx[2, 1], dudx[2, 3] + dudx[3, 2], dudx[1, 3] + dudx[3, 1]] + dudx = q*dudx + ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]] return ε end; @@ -274,7 +269,7 @@ shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_ function integrate_shell!(ke, cv, qr_ooplane, X, data) nnodes = getnbasefunctions(cv) - ndofs = nnodes * 5 + ndofs = nnodes*5 h = data.thickness #Create the directors in each node. @@ -283,7 +278,7 @@ function integrate_shell!(ke, cv, qr_ooplane, X, data) p = zeros(Vec{3}, nnodes) for i in 1:nnodes a = Vec{3}((0.0, 0.0, 1.0)) - p[i] = a / norm(a) + p[i] = a/norm(a) end ef1, ef2, ef3 = fiber_coordsys(p) @@ -303,14 +298,12 @@ function integrate_shell!(ke, cv, qr_ooplane, X, data) #For simplicity, use automatic differentiation to construct the B-matrix from the strain. B = ForwardDiff.jacobian( - (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) - ) + (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) ) dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp) - ke .+= B' * data.C * B * dV + ke .+= B'*data.C*B * dV end end - return end; # Run everything: diff --git a/previews/PR1096/literate-tutorials/ns_vs_diffeq.jl b/previews/PR1096/literate-tutorials/ns_vs_diffeq.jl index e66dbfaa2a..2b8fc4c3a2 100644 --- a/previews/PR1096/literate-tutorials/ns_vs_diffeq.jl +++ b/previews/PR1096/literate-tutorials/ns_vs_diffeq.jl @@ -145,7 +145,7 @@ if !IS_CI circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag]) gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)]) else #hide - rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide + rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide end #hide nothing #hide # Now, the geometrical entities need to be synchronized in order to be available outside @@ -159,11 +159,11 @@ if !IS_CI toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, "top") holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, "hole") else #hide - gmsh.model.model.add_physical_group(dim - 1, [4], 7, "left") #hide - gmsh.model.model.add_physical_group(dim - 1, [3], 8, "top") #hide - gmsh.model.model.add_physical_group(dim - 1, [2], 9, "right") #hide - gmsh.model.model.add_physical_group(dim - 1, [1], 10, "bottom") #hide -end #hide + gmsh.model.model.add_physical_group(dim - 1, [4], 7, "left") #hide + gmsh.model.model.add_physical_group(dim - 1, [3], 8, "top") #hide + gmsh.model.model.add_physical_group(dim - 1, [2], 9, "right") #hide + gmsh.model.model.add_physical_group(dim - 1, [1], 10, "bottom") #hide +end #hide nothing #hide # Since we want a quad mesh, we specify the meshing algorithm to the quasi structured quad one. # For a complete list, [see the Gmsh docs](https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options-list). @@ -171,8 +171,8 @@ gmsh.option.setNumber("Mesh.Algorithm", 11) gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) gmsh.option.setNumber("Mesh.MeshSizeMax", 0.05) if IS_CI #hide - gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) #hide - gmsh.option.setNumber("Mesh.MeshSizeMax", 0.15) #hide + gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) #hide + gmsh.option.setNumber("Mesh.MeshSizeMax", 0.15) #hide end #hide # In the next step, the mesh is generated and finally translated. gmsh.model.mesh.generate(dim) @@ -204,7 +204,7 @@ ch = ConstraintHandler(dh); nosplip_facet_names = ["top", "bottom", "hole"]; # No hole for the test present #src if IS_CI #hide - nosplip_facet_names = ["top", "bottom"] #hide + nosplip_facet_names = ["top", "bottom"] #hide end #hide ∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...); noslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2]) @@ -610,7 +610,7 @@ if IS_CI let #hide u = copy(integrator.u) #hide Δdivv = abs(compute_divergence(dh, u, cellvalues_v)) #hide - @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide + @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide #hide Δv = 0.0 #hide for cell in CellIterator(dh) #hide @@ -623,10 +623,10 @@ if IS_CI dΩ = getdetJdV(cellvalues_v, q_point) #hide coords_qp = spatial_coordinate(cellvalues_v, q_point, coords) #hide v = function_value(cellvalues_v, q_point, v_cell) #hide - Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide + Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide end #hide end #hide - @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide + @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide end #hide nothing #hide end #hide diff --git a/previews/PR1096/literate-tutorials/plasticity.jl b/previews/PR1096/literate-tutorials/plasticity.jl index 1db9851516..b1e4cbf60d 100644 --- a/previews/PR1096/literate-tutorials/plasticity.jl +++ b/previews/PR1096/literate-tutorials/plasticity.jl @@ -100,7 +100,7 @@ function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticit σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress sᵗ = dev(σᵗ) # deviatoric part of trial-stress J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ - σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress) + σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress) σʸ = material.σ₀ + H * state.k # Previous yield limit φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface @@ -109,7 +109,7 @@ function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticit return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k) else # plastic loading h = H + 3G - μ = φᵗ / h # plastic multiplier + μ = φᵗ / h # plastic multiplier c1 = 1 - 3G * μ / σᵗₑ s = c1 * sᵗ # updated deviatoric stress @@ -129,8 +129,8 @@ function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticit ## Return new state Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain - ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain - k = state.k + μ # hardening variable + ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain + k = state.k + μ # hardening variable return σ, D, MaterialState(ϵᵖ, σ, k) end end @@ -200,10 +200,7 @@ end #md # Due to symmetry, we only compute the lower half of the tangent #md # and then symmetrize it. #md # -function assemble_cell!( - Ke, re, cell, cellvalues, material, - ue, state, state_old - ) +function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old) n_basefuncs = getnbasefunctions(cellvalues) reinit!(cellvalues, cell) @@ -222,7 +219,8 @@ function assemble_cell!( end end end - return symmetrize_lower!(Ke) + symmetrize_lower!(Ke) + return end # Helper function to symmetrize the material tangent @@ -257,10 +255,10 @@ end # Define a function which solves the FE-problem. function solve() ## Define material parameters - E = 200.0e9 # [Pa] + E = 200.0e9 # [Pa] H = E / 20 # [Pa] - ν = 0.3 # [-] - σ₀ = 200.0e6 # [Pa] + ν = 0.3 # [-] + σ₀ = 200.0e6 # [Pa] material = J2Plasticity(E, ν, σ₀, H) L = 10.0 # beam length [m] @@ -285,10 +283,10 @@ function solve() ## Pre-allocate solution vectors, etc. n_dofs = ndofs(dh) # total number of dofs - u = zeros(n_dofs) # solution vector + u = zeros(n_dofs) # solution vector Δu = zeros(n_dofs) # displacement correction r = zeros(n_dofs) # residual - K = allocate_matrix(dh) # tangent stiffness matrix + K = allocate_matrix(dh) # tangent stiffness matrix ## Create material states. One array for each cell, where each element is an array of material- ## states - one for each integration point @@ -310,7 +308,6 @@ function solve() while true newton_itr += 1 - if newton_itr > 8 error("Reached maximum Newton iterations, aborting") break diff --git a/previews/PR1096/literate-tutorials/porous_media.jl b/previews/PR1096/literate-tutorials/porous_media.jl index 1c51e70401..71c0837d62 100644 --- a/previews/PR1096/literate-tutorials/porous_media.jl +++ b/previews/PR1096/literate-tutorials/porous_media.jl @@ -359,7 +359,8 @@ function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0) pvd[t] = vtk end end - return vtk_save(pvd) + vtk_save(pvd) + return end; # Finally we call the functions to actually run the code diff --git a/previews/PR1096/literate-tutorials/reactive_surface.jl b/previews/PR1096/literate-tutorials/reactive_surface.jl index eb0c96fd82..723c50a756 100644 --- a/previews/PR1096/literate-tutorials/reactive_surface.jl +++ b/previews/PR1096/literate-tutorials/reactive_surface.jl @@ -202,7 +202,8 @@ function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::Dof end end - return u₀ .+= 0.01 * rand(ndofs(dh)) + u₀ .+= 0.01 * rand(ndofs(dh)) + return end; # ### Mesh generation @@ -227,7 +228,7 @@ function create_embedded_sphere(refinements) nodes = tonodes() elements, _ = toelements(2) gmsh.finalize() - return grid = Grid(elements, nodes) + return Grid(elements, nodes) end # ### Simulation routines @@ -312,8 +313,8 @@ function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, r ## Finally we totate the solution to initialize the next timestep. uₜ₋₁ .= uₜ end - - return vtk_save(pvd) + vtk_save(pvd) + return end ## This parametrization gives the spot pattern shown in the gif above. diff --git a/previews/PR1096/literate-tutorials/stokes-flow.jl b/previews/PR1096/literate-tutorials/stokes-flow.jl index a19a873981..b88baa3a4a 100644 --- a/previews/PR1096/literate-tutorials/stokes-flow.jl +++ b/previews/PR1096/literate-tutorials/stokes-flow.jl @@ -444,7 +444,7 @@ function check_mean_constraint(dh, fvp, u) #src range_p = dof_range(dh, :p) #src cc = CellCache(dh) #src ## Loop over all the boundaries and compute the integrated pressure #src - ∫pdΓ, Γ = 0.0, 0.0 #src + ∫pdΓ, Γ = 0.0, 0.0 #src for (ci, fi) in set #src reinit!(cc, ci) #src reinit!(fvp, cc, fi) #src @@ -452,10 +452,11 @@ function check_mean_constraint(dh, fvp, u) #src for qp in 1:getnquadpoints(fvp) #src dΓ = getdetJdV(fvp, qp) #src ∫pdΓ += function_value(fvp, qp, ue, range_p) * dΓ #src - Γ += dΓ #src + Γ += dΓ #src end #src end #src - return @test ∫pdΓ / Γ ≈ 0.0 atol = 1.0e-16 #src + @test ∫pdΓ / Γ ≈ 0.0 atol = 1.0e-16 #src + return #src end #src function check_L2(dh, cvu, cvp, u) #src @@ -473,11 +474,12 @@ function check_L2(dh, cvu, cvp, u) #src ph = function_value(cvp, qp, ue, range_p) #src ∫uudΩ += (uh ⋅ uh) * dΩ #src ∫ppdΩ += (ph * ph) * dΩ #src - Ω += dΩ #src + Ω += dΩ #src end #src end #src - @test √(∫uudΩ) / Ω ≈ 0.0007255988117907926 atol = 1.0e-7 #src - return @test √(∫ppdΩ) / Ω ≈ 0.02169683180923709 atol = 1.0e-5 #src + @test √(∫uudΩ) / Ω ≈ 0.0007255988117907926 atol = 1.0e-7 #src + @test √(∫ppdΩ) / Ω ≈ 0.02169683180923709 atol = 1.0e-5 #src + return #src end #src function main() @@ -486,7 +488,7 @@ function main() grid = setup_grid(h) ## Interpolations ipu = Lagrange{RefTriangle, 2}()^2 # quadratic - ipp = Lagrange{RefTriangle, 1}() # linear + ipp = Lagrange{RefTriangle, 1}() # linear ## Dofs dh = setup_dofs(grid, ipu, ipp) ## FE values diff --git a/previews/PR1096/reference/assembly/index.html b/previews/PR1096/reference/assembly/index.html index 34298bd343..67ce20b4ba 100644 --- a/previews/PR1096/reference/assembly/index.html +++ b/previews/PR1096/reference/assembly/index.html @@ -1,6 +1,6 @@ Assembly · Ferrite.jl

Assembly

Ferrite.start_assembleFunction
start_assemble(K::AbstractSparseMatrixCSC;            fillzero::Bool=true) -> CSCAssembler
 start_assemble(K::AbstractSparseMatrixCSC, f::Vector; fillzero::Bool=true) -> CSCAssembler

Create a CSCAssembler from the matrix K and optional vector f.

start_assemble(K::Symmetric{AbstractSparseMatrixCSC};                 fillzero::Bool=true) -> SymmetricCSCAssembler
-start_assemble(K::Symmetric{AbstractSparseMatrixCSC}, f::Vector=Td[]; fillzero::Bool=true) -> SymmetricCSCAssembler

Create a SymmetricCSCAssembler from the matrix K and optional vector f.

CSCAssembler and SymmetricCSCAssembler allocate workspace necessary for efficient matrix assembly. To assemble the contribution from an element, use assemble!.

The keyword argument fillzero can be set to false if K and f should not be zeroed out, but instead keep their current values.

source
Ferrite.assemble!Function
assemble!(a::COOAssembler, dofs, Ke)
-assemble!(a::COOAssembler, dofs, Ke, fe)

Assembles the element matrix Ke and element vector fe into a.

source
assemble!(a::COOAssembler, rowdofs, coldofs, Ke)

Assembles the matrix Ke into a according to the dofs specified by rowdofs and coldofs.

source
assemble!(g, dofs, ge)

Assembles the element residual ge into the global residual vector g.

source
assemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix)
-assemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix, fe::AbstractVector)

Assemble the element stiffness matrix Ke (and optional force vector fe) into the global stiffness (and force) in A, given the element degrees of freedom dofs.

This is equivalent to K[dofs, dofs] += Ke and f[dofs] += fe, where K is the global stiffness matrix and f the global force/residual vector, but more efficient.

source
Ferrite.finish_assembleFunction
finish_assemble(a::COOAssembler) -> K, f

Finalize the assembly and return the sparse matrix K::SparseMatrixCSC and vector f::Vector. If the assembler have not been used for vector assembly, f is an empty vector.

source
+start_assemble(K::Symmetric{AbstractSparseMatrixCSC}, f::Vector=Td[]; fillzero::Bool=true) -> SymmetricCSCAssembler

Create a SymmetricCSCAssembler from the matrix K and optional vector f.

CSCAssembler and SymmetricCSCAssembler allocate workspace necessary for efficient matrix assembly. To assemble the contribution from an element, use assemble!.

The keyword argument fillzero can be set to false if K and f should not be zeroed out, but instead keep their current values.

source
Ferrite.assemble!Function
assemble!(a::COOAssembler, dofs, Ke)
+assemble!(a::COOAssembler, dofs, Ke, fe)

Assembles the element matrix Ke and element vector fe into a.

source
assemble!(a::COOAssembler, rowdofs, coldofs, Ke)

Assembles the matrix Ke into a according to the dofs specified by rowdofs and coldofs.

source
assemble!(g, dofs, ge)

Assembles the element residual ge into the global residual vector g.

source
assemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix)
+assemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix, fe::AbstractVector)

Assemble the element stiffness matrix Ke (and optional force vector fe) into the global stiffness (and force) in A, given the element degrees of freedom dofs.

This is equivalent to K[dofs, dofs] += Ke and f[dofs] += fe, where K is the global stiffness matrix and f the global force/residual vector, but more efficient.

source
Ferrite.finish_assembleFunction
finish_assemble(a::COOAssembler) -> K, f

Finalize the assembly and return the sparse matrix K::SparseMatrixCSC and vector f::Vector. If the assembler have not been used for vector assembly, f is an empty vector.

source
diff --git a/previews/PR1096/reference/boundary_conditions/index.html b/previews/PR1096/reference/boundary_conditions/index.html index 05b8505f59..d6d1616621 100644 --- a/previews/PR1096/reference/boundary_conditions/index.html +++ b/previews/PR1096/reference/boundary_conditions/index.html @@ -1,5 +1,5 @@ -Boundary conditions · Ferrite.jl

Boundary conditions

Ferrite.ConstraintHandlerType
ConstraintHandler([T=Float64], dh::AbstractDofHandler)

A collection of constraints associated with the dof handler dh. T is the numeric type for stored values.

source
Ferrite.DirichletType
Dirichlet(u::Symbol, ∂Ω::AbstractVecOrSet, f::Function, components=nothing)

Create a Dirichlet boundary condition on u on the ∂Ω part of the boundary. f is a function of the form f(x) or f(x, t) where x is the spatial coordinate and t is the current time, and returns the prescribed value. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.

The set, ∂Ω, can be an AbstractSet or AbstractVector with elements of type FacetIndex, FaceIndex, EdgeIndex, VertexIndex, or Int. For most cases, the element type is FacetIndex, as shown below. To constrain a single point, using VertexIndex is recommended, but it is also possible to constrain a specific nodes by giving the node numbers via Int elements. To constrain e.g. an edge in 3d EdgeIndex elements can be given.

For example, here we create a Dirichlet condition for the :u field, on the facetset called ∂Ω and the value given by the sin function:

Examples

# Obtain the facetset from the grid
+Boundary conditions · Ferrite.jl

Boundary conditions

Ferrite.ConstraintHandlerType
ConstraintHandler([T=Float64], dh::AbstractDofHandler)

A collection of constraints associated with the dof handler dh. T is the numeric type for stored values.

source
Ferrite.DirichletType
Dirichlet(u::Symbol, ∂Ω::AbstractVecOrSet, f::Function, components=nothing)

Create a Dirichlet boundary condition on u on the ∂Ω part of the boundary. f is a function of the form f(x) or f(x, t) where x is the spatial coordinate and t is the current time, and returns the prescribed value. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.

The set, ∂Ω, can be an AbstractSet or AbstractVector with elements of type FacetIndex, FaceIndex, EdgeIndex, VertexIndex, or Int. For most cases, the element type is FacetIndex, as shown below. To constrain a single point, using VertexIndex is recommended, but it is also possible to constrain a specific nodes by giving the node numbers via Int elements. To constrain e.g. an edge in 3d EdgeIndex elements can be given.

For example, here we create a Dirichlet condition for the :u field, on the facetset called ∂Ω and the value given by the sin function:

Examples

# Obtain the facetset from the grid
 ∂Ω = getfacetset(grid, "boundary-1")
 
 # Prescribe scalar field :s on ∂Ω to sin(t)
@@ -9,26 +9,26 @@
 dbc = Dirichlet(:v, ∂Ω, x -> 0 * x)
 
 # Prescribe component 2 and 3 of vector field :v on ∂Ω to [sin(t), cos(t)]
-dbc = Dirichlet(:v, ∂Ω, (x, t) -> [sin(t), cos(t)], [2, 3])

Dirichlet boundary conditions are added to a ConstraintHandler which applies the condition via apply! and/or apply_zero!.

source
Ferrite.PeriodicDirichletType
PeriodicDirichlet(u::Symbol, facet_mapping, components=nothing)
 PeriodicDirichlet(u::Symbol, facet_mapping, R::AbstractMatrix, components=nothing)
-PeriodicDirichlet(u::Symbol, facet_mapping, f::Function, components=nothing)

Create a periodic Dirichlet boundary condition for the field u on the facet-pairs given in facet_mapping. The mapping can be computed with collect_periodic_facets. The constraint ensures that degrees-of-freedom on the mirror facet are constrained to the corresponding degrees-of-freedom on the image facet. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.

If the mapping is not aligned with the coordinate axis (e.g. rotated) a rotation matrix R should be passed to the constructor. This matrix rotates dofs on the mirror facet to the image facet. Note that this is only applicable for vector-valued problems.

To construct an inhomogeneous periodic constraint it is possible to pass a function f. Note that this is currently only supported when the periodicity is aligned with the coordinate axes.

See the manual section on Periodic boundary conditions for more information.

source
Ferrite.collect_periodic_facetsFunction
collect_periodic_facets(grid::Grid, mset, iset, transform::Union{Function,Nothing}=nothing; tol=1e-12)

Match all mirror facets in mset with a corresponding image facet in iset. Return a dictionary which maps each mirror facet to a image facet. The result can then be passed to PeriodicDirichlet.

mset and iset can be given as a String (an existing facet set in the grid) or as a AbstractSet{FacetIndex} directly.

By default this function looks for a matching facet in the directions of the coordinate system. For other types of periodicities the transform function can be used. The transform function is applied on the coordinates of the image facet, and is expected to transform the coordinates to the matching locations in the mirror set.

The keyword tol specifies the tolerance (i.e. distance and deviation in facet-normals) between a image-facet and mirror-facet, for them to be considered matched.

See also: collect_periodic_facets!, PeriodicDirichlet.

source
collect_periodic_facets(grid::Grid, all_facets::Union{AbstractSet{FacetIndex},String,Nothing}=nothing; tol=1e-12)

Split all facets in all_facets into image and mirror sets. For each matching pair, the facet located further along the vector (1, 1, 1) becomes the image facet.

If no set is given, all facets on the outer boundary of the grid (i.e. all facets that do not have a neighbor) is used.

See also: collect_periodic_facets!, PeriodicDirichlet.

source
Ferrite.add!Function
add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the SubDofHandler sdh.

source
add!(dh::DofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the DofHandler dh.

The field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.

source
add!(ch::ConstraintHandler, ac::AffineConstraint)

Add the AffineConstraint to the ConstraintHandler.

source
add!(ch::ConstraintHandler, dbc::Dirichlet)

Add a Dirichlet boundary condition to the ConstraintHandler.

source
add!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;
-    qr_rhs, [qr_lhs])

Add an interpolation ip on the cells in set to the L2Projector proj.

  • qr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.
  • The optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.
source
Ferrite.close!Function
close!(dh::AbstractDofHandler)

Closes dh and creates degrees of freedom for each cell.

source
close!(ch::ConstraintHandler)

Close and finalize the ConstraintHandler.

source
close!(proj::L2Projector)

Close proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.

source
Ferrite.update!Function
update!(ch::ConstraintHandler, time::Real=0.0)

Update time-dependent inhomogeneities for the new time. This calls f(x) or f(x, t) when applicable, where f is the function(s) corresponding to the constraints in the handler, to compute the inhomogeneities.

Note that this is called implicitly in close!(::ConstraintHandler).

source
Ferrite.apply!Function
apply!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)

Adjust the matrix K and right hand side rhs to account for the Dirichlet boundary conditions specified in ch such that K \ rhs gives the expected solution.

Note

apply!(K, rhs, ch) essentially calculates

rhs[free] = rhs[free] - K[constrained, constrained] * a[constrained]

where a[constrained] are the inhomogeneities. Consequently, the sign of rhs matters (in contrast with apply_zero!).

apply!(v::AbstractVector, ch::ConstraintHandler)

Apply Dirichlet boundary conditions and affine constraints, specified in ch, to the solution vector v.

Examples

K, f = assemble_system(...) # Assemble system
+PeriodicDirichlet(u::Symbol, facet_mapping, f::Function, components=nothing)

Create a periodic Dirichlet boundary condition for the field u on the facet-pairs given in facet_mapping. The mapping can be computed with collect_periodic_facets. The constraint ensures that degrees-of-freedom on the mirror facet are constrained to the corresponding degrees-of-freedom on the image facet. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.

If the mapping is not aligned with the coordinate axis (e.g. rotated) a rotation matrix R should be passed to the constructor. This matrix rotates dofs on the mirror facet to the image facet. Note that this is only applicable for vector-valued problems.

To construct an inhomogeneous periodic constraint it is possible to pass a function f. Note that this is currently only supported when the periodicity is aligned with the coordinate axes.

See the manual section on Periodic boundary conditions for more information.

source
Ferrite.collect_periodic_facetsFunction
collect_periodic_facets(grid::Grid, mset, iset, transform::Union{Function,Nothing}=nothing; tol=1e-12)

Match all mirror facets in mset with a corresponding image facet in iset. Return a dictionary which maps each mirror facet to a image facet. The result can then be passed to PeriodicDirichlet.

mset and iset can be given as a String (an existing facet set in the grid) or as a AbstractSet{FacetIndex} directly.

By default this function looks for a matching facet in the directions of the coordinate system. For other types of periodicities the transform function can be used. The transform function is applied on the coordinates of the image facet, and is expected to transform the coordinates to the matching locations in the mirror set.

The keyword tol specifies the tolerance (i.e. distance and deviation in facet-normals) between a image-facet and mirror-facet, for them to be considered matched.

See also: collect_periodic_facets!, PeriodicDirichlet.

source
collect_periodic_facets(grid::Grid, all_facets::Union{AbstractSet{FacetIndex},String,Nothing}=nothing; tol=1e-12)

Split all facets in all_facets into image and mirror sets. For each matching pair, the facet located further along the vector (1, 1, 1) becomes the image facet.

If no set is given, all facets on the outer boundary of the grid (i.e. all facets that do not have a neighbor) is used.

See also: collect_periodic_facets!, PeriodicDirichlet.

source
Ferrite.add!Function
add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the SubDofHandler sdh.

source
add!(dh::DofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the DofHandler dh.

The field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.

source
add!(ch::ConstraintHandler, ac::AffineConstraint)

Add the AffineConstraint to the ConstraintHandler.

source
add!(ch::ConstraintHandler, dbc::Dirichlet)

Add a Dirichlet boundary condition to the ConstraintHandler.

source
add!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;
+    qr_rhs, [qr_lhs])

Add an interpolation ip on the cells in set to the L2Projector proj.

  • qr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.
  • The optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.
source
Ferrite.close!Function
close!(dh::AbstractDofHandler)

Closes dh and creates degrees of freedom for each cell.

source
close!(ch::ConstraintHandler)

Close and finalize the ConstraintHandler.

source
close!(proj::L2Projector)

Close proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.

source
Ferrite.update!Function
update!(ch::ConstraintHandler, time::Real=0.0)

Update time-dependent inhomogeneities for the new time. This calls f(x) or f(x, t) when applicable, where f is the function(s) corresponding to the constraints in the handler, to compute the inhomogeneities.

Note that this is called implicitly in close!(::ConstraintHandler).

source
Ferrite.apply!Function
apply!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)

Adjust the matrix K and right hand side rhs to account for the Dirichlet boundary conditions specified in ch such that K \ rhs gives the expected solution.

Note

apply!(K, rhs, ch) essentially calculates

rhs[free] = rhs[free] - K[constrained, constrained] * a[constrained]

where a[constrained] are the inhomogeneities. Consequently, the sign of rhs matters (in contrast with apply_zero!).

apply!(v::AbstractVector, ch::ConstraintHandler)

Apply Dirichlet boundary conditions and affine constraints, specified in ch, to the solution vector v.

Examples

K, f = assemble_system(...) # Assemble system
 apply!(K, f, ch)            # Adjust K and f to account for boundary conditions
 u = K \ f                   # Solve the system, u should be "approximately correct"
-apply!(u, ch)               # Explicitly make sure bcs are correct
Note

The last operation is not strictly necessary since the boundary conditions should already be fulfilled after apply!(K, f, ch). However, solvers of linear systems are not exact, and thus apply!(u, ch) can be used to make sure the boundary conditions are fulfilled exactly.

source
Ferrite.apply_zero!Function
apply_zero!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)

Adjust the matrix K and the right hand side rhs to account for prescribed Dirichlet boundary conditions and affine constraints such that du = K \ rhs gives the expected result (e.g. du zero for all prescribed degrees of freedom).

apply_zero!(v::AbstractVector, ch::ConstraintHandler)

Zero-out values in v corresponding to prescribed degrees of freedom and update values prescribed by affine constraints, such that if a fulfills the constraints, a ± v also will.

These methods are typically used in e.g. a Newton solver where the increment, du, should be prescribed to zero even for non-homogeneouos boundary conditions.

See also: apply!.

Examples

u = un + Δu                 # Current guess
+apply!(u, ch)               # Explicitly make sure bcs are correct
Note

The last operation is not strictly necessary since the boundary conditions should already be fulfilled after apply!(K, f, ch). However, solvers of linear systems are not exact, and thus apply!(u, ch) can be used to make sure the boundary conditions are fulfilled exactly.

source
Ferrite.apply_zero!Function
apply_zero!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)

Adjust the matrix K and the right hand side rhs to account for prescribed Dirichlet boundary conditions and affine constraints such that du = K \ rhs gives the expected result (e.g. du zero for all prescribed degrees of freedom).

apply_zero!(v::AbstractVector, ch::ConstraintHandler)

Zero-out values in v corresponding to prescribed degrees of freedom and update values prescribed by affine constraints, such that if a fulfills the constraints, a ± v also will.

These methods are typically used in e.g. a Newton solver where the increment, du, should be prescribed to zero even for non-homogeneouos boundary conditions.

See also: apply!.

Examples

u = un + Δu                 # Current guess
 K, g = assemble_system(...) # Assemble residual and tangent for current guess
 apply_zero!(K, g, ch)       # Adjust tangent and residual to take prescribed values into account
 ΔΔu = K \ g                # Compute the (negative) increment, prescribed values are "approximately" zero
 apply_zero!(ΔΔu, ch)        # Make sure values are exactly zero
-Δu .-= ΔΔu                  # Update current guess
Note

The last call to apply_zero! is only strictly necessary for affine constraints. However, even if the Dirichlet boundary conditions should be fulfilled after apply!(K, g, ch), solvers of linear systems are not exact. apply!(ΔΔu, ch) can be used to make sure the values for the prescribed degrees of freedom are fulfilled exactly.

source
Ferrite.apply_local!Function
apply_local!(
+Δu .-= ΔΔu                  # Update current guess
Note

The last call to apply_zero! is only strictly necessary for affine constraints. However, even if the Dirichlet boundary conditions should be fulfilled after apply!(K, g, ch), solvers of linear systems are not exact. apply!(ΔΔu, ch) can be used to make sure the values for the prescribed degrees of freedom are fulfilled exactly.

source
Ferrite.apply_local!Function
apply_local!(
     local_matrix::AbstractMatrix, local_vector::AbstractVector,
     global_dofs::AbstractVector, ch::ConstraintHandler;
     apply_zero::Bool = false
-)

Similar to apply! but perform condensation of constrained degrees-of-freedom locally in local_matrix and local_vector before they are to be assembled into the global system.

When the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).

This method can only be used if all constraints are "local", i.e. no constraint couples with dofs outside of the element dofs (global_dofs) since condensation of such constraints requires writing to entries in the global matrix/vector. For such a case, apply_assemble! can be used instead.

Note that this method is destructive since it, by definition, modifies local_matrix and local_vector.

source
Ferrite.apply_assemble!Function
apply_assemble!(
+)

Similar to apply! but perform condensation of constrained degrees-of-freedom locally in local_matrix and local_vector before they are to be assembled into the global system.

When the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).

This method can only be used if all constraints are "local", i.e. no constraint couples with dofs outside of the element dofs (global_dofs) since condensation of such constraints requires writing to entries in the global matrix/vector. For such a case, apply_assemble! can be used instead.

Note that this method is destructive since it, by definition, modifies local_matrix and local_vector.

source
Ferrite.apply_assemble!Function
apply_assemble!(
     assembler::AbstractAssembler, ch::ConstraintHandler,
     global_dofs::AbstractVector{Int},
     local_matrix::AbstractMatrix, local_vector::AbstractVector;
     apply_zero::Bool = false
-)

Assemble local_matrix and local_vector into the global system in assembler by first doing constraint condensation using apply_local!.

This is similar to using apply_local! followed by assemble! with the advantage that non-local constraints can be handled, since this method can write to entries of the global matrix and vector outside of the indices in global_dofs.

When the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).

Note that this method is destructive since it modifies local_matrix and local_vector.

source
Ferrite.get_rhs_dataFunction
get_rhs_data(ch::ConstraintHandler, A::SparseMatrixCSC) -> RHSData

Returns the needed RHSData for apply_rhs!.

This must be used when the same stiffness matrix is reused for multiple steps, for example when timestepping, with different non-homogeneouos Dirichlet boundary conditions.

source
Ferrite.apply_rhs!Function
apply_rhs!(data::RHSData, f::AbstractVector, ch::ConstraintHandler, applyzero::Bool=false)

Applies the boundary condition to the right-hand-side vector without modifying the stiffness matrix.

See also: get_rhs_data.

source
Ferrite.RHSDataType
RHSData

Stores the constrained columns and mean of the diagonal of stiffness matrix A.

source

Initial conditions

Ferrite.apply_analytical!Function
apply_analytical!(
+)

Assemble local_matrix and local_vector into the global system in assembler by first doing constraint condensation using apply_local!.

This is similar to using apply_local! followed by assemble! with the advantage that non-local constraints can be handled, since this method can write to entries of the global matrix and vector outside of the indices in global_dofs.

When the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).

Note that this method is destructive since it modifies local_matrix and local_vector.

source
Ferrite.get_rhs_dataFunction
get_rhs_data(ch::ConstraintHandler, A::SparseMatrixCSC) -> RHSData

Returns the needed RHSData for apply_rhs!.

This must be used when the same stiffness matrix is reused for multiple steps, for example when timestepping, with different non-homogeneouos Dirichlet boundary conditions.

source
Ferrite.apply_rhs!Function
apply_rhs!(data::RHSData, f::AbstractVector, ch::ConstraintHandler, applyzero::Bool=false)

Applies the boundary condition to the right-hand-side vector without modifying the stiffness matrix.

See also: get_rhs_data.

source
Ferrite.RHSDataType
RHSData

Stores the constrained columns and mean of the diagonal of stiffness matrix A.

source

Initial conditions

Ferrite.apply_analytical!Function
apply_analytical!(
     a::AbstractVector, dh::AbstractDofHandler, fieldname::Symbol,
-    f::Function, cellset=1:getncells(get_grid(dh)))

Apply a solution f(x) by modifying the values in the degree of freedom vector a pertaining to the field fieldname for all cells in cellset. The function f(x) are given the spatial coordinate of the degree of freedom. For scalar fields, f(x)::Number, and for vector fields with dimension dim, f(x)::Vec{dim}.

This function can be used to apply initial conditions for time dependent problems.

Note

This function only works for standard nodal finite element interpolations when the function value at the (algebraic) node is equal to the corresponding degree of freedom value. This holds for e.g. Lagrange and Serendipity interpolations, including sub- and superparametric elements.

source
+ f::Function, cellset=1:getncells(get_grid(dh)))

Apply a solution f(x) by modifying the values in the degree of freedom vector a pertaining to the field fieldname for all cells in cellset. The function f(x) are given the spatial coordinate of the degree of freedom. For scalar fields, f(x)::Number, and for vector fields with dimension dim, f(x)::Vec{dim}.

This function can be used to apply initial conditions for time dependent problems.

Note

This function only works for standard nodal finite element interpolations when the function value at the (algebraic) node is equal to the corresponding degree of freedom value. This holds for e.g. Lagrange and Serendipity interpolations, including sub- and superparametric elements.

source
diff --git a/previews/PR1096/reference/dofhandler/index.html b/previews/PR1096/reference/dofhandler/index.html index c702b5a4f2..8a252ce6cd 100644 --- a/previews/PR1096/reference/dofhandler/index.html +++ b/previews/PR1096/reference/dofhandler/index.html @@ -4,7 +4,7 @@ ip_p = Lagrange{RefTriangle, 1}() # scalar interpolation for a field p add!(dh, :u, ip_u) add!(dh, :p, ip_p) -close!(dh)source
Ferrite.SubDofHandlerType
SubDofHandler(dh::AbstractDofHandler, cellset::AbstractVecOrSet{Int})

Create an sdh::SubDofHandler from the parent dh, pertaining to the cells in cellset. This allows you to add fields to parts of the domain, or using different interpolations or cell types (e.g. Triangles and Quadrilaterals). All fields and cell types must be the same in one SubDofHandler.

After construction any number of discrete fields can be added to the SubDofHandler using add!. Construction is finalized by calling close! on the parent dh.

Examples

We assume we have a grid containing "Triangle" and "Quadrilateral" cells, including the cellsets "triangles" and "quadilaterals" for to these cells.

dh = DofHandler(grid)
+close!(dh)
source
Ferrite.SubDofHandlerType
SubDofHandler(dh::AbstractDofHandler, cellset::AbstractVecOrSet{Int})

Create an sdh::SubDofHandler from the parent dh, pertaining to the cells in cellset. This allows you to add fields to parts of the domain, or using different interpolations or cell types (e.g. Triangles and Quadrilaterals). All fields and cell types must be the same in one SubDofHandler.

After construction any number of discrete fields can be added to the SubDofHandler using add!. Construction is finalized by calling close! on the parent dh.

Examples

We assume we have a grid containing "Triangle" and "Quadrilateral" cells, including the cellsets "triangles" and "quadilaterals" for to these cells.

dh = DofHandler(grid)
 
 sdh_tri = SubDofHandler(dh, getcellset(grid, "triangles"))
 ip_tri = Lagrange{RefTriangle, 2}()^2 # vector interpolation for a field u
@@ -14,10 +14,10 @@
 ip_quad = Lagrange{RefQuadrilateral, 2}()^2 # vector interpolation for a field u
 add!(sdh_quad, :u, ip_quad)
 
-close!(dh) # Finalize by closing the parent
source

Adding fields to the DofHandlers

Ferrite.add!Method
add!(dh::DofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the DofHandler dh.

The field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.

source
Ferrite.add!Method
add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the SubDofHandler sdh.

source
Ferrite.close!Method
close!(dh::AbstractDofHandler)

Closes dh and creates degrees of freedom for each cell.

source

Dof renumbering

Ferrite.renumber!Function
renumber!(dh::AbstractDofHandler, order)
-renumber!(dh::AbstractDofHandler, ch::ConstraintHandler, order)

Renumber the degrees of freedom in the DofHandler and/or ConstraintHandler according to the ordering order.

order can be given by one of the following options:

  • A permutation vector perm::AbstractVector{Int} such that dof i is renumbered to perm[i].
  • DofOrder.FieldWise() for renumbering dofs field wise.
  • DofOrder.ComponentWise() for renumbering dofs component wise.
  • DofOrder.Ext{T} for "external" renumber permutations, see documentation for DofOrder.Ext for details.
Warning

The dof numbering in the DofHandler and ConstraintHandler must always be consistent. It is therefore necessary to either renumber before creating the ConstraintHandler in the first place, or to renumber the DofHandler and the ConstraintHandler together.

source
Ferrite.DofOrder.FieldWiseType
DofOrder.FieldWise()
-DofOrder.FieldWise(target_blocks::Vector{Int})

Dof order passed to renumber! to renumber global dofs field wise resulting in a globally blocked system.

The default behavior is to group dofs of each field into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of the same length as the total number of fields in the DofHandler (see getfieldnames(dh)) that maps each field to a "target block": to renumber a DofHandler with three fields :u, :v, :w such that dofs for :u and :w end up in the first global block, and dofs for :v in the second global block use DofOrder.FieldWise([1, 2, 1]).

This renumbering is stable such that the original relative ordering of dofs within each target block is maintained.

source
Ferrite.DofOrder.ComponentWiseType
DofOrder.ComponentWise()
-DofOrder.ComponentWise(target_blocks::Vector{Int})

Dof order passed to renumber! to renumber global dofs component wise resulting in a globally blocked system.

The default behavior is to group dofs of each component into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of length ncomponents that maps each component to a "target block" (see DofOrder.FieldWise for details).

This renumbering is stable such that the original relative ordering of dofs within each target block is maintained.

source

Common methods

Ferrite.ndofsFunction
ndofs(dh::AbstractDofHandler)

Return the number of degrees of freedom in dh

source
Ferrite.ndofs_per_cellFunction
ndofs_per_cell(dh::AbstractDofHandler[, cell::Int=1])

Return the number of degrees of freedom for the cell with index cell.

See also ndofs.

source
Ferrite.dof_rangeFunction
dof_range(sdh::SubDofHandler, field_idx::Int)
+close!(dh) # Finalize by closing the parent
source

Adding fields to the DofHandlers

Ferrite.add!Method
add!(dh::DofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the DofHandler dh.

The field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.

source
Ferrite.add!Method
add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)

Add a field called name approximated by ip to the SubDofHandler sdh.

source
Ferrite.close!Method
close!(dh::AbstractDofHandler)

Closes dh and creates degrees of freedom for each cell.

source

Dof renumbering

Ferrite.renumber!Function
renumber!(dh::AbstractDofHandler, order)
+renumber!(dh::AbstractDofHandler, ch::ConstraintHandler, order)

Renumber the degrees of freedom in the DofHandler and/or ConstraintHandler according to the ordering order.

order can be given by one of the following options:

  • A permutation vector perm::AbstractVector{Int} such that dof i is renumbered to perm[i].
  • DofOrder.FieldWise() for renumbering dofs field wise.
  • DofOrder.ComponentWise() for renumbering dofs component wise.
  • DofOrder.Ext{T} for "external" renumber permutations, see documentation for DofOrder.Ext for details.
Warning

The dof numbering in the DofHandler and ConstraintHandler must always be consistent. It is therefore necessary to either renumber before creating the ConstraintHandler in the first place, or to renumber the DofHandler and the ConstraintHandler together.

source
Ferrite.DofOrder.FieldWiseType
DofOrder.FieldWise()
+DofOrder.FieldWise(target_blocks::Vector{Int})

Dof order passed to renumber! to renumber global dofs field wise resulting in a globally blocked system.

The default behavior is to group dofs of each field into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of the same length as the total number of fields in the DofHandler (see getfieldnames(dh)) that maps each field to a "target block": to renumber a DofHandler with three fields :u, :v, :w such that dofs for :u and :w end up in the first global block, and dofs for :v in the second global block use DofOrder.FieldWise([1, 2, 1]).

This renumbering is stable such that the original relative ordering of dofs within each target block is maintained.

source
Ferrite.DofOrder.ComponentWiseType
DofOrder.ComponentWise()
+DofOrder.ComponentWise(target_blocks::Vector{Int})

Dof order passed to renumber! to renumber global dofs component wise resulting in a globally blocked system.

The default behavior is to group dofs of each component into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of length ncomponents that maps each component to a "target block" (see DofOrder.FieldWise for details).

This renumbering is stable such that the original relative ordering of dofs within each target block is maintained.

source

Common methods

Ferrite.ndofsFunction
ndofs(dh::AbstractDofHandler)

Return the number of degrees of freedom in dh

source
Ferrite.ndofs_per_cellFunction
ndofs_per_cell(dh::AbstractDofHandler[, cell::Int=1])

Return the number of degrees of freedom for the cell with index cell.

See also ndofs.

source
Ferrite.dof_rangeFunction
dof_range(sdh::SubDofHandler, field_idx::Int)
 dof_range(sdh::SubDofHandler, field_name::Symbol)
 dof_range(dh:DofHandler, field_name::Symbol)

Return the local dof range for a given field. The field can be specified by its name or index, where field_idx represents the index of a field within a SubDofHandler and field_idxs is a tuple of the SubDofHandler-index within the DofHandler and the field_idx.

Note

The dof_range of a field can vary between different SubDofHandlers. Therefore, it is advised to use the field_idxs or refer to a given SubDofHandler directly in case several SubDofHandlers exist. Using the field_name will always refer to the first occurrence of field within the DofHandler.

Example:

julia> grid = generate_grid(Triangle, (3, 3))
 Grid{2, Triangle, Float64} with 18 Triangle cells and 16 nodes
@@ -34,19 +34,19 @@
 1:9
 
 julia> dof_range(dh.subdofhandlers[1], 2) # field :p
-10:12
source
Ferrite.celldofsFunction
celldofs(dh::AbstractDofHandler, i::Int)

Return a vector with the degrees of freedom that belong to cell i.

See also celldofs!.

source
Ferrite.celldofs!Function
celldofs!(global_dofs::Vector{Int}, dh::AbstractDofHandler, i::Int)

Store the degrees of freedom that belong to cell i in global_dofs.

See also celldofs.

source

Grid iterators

Ferrite.CellCacheType
CellCache(grid::Grid)
-CellCache(dh::AbstractDofHandler)

Create a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell. The cache is updated for a new cell by calling reinit!(cache, cellid) where cellid::Int is the cell id.

Methods with CellCache

  • reinit!(cc, i): reinitialize the cache for cell i
  • cellid(cc): get the cell id of the currently cached cell
  • getnodes(cc): get the global node ids of the cell
  • getcoordinates(cc): get the coordinates of the cell
  • celldofs(cc): get the global dof ids of the cell
  • reinit!(fev, cc): reinitialize CellValues or FacetValues

See also CellIterator.

source
Ferrite.CellIteratorType
CellIterator(grid::Grid, cellset=1:getncells(grid))
+10:12
source
Ferrite.celldofsFunction
celldofs(dh::AbstractDofHandler, i::Int)

Return a vector with the degrees of freedom that belong to cell i.

See also celldofs!.

source
Ferrite.celldofs!Function
celldofs!(global_dofs::Vector{Int}, dh::AbstractDofHandler, i::Int)

Store the degrees of freedom that belong to cell i in global_dofs.

See also celldofs.

source

Grid iterators

Ferrite.CellCacheType
CellCache(grid::Grid)
+CellCache(dh::AbstractDofHandler)

Create a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell. The cache is updated for a new cell by calling reinit!(cache, cellid) where cellid::Int is the cell id.

Methods with CellCache

  • reinit!(cc, i): reinitialize the cache for cell i
  • cellid(cc): get the cell id of the currently cached cell
  • getnodes(cc): get the global node ids of the cell
  • getcoordinates(cc): get the coordinates of the cell
  • celldofs(cc): get the global dof ids of the cell
  • reinit!(fev, cc): reinitialize CellValues or FacetValues

See also CellIterator.

source
Ferrite.CellIteratorType
CellIterator(grid::Grid, cellset=1:getncells(grid))
 CellIterator(dh::AbstractDofHandler, cellset=1:getncells(dh))

Create a CellIterator to conveniently iterate over all, or a subset, of the cells in a grid. The elements of the iterator are CellCaches which are properly reinit!ialized. See CellCache for more details.

Looping over a CellIterator, i.e.:

for cc in CellIterator(grid, cellset)
     # ...
 end

is thus simply convenience for the following equivalent snippet:

cc = CellCache(grid)
 for idx in cellset
     reinit!(cc, idx)
     # ...
-end
Warning

CellIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).

source
Ferrite.FacetCacheType
FacetCache(grid::Grid)
-FacetCache(dh::AbstractDofHandler)

Create a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell suitable for looping over faces in a grid. The cache is updated for a new face by calling reinit!(cache, fi::FacetIndex).

Methods with fc::FacetCache

  • reinit!(fc, fi): reinitialize the cache for face fi::FacetIndex
  • cellid(fc): get the current cellid
  • getnodes(fc): get the global node ids of the cell
  • getcoordinates(fc): get the coordinates of the cell
  • celldofs(fc): get the global dof ids of the cell
  • reinit!(fv, fc): reinitialize FacetValues

See also FacetIterator.

source
Ferrite.FacetIteratorType
FacetIterator(gridordh::Union{Grid,AbstractDofHandler}, facetset::AbstractVecOrSet{FacetIndex})

Create a FacetIterator to conveniently iterate over the faces in facestet. The elements of the iterator are FacetCaches which are properly reinit!ialized. See FacetCache for more details.

Looping over a FacetIterator, i.e.:

for fc in FacetIterator(grid, facetset)
+end
Warning

CellIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).

source
Ferrite.FacetCacheType
FacetCache(grid::Grid)
+FacetCache(dh::AbstractDofHandler)

Create a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell suitable for looping over faces in a grid. The cache is updated for a new face by calling reinit!(cache, fi::FacetIndex).

Methods with fc::FacetCache

  • reinit!(fc, fi): reinitialize the cache for face fi::FacetIndex
  • cellid(fc): get the current cellid
  • getnodes(fc): get the global node ids of the cell
  • getcoordinates(fc): get the coordinates of the cell
  • celldofs(fc): get the global dof ids of the cell
  • reinit!(fv, fc): reinitialize FacetValues

See also FacetIterator.

source
Ferrite.FacetIteratorType
FacetIterator(gridordh::Union{Grid,AbstractDofHandler}, facetset::AbstractVecOrSet{FacetIndex})

Create a FacetIterator to conveniently iterate over the faces in facestet. The elements of the iterator are FacetCaches which are properly reinit!ialized. See FacetCache for more details.

Looping over a FacetIterator, i.e.:

for fc in FacetIterator(grid, facetset)
     # ...
-end

is thus simply convenience for the following equivalent snippet: ```julia fc = FacetCache(grid) for faceindex in facetset reinit!(fc, faceindex) # ... end

source
Ferrite.InterfaceCacheType
InterfaceCache(grid::Grid)
-InterfaceCache(dh::AbstractDofHandler)

Create a cache object with pre-allocated memory for the nodes, coordinates, and dofs of an interface. The cache is updated for a new cell by calling reinit!(cache, facet_a, facet_b) where facet_a::FacetIndex and facet_b::FacetIndex are the two interface faces.

Struct fields of InterfaceCache

  • ic.a :: FacetCache: face cache for the first face of the interface
  • ic.b :: FacetCache: face cache for the second face of the interface
  • ic.dofs :: Vector{Int}: global dof ids for the interface (union of ic.a.dofs and ic.b.dofs)

Methods with InterfaceCache

  • reinit!(cache::InterfaceCache, facet_a::FacetIndex, facet_b::FacetIndex): reinitialize the cache for a new interface
  • interfacedofs(ic): get the global dof ids of the interface

See also InterfaceIterator.

source
Ferrite.InterfaceIteratorType
InterfaceIterator(grid::Grid, [topology::ExclusiveTopology])
+end

is thus simply convenience for the following equivalent snippet: ```julia fc = FacetCache(grid) for faceindex in facetset reinit!(fc, faceindex) # ... end

source
Ferrite.InterfaceCacheType
InterfaceCache(grid::Grid)
+InterfaceCache(dh::AbstractDofHandler)

Create a cache object with pre-allocated memory for the nodes, coordinates, and dofs of an interface. The cache is updated for a new cell by calling reinit!(cache, facet_a, facet_b) where facet_a::FacetIndex and facet_b::FacetIndex are the two interface faces.

Struct fields of InterfaceCache

  • ic.a :: FacetCache: face cache for the first face of the interface
  • ic.b :: FacetCache: face cache for the second face of the interface
  • ic.dofs :: Vector{Int}: global dof ids for the interface (union of ic.a.dofs and ic.b.dofs)

Methods with InterfaceCache

  • reinit!(cache::InterfaceCache, facet_a::FacetIndex, facet_b::FacetIndex): reinitialize the cache for a new interface
  • interfacedofs(ic): get the global dof ids of the interface

See also InterfaceIterator.

source
Ferrite.InterfaceIteratorType
InterfaceIterator(grid::Grid, [topology::ExclusiveTopology])
 InterfaceIterator(dh::AbstractDofHandler, [topology::ExclusiveTopology])

Create an InterfaceIterator to conveniently iterate over all the interfaces in a grid. The elements of the iterator are InterfaceCaches which are properly reinit!ialized. See InterfaceCache for more details. Looping over an InterfaceIterator, i.e.:

for ic in InterfaceIterator(grid, topology)
     # ...
 end

is thus simply convenience for the following equivalent snippet for grids of dimensions > 1:

ic = InterfaceCache(grid, topology)
@@ -56,4 +56,4 @@
     neighbor_face = neighborhood[1]
     reinit!(ic, face, neighbor_face)
     # ...
-end
Warning

InterfaceIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).

source
+end
Warning

InterfaceIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).

source diff --git a/previews/PR1096/reference/export/index.html b/previews/PR1096/reference/export/index.html index c1b9c34e0c..189e5c9ed0 100644 --- a/previews/PR1096/reference/export/index.html +++ b/previews/PR1096/reference/export/index.html @@ -15,8 +15,8 @@ end end -projected = project(proj, vals)

where projected can be used in e.g. evaluate_at_points with the PointEvalHandler, or with evaluate_at_grid_nodes.

source
Ferrite.add!Method
add!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;
-    qr_rhs, [qr_lhs])

Add an interpolation ip on the cells in set to the L2Projector proj.

  • qr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.
  • The optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.
source
Ferrite.close!Method
close!(proj::L2Projector)

Close proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.

source
Ferrite.L2ProjectorMethod
L2Projector(ip::Interpolation, grid::AbstractGrid; [qr_lhs], [set])

A quick way to initiate an L2Projector, add an interpolation ip on the set to it, and then close! it so that it can be used to project. The optional keyword argument set defaults to all cells in the grid, while qr_lhs defaults to a quadrature rule that integrates the mass matrix exactly for the interpolation ip.

source
Ferrite.projectFunction
project(proj::L2Projector, vals, [qr_rhs::QuadratureRule])

Makes a L2 projection of data vals to the nodes of the grid using the projector proj (see L2Projector).

project integrates the right hand side, and solves the projection $u$ from the following projection equation: Find projection $u \in U_h(\Omega) \subset L_2(\Omega)$ such that

\[\int v u \ \mathrm{d}\Omega = \int v f \ \mathrm{d}\Omega \quad \forall v \in U_h(\Omega),\]

where $f \in L_2(\Omega)$ is the data to project. The function space $U_h(\Omega)$ is the finite element approximation given by the interpolations in proj.

The data vals should be an AbstractVector or AbstractDict that is indexed by the cell number. Each index in vals should give an AbstractVector with one element for each cell quadrature point.

If proj was created by calling L2Projector(ip, grid, set), qr_rhs must be given. Otherwise, this is added for each domain when calling add!(proj, args...).

Alternatively, vals can be a matrix, with the column index referring the cell number, and the row index corresponding to quadrature point number. Example (scalar) input data:

vals = [
+projected = project(proj, vals)

where projected can be used in e.g. evaluate_at_points with the PointEvalHandler, or with evaluate_at_grid_nodes.

source
Ferrite.add!Method
add!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;
+    qr_rhs, [qr_lhs])

Add an interpolation ip on the cells in set to the L2Projector proj.

  • qr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.
  • The optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.
source
Ferrite.close!Method
close!(proj::L2Projector)

Close proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.

source
Ferrite.L2ProjectorMethod
L2Projector(ip::Interpolation, grid::AbstractGrid; [qr_lhs], [set])

A quick way to initiate an L2Projector, add an interpolation ip on the set to it, and then close! it so that it can be used to project. The optional keyword argument set defaults to all cells in the grid, while qr_lhs defaults to a quadrature rule that integrates the mass matrix exactly for the interpolation ip.

source
Ferrite.projectFunction
project(proj::L2Projector, vals, [qr_rhs::QuadratureRule])

Makes a L2 projection of data vals to the nodes of the grid using the projector proj (see L2Projector).

project integrates the right hand side, and solves the projection $u$ from the following projection equation: Find projection $u \in U_h(\Omega) \subset L_2(\Omega)$ such that

\[\int v u \ \mathrm{d}\Omega = \int v f \ \mathrm{d}\Omega \quad \forall v \in U_h(\Omega),\]

where $f \in L_2(\Omega)$ is the data to project. The function space $U_h(\Omega)$ is the finite element approximation given by the interpolations in proj.

The data vals should be an AbstractVector or AbstractDict that is indexed by the cell number. Each index in vals should give an AbstractVector with one element for each cell quadrature point.

If proj was created by calling L2Projector(ip, grid, set), qr_rhs must be given. Otherwise, this is added for each domain when calling add!(proj, args...).

Alternatively, vals can be a matrix, with the column index referring the cell number, and the row index corresponding to quadrature point number. Example (scalar) input data:

vals = [
     [0.44, 0.98, 0.32], # data for quadrature point 1, 2, 3 of element 1
     [0.29, 0.48, 0.55], # data for quadrature point 1, 2, 3 of element 2
     # ...
@@ -24,19 +24,19 @@
     0.44 0.29 # ...
     0.98 0.48 # ...
     0.32 0.55 # ...
-]

Supported data types to project are Numbers and AbstractTensors.

Note

The order of the returned data correspond to the order of the L2Projector's internal DofHandler. The data can be further analyzed with evaluate_at_points and evaluate_at_grid_nodes. Use write_projection to export the result.

source

Evaluation at points

Ferrite.evaluate_at_grid_nodesFunction
evaluate_at_grid_nodes(dh::AbstractDofHandler, u::AbstractVector{T}, fieldname::Symbol) where T

Evaluate the approximated solution for field fieldname at the node coordinates of the grid given the Dof handler dh and the solution vector u.

Return a vector of length getnnodes(grid) where entry i contains the evaluation of the approximation in the coordinate of node i. If the field does not live on parts of the grid, the corresponding values for those nodes will be returned as NaNs.

source
Ferrite.PointEvalHandlerType
PointEvalHandler(grid::Grid, points::AbstractVector{Vec{dim,T}}; kwargs...) where {dim, T}

The PointEvalHandler can be used for function evaluation in arbitrary points in the domain – not just in quadrature points or nodes.

The constructor takes a grid and a vector of coordinates for the points. The PointEvalHandler computes i) the corresponding cell, and ii) the (local) coordinate within the cell, for each point. The fields of the PointEvalHandler are:

  • cells::Vector{Union{Int,Nothing}}: vector with cell IDs for the points, with nothing for points that could not be found.
  • local_coords::Vector{Union{Vec,Nothing}}: vector with the local coordinates (i.e. coordinates in the reference configuration) for the points, with nothing for points that could not be found.

There are two ways to use the PointEvalHandler to evaluate functions:

  • evaluate_at_points: can be used when the function is described by i) a dh::DofHandler + uh::Vector (for example the FE-solution), or ii) a p::L2Projector + ph::Vector (for projected data).
  • Iteration with PointIterator + PointValues: can be used for more flexible evaluation in the points, for example to compute gradients.
source
Ferrite.evaluate_at_pointsFunction
evaluate_at_points(ph::PointEvalHandler, dh::AbstractDofHandler, dof_values::Vector{T}, [fieldname::Symbol]) where T
-evaluate_at_points(ph::PointEvalHandler, proj::L2Projector, dof_values::Vector{T}) where T

Return a Vector{T} (for a 1-dimensional field) or a Vector{Vec{fielddim, T}} (for a vector field) with the field values of field fieldname in the points of the PointEvalHandler. The fieldname can be omitted if only one field is stored in dh. The field values are computed based on the dof_values and interpolated to the local coordinates by the function interpolation of the corresponding field stored in the AbstractDofHandler or the L2Projector.

Points that could not be found in the domain when constructing the PointEvalHandler will have NaNs for the corresponding entries in the output vector.

source
Ferrite.PointValuesType
PointValues(cv::CellValues)
-PointValues([::Type{T}], func_interpol::Interpolation, [geom_interpol::Interpolation])

Similar to CellValues but with a single updateable "quadrature point". PointValues are used for evaluation of functions/gradients in arbitrary points of the domain together with a PointEvalHandler.

PointValues can be created from CellValues, or from the interpolations directly.

PointValues are reinitialized like other CellValues, but since the local reference coordinate of the "quadrature point" changes this needs to be passed to reinit!, in addition to the element coordinates: reinit!(pv, coords, local_coord). Alternatively, it can be reinitialized with a PointLocation when iterating a PointEvalHandler with a PointIterator.

For function/gradient evaluation, PointValues are used in the same way as CellValues, i.e. by using function_value, function_gradient, etc, with the exception that there is no need to specify the quadrature point index (since PointValues only have 1, this is the default).

source
Ferrite.PointIteratorType
PointIterator(ph::PointEvalHandler)

Create an iterator over the points in the PointEvalHandler. The elements of the iterator are either a PointLocation, if the corresponding point could be found in the grid, or nothing, if the point was not found.

A PointLocation can be used to query the cell ID with the cellid function, and can be used to reinitialize PointValues with reinit!.

Examples

ph = PointEvalHandler(grid, points)
+]

Supported data types to project are Numbers and AbstractTensors.

Note

The order of the returned data correspond to the order of the L2Projector's internal DofHandler. The data can be further analyzed with evaluate_at_points and evaluate_at_grid_nodes. Use write_projection to export the result.

source

Evaluation at points

Ferrite.evaluate_at_grid_nodesFunction
evaluate_at_grid_nodes(dh::AbstractDofHandler, u::AbstractVector{T}, fieldname::Symbol) where T

Evaluate the approximated solution for field fieldname at the node coordinates of the grid given the Dof handler dh and the solution vector u.

Return a vector of length getnnodes(grid) where entry i contains the evaluation of the approximation in the coordinate of node i. If the field does not live on parts of the grid, the corresponding values for those nodes will be returned as NaNs.

source
Ferrite.PointEvalHandlerType
PointEvalHandler(grid::Grid, points::AbstractVector{Vec{dim,T}}; kwargs...) where {dim, T}

The PointEvalHandler can be used for function evaluation in arbitrary points in the domain – not just in quadrature points or nodes.

The constructor takes a grid and a vector of coordinates for the points. The PointEvalHandler computes i) the corresponding cell, and ii) the (local) coordinate within the cell, for each point. The fields of the PointEvalHandler are:

  • cells::Vector{Union{Int,Nothing}}: vector with cell IDs for the points, with nothing for points that could not be found.
  • local_coords::Vector{Union{Vec,Nothing}}: vector with the local coordinates (i.e. coordinates in the reference configuration) for the points, with nothing for points that could not be found.

There are two ways to use the PointEvalHandler to evaluate functions:

  • evaluate_at_points: can be used when the function is described by i) a dh::DofHandler + uh::Vector (for example the FE-solution), or ii) a p::L2Projector + ph::Vector (for projected data).
  • Iteration with PointIterator + PointValues: can be used for more flexible evaluation in the points, for example to compute gradients.
source
Ferrite.evaluate_at_pointsFunction
evaluate_at_points(ph::PointEvalHandler, dh::AbstractDofHandler, dof_values::Vector{T}, [fieldname::Symbol]) where T
+evaluate_at_points(ph::PointEvalHandler, proj::L2Projector, dof_values::Vector{T}) where T

Return a Vector{T} (for a 1-dimensional field) or a Vector{Vec{fielddim, T}} (for a vector field) with the field values of field fieldname in the points of the PointEvalHandler. The fieldname can be omitted if only one field is stored in dh. The field values are computed based on the dof_values and interpolated to the local coordinates by the function interpolation of the corresponding field stored in the AbstractDofHandler or the L2Projector.

Points that could not be found in the domain when constructing the PointEvalHandler will have NaNs for the corresponding entries in the output vector.

source
Ferrite.PointValuesType
PointValues(cv::CellValues)
+PointValues([::Type{T}], func_interpol::Interpolation, [geom_interpol::Interpolation])

Similar to CellValues but with a single updateable "quadrature point". PointValues are used for evaluation of functions/gradients in arbitrary points of the domain together with a PointEvalHandler.

PointValues can be created from CellValues, or from the interpolations directly.

PointValues are reinitialized like other CellValues, but since the local reference coordinate of the "quadrature point" changes this needs to be passed to reinit!, in addition to the element coordinates: reinit!(pv, coords, local_coord). Alternatively, it can be reinitialized with a PointLocation when iterating a PointEvalHandler with a PointIterator.

For function/gradient evaluation, PointValues are used in the same way as CellValues, i.e. by using function_value, function_gradient, etc, with the exception that there is no need to specify the quadrature point index (since PointValues only have 1, this is the default).

source
Ferrite.PointIteratorType
PointIterator(ph::PointEvalHandler)

Create an iterator over the points in the PointEvalHandler. The elements of the iterator are either a PointLocation, if the corresponding point could be found in the grid, or nothing, if the point was not found.

A PointLocation can be used to query the cell ID with the cellid function, and can be used to reinitialize PointValues with reinit!.

Examples

ph = PointEvalHandler(grid, points)
 
 for point in PointIterator(ph)
     point === nothing && continue # Skip any points that weren't found
     reinit!(pointvalues, point)   # Update pointvalues
     # ...
-end
source
Ferrite.PointLocationType
PointLocation

Element of a PointIterator, typically used to reinitialize PointValues. Fields:

  • cid::Int: ID of the cell containing the point
  • local_coord::Vec: the local (reference) coordinate of the point
  • coords::Vector{Vec}: the coordinates of the cell
source

VTK export

Ferrite.VTKGridFileType
VTKGridFile(filename::AbstractString, grid::AbstractGrid; kwargs...)
+end
source
Ferrite.PointLocationType
PointLocation

Element of a PointIterator, typically used to reinitialize PointValues. Fields:

  • cid::Int: ID of the cell containing the point
  • local_coord::Vec: the local (reference) coordinate of the point
  • coords::Vector{Vec}: the coordinates of the cell
source

VTK export

Ferrite.VTKGridFileType
VTKGridFile(filename::AbstractString, grid::AbstractGrid; kwargs...)
 VTKGridFile(filename::AbstractString, dh::DofHandler; kwargs...)

Create a VTKGridFile that contains an unstructured VTK grid. The keyword arguments are forwarded to WriteVTK.vtk_grid, see Data Formatting Options

This file handler can be used to to write data with

It is necessary to call close(::VTKGridFile) to save the data after writing to the file handler. Using the supported do-block does this automatically:

VTKGridFile(filename, grid) do vtk
     write_solution(vtk, dh, u)
     write_cell_data(vtk, celldata)
-end
source
Ferrite.write_solutionFunction
write_solution(vtk::VTKGridFile, dh::AbstractDofHandler, u::Vector, suffix="")

Save the values at the nodes in the degree of freedom vector u to vtk. Each field in dh will be saved separately, and suffix can be used to append to the fieldname.

u can also contain tensorial values, but each entry in u must correspond to a degree of freedom in dh, see write_node_data for details. Use write_node_data directly when exporting values that are already sorted by the nodes in the grid.

source
Ferrite.write_projectionFunction
write_projection(vtk::VTKGridFile, proj::L2Projector, vals::Vector, name::AbstractString)

Project vals to the grid nodes with proj and save to vtk.

source
Ferrite.write_cell_dataFunction
write_cell_data(vtk::VTKGridFile, celldata::AbstractVector, name::String)

Write the celldata that is ordered by the cells in the grid to the vtk file.

source
Ferrite.write_node_dataFunction
write_node_data(vtk::VTKGridFile, nodedata::Vector{Real}, name)
-write_node_data(vtk::VTKGridFile, nodedata::Vector{<:AbstractTensor}, name)

Write the nodedata that is ordered by the nodes in the grid to vtk.

When nodedata contains Tensors.Vecs, each component is exported. Two-dimensional vectors are padded with zeros.

When nodedata contains second order tensors, the index order, [11, 22, 33, 23, 13, 12, 32, 31, 21], follows the default Voigt order in Tensors.jl.

source
Ferrite.write_cellsetFunction
write_cellset(vtk, grid::AbstractGrid)
+end
source
Ferrite.write_solutionFunction
write_solution(vtk::VTKGridFile, dh::AbstractDofHandler, u::Vector, suffix="")

Save the values at the nodes in the degree of freedom vector u to vtk. Each field in dh will be saved separately, and suffix can be used to append to the fieldname.

u can also contain tensorial values, but each entry in u must correspond to a degree of freedom in dh, see write_node_data for details. Use write_node_data directly when exporting values that are already sorted by the nodes in the grid.

source
Ferrite.write_projectionFunction
write_projection(vtk::VTKGridFile, proj::L2Projector, vals::Vector, name::AbstractString)

Project vals to the grid nodes with proj and save to vtk.

source
Ferrite.write_cell_dataFunction
write_cell_data(vtk::VTKGridFile, celldata::AbstractVector, name::String)

Write the celldata that is ordered by the cells in the grid to the vtk file.

source
Ferrite.write_node_dataFunction
write_node_data(vtk::VTKGridFile, nodedata::Vector{Real}, name)
+write_node_data(vtk::VTKGridFile, nodedata::Vector{<:AbstractTensor}, name)

Write the nodedata that is ordered by the nodes in the grid to vtk.

When nodedata contains Tensors.Vecs, each component is exported. Two-dimensional vectors are padded with zeros.

When nodedata contains second order tensors, the index order, [11, 22, 33, 23, 13, 12, 32, 31, 21], follows the default Voigt order in Tensors.jl.

source
Ferrite.write_cellsetFunction
write_cellset(vtk, grid::AbstractGrid)
 write_cellset(vtk, grid::AbstractGrid, cellset::String)
-write_cellset(vtk, grid::AbstractGrid, cellsets::Union{AbstractVector{String},AbstractSet{String})

Write all cell sets in the grid with name according to their keys and celldata 1 if the cell is in the set, and 0 otherwise. It is also possible to only export a single cellset, or multiple cellsets.

source
Ferrite.write_nodesetFunction
write_nodeset(vtk::VTKGridFile, grid::AbstractGrid, nodeset::String)

Write nodal values of 1 for nodes in nodeset, and 0 otherwise

source
Ferrite.write_constraintsFunction
write_constraints(vtk::VTKGridFile, ch::ConstraintHandler)

Saves the dirichlet boundary conditions to a vtkfile. Values will have a 1 where bcs are active and 0 otherwise

source
Ferrite.write_cell_colorsFunction
write_cell_colors(vtk::VTKGridFile, grid::AbstractGrid, cell_colors, name="coloring")

Write cell colors (see create_coloring) to a VTK file for visualization.

In case of coloring a subset, the cells which are not part of the subset are represented as color 0.

source
+write_cellset(vtk, grid::AbstractGrid, cellsets::Union{AbstractVector{String},AbstractSet{String})

Write all cell sets in the grid with name according to their keys and celldata 1 if the cell is in the set, and 0 otherwise. It is also possible to only export a single cellset, or multiple cellsets.

source
Ferrite.write_nodesetFunction
write_nodeset(vtk::VTKGridFile, grid::AbstractGrid, nodeset::String)

Write nodal values of 1 for nodes in nodeset, and 0 otherwise

source
Ferrite.write_constraintsFunction
write_constraints(vtk::VTKGridFile, ch::ConstraintHandler)

Saves the dirichlet boundary conditions to a vtkfile. Values will have a 1 where bcs are active and 0 otherwise

source
Ferrite.write_cell_colorsFunction
write_cell_colors(vtk::VTKGridFile, grid::AbstractGrid, cell_colors, name="coloring")

Write cell colors (see create_coloring) to a VTK file for visualization.

In case of coloring a subset, the cells which are not part of the subset are represented as color 0.

source
diff --git a/previews/PR1096/reference/fevalues/index.html b/previews/PR1096/reference/fevalues/index.html index 81fbb3018c..c080aba84c 100644 --- a/previews/PR1096/reference/fevalues/index.html +++ b/previews/PR1096/reference/fevalues/index.html @@ -1,11 +1,11 @@ -FEValues · Ferrite.jl

FEValues

Main types

CellValues and FacetValues are the most common subtypes of Ferrite.AbstractValues. For more details about how these work, please see the related topic guide.

Ferrite.CellValuesType
CellValues([::Type{T},] quad_rule::QuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])

A CellValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. in the finite element cell.

Arguments:

  • T: an optional argument (default to Float64) to determine the type the internal data is stored as.
  • quad_rule: an instance of a QuadratureRule
  • func_interpol: an instance of an Interpolation used to interpolate the approximated function
  • geom_interpol: an optional instance of a Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used. For embedded elements the geometric interpolations should be vectorized to the spatial dimension.

Keyword arguments: The following keyword arguments are experimental and may change in future minor releases

  • update_gradients: Specifies if the gradients of the shape functions should be updated (default true)
  • update_hessians: Specifies if the hessians of the shape functions should be updated (default false)
  • update_detJdV: Specifies if the volume associated with each quadrature point should be updated (default true)

Common methods:

source
Ferrite.FacetValuesType
FacetValues([::Type{T}], quad_rule::FacetQuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])

A FacetValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. on the facets of finite elements.

Arguments:

  • T: an optional argument (default to Float64) to determine the type the internal data is stored as.
  • quad_rule: an instance of a FacetQuadratureRule
  • func_interpol: an instance of an Interpolation used to interpolate the approximated function
  • geom_interpol: an optional instance of an Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used.

Keyword arguments: The following keyword arguments are experimental and may change in future minor releases

  • update_gradients: Specifies if the gradients of the shape functions should be updated (default true)
  • update_hessians: Specifies if the hessians of the shape functions should be updated (default false)

Common methods:

source
Embedded API

Currently, embedded FEValues returns SArrays, which behave differently from the Tensors for normal value. In the future, we expect to return an AbstractTensor, this change may happen in a minor release, and the API for embedded FEValues should therefore be considered experimental.

Applicable functions

The following functions are applicable to both CellValues and FacetValues.

Ferrite.reinit!Function
reinit!(cv::CellValues, cell::AbstractCell, x::AbstractVector)
+FEValues · Ferrite.jl

FEValues

Main types

CellValues and FacetValues are the most common subtypes of Ferrite.AbstractValues. For more details about how these work, please see the related topic guide.

Ferrite.CellValuesType
CellValues([::Type{T},] quad_rule::QuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])

A CellValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. in the finite element cell.

Arguments:

  • T: an optional argument (default to Float64) to determine the type the internal data is stored as.
  • quad_rule: an instance of a QuadratureRule
  • func_interpol: an instance of an Interpolation used to interpolate the approximated function
  • geom_interpol: an optional instance of a Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used. For embedded elements the geometric interpolations should be vectorized to the spatial dimension.

Keyword arguments: The following keyword arguments are experimental and may change in future minor releases

  • update_gradients: Specifies if the gradients of the shape functions should be updated (default true)
  • update_hessians: Specifies if the hessians of the shape functions should be updated (default false)
  • update_detJdV: Specifies if the volume associated with each quadrature point should be updated (default true)

Common methods:

source
Ferrite.FacetValuesType
FacetValues([::Type{T}], quad_rule::FacetQuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])

A FacetValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. on the facets of finite elements.

Arguments:

  • T: an optional argument (default to Float64) to determine the type the internal data is stored as.
  • quad_rule: an instance of a FacetQuadratureRule
  • func_interpol: an instance of an Interpolation used to interpolate the approximated function
  • geom_interpol: an optional instance of an Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used.

Keyword arguments: The following keyword arguments are experimental and may change in future minor releases

  • update_gradients: Specifies if the gradients of the shape functions should be updated (default true)
  • update_hessians: Specifies if the hessians of the shape functions should be updated (default false)

Common methods:

source
Embedded API

Currently, embedded FEValues returns SArrays, which behave differently from the Tensors for normal value. In the future, we expect to return an AbstractTensor, this change may happen in a minor release, and the API for embedded FEValues should therefore be considered experimental.

Applicable functions

The following functions are applicable to both CellValues and FacetValues.

Ferrite.reinit!Function
reinit!(cv::CellValues, cell::AbstractCell, x::AbstractVector)
 reinit!(cv::CellValues, x::AbstractVector)
 reinit!(fv::FacetValues, cell::AbstractCell, x::AbstractVector, facet::Int)
-reinit!(fv::FacetValues, x::AbstractVector, function_gradient::Int)

Update the CellValues/FacetValues object for a cell or facet with cell coordinates x. The derivatives of the shape functions, and the new integration weights are computed. For interpolations with non-identity mappings, the current cell is also required.

source
Ferrite.getnquadpointsFunction
getnquadpoints(fe_v::AbstractValues)

Return the number of quadrature points. For FacetValues, this is the number for the current facet.

source
Ferrite.getdetJdVFunction
getdetJdV(fe_v::AbstractValues, q_point::Int)

Return the product between the determinant of the Jacobian and the quadrature point weight for the given quadrature point: $\det(J(\mathbf{x})) w_q$.

This value is typically used when one integrates a function on a finite element cell or facet as

$\int\limits_\Omega f(\mathbf{x}) d \Omega \approx \sum\limits_{q = 1}^{n_q} f(\mathbf{x}_q) \det(J(\mathbf{x})) w_q$ $\int\limits_\Gamma f(\mathbf{x}) d \Gamma \approx \sum\limits_{q = 1}^{n_q} f(\mathbf{x}_q) \det(J(\mathbf{x})) w_q$

source
Ferrite.shape_valueMethod
shape_value(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the value of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_gradientMethod
shape_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the gradient of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_symmetric_gradientFunction
shape_symmetric_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the symmetric gradient of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_divergenceFunction
shape_divergence(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the divergence of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_curlFunction
shape_curl(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the curl of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.geometric_valueFunction
geometric_value(fe_v::AbstractValues, q_point, base_function::Int)

Return the value of the geometric shape function base_function evaluated in quadrature point q_point.

source
Ferrite.function_valueFunction
function_value(iv::InterfaceValues, q_point::Int, u; here::Bool)
-function_value(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)

Compute the value of the function in quadrature point q_point on the "here" (here=true) or "there" (here=false) side of the interface. u_here and u_there are the values of the degrees of freedom for the respective element.

u is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.

here determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.

The value of a scalar valued function is computed as $u(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) u_i$ where $u_i$ are the value of $u$ in the nodes. For a vector valued function the value is calculated as $\mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
function_value(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the value of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).

The value of a scalar valued function is computed as $u(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) u_i$ where $u_i$ are the value of $u$ in the nodes. For a vector valued function the value is calculated as $\mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
Ferrite.function_gradientFunction
function_gradient(iv::InterfaceValues, q_point::Int, u; here::Bool)
-function_gradient(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)

Compute the gradient of the function in a quadrature point. u is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.

here determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.

The gradient of a scalar function or a vector valued function with use of VectorValues is computed as $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i (\mathbf{x}) u_i$ or $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} \mathbf{N}_i (\mathbf{x}) u_i$ respectively, where $u_i$ are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as $\mathbf{\nabla} \mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{u}_i \otimes \mathbf{\nabla} N_i (\mathbf{x})$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
function_gradient(fe_v::AbstractValues{dim}, q_point::Int, u::AbstractVector, [dof_range])

Compute the gradient of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).

The gradient of a scalar function or a vector valued function with use of VectorValues is computed as $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i (\mathbf{x}) u_i$ or $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} \mathbf{N}_i (\mathbf{x}) u_i$ respectively, where $u_i$ are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as $\mathbf{\nabla} \mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{u}_i \otimes \mathbf{\nabla} N_i (\mathbf{x})$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
Ferrite.function_symmetric_gradientFunction
function_symmetric_gradient(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the symmetric gradient of the function, see function_gradient. Return a SymmetricTensor.

The symmetric gradient of a scalar function is computed as $\left[ \mathbf{\nabla} \mathbf{u}(\mathbf{x_q}) \right]^\text{sym} = \sum\limits_{i = 1}^n \frac{1}{2} \left[ \mathbf{\nabla} N_i (\mathbf{x}_q) \otimes \mathbf{u}_i + \mathbf{u}_i \otimes \mathbf{\nabla} N_i (\mathbf{x}_q) \right]$ where $\mathbf{u}_i$ are the nodal values of the function.

source
Ferrite.function_divergenceFunction
function_divergence(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the divergence of the vector valued function in a quadrature point.

The divergence of a vector valued functions in the quadrature point $\mathbf{x}_q)$ is computed as $\mathbf{\nabla} \cdot \mathbf{u}(\mathbf{x_q}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i (\mathbf{x_q}) \cdot \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of the function.

source
Ferrite.function_curlFunction
function_curl(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the curl of the vector valued function in a quadrature point.

The curl of a vector valued functions in the quadrature point $\mathbf{x}_q)$ is computed as $\mathbf{\nabla} \times \mathbf{u}(\mathbf{x_q}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i \times (\mathbf{x_q}) \cdot \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of the function.

source
Ferrite.spatial_coordinateFunction
spatial_coordinate(fe_v::AbstractValues, q_point::Int, x::AbstractVector)

Compute the spatial coordinate in a quadrature point. x contains the nodal coordinates of the cell.

The coordinate is computed, using the geometric interpolation, as $\mathbf{x} = \sum\limits_{i = 1}^n M_i (\mathbf{\xi}) \mathbf{\hat{x}}_i$.

where $\xi$is the coordinate of the given quadrature point q_point of the associated quadrature rule.

source
spatial_coordinate(ip::ScalarInterpolation, ξ::Vec, x::AbstractVector{<:Vec{sdim, T}})

Compute the spatial coordinate in a given quadrature point. x contains the nodal coordinates of the cell.

The coordinate is computed, using the geometric interpolation, as $\mathbf{x} = \sum\limits_{i = 1}^n M_i (\mathbf{\xi}) \mathbf{\hat{x}}_i$

source

In addition, there are some methods that are unique for FacetValues.

Ferrite.getcurrentfacetFunction
getcurrentfacet(fv::FacetValues)

Return the current active facet of the FacetValues object (from last reinit!).

source
Ferrite.getnormalFunction
getnormal(fv::FacetValues, qp::Int)

Return the normal at the quadrature point qp for the active facet of the FacetValues object(from last reinit!).

source
getnormal(iv::InterfaceValues, qp::Int; here::Bool=true)

Return the normal vector in the quadrature point qp on the interface. If here = true (default) the outward normal to the "here" element is returned, otherwise the outward normal to the "there" element.

source

InterfaceValues

All of the methods for FacetValues apply for InterfaceValues as well. In addition, there are some methods that are unique for InterfaceValues:

Ferrite.InterfaceValuesType
InterfaceValues

An InterfaceValues object facilitates the process of evaluating values, averages, jumps and gradients of shape functions and function on the interfaces between elements.

The first element of the interface is denoted "here" and the second element "there".

Constructors

  • InterfaceValues(qr::FacetQuadratureRule, ip::Interpolation): same quadrature rule and interpolation on both sides, default linear Lagrange geometric interpolation.
  • InterfaceValues(qr::FacetQuadratureRule, ip::Interpolation, ip_geo::Interpolation): same as above but with given geometric interpolation.
  • InterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation): different quadrature rule and interpolation on the two sides, default linear Lagrange geometric interpolation.
  • InterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, ip_geo_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation, ip_geo_there::Interpolation): same as above but with given geometric interpolation.
  • InterfaceValues(fv::FacetValues): quadrature rule and interpolations from face values (same on both sides).
  • InterfaceValues(fv_here::FacetValues, fv_there::FacetValues): quadrature rule and interpolations from the face values.

Associated methods:

Common methods:

source
Ferrite.shape_value_averageFunction
shape_value_average(iv::InterfaceValues, qp::Int, i::Int)

Compute the average of the value of shape function i at quadrature point qp across the interface.

source
Ferrite.shape_value_jumpFunction
shape_value_jump(iv::InterfaceValues, qp::Int, i::Int)

Compute the jump of the value of shape function i at quadrature point qp across the interface in the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} \cdot \vec{n}^\text{there} + \vec{v}^\text{here} \cdot \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
Ferrite.shape_gradient_averageFunction
shape_gradient_average(iv::InterfaceValues, qp::Int, i::Int)

Compute the average of the gradient of shape function i at quadrature point qp across the interface.

source
Ferrite.shape_gradient_jumpFunction
shape_gradient_jump(iv::InterfaceValues, qp::Int, i::Int)

Compute the jump of the gradient of shape function i at quadrature point qp across the interface in the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} ⋅ \vec{n}^\text{there} + \vec{v}^\text{here} ⋅ \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
Ferrite.function_value_averageFunction
function_value_average(iv::InterfaceValues, q_point::Int, u)
-function_value_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the average of the function value at the quadrature point on the interface.

source
Ferrite.function_value_jumpFunction
function_value_jump(iv::InterfaceValues, q_point::Int, u)
-function_value_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the jump of the function value at the quadrature point over the interface along the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} ⋅ \vec{n}^\text{there} + \vec{v}^\text{here} ⋅ \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
Ferrite.function_gradient_averageFunction
function_gradient_average(iv::InterfaceValues, q_point::Int, u)
-function_gradient_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the average of the function gradient at the quadrature point on the interface.

source
Ferrite.function_gradient_jumpFunction
function_gradient_jump(iv::InterfaceValues, q_point::Int, u)
-function_gradient_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the jump of the function gradient at the quadrature point over the interface along the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} ⋅ \vec{n}^\text{there} + \vec{v}^\text{here} ⋅ \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
+reinit!(fv::FacetValues, x::AbstractVector, function_gradient::Int)

Update the CellValues/FacetValues object for a cell or facet with cell coordinates x. The derivatives of the shape functions, and the new integration weights are computed. For interpolations with non-identity mappings, the current cell is also required.

source
Ferrite.getnquadpointsFunction
getnquadpoints(fe_v::AbstractValues)

Return the number of quadrature points. For FacetValues, this is the number for the current facet.

source
Ferrite.getdetJdVFunction
getdetJdV(fe_v::AbstractValues, q_point::Int)

Return the product between the determinant of the Jacobian and the quadrature point weight for the given quadrature point: $\det(J(\mathbf{x})) w_q$.

This value is typically used when one integrates a function on a finite element cell or facet as

$\int\limits_\Omega f(\mathbf{x}) d \Omega \approx \sum\limits_{q = 1}^{n_q} f(\mathbf{x}_q) \det(J(\mathbf{x})) w_q$ $\int\limits_\Gamma f(\mathbf{x}) d \Gamma \approx \sum\limits_{q = 1}^{n_q} f(\mathbf{x}_q) \det(J(\mathbf{x})) w_q$

source
Ferrite.shape_valueMethod
shape_value(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the value of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_gradientMethod
shape_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the gradient of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_symmetric_gradientFunction
shape_symmetric_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the symmetric gradient of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_divergenceFunction
shape_divergence(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the divergence of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.shape_curlFunction
shape_curl(fe_v::AbstractValues, q_point::Int, base_function::Int)

Return the curl of shape function base_function evaluated in quadrature point q_point.

source
Ferrite.geometric_valueFunction
geometric_value(fe_v::AbstractValues, q_point, base_function::Int)

Return the value of the geometric shape function base_function evaluated in quadrature point q_point.

source
Ferrite.function_valueFunction
function_value(iv::InterfaceValues, q_point::Int, u; here::Bool)
+function_value(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)

Compute the value of the function in quadrature point q_point on the "here" (here=true) or "there" (here=false) side of the interface. u_here and u_there are the values of the degrees of freedom for the respective element.

u is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.

here determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.

The value of a scalar valued function is computed as $u(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) u_i$ where $u_i$ are the value of $u$ in the nodes. For a vector valued function the value is calculated as $\mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
function_value(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the value of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).

The value of a scalar valued function is computed as $u(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) u_i$ where $u_i$ are the value of $u$ in the nodes. For a vector valued function the value is calculated as $\mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n N_i (\mathbf{x}) \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
Ferrite.function_gradientFunction
function_gradient(iv::InterfaceValues, q_point::Int, u; here::Bool)
+function_gradient(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)

Compute the gradient of the function in a quadrature point. u is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.

here determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.

The gradient of a scalar function or a vector valued function with use of VectorValues is computed as $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i (\mathbf{x}) u_i$ or $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} \mathbf{N}_i (\mathbf{x}) u_i$ respectively, where $u_i$ are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as $\mathbf{\nabla} \mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{u}_i \otimes \mathbf{\nabla} N_i (\mathbf{x})$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
function_gradient(fe_v::AbstractValues{dim}, q_point::Int, u::AbstractVector, [dof_range])

Compute the gradient of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).

The gradient of a scalar function or a vector valued function with use of VectorValues is computed as $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i (\mathbf{x}) u_i$ or $\mathbf{\nabla} u(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{\nabla} \mathbf{N}_i (\mathbf{x}) u_i$ respectively, where $u_i$ are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as $\mathbf{\nabla} \mathbf{u}(\mathbf{x}) = \sum\limits_{i = 1}^n \mathbf{u}_i \otimes \mathbf{\nabla} N_i (\mathbf{x})$ where $\mathbf{u}_i$ are the nodal values of $\mathbf{u}$.

source
Ferrite.function_symmetric_gradientFunction
function_symmetric_gradient(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the symmetric gradient of the function, see function_gradient. Return a SymmetricTensor.

The symmetric gradient of a scalar function is computed as $\left[ \mathbf{\nabla} \mathbf{u}(\mathbf{x_q}) \right]^\text{sym} = \sum\limits_{i = 1}^n \frac{1}{2} \left[ \mathbf{\nabla} N_i (\mathbf{x}_q) \otimes \mathbf{u}_i + \mathbf{u}_i \otimes \mathbf{\nabla} N_i (\mathbf{x}_q) \right]$ where $\mathbf{u}_i$ are the nodal values of the function.

source
Ferrite.function_divergenceFunction
function_divergence(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the divergence of the vector valued function in a quadrature point.

The divergence of a vector valued functions in the quadrature point $\mathbf{x}_q)$ is computed as $\mathbf{\nabla} \cdot \mathbf{u}(\mathbf{x_q}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i (\mathbf{x_q}) \cdot \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of the function.

source
Ferrite.function_curlFunction
function_curl(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])

Compute the curl of the vector valued function in a quadrature point.

The curl of a vector valued functions in the quadrature point $\mathbf{x}_q)$ is computed as $\mathbf{\nabla} \times \mathbf{u}(\mathbf{x_q}) = \sum\limits_{i = 1}^n \mathbf{\nabla} N_i \times (\mathbf{x_q}) \cdot \mathbf{u}_i$ where $\mathbf{u}_i$ are the nodal values of the function.

source
Ferrite.spatial_coordinateFunction
spatial_coordinate(fe_v::AbstractValues, q_point::Int, x::AbstractVector)

Compute the spatial coordinate in a quadrature point. x contains the nodal coordinates of the cell.

The coordinate is computed, using the geometric interpolation, as $\mathbf{x} = \sum\limits_{i = 1}^n M_i (\mathbf{\xi}) \mathbf{\hat{x}}_i$.

where $\xi$is the coordinate of the given quadrature point q_point of the associated quadrature rule.

source
spatial_coordinate(ip::ScalarInterpolation, ξ::Vec, x::AbstractVector{<:Vec{sdim, T}})

Compute the spatial coordinate in a given quadrature point. x contains the nodal coordinates of the cell.

The coordinate is computed, using the geometric interpolation, as $\mathbf{x} = \sum\limits_{i = 1}^n M_i (\mathbf{\xi}) \mathbf{\hat{x}}_i$

source

In addition, there are some methods that are unique for FacetValues.

Ferrite.getcurrentfacetFunction
getcurrentfacet(fv::FacetValues)

Return the current active facet of the FacetValues object (from last reinit!).

source
Ferrite.getnormalFunction
getnormal(fv::FacetValues, qp::Int)

Return the normal at the quadrature point qp for the active facet of the FacetValues object(from last reinit!).

source
getnormal(iv::InterfaceValues, qp::Int; here::Bool=true)

Return the normal vector in the quadrature point qp on the interface. If here = true (default) the outward normal to the "here" element is returned, otherwise the outward normal to the "there" element.

source

InterfaceValues

All of the methods for FacetValues apply for InterfaceValues as well. In addition, there are some methods that are unique for InterfaceValues:

Ferrite.InterfaceValuesType
InterfaceValues

An InterfaceValues object facilitates the process of evaluating values, averages, jumps and gradients of shape functions and function on the interfaces between elements.

The first element of the interface is denoted "here" and the second element "there".

Constructors

  • InterfaceValues(qr::FacetQuadratureRule, ip::Interpolation): same quadrature rule and interpolation on both sides, default linear Lagrange geometric interpolation.
  • InterfaceValues(qr::FacetQuadratureRule, ip::Interpolation, ip_geo::Interpolation): same as above but with given geometric interpolation.
  • InterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation): different quadrature rule and interpolation on the two sides, default linear Lagrange geometric interpolation.
  • InterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, ip_geo_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation, ip_geo_there::Interpolation): same as above but with given geometric interpolation.
  • InterfaceValues(fv::FacetValues): quadrature rule and interpolations from face values (same on both sides).
  • InterfaceValues(fv_here::FacetValues, fv_there::FacetValues): quadrature rule and interpolations from the face values.

Associated methods:

Common methods:

source
Ferrite.shape_value_averageFunction
shape_value_average(iv::InterfaceValues, qp::Int, i::Int)

Compute the average of the value of shape function i at quadrature point qp across the interface.

source
Ferrite.shape_value_jumpFunction
shape_value_jump(iv::InterfaceValues, qp::Int, i::Int)

Compute the jump of the value of shape function i at quadrature point qp across the interface in the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} \cdot \vec{n}^\text{there} + \vec{v}^\text{here} \cdot \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
Ferrite.shape_gradient_averageFunction
shape_gradient_average(iv::InterfaceValues, qp::Int, i::Int)

Compute the average of the gradient of shape function i at quadrature point qp across the interface.

source
Ferrite.shape_gradient_jumpFunction
shape_gradient_jump(iv::InterfaceValues, qp::Int, i::Int)

Compute the jump of the gradient of shape function i at quadrature point qp across the interface in the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} ⋅ \vec{n}^\text{there} + \vec{v}^\text{here} ⋅ \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
Ferrite.function_value_averageFunction
function_value_average(iv::InterfaceValues, q_point::Int, u)
+function_value_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the average of the function value at the quadrature point on the interface.

source
Ferrite.function_value_jumpFunction
function_value_jump(iv::InterfaceValues, q_point::Int, u)
+function_value_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the jump of the function value at the quadrature point over the interface along the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} ⋅ \vec{n}^\text{there} + \vec{v}^\text{here} ⋅ \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
Ferrite.function_gradient_averageFunction
function_gradient_average(iv::InterfaceValues, q_point::Int, u)
+function_gradient_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the average of the function gradient at the quadrature point on the interface.

source
Ferrite.function_gradient_jumpFunction
function_gradient_jump(iv::InterfaceValues, q_point::Int, u)
+function_gradient_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)

Compute the jump of the function gradient at the quadrature point over the interface along the default normal direction.

This function uses the definition $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} -\vec{v}^\text{here}$. To obtain the form, $\llbracket \vec{v} \rrbracket=\vec{v}^\text{there} ⋅ \vec{n}^\text{there} + \vec{v}^\text{here} ⋅ \vec{n}^\text{here}$, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).

source
diff --git a/previews/PR1096/reference/grid/index.html b/previews/PR1096/reference/grid/index.html index 34a940d589..e0506745f3 100644 --- a/previews/PR1096/reference/grid/index.html +++ b/previews/PR1096/reference/grid/index.html @@ -1,26 +1,26 @@ -Grid & AbstractGrid · Ferrite.jl

Grid & AbstractGrid

Grid

Ferrite.generate_gridFunction
generate_grid(celltype::Cell, nel::NTuple, [left::Vec, right::Vec)

Return a Grid for a rectangle in 1, 2 or 3 dimensions. celltype defined the type of cells, e.g. Triangle or Hexahedron. nel is a tuple of the number of elements in each direction. left and right are optional endpoints of the domain. Defaults to -1 and 1 in all directions.

source
Ferrite.NodeType
Node{dim, T}

A Node is a point in space.

Fields

  • x::Vec{dim,T}: stores the coordinates
source
Ferrite.VertexIndexType

A VertexIndex wraps an (Int, Int) and defines a local vertex by pointing to a (cell, vert).

source
Ferrite.EdgeIndexType

A EdgeIndex wraps an (Int, Int) and defines a local edge by pointing to a (cell, edge).

source
Ferrite.FaceIndexType

A FaceIndex wraps an (Int, Int) and defines a local face by pointing to a (cell, face).

source
Ferrite.FacetIndexType

A FacetIndex wraps an (Int, Int) and defines a local facet by pointing to a (cell, facet).

source
Ferrite.GridType
Grid{dim, C<:AbstractCell, T<:Real} <: AbstractGrid}

A Grid is a collection of Ferrite.AbstractCells and Ferrite.Nodes which covers the computational domain. Helper structures for applying boundary conditions or define subdomains are gathered in cellsets, nodesets, facetsets, and vertexsets.

Fields

  • cells::Vector{C}: stores all cells of the grid
  • nodes::Vector{Node{dim,T}}: stores the dim dimensional nodes of the grid
  • cellsets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of cell ids
  • nodesets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of global node ids
  • facetsets::Dict{String, OrderedSet{FacetIndex}}: maps a String to an OrderedSet of FacetIndex
  • vertexsets::Dict{String, OrderedSet{VertexIndex}}: maps a String key to an OrderedSet of VertexIndex
source

Utility Functions

Ferrite.getcellsFunction
getcells(grid::AbstractGrid)
+Grid & AbstractGrid · Ferrite.jl

Grid & AbstractGrid

Grid

Ferrite.generate_gridFunction
generate_grid(celltype::Cell, nel::NTuple, [left::Vec, right::Vec)

Return a Grid for a rectangle in 1, 2 or 3 dimensions. celltype defined the type of cells, e.g. Triangle or Hexahedron. nel is a tuple of the number of elements in each direction. left and right are optional endpoints of the domain. Defaults to -1 and 1 in all directions.

source
Ferrite.NodeType
Node{dim, T}

A Node is a point in space.

Fields

  • x::Vec{dim,T}: stores the coordinates
source
Ferrite.VertexIndexType

A VertexIndex wraps an (Int, Int) and defines a local vertex by pointing to a (cell, vert).

source
Ferrite.EdgeIndexType

A EdgeIndex wraps an (Int, Int) and defines a local edge by pointing to a (cell, edge).

source
Ferrite.FaceIndexType

A FaceIndex wraps an (Int, Int) and defines a local face by pointing to a (cell, face).

source
Ferrite.FacetIndexType

A FacetIndex wraps an (Int, Int) and defines a local facet by pointing to a (cell, facet).

source
Ferrite.GridType
Grid{dim, C<:AbstractCell, T<:Real} <: AbstractGrid}

A Grid is a collection of Ferrite.AbstractCells and Ferrite.Nodes which covers the computational domain. Helper structures for applying boundary conditions or define subdomains are gathered in cellsets, nodesets, facetsets, and vertexsets.

Fields

  • cells::Vector{C}: stores all cells of the grid
  • nodes::Vector{Node{dim,T}}: stores the dim dimensional nodes of the grid
  • cellsets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of cell ids
  • nodesets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of global node ids
  • facetsets::Dict{String, OrderedSet{FacetIndex}}: maps a String to an OrderedSet of FacetIndex
  • vertexsets::Dict{String, OrderedSet{VertexIndex}}: maps a String key to an OrderedSet of VertexIndex
source

Utility Functions

Ferrite.getcellsFunction
getcells(grid::AbstractGrid)
 getcells(grid::AbstractGrid, v::Union{Int,Vector{Int}}
-getcells(grid::AbstractGrid, setname::String)

Returns either all cells::Collection{C<:AbstractCell} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. Whereas the last option tries to call a cellset of the grid. Collection can be any indexable type, for Grid it is Vector{C<:AbstractCell}.

source
Ferrite.getnodesFunction
getnodes(grid::AbstractGrid)
+getcells(grid::AbstractGrid, setname::String)

Returns either all cells::Collection{C<:AbstractCell} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. Whereas the last option tries to call a cellset of the grid. Collection can be any indexable type, for Grid it is Vector{C<:AbstractCell}.

source
Ferrite.getnodesFunction
getnodes(grid::AbstractGrid)
 getnodes(grid::AbstractGrid, v::Union{Int,Vector{Int}}
-getnodes(grid::AbstractGrid, setname::String)

Returns either all nodes::Collection{N} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. The last option tries to call a nodeset of the <:AbstractGrid. Collection{N} refers to some indexable collection where each element corresponds to a Node.

source
Ferrite.getcellsetFunction
getcellset(grid::AbstractGrid, setname::String)

Returns all cells as cellid in the set with name setname.

source
Ferrite.getnodesetFunction
getnodeset(grid::AbstractGrid, setname::String)

Returns all nodes as nodeid in the set with name setname.

source
Ferrite.getfacetsetFunction
getfacetset(grid::AbstractGrid, setname::String)

Returns all faces as FacetIndex in the set with name setname.

source
Ferrite.getvertexsetFunction
getvertexset(grid::AbstractGrid, setname::String)

Returns all vertices as VertexIndex in the set with name setname.

source
Ferrite.transform_coordinates!Function
transform_coordinates!(grid::Abstractgrid, f::Function)

Transform the coordinates of all nodes of the grid based on some transformation function f(x).

source
Ferrite.getcoordinatesFunction
getcoordinates(grid::AbstractGrid, idx::Union{Int,CellIndex})
-getcoordinates(cache::CellCache)

Get a vector with the coordinates of the cell corresponding to idx or cache

source
Ferrite.getcoordinates!Function
getcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, idx::Union{Int,CellIndex})
-getcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, cell::AbstractCell)

Mutate x to the coordinates of the cell corresponding to idx or cell.

source
Ferrite.geometric_interpolationMethod
geometric_interpolation(::AbstractCell)::ScalarInterpolation
-geometric_interpolation(::Type{<:AbstractCell})::ScalarInterpolation

Each AbstractCell type has a unique geometric interpolation describing its geometry. This function returns that interpolation, which is always a scalar interpolation.

source
Ferrite.get_node_coordinateFunction
get_node_coordinate(::Node)

Get the value of the node coordinate.

source
get_node_coordinate(grid::AbstractGrid, n::Int)

Return the coordinate of the nth node in grid

source
Ferrite.getspatialdimMethod
Ferrite.getspatialdim(grid::AbstractGrid)

Get the spatial dimension of the grid, corresponding to the vector dimension of the grid's coordinates.

source
Ferrite.getrefdimMethod
Ferrite.getrefdim(cell::AbstractCell)
-Ferrite.getrefdim(::Type{<:AbstractCell})

Get the reference dimension of the cell, i.e. the dimension of the cell's reference shape.

source

Topology

Ferrite.ExclusiveTopologyType
ExclusiveTopology(grid::AbstractGrid)

The experimental feature ExclusiveTopology saves topological (connectivity/neighborhood) data of the grid. Only the highest dimensional neighborhood is saved. I.e., if something is connected by a face and an edge, only the face neighborhood is saved. The lower dimensional neighborhood is recomputed when calling getneighborhood if needed.

Fields

  • vertex_to_cell::AbstractArray{AbstractVector{Int}, 1}: global vertex id to all cells containing the vertex
  • cell_neighbor::AbstractArray{AbstractVector{Int}, 1}: cellid to all connected cells
  • face_neighbor::AbstractArray{AbstractVector{FaceIndex}, 2}: face_neighbor[cellid, local_face_id] -> neighboring faces
  • edge_neighbor::AbstractArray{AbstractVector{EdgeIndex}, 2}: edge_neighbor[cellid, local_edge_id] -> neighboring edges
  • vertex_neighbor::AbstractArray{AbstractVector{VertexIndex}, 2}: vertex_neighbor[cellid, local_vertex_id] -> neighboring vertices
  • face_skeleton::Union{Vector{FaceIndex}, Nothing}: List of unique faces in the grid given as FaceIndex
  • edge_skeleton::Union{Vector{EdgeIndex}, Nothing}: List of unique edges in the grid given as EdgeIndex
  • vertex_skeleton::Union{Vector{VertexIndex}, Nothing}: List of unique vertices in the grid given as VertexIndex
Limitations

The implementation only works with conforming grids, i.e. grids without "hanging nodes". Non-conforming grids will give unexpected results. Grids with embedded cells (different reference dimension compared to the spatial dimension) are not supported, and will error on construction.

source
Ferrite.getneighborhoodFunction
getneighborhood(topology, grid::AbstractGrid, cellidx::CellIndex, include_self=false)
+getnodes(grid::AbstractGrid, setname::String)

Returns either all nodes::Collection{N} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. The last option tries to call a nodeset of the <:AbstractGrid. Collection{N} refers to some indexable collection where each element corresponds to a Node.

source
Ferrite.getcellsetFunction
getcellset(grid::AbstractGrid, setname::String)

Returns all cells as cellid in the set with name setname.

source
Ferrite.getnodesetFunction
getnodeset(grid::AbstractGrid, setname::String)

Returns all nodes as nodeid in the set with name setname.

source
Ferrite.getfacetsetFunction
getfacetset(grid::AbstractGrid, setname::String)

Returns all faces as FacetIndex in the set with name setname.

source
Ferrite.getvertexsetFunction
getvertexset(grid::AbstractGrid, setname::String)

Returns all vertices as VertexIndex in the set with name setname.

source
Ferrite.transform_coordinates!Function
transform_coordinates!(grid::Abstractgrid, f::Function)

Transform the coordinates of all nodes of the grid based on some transformation function f(x).

source
Ferrite.getcoordinatesFunction
getcoordinates(grid::AbstractGrid, idx::Union{Int,CellIndex})
+getcoordinates(cache::CellCache)

Get a vector with the coordinates of the cell corresponding to idx or cache

source
Ferrite.getcoordinates!Function
getcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, idx::Union{Int,CellIndex})
+getcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, cell::AbstractCell)

Mutate x to the coordinates of the cell corresponding to idx or cell.

source
Ferrite.geometric_interpolationMethod
geometric_interpolation(::AbstractCell)::ScalarInterpolation
+geometric_interpolation(::Type{<:AbstractCell})::ScalarInterpolation

Each AbstractCell type has a unique geometric interpolation describing its geometry. This function returns that interpolation, which is always a scalar interpolation.

source
Ferrite.get_node_coordinateFunction
get_node_coordinate(::Node)

Get the value of the node coordinate.

source
get_node_coordinate(grid::AbstractGrid, n::Int)

Return the coordinate of the nth node in grid

source
Ferrite.getspatialdimMethod
Ferrite.getspatialdim(grid::AbstractGrid)

Get the spatial dimension of the grid, corresponding to the vector dimension of the grid's coordinates.

source
Ferrite.getrefdimMethod
Ferrite.getrefdim(cell::AbstractCell)
+Ferrite.getrefdim(::Type{<:AbstractCell})

Get the reference dimension of the cell, i.e. the dimension of the cell's reference shape.

source

Topology

Ferrite.ExclusiveTopologyType
ExclusiveTopology(grid::AbstractGrid)

The experimental feature ExclusiveTopology saves topological (connectivity/neighborhood) data of the grid. Only the highest dimensional neighborhood is saved. I.e., if something is connected by a face and an edge, only the face neighborhood is saved. The lower dimensional neighborhood is recomputed when calling getneighborhood if needed.

Fields

  • vertex_to_cell::AbstractArray{AbstractVector{Int}, 1}: global vertex id to all cells containing the vertex
  • cell_neighbor::AbstractArray{AbstractVector{Int}, 1}: cellid to all connected cells
  • face_neighbor::AbstractArray{AbstractVector{FaceIndex}, 2}: face_neighbor[cellid, local_face_id] -> neighboring faces
  • edge_neighbor::AbstractArray{AbstractVector{EdgeIndex}, 2}: edge_neighbor[cellid, local_edge_id] -> neighboring edges
  • vertex_neighbor::AbstractArray{AbstractVector{VertexIndex}, 2}: vertex_neighbor[cellid, local_vertex_id] -> neighboring vertices
  • face_skeleton::Union{Vector{FaceIndex}, Nothing}: List of unique faces in the grid given as FaceIndex
  • edge_skeleton::Union{Vector{EdgeIndex}, Nothing}: List of unique edges in the grid given as EdgeIndex
  • vertex_skeleton::Union{Vector{VertexIndex}, Nothing}: List of unique vertices in the grid given as VertexIndex
Limitations

The implementation only works with conforming grids, i.e. grids without "hanging nodes". Non-conforming grids will give unexpected results. Grids with embedded cells (different reference dimension compared to the spatial dimension) are not supported, and will error on construction.

source
Ferrite.getneighborhoodFunction
getneighborhood(topology, grid::AbstractGrid, cellidx::CellIndex, include_self=false)
 getneighborhood(topology, grid::AbstractGrid, faceidx::FaceIndex, include_self=false)
 getneighborhood(topology, grid::AbstractGrid, vertexidx::VertexIndex, include_self=false)
-getneighborhood(topology, grid::AbstractGrid, edgeidx::EdgeIndex, include_self=false)

Returns all connected entities of the same type as defined by the respective topology. If include_self is true, the given entity is included in the returned list as well.

source
Ferrite.facetskeletonFunction
facetskeleton(top::ExclusiveTopology, grid::AbstractGrid)

Materializes the skeleton from the neighborhood information by returning an iterable over the unique facets in the grid, described by FacetIndex.

source
Ferrite.vertex_star_stencilsFunction
vertex_star_stencils(top::ExclusiveTopology, grid::Grid) -> AbstractVector{AbstractVector{VertexIndex}}

Computes the stencils induced by the edge connectivity of the vertices.

source
Ferrite.getstencilFunction
getstencil(top::ArrayOfVectorViews{VertexIndex, 1}, grid::AbstractGrid, vertex_idx::VertexIndex) -> AbstractVector{VertexIndex}

Get an iterateable over the stencil members for a given local entity.

source

Grid Sets Utility

Ferrite.addcellset!Function
addcellset!(grid::AbstractGrid, name::String, cellid::AbstractVecOrSet{Int})
+getneighborhood(topology, grid::AbstractGrid, edgeidx::EdgeIndex, include_self=false)

Returns all connected entities of the same type as defined by the respective topology. If include_self is true, the given entity is included in the returned list as well.

source
Ferrite.facetskeletonFunction
facetskeleton(top::ExclusiveTopology, grid::AbstractGrid)

Materializes the skeleton from the neighborhood information by returning an iterable over the unique facets in the grid, described by FacetIndex.

source
Ferrite.vertex_star_stencilsFunction
vertex_star_stencils(top::ExclusiveTopology, grid::Grid) -> AbstractVector{AbstractVector{VertexIndex}}

Computes the stencils induced by the edge connectivity of the vertices.

source
Ferrite.getstencilFunction
getstencil(top::ArrayOfVectorViews{VertexIndex, 1}, grid::AbstractGrid, vertex_idx::VertexIndex) -> AbstractVector{VertexIndex}

Get an iterateable over the stencil members for a given local entity.

source

Grid Sets Utility

Ferrite.addcellset!Function
addcellset!(grid::AbstractGrid, name::String, cellid::AbstractVecOrSet{Int})
 addcellset!(grid::AbstractGrid, name::String, f::function; all::Bool=true)

Adds a cellset to the grid with key name. Cellsets are typically used to define subdomains of the problem, e.g. two materials in the computational domain. The DofHandler can construct different fields which live not on the whole domain, but rather on a cellset. all=true implies that f(x) must return true for all nodal coordinates x in the cell if the cell should be added to the set, otherwise it suffices that f(x) returns true for one node.

addcellset!(grid, "left", Set((1,3))) #add cells with id 1 and 3 to cellset left
-addcellset!(grid, "right", x -> norm(x[1]) < 2.0 ) #add cell to cellset right, if x[1] of each cell's node is smaller than 2.0
source
Ferrite.addfacetset!Function
addfacetset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FacetIndex})
+addcellset!(grid, "right", x -> norm(x[1]) < 2.0 ) #add cell to cellset right, if x[1] of each cell's node is smaller than 2.0
source
Ferrite.addfacetset!Function
addfacetset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FacetIndex})
 addfacetset!(grid::AbstractGrid, name::String, f::Function; all::Bool=true)

Adds a facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.

addfacetset!(grid, "right", Set((FacetIndex(2,2), FacetIndex(4,2)))) #see grid manual example for reference
-addfacetset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0) #see incompressible elasticity example for reference
source
Ferrite.addboundaryfacetset!Function

addboundaryfacetset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)

Adds a boundary facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets are used to initialize Dirichlet structs, that are needed to specify the boundary for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.

source
Ferrite.addvertexset!Function
addvertexset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FaceIndex})
+addfacetset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0) #see incompressible elasticity example for reference
source
Ferrite.addboundaryfacetset!Function

addboundaryfacetset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)

Adds a boundary facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets are used to initialize Dirichlet structs, that are needed to specify the boundary for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.

source
Ferrite.addvertexset!Function
addvertexset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FaceIndex})
 addvertexset!(grid::AbstractGrid, name::String, f::Function)

Adds a vertexset to the grid with key name. A vertexset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). Vertexsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler.

addvertexset!(grid, "right", Set((VertexIndex(2,2), VertexIndex(4,2))))
-addvertexset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0)
source
Ferrite.addboundaryvertexset!Function

addboundaryvertexset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)

Adds a boundary vertexset to the grid with key name. A vertexset maps a String key to an OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). all=true implies that f(x) must return true for all nodal coordinates x on the face if the face should be added to the set, otherwise it suffices that f(x) returns true for one node.

source
Ferrite.addnodeset!Function
addnodeset!(grid::AbstractGrid, name::String, nodeid::AbstractVecOrSet{Int})
-addnodeset!(grid::AbstractGrid, name::String, f::Function)

Adds a nodeset::OrderedSet{Int} to the grid's nodesets with key name. Has the same interface as addcellset. However, instead of mapping a cell id to the String key, a set of node ids is returned.

source

Multithreaded Assembly

Ferrite.create_coloringFunction
create_coloring(g::Grid, cellset=1:getncells(g); alg::ColoringAlgorithm)

Create a coloring of the cells in grid g such that no neighboring cells have the same color. If only a subset of cells should be colored, the cells to color can be specified by cellset.

Returns a vector of vectors with cell indexes, e.g.:

ret = [
+addvertexset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0)
source
Ferrite.addboundaryvertexset!Function

addboundaryvertexset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)

Adds a boundary vertexset to the grid with key name. A vertexset maps a String key to an OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). all=true implies that f(x) must return true for all nodal coordinates x on the face if the face should be added to the set, otherwise it suffices that f(x) returns true for one node.

source
Ferrite.addnodeset!Function
addnodeset!(grid::AbstractGrid, name::String, nodeid::AbstractVecOrSet{Int})
+addnodeset!(grid::AbstractGrid, name::String, f::Function)

Adds a nodeset::OrderedSet{Int} to the grid's nodesets with key name. Has the same interface as addcellset. However, instead of mapping a cell id to the String key, a set of node ids is returned.

source

Multithreaded Assembly

Ferrite.create_coloringFunction
create_coloring(g::Grid, cellset=1:getncells(g); alg::ColoringAlgorithm)

Create a coloring of the cells in grid g such that no neighboring cells have the same color. If only a subset of cells should be colored, the cells to color can be specified by cellset.

Returns a vector of vectors with cell indexes, e.g.:

ret = [
    [1, 3, 5, 10, ...], # cells for color 1
    [2, 4, 6, 12, ...], # cells for color 2
 ]

Two different algorithms are available, specified with the alg keyword argument:

  • alg = ColoringAlgorithm.WorkStream (default): Three step algorithm from Turcksin et al. [11], albeit with a greedy coloring in the second step. Generally results in more colors than ColoringAlgorithm.Greedy, however the cells are more equally distributed among the colors.
  • alg = ColoringAlgorithm.Greedy: greedy algorithm that works well for structured quadrilateral grids such as e.g. quadrilateral grids from generate_grid.

The resulting colors can be visualized using Ferrite.write_cell_colors.

Cell to color mapping

In a previous version of Ferrite this function returned a dictionary mapping cell ID to color numbers as the first argument. If you need this mapping you can create it using the following construct:

colors = create_coloring(...)
 cell_colormap = Dict{Int,Int}(
     cellid => color for (color, cellids) in enumerate(final_colors) for cellid in cellids
-)

References

  • [11] Turcksin et al. ACM Trans. Math. Softw. 43 (2016).
source
+)

References

source diff --git a/previews/PR1096/reference/index.html b/previews/PR1096/reference/index.html index d9ca63be39..c467c94b39 100644 --- a/previews/PR1096/reference/index.html +++ b/previews/PR1096/reference/index.html @@ -1,2 +1,2 @@ -Reference overview · Ferrite.jl
+Reference overview · Ferrite.jl
diff --git a/previews/PR1096/reference/interpolations/index.html b/previews/PR1096/reference/interpolations/index.html index 39d1c3cbb6..319a7784e1 100644 --- a/previews/PR1096/reference/interpolations/index.html +++ b/previews/PR1096/reference/interpolations/index.html @@ -3,4 +3,4 @@ Lagrange{RefTriangle, 2}() julia> getnbasefunctions(ip) -6source
Ferrite.getnbasefunctionsFunction
Ferrite.getnbasefunctions(ip::Interpolation)

Return the number of base functions for the interpolation ip.

source
Ferrite.getrefdimMethod
Ferrite.getrefdim(::Interpolation)

Return the dimension of the reference element for a given interpolation.

source
Ferrite.getrefshapeFunction
Ferrite.getrefshape(::Interpolation)::AbstractRefShape

Return the reference element shape of the interpolation.

source
Ferrite.getorderFunction
Ferrite.getorder(::Interpolation)

Return order of the interpolation.

source

Implemented interpolations:

Ferrite.LagrangeType
Lagrange{refshape, order} <: ScalarInterpolation

Standard continuous Lagrange polynomials with equidistant node placement.

source
Ferrite.SerendipityType
Serendipity{refshape, order} <: ScalarInterpolation

Serendipity element on hypercubes. Currently only second order variants are implemented.

source
Ferrite.DiscontinuousLagrangeType

Piecewise discontinuous Lagrange basis via Gauss-Lobatto points.

source
Ferrite.BubbleEnrichedLagrangeType

Lagrange element with bubble stabilization.

source
Ferrite.CrouzeixRaviartType
CrouzeixRaviart{refshape, order} <: ScalarInterpolation

Classical non-conforming Crouzeix–Raviart element.

For details we refer to the original paper [9].

source
Ferrite.RannacherTurekType
RannacherTurek{refshape, order} <: ScalarInterpolation

Classical non-conforming Rannacher-Turek element.

This element is basically the idea from Crouzeix and Raviart applied to hypercubes. For details see the original paper [10].

source
+6source
Ferrite.getnbasefunctionsFunction
Ferrite.getnbasefunctions(ip::Interpolation)

Return the number of base functions for the interpolation ip.

source
Ferrite.getrefdimMethod
Ferrite.getrefdim(::Interpolation)

Return the dimension of the reference element for a given interpolation.

source
Ferrite.getrefshapeFunction
Ferrite.getrefshape(::Interpolation)::AbstractRefShape

Return the reference element shape of the interpolation.

source
Ferrite.getorderFunction
Ferrite.getorder(::Interpolation)

Return order of the interpolation.

source

Implemented interpolations:

Ferrite.LagrangeType
Lagrange{refshape, order} <: ScalarInterpolation

Standard continuous Lagrange polynomials with equidistant node placement.

source
Ferrite.SerendipityType
Serendipity{refshape, order} <: ScalarInterpolation

Serendipity element on hypercubes. Currently only second order variants are implemented.

source
Ferrite.DiscontinuousLagrangeType

Piecewise discontinuous Lagrange basis via Gauss-Lobatto points.

source
Ferrite.BubbleEnrichedLagrangeType

Lagrange element with bubble stabilization.

source
Ferrite.CrouzeixRaviartType
CrouzeixRaviart{refshape, order} <: ScalarInterpolation

Classical non-conforming Crouzeix–Raviart element.

For details we refer to the original paper [9].

source
Ferrite.RannacherTurekType
RannacherTurek{refshape, order} <: ScalarInterpolation

Classical non-conforming Rannacher-Turek element.

This element is basically the idea from Crouzeix and Raviart applied to hypercubes. For details see the original paper [10].

source
diff --git a/previews/PR1096/reference/quadrature/index.html b/previews/PR1096/reference/quadrature/index.html index 31b5230262..5f0c496884 100644 --- a/previews/PR1096/reference/quadrature/index.html +++ b/previews/PR1096/reference/quadrature/index.html @@ -5,20 +5,20 @@ julia> getpoints(qr) 1-element Vector{Vec{2, Float64}}: - [0.33333333333333, 0.33333333333333]source
Ferrite.FacetQuadratureRuleType
FacetQuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)
+ [0.33333333333333, 0.33333333333333]
source
Ferrite.FacetQuadratureRuleType
FacetQuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)
 FacetQuadratureRule{shape}(face_rules::NTuple{<:Any, <:QuadratureRule{shape}})
-FacetQuadratureRule{shape}(face_rules::AbstractVector{<:QuadratureRule{shape}})

Create a FacetQuadratureRule used for integration of the faces of the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. If no symbol is provided, the default quad_rule_type for each facet's reference shape is used (see QuadratureRule). For non-default quad_rule_types on cells with mixed facet types (e.g. RefPrism and RefPyramid), the face_rules must be provided explicitly.

FacetQuadratureRule is used as one of the components to create FacetValues.

source
Ferrite.getnquadpointsMethod
getnquadpoints(qr::QuadratureRule)

Return the number of quadrature points in qr.

source
Ferrite.getnquadpointsMethod
getnquadpoints(qr::FacetQuadratureRule, face::Int)

Return the number of quadrature points in qr for local face index face.

source
Ferrite.getpointsFunction
getpoints(qr::QuadratureRule)
+FacetQuadratureRule{shape}(face_rules::AbstractVector{<:QuadratureRule{shape}})

Create a FacetQuadratureRule used for integration of the faces of the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. If no symbol is provided, the default quad_rule_type for each facet's reference shape is used (see QuadratureRule). For non-default quad_rule_types on cells with mixed facet types (e.g. RefPrism and RefPyramid), the face_rules must be provided explicitly.

FacetQuadratureRule is used as one of the components to create FacetValues.

source
Ferrite.getnquadpointsMethod
getnquadpoints(qr::QuadratureRule)

Return the number of quadrature points in qr.

source
Ferrite.getnquadpointsMethod
getnquadpoints(qr::FacetQuadratureRule, face::Int)

Return the number of quadrature points in qr for local face index face.

source
Ferrite.getpointsFunction
getpoints(qr::QuadratureRule)
 getpoints(qr::FacetQuadratureRule, face::Int)

Return the points of the quadrature rule.

Examples

julia> qr = QuadratureRule{RefTriangle}(:legendre, 2);
 
 julia> getpoints(qr)
 3-element Vector{Vec{2, Float64}}:
  [0.16666666666667, 0.16666666666667]
  [0.16666666666667, 0.66666666666667]
- [0.66666666666667, 0.16666666666667]
source
Ferrite.getweightsFunction
getweights(qr::QuadratureRule)
+ [0.66666666666667, 0.16666666666667]
source
Ferrite.getweightsFunction
getweights(qr::QuadratureRule)
 getweights(qr::FacetQuadratureRule, face::Int)

Return the weights of the quadrature rule.

Examples

julia> qr = QuadratureRule{RefTriangle}(:legendre, 2);
 
 julia> getweights(qr)
 3-element Array{Float64,1}:
  0.166667
  0.166667
- 0.166667
source
+ 0.166667source diff --git a/previews/PR1096/reference/sparsity_pattern/index.html b/previews/PR1096/reference/sparsity_pattern/index.html index e00b806bce..fe7e888434 100644 --- a/previews/PR1096/reference/sparsity_pattern/index.html +++ b/previews/PR1096/reference/sparsity_pattern/index.html @@ -1,5 +1,5 @@ -Sparsity pattern and sparse matrices · Ferrite.jl

Sparsity pattern and sparse matrices

This is the reference documentation for sparsity patterns and sparse matrix instantiation. See the topic section on Sparsity pattern and sparse matrices.

Sparsity patterns

AbstractSparsityPattern

The following applies to all subtypes of AbstractSparsityPattern:

Ferrite.init_sparsity_patternFunction
init_sparsity_pattern(dh::DofHandler; nnz_per_row::Int)

Initialize an empty SparsityPattern with ndofs(dh) rows and ndofs(dh) columns.

Keyword arguments

  • nnz_per_row: memory optimization hint for the number of non-zero entries per row that will be added to the pattern.
source
Ferrite.add_sparsity_entries!Function
add_sparsity_entries!(
+Sparsity pattern and sparse matrices · Ferrite.jl

Sparsity pattern and sparse matrices

This is the reference documentation for sparsity patterns and sparse matrix instantiation. See the topic section on Sparsity pattern and sparse matrices.

Sparsity patterns

AbstractSparsityPattern

The following applies to all subtypes of AbstractSparsityPattern:

Ferrite.init_sparsity_patternFunction
init_sparsity_pattern(dh::DofHandler; nnz_per_row::Int)

Initialize an empty SparsityPattern with ndofs(dh) rows and ndofs(dh) columns.

Keyword arguments

  • nnz_per_row: memory optimization hint for the number of non-zero entries per row that will be added to the pattern.
source
Ferrite.add_sparsity_entries!Function
add_sparsity_entries!(
     sp::AbstractSparsityPattern,
     dh::DofHandler,
     ch::Union{ConstraintHandler, Nothing} = nothing;
@@ -7,37 +7,37 @@
     keep_constrained::Bool = true,
     coupling = nothing,
     interface_coupling = nothing,
-)

Convenience method for doing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!, depending on what arguments are passed:

  • add_cell_entries! is always called
  • add_interface_entries! is called if topology is provided (i.e. not nothing)
  • add_constraint_entries! is called if the ConstraintHandler is provided

For more details about arguments and keyword arguments, see the respective functions.

source
Ferrite.add_cell_entries!Function
add_cell_entries!(
+)

Convenience method for doing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!, depending on what arguments are passed:

  • add_cell_entries! is always called
  • add_interface_entries! is called if topology is provided (i.e. not nothing)
  • add_constraint_entries! is called if the ConstraintHandler is provided

For more details about arguments and keyword arguments, see the respective functions.

source
Ferrite.add_cell_entries!Function
add_cell_entries!(
     sp::AbstractSparsityPattern,
     dh::DofHandler,
     ch::Union{ConstraintHandler, Nothing} = nothing;
     keep_constrained::Bool = true,
     coupling::Union{AbstractMatrix{Bool}, Nothing}, = nothing
-)

Add entries to the sparsity pattern sp corresponding to DoF couplings within the cells as described by the DofHandler dh.

Keyword arguments

  • keep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.
  • coupling: the coupling between fields/components within each cell. By default (coupling = nothing) it is assumed that all DoFs in each cell couple with each other.
source
Ferrite.add_interface_entries!Function
add_interface_entries!(
+)

Add entries to the sparsity pattern sp corresponding to DoF couplings within the cells as described by the DofHandler dh.

Keyword arguments

  • keep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.
  • coupling: the coupling between fields/components within each cell. By default (coupling = nothing) it is assumed that all DoFs in each cell couple with each other.
source
Ferrite.add_interface_entries!Function
add_interface_entries!(
     sp::SparsityPattern, dh::DofHandler, ch::Union{ConstraintHandler, Nothing};
     topology::ExclusiveTopology, keep_constrained::Bool = true,
     interface_coupling::AbstractMatrix{Bool},
-)

Add entries to the sparsity pattern sp corresponding to DoF couplings on the interface between cells as described by the DofHandler dh.

Keyword arguments

  • topology: the topology corresponding to the grid.
  • keep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.
  • interface_coupling: the coupling between fields/components across the interface.
source
Ferrite.add_constraint_entries!Function
add_constraint_entries!(
+)

Add entries to the sparsity pattern sp corresponding to DoF couplings on the interface between cells as described by the DofHandler dh.

Keyword arguments

  • topology: the topology corresponding to the grid.
  • keep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.
  • interface_coupling: the coupling between fields/components across the interface.
source
Ferrite.add_constraint_entries!Function
add_constraint_entries!(
     sp::AbstractSparsityPattern, ch::ConstraintHandler;
     keep_constrained::Bool = true,
-)

Add all entries resulting from constraints in the ConstraintHandler ch to the sparsity pattern. Note that, since this operation depends on existing entries in the pattern, this function must be called as the last step when creating the sparsity pattern.

Keyword arguments

  • keep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern.
source
Ferrite.add_entry!Function
add_entry!(sp::AbstractSparsityPattern, row::Int, col::Int)

Add an entry to the sparsity pattern sp at row row and column col.

source

SparsityPattern

Ferrite.SparsityPatternMethod
SparsityPattern(nrows::Int, ncols::Int; nnz_per_row::Int = 8)

Create an empty SparsityPattern with nrows rows and ncols columns. nnz_per_row is used as a memory hint for the number of non zero entries per row.

SparsityPattern is the default sparsity pattern type for the standard DofHandler and is therefore commonly constructed using init_sparsity_pattern instead of with this constructor.

Examples

# Create a sparsity pattern for an 100 x 100 matrix, hinting at 10 entries per row
-sparsity_pattern = SparsityPattern(100, 100; nnz_per_row = 10)

Methods

The following methods apply to SparsityPattern (see their respective documentation for more details):

source
Ferrite.SparsityPatternType
struct SparsityPattern <: AbstractSparsityPattern

Data structure representing non-zero entries in the eventual sparse matrix.

See the constructor SparsityPattern(::Int, ::Int) for the user-facing documentation.

Struct fields

  • nrows::Int: number of rows
  • ncols::Int: number of column
  • rows::Vector{Vector{Int}}: vector of length nrows, where rows[i] is a sorted vector of column indices for non zero entries in row i.
Internal struct

The specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.

source

BlockSparsityPattern

Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

Ferrite.BlockSparsityPatternMethod
BlockSparsityPattern(block_sizes::Vector{Int})

Create an empty BlockSparsityPattern with row and column block sizes given by block_sizes.

Examples

# Create a block sparsity pattern with block size 10 x 5
-sparsity_pattern = BlockSparsityPattern([10, 5])

Methods

The following methods apply to BlockSparsityPattern (see their respective documentation for more details):

Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

source
Ferrite.BlockSparsityPatternType
struct BlockSparsityPattern <: AbstractSparsityPattern

Data structure representing non-zero entries for an eventual blocked sparse matrix.

See the constructor BlockSparsityPattern(::Vector{Int}) for the user-facing documentation.

Struct fields

  • nrows::Int: number of rows
  • ncols::Int: number of column
  • block_sizes::Vector{Int}: row and column block sizes
  • blocks::Matrix{SparsityPattern}: matrix of size length(block_sizes) × length(block_sizes) where blocks[i, j] is a SparsityPattern corresponding to block (i, j).
Internal struct

The specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.

source
Ferrite.allocate_matrixMethod
allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)

Allocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.

source
allocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)

Instantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.

source
allocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)

Allocate a matrix of type MatrixType from the DofHandler dh.

This is a convenience method and is equivalent to:

julia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`

Refer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.

Note

If more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. use

sp = init_sparsity_pattern(dh)
+)

Add all entries resulting from constraints in the ConstraintHandler ch to the sparsity pattern. Note that, since this operation depends on existing entries in the pattern, this function must be called as the last step when creating the sparsity pattern.

Keyword arguments

  • keep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern.
source
Ferrite.add_entry!Function
add_entry!(sp::AbstractSparsityPattern, row::Int, col::Int)

Add an entry to the sparsity pattern sp at row row and column col.

source

SparsityPattern

Ferrite.SparsityPatternMethod
SparsityPattern(nrows::Int, ncols::Int; nnz_per_row::Int = 8)

Create an empty SparsityPattern with nrows rows and ncols columns. nnz_per_row is used as a memory hint for the number of non zero entries per row.

SparsityPattern is the default sparsity pattern type for the standard DofHandler and is therefore commonly constructed using init_sparsity_pattern instead of with this constructor.

Examples

# Create a sparsity pattern for an 100 x 100 matrix, hinting at 10 entries per row
+sparsity_pattern = SparsityPattern(100, 100; nnz_per_row = 10)

Methods

The following methods apply to SparsityPattern (see their respective documentation for more details):

source
Ferrite.SparsityPatternType
struct SparsityPattern <: AbstractSparsityPattern

Data structure representing non-zero entries in the eventual sparse matrix.

See the constructor SparsityPattern(::Int, ::Int) for the user-facing documentation.

Struct fields

  • nrows::Int: number of rows
  • ncols::Int: number of column
  • rows::Vector{Vector{Int}}: vector of length nrows, where rows[i] is a sorted vector of column indices for non zero entries in row i.
Internal struct

The specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.

source

BlockSparsityPattern

Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

Ferrite.BlockSparsityPatternMethod
BlockSparsityPattern(block_sizes::Vector{Int})

Create an empty BlockSparsityPattern with row and column block sizes given by block_sizes.

Examples

# Create a block sparsity pattern with block size 10 x 5
+sparsity_pattern = BlockSparsityPattern([10, 5])

Methods

The following methods apply to BlockSparsityPattern (see their respective documentation for more details):

Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

source
Ferrite.BlockSparsityPatternType
struct BlockSparsityPattern <: AbstractSparsityPattern

Data structure representing non-zero entries for an eventual blocked sparse matrix.

See the constructor BlockSparsityPattern(::Vector{Int}) for the user-facing documentation.

Struct fields

  • nrows::Int: number of rows
  • ncols::Int: number of column
  • block_sizes::Vector{Int}: row and column block sizes
  • blocks::Matrix{SparsityPattern}: matrix of size length(block_sizes) × length(block_sizes) where blocks[i, j] is a SparsityPattern corresponding to block (i, j).
Internal struct

The specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.

source
Ferrite.allocate_matrixMethod
allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)

Allocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.

source
allocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)

Instantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.

source
allocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)

Allocate a matrix of type MatrixType from the DofHandler dh.

This is a convenience method and is equivalent to:

julia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`

Refer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.

Note

If more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. use

sp = init_sparsity_pattern(dh)
 add_sparsity_entries!(sp, dh)
 K = allocate_matrix(sp)
 M = allocate_matrix(sp)

instead of

K = allocate_matrix(dh)
-M = allocate_matrix(dh)

Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.

source
allocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)
+M = allocate_matrix(dh)

Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.

source
allocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)
 allocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)

Instantiate a blocked sparse matrix from the blocked sparsity pattern sp.

The type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).

Examples

# Create a sparse matrix with default block type
 allocate_matrix(BlockMatrix, sparsity_pattern)
 
 # Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}
-allocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)
Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

source
Ferrite.allocate_matrixMethod
allocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)
+allocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)
Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

source
Ferrite.allocate_matrixMethod
allocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)
 allocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)

Instantiate a blocked sparse matrix from the blocked sparsity pattern sp.

The type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).

Examples

# Create a sparse matrix with default block type
 allocate_matrix(BlockMatrix, sparsity_pattern)
 
 # Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}
-allocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)
Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

source

Sparse matrices

Creating matrix from SparsityPattern

Ferrite.allocate_matrixMethod
allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)

Allocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.

source
Ferrite.allocate_matrixMethod
allocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)

Instantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.

source

Creating matrix from DofHandler

Ferrite.allocate_matrixMethod
allocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)

Allocate a matrix of type MatrixType from the DofHandler dh.

This is a convenience method and is equivalent to:

julia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`

Refer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.

Note

If more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. use

sp = init_sparsity_pattern(dh)
+allocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)
Package extension

This functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.

source

Sparse matrices

Creating matrix from SparsityPattern

Ferrite.allocate_matrixMethod
allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)

Allocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.

source
Ferrite.allocate_matrixMethod
allocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)

Instantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.

source

Creating matrix from DofHandler

Ferrite.allocate_matrixMethod
allocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)

Allocate a matrix of type MatrixType from the DofHandler dh.

This is a convenience method and is equivalent to:

julia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`

Refer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.

Note

If more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. use

sp = init_sparsity_pattern(dh)
 add_sparsity_entries!(sp, dh)
 K = allocate_matrix(sp)
 M = allocate_matrix(sp)

instead of

K = allocate_matrix(dh)
-M = allocate_matrix(dh)

Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.

source
+M = allocate_matrix(dh)

Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.

source
Ferrite.allocate_matrixMethod
allocate_matrix(dh::DofHandler, args...; kwargs...)

Allocate a matrix of type SparseMatrixCSC{Float64, Int} from the DofHandler dh.

This method is a shorthand for the equivalent allocate_matrix(SparseMatrixCSC{Float64, Int}, dh, args...; kwargs...) – refer to that method for details.

source
diff --git a/previews/PR1096/reference/utils/index.html b/previews/PR1096/reference/utils/index.html index af0bc4a219..c7e10e191d 100644 --- a/previews/PR1096/reference/utils/index.html +++ b/previews/PR1096/reference/utils/index.html @@ -1,2 +1,2 @@ -Development utility functions · Ferrite.jl

Development utility functions

Ferrite.debug_modeFunction
Ferrite.debug_mode(; enable=true)

Helper to turn on (enable=true) or off (enable=false) debug expressions in Ferrite.

Debug mode influences Ferrite.@debug expr: when debug mode is enabled, expr is evaluated, and when debug mode is disabled expr is ignored.

source
+Development utility functions · Ferrite.jl

Development utility functions

Ferrite.debug_modeFunction
Ferrite.debug_mode(; enable=true)

Helper to turn on (enable=true) or off (enable=false) debug expressions in Ferrite.

Debug mode influences Ferrite.@debug expr: when debug mode is enabled, expr is evaluated, and when debug mode is disabled expr is ignored.

source
diff --git a/previews/PR1096/search_index.js b/previews/PR1096/search_index.js index 686901ce6c..47b79fcc2a 100644 --- a/previews/PR1096/search_index.js +++ b/previews/PR1096/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"EditURL = \"../literate-tutorials/linear_shell.jl\"","category":"page"},{"location":"tutorials/linear_shell/#tutorial-linear-shell","page":"Linear shell","title":"Linear shell","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"(Image: )","category":"page"},{"location":"tutorials/linear_shell/#Introduction","page":"Linear shell","title":"Introduction","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"In this example we show how shell elements can be analyzed with Ferrite. The shell implemented here comes from the book \"The finite element method - Linear static and dynamic finite element analysis\" by Hughes (1987), and a brief description of it is given at the end of this tutorial. The first part of the tutorial explains how to set up the problem.","category":"page"},{"location":"tutorials/linear_shell/#Setting-up-the-problem","page":"Linear shell","title":"Setting up the problem","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"using Ferrite\nusing ForwardDiff\n\nfunction main() #wrap everything in a function...","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"First we generate a flat rectangular mesh. There is currently no built-in function for generating shell meshes in Ferrite, so we have to create our own simple mesh generator (see the function generate_shell_grid further down in this file).","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" nels = (10, 10)\n size = (10.0, 10.0)\n grid = generate_shell_grid(nels, size)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Here we define the bi-linear interpolation used for the geometrical description of the shell. We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use under integration for the inplane integration, to avoid shear locking.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" ip = Lagrange{RefQuadrilateral, 1}()\n qr_inplane = QuadratureRule{RefQuadrilateral}(1)\n qr_ooplane = QuadratureRule{RefLine}(2)\n cv = CellValues(qr_inplane, ip, ip^3)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Next we distribute displacement dofs,:u = (x,y,z) and rotational dofs, :θ = (θ₁, θ₂).","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" dh = DofHandler(grid)\n add!(dh, :u, ip^3)\n add!(dh, :θ, ip^2)\n close!(dh)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This is done with addfacetset! and addvertexset!","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" addfacetset!(grid, \"left\", (x) -> x[1] ≈ 0.0)\n addfacetset!(grid, \"right\", (x) -> x[1] ≈ size[1])\n addvertexset!(grid, \"corner\", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" ch = ConstraintHandler(dh)\n add!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1, 3]))\n add!(ch, Dirichlet(:θ, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1, 2]))","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" add!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> (0.0, 0.0), [1, 3]))\n add!(ch, Dirichlet(:θ, getfacetset(grid, \"right\"), (x, t) -> (0.0, pi / 10), [1, 2]))","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"In order to not get rigid body motion, we lock the y-displacement in one of the corners.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" add!(ch, Dirichlet(:θ, getvertexset(grid, \"corner\"), (x, t) -> (0.0), [2]))\n\n close!(ch)\n update!(ch, 0.0)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material. In this linear shell, plane stress is assumed, ie sigma_zz = 0. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6).","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" κ = 5 / 6 # Shear correction factor\n E = 210.0\n ν = 0.3\n a = (1 - ν) / 2\n C = E / (1 - ν^2) * [\n 1 ν 0 0 0;\n ν 1 0 0 0;\n 0 0 a * κ 0 0;\n 0 0 0 a * κ 0;\n 0 0 0 0 a * κ\n ]\n\n\n data = (thickness = 1.0, C = C) #Named tuple","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"We now assemble the problem in standard finite element fashion","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" nnodes = getnbasefunctions(ip)\n ndofs_shell = ndofs_per_cell(dh)\n\n K = allocate_matrix(dh)\n f = zeros(Float64, ndofs(dh))\n\n ke = zeros(ndofs_shell, ndofs_shell)\n fe = zeros(ndofs_shell)\n\n celldofs = zeros(Int, ndofs_shell)\n cellcoords = zeros(Vec{3, Float64}, nnodes)\n\n assembler = start_assemble(K, f)\n for cell in CellIterator(grid)\n fill!(ke, 0.0)\n reinit!(cv, cell)\n celldofs!(celldofs, dh, cellid(cell))\n getcoordinates!(cellcoords, grid, cellid(cell))\n\n #Call the element routine\n integrate_shell!(ke, cv, qr_ooplane, cellcoords, data)\n\n assemble!(assembler, celldofs, ke, fe)\n end","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Apply BC and solve.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" apply!(K, f, ch)\n a = K \\ f","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Output results.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":" return VTKGridFile(\"linear_shell\", dh) do vtk\n write_solution(vtk, dh, a)\n end\n\nend; #end main functions\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Below is the function that creates the shell mesh. It simply generates a 2d-quadrature mesh, and appends a third coordinate (z-direction) to the node-positions.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function generate_shell_grid(nels, size)\n _grid = generate_grid(Quadrilateral, nels, Vec((0.0, 0.0)), Vec(size))\n nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node for n in _grid.nodes]\n\n grid = Grid(_grid.cells, nodes)\n\n return grid\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#The-shell-element","page":"Linear shell","title":"The shell element","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The shell presented here comes from the book \"The finite element method - Linear static and dynamic finite element analysis\" by Hughes (1987). The shell is a so called degenerate shell element, meaning it is based on a continuum element. A brief description of the shell is given here.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"note: Note\nThis element might experience various locking phenomenas, and should only be seen as a proof of concept.","category":"page"},{"location":"tutorials/linear_shell/#Fiber-coordinate-system","page":"Linear shell","title":"Fiber coordinate system","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The element uses two coordinate systems. The first coordianate system, called the fiber system, is created for each element node, and is used as a reference frame for the rotations. The function below implements an algorithm that return the fiber directions, boldsymbole^f_a1, boldsymbole^f_a2 and boldsymbole^f_a3, at each node a.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function fiber_coordsys(Ps::Vector{Vec{3, Float64}})\n\n ef1 = Vec{3, Float64}[]\n ef2 = Vec{3, Float64}[]\n ef3 = Vec{3, Float64}[]\n for P in Ps\n a = abs.(P)\n j = 1\n if a[1] > a[3]\n a[3] = a[1]; j = 2\n end\n if a[2] > a[3]\n j = 3\n end\n\n e3 = P\n e2 = Tensors.cross(P, basevec(Vec{3}, j))\n e2 /= norm(e2)\n e1 = Tensors.cross(e2, P)\n\n push!(ef1, e1)\n push!(ef2, e2)\n push!(ef3, e3)\n end\n return ef1, ef2, ef3\n\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Lamina-coordinate-system","page":"Linear shell","title":"Lamina coordinate system","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The second coordinate system is the so called Lamina Coordinate system. It is created for each integration point, and is defined to be tangent to the mid-surface. It is in this system that we enforce that plane stress assumption, i.e. sigma_zz = 0. The function below returns the rotation matrix, boldsymbolq, for this coordinate system.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function lamina_coordsys(dNdξ, ζ, x, p, h)\n\n e1 = zero(Vec{3})\n e2 = zero(Vec{3})\n\n for i in 1:length(dNdξ)\n e1 += dNdξ[i][1] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i]\n e2 += dNdξ[i][2] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i]\n end\n\n e1 /= norm(e1)\n e2 /= norm(e2)\n\n ez = Tensors.cross(e1, e2)\n ez /= norm(ez)\n\n a = 0.5 * (e1 + e2)\n a /= norm(a)\n\n b = Tensors.cross(ez, a)\n b /= norm(b)\n\n ex = sqrt(2) / 2 * (a - b)\n ey = sqrt(2) / 2 * (a + b)\n\n return Tensor{2, 3}(hcat(ex, ey, ez))\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Geometrical-description","page":"Linear shell","title":"Geometrical description","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"A material point in the shell is defined as","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"boldsymbol x(xi eta zeta) = sum_a=1^N_textnodes N_a(xi eta) boldsymbolbarx_a + ζ frach2 boldsymbolbarp_a","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"where boldsymbolbarx_a are nodal positions on the mid-surface, and boldsymbolbarp_a is an vector that defines the fiber direction on the reference surface. N_a arethe shape functions.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Based on the definition of the position vector, we create an function for obtaining the Jacobian-matrix,","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"J_ij = fracpartial x_ipartial xi_j","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function getjacobian(q, N, dNdξ, ζ, X, p, h)\n J = zeros(3, 3)\n for a in 1:length(N)\n for i in 1:3, j in 1:3\n _dNdξ = (j == 3) ? 0.0 : dNdξ[a][j]\n _dζdξ = (j == 3) ? 1.0 : 0.0\n _N = N[a]\n\n J[i, j] += _dNdξ * X[a][i] + (_dNdξ * ζ + _N * _dζdξ) * h / 2 * p[a][i]\n end\n end\n\n return (q' * J) |> Tensor{2, 3, Float64}\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Strains","page":"Linear shell","title":"Strains","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Small deformation is assumed,","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"varepsilon_ij= frac12(fracpartial u_ipartial x_j + fracpartial u_jpartial x_i)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The displacement field is calculated as:","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"boldsymbol u = sum_a=1^N_textnodes N_a barboldsymbol u_a +\n N_a ζfrach2(theta_a2 boldsymbol e^f_a1 - theta_a1 boldsymbol e^f_a2)\n","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The gradient of the displacement (in the lamina coordinate system), then becomes:","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"fracpartial u_ipartial x_j = sum_m=1^3 q_im sum_a=1^N_textnodes fracpartial N_apartial x_j baru_am +\n fracpartial(N_a ζ)partial x_j frach2 (theta_a2 e^f_am1 - theta_a1 e^f_am2)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where {T}\n\n u = reinterpret(Vec{3, T}, dofvec[1:12])\n θ = reinterpret(Vec{2, T}, dofvec[13:20])\n\n dudx = zeros(T, 3, 3)\n for m in 1:3, j in 1:3\n for a in 1:length(N)\n dudx[m, j] += dNdx[a][j] * u[a][m] + h / 2 * (dNdx[a][j] * ζ + N[a] * dζdx[j]) * (θ[a][2] * ef1[a][m] - θ[a][1] * ef2[a][m])\n end\n end\n\n dudx = q * dudx\n ε = [dudx[1, 1], dudx[2, 2], dudx[1, 2] + dudx[2, 1], dudx[2, 3] + dudx[3, 2], dudx[1, 3] + dudx[3, 1]]\n return ε\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Main-element-routine","page":"Linear shell","title":"Main element routine","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Below is the main routine that calculates the stiffness matrix of the shell element. Since it is a so called degenerate shell element, the code is similar to that for an standard continuum element.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_point]\n\nfunction integrate_shell!(ke, cv, qr_ooplane, X, data)\n nnodes = getnbasefunctions(cv)\n ndofs = nnodes * 5\n h = data.thickness\n\n #Create the directors in each node.\n #Note: For a more general case, the directors should\n #be input parameters for the element routine.\n p = zeros(Vec{3}, nnodes)\n for i in 1:nnodes\n a = Vec{3}((0.0, 0.0, 1.0))\n p[i] = a / norm(a)\n end\n\n ef1, ef2, ef3 = fiber_coordsys(p)\n\n for iqp in 1:getnquadpoints(cv)\n N = [shape_value(cv, iqp, i) for i in 1:nnodes]\n dNdξ = [shape_reference_gradient(cv, iqp, i) for i in 1:nnodes]\n dNdx = [shape_gradient(cv, iqp, i) for i in 1:nnodes]\n\n for oqp in 1:length(qr_ooplane.weights)\n ζ = qr_ooplane.points[oqp][1]\n q = lamina_coordsys(dNdξ, ζ, X, p, h)\n\n J = getjacobian(q, N, dNdξ, ζ, X, p, h)\n Jinv = inv(J)\n dζdx = Vec{3}((0.0, 0.0, 1.0)) ⋅ Jinv\n\n #For simplicity, use automatic differentiation to construct the B-matrix from the strain.\n B = ForwardDiff.jacobian(\n (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs)\n )\n\n dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp)\n ke .+= B' * data.C * B * dV\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Run everything:","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"main()","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"This page was generated using Literate.jl.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"using Ferrite\ngrid = generate_grid(Triangle, (2, 2))\ndh = DofHandler(grid); add!(dh, :u, Lagrange{RefTriangle,1}()); close!(dh)\nu = rand(ndofs(dh)); σ = rand(getncells(grid))","category":"page"},{"location":"topics/export/#Export","page":"Export","title":"Export","text":"","category":"section"},{"location":"topics/export/","page":"Export","title":"Export","text":"When the problem is solved, and the solution vector u is known we typically want to visualize it. The simplest way to do this is to write the solution to a VTK-file, which can be viewed in e.g. Paraview. To write VTK-files, Ferrite comes with an export interface with a WriteVTK.jl backend to simplify the exporting.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"The following structure can be used to write various output to a vtk-file:","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"VTKGridFile(\"my_solution\", grid) do vtk\n write_solution(vtk, dh, u)\nend;","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"where write_solution is just one example of the following functions that can be used","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"write_solution\nwrite_cell_data\nwrite_node_data\nwrite_projection\nFerrite.write_cellset\nFerrite.write_nodeset\nFerrite.write_constraints\nFerrite.write_cell_colors","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"Instead of using the do-block, it is also possible to do","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"vtk = VTKGridFile(\"my_solution\", grid)\nwrite_solution(vtk, dh, u)\n# etc.\nclose(vtk);","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"The data written by write_solution, write_cell_data, write_node_data, and write_projection may be either scalar (Vector{<:Number}) or tensor (Vector{<:AbstractTensor}) data.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"For simulations with multiple time steps, typically one VTK (.vtu) file is written for each time step. In order to connect the actual time with each of these files, the paraview_collection can function from WriteVTK.jl can be used. This will create one paraview datafile (.pvd) file and one VTKGridFile (.vtu) for each time step.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"using WriteVTK\npvd = paraview_collection(\"my_results\")\nfor (step, t) in enumerate(range(0, 1, 5))\n # Do calculations to update u\n VTKGridFile(\"my_results_$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\nend\nvtk_save(pvd);","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"See Transient heat equation for an example","category":"page"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/export/#Postprocessing","page":"Postprocessing","title":"Postprocessing","text":"","category":"section"},{"location":"reference/export/#Projection-of-quadrature-point-data","page":"Postprocessing","title":"Projection of quadrature point data","text":"","category":"section"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"L2Projector(::Ferrite.AbstractGrid)\nadd!(::L2Projector, ::Ferrite.AbstractVecOrSet{Int}, ::Interpolation; kwargs...)\nclose!(::L2Projector)\nL2Projector(::Interpolation, ::Ferrite.AbstractGrid; kwargs...)\nproject","category":"page"},{"location":"reference/export/#Ferrite.L2Projector-Tuple{Ferrite.AbstractGrid}","page":"Postprocessing","title":"Ferrite.L2Projector","text":"L2Projector(grid::AbstractGrid)\n\nInitiate an L2Projector for projecting quadrature data onto a function space. To define the function space, add interpolations for differents cell sets with add! before close!ing the projector, see the example below.\n\nThe L2Projector acts as the integrated left hand side of the projection equation: Find projection u in U_h(Omega) subset L_2(Omega) such that\n\nint v u mathrmdOmega = int v f mathrmdOmega quad forall v in U_h(Omega)\n\nwhere f in L_2(Omega) is the data to project. The function space U_h(Omega) is the finite element approximation given by the interpolations add!ed to the L2Projector.\n\nExample\n\nproj = L2Projector(grid)\nqr_quad = QuadratureRule{RefQuadrilateral}(2)\nadd!(proj, quad_set, Lagrange{RefQuadrilateral, 1}(); qr_rhs = qr_quad)\nqr_tria = QuadratureRule{RefTriangle}(1)\nadd!(proj, tria_set, Lagrange{RefTriangle, 1}(); qr_rhs = qr_tria)\nclose!(proj)\n\nvals = Dict{Int, Vector{Float64}}() # Can also be Vector{Vector},\n # indexed with cellnr\nfor (set, qr) in ((quad_set, qr_quad), (tria_set, qr_tria))\n nqp = getnquadpoints(qr)\n for cellnr in set\n vals[cellnr] = rand(nqp)\n end\nend\n\nprojected = project(proj, vals)\n\nwhere projected can be used in e.g. evaluate_at_points with the PointEvalHandler, or with evaluate_at_grid_nodes.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.add!-Tuple{L2Projector, Union{AbstractSet{Int64}, AbstractVector{Int64}}, Interpolation}","page":"Postprocessing","title":"Ferrite.add!","text":"add!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;\n qr_rhs, [qr_lhs])\n\nAdd an interpolation ip on the cells in set to the L2Projector proj.\n\nqr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.\nThe optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.close!-Tuple{L2Projector}","page":"Postprocessing","title":"Ferrite.close!","text":"close!(proj::L2Projector)\n\nClose proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.L2Projector-Tuple{Interpolation, Ferrite.AbstractGrid}","page":"Postprocessing","title":"Ferrite.L2Projector","text":"L2Projector(ip::Interpolation, grid::AbstractGrid; [qr_lhs], [set])\n\nA quick way to initiate an L2Projector, add an interpolation ip on the set to it, and then close! it so that it can be used to project. The optional keyword argument set defaults to all cells in the grid, while qr_lhs defaults to a quadrature rule that integrates the mass matrix exactly for the interpolation ip.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.project","page":"Postprocessing","title":"Ferrite.project","text":"project(proj::L2Projector, vals, [qr_rhs::QuadratureRule])\n\nMakes a L2 projection of data vals to the nodes of the grid using the projector proj (see L2Projector).\n\nproject integrates the right hand side, and solves the projection u from the following projection equation: Find projection u in U_h(Omega) subset L_2(Omega) such that\n\nint v u mathrmdOmega = int v f mathrmdOmega quad forall v in U_h(Omega)\n\nwhere f in L_2(Omega) is the data to project. The function space U_h(Omega) is the finite element approximation given by the interpolations in proj.\n\nThe data vals should be an AbstractVector or AbstractDict that is indexed by the cell number. Each index in vals should give an AbstractVector with one element for each cell quadrature point.\n\nIf proj was created by calling L2Projector(ip, grid, set), qr_rhs must be given. Otherwise, this is added for each domain when calling add!(proj, args...).\n\nAlternatively, vals can be a matrix, with the column index referring the cell number, and the row index corresponding to quadrature point number. Example (scalar) input data:\n\nvals = [\n [0.44, 0.98, 0.32], # data for quadrature point 1, 2, 3 of element 1\n [0.29, 0.48, 0.55], # data for quadrature point 1, 2, 3 of element 2\n # ...\n]\n\nor equivalent in matrix form:\n\nvals = [\n 0.44 0.29 # ...\n 0.98 0.48 # ...\n 0.32 0.55 # ...\n]\n\nSupported data types to project are Numbers and AbstractTensors.\n\nnote: Note\nThe order of the returned data correspond to the order of the L2Projector's internal DofHandler. The data can be further analyzed with evaluate_at_points and evaluate_at_grid_nodes. Use write_projection to export the result.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Evaluation-at-points","page":"Postprocessing","title":"Evaluation at points","text":"","category":"section"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"evaluate_at_grid_nodes\nPointEvalHandler\nevaluate_at_points\nPointValues\nPointIterator\nPointLocation","category":"page"},{"location":"reference/export/#Ferrite.evaluate_at_grid_nodes","page":"Postprocessing","title":"Ferrite.evaluate_at_grid_nodes","text":"evaluate_at_grid_nodes(dh::AbstractDofHandler, u::AbstractVector{T}, fieldname::Symbol) where T\n\nEvaluate the approximated solution for field fieldname at the node coordinates of the grid given the Dof handler dh and the solution vector u.\n\nReturn a vector of length getnnodes(grid) where entry i contains the evaluation of the approximation in the coordinate of node i. If the field does not live on parts of the grid, the corresponding values for those nodes will be returned as NaNs.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.PointEvalHandler","page":"Postprocessing","title":"Ferrite.PointEvalHandler","text":"PointEvalHandler(grid::Grid, points::AbstractVector{Vec{dim,T}}; kwargs...) where {dim, T}\n\nThe PointEvalHandler can be used for function evaluation in arbitrary points in the domain – not just in quadrature points or nodes.\n\nThe constructor takes a grid and a vector of coordinates for the points. The PointEvalHandler computes i) the corresponding cell, and ii) the (local) coordinate within the cell, for each point. The fields of the PointEvalHandler are:\n\ncells::Vector{Union{Int,Nothing}}: vector with cell IDs for the points, with nothing for points that could not be found.\nlocal_coords::Vector{Union{Vec,Nothing}}: vector with the local coordinates (i.e. coordinates in the reference configuration) for the points, with nothing for points that could not be found.\n\nThere are two ways to use the PointEvalHandler to evaluate functions:\n\nevaluate_at_points: can be used when the function is described by i) a dh::DofHandler + uh::Vector (for example the FE-solution), or ii) a p::L2Projector + ph::Vector (for projected data).\nIteration with PointIterator + PointValues: can be used for more flexible evaluation in the points, for example to compute gradients.\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.evaluate_at_points","page":"Postprocessing","title":"Ferrite.evaluate_at_points","text":"evaluate_at_points(ph::PointEvalHandler, dh::AbstractDofHandler, dof_values::Vector{T}, [fieldname::Symbol]) where T\nevaluate_at_points(ph::PointEvalHandler, proj::L2Projector, dof_values::Vector{T}) where T\n\nReturn a Vector{T} (for a 1-dimensional field) or a Vector{Vec{fielddim, T}} (for a vector field) with the field values of field fieldname in the points of the PointEvalHandler. The fieldname can be omitted if only one field is stored in dh. The field values are computed based on the dof_values and interpolated to the local coordinates by the function interpolation of the corresponding field stored in the AbstractDofHandler or the L2Projector.\n\nPoints that could not be found in the domain when constructing the PointEvalHandler will have NaNs for the corresponding entries in the output vector.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.PointValues","page":"Postprocessing","title":"Ferrite.PointValues","text":"PointValues(cv::CellValues)\nPointValues([::Type{T}], func_interpol::Interpolation, [geom_interpol::Interpolation])\n\nSimilar to CellValues but with a single updateable \"quadrature point\". PointValues are used for evaluation of functions/gradients in arbitrary points of the domain together with a PointEvalHandler.\n\nPointValues can be created from CellValues, or from the interpolations directly.\n\nPointValues are reinitialized like other CellValues, but since the local reference coordinate of the \"quadrature point\" changes this needs to be passed to reinit!, in addition to the element coordinates: reinit!(pv, coords, local_coord). Alternatively, it can be reinitialized with a PointLocation when iterating a PointEvalHandler with a PointIterator.\n\nFor function/gradient evaluation, PointValues are used in the same way as CellValues, i.e. by using function_value, function_gradient, etc, with the exception that there is no need to specify the quadrature point index (since PointValues only have 1, this is the default).\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.PointIterator","page":"Postprocessing","title":"Ferrite.PointIterator","text":"PointIterator(ph::PointEvalHandler)\n\nCreate an iterator over the points in the PointEvalHandler. The elements of the iterator are either a PointLocation, if the corresponding point could be found in the grid, or nothing, if the point was not found.\n\nA PointLocation can be used to query the cell ID with the cellid function, and can be used to reinitialize PointValues with reinit!.\n\nExamples\n\nph = PointEvalHandler(grid, points)\n\nfor point in PointIterator(ph)\n point === nothing && continue # Skip any points that weren't found\n reinit!(pointvalues, point) # Update pointvalues\n # ...\nend\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.PointLocation","page":"Postprocessing","title":"Ferrite.PointLocation","text":"PointLocation\n\nElement of a PointIterator, typically used to reinitialize PointValues. Fields:\n\ncid::Int: ID of the cell containing the point\nlocal_coord::Vec: the local (reference) coordinate of the point\ncoords::Vector{Vec}: the coordinates of the cell\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#VTK-export","page":"Postprocessing","title":"VTK export","text":"","category":"section"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"VTKGridFile\nwrite_solution\nwrite_projection\nwrite_cell_data\nwrite_node_data\nFerrite.write_cellset\nFerrite.write_nodeset\nFerrite.write_constraints\nFerrite.write_cell_colors","category":"page"},{"location":"reference/export/#Ferrite.VTKGridFile","page":"Postprocessing","title":"Ferrite.VTKGridFile","text":"VTKGridFile(filename::AbstractString, grid::AbstractGrid; kwargs...)\nVTKGridFile(filename::AbstractString, dh::DofHandler; kwargs...)\n\nCreate a VTKGridFile that contains an unstructured VTK grid. The keyword arguments are forwarded to WriteVTK.vtk_grid, see Data Formatting Options\n\nThis file handler can be used to to write data with\n\nwrite_solution\nwrite_cell_data\nwrite_projection\nwrite_node_data.\nFerrite.write_cellset\nFerrite.write_nodeset\nFerrite.write_constraints\n\nIt is necessary to call close(::VTKGridFile) to save the data after writing to the file handler. Using the supported do-block does this automatically:\n\nVTKGridFile(filename, grid) do vtk\n write_solution(vtk, dh, u)\n write_cell_data(vtk, celldata)\nend\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.write_solution","page":"Postprocessing","title":"Ferrite.write_solution","text":"write_solution(vtk::VTKGridFile, dh::AbstractDofHandler, u::Vector, suffix=\"\")\n\nSave the values at the nodes in the degree of freedom vector u to vtk. Each field in dh will be saved separately, and suffix can be used to append to the fieldname.\n\nu can also contain tensorial values, but each entry in u must correspond to a degree of freedom in dh, see write_node_data for details. Use write_node_data directly when exporting values that are already sorted by the nodes in the grid.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_projection","page":"Postprocessing","title":"Ferrite.write_projection","text":"write_projection(vtk::VTKGridFile, proj::L2Projector, vals::Vector, name::AbstractString)\n\nProject vals to the grid nodes with proj and save to vtk.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_cell_data","page":"Postprocessing","title":"Ferrite.write_cell_data","text":"write_cell_data(vtk::VTKGridFile, celldata::AbstractVector, name::String)\n\nWrite the celldata that is ordered by the cells in the grid to the vtk file.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_node_data","page":"Postprocessing","title":"Ferrite.write_node_data","text":"write_node_data(vtk::VTKGridFile, nodedata::Vector{Real}, name)\nwrite_node_data(vtk::VTKGridFile, nodedata::Vector{<:AbstractTensor}, name)\n\nWrite the nodedata that is ordered by the nodes in the grid to vtk.\n\nWhen nodedata contains Tensors.Vecs, each component is exported. Two-dimensional vectors are padded with zeros.\n\nWhen nodedata contains second order tensors, the index order, [11, 22, 33, 23, 13, 12, 32, 31, 21], follows the default Voigt order in Tensors.jl.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_cellset","page":"Postprocessing","title":"Ferrite.write_cellset","text":"write_cellset(vtk, grid::AbstractGrid)\nwrite_cellset(vtk, grid::AbstractGrid, cellset::String)\nwrite_cellset(vtk, grid::AbstractGrid, cellsets::Union{AbstractVector{String},AbstractSet{String})\n\nWrite all cell sets in the grid with name according to their keys and celldata 1 if the cell is in the set, and 0 otherwise. It is also possible to only export a single cellset, or multiple cellsets.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_nodeset","page":"Postprocessing","title":"Ferrite.write_nodeset","text":"write_nodeset(vtk::VTKGridFile, grid::AbstractGrid, nodeset::String)\n\nWrite nodal values of 1 for nodes in nodeset, and 0 otherwise\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_constraints","page":"Postprocessing","title":"Ferrite.write_constraints","text":"write_constraints(vtk::VTKGridFile, ch::ConstraintHandler)\n\nSaves the dirichlet boundary conditions to a vtkfile. Values will have a 1 where bcs are active and 0 otherwise\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_cell_colors","page":"Postprocessing","title":"Ferrite.write_cell_colors","text":"write_cell_colors(vtk::VTKGridFile, grid::AbstractGrid, cell_colors, name=\"coloring\")\n\nWrite cell colors (see create_coloring) to a VTK file for visualization.\n\nIn case of coloring a subset, the cells which are not part of the subset are represented as color 0.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"EditURL = \"../literate-tutorials/linear_elasticity.jl\"","category":"page"},{"location":"tutorials/linear_elasticity/#tutorial-linear-elasticity","page":"Linear elasticity","title":"Linear elasticity","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"(Image: )","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Figure 1: Linear elastically deformed 1mm times 1mm Ferrite logo.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"tip: Tip\nThis tutorial is also available as a Jupyter notebook: linear_elasticity.ipynb.","category":"page"},{"location":"tutorials/linear_elasticity/#Introduction","page":"Linear elasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The classical first finite element problem to solve in solid mechanics is a linear balance of momentum problem. We will use this to introduce a vector valued field, the displacements boldsymbolu(boldsymbolx). In addition, some features of the Tensors.jl toolbox are demonstrated.","category":"page"},{"location":"tutorials/linear_elasticity/#Strong-form","page":"Linear elasticity","title":"Strong form","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The strong form of the balance of momentum for quasi-static loading is given by","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"beginalignat*2\n mathrmdiv(boldsymbolsigma) + boldsymbolb = 0 quad boldsymbolx in Omega \n boldsymbolu = boldsymbolu_mathrmD quad boldsymbolx in Gamma_mathrmD \n boldsymboln cdot boldsymbolsigma = boldsymbolt_mathrmN quad boldsymbolx in Gamma_mathrmN\nendalignat*","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where boldsymbolsigma is the (Cauchy) stress tensor and boldsymbolb the body force. The domain, Omega, has the boundary Gamma, consisting of a Dirichlet part, Gamma_mathrmD, and a Neumann part, Gamma_mathrmN, with outward pointing normal vector boldsymboln. boldsymbolu_mathrmD denotes prescribed displacements on Gamma_mathrmD, while boldsymbolt_mathrmN the known tractions on Gamma_mathrmN.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In this tutorial, we use linear elasticity, such that","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolsigma = mathsfC boldsymbolvarepsilon quad\nboldsymbolvarepsilon = leftmathrmgrad(boldsymbolu)right^mathrmsym","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where mathsfC is the 4th order elastic stiffness tensor and boldsymbolvarepsilon the small strain tensor. The colon, , represents the double contraction, sigma_ij = mathsfC_ijkl varepsilon_kl, and the superscript mathrmsym denotes the symmetric part.","category":"page"},{"location":"tutorials/linear_elasticity/#Weak-form","page":"Linear elasticity","title":"Weak form","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The resulting weak form is given given as follows: Find boldsymbolu in mathbbU such that","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"int_Omega\n mathrmgrad(delta boldsymbolu) boldsymbolsigma\n mathrmdOmega\n=\nint_Gamma\n delta boldsymbolu cdot boldsymbolt\n mathrmdGamma\n+\nint_Omega\n delta boldsymbolu cdot boldsymbolb\n mathrmdOmega\nquad forall delta boldsymbolu in mathbbT","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where mathbbU and mathbbT denote suitable trial and test function spaces. delta boldsymbolu is a vector valued test function and boldsymbolt = boldsymbolsigmacdotboldsymboln is the traction vector on the boundary. In this tutorial, we will neglect body forces (i.e. boldsymbolb = boldsymbol0) and the weak form reduces to","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"int_Omega\n mathrmgrad(delta boldsymbolu) boldsymbolsigma\n mathrmdOmega\n=\nint_Gamma\n delta boldsymbolu cdot boldsymbolt\n mathrmdGamma ","category":"page"},{"location":"tutorials/linear_elasticity/#Finite-element-form","page":"Linear elasticity","title":"Finite element form","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Finally, the finite element form is obtained by introducing the finite element shape functions. Since the displacement field, boldsymbolu, is vector valued, we use vector valued shape functions deltaboldsymbolN_i and boldsymbolN_i to approximate the test and trial functions:","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolu approx sum_i=1^N boldsymbolN_i (boldsymbolx) hatu_i\nqquad\ndelta boldsymbolu approx sum_i=1^N deltaboldsymbolN_i (boldsymbolx) delta hatu_i","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Here N is the number of nodal variables, with hatu_i and deltahatu_i representing the i-th nodal value. Using the Einstein summation convention, we can write this in short form as boldsymbolu approx boldsymbolN_i hatu_i and deltaboldsymbolu approx deltaboldsymbolN_i deltahatu_i.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Inserting the these into the weak form, and noting that that the equation should hold for all delta hatu_i, we get","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"underbraceint_Omega mathrmgrad(delta boldsymbolN_i) boldsymbolsigma mathrmdOmega_f_i^mathrmint = underbraceint_Gamma delta boldsymbolN_i cdot boldsymbolt mathrmdGamma_f_i^mathrmext","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Inserting the linear constitutive relationship, boldsymbolsigma = mathsfCboldsymbolvarepsilon, in the internal force vector, f_i^mathrmint, yields the linear equation","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"underbraceleftint_Omega mathrmgrad(delta boldsymbolN_i) mathsfC leftmathrmgrad(boldsymbolN_j)right^mathrmsym mathrmdOmegaright_K_ij hatu_j = f_i^mathrmext","category":"page"},{"location":"tutorials/linear_elasticity/#Implementation","page":"Linear elasticity","title":"Implementation","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"First we load Ferrite, and some other packages we need.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Ferrite, FerriteGmsh, SparseArrays","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"As in the heat equation tutorial, we will use a unit square - but here we'll load the grid of the Ferrite logo! This is done by downloading logo.geo and loading it using FerriteGmsh.jl,","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Downloads: download\nlogo_mesh = \"logo.geo\"\nasset_url = \"https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/\"\nisfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh)\n\nFerriteGmsh.Gmsh.initialize() #hide\nFerriteGmsh.Gmsh.gmsh.option.set_number(\"General.Verbosity\", 2) #hide\ngrid = togrid(logo_mesh);\nFerriteGmsh.Gmsh.finalize(); #hide\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The generated grid lacks the facetsets for the boundaries, so we add them by using Ferrite's addfacetset!. It allows us to add facetsets to the grid based on coordinates. Note that approximate comparison to 0.0 doesn't work well, so we use a tolerance instead.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"addfacetset!(grid, \"top\", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes\naddfacetset!(grid, \"left\", x -> abs(x[1]) < 1.0e-6)\naddfacetset!(grid, \"bottom\", x -> abs(x[2]) < 1.0e-6);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Trial-and-test-functions","page":"Linear elasticity","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In this tutorial, we use the same linear Lagrange shape functions to approximate both the test and trial spaces, i.e. deltaboldsymbolN_i = boldsymbolN_i. As our grid is composed of triangular elements, we need the Lagrange functions defined on a RefTriangle. All currently available interpolations can be found under Interpolation.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Here we use linear triangular elements (also called constant strain triangles). The vector valued shape functions are constructed by raising the interpolation to the power dim (the dimension) since the displacement field has one component in each spatial dimension.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"dim = 2\norder = 1 # linear interpolation\nip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In order to evaluate the integrals, we need to specify the quadrature rules to use. Due to the linear interpolation, a single quadrature point suffices, both inside the cell and on the facet. In 2d, a facet is the edge of the element.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"qr = QuadratureRule{RefTriangle}(1) # 1 quadrature point\nqr_face = FacetQuadratureRule{RefTriangle}(1);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Finally, we collect the interpolations and quadrature rules into the CellValues and FacetValues buffers, which we will later use to evaluate the integrals over the cells and facets.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"cellvalues = CellValues(qr, ip)\nfacetvalues = FacetValues(qr_face, ip);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Degrees-of-freedom","page":"Linear elasticity","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"For distributing degrees of freedom, we define a DofHandler. The DofHandler knows that u has two degrees of freedom per node because we vectorized the interpolation above.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Boundary-conditions","page":"Linear elasticity","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We set Dirichlet boundary conditions by fixing the motion normal to the bottom and left boundaries. The last argument to Dirichlet determines which components of the field should be constrained. If no argument is given, all components are constrained by default.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"ch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> 0.0, 2))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> 0.0, 1))\nclose!(ch);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In addition, we will use Neumann boundary conditions on the top surface, where we add a traction vector of the form","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolt_mathrmN(boldsymbolx) = (20e3) x_1 boldsymbole_2 mathrmNmathrmmm^2","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"traction(x) = Vec(0.0, 20.0e3 * x[1]);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"On the right boundary, we don't do anything, resulting in a zero traction Neumann boundary. In order to assemble the external forces, f_i^mathrmext, we need to iterate over all facets in the relevant facetset. We do this by using the FacetIterator.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction)\n # Create a temporary array for the facet's local contributions to the external force vector\n fe_ext = zeros(getnbasefunctions(facetvalues))\n for facet in FacetIterator(dh, facetset)\n # Update the facetvalues to the correct facet number\n reinit!(facetvalues, facet)\n # Reset the temporary array for the next facet\n fill!(fe_ext, 0.0)\n # Access the cell's coordinates\n cell_coordinates = getcoordinates(facet)\n for qp in 1:getnquadpoints(facetvalues)\n # Calculate the global coordinate of the quadrature point.\n x = spatial_coordinate(facetvalues, qp, cell_coordinates)\n tₚ = prescribed_traction(x)\n # Get the integration weight for the current quadrature point.\n dΓ = getdetJdV(facetvalues, qp)\n for i in 1:getnbasefunctions(facetvalues)\n Nᵢ = shape_value(facetvalues, qp, i)\n fe_ext[i] += tₚ ⋅ Nᵢ * dΓ\n end\n end\n # Add the local contributions to the correct indices in the global external force vector\n assemble!(f_ext, celldofs(facet), fe_ext)\n end\n return f_ext\nend\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Material-behavior","page":"Linear elasticity","title":"Material behavior","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Next, we need to define the material behavior, specifically the elastic stiffness tensor, mathsfC. In this tutorial, we use plane strain linear isotropic elasticity, with Hooke's law as","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolsigma = 2G boldsymbolvarepsilon^mathrmdev + 3K boldsymbolvarepsilon^mathrmvol","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where G is the shear modulus and K the bulk modulus. This expression can be written as boldsymbolsigma = mathsfCboldsymbolvarepsilon, with","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":" mathsfC = fracpartial boldsymbolsigmapartial boldsymbolvarepsilon","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The volumetric, boldsymbolvarepsilon^mathrmvol, and deviatoric, boldsymbolvarepsilon^mathrmdev strains, are defined as","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"beginalign*\nboldsymbolvarepsilon^mathrmvol = fracmathrmtr(boldsymbolvarepsilon)3boldsymbolI quad\nboldsymbolvarepsilon^mathrmdev = boldsymbolvarepsilon - boldsymbolvarepsilon^mathrmvol\nendalign*","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Starting from Young's modulus, E, and Poisson's ratio, nu, the shear and bulk modulus are","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"G = fracE2(1 + nu) quad K = fracE3(1 - 2nu)","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Emod = 200.0e3 # Young's modulus [MPa]\nν = 0.3 # Poisson's ratio [-]\n\nGmod = Emod / (2(1 + ν)) # Shear modulus\nKmod = Emod / (3(1 - 2ν)) # Bulk modulus","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Finally, we demonstrate Tensors.jl's automatic differentiation capabilities when calculating the elastic stiffness tensor","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"C = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"details: Plane stress instead of plane strain?\nIn order to change this tutorial to consider plane stress instead of plane strain, the elastic stiffness tensor should be changed to reflect this. The plane stress elasticity stiffness matrix in Voigt notation for engineering shear strains, is given asunderlineunderlineboldsymbolE = fracE1 - nu^2beginbmatrix\n1 nu 0 \nnu 1 0 \n0 0 (1 - nu)2\nendbmatrixThis matrix can be converted into the 4th order elastic stiffness tensor asC_voigt = Emod * [1.0 ν 0.0; ν 1.0 0.0; 0.0 0.0 (1-ν)/2] / (1 - ν^2)\nC = fromvoigt(SymmetricTensor{4,2}, E_voigt)","category":"page"},{"location":"tutorials/linear_elasticity/#Element-routine","page":"Linear elasticity","title":"Element routine","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"To calculate the global stiffness matrix, K_ij, the element routine computes the local stiffness matrix ke for a single element and assembles it into the global matrix. ke is pre-allocated and reused for all elements.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Note that the elastic stiffness tensor mathsfC is constant. Thus is needs to be computed and once and can then be used for all integration points.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function assemble_cell!(ke, cellvalues, C)\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the integration weight for the quadrature point\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n # Gradient of the test function\n ∇Nᵢ = shape_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n # Symmetric gradient of the trial function\n ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j)\n ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ\n end\n end\n end\n return ke\nend\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Global-assembly","page":"Linear elasticity","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We define the function assemble_global to loop over the elements and do the global assembly. The function takes the preallocated sparse matrix K, our DofHandler dh, our cellvalues and the elastic stiffness tensor C as input arguments and computes the global stiffness matrix K.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function assemble_global!(K, dh, cellvalues, C)\n # Allocate the element stiffness matrix\n n_basefuncs = getnbasefunctions(cellvalues)\n ke = zeros(n_basefuncs, n_basefuncs)\n # Create an assembler\n assembler = start_assemble(K)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Update the shape function gradients based on the cell coordinates\n reinit!(cellvalues, cell)\n # Reset the element stiffness matrix\n fill!(ke, 0.0)\n # Compute element contribution\n assemble_cell!(ke, cellvalues, C)\n # Assemble ke into K\n assemble!(assembler, celldofs(cell), ke)\n end\n return K\nend\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Solution-of-the-system","page":"Linear elasticity","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The last step is to solve the system. First we allocate the global stiffness matrix K and assemble it.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"K = allocate_matrix(dh)\nassemble_global!(K, dh, cellvalues, C);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Then we allocate and assemble the external force vector.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"f_ext = zeros(ndofs(dh))\nassemble_external_forces!(f_ext, dh, getfacetset(grid, \"top\"), facetvalues, traction);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"To account for the Dirichlet boundary conditions we use the apply! function. This modifies elements in K and f, such that we can get the correct solution vector u by using solving the linear equation system K_ij hatu_j = f^mathrmext_i,","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"apply!(K, f_ext, ch)\nu = K \\ f_ext;\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Postprocessing","page":"Linear elasticity","title":"Postprocessing","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In this case, we want to analyze the displacements, as well as the stress field. We calculate the stress in each quadrature point, and then export it in two different ways:","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Constant in each cell (matching the approximation of constant strains in each element). Note that a current limitation is that cell data for second order tensors must be exported component-wise (see issue #768)\nInterpolated using the linear lagrange ansatz functions via the L2Projector.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function calculate_stresses(grid, dh, cv, u, C)\n qp_stresses = [\n [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)]\n for _ in 1:getncells(grid)\n ]\n avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...)\n for cell in CellIterator(dh)\n reinit!(cv, cell)\n cell_stresses = qp_stresses[cellid(cell)]\n for q_point in 1:getnquadpoints(cv)\n ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell))\n cell_stresses[q_point] = C ⊡ ε\n end\n σ_avg = sum(cell_stresses) / getnquadpoints(cv)\n avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1]\n avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2]\n avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2]\n end\n return qp_stresses, avg_cell_stresses\nend\n\nqp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We now use the the L2Projector to project the stress-field onto the piecewise linear finite element space that we used to solve the problem.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"proj = L2Projector(Lagrange{RefTriangle, 1}(), grid)\nstress_field = project(proj, qp_stresses, qr);\n\ncolor_data = zeros(Int, getncells(grid)) #hide\ncolors = [ #hide\n \"1\" => 1, \"5\" => 1, # purple #hide\n \"2\" => 2, \"3\" => 2, # red #hide\n \"4\" => 3, # blue #hide\n \"6\" => 4, # green #hide\n] #hide\nfor (key, color) in colors #hide\n for i in getcellset(grid, key) #hide\n color_data[i] = color #hide\n end #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"To visualize the result we export to a VTK-file. Specifically, an unstructured grid file, .vtu, is created, which can be viewed in e.g. ParaView.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"VTKGridFile(\"linear_elasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n for (i, key) in enumerate((\"11\", \"22\", \"12\"))\n write_cell_data(vtk, avg_cell_stresses[i], \"sigma_\" * key)\n end\n write_projection(vtk, proj, stress_field, \"stress field\")\n Ferrite.write_cellset(vtk, grid)\n write_cell_data(vtk, color_data, \"colors\") #hide\nend","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We used the displacement field to visualize the deformed logo in Figure 1, and in Figure 2, we demonstrate the difference between the interpolated stress field and the constant stress in each cell.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"(Image: )","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Figure 2: Vertical normal stresses (MPa) exported using the L2Projector (left) and constant stress in each cell (right).","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Test #hide\nlinux_result = 0.31742879147646924 #hide\n@test abs(norm(u) - linux_result) < 0.01 #hide\nSys.islinux() && @test norm(u) ≈ linux_result #hide\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#linear_elasticity-plain-program","page":"Linear elasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Here follows a version of the program without any comments. The file is also available here: linear_elasticity.jl.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Ferrite, FerriteGmsh, SparseArrays\n\nusing Downloads: download\nlogo_mesh = \"logo.geo\"\nasset_url = \"https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/\"\nisfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh)\n\ngrid = togrid(logo_mesh);\n\naddfacetset!(grid, \"top\", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes\naddfacetset!(grid, \"left\", x -> abs(x[1]) < 1.0e-6)\naddfacetset!(grid, \"bottom\", x -> abs(x[2]) < 1.0e-6);\n\ndim = 2\norder = 1 # linear interpolation\nip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation\n\nqr = QuadratureRule{RefTriangle}(1) # 1 quadrature point\nqr_face = FacetQuadratureRule{RefTriangle}(1);\n\ncellvalues = CellValues(qr, ip)\nfacetvalues = FacetValues(qr_face, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> 0.0, 2))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> 0.0, 1))\nclose!(ch);\n\ntraction(x) = Vec(0.0, 20.0e3 * x[1]);\n\nfunction assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction)\n # Create a temporary array for the facet's local contributions to the external force vector\n fe_ext = zeros(getnbasefunctions(facetvalues))\n for facet in FacetIterator(dh, facetset)\n # Update the facetvalues to the correct facet number\n reinit!(facetvalues, facet)\n # Reset the temporary array for the next facet\n fill!(fe_ext, 0.0)\n # Access the cell's coordinates\n cell_coordinates = getcoordinates(facet)\n for qp in 1:getnquadpoints(facetvalues)\n # Calculate the global coordinate of the quadrature point.\n x = spatial_coordinate(facetvalues, qp, cell_coordinates)\n tₚ = prescribed_traction(x)\n # Get the integration weight for the current quadrature point.\n dΓ = getdetJdV(facetvalues, qp)\n for i in 1:getnbasefunctions(facetvalues)\n Nᵢ = shape_value(facetvalues, qp, i)\n fe_ext[i] += tₚ ⋅ Nᵢ * dΓ\n end\n end\n # Add the local contributions to the correct indices in the global external force vector\n assemble!(f_ext, celldofs(facet), fe_ext)\n end\n return f_ext\nend\n\nEmod = 200.0e3 # Young's modulus [MPa]\nν = 0.3 # Poisson's ratio [-]\n\nGmod = Emod / (2(1 + ν)) # Shear modulus\nKmod = Emod / (3(1 - 2ν)) # Bulk modulus\n\nC = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));\n\nfunction assemble_cell!(ke, cellvalues, C)\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the integration weight for the quadrature point\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n # Gradient of the test function\n ∇Nᵢ = shape_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n # Symmetric gradient of the trial function\n ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j)\n ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ\n end\n end\n end\n return ke\nend\n\nfunction assemble_global!(K, dh, cellvalues, C)\n # Allocate the element stiffness matrix\n n_basefuncs = getnbasefunctions(cellvalues)\n ke = zeros(n_basefuncs, n_basefuncs)\n # Create an assembler\n assembler = start_assemble(K)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Update the shape function gradients based on the cell coordinates\n reinit!(cellvalues, cell)\n # Reset the element stiffness matrix\n fill!(ke, 0.0)\n # Compute element contribution\n assemble_cell!(ke, cellvalues, C)\n # Assemble ke into K\n assemble!(assembler, celldofs(cell), ke)\n end\n return K\nend\n\nK = allocate_matrix(dh)\nassemble_global!(K, dh, cellvalues, C);\n\nf_ext = zeros(ndofs(dh))\nassemble_external_forces!(f_ext, dh, getfacetset(grid, \"top\"), facetvalues, traction);\n\napply!(K, f_ext, ch)\nu = K \\ f_ext;\n\nfunction calculate_stresses(grid, dh, cv, u, C)\n qp_stresses = [\n [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)]\n for _ in 1:getncells(grid)\n ]\n avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...)\n for cell in CellIterator(dh)\n reinit!(cv, cell)\n cell_stresses = qp_stresses[cellid(cell)]\n for q_point in 1:getnquadpoints(cv)\n ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell))\n cell_stresses[q_point] = C ⊡ ε\n end\n σ_avg = sum(cell_stresses) / getnquadpoints(cv)\n avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1]\n avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2]\n avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2]\n end\n return qp_stresses, avg_cell_stresses\nend\n\nqp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C);\n\nproj = L2Projector(Lagrange{RefTriangle, 1}(), grid)\nstress_field = project(proj, qp_stresses, qr);\n\n\nVTKGridFile(\"linear_elasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n for (i, key) in enumerate((\"11\", \"22\", \"12\"))\n write_cell_data(vtk, avg_cell_stresses[i], \"sigma_\" * key)\n end\n write_projection(vtk, proj, stress_field, \"stress field\")\n Ferrite.write_cellset(vtk, grid)\nend","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"EditURL = \"../literate-tutorials/heat_equation.jl\"","category":"page"},{"location":"tutorials/heat_equation/#tutorial-heat-equation","page":"Heat equation","title":"Heat equation","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"(Image: )","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Figure 1: Temperature field on the unit square with an internal uniform heat source solved with homogeneous Dirichlet boundary conditions on the boundary.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"tip: Tip\nThis example is also available as a Jupyter notebook: heat_equation.ipynb.","category":"page"},{"location":"tutorials/heat_equation/#Introduction","page":"Heat equation","title":"Introduction","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"The heat equation is the \"Hello, world!\" equation of finite elements. Here we solve the equation on a unit square, with a uniform internal source. The strong form of the (linear) heat equation is given by","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":" -nabla cdot (k nabla u) = f quad textbfx in Omega","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"where u is the unknown temperature field, k the heat conductivity, f the heat source and Omega the domain. For simplicity we set f = 1 and k = 1. We will consider homogeneous Dirichlet boundary conditions such that","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"u(textbfx) = 0 quad textbfx in partial Omega","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"where partial Omega denotes the boundary of Omega. The resulting weak form is given given as follows: Find u in mathbbU such that","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"int_Omega nabla delta u cdot nabla u dOmega = int_Omega delta u dOmega quad forall delta u in mathbbT","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"where delta u is a test function, and where mathbbU and mathbbT are suitable trial and test function sets, respectively.","category":"page"},{"location":"tutorials/heat_equation/#Commented-Program","page":"Heat equation","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"First we load Ferrite, and some other packages we need","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"using Ferrite, SparseArrays","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"We start by generating a simple grid with 20x20 quadrilateral elements using generate_grid. The generator defaults to the unit square, so we don't need to specify the corners of the domain.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"grid = generate_grid(Quadrilateral, (20, 20));\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/#Trial-and-test-functions","page":"Heat equation","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"A CellValues facilitates the process of evaluating values and gradients of test and trial functions (among other things). To define this we need to specify an interpolation space for the shape functions. We use Lagrange functions based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on the same reference element. We combine the interpolation and the quadrature rule to a CellValues object.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"ip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/#Degrees-of-freedom","page":"Heat equation","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Next we need to define a DofHandler, which will take care of numbering and distribution of degrees of freedom for our approximated fields. We create the DofHandler and then add a single scalar field called :u based on our interpolation ip defined above. Lastly we close! the DofHandler, it is now that the dofs are distributed for all the elements.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now that we have distributed all our dofs we can create our tangent matrix, using allocate_matrix. This function returns a sparse matrix with the correct entries stored.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"K = allocate_matrix(dh)","category":"page"},{"location":"tutorials/heat_equation/#Boundary-conditions","page":"Heat equation","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"In Ferrite constraints like Dirichlet boundary conditions are handled by a ConstraintHandler.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"ch = ConstraintHandler(dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Next we need to add constraints to ch. For this problem we define homogeneous Dirichlet boundary conditions on the whole boundary, i.e. the union of all the facet sets on the boundary.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"∂Ω = union(\n getfacetset(grid, \"left\"),\n getfacetset(grid, \"right\"),\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now we are set up to define our constraint. We specify which field the condition is for, and our combined facet set ∂Ω. The last argument is a function of the form f(textbfx) or f(textbfx t), where textbfx is the spatial coordinate and t the current time, and returns the prescribed value. Since the boundary condition in this case do not depend on time we define our function as f(textbfx) = 0, i.e. no matter what textbfx we return 0. When we have specified our constraint we add! it to ch.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"dbc = Dirichlet(:u, ∂Ω, (x, t) -> 0)\nadd!(ch, dbc);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Finally we also need to close! our constraint handler. When we call close! the dofs corresponding to our constraints are calculated and stored in our ch object.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"close!(ch)","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Note that if one or more of the constraints are time dependent we would use update! to recompute prescribed values in each new timestep.","category":"page"},{"location":"tutorials/heat_equation/#Assembling-the-linear-system","page":"Heat equation","title":"Assembling the linear system","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now we have all the pieces needed to assemble the linear system, K u = f. Assembling of the global system is done by looping over all the elements in order to compute the element contributions K_e and f_e, which are then assembled to the appropriate place in the global K and f.","category":"page"},{"location":"tutorials/heat_equation/#Element-assembly","page":"Heat equation","title":"Element assembly","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"We define the function assemble_element! (see below) which computes the contribution for an element. The function takes pre-allocated ke and fe (they are allocated once and then reused for all elements) so we first need to make sure that they are all zeroes at the start of the function by using fill!. Then we loop over all the quadrature points, and for each quadrature point we loop over all the (local) shape functions. We need the value and gradient of the test function, δu and also the gradient of the trial function u. We get all of these from cellvalues.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"note: Notation\nComparing with the brief finite element introduction in Introduction to FEM, the variables δu, ∇δu and ∇u are actually phi_i(textbfx_q), nabla phi_i(textbfx_q) and nabla phi_j(textbfx_q), i.e. the evaluation of the trial and test functions in the quadrature point textbfx_q. However, to underline the strong parallel between the weak form and the implementation, this example uses the symbols appearing in the weak form.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\nnothing # hide","category":"page"},{"location":"tutorials/heat_equation/#Global-assembly","page":"Heat equation","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"We define the function assemble_global to loop over the elements and do the global assembly. The function takes our cellvalues, the sparse matrix K, and our DofHandler as input arguments and returns the assembled global stiffness matrix, and the assembled global force vector. We start by allocating Ke, fe, and the global force vector f. We also create an assembler by using start_assemble. The assembler lets us assemble into K and f efficiently. We then start the loop over all the elements. In each loop iteration we reinitialize cellvalues (to update derivatives of shape functions etc.), compute the element contribution with assemble_element!, and then assemble into the global K and f with assemble!.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"note: Notation\nComparing again with Introduction to FEM, f and u correspond to underlinehatf and underlinehatu, since they represent the discretized versions. However, through the code we use f and u instead to reflect the strong connection between the weak form and the Ferrite implementation.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"function assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"tutorials/heat_equation/#Solution-of-the-system","page":"Heat equation","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"The last step is to solve the system. First we call assemble_global to obtain the global stiffness matrix K and force vector f.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"K, f = assemble_global(cellvalues, K, dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"To account for the boundary conditions we use the apply! function. This modifies elements in K and f respectively, such that we can get the correct solution vector u by using \\.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"apply!(K, f, ch)\nu = K \\ f;\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/#Exporting-to-VTK","page":"Heat equation","title":"Exporting to VTK","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"To visualize the result we export the grid and our field u to a VTK-file, which can be viewed in e.g. ParaView.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"VTKGridFile(\"heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend","category":"page"},{"location":"tutorials/heat_equation/#heat_equation-plain-program","page":"Heat equation","title":"Plain program","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Here follows a version of the program without any comments. The file is also available here: heat_equation.jl.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"using Ferrite, SparseArrays\n\ngrid = generate_grid(Quadrilateral, (20, 20));\n\nip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nK = allocate_matrix(dh)\n\nch = ConstraintHandler(dh);\n\n∂Ω = union(\n getfacetset(grid, \"left\"),\n getfacetset(grid, \"right\"),\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\n\ndbc = Dirichlet(:u, ∂Ω, (x, t) -> 0)\nadd!(ch, dbc);\n\nclose!(ch)\n\nfunction assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\n\nK, f = assemble_global(cellvalues, K, dh);\n\napply!(K, f, ch)\nu = K \\ f;\n\nVTKGridFile(\"heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/assembly/#man-assembly","page":"Assembly","title":"Assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"When the local stiffness matrix and force vector have been calculated they should be assembled into the global stiffness matrix and the global force vector. This is just a matter of adding the local matrix and vector to the global one, at the correct place. Consider e.g. assembling the local stiffness matrix ke and the local force vector fe into the global K and f respectively. These should be assembled into the row/column which corresponds to the degrees of freedom for the cell:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K[celldofs, celldofs] += ke\nf[celldofs] += fe","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"where celldofs is the vector containing the degrees of freedom for the cell. The method above is very inefficient – it is especially costly to index into the sparse matrix K directly (see Comparison of assembly strategies for details). Therefore we will instead use an Assembler that will help with the assembling of both the global stiffness matrix and the global force vector. It is also often convenient to create the sparse matrix just once, and reuse the allocated matrix. This is useful for e.g. iterative solvers or time dependent problems where the sparse matrix structure, or Sparsity Pattern will stay the same in every iteration/time step.","category":"page"},{"location":"topics/assembly/#Assembler","page":"Assembly","title":"Assembler","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Assembling efficiently into the sparse matrix requires some extra workspace. This workspace is allocated in an Assembler. start_assemble is used to create an Assembler:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"A = start_assemble(K)\nA = start_assemble(K, f)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"where K is the global stiffness matrix, and f the global force vector. It is optional to pass the force vector to the assembler – sometimes there is no need to assemble a global force vector.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The assemble! function is used to assemble element contributions to the assembler. For example, to assemble the element tangent stiffness ke and the element force vector fe to the assembler A, the following code can be used:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"assemble!(A, celldofs, ke)\nassemble!(A, celldofs, ke, fe)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"which perform the following operations in an efficient manner:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K[celldofs, celldofs] += ke\nf[celldofs] += fe","category":"page"},{"location":"topics/assembly/#Pseudo-code-for-efficient-assembly","page":"Assembly","title":"Pseudo-code for efficient assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Quite often the same sparsity pattern can be reused multiple times. For example:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"For time-dependent problems the pattern can be reused for all timesteps\nFor non-linear problems the pattern can be reused for all iterations","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"In such cases it is enough to construct the global matrix K once. Below is some pseudo-code for how to do this for a time-dependent problem:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K = allocate_matrix(dh)\nf = zeros(ndofs(dh))\n\nfor t in 1:timesteps\n A = start_assemble(K, f) # start_assemble zeroes K and f\n for cell in CellIterator(dh)\n ke, fe = element_routine(...)\n assemble!(A, celldofs(cell), ke, fe)\n end\n # Apply boundary conditions and solve for u(t)\n # ...\nend","category":"page"},{"location":"topics/assembly/#Comparison-of-assembly-strategies","page":"Assembly","title":"Comparison of assembly strategies","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"As discussed above there are various ways to assemble the local matrix into the global one. In particular, it was mentioned that naive indexing is very inefficient and that using an assembler is faster. To put some concrete numbers to these statements we will compare some strategies in this section. First we compare just a single assembly operation (e.g. assembling an already computed local matrix) and then to relate this to a more realistic scenario we compare the full matrix assembly including the integration of all the elements.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"note: Pre-allocated global matrix\nAll strategies that we compare below uses a pre-allocated global matrix K with the correct sparsity pattern. Starting with something like K = spzeros(ndofs(dh), ndofs(dh)) and then inserting entries is excruciatingly slow due to the sparse data structure so this method is not even considered.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"For the comparison we need a representative global matrix to assemble into. In the following setup code we create a grid with triangles and a DofHandler with a quadratic scalar field. From this we instantiate the global matrix.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"using Ferrite\n\n# Quadratic scalar interpolation\nip = Lagrange{RefTriangle, 2}()\n\n# DofHandler\nconst N = 100\ngrid = generate_grid(Triangle, (N, N))\nconst dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh)\n\n# Global matrix and a corresponding assembler\nconst K = allocate_matrix(dh)\nnothing # hide","category":"page"},{"location":"topics/assembly/#Strategy-1:-matrix-indexing","page":"Assembly","title":"Strategy 1: matrix indexing","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The first strategy is to index directly, using the vector of global dofs, into the global matrix:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v1(_, K, dofs, Ke)\n K[dofs, dofs] += Ke\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"This looks very simple, but it is very inefficient (as the numbers will show later). To understand why the operation K[dofs, dofs] += Ke (with K being a sparse matrix) is so slow we can dig into the details.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"In Julia there is no \"+=\"-operation and so x += y is identical to x = x + y. Translating this to our example we have","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K[dofs, dofs] = K[dofs, dofs] + Ke","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We can break down this a bit further into these equivalent three steps:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"tmp1 = K[dofs, dofs] # 1\ntmp2 = tmp1 + Ke # 2\nK[dofs, dofs] = tmp2 # 3","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Now the problem with this strategy becomes a bit more obvious:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"In line 1 there is first an allocation of a new matrix (tmp1) followed by indexing into K to copy elements from K to tmp1. Both of these operations are rather costly: allocations should always be minimized in tight loops, and indexing into a sparse matrix is non-trivial due to the data structure. In addition, since the dofs vector contains the global indices (which are neither sorted nor consecutive) we have a random access pattern.\nIn line 2 there is another allocation of a matrix (tmp2) for the result of the addition of tmp1 and Ke.\nIn line 3 we again need to index into the sparse matrix to copy over the elements from tmp2 to K. This essentially duplicates the indexing effort from line 1 since we need to lookup the same locations in K again.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"note: Broadcasting\nUsing broadcasting, e.g. K[dofs, dofs] .+= Ke is an alternative to the above, and resembles a +=-operation. In theory this should be as efficient as the explicit loop presented in the next section.","category":"page"},{"location":"topics/assembly/#Strategy-2:-scalar-indexing","page":"Assembly","title":"Strategy 2: scalar indexing","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"A variant of the first strategy is to explicitly loop over the indices and add the elements individually as scalars:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v2(_, K, dofs, Ke)\n for (i, I) in pairs(dofs)\n for (j, J) in pairs(dofs)\n K[I, J] += Ke[i, j]\n end\n end\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The core operation, K[I, J] += Ke[i, j], can still be broken down into three equivalent steps:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"tmp1 = K[I, J]\ntmp2 = tmp1 + Ke[i, j]\nK[I, J] = tmp2","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The key difference here is that we index using integers (I, J, i, and j) which means that tmp1 and tmp2 are scalars which don't need to be allocated on the heap. This stragety thus eliminates all allocations that were present in the first strategy. However, we still lookup the same location in K twice, and we still have a random access pattern.","category":"page"},{"location":"topics/assembly/#Strategy-3:-scalar-indexing-with-single-lookup","page":"Assembly","title":"Strategy 3: scalar indexing with single lookup","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"To improve on the second strategy we will get rid of the double lookup into the sparse matrix K. While Julia doesn't have a \"+=\"-operation, Ferrite has an internal addindex!-function which does exactly what we want: it adds a value to a specific location in a sparse matrix using a single lookup.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v3(_, K, dofs, Ke)\n for (i, I) in pairs(dofs)\n for (j, J) in pairs(dofs)\n Ferrite.addindex!(K, Ke[i, j], I, J)\n end\n end\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"With this method we remove the double lookup, but the issue of random access patterns still remains.","category":"page"},{"location":"topics/assembly/#Strategy-4:-using-an-assembler","page":"Assembly","title":"Strategy 4: using an assembler","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Finally, the last strategy we consider uses an assembler. The assembler is a specific datastructure that pre-allocates some workspace to make the assembly more efficient:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v4(assembler, _, dofs, Ke)\n assemble!(assembler, dofs, Ke)\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The extra workspace inside the assembler is used to sort the dofs when assemble! is called. After sorting it is possible to loop over the sparse matrix data structure and insert all elements of Ke in one go instead of having to lookup locations randomly.","category":"page"},{"location":"topics/assembly/#Single-element-assembly","page":"Assembly","title":"Single element assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"First we will compare the four functions above for a single assembly operation, i.e. inserting one local matrix into the global matrix. For this we simply create a random local matrix since we are not conserned with the actual values. We also pick the \"middle\" element and extract the dofs for that element. Finally, an assembler is created with start_assemble to use with the fourth strategy.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"dofs_per_cell = ndofs_per_cell(dh)\nconst Ke = rand(dofs_per_cell, dofs_per_cell)\nconst dofs = celldofs(dh, N * N ÷ 2)\n\nconst assembler = start_assemble(K)\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We use BenchmarkTools to measure the performance:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"assemble_v1(assembler, K, dofs, Ke) # hide\nassemble_v2(assembler, K, dofs, Ke) # hide\nassemble_v3(assembler, K, dofs, Ke) # hide\nassemble_v4(assembler, K, dofs, Ke) # hide\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"using BenchmarkTools\n\n@btime assemble_v1(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)\n@btime assemble_v2(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)\n@btime assemble_v3(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)\n@btime assemble_v4(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The results below are obtained on an Macbook Pro with an Apple M3 CPU.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"606.438 μs (36 allocations: 7.67 MiB)\n283.300 ns (0 allocations: 0 bytes)\n158.300 ns (0 allocations: 0 bytes)\n 83.400 ns (0 allocations: 0 bytes)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The results match what we expect based on the explanations above:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Between strategy 1 and 2 we got rid of the allocations completely and decreased the time with a factor of 2100(!).\nBetween strategy 2 and 3 we got rid of the double lookup and decreased the time with another factor of almost 2.\nBetween strategy 3 and 4 we got rid of the random lookup order and decreased the time with another factor of almost 2.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The most important thing for this benchmark is to get rid of the allocations. By using an assembler instead of doing the naive thing we reduce the runtime with a factor of more than 7000(!!) in total.","category":"page"},{"location":"topics/assembly/#Full-system-assembly","page":"Assembly","title":"Full system assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We will now compare the four strategies in a more realistic scenario where we assemble all elements. This is to put the assembly performance in relation to other operations in the finite element program. After all, assembly performance might not matter in the end if other things dominate the runtime anyway.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"For this comparison we simply consider the heat equation (see Tutorial 1: Heat equation) and assemble the global matrix.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_system!(assembler_function::F, K, dh, cv) where {F}\n assembler = start_assemble(K)\n ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n n = getnbasefunctions(cv)\n for cell in CellIterator(dh)\n reinit!(cv, cell)\n ke .= 0\n for qp in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, qp)\n for i in 1:n\n ∇ϕi = shape_gradient(cv, qp, i)\n for j in 1:n\n ∇ϕj = shape_gradient(cv, qp, j)\n ke[i, j] += ( ∇ϕi ⋅ ∇ϕj ) * dΩ\n end\n end\n end\n assembler_function(assembler, K, celldofs(cell), ke)\n end\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Finally, we need cellvalues for the field in order to perform the integration:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"qr = QuadratureRule{RefTriangle}(2)\nconst cellvalues = CellValues(qr, ip)\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We can now time the four assembly strategies:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"res = 1138.8803468514259 # hide\n# assemble_system!(assemble_v1, K, dh, cellvalues) # hide\n# @assert norm(K.nzval) ≈ res # hide\nassemble_system!(assemble_v2, K, dh, cellvalues) # hide\n@assert norm(K.nzval) ≈ res # hide\nassemble_system!(assemble_v3, K, dh, cellvalues) # hide\n@assert norm(K.nzval) ≈ res # hide\nassemble_system!(assemble_v4, K, dh, cellvalues) # hide\n@assert norm(K.nzval) ≈ res # hide\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"@time assemble_system!(assemble_v1, K, dh, cellvalues)\n@time assemble_system!(assemble_v2, K, dh, cellvalues)\n@time assemble_system!(assemble_v3, K, dh, cellvalues)\n@time assemble_system!(assemble_v4, K, dh, cellvalues)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We then obtain the following results (running on the same machine as above):","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"12.175625 seconds (719.99 k allocations: 149.809 GiB, 11.59% gc time)\n 0.009313 seconds (8 allocations: 928 bytes)\n 0.006055 seconds (8 allocations: 928 bytes)\n 0.004530 seconds (10 allocations: 1.062 KiB)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"This follows the same trend as for the benchmarks for individual cell assembly and shows that the efficiency of the assembly strategy is crucial for the overall performance of the program. In particular this benchmark shows that allocations in such a tight loop from the first strategy is very costly and puts a strain on the garbage collector: 11% of the time is spent in GC instead of crunching numbers.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"It should of course be noted that the more expensive the element routine is, the less the performance of the assembly strategy matters for the total runtime. However, there are no reason not to use the fastest method given that it is readily available in Ferrite.","category":"page"},{"location":"devdocs/elements/#devdocs-elements","page":"Elements and cells","title":"Elements and cells","text":"","category":"section"},{"location":"devdocs/elements/#Type-definitions","page":"Elements and cells","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Elements or cells are subtypes of AbstractCell{<:AbstractRefShape}. As shown, they are parametrized by the associated reference element.","category":"page"},{"location":"devdocs/elements/#Required-methods-to-implement-for-all-subtypes-of-AbstractCell-to-define-a-new-element","page":"Elements and cells","title":"Required methods to implement for all subtypes of AbstractCell to define a new element","text":"","category":"section"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Ferrite.get_node_ids","category":"page"},{"location":"devdocs/elements/#Ferrite.get_node_ids","page":"Elements and cells","title":"Ferrite.get_node_ids","text":"Ferrite.get_node_ids(c::AbstractCell)\n\nReturn the node id's for cell c in the order determined by the cell's reference cell.\n\nDefault implementation: c.nodes.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Common-utilities-and-definitions-when-working-with-grids-internally.","page":"Elements and cells","title":"Common utilities and definitions when working with grids internally.","text":"","category":"section"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"First we have some topological queries on the element","category":"page"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Ferrite.vertices(::Ferrite.AbstractCell)\nFerrite.edges(::Ferrite.AbstractCell)\nFerrite.faces(::Ferrite.AbstractCell)\nFerrite.facets(::Ferrite.AbstractCell)\nFerrite.boundaryfunction(::Type{<:Ferrite.BoundaryIndex})\nFerrite.reference_vertices(::Ferrite.AbstractCell)\nFerrite.reference_edges(::Ferrite.AbstractCell)\nFerrite.reference_faces(::Ferrite.AbstractCell)","category":"page"},{"location":"devdocs/elements/#Ferrite.vertices-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.vertices","text":"Ferrite.vertices(::AbstractCell)\n\nReturns a tuple with the node indices (of the nodes in a grid) for each vertex in a given cell. This function induces the VertexIndex, where the second index corresponds to the local index into this tuple.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.edges-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.edges","text":"Ferrite.edges(::AbstractCell)\n\nReturns a tuple of 2-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented edge. This function induces the EdgeIndex, where the second index corresponds to the local index into this tuple.\n\nNote that the vertices are sufficient to define an edge uniquely.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.faces-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.faces","text":"Ferrite.faces(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented face. This function induces the FaceIndex, where the second index corresponds to the local index into this tuple.\n\nAn oriented face is a face with the first node having the local index and the other nodes spanning such that the normal to the face is pointing outwards.\n\nNote that the vertices are sufficient to define a face uniquely.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.facets-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.facets","text":"Ferrite.facets(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented facet. This function induces the FacetIndex, where the second index corresponds to the local index into this tuple.\n\nSee also vertices, edges, and faces\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.boundaryfunction-Tuple{Type{<:Ferrite.BoundaryIndex}}","page":"Elements and cells","title":"Ferrite.boundaryfunction","text":"boundaryfunction(::Type{<:BoundaryIndex})\n\nHelper function to dispatch on the correct entity from a given boundary index.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.reference_vertices-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.reference_vertices","text":"reference_vertices(::Type{<:AbstractRefShape})\nreference_vertices(::AbstractCell)\n\nReturns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.reference_edges-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.reference_edges","text":"reference_edges(::Type{<:AbstractRefShape})\nreference_edges(::AbstractCell)\n\nReturns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.reference_faces-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.reference_faces","text":"reference_faces(::Type{<:AbstractRefShape})\nreference_faces(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"and some generic utils which are commonly found in finite element codes","category":"page"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Ferrite.BoundaryIndex\nFerrite.get_coordinate_eltype(::Ferrite.AbstractGrid)\nFerrite.get_coordinate_eltype(::Node)\nFerrite.toglobal\nFerrite.sortface\nFerrite.sortface_fast\nFerrite.sortedge\nFerrite.sortedge_fast\nFerrite.element_to_facet_transformation\nFerrite.facet_to_element_transformation\nFerrite.InterfaceOrientationInfo\nFerrite.transform_interface_points!\nFerrite.get_transformation_matrix","category":"page"},{"location":"devdocs/elements/#Ferrite.BoundaryIndex","page":"Elements and cells","title":"Ferrite.BoundaryIndex","text":"Abstract type which is used as identifier for faces, edges and verices\n\n\n\n\n\n","category":"type"},{"location":"devdocs/elements/#Ferrite.get_coordinate_eltype-Tuple{Ferrite.AbstractGrid}","page":"Elements and cells","title":"Ferrite.get_coordinate_eltype","text":"Return the number type of the nodal coordinates.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.get_coordinate_eltype-Tuple{Node}","page":"Elements and cells","title":"Ferrite.get_coordinate_eltype","text":"get_coordinate_eltype(::Node)\n\nGet the data type of the components of the nodes coordinate.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.toglobal","page":"Elements and cells","title":"Ferrite.toglobal","text":"toglobal(grid::AbstractGrid, vertexidx::VertexIndex) -> Int\ntoglobal(grid::AbstractGrid, vertexidx::Vector{VertexIndex}) -> Vector{Int}\n\nThis function takes the local vertex representation (a VertexIndex) and looks up the unique global id (an Int).\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortface","page":"Elements and cells","title":"Ferrite.sortface","text":"sortface(face::Tuple{Int})\nsortface(face::Tuple{Int,Int})\nsortface(face::Tuple{Int,Int,Int})\nsortface(face::Tuple{Int,Int,Int,Int})\n\nReturns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortface_fast","page":"Elements and cells","title":"Ferrite.sortface_fast","text":"sortface_fast(face::Tuple{Int})\nsortface_fast(face::Tuple{Int,Int})\nsortface_fast(face::Tuple{Int,Int,Int})\nsortface_fast(face::Tuple{Int,Int,Int,Int})\n\nReturns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortedge","page":"Elements and cells","title":"Ferrite.sortedge","text":"sortedge(edge::Tuple{Int,Int})\n\nReturns the unique representation of an edge and its orientation. Here the unique representation is the sorted node index tuple. The orientation is true if the edge is not flipped, where it is false if the edge is flipped.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortedge_fast","page":"Elements and cells","title":"Ferrite.sortedge_fast","text":"sortedge_fast(edge::Tuple{Int,Int})\n\nReturns the unique representation of an edge. Here the unique representation is the sorted node index tuple.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.element_to_facet_transformation","page":"Elements and cells","title":"Ferrite.element_to_facet_transformation","text":"element_to_facet_transformation(point::AbstractVector, ::Type{<:AbstractRefShape}, facet::Int)\n\nTransform quadrature point from the cell's coordinates to the facet's reference coordinates, decreasing the number of dimensions by one. This is the inverse of facet_to_element_transformation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.facet_to_element_transformation","page":"Elements and cells","title":"Ferrite.facet_to_element_transformation","text":"facet_to_element_transformation(point::Vec, ::Type{<:AbstractRefShape}, facet::Int)\n\nTransform quadrature point from the facet's reference coordinates to coordinates on the cell's facet, increasing the number of dimensions by one.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.InterfaceOrientationInfo","page":"Elements and cells","title":"Ferrite.InterfaceOrientationInfo","text":"InterfaceOrientationInfo\n\nRelative orientation information for 1D and 2D interfaces in 2D and 3D elements respectively. This information is used to construct the transformation matrix to transform the quadrature points from faceta to facetb achieving synced spatial coordinates. Face B's orientation relative to Face A's can possibly be flipped (i.e. the vertices indices order is reversed) and the vertices can be rotated against each other. The reference orientation of face B is such that the first node has the lowest vertex index. Thus, this structure also stores the shift of the lowest vertex index which is used to reorient the face in case of flipping transform_interface_points!.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/elements/#Ferrite.transform_interface_points!","page":"Elements and cells","title":"Ferrite.transform_interface_points!","text":"transform_interface_points!(dst::AbstractVector{Vec{3, Float64}}, points::AbstractVector{Vec{3, Float64}}, interface_transformation::InterfaceOrientationInfo)\n\nTransform the points from face A to face B using the orientation information of the interface and store it in the vector dst. For 3D, the faces are transformed into regular polygons such that the rotation angle is the shift in reference node index × 2π ÷ number of edges in face. If the face is flipped then the flipping is about the axis that preserves the position of the first node (which is the reference node after being rotated to be in the first position, it's rotated back in the opposite direction after flipping). Take for example the interface\n\n 2 3\n | \\ | \\\n | \\ | \\\ny | A \\ | B \\\n↑ | \\ | \\\n→ x 1-----3 1-----2\n\nTransforming A to an equilateral triangle and translating it such that {0,0} is equidistant to all nodes\n\n 3\n +\n / \\\n / \\\n / x \\\n / ↑ \\\n / ← \\\n / y \\\n2+-------------+1\n\nRotating it -270° (or 120°) such that the reference node (the node with the smallest index) is at index 1\n\n 1\n +\n / \\\n / \\\n / x \\\n / ↑ \\\n / ← \\\n / y \\\n3+-------------+2\n\nFlipping about the x axis (such that the position of the reference node doesn't change) and rotating 270° (or -120°)\n\n 2\n +\n / \\\n / \\\n / x \\\n / ↑ \\\n / ← \\\n / y \\\n3+-------------+1\n\nTransforming back to triangle B\n\n 3\n | \\\n | \\\ny | \\\n↑ | \\\n→ x 1-----2\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.get_transformation_matrix","page":"Elements and cells","title":"Ferrite.get_transformation_matrix","text":"get_transformation_matrix(interface_transformation::InterfaceOrientationInfo)\n\nReturns the transformation matrix corresponding to the interface orientation information stored in InterfaceOrientationInfo. The transformation matrix is constructed using a combination of affine transformations defined for each interface reference shape. The transformation for a flipped face is a function of both relative orientation and the orientation of the second face. If the face is not flipped then the transformation is a function of relative orientation only.\n\n\n\n\n\n","category":"function"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"EditURL = \"https://github.com/Ferrite-FEM/Ferrite.jl/blob/master/CHANGELOG.md\"","category":"page"},{"location":"changelog/#Ferrite-changelog","page":"Changelog","title":"Ferrite changelog","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"All notable changes to this project will be documented in this file.","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.","category":"page"},{"location":"changelog/#[Unreleased]","page":"Changelog","title":"[Unreleased]","text":"","category":"section"},{"location":"changelog/#Removed","page":"Changelog","title":"Removed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The deprecated third type parameter for interpolations have been removed. Old code which tries to use three parameters will now throw the somewhat cryptic error:\njulia> Lagrange{2, RefCube, 1}()\nERROR: too many parameters for type\n(#1083)","category":"page"},{"location":"changelog/#[v1.0.0](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v1.0.0)-2024-09-30","page":"Changelog","title":"v1.0.0 - 2024-09-30","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite version 1.0 is a relatively large release, with a lot of new features, improvements, deprecations and some removals. These changes are made to make the code base more consistent and more suitable for future improvements. With this 1.0 release we are aiming for long time stability, and there is no breaking release 2.0 on the horizon.","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Unfortunately this means that code written for Ferrite version 0.3 will have to be updated. All changes, with upgrade paths, are listed in the sections below. Since these sections include a lot of other information as well (new features, internal changes, ...) there is also a dedicated section about upgrading code from Ferrite 0.3 to 1.0 (see below) which include the most common changes that are required. In addition, in all cases where possible, you will be presented with a descriptive error message telling you what needs to change.","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Deprecations for 1.0 will be removed during the 1.x release series. When upgrading old code it is therefore recommended to use Ferrite 1.0 as a first stepping stone since this release contain descriptive deprecation error messages that might not exist in e.g. Ferrite version 1.2.","category":"page"},{"location":"changelog/#Upgrading-code-from-Ferrite-0.3-to-1.0","page":"Changelog","title":"Upgrading code from Ferrite 0.3 to 1.0","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"This section give a short overview of the most common required changes. More details and motivation are included in the following sections (with links to issues/pull request for more discussion).","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Interpolations: remove the first parameter (the reference dimension) and use new reference shapes.\nExamples:\n# Linear Lagrange interpolation for a line\n- Lagrange{1, RefCube, 1}()\n+ Lagrange{RefLine, 1}()\n\n# Linear Lagrange interpolation for a quadrilateral\n- Lagrange{2, RefCube, 1}()\n+ Lagrange{RefQuadrilateral, 1}()\n\n# Quadratic Lagrange interpolation for a triangle\n- Lagrange{2, RefTetrahedron, 2}()\n+ Lagrange{RefTriangle, 2}()\nFor vector valued problems it is now required to explicitly vectorize the interpolation using the new VectorizedInterpolation. This is required when passing the interpolation to CellValues and when adding fields to the DofHandler using add!. In both of these places the interpolation was implicitly vectorized in Ferrite 0.3.\nExamples:\n# Linear Lagrange interpolation for a vector problem on the triangle (vector dimension\n# same as the reference dimension)\nip_scalar = Lagrange{RefTriangle, 1}()\nip_vector = ip_scalar ^ 2 # or VectorizedInterpolation{2}(ip_scalar)\nQuadrature: remove the first parameter (the reference dimension) and use new reference shapes.\nExamples:\n# Quadrature for a line\n- QuadratureRule{1, RefCube}(quadrature_order)\n+ QuadratureRule{RefLine}(quadrature_order)\n\n# Quadrature for a quadrilateral\n- QuadratureRule{2, RefCube}(quadrature_order)\n+ QuadratureRule{RefQuadrilateral}(quadrature_order)\n\n# Quadrature for a tetrahedron\n- QuadratureRule{3, RefTetrahedron}(quadrature_order)\n+ QuadratureRule{RefTetrahedron}(quadrature_order)\nQuadrature for face integration (FacetValues): replace QuadratureRule{dim-1, reference_shape}(quadrature_order) with FacetQuadratureRule{reference_shape}(quadrature_order).\nExamples:\n# Quadrature for the facets of a quadrilateral\n- QuadratureRule{1, RefCube}(quadrature_order)\n+ FacetQuadratureRule{RefQuadrilateral}(quadrature_order)\n\n# Quadrature for the facets of a triangle\n- QuadratureRule{1, RefTetrahedron}(quadrature_order)\n+ FacetQuadratureRule{RefTriangle}(quadrature_order)\n\n# Quadrature for the facets of a hexhedron\n- QuadratureRule{2, RefCube}(quadrature_order)\n+ FacetQuadratureRule{RefHexahedron}(quadrature_order)\nCellValues: replace usage of CellScalarValues and CellVectorValues with CellValues. For vector valued problems the interpolation passed to CellValues should be vectorized to a VectorizedInterpolation (see above).\nExamples:\n# CellValues for a scalar problem with triangle elements\n- qr = QuadratureRule{2, RefTetrahedron}(quadrature_order)\n- ip = Lagrange{2, RefTetrahedron, 1}()\n- cv = CellScalarValues(qr, ip)\n+ qr = QuadratureRule{RefTriangle}(quadrature_order)\n+ ip = Lagrange{RefTriangle, 1}()\n+ cv = CellValues(qr, ip)\n\n# CellValues for a vector problem with hexahedronal elements\n- qr = QuadratureRule{3, RefCube}(quadrature_order)\n- ip = Lagrange{3, RefCube, 1}()\n- cv = CellVectorValues(qr, ip)\n+ qr = QuadratureRule{RefHexahedron}(quadrature_order)\n+ ip = Lagrange{RefHexahedron, 1}() ^ 3\n+ cv = CellValues(qr, ip)\nIf you use CellScalarValues or CellVectorValues in method signature you must replace them with CellValues. Note that the type parameters are different.\nExamples:\n- function do_something(cvs::CellScalarValues, cvv::CellVectorValues)\n+ function do_something(cvs::CellValues, cvv::CellValues)\nThe default geometric interpolation have changed from the function interpolation to always use linear Lagrange interpolation. If you use linear elements in the grid, and a higher order interpolation for the function you can now rely on the new default:\nqr = QuadratureRule(...)\n- ip_function = Lagrange{2, RefTetrahedron, 2}()\n- ip_geometry = Lagrange{2, RefTetrahedron, 1}()\n- cv = CellScalarValues(qr, ip_function, ip_geometry)\n+ ip_function = Lagrange{2, RefTetrahedron, 2}()\n+ cv = CellValues(qr, ip_function)\nand if you have quadratic (or higher order) elements in the grid you must now pass the corresponding interpolation to the constructor:\nqr = QuadratureRule(...)\n- ip_function = Lagrange{2, RefTetrahedron, 2}()\n- cv = CellScalarValues(qr, ip_function)\n+ ip_function = Lagrange{2, RefTetrahedron, 2}()\n+ ip_geometry = Lagrange{2, RefTetrahedron, 1}()\n+ cv = CellValues(qr, ip_function, ip_geometry)\nFacetValues: replace usage of FaceScalarValues and FaceVectorValues with FacetValues. For vector valued problems the interpolation passed to FacetValues should be vectorized to a VectorizedInterpolation (see above). The input quadrature rule should be a FacetQuadratureRule instead of a QuadratureRule.\nExamples:\n# FacetValues for a scalar problem with triangle elements\n- qr = QuadratureRule{1, RefTetrahedron}(quadrature_order)\n- ip = Lagrange{2, RefTetrahedron, 1}()\n- cv = FaceScalarValues(qr, ip)\n+ qr = FacetQuadratureRule{RefTriangle}(quadrature_order)\n+ ip = Lagrange{RefTriangle, 1}()\n+ cv = FacetValues(qr, ip)\n\n# FaceValues for a vector problem with hexahedronal elements\n- qr = QuadratureRule{2, RefCube}(quadrature_order)\n- ip = Lagrange{3, RefCube, 1}()\n- cv = FaceVectorValues(qr, ip)\n+ qr = FacetQuadratureRule{RefHexahedron}(quadrature_order)\n+ ip = Lagrange{RefHexahedron, 1}() ^ 3\n+ cv = FacetValues(qr, ip)\nDofHandler construction: it is now required to pass the interpolation explicitly when adding new fields using add! (previously it was optional, defaulting to the default interpolation of the elements in the grid). For vector-valued fields the interpolation should be vectorized, instead of passing the number of components to add! as an integer.\nExamples:\ndh = DofHandler(grid) # grid with triangles\n\n# Vector field :u\n- add!(dh, :u, 2)\n+ add!(dh, :u, Lagrange{RefTriangle, 1}()^2)\n\n# Scalar field :p\n- add!(dh, :u, 1)\n+ add!(dh, :u, Lagrange{RefTriangle, 1}())\nBoundary conditions: The entity enclosing a cell was previously called face, but is now denoted a facet. When applying boundary conditions, rename getfaceset to getfacetset and addfaceset! is now addfacetset!. These sets are now described by FacetIndex instead of FaceIndex. When looping over the facets of a cell, change nfaces to nfacets.\nExamples:\n# Dirichlet boundary conditions\n- addfaceset!(grid, \"dbc\", x -> x[1] ≈ 1.0)\n+ addfacetset!(grid, \"dbc\", x -> x[1] ≈ 1.0)\n\n- dbc = Dirichlet(:u, getfaceset(grid, \"dbc\"), Returns(0.0))\n+ dbc = Dirichlet(:u, getfacetset(grid, \"dbc\"), Returns(0.0))\n\n# Neumann boundary conditions\n- for facet in 1:nfaces(cell)\n- if (cellid(cell), facet) ∈ getfaceset(grid, \"Neumann Boundary\")\n+ for facet in 1:nfacets(cell)\n+ if (cellid(cell), facet) ∈ getfacetset(grid, \"Neumann Boundary\")\n # ...\nVTK Export: The VTK export has been changed #692.\n- vtk_grid(name, dh) do vtk\n- vtk_point_data(vtk, dh, a)\n- vtk_point_data(vtk, nodal_data, \"my node data\")\n- vtk_point_data(vtk, proj, projected_data, \"my projected data\")\n- vtk_cell_data(vtk, proj, projected_data, \"my projected data\")\n+ VTKGridFile(name, dh) do vtk\n+ write_solution(vtk, dh, a)\n+ write_node_data(vtk, nodal_data, \"my node data\")\n+ write_projection(vtk, proj, projected_data, \"my projected data\")\n+ write_cell_data(vtk, cell_data, \"my projected data\")\nend\nWhen using a paraview_collection collection for e.g. multiple timesteps the VTKGridFile object can be used instead of the previous type returned from vtk_grid.\nSparsity pattern and global matrix construction: since there is now explicit support for working with the sparsity pattern before instantiating a matrix the function create_sparsity_pattern has been removed. To recover the old functionality that return a sparse matrix from the DofHandler directly use allocate_matrix instead.\nExamples:\n# Create sparse matrix from DofHandler\n- K = create_sparsity_pattern(dh)\n+ K = allocate_matrix(dh)\n\n# Create condensed sparse matrix from DofHandler + ConstraintHandler\n- K = create_sparsity_pattern(dh, ch)\n+ K = allocate_matrix(dh, ch)","category":"page"},{"location":"changelog/#Added","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"InterfaceValues for computing jumps and averages over interfaces. (#743)\nInterfaceIterator and InterfaceCache for iterating over interfaces. (#747)\nFacetQuadratureRule implementation for RefPrism and RefPyramid. (#779)\nThe DofHandler now support selectively adding fields on sub-domains (rather than the full domain). This new functionality is included with the new SubDofHandler struct, which, as the name suggest, is a DofHandler for a subdomain. (#624, #667, #735)\nNew reference shape structs RefLine, RefTriangle, RefQuadrilateral, RefTetrahedron, RefHexahedron, and RefPrism have been added. These encode the reference dimension, and will thus replace the old reference shapes for which it was necessary to always pair with an explicit dimension (i.e. RefLine replaces (RefCube, 1), RefTriangle replaces (RefTetrahedron, 2), etc.). For writing \"dimension independent code\" it is possible to use Ferrite.RefHypercube{dim} and Ferrite.RefSimplex{dim}. (#679)\nNew methods for adding entitysets that are located on the boundary of the grid: addboundaryfacetset! and addboundaryvertexset!. These work similar to addfacetset! and addvertexset!, but filters out all instances not on the boundary (this can be used to avoid accidental inclusion of internal entities in sets used for boundary conditions, for example). (#606)\nNew interpolation VectorizedInterpolation which vectorizes scalar interpolations for vector-valued problems. A VectorizedInterpolation is created from a (scalar) interpolation ip using either ip ^ dim or VectorizedInterpolation{dim}(ip). For convenience, the method VectorizedInterpolation(ip) vectorizes the interpolation to the reference dimension of the interpolation. (#694, #736)\nNew (scalar) interpolation Lagrange{RefQuadrilateral, 3}(), i.e. third order Lagrange interpolation for 2D quadrilaterals. (#701, #731)\nCellValues now support embedded elements. Specifically you can now embed elements with reference dimension 1 into spatial dimension 2 or 3, and elements with reference dimension 2 in to spatial dimension 3. (#651)\nCellValues now support (vector) interpolations with dimension different from the spatial dimension. (#651)\nFacetQuadratureRule have been added and should be used for FacetValues. A FacetQuadratureRule for integration of the facets of e.g. a triangle can be constructed by FacetQuadratureRule{RefTriangle}(order) (similar to how QuadratureRule is constructed). (#716)\nNew functions Ferrite.reference_shape_value(::Interpolation, ξ::Vec, i::Int) and Ferrite.reference_shape_gradient(::Interpolation, ξ::Vec, i::Int) for evaluating the value/gradient of the ith shape function of an interpolation in local reference coordinate ξ. These methods are public but not exported. (Note that these methods return the value/gradient wrt. the reference coordinate ξ, whereas the corresponding methods for CellValues etc return the value/gradient wrt the spatial coordinate x.) (#721)\nFacetIterator and FacetCache have been added. These work similarly to CellIterator and CellCache but are used to iterate over (boundary) face sets instead. These simplify boundary integrals in general, and in particular Neumann boundary conditions are more convenient to implement now that you can loop directly over the face set instead of checking all faces of a cell inside the element routine. (#495)\nThe ConstraintHandler now support adding Dirichlet boundary conditions on discontinuous interpolations. (#729)\ncollect_periodic_faces now have a keyword argument tol that can be used to relax the default tolerance when necessary. (#749)\nVTK export now work with QuadraticHexahedron elements. (#714)\nThe function bounding_box(::AbstractGrid) has been added. It computes the bounding box for a given grid (based on its node coordinates), and returns the minimum and maximum vertices of the bounding box. (#880)\nSupport for working with sparsity patterns has been added. This means that Ferrite exposes the intermediate \"state\" between the DofHandler and the instantiated matrix as the new struct SparsityPattern. This make it possible to insert custom equations or couplings in the pattern before instantiating the matrix. The function create_sparsity_pattern have been removed. The new function allocate_matrix is instead used to instantiate the matrix. Refer to the documentation for more details. (#888)\nTo upgrade: if you want to recover the old functionality and don't need to work with the pattern, replace any usage of create_sparsity_pattern with allocate_matrix.\nA new function, geometric_interpolation, is exported, which gives the geometric interpolation for each cell type. This is equivalent to the deprecated Ferrite.default_interpolation function. (#953)\nCellValues and FacetValues can now store and map second order gradients (Hessians). The number of gradients computed in CellValues/FacetValues is specified using the keyword arguments update_gradients::Bool (default true) and update_hessians::Bool (default false) in the constructors, i.e. CellValues(...; update_hessians=true). (#953)\nL2Projector supports projecting on grids with mixed celltypes. (#949)","category":"page"},{"location":"changelog/#Changed","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"It is now possible to create sparsity patterns with interface couplings, see the new function add_interface_entries! and the rework of sparsity pattern construction. (#710)\nThe AbstractCell interface has been reworked. This change should not affect user code, but may in some cases be relevant for code parsing external mesh files. In particular, the generic Cell struct have been removed in favor of concrete cell implementations (Line, Triangle, ...). (#679, #712)\nTo upgrade replace any usage of Cell{...}(...) with calls to the concrete implementations.\nThe default geometric mapping in CellValues and FacetValues have changed. The new default is to always use Lagrange{refshape, 1}(), i.e. linear Lagrange polynomials, for the geometric interpolation. Previously, the function interpolation was (re) used also for the geometry interpolation. (#695)\nTo upgrade, if you relied on the previous default, simply pass the function interpolation also as the third argument (the geometric interpolation).\nAll interpolations are now categorized as either scalar or vector interpolations. All (previously) existing interpolations are scalar. (Scalar) interpolations must now be explicitly vectorized, using the new VectorizedInterpolation, when used for vector problems. (Previously implicit vectorization happened in the CellValues constructor, and when adding fields to the DofHandler). (#694)\nIt is now required to explicitly pass the interpolation to the DofHandler when adding a new field using add!. For vector fields the interpolation should be vectorized, instead of passing number of components as an integer. (#694)\nTo upgrade don't pass the dimension as an integer, and pass the interpolation explicitly. See more details in Upgrading code from Ferrite 0.3 to 1.0.\nInterpolations should now be constructed using the new reference shapes. Since the new reference shapes encode the reference dimension the first type parameter of interpolations have been removed. (#711) To upgrade replace e.g. Lagrange{1, RefCube, 1}() with Lagrange{RefLine, 1}(), and Lagrange{2, RefTetrahedron, 1}() with Lagrange{RefTriangle, 1}(), etc.\nQuadratureRules should now be constructed using the new reference shapes. Since the new reference shapes encode the reference dimension the first type parameter of QuadratureRule have been removed. (#711, #716) To upgrade replace e.g. QuadratureRule{1, RefCube}(order) with QuadratureRule{RefLine}(order), and QuadratureRule{2, RefTetrahedron}(1) with Lagrange{RefTriangle}(order), etc.\nCellScalarValues and CellVectorValues have been merged into CellValues, FaceScalarValues and FaceVectorValues have been merged into FacetValues, and PointScalarValues and PointVectorValues have been merged into PointValues. The differentiation between scalar and vector have thus been moved to the interpolation (see above). Note that previously CellValues, FaceValues, and PointValues where abstract types, but they are now concrete implementations with different type parameters, except FaceValues which is now FacetValues (#708) To upgrade, for scalar problems, it is enough to replace CellScalarValues with CellValues, FaceScalarValues with FacetValues and PointScalarValues with PointValues, respectively. For vector problems, make sure to vectorize the interpolation (see above) and then replace CellVectorValues with CellValues, FaceVectorValues with FacetValues, and PointVectorValues with PointValues.\nThe quadrature rule passed to FacetValues should now be of type FacetQuadratureRule rather than of type QuadratureRule. (#716) To upgrade replace the quadrature rule passed to FacetValues with a FacetQuadratureRule.\nChecking if a face (ele_id, local_face_id) ∈ faceset has been previously implemented by type piracy. In order to be invariant to the underlying Set datatype as well as omitting type piracy, (#835) implemented isequal and hash for BoundaryIndex datatypes.\nVTK export: Ferrite no longer extends WriteVTK.vtk_grid and associated functions, instead the new type VTKGridFile should be used instead. New methods exists for writing to a VTKGridFile, e.g. write_solution, write_cell_data, write_node_data, and write_projection. See #692.\nDefinitions: Previously, face and edge referred to codimension 1 relative reference shape. In Ferrite v1, volume, face, edge, and vertex refer to 3, 2, 1, and 0 dimensional entities, and facet replaces the old definition of face. No direct replacement for edges exits. See #914 and #914. The main implications of this change are\nFaceIndex -> FacetIndex (FaceIndex still exists, but has a different meaning)\nFaceValues -> FacetValues\nnfaces -> nfacets (nfaces is now an internal method with different meaning)\naddfaceset! -> addfacetset\ngetfaceset -> getfacetset\nFurthermore, subtypes of Interpolation should now define vertexdof_indices, edgedof_indices, facedof_indices, volumedof_indices (and similar) according to these definitions.\nFerrite.getdim has been changed into Ferrite.getrefdim for getting the dimension of the reference shape and Ferrite.getspatialdim to get the spatial dimension (of the grid). (#943)\nFerrite.getfielddim(::AbstractDofHandler, args...) has been renamed to Ferrite.n_components. (#943)\nThe constructor for ExclusiveTopology only accept an AbstractGrid as input, removing the alternative of providing a Vector{<:AbstractCell}, as knowing the spatial dimension is required for correct code paths. Furthermore, it uses a new internal data structure, ArrayOfVectorViews, to store the neighborhood information more efficiently The datatype for the neighborhood has thus changed to a view of a vector, instead of the now removed EntityNeighborhood container. This also applies to vertex_star_stencils. (#974).\nproject(::L2Projector, data, qr_rhs) now expects data to be indexed by the cellid, as opposed to the index in the vector of cellids passed to the L2Projector. The data may be passed as an AbstractDict{Int, <:AbstractVector}, as an alternative to AbstractArray{<:AbstractVector}. (#949)","category":"page"},{"location":"changelog/#Deprecated","page":"Changelog","title":"Deprecated","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The rarely (if ever) used methods of function_value, function_gradient, function_divergence, and function_curl taking vectorized dof values as in put have been deprecated. (#698)\nThe function reshape_to_nodes have been deprecated in favor of evaluate_at_grid_nodes. (#703)\nstart_assemble([n::Int]) has been deprecated in favor of calling Ferrite.COOAssembler() directly (#916, #1058).\nstart_assemble(f, K) have been deprecated in favor of the \"canonical\" start_assemble(K, f). (#707)\nassemble!(assembler, dofs, fe, Ke) have been deprecated in favor of the \"canonical\" assemble!(assembler, dofs, Ke, fe). (#1059)\nend_assemble have been deprecated in favor of finish_assemble. (#754)\nget_point_values have been deprecated in favor of evaluate_at_points. (#754)\ntransform! have been deprecated in favor of transform_coordinates!. (#754)\nFerrite.default_interpolation has been deprecated in favor of geometric_interpolation. (#953)","category":"page"},{"location":"changelog/#Removed-2","page":"Changelog","title":"Removed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"MixedDofHandler + FieldHandler have been removed in favor of DofHandler + SubDofHandler. Note that the syntax has changed, and note that SubDofHandler is much more capable compared to FieldHandler. Previously it was often required to pass both the MixedDofHandler and the FieldHandler to e.g. the assembly routine, but now it is enough to pass the SubDofHandler since it can be used for e.g. DoF queries etc. (#624, #667, #735)\nSome old methods to construct the L2Projector have been removed after being deprecated for several releases. (#697)\nThe option project_to_nodes have been removed from project(::L2Projector, ...). The returned values are now always ordered according to the projectors internal DofHandler. (#699)\nThe function compute_vertex_values have been removed. (#700)\nThe names getweights, getpoints, getcellsets, getnodesets, getfacesets, getedgesets, and getvertexsets have been removed from the list of exported names. (For now you can still use them by prefixing Ferrite., e.g. Ferrite.getweights.) (#754)\nThe onboundary function (and the associated boundary_matrix property of the Grid datastructure) have been removed (#924). Instead of first checking onboundary and then check whether a facet belong to a specific facetset, check the facetset directly. For example:\n- if onboundary(cell, local_face_id) && (cell_id, local_face_id) in getfacesets(grid, \"traction_boundary\")\n+ if (cell_id, local_face_id) in getfacesets(grid, \"traction_boundary\")\n # integrate the \"traction_boundary\" boundary\n end","category":"page"},{"location":"changelog/#Fixed","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Benchmarks now work with master branch. (#751, #855)\nTopology construction have been generalized to, in particular, fix construction for 1D and for wedge elements. (#641, #670, #684)","category":"page"},{"location":"changelog/#Other-improvements","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Documentation:\nThe documentation is now structured according to the Diataxis framework. There is now also clear separation between tutorials (for teaching) and code gallery (for showing off). (#737, #756)\nNew section in the developer documentation that describes the (new) reference shapes and their numbering scheme. (#688)\nPerformance:\nFerrite.transform!(grid, f) (for transforming the node coordinates in the grid according to a function f) is now faster and allocates less. (#675)\nSlight performance improvement in construction of PointEvalHandler (faster reverse coordinate lookup). (#719)\nVarious performance improvements to topology construction. (#753, #759)\nInternal improvements:\nThe dof distribution interface have been updated to support higher order elements (future work). (#627, #732, #733)\nThe AbstractGrid and AbstractDofHandler interfaces are now used more consistently internally. This will help with the implementation of distributed grids and DofHandlers. (#655)\nVTK export now uses the (geometric) interpolation directly when evaluating the finite element field instead of trying to work backwards how DoFs map to nodes. (#703)\nImproved bounds checking in assemble!. (#706)\nInternal methods Ferrite.value and Ferrite.derivative for computing the value/gradient of all shape functions have been removed. (#720)\nFerrite.create_incidence_matrix now work with any AbstractGrid (not just Grid). (#726)","category":"page"},{"location":"changelog/#[v0.3.14](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.14)-2023-04-03","page":"Changelog","title":"v0.3.14 - 2023-04-03","text":"","category":"section"},{"location":"changelog/#Added-2","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Support reordering dofs of a MixedDofHandler by the built-in orderings FieldWise and ComponentWise. This includes support for reordering dofs of fields on subdomains. (#645)\nSupport specifying the coupling between fields in a MixedDofHandler when creating the sparsity pattern. (#650)\nSupport Metis dof reordering with coupling information for MixedDofHandler. (#650)\nPretty printing for MixedDofHandler and L2Projector. (#465)","category":"page"},{"location":"changelog/#Other-improvements-2","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The MixedDofHandler have gone through a performance review (see #629) and now performs the same as DofHandler. This was part of the push to merge the two DoF handlers. Since MixedDofHandler is strictly more flexible, and now equally performant, it will replace DofHandler in the next breaking release. (#637, #639, #642, #643, #656, #660)","category":"page"},{"location":"changelog/#Internal-changes","page":"Changelog","title":"Internal changes","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Changes listed here should not affect regular usage, but listed here in case you have been poking into Ferrite internals:","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite.ndim(dh, fieldname) has been removed, use Ferrite.getfielddim(dh, fieldname) instead. (#658)\nFerrite.nfields(dh) has been removed, use length(Ferrite.getfieldnames(dh)) instead. (#444, #653)\ngetfielddims(::FieldHandler) and getfieldinterpolations(::FieldHandler) have been removed (#647, #659)","category":"page"},{"location":"changelog/#[v0.3.13](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.13)-2023-03-23","page":"Changelog","title":"v0.3.13 - 2023-03-23","text":"","category":"section"},{"location":"changelog/#Added-3","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Support for classical trilinear and triquadratic wedge elements. (#581)\nSymmetric quadrature rules up to order 10 for prismatic elements. (#581)\nFiner granulation of dof distribution, allowing to distribute different amounts of dofs per entity. (#581)","category":"page"},{"location":"changelog/#Fixed-2","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Dof distribution for embedded elements. (#581)\nImprove numerical accuracy in shape function evaluation for the Lagrange{2,Tetrahedron,(3|4|5)} interpolations. (#582, #633)","category":"page"},{"location":"changelog/#Other-improvements-3","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Documentation:\nNew \"Developer documentation\" section in the manual for documenting Ferrite.jl internals and developer tools. (#611)\nFix a bug in constraint computation in Stoke's flow example. (#614)\nPerformance:\nBenchmarking infrastructure to help tracking performance changes. (#388)\nPerformance improvements for various accessor functions for MixedDofHandler. (#621)","category":"page"},{"location":"changelog/#Internal-changes-2","page":"Changelog","title":"Internal changes","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"To clarify the dof management vertices(ip), edges(ip) and faces(ip) has been deprecated in favor of vertexdof_indices(ip), edgedof_indices(ip) and facedof_indices(ip). (#581)\nDuplicate grid representation has been removed from the MixedDofHandler. (#577)","category":"page"},{"location":"changelog/#[v0.3.12](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.12)-2023-02-28","page":"Changelog","title":"v0.3.12 - 2023-02-28","text":"","category":"section"},{"location":"changelog/#Added-4","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Added a basic show method for assemblers. (#598)","category":"page"},{"location":"changelog/#Fixed-3","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix an issue in constraint application of Symmetric-wrapped sparse matrices (i.e. obtained from create_symmatric_sparsity_pattern). In particular, apply!(K::Symmetric, f, ch) would incorrectly modify f if any of the constraints were inhomogeneous. (#592)\nProperly disable the Metis extension on Julia 1.9 instead of causing precompilation errors. (#588)\nFix adding Dirichlet boundary conditions on nodes when using MixedDofHandler. (#593, #594)\nFix accidentally slow implementation of show for Grids. (#599)\nFixes to topology functionality. (#453, #518, #455)\nFix grid coloring for cell sets with 0 or 1 cells. (#600)","category":"page"},{"location":"changelog/#Other-improvements-4","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Documentation improvements:\nSimplications and clarifications to hyperelasticity example. (#591)\nRemove duplicate docstring entry for vtk_point_data. (#602)\nUpdate documentation about initial conditions. (#601, #604)","category":"page"},{"location":"changelog/#[v0.3.11](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.11)-2023-01-17","page":"Changelog","title":"v0.3.11 - 2023-01-17","text":"","category":"section"},{"location":"changelog/#Added-5","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Metis.jl extension for fill-reducing DoF permutation. This uses Julias new package extension mechanism (requires Julia 1.10) to support a new DoF renumbering order DofOrder.Ext{Metis}() that can be passed to renumber! to renumber DoFs using the Metis.jl library. (#393, #549)\nBlockArrays.jl extension for creating a globally blocked system matrix. create_sparsity_pattern(BlockMatrix, dh, ch; kwargs...) return a matrix that is blocked by field (requires DoFs to be (re)numbered by field, i.e. renumber!(dh, DofOrder.FieldWise())). For custom blocking it is possible to pass an uninitialized BlockMatrix with the correct block sizes (see BlockArrays.jl docs). This functionality is useful for e.g. special solvers where individual blocks need to be extracted. Requires Julia version 1.9 or above. (#567)\nNew function apply_analytical! for setting the values of the degrees of freedom for a specific field according to a spatial function f(x). (#532)\nNew cache struct CellCache to be used when iterating over the cells in a grid or DoF handler. CellCache caches nodes, coordinates, and DoFs, for the cell. The cache cc can be re-initialized for a new cell index ci by calling reinit!(cc, ci). This can be used as an alternative to CellIterator when more control over which element to loop over is needed. See documentation for CellCache for more information. (#546)\nIt is now possible to create the sparsity pattern without constrained entries (they will be zeroed out later anyway) by passing keep_constrained=false to create_sparsity_pattern. This naturally only works together with local condensation of constraints since there won't be space allocated in the global matrix for the full (i.e. \"non-condensed\") element matrix. Creating the matrix without constrained entries reduces the memory footprint, but unless a significant amount of DoFs are constrained (e.g. high mesh resolution at a boundary) the savings are negligible. (#539)","category":"page"},{"location":"changelog/#Changed-2","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"ConstraintHandler: update! is now called implicitly in close!. This was easy to miss, and somewhat of a strange requirement when solving problems without time stepping. (#459)\nThe function for computing the inhomogeneity in a Dirichlet constraint can now be specified as either f(x) or f(x, t), where x is the spatial coordinate and t the time. (#459)\nThe elements of a CellIterator are now CellCache instead of the iterator itself, which was confusing in some cases. This change does not affect typical user code. (#546)","category":"page"},{"location":"changelog/#Deprecated-2","page":"Changelog","title":"Deprecated","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Adding fields to a DoF handler with push!(dh, ...) has been deprecated in favor of add!(dh, ...). This is to make it consistent with how constraints are added to a constraint handler. (#578)","category":"page"},{"location":"changelog/#Fixed-4","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix shape_value for the linear, discontinuous Lagrange interpolation. (#553)\nFix reference_coordinate dispatch for discontinuous Lagrange interpolations. (#559)\nFix show(::Grid) for custom cell types. (#570)\nFix apply_zero!(Δa, ch) when using inhomogeneous affine constraints (#575)","category":"page"},{"location":"changelog/#Other-improvements-5","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Internal changes defining a new global matrix/vector \"interface\". These changes make it easy to enable more array types (e.g. BlockMatrix support added in this release) and solvers in the future. (#562, #571)\nPerformance improvements:\nReduced time and memory allocations for global sparse matrix creation (Julia >= 1.10). (#563)\nDocumentation improvements:\nAdded an overview of the Examples section. (#531)\nAdded an example showing topology optimization. (#531)\nVarious typo fixes. (#574)\nFix broken links. (#583)","category":"page"},{"location":"changelog/#[v0.3.10](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.10)-2022-12-11","page":"Changelog","title":"v0.3.10 - 2022-12-11","text":"","category":"section"},{"location":"changelog/#Added-6","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"New functions apply_local! and apply_assemble! for applying constraints locally on the element level before assembling to the global system. (#528)\nNew functionality to renumber DoFs by fields or by components. This is useful when you need the global matrix to be blocked. (#378, #545)\nFunctionality to renumber DoFs in DofHandler and ConstraintHandler simultaneously: renumber!(dh::DofHandler, ch::ConstraintHandler, order). Previously renumbering had to be done before creating the ConstraintHandler since otherwise DoF numbers would be inconsistent. However, this was inconvenient in cases where the constraints impact the new DoF order permutation. (#542)\nThe coupling between fields can now be specified when creating the global matrix with create_sparsity_pattern by passing a Matrix{Bool}. For example, in a problem with unknowns (u, p) and corresponding test functions (v, q), if there is no coupling between p and q it is unnecessary to allocate entries in the global matrix corresponding to these DoFs. This can now be communicated to create_sparsity_pattern by passing the coupling matrix [true true; true false] in the keyword argument coupling. (#544)","category":"page"},{"location":"changelog/#Changed-3","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Runtime and allocations for application of boundary conditions in apply! and apply_zero! have been improved. As a result, the strategy keyword argument is obsolete and thus ignored. (#489)\nThe internal representation of Dirichlet boundary conditions and AffineConstraints in the ConstraintHandler have been unified. As a result, conflicting constraints on DoFs are handled more consistently: the constraint added last to the ConstraintHandler now always override any previous constraints. Conflicting constraints could previously cause problems when a DoF where prescribed by both Dirichlet and AffineConstraint. (#529)\nEntries in local matrix/vector are now ignored in the assembly procedure. This allows, for example, using a dense local matrix [a b; c d] even if no entries exist in the global matrix for the d block, i.e. in [A B; C D] the D block is zero, and these global entries might not exist in the sparse matrix. (Such sparsity patterns can now be created by create_sparsity_pattern, see #544.) (#543)","category":"page"},{"location":"changelog/#Fixed-5","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix affine constraints with prescribed DoFs in the right-hand-side. In particular, DoFs that are prescribed by just an inhomogeneity are now handled correctly, and nested affine constraints now give an error instead of silently giving the wrong result. (#530, #535)\nFixed internal inconsistency in edge ordering for 2nd order RefTetrahedron and RefCube. (#520, #523)","category":"page"},{"location":"changelog/#Other-improvements-6","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Performance improvements:\nReduced time and memory allocations in DoF distribution for MixedDofHandler. (#533)\nReduced time and memory allocations reductions in getcoordinates!. (#536)\nReduced time and memory allocations in affine constraint condensation. (#537, #541, #550)\nDocumentation improvements:\nUse :static scheduling for threaded for-loop (#534)\nRemove use of @inbounds (#547)\nUnification of create_sparsity_pattern methods to remove code duplication between DofHandler and MixedDofHandler. (#538, #540)","category":"page"},{"location":"changelog/#[v0.3.9](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.9)-2022-10-19","page":"Changelog","title":"v0.3.9 - 2022-10-19","text":"","category":"section"},{"location":"changelog/#Added-7","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"New higher order function interpolations for triangles (Lagrange{2,RefTetrahedron,3}, Lagrange{2,RefTetrahedron,4}, and Lagrange{2,RefTetrahedron,5}). (#482, #512)\nNew Gaussian quadrature formula for triangles up to order 15. (#514)\nAdd debug mode for working with Ferrite internals. (#524)","category":"page"},{"location":"changelog/#Changed-4","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The default components to constrain in Dirichlet and PeriodicDirichlet have changed from component 1 to all components of the field. For scalar problems this has no effect. (#506, #509)","category":"page"},{"location":"changelog/#[v0.3.8](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.8)-2022-10-05","page":"Changelog","title":"v0.3.8 - 2022-10-05","text":"","category":"section"},{"location":"changelog/#Added-8","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite.jl now has a logo! (#464)\nNew keyword argument search_nneighbors::Int in PointEvalHandler for specifying how many neighboring elements to consider in the kNN search. The default is still 3 (usually sufficient). (#466)\nThe IJV-assembler now support assembling non-square matrices. (#471)\nPeriodic boundary conditions have been reworked and generalized. It now supports arbitrary relations between the mirror and image boundaries (e.g. not only translations in x/y/z direction). (#478, #481, #496, #501)","category":"page"},{"location":"changelog/#Fixed-6","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix PointEvalHandler when the first point is missing. (#466)\nFix the ordering of nodes on the face for (Quadratic)Tetrahedron cells. (#475)","category":"page"},{"location":"changelog/#Other-improvements-7","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Many improvements to the documentation. (#467, #473, #487, #494, #500)\nImproved error messages in reinit! when number of geometric base functions and number of element coordinates mismatch. (#469)\nRemove some unnecessary function parametrizations. (#503)\nRemove some unnecessary allocations in grid coloring. (#505)\nMore efficient way of creating the sparsity pattern when using AffineConstraints and/or PeriodicDirichlet. (#436)","category":"page"},{"location":"changelog/#[v0.3.7](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.7)-2022-07-05","page":"Changelog","title":"v0.3.7 - 2022-07-05","text":"","category":"section"},{"location":"changelog/#Fixed-7","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix tests for newer version of WriteVTK (no functional change). (#462)","category":"page"},{"location":"changelog/#Other-improvements-8","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Various improvements to the heat equation example and the hyperelasticity example in the documentation. (#460, #461)","category":"page"},{"location":"changelog/#[v0.3.6](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.6)-2022-06-30","page":"Changelog","title":"v0.3.6 - 2022-06-30","text":"","category":"section"},{"location":"changelog/#Fixed-8","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix a bug with L2Projection of mixed grid. (#456)","category":"page"},{"location":"changelog/#Other-improvements-9","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Expanded manual section of Dirichlet BCs. (#458)","category":"page"},{"location":"changelog/#[v0.3.5](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.5)-2022-05-30","page":"Changelog","title":"v0.3.5 - 2022-05-30","text":"","category":"section"},{"location":"changelog/#Added-9","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Functionality for querying information about the grid topology (e.g. neighboring cells, boundaries, ...). (#363)","category":"page"},{"location":"changelog/#Fixed-9","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix application of boundary conditions when combining RHSData and affine constraints. (#431)","category":"page"},{"location":"changelog/#[v0.3.4](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.4)-2022-02-25","page":"Changelog","title":"v0.3.4 - 2022-02-25","text":"","category":"section"},{"location":"changelog/#Added-10","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Affine (linear) constraints between degrees-of-freedom. (#401)\nPeriodic Dirichlet boundary conditions. (#418)\nEvaluation of arbitrary quantities in FE space. (#425)","category":"page"},{"location":"changelog/#Changed-5","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Interpolation(s) and the quadrature rule are now stored as part of the CellValues structs (cv.func_interp, cv.geo_interp, and cv.qr). (#428)","category":"page"},{"location":"changelog/#[v0.3.3](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.3)-2022-02-04","page":"Changelog","title":"v0.3.3 - 2022-02-04","text":"","category":"section"},{"location":"changelog/#Changed-6","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Verify user input in various functions to eliminate possible out-of-bounds accesses. (#407, #411)","category":"page"},{"location":"changelog/#[v0.3.2](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.2)-2022-01-18","page":"Changelog","title":"v0.3.2 - 2022-01-18","text":"","category":"section"},{"location":"changelog/#Added-11","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Support for new interpolation types: DiscontinuousLagrange, BubbleEnrichedLagrange, and CrouzeixRaviart. (#352, #392)","category":"page"},{"location":"changelog/#Changed-7","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Julia version 1.0 is no longer supported for Ferrite versions >= 0.3.2. Use Julia version >= 1.6. (#385)\nQuadrature data for L2 projection can now be given as a matrix of size \"number of elements\" x \"number of quadrature points per element\". (#386)\nProjected values from L2 projection can now be exported directly to VTK. (#390)\nGrid coloring can now act on a subset of cells. (#402)\nVarious functions related to cell values now use traits to make it easier to extend and reuse functionality in external code. (#404)","category":"page"},{"location":"changelog/#Fixed-10","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Exporting tensors to VTK now use correct names for the components. (#406)","category":"page"},{"location":"reference/quadrature/","page":"Quadrature","title":"Quadrature","text":"CurrentModule = Ferrite\nDocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/quadrature/#Quadrature","page":"Quadrature","title":"Quadrature","text":"","category":"section"},{"location":"reference/quadrature/","page":"Quadrature","title":"Quadrature","text":"QuadratureRule\nFacetQuadratureRule\ngetnquadpoints(::QuadratureRule)\ngetnquadpoints(::FacetQuadratureRule, ::Int)\ngetpoints\ngetweights","category":"page"},{"location":"reference/quadrature/#Ferrite.QuadratureRule","page":"Quadrature","title":"Ferrite.QuadratureRule","text":"QuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)\nQuadratureRule{shape}(weights::AbstractVector{T}, points::AbstractVector{Vec{rdim, T}})\n\nCreate a QuadratureRule used for integration on the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. quad_rule_type is an optional argument determining the type of quadrature rule, currently the :legendre and :lobatto rules are implemented for hypercubes. For triangles up to order 8 the default rule is the one by :dunavant (see [6]) and for tetrahedra the default rule is keast_minimal (see [7]). Wedges and pyramids default to :polyquad (see [8]). Furthermore we have implemented\n\n:gaussjacobi for triangles (order 9-15)\n:keast_minimal (see [7]) for tetrahedra (order 1-5), containing negative weights\n:keast_positive (see [7]) for tetrahedra (order 1-5), containing only positive weights\n\nA QuadratureRule is used to approximate an integral on a domain by a weighted sum of function values at specific points:\n\nintlimits_Omega f(mathbfx) textd Omega approx sumlimits_q = 1^n_q f(mathbfx_q) w_q\n\nThe quadrature rule consists of n_q points in space mathbfx_q with corresponding weights w_q.\n\nIn Ferrite, the QuadratureRule type is mostly used as one of the components to create CellValues.\n\nCommon methods:\n\ngetpoints : the points of the quadrature rule\ngetweights : the weights of the quadrature rule\n\nExample:\n\njulia> qr = QuadratureRule{RefTriangle}(1)\nQuadratureRule{RefTriangle, Float64, 2}([0.5], Vec{2, Float64}[[0.33333333333333, 0.33333333333333]])\n\njulia> getpoints(qr)\n1-element Vector{Vec{2, Float64}}:\n [0.33333333333333, 0.33333333333333]\n\n\n\n\n\n","category":"type"},{"location":"reference/quadrature/#Ferrite.FacetQuadratureRule","page":"Quadrature","title":"Ferrite.FacetQuadratureRule","text":"FacetQuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)\nFacetQuadratureRule{shape}(face_rules::NTuple{<:Any, <:QuadratureRule{shape}})\nFacetQuadratureRule{shape}(face_rules::AbstractVector{<:QuadratureRule{shape}})\n\nCreate a FacetQuadratureRule used for integration of the faces of the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. If no symbol is provided, the default quad_rule_type for each facet's reference shape is used (see QuadratureRule). For non-default quad_rule_types on cells with mixed facet types (e.g. RefPrism and RefPyramid), the face_rules must be provided explicitly.\n\nFacetQuadratureRule is used as one of the components to create FacetValues.\n\n\n\n\n\n","category":"type"},{"location":"reference/quadrature/#Ferrite.getnquadpoints-Tuple{QuadratureRule}","page":"Quadrature","title":"Ferrite.getnquadpoints","text":"getnquadpoints(qr::QuadratureRule)\n\nReturn the number of quadrature points in qr.\n\n\n\n\n\n","category":"method"},{"location":"reference/quadrature/#Ferrite.getnquadpoints-Tuple{FacetQuadratureRule, Int64}","page":"Quadrature","title":"Ferrite.getnquadpoints","text":"getnquadpoints(qr::FacetQuadratureRule, face::Int)\n\nReturn the number of quadrature points in qr for local face index face.\n\n\n\n\n\n","category":"method"},{"location":"reference/quadrature/#Ferrite.getpoints","page":"Quadrature","title":"Ferrite.getpoints","text":"getpoints(qr::QuadratureRule)\ngetpoints(qr::FacetQuadratureRule, face::Int)\n\nReturn the points of the quadrature rule.\n\nExamples\n\njulia> qr = QuadratureRule{RefTriangle}(:legendre, 2);\n\njulia> getpoints(qr)\n3-element Vector{Vec{2, Float64}}:\n [0.16666666666667, 0.16666666666667]\n [0.16666666666667, 0.66666666666667]\n [0.66666666666667, 0.16666666666667]\n\n\n\n\n\n","category":"function"},{"location":"reference/quadrature/#Ferrite.getweights","page":"Quadrature","title":"Ferrite.getweights","text":"getweights(qr::QuadratureRule)\ngetweights(qr::FacetQuadratureRule, face::Int)\n\nReturn the weights of the quadrature rule.\n\nExamples\n\njulia> qr = QuadratureRule{RefTriangle}(:legendre, 2);\n\njulia> getweights(qr)\n3-element Array{Float64,1}:\n 0.166667\n 0.166667\n 0.166667\n\n\n\n\n\n","category":"function"},{"location":"topics/#Topic-guides","page":"Topic guide overview","title":"Topic guides","text":"","category":"section"},{"location":"topics/","page":"Topic guide overview","title":"Topic guide overview","text":"This is an overview of the topic guides.","category":"page"},{"location":"topics/","page":"Topic guide overview","title":"Topic guide overview","text":"Pages = [\n \"fe_intro.md\",\n \"reference_shapes.md\",\n \"FEValues.md\",\n \"degrees_of_freedom.md\",\n \"assembly.md\",\n \"boundary_conditions.md\",\n \"constraints.md\",\n \"grid.md\",\n \"export.md\"\n]","category":"page"},{"location":"devdocs/dofhandler/#dofhandler-interpolations","page":"Dof Handler","title":"Dof Handler","text":"","category":"section"},{"location":"devdocs/dofhandler/#Type-definitions","page":"Dof Handler","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"Dof handlers are subtypes of AbstractDofhandler{sdim}, i.e. they are parametrized by the spatial dimension. Internally a helper struct InterpolationInfo is utilized to enforce type stability during dof distribution, because the interpolations are not available as concrete types.","category":"page"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"Ferrite.InterpolationInfo\nFerrite.PathOrientationInfo\nFerrite.SurfaceOrientationInfo","category":"page"},{"location":"devdocs/dofhandler/#Ferrite.InterpolationInfo","page":"Dof Handler","title":"Ferrite.InterpolationInfo","text":"InterpolationInfo\n\nGathers all the information needed to distribute dofs for a given interpolation. Note that this cache is of the same type no matter the interpolation: the purpose is to make dof-distribution type-stable.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/dofhandler/#Ferrite.PathOrientationInfo","page":"Dof Handler","title":"Ferrite.PathOrientationInfo","text":"PathOrientationInfo\n\nOrientation information for 1D entities.\n\nThe orientation for 1D entities is defined by the indices of the grid nodes associated to the vertices. To give an example, the oriented path\n\n1 ---> 2\n\nis called regular, indicated by regular=true, while the oriented path\n\n2 ---> 1\n\nis called inverted, indicated by regular=false.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/dofhandler/#Ferrite.SurfaceOrientationInfo","page":"Dof Handler","title":"Ferrite.SurfaceOrientationInfo","text":"SurfaceOrientationInfo\n\nOrientation information for 2D entities. Such an entity can be possibly flipped (i.e. the defining vertex order is reverse to the spanning vertex order) and the vertices can be rotated against each other. Take for example the faces\n\n1---2 2---3\n| A | | B |\n4---3 1---4\n\nwhich are rotated against each other by 90° (shift index is 1) or the faces\n\n1---2 2---1\n| A | | B |\n4---3 3---4\n\nwhich are flipped against each other. Any combination of these can happen. The combination to map this local face to the defining face is encoded with this data structure via rotate circ flip where the rotation is indiced by the shift index. !!!NOTE TODO implement me.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/dofhandler/#Internal-API","page":"Dof Handler","title":"Internal API","text":"","category":"section"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"The main entry point for dof distribution is __close!.","category":"page"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"Ferrite.__close!\nFerrite.get_grid\nFerrite.find_field\nFerrite._find_field\nFerrite._close_subdofhandler!\nFerrite._distribute_dofs_for_cell!\nFerrite.permute_and_push!","category":"page"},{"location":"devdocs/dofhandler/#Ferrite.__close!","page":"Dof Handler","title":"Ferrite.__close!","text":"__close!(dh::DofHandler)\n\nInternal entry point for dof distribution.\n\nDofs are distributed as follows: For the DofHandler each SubDofHandler is visited in the order they were added. For each field in the SubDofHandler create dofs for the cell. This means that dofs on a particular cell will be numbered in groups for each field, so first the dofs for field 1 are distributed, then field 2, etc. For each cell dofs are first distributed on its vertices, then on the interior of edges (if applicable), then on the interior of faces (if applicable), and finally on the cell interior. The entity ordering follows the geometrical ordering found in vertices, faces and edges.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite.get_grid","page":"Dof Handler","title":"Ferrite.get_grid","text":"get_grid(dh::AbstractDofHandler)\n\nAccess some grid representation for the dof handler.\n\nnote: Note\nThis API function is currently not well-defined. It acts as the interface between distributed assembly and assembly on a single process, because most parts of the functionality can be handled by only acting on the locally owned cell set.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite.find_field","page":"Dof Handler","title":"Ferrite.find_field","text":"find_field(dh::DofHandler, field_name::Symbol)::NTuple{2,Int}\n\nReturn the index of the field with name field_name in a DofHandler. The index is a NTuple{2,Int}, where the 1st entry is the index of the SubDofHandler within which the field was found and the 2nd entry is the index of the field within the SubDofHandler.\n\nnote: Note\nAlways finds the 1st occurrence of a field within DofHandler.\n\nSee also: find_field(sdh::SubDofHandler, field_name::Symbol), Ferrite._find_field(sdh::SubDofHandler, field_name::Symbol).\n\n\n\n\n\nfind_field(sdh::SubDofHandler, field_name::Symbol)::Int\n\nReturn the index of the field with name field_name in a SubDofHandler. Throw an error if the field is not found.\n\nSee also: find_field(dh::DofHandler, field_name::Symbol), _find_field(sdh::SubDofHandler, field_name::Symbol).\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite._find_field","page":"Dof Handler","title":"Ferrite._find_field","text":"_find_field(sdh::SubDofHandler, field_name::Symbol)::Int\n\nReturn the index of the field with name field_name in the SubDofHandler sdh. Return nothing if the field is not found.\n\nSee also: find_field(dh::DofHandler, field_name::Symbol), find_field(sdh::SubDofHandler, field_name::Symbol).\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite._close_subdofhandler!","page":"Dof Handler","title":"Ferrite._close_subdofhandler!","text":"_close_subdofhandler!(dh::DofHandler{sdim}, sdh::SubDofHandler, sdh_index::Int, nextdof::Int, vertexdicts, edgedicts, facedicts) where {sdim}\n\nMain entry point to distribute dofs for a single SubDofHandler on its subdomain.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite._distribute_dofs_for_cell!","page":"Dof Handler","title":"Ferrite._distribute_dofs_for_cell!","text":"_distribute_dofs_for_cell!(dh::DofHandler{sdim}, cell::AbstractCell, ip_info::InterpolationInfo, nextdof::Int, vertexdict, edgedict, facedict) where {sdim}\n\nMain entry point to distribute dofs for a single cell.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite.permute_and_push!","page":"Dof Handler","title":"Ferrite.permute_and_push!","text":"permute_and_push!\n\nFor interpolations with more than one interior dof per edge it may be necessary to adjust the dofs. Since dofs are (initially) enumerated according to the local edge direction there can be a direction mismatch with the neighboring element. For example, in the following nodal interpolation example, with three interior dofs on each edge, the initial pass have distributed dofs 4, 5, 6 according to the local edge directions:\n\n+-----------+\n| A |\n+--4--5--6->+ local edge on element A\n\n ----------> global edge\n\n+<-6--5--4--+ local edge on element B\n| B |\n+-----------+\n\nFor most scalar-valued interpolations we can simply compensate for this by reversing the numbering on all edges that do not match the global edge direction, i.e. for the edge on element B in the example.\n\nIn addition, we also have to preserve the ordering at each dof location.\n\nFor more details we refer to Scroggs et al. [13] as we follow the methodology described therein.\n\nReferences\n\n[13] Scroggs et al. ACM Trans. Math. Softw. 48 (2022).\n\n\n\n\n\n!!!NOTE TODO implement me.\n\nFor more details we refer to [1] as we follow the methodology described therein.\n\n[1] Scroggs, M. W., Dokken, J. S., Richardson, C. N., & Wells, G. N. (2022). Construction of arbitrary order finite element degree-of-freedom maps on polygonal and polyhedral cell meshes. ACM Transactions on Mathematical Software (TOMS), 48(2), 1-23.\n\n!!!TODO citation via software.\n\n!!!TODO Investigate if we can somehow pass the interpolation into this function in a typestable way.\n\n\n\n\n\n","category":"function"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"EditURL = \"../literate-howto/threaded_assembly.jl\"","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Draft = false","category":"page"},{"location":"howto/threaded_assembly/#tutorial-threaded-assembly","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"tip: Tip\nThis example is also available as a Jupyter notebook: threaded_assembly.ipynb.","category":"page"},{"location":"howto/threaded_assembly/#Introduction","page":"Multi-threaded assembly","title":"Introduction","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"In this howto we will explore how to use task based multithreading (shared memory parallelism) to speed up the analysis. Some parts of a finite element simulation are trivially parallelizable such as the computation of the local element contributions since each element can be processed independently. However, two things need to be considered in order to parallelize safely:","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Modification of shared data: Although the contributions from all the elements can be computed independently, eventually they need to be assembled into the global matrix and vector. Letting each task assemble their own contribution would lead to race conditions since elements share degrees of freedom with each other. There are various ways to remedy this, for example:\nLocking: By using a lock around the call to assemble! we can ensure that only one task assembles at a time. This is simple to implement but can lead to lock contention and thus poor performance. Another drawback is that the results will not be deterministic since floating point operations are neither associative nor commutative.\nAssembler task: By using a designated task for the assembling we (obviously) ensure that only a single task assembles. The worker tasks (the tasks computing the element contributions) would then hand off their results to the assemly task. This can be a useful approach if computing the element contributions is much slower than the assembly – otherwise the assembler task can't keep up with the worker tasks. There might also be some extra overhead because of task switching in the scheduler. The problem with non-deterministic results still remains.\nGrid coloring: By \"coloring\" the grid such that, within each color, no two elements share degrees of freedom, we can safely assemble each color in parallel. Even if concurrently running tasks will write to the global matrix and vector they will not write to the same memory locations. Note also that this procedure gives predictable results because for a memory location which, for example, a \"red\", a \"blue\", and a \"green\" element will contribute to we will always add the red first, then the blue, and finally the green.\nScratch data: In order to speed up the computation of the element contributions we typically pre-allocate some data structures that can be reused for every element. Such scratch data include, for example, the local matrix and vector, and the CellValues. Each task need their own copy of the scratch data since they will be modified for each element.","category":"page"},{"location":"howto/threaded_assembly/#Grid-coloring","page":"Multi-threaded assembly","title":"Grid coloring","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Ferrite include functionality to color the grid with the create_coloring function. Here we create a simple 2D grid, color it, and export the colors to a VTK file to visualize the result (see Figure 1.). Note that no cells with the same color has any shared nodes (dofs). This means that it is safe to assemble in parallel as long as we only assemble one color at a time.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"There are two coloring algorithms implemented: the \"workstream\" algorithm (from Turcksin et al. [11]) and a \"greedy\" algorithm. For this structured grid the greedy algorithm uses fewer colors, but both algorithms result in colors that contain roughly the same number of elements. The workstream algorithm is the default one since it in general results in more balanced colors. For unstructured grids the greedy algorithm can result in colors with very few elements, for example.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"using Ferrite, SparseArrays\n\nfunction create_example_2d_grid()\n grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))\n colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)\n colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)\n return VTKGridFile(\"colored\", grid) do vtk\n Ferrite.write_cell_colors(vtk, grid, colors_workstream, \"workstream-coloring\")\n Ferrite.write_cell_colors(vtk, grid, colors_greedy, \"greedy-coloring\")\n end\nend\n\ncreate_example_2d_grid()","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"(Image: )","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Figure 1: Element coloring using the \"workstream\"-algorithm (left) and the \"greedy\"- algorithm (right).","category":"page"},{"location":"howto/threaded_assembly/#Multithreaded-assembly-of-a-cantilever-beam-in-3D","page":"Multi-threaded assembly","title":"Multithreaded assembly of a cantilever beam in 3D","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We will now look at an example where we assemble the stiffness matrix and right hand side using multiple threads. The problem setup is a cantilever beam in 3D with a linear elastic material behavior. For this exercise we only focus on the multithreading and are not bothered with boundary conditions. For more details refer to the tutorial on linear elasticity.","category":"page"},{"location":"howto/threaded_assembly/#Setup","page":"Multi-threaded assembly","title":"Setup","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We define the element routine, material stiffness, grid and DofHandler just like in the tutorial on linear elasticity without discussing it further here.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"# Element routine\nfunction assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)\n fill!(Ke, 0)\n fill!(fe, 0)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n δui = shape_value(cellvalues, q_point, i)\n fe[i] += (δui ⋅ b) * dΩ\n ∇δui = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n ∇uj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\n# Material stiffness\nfunction create_material_stiffness()\n E = 200.0e9\n ν = 0.3\n λ = E * ν / ((1 + ν) * (1 - 2ν))\n μ = E / (2(1 + ν))\n δ(i, j) = i == j ? 1.0 : 0.0\n C = SymmetricTensor{4, 3}() do i, j, k, l\n return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n end\n return C\nend\n\n# Grid and grid coloring\nfunction create_cantilever_grid(n::Int)\n xmin = Vec{3}((0.0, 0.0, 0.0))\n xmax = Vec{3}((10.0, 1.0, 1.0))\n grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax)\n colors = create_coloring(grid)\n return grid, colors\nend\n\n# DofHandler with displacement field u\nfunction create_dofhandler(grid::Grid, interpolation::VectorInterpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation)\n close!(dh)\n return dh\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/#Task-local-scratch-data","page":"Multi-threaded assembly","title":"Task local scratch data","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We group everything that needs to be duplicated for each task in the struct ScratchData:","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"cell_cache::CellCache: contain buffers for coordinates and (global) dofs which will be reinit!ed for each cell.\ncellvalues::CellValues: the cell values which will be reinit!ed for each cell using the cell_cache\nKe::Matrix: the local matrix\nfe::Vector: the local vector\nassembler: the assembler (which needs to be duplicated because it contains buffers that are modified during the call to assemble!)","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"struct ScratchData{CC, CV, T, A}\n cell_cache::CC\n cellvalues::CV\n Ke::Matrix{T}\n fe::Vector{T}\n assembler::A\nend","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"This constructor will be called within each task to create a independent ScratchData object. For cell_cache, Ke, and fe we simply call the constructors to allocate independent objects. For cellvalues we use copy which Ferrite defines for this purpose. Finally, for the assembler we call start_assemble to create a new assembler but note that we set fillzero = false because we don't want to risk that a task that starts a bit later will zero out data that another task have already assembled.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"function ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues)\n cell_cache = CellCache(dh)\n n = ndofs_per_cell(dh)\n Ke = zeros(n, n)\n fe = zeros(n)\n asm = start_assemble(K, f; fillzero = false)\n return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm)\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/#Global-assembly-routine","page":"Multi-threaded assembly","title":"Global assembly routine","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Finally we define the global assemble routine, which is where the parallelization happens. The main difference from all previous assemble_global! functions is that we now have an outer loop over the colors, and then the inner loop over the cells in each color, which can be parallelized.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"For the scheduling of parallel tasks we use the OhMyThreads.jl package. OhMyThreads provides a macro based and a functional API. Here we use the macro based API because it is slightly more convenient when using task local values since they can be defined with the @local macro.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"note: Schedulers and load balancing\nOhMyThreads provides a number of different schedulers. In this example we use the DynamicScheduler (which is the default one). The DynamicScheduler will spawn ntasks tasks where each task will process a chunk of (roughly) equal number of cells (i.e. length(color) ÷ ntasks). This should be a good choice for this example because we expect all cells to take the same time to process and we don't need any load balancing.For a different problem setup where some cells might take longer to process (perhaps they experience plastic deformation and we need to solve a local problem) we might benefit from load balancing. The DynamicScheduler can be used also for load balancing by specifiying nchunks or chunksize. However, the DynamicScheduler will always spawn nchunks tasks which can become costly since we are allocating scratch data for every task. To limit the number of tasks, while allowing for more than ntasks chunks, we can use the GreedyScheduler with chunking. For example, scheduler = OhMyThreads.GreedyScheduler(; ntasks = ntasks, nchunks = 10 * ntasks) will split the work into 10 * ntasks chunks and spawn ntasks tasks to process them. Refer to the OhMyThreads documentation for details.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"using OhMyThreads, TaskLocalValues\n\nfunction assemble_global!(\n K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors,\n cellvalues_template::CellValues; ntasks = Threads.nthreads()\n )\n # Zero-out existing data in K and f\n _ = start_assemble(K, f)\n # Body force and material stiffness\n b = Vec{3}((0.0, 0.0, -1.0))\n C = create_material_stiffness()\n # Loop over the colors\n for color in colors\n # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of\n # (roughly) equal number of cells (`length(color) ÷ ntasks`).\n scheduler = OhMyThreads.DynamicScheduler(; ntasks)\n # Parallelize the loop over the cells in this color\n OhMyThreads.@tasks for cellidx in color\n # Tell the @tasks loop to use the scheduler defined above\n @set scheduler = scheduler\n # Obtain a task local scratch and unpack it\n @local scratch = ScratchData(dh, K, f, cellvalues_template)\n (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n # Reinitialize the cell cache and then the cellvalues\n reinit!(cell_cache, cellidx)\n reinit!(cellvalues, cell_cache)\n # Compute the local contribution of the cell\n assemble_cell!(Ke, fe, cellvalues, C, b)\n # Assemble local contribution\n assemble!(assembler, celldofs(cell_cache), Ke, fe)\n end\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"details: OhMyThreads functional API: OhMyThreads.tforeach\nThe OhMyThreads.@tasks block above corresponds to a call to OhMyThreads.tforeach. Using the functional API directly would look like below. The main difference is that we need to manually create a TaskLocalValue for the scratch data.# using TaskLocalValues\nscratches = TaskLocalValue() do\n ScratchData(dh, K, f, cellvalues)\nend\nOhMyThreads.tforeach(color; scheduler) do cellidx\n # Obtain a task local scratch and unpack it\n scratch = scratches[]\n (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n # Reinitialize the cell cache and then the cellvalues\n reinit!(cell_cache, cellidx)\n reinit!(cellvalues, cell_cache)\n # Compute the local contribution of the cell\n assemble_cell!(Ke, fe, cellvalues, C, b)\n # Assemble local contribution\n assemble!(assembler, celldofs(cell_cache), Ke, fe)\nend","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We define the main function to setup everything and then time the call to assemble_global!.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"function main(; n = 20, ntasks = Threads.nthreads())\n # Interpolation, quadrature and cellvalues\n interpolation = Lagrange{RefHexahedron, 1}()^3\n quadrature = QuadratureRule{RefHexahedron}(2)\n cellvalues = CellValues(quadrature, interpolation)\n # Grid, colors and DofHandler\n grid, colors = create_cantilever_grid(n)\n dh = create_dofhandler(grid, interpolation)\n # Global matrix and vector\n K = allocate_matrix(dh)\n f = zeros(ndofs(dh))\n # Compile it\n assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n # Time it\n @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n return\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"On a machine with 4 cores, starting julia with --threads=auto, we obtain the following timings:","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"main(; ntasks = 1) # 1.970784 seconds (902 allocations: 816.172 KiB)\nmain(; ntasks = 2) # 1.025065 seconds (1.64 k allocations: 1.564 MiB)\nmain(; ntasks = 3) # 0.700423 seconds (2.38 k allocations: 2.332 MiB)\nmain(; ntasks = 4) # 0.548356 seconds (3.12 k allocations: 3.099 MiB)","category":"page"},{"location":"howto/threaded_assembly/#threaded_assembly-plain-program","page":"Multi-threaded assembly","title":"Plain program","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Here follows a version of the program without any comments. The file is also available here: threaded_assembly.jl.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"using Ferrite, SparseArrays\n\nfunction create_example_2d_grid()\n grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))\n colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)\n colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)\n return VTKGridFile(\"colored\", grid) do vtk\n Ferrite.write_cell_colors(vtk, grid, colors_workstream, \"workstream-coloring\")\n Ferrite.write_cell_colors(vtk, grid, colors_greedy, \"greedy-coloring\")\n end\nend\n\ncreate_example_2d_grid()\n\n# Element routine\nfunction assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)\n fill!(Ke, 0)\n fill!(fe, 0)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n δui = shape_value(cellvalues, q_point, i)\n fe[i] += (δui ⋅ b) * dΩ\n ∇δui = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n ∇uj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\n# Material stiffness\nfunction create_material_stiffness()\n E = 200.0e9\n ν = 0.3\n λ = E * ν / ((1 + ν) * (1 - 2ν))\n μ = E / (2(1 + ν))\n δ(i, j) = i == j ? 1.0 : 0.0\n C = SymmetricTensor{4, 3}() do i, j, k, l\n return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n end\n return C\nend\n\n# Grid and grid coloring\nfunction create_cantilever_grid(n::Int)\n xmin = Vec{3}((0.0, 0.0, 0.0))\n xmax = Vec{3}((10.0, 1.0, 1.0))\n grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax)\n colors = create_coloring(grid)\n return grid, colors\nend\n\n# DofHandler with displacement field u\nfunction create_dofhandler(grid::Grid, interpolation::VectorInterpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation)\n close!(dh)\n return dh\nend\nnothing # hide\n\nstruct ScratchData{CC, CV, T, A}\n cell_cache::CC\n cellvalues::CV\n Ke::Matrix{T}\n fe::Vector{T}\n assembler::A\nend\n\nfunction ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues)\n cell_cache = CellCache(dh)\n n = ndofs_per_cell(dh)\n Ke = zeros(n, n)\n fe = zeros(n)\n asm = start_assemble(K, f; fillzero = false)\n return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm)\nend\nnothing # hide\n\nusing OhMyThreads, TaskLocalValues\n\nfunction assemble_global!(\n K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors,\n cellvalues_template::CellValues; ntasks = Threads.nthreads()\n )\n # Zero-out existing data in K and f\n _ = start_assemble(K, f)\n # Body force and material stiffness\n b = Vec{3}((0.0, 0.0, -1.0))\n C = create_material_stiffness()\n # Loop over the colors\n for color in colors\n # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of\n # (roughly) equal number of cells (`length(color) ÷ ntasks`).\n scheduler = OhMyThreads.DynamicScheduler(; ntasks)\n # Parallelize the loop over the cells in this color\n OhMyThreads.@tasks for cellidx in color\n # Tell the @tasks loop to use the scheduler defined above\n @set scheduler = scheduler\n # Obtain a task local scratch and unpack it\n @local scratch = ScratchData(dh, K, f, cellvalues_template)\n (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n # Reinitialize the cell cache and then the cellvalues\n reinit!(cell_cache, cellidx)\n reinit!(cellvalues, cell_cache)\n # Compute the local contribution of the cell\n assemble_cell!(Ke, fe, cellvalues, C, b)\n # Assemble local contribution\n assemble!(assembler, celldofs(cell_cache), Ke, fe)\n end\n end\n return K, f\nend\nnothing # hide\n\nfunction main(; n = 20, ntasks = Threads.nthreads())\n # Interpolation, quadrature and cellvalues\n interpolation = Lagrange{RefHexahedron, 1}()^3\n quadrature = QuadratureRule{RefHexahedron}(2)\n cellvalues = CellValues(quadrature, interpolation)\n # Grid, colors and DofHandler\n grid, colors = create_cantilever_grid(n)\n dh = create_dofhandler(grid, interpolation)\n # Global matrix and vector\n K = allocate_matrix(dh)\n f = zeros(ndofs(dh))\n # Compile it\n assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n # Time it\n @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n return\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"CurrentModule = Ferrite\nDocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/interpolations/#reference-interpolation","page":"Interpolation","title":"Interpolation","text":"","category":"section"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"Interpolation\ngetnbasefunctions\ngetrefdim(::Interpolation)\ngetrefshape\ngetorder","category":"page"},{"location":"reference/interpolations/#Ferrite.Interpolation","page":"Interpolation","title":"Ferrite.Interpolation","text":"Interpolation{ref_shape, order}()\n\nAbstract type for interpolations defined on ref_shape (see AbstractRefShape). order corresponds to the order of the interpolation. The interpolation is used to define shape functions to interpolate a function between nodes.\n\nThe following interpolations are implemented:\n\nLagrange{RefLine,1}\nLagrange{RefLine,2}\nLagrange{RefQuadrilateral,1}\nLagrange{RefQuadrilateral,2}\nLagrange{RefQuadrilateral,3}\nLagrange{RefTriangle,1}\nLagrange{RefTriangle,2}\nLagrange{RefTriangle,3}\nLagrange{RefTriangle,4}\nLagrange{RefTriangle,5}\nBubbleEnrichedLagrange{RefTriangle,1}\nCrouzeixRaviart{RefTriangle, 1}\nCrouzeixRaviart{RefTetrahedron, 1}\nRannacherTurek{RefQuadrilateral, 1}\nRannacherTurek{RefHexahedron, 1}\nLagrange{RefHexahedron,1}\nLagrange{RefHexahedron,2}\nLagrange{RefTetrahedron,1}\nLagrange{RefTetrahedron,2}\nLagrange{RefPrism,1}\nLagrange{RefPrism,2}\nLagrange{RefPyramid,1}\nLagrange{RefPyramid,2}\nSerendipity{RefQuadrilateral,2}\nSerendipity{RefHexahedron,2}\n\nExamples\n\njulia> ip = Lagrange{RefTriangle, 2}()\nLagrange{RefTriangle, 2}()\n\njulia> getnbasefunctions(ip)\n6\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.getnbasefunctions","page":"Interpolation","title":"Ferrite.getnbasefunctions","text":"Ferrite.getnbasefunctions(ip::Interpolation)\n\nReturn the number of base functions for the interpolation ip.\n\n\n\n\n\n","category":"function"},{"location":"reference/interpolations/#Ferrite.getrefdim-Tuple{Interpolation}","page":"Interpolation","title":"Ferrite.getrefdim","text":"Ferrite.getrefdim(::Interpolation)\n\nReturn the dimension of the reference element for a given interpolation.\n\n\n\n\n\n","category":"method"},{"location":"reference/interpolations/#Ferrite.getrefshape","page":"Interpolation","title":"Ferrite.getrefshape","text":"Ferrite.getrefshape(::Interpolation)::AbstractRefShape\n\nReturn the reference element shape of the interpolation.\n\n\n\n\n\n","category":"function"},{"location":"reference/interpolations/#Ferrite.getorder","page":"Interpolation","title":"Ferrite.getorder","text":"Ferrite.getorder(::Interpolation)\n\nReturn order of the interpolation.\n\n\n\n\n\n","category":"function"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"Implemented interpolations:","category":"page"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"Lagrange\nSerendipity\nDiscontinuousLagrange\nBubbleEnrichedLagrange\nCrouzeixRaviart\nRannacherTurek","category":"page"},{"location":"reference/interpolations/#Ferrite.Lagrange","page":"Interpolation","title":"Ferrite.Lagrange","text":"Lagrange{refshape, order} <: ScalarInterpolation\n\nStandard continuous Lagrange polynomials with equidistant node placement.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.Serendipity","page":"Interpolation","title":"Ferrite.Serendipity","text":"Serendipity{refshape, order} <: ScalarInterpolation\n\nSerendipity element on hypercubes. Currently only second order variants are implemented.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.DiscontinuousLagrange","page":"Interpolation","title":"Ferrite.DiscontinuousLagrange","text":"Piecewise discontinuous Lagrange basis via Gauss-Lobatto points.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.BubbleEnrichedLagrange","page":"Interpolation","title":"Ferrite.BubbleEnrichedLagrange","text":"Lagrange element with bubble stabilization.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.CrouzeixRaviart","page":"Interpolation","title":"Ferrite.CrouzeixRaviart","text":"CrouzeixRaviart{refshape, order} <: ScalarInterpolation\n\nClassical non-conforming Crouzeix–Raviart element.\n\nFor details we refer to the original paper [9].\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.RannacherTurek","page":"Interpolation","title":"Ferrite.RannacherTurek","text":"RannacherTurek{refshape, order} <: ScalarInterpolation\n\nClassical non-conforming Rannacher-Turek element.\n\nThis element is basically the idea from Crouzeix and Raviart applied to hypercubes. For details see the original paper [10].\n\n\n\n\n\n","category":"type"},{"location":"devdocs/#Developer-documentation","page":"Developer documentation","title":"Developer documentation","text":"","category":"section"},{"location":"devdocs/","page":"Developer documentation","title":"Developer documentation","text":"Here you can find some documentation of the internals of Ferrite which are useful when developing the library.","category":"page"},{"location":"devdocs/","page":"Developer documentation","title":"Developer documentation","text":"Depth = 1\nPages = [\"reference_cells.md\", \"interpolations.md\", \"elements.md\", \"FEValues.md\", \"dofhandler.md\", \"assembly.md\", \"performance.md\", \"special_datastructures.md\"]","category":"page"},{"location":"topics/fe_intro/#Introduction-to-FEM","page":"Introduction to FEM","title":"Introduction to FEM","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Here we will present a very brief introduction to partial differential equations (PDEs) and to the finite element method (FEM). Perhaps the simplest PDE of all is the (steady-state, linear) heat equation, also known as the Poisson equation. We will use this equation as a demonstrative example of the method, and demonstrate how we go from the strong form of the equation, to the weak form, and then finally to the discrete FE problem.","category":"page"},{"location":"topics/fe_intro/#Strong-form","page":"Introduction to FEM","title":"Strong form","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"The strong form of the heat equation may be written as:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"- nabla cdot mathbfq(u) = f quad forall mathbfx in Omega","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where u is the unknown temperature field, mathbfq is the heat flux, f is an internal heat source, and Omega is the domain on which the equation is defined. To complete the problem we need to specify what happens at the domain boundary Gamma. This set of specifications is called boundary conditions. There are different types of boundary conditions, where the most common ones are Dirichlet – which means that the solution u is known at some part of the boundary, and Neumann – which means that the gradient of the solution, nabla u is known. Formally we write for our example","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"u = u^mathrmp quad forall mathbfx in Gamma_mathrmD\nmathbfq cdot mathbfn = q^mathrmp quad forall mathbfx in Gamma_mathrmN","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"i.e. the temperature is prescribed to a known function u^mathrmp at the Dirichlet part of the boundary, Gamma_mathrmD, and the heat flux is prescribed to q^mathrmp at the Neumann part of the boundary, Gamma_mathrmN, where mathbfn describes the outward pointing normal vector at the boundary.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"We also need a constitutive equation which links the temperature field, u, to the heat flux, mathbfq. The simplest case is to use Fourier's law","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"mathbfq(u) = -k nabla u","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where k is the conductivity of the material. In general the conductivity can vary throughout the domain as a function of the coordinate, i.e. k = k(mathbfx), but for simplicity we will consider only constant conductivity k.","category":"page"},{"location":"topics/fe_intro/#Weak-form","page":"Introduction to FEM","title":"Weak form","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"The solution to the equation above is usually calculated from the corresponding weak form. By multiplying the equation with an arbitrary test function delta u, integrating over the domain and using partial integration we obtain the weak form. Now our problem can be stated as:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Find u in mathbbU s.t.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"int_Omega nabla delta u cdot (k nabla u) mathrmdOmega =\nint_Gamma_mathrmN delta u q^mathrmp mathrmdGamma +\nint_Omega delta u f mathrmdOmega quad forall delta u in mathbbT","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where mathbbU mathbbT are suitable function spaces with sufficiently regular functions. Under very general assumptions it can be shown that the solution to the weak form is identical to the solution to the strong form.","category":"page"},{"location":"topics/fe_intro/#Finite-Element-approximation","page":"Introduction to FEM","title":"Finite Element approximation","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Using the finite element method to solve partial differential equations is usually preceded with the construction of a discretization of the domain Omega into a finite set of elements or cells. We call this geometric discretization grid (or mesh) and denote it with Omega_h. In this example the corners of the triangles are called nodes.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Next we introduce the finite element approximation u_mathrmh approx u as a sum of N nodal shape functions, where we denote each of these function by phi_i and the corresponding nodal values hatu_i. Note that shape functions are sometimes referred to as basis functions or trial functions, and instead of phi_i they are sometimes denoted N_i. In this example we choose to approximate the test function in the same way. This approach is known as the Galerkin finite element method. Formally we write the evaluation of our approximations at a specific point mathbfx in our domain Omega as:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"u_mathrmh(mathbfx) = sum_i=1^mathrmN phi_i(mathbfx) hatu_iqquad\ndelta u_mathrmh(mathbfx) = sum_i=1^mathrmN phi_i(mathbfx) delta hatu_i ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Since test and trial functions are usually chosen in such a way, that they build the basis of some function space (basis as in basis of a vector space), sometimes are they are also called basis functions. In the following the argument mathbfx is dropped to keep the notation compact. We may now insert these approximations in the weak form, which results in","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"sum_i^N delta hatu_i left(sum_j^N int_Omega_mathrmh nabla phi_i cdot (k nabla phi_j) mathrmdOmega hatu_j right) =\nsum_i^N delta hatu_i left( int_Gamma_mathrmN phi_i q^mathrmp mathrmdGamma +\nint_Omega_mathrmh phi_i f mathrmdOmega right) ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Since this equation must hold for arbitrary delta u_mathrmh, the equation must especially hold for the specific choice that only one of the nodal values delta hatu_i is fixed to 1 while an all other coefficients are fixed to 0. Repeating this argument for all i from 1 to N we obtain N linear equations. This way the discrete problem can be written as a system of linear equations","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"underlineunderlineK underlinehatu = underlinehatf ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where we call underlineunderlineK the (tangent) stiffness matrix, underlinehatu the solution vector with the nodal values and underlinehatf the force vector. The specific naming is for historical reasons, because the finite element method has its origins in mechanics. The elements of underlineunderlineK and underlinehatf are given by","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"(underlineunderlineK)_ij =\n int_Omega_mathrmh nabla phi_i cdot (k nabla phi_j) mathrmdOmega \n\n(underlinehatf)_i =\n int_Gamma_mathrmN phi_i q^mathrmp mathrmdGamma + int_Omega_mathrmh phi_i f mathrmdOmega ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Finally we also need to take care of the Dirichlet boundary conditions. These are enforced by setting the corresponding hatu_i to the prescribed values and eliminating the associated equations from the system. Now, solving this equation system yields the nodal values and thus an approximation to the true solution.","category":"page"},{"location":"topics/fe_intro/#Notes-on-the-implementation","page":"Introduction to FEM","title":"Notes on the implementation","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"In practice, the shape functions phi_i are only non-zero on parts of the domain Omega_mathrmh. Thus, the integrals are evaluated on sub-domains, called elements or cells.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Each cell gives a contribution to the global stiffness matrix and force vector. The process of constructing the system of equations is also called assembly. For clarification, let us rewrite the formula for the stiffness matrix entries as follows:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"(underlineunderlineK)_ij\n = int_Omega_mathrmh nabla phi_i cdot (k nabla phi_j) mathrmdOmega\n = sum_E in Omega_mathrmh int_E nabla phi_i cdot (k nabla phi_j) mathrmdOmega ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"This formulation underlines the element-centric perspective of finite element methods and reflects how it is usually implemented in software.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Computing the element integrals by hand can become a tedious task. To avoid this issue we approximate the element integrals with a technique called numerical integration. Skipping any of the mathematical details, the basic idea is to evaluate the function under the integral at specific points and weighting the evaluations accordingly, such that their sum approximates the volume properly. A very nice feature of these techniques is, that under quite general circumstances the formula is not just an approximation, but the exact evaluation of the integral. To avoid the recomputation of the just mentioned evaluation positions of the integral for each individual element, we perform a coordinate transformation onto a so-called reference element. Formally we write","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":" int_E nabla phi_i cdot (k nabla phi_j) mathrmdOmega\n approx sum_q nabla phi_i(textbfx_q) cdot (k(textbfx_q) nabla phi_j(textbfx_q)) w_q textrmdet(J(textbfx_q)) ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where J is the Jacobian of the coordinate transformation function. The computation of the transformation, weights, positions and of the Jacobi determinant is handled by Ferrite. On an intuitive level, and to explain the notation used in the implementation, we think of","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":" mathrmdOmega approx w textrmdet(J)","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"being the chosen approximation when changing from the integral to the finite summation.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"For an example of the implementation to solve a heat problem with Ferrite check out this thoroughly commented example.","category":"page"},{"location":"topics/fe_intro/#More-details","page":"Introduction to FEM","title":"More details","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"We finally want to note that this quick introduction barely scratches the surface of the finite element method. Also, we presented some things in a simplified way for the sake of keeping this article short and concise. There is a large corpus of literature and online tutorials containing more details about the finite element method. To give a few recommendations there is:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Hans Petter Langtangen's Script\nWolfgang Bangerth's Lecture Series\nIntroduction to the Finite Element Method by Niels Ottosen and Hans Petersson\nThe Finite Element Method for Elliptic Problems by Philippe Ciarlet\nFinite Elements: Theory, Fast Solvers, and Applications in Elasticity Theory by Dietrich Braess\nAn Analysis of the Finite Element Method by Gilbert Strang and George Fix\nFinite Element Procedures by Klaus-Jürgen Bathe\nThe Finite Element Method: Its Basis and Fundamentals by Olgierd Cecil Zienkiewicz, Robert Taylor and J.Z. Zhu\nHigher-Order Finite Element Methods by Pavel Šolín, Karel Segeth and Ivo Doležel","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"This list is neither meant to be exhaustive, nor does the absence of a work mean that it is in any way bad or not recommendable. The ordering of the articles also has no particular meaning.","category":"page"},{"location":"devdocs/FEValues/#devdocs-fevalues","page":"FEValues","title":"FEValues","text":"","category":"section"},{"location":"devdocs/FEValues/#Type-definitions","page":"FEValues","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"AbstractValues\nAbstractCellValues\nCellValues\nAbstractFacetValues\nFacetValues\nBCValues\nPointValues\nInterfaceValues","category":"page"},{"location":"devdocs/FEValues/#Internal-types","page":"FEValues","title":"Internal types","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.GeometryMapping\nFerrite.MappingValues\nFerrite.FunctionValues\nFerrite.BCValues","category":"page"},{"location":"devdocs/FEValues/#Ferrite.GeometryMapping","page":"FEValues","title":"Ferrite.GeometryMapping","text":"GeometryMapping{DiffOrder}(::Type{T}, ip_geo, qr::QuadratureRule)\n\nCreate a GeometryMapping object which contains the geometric\n\nshape values\ngradient values (if DiffOrder ≥ 1)\nhessians values (if DiffOrder ≥ 2)\n\nT<:AbstractFloat gives the numeric type of the values.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Ferrite.MappingValues","page":"FEValues","title":"Ferrite.MappingValues","text":"MappingValues(J, H)\n\nThe mapping values are calculated based on a geometric_mapping::GeometryMapping along with the cell coordinates, and the stored jacobian, J, and potentially hessian, H, are used when mapping the FunctionValues to the current cell during reinit!.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Ferrite.FunctionValues","page":"FEValues","title":"Ferrite.FunctionValues","text":"FunctionValues{DiffOrder}(::Type{T}, ip_fun, qr::QuadratureRule, ip_geo::VectorizedInterpolation)\n\nCreate a FunctionValues object containing the shape values and gradients (up to order DiffOrder) for both the reference cell (precalculated) and the real cell (updated in reinit!).\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Ferrite.BCValues","page":"FEValues","title":"Ferrite.BCValues","text":"BCValues(func_interpol::Interpolation, geom_interpol::Interpolation, boundary_type::Union{Type{<:BoundaryIndex}})\n\nBCValues stores the shape values at all facet/faces/edges/vertices (depending on boundary_type) for the geometric interpolation (geom_interpol), for each dof-position determined by the func_interpol. Used mainly by the ConstraintHandler.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Internal-utilities","page":"FEValues","title":"Internal utilities","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.embedding_det\nFerrite.shape_value_type\nFerrite.shape_gradient_type\nFerrite.ValuesUpdateFlags","category":"page"},{"location":"devdocs/FEValues/#Ferrite.embedding_det","page":"FEValues","title":"Ferrite.embedding_det","text":"embedding_det(J::SMatrix{3, 2})\n\nEmbedding determinant for surfaces in 3D.\n\nTLDR: \"det(J) =\" ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂\n\nThe transformation theorem for some function f on a 2D surface in 3D space leads to ∫ f ⋅ dS = ∫ f ⋅ (∂x/∂ξ₁ × ∂x/∂ξ₂) dξ₁dξ₂ = ∫ f ⋅ n ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ dξ₁dξ₂ where ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ is \"detJ\" and n is the unit normal. See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation. For more details see e.g. the doctoral thesis by Mirza Cenanovic Tangential Calculus [12].\n\n\n\n\n\nembedding_det(J::Union{SMatrix{2, 1}, SMatrix{3, 1}})\n\nEmbedding determinant for curves in 2D and 3D.\n\nTLDR: \"det(J) =\" ||∂x/∂ξ||₂\n\nThe transformation theorem for some function f on a 1D curve in 2D and 3D space leads to ∫ f ⋅ dE = ∫ f ⋅ ∂x/∂ξ dξ = ∫ f ⋅ t ||∂x/∂ξ||₂ dξ where ||∂x/∂ξ||₂ is \"detJ\" and t is \"the unit tangent\". See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/FEValues/#Ferrite.shape_value_type","page":"FEValues","title":"Ferrite.shape_value_type","text":"shape_value_type(fe_v::AbstractValues)\n\nReturn the type of shape_value(fe_v, q_point, base_function)\n\n\n\n\n\n","category":"function"},{"location":"devdocs/FEValues/#Ferrite.shape_gradient_type","page":"FEValues","title":"Ferrite.shape_gradient_type","text":"shape_gradient_type(fe_v::AbstractValues)\n\nReturn the type of shape_gradient(fe_v, q_point, base_function)\n\n\n\n\n\n","category":"function"},{"location":"devdocs/FEValues/#Ferrite.ValuesUpdateFlags","page":"FEValues","title":"Ferrite.ValuesUpdateFlags","text":"ValuesUpdateFlags(ip_fun::Interpolation; update_gradients = Val(true), update_hessians = Val(false), update_detJdV = Val(true))\n\nCreates a singelton type for specifying what parts of the AbstractValues should be updated. Note that this is internal API used to get type-stable construction. Keyword arguments in AbstractValues constructors are forwarded, and the public API is passing these as Bool, while the ValuesUpdateFlags method supports both boolean and Val(::Bool) keyword args.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Custom-FEValues","page":"FEValues","title":"Custom FEValues","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Custom FEValues, fe_v::AbstractValues, should normally implement the reinit! method. Subtypes of AbstractValues have default implementations for some functions, but require some lower-level access functions, specifically","category":"page"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"function_value, requires\nshape_value\ngetnquadpoints\ngetnbasefunctions\nfunction_gradient, function_divergence, function_symmetric_gradient, and function_curl requires\nshape_gradient\ngetnquadpoints\ngetnbasefunctions\nspatial_coordinate, requires\ngeometric_value\ngetngeobasefunctions\ngetnquadpoints","category":"page"},{"location":"devdocs/FEValues/#Array-bounds","page":"FEValues","title":"Array bounds","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Asking for the nth quadrature point must be inside array bounds if 1 <= n <= getnquadpoints(fe_v). (checkquadpoint can, alternatively, be dispatched to check that n is inbounds.)\nAsking for the ith shape value or gradient must be inside array bounds if 1 <= i <= getnbasefunctions(fe_v)\nAsking for the ith geometric value must be inside array bounds if 1 <= i <= getngeobasefunctions(fe_v)","category":"page"},{"location":"topics/FEValues/#fevalues_topicguide","page":"FEValues","title":"FEValues","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"A key type of object in Ferrite is the so-called FEValues, where the most common ones are CellValues and FacetValues. These objects are used inside the element routines and are used to query the integration weights, shape function values and gradients, and much more; see CellValues and FacetValues. For these values to be correct, it is necessary to reinitialize these for the current cell by using the reinit! function. This function maps the values from the reference cell to the actual cell, a process described in detail below, see Mapping of finite elements. After that, we show an implementation of a SimpleCellValues type to illustrate how CellValues work for the most standard case, excluding the generalizations and optimization that complicates the actual code.","category":"page"},{"location":"topics/FEValues/#mapping_theory","page":"FEValues","title":"Mapping of finite elements","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The shape functions and gradients stored in an FEValues object, are reinitialized for each cell by calling the reinit! function. The main part of this calculation, considers how to map the values and derivatives of the shape functions, defined on the reference cell, to the actual cell.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The geometric mapping of a finite element from the reference coordinates to the real coordinates is shown in the following illustration.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"(Image: mapping_figure)","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"This mapping is given by the geometric shape functions, hatN_i^g(boldsymbolxi), such that","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n boldsymbolx(boldsymbolxi) = sum_alpha=1^N hatboldsymbolx_alpha hatN_alpha^g(boldsymbolxi) \n boldsymbolJ = fracmathrmdboldsymbolxmathrmdboldsymbolxi = sum_alpha=1^N hatboldsymbolx_alpha otimes fracmathrmd hatN_alpha^gmathrmdboldsymbolxi\n boldsymbolmathcalH =\n fracmathrmd boldsymbolJmathrmd boldsymbolxi = sum_alpha=1^N hatboldsymbolx_alpha otimes fracmathrmd^2 hatN^g_alphamathrmd boldsymbolxi^2\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"where the defined boldsymbolJ is the jacobian of the mapping, and in some cases we will also need the corresponding hessian, boldsymbolmathcalH (3rd order tensor).","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"We require that the mapping from reference coordinates to real coordinates is diffeomorphic, meaning that we can express boldsymbolx = boldsymbolx(boldsymbolxi(boldsymbolx)), such that","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n fracmathrmdboldsymbolxmathrmdboldsymbolx = boldsymbolI = fracmathrmdboldsymbolxmathrmdboldsymbolxi cdot fracmathrmdboldsymbolximathrmdboldsymbolx\n quadRightarrowquad\n fracmathrmdboldsymbolximathrmdboldsymbolx = leftfracmathrmdboldsymbolxmathrmdboldsymbolxiright^-1 = boldsymbolJ^-1\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Depending on the function interpolation, we may want different types of mappings to conserve certain properties of the fields. This results in the different mapping types described below.","category":"page"},{"location":"topics/FEValues/#Identity-mapping","page":"FEValues","title":"Identity mapping","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.IdentityMapping","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"For scalar fields, we always use scalar base functions. For tensorial fields (non-scalar, e.g. vector-fields), the base functions can be constructed from scalar base functions, by using e.g. VectorizedInterpolation. From the perspective of the mapping, however, each component is mapped as an individual scalar base function. And for scalar base functions, we only require that the value of the base function is invariant to the element shape (real coordinate), and only depends on the reference coordinate, i.e.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n N(boldsymbolx) = hatN(boldsymbolxi(boldsymbolx))nonumber \n mathrmgrad(N(boldsymbolx)) = fracmathrmdhatNmathrmdboldsymbolxi cdot boldsymbolJ^-1\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Second order gradients of the shape functions are computed as","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n mathrmgrad(mathrmgrad(N(boldsymbolx))) = fracmathrmd^2 Nmathrmdboldsymbolx^2 = boldsymbolJ^-T cdot fracmathrmd^2hatNmathrmdboldsymbolxi^2 cdot boldsymbolJ^-1 - boldsymbolJ^-T cdotmathrmgrad(N) cdot boldsymbolmathcalH cdot boldsymbolJ^-1\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"details: Derivation\nThe gradient of the shape functions is obtained using the chain rule:beginalign*\n fracmathrmd Nmathrmdx_i = fracmathrmd hat Nmathrmd xi_rfracmathrmd xi_rmathrmd x_i = fracmathrmd hat Nmathrmd xi_r J^-1_ri\nendalign*For the second order gradients, we first use the product rule on the equation above:beginalign\n fracmathrmd^2 Nmathrmdx_i mathrmdx_j = fracmathrmdmathrmdx_jleftfracmathrmd hat Nmathrmd xi_rright J^-1_ri + fracmathrmd hat Nmathrmd xi_r fracmathrmdJ^-1_rimathrmdx_j\nendalignUsing the fact that fracmathrmdhatf(boldsymbolxi)mathrmdx_j = fracmathrmdhatf(boldsymbolxi)mathrmdxi_s J^-1_sj, the first term in the equation above can be expressed as:beginalign*\n fracmathrmdmathrmdx_jleftfracmathrmd hat Nmathrmd xi_rright J^-1_ri = J^-1_sjfracmathrmdmathrmdxi_sleftfracmathrmd hat Nmathrmd xi_rright J^-1_ri = J^-1_sjleftfracmathrmd^2 hat Nmathrmd xi_smathrmd xi_rright J^-1_ri\nendalign*The second term can be written as:beginalign*\n fracmathrmd hat Nmathrmd xi_rfracmathrmdJ^-1_rimathrmdx_j = fracmathrmd hat Nmathrmd xi_rleftfracmathrmdJ^-1_rimathrmdxi_srightJ^-1_sj = fracmathrmd hat Nmathrmd xi_rleft- J^-1_rkmathcalH_kps J^-1_piright J^-1_sj = - fracmathrmd hat Nmathrmd x_kmathcalH_kps J^-1_piJ^-1_sj\nendalign*where we have used that the inverse of the jacobian can be computed as:beginalign*\n0 = fracmathrmdmathrmdxi_s (J_kr J^-1_ri ) = fracmathrmdJ_kpmathrmdxi_s J^-1_pi + J_kr fracmathrmdJ^-1_rimathrmdxi_s = 0 quad Rightarrow \nendalign*beginalign*\nfracmathrmdJ^-1_rimathrmdxi_s = - J^-1_rkfracmathrmdJ_kpmathrmdxi_s J^-1_pi = - J^-1_rkmathcalH_kps J^-1_pi\nendalign*","category":"page"},{"location":"topics/FEValues/#Covariant-Piola-mapping,-H(curl)","page":"FEValues","title":"Covariant Piola mapping, H(curl)","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.CovariantPiolaMapping","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The covariant Piola mapping of a vectorial base function preserves the tangential components. For the value, the mapping is defined as","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n boldsymbolN(boldsymbolx) = boldsymbolJ^-mathrmT cdot hatboldsymbolN(boldsymbolxi(boldsymbolx))\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"which yields the gradient,","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n mathrmgrad(boldsymbolN(boldsymbolx)) = boldsymbolJ^-T cdot fracmathrmd hatboldsymbolNmathrmd boldsymbolxi cdot boldsymbolJ^-1 - boldsymbolJ^-T cdot lefthatboldsymbolN(boldsymbolxi(boldsymbolx))cdot boldsymbolJ^-1 cdot boldsymbolmathcalHcdot boldsymbolJ^-1right\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"details: Derivation\nExpressing the gradient, mathrmgrad(boldsymbolN), in index notation,beginalign*\n fracmathrmd N_imathrmd x_j = fracmathrmdmathrmd x_j leftJ^-mathrmT_ik hatN_kright = fracmathrmd J^-mathrmT_ikmathrmd x_j hatN_k + J^-mathrmT_ik fracmathrmd hatN_kmathrmd xi_l J_lj^-1\nendalign*Except for a few elements, boldsymbolJ varies as a function of boldsymbolx. The derivative can be calculated asbeginalign*\n fracmathrmd J^-mathrmT_ikmathrmd x_j = fracmathrmd J^-mathrmT_ikmathrmd J_mn fracmathrmd J_mnmathrmd x_j = - J_km^-1 J_in^-T fracmathrmd J_mnmathrmd x_j nonumber \n fracmathrmd J_mnmathrmd x_j = mathcalH_mno J_oj^-1\nendalign*","category":"page"},{"location":"topics/FEValues/#Contravariant-Piola-mapping,-H(div)","page":"FEValues","title":"Contravariant Piola mapping, H(div)","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.ContravariantPiolaMapping","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The covariant Piola mapping of a vectorial base function preserves the normal components. For the value, the mapping is defined as","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n boldsymbolN(boldsymbolx) = fracboldsymbolJdet(boldsymbolJ) cdot hatboldsymbolN(boldsymbolxi(boldsymbolx))\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"This gives the gradient","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n mathrmgrad(boldsymbolN(boldsymbolx)) = boldsymbolmathcalHcdotboldsymbolJ^-1 fracboldsymbolI underlineotimes boldsymbolI cdot hatboldsymbolNdet(boldsymbolJ)\n - leftfracboldsymbolJ cdot hatboldsymbolNdet(boldsymbolJ)right otimes leftboldsymbolJ^-T boldsymbolmathcalH cdot boldsymbolJ^-1right\n + boldsymbolJ cdot fracmathrmd hatboldsymbolNmathrmd boldsymbolxi cdot fracboldsymbolJ^-1det(boldsymbolJ)\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"details: Derivation\nExpressing the gradient, mathrmgrad(boldsymbolN), in index notation,beginalign*\n fracmathrmd N_imathrmd x_j = fracmathrmdmathrmd x_j leftfracJ_ikdet(boldsymbolJ) hatN_kright =nonumber\n = fracmathrmd J_ikmathrmd x_j frachatN_kdet(boldsymbolJ)\n - fracmathrmd det(boldsymbolJ)mathrmd x_j fracJ_ik hatN_kdet(boldsymbolJ)^2\n + fracJ_ikdet(boldsymbolJ) fracmathrmd hatN_kmathrmd xi_l J_lj^-1 \n = mathcalH_ikl J^-1_lj frachatN_kdet(boldsymbolJ)\n - J^-T_mn mathcalH_mnl J^-1_lj fracJ_ik hatN_kdet(boldsymbolJ)\n + fracJ_ikdet(boldsymbolJ) fracmathrmd hatN_kmathrmd xi_l J_lj^-1\nendalign*","category":"page"},{"location":"topics/FEValues/#SimpleCellValues","page":"FEValues","title":"Walkthrough: Creating SimpleCellValues","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"In the following, we walk through how to create a SimpleCellValues type which works similar to Ferrite's CellValues, but is not performance optimized and not as general. The main purpose is to explain how the CellValues works for the standard case of IdentityMapping described above. Please note that several internal functions are used, and these may change without a major version increment. Please see the Developer documentation for their documentation.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"# Include the example here, but modify the Literate output to suit being embedded\nusing Literate, Markdown\nbase_name = \"SimpleCellValues_literate\"\nLiterate.markdown(string(base_name, \".jl\"); name = base_name, execute = true, credit = false, documenter=false)\ncontent = read(string(base_name, \".md\"), String)\nrm(string(base_name, \".md\"))\nrm(string(base_name, \".jl\"))\nMarkdown.parse(content)","category":"page"},{"location":"topics/FEValues/#Further-reading","page":"FEValues","title":"Further reading","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"defelement.com\nKirby (2017) [5]","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/grid/#Grid","page":"Grid","title":"Grid","text":"","category":"section"},{"location":"topics/grid/#Mesh-reading","page":"Grid","title":"Mesh reading","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"A Ferrite Grid can be generated with the generate_grid function. More advanced meshes can be imported with the FerriteMeshParser.jl (from Abaqus input files), or even created and translated with the Gmsh.jl and FerriteGmsh.jl package, respectively.","category":"page"},{"location":"topics/grid/#FerriteGmsh.jl","page":"Grid","title":"FerriteGmsh.jl","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteGmsh.jl supports all defined cells with an alias in Ferrite.jl as well as the 3D Serendipity Cell{3,20,6}. Either, a mesh is created on the fly with the gmsh API or a mesh in .msh or .geo format can be read and translated with the FerriteGmsh.togrid function.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteGmsh.togrid","category":"page"},{"location":"topics/grid/#FerriteGmsh.togrid","page":"Grid","title":"FerriteGmsh.togrid","text":"togrid(filename::String; domain=\"\")\n\nOpen the Gmsh file filename (ie a .geo or .msh file) and return the corresponding Ferrite.Grid.\n\n\n\n\n\ntogrid(; domain=\"\")\n\nGenerate a Ferrite.Grid from the current active/open model in the Gmsh library.\n\n\n\n\n\n","category":"function"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteGmsh supports currently the translation of cellsets and facetsets. Such sets are defined in Gmsh as PhysicalGroups of dimension dim and dim-1, respectively. In case only a part of the mesh is the domain, the domain can be specified by providing the keyword argument domain the name of the PhysicalGroups in the FerriteGmsh.togrid function.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"note: Why you should read a .msh file\nReading a .msh file is the advertised way, since otherwise you remesh whenever you run the code. Further, if you choose to read the grid directly from the current model of the gmsh API you get artificial nodes, which doesn't harm the FE computation, but maybe distort your sophisticated grid operations (if present). For more information, see this issue.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"If you want to read another, not yet supported cell from gmsh, consider to open a PR at FerriteGmsh that extends the gmshtoferritecell dict and if needed, reorder the element nodes by dispatching FerriteGmsh.translate_elements. The reordering of nodes is necessary if the Gmsh ordering doesn't match the one from Ferrite. Gmsh ordering is documented here. For an exemplary usage of Gmsh.jl and FerriteGmsh.jl, consider the Stokes flow and Incompressible Navier-Stokes Equations via DifferentialEquations.jl example.","category":"page"},{"location":"topics/grid/#FerriteMeshParser.jl","page":"Grid","title":"FerriteMeshParser.jl","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteMeshParser.jl converts the mesh in an Abaqus input file (.inp) to a Ferrite.Grid with its function get_ferrite_grid. The translations for most of Abaqus' standard 2d and 3d continuum elements to a Ferrite.AbstractCell are defined. Custom translations can be given as input, which can be used to import other (custom) elements or to override the default translation.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteMeshParser.get_ferrite_grid","category":"page"},{"location":"topics/grid/#FerriteMeshParser.get_ferrite_grid","page":"Grid","title":"FerriteMeshParser.get_ferrite_grid","text":"function get_ferrite_grid(\n filename; \n meshformat=AutomaticMeshFormat(), \n user_elements=Dict{String,DataType}(), \n generate_facetsets=true\n )\n\nCreate a Ferrite.Grid by reading in the file specified by filename.\n\nOptional arguments:\n\nmeshformat: Which format the mesh is given in, normally automatically detected by the file extension\nuser_elements: Used to add extra elements not supported, might require a separate cell constructor.\ngenerate_facetsets: Should facesets be automatically generated from all nodesets?\n\n\n\n\n\n","category":"function"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"If you are missing the translation of an Abaqus element that is equivalent to a Ferrite.AbstractCell, consider to open an issue or a pull request.","category":"page"},{"location":"topics/grid/#Grid-datastructure","page":"Grid","title":"Grid datastructure","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"In Ferrite a Grid is a collection of Nodes and Cells and is parameterized in its physical dimensionality and cell type. Nodes are points in the physical space and can be initialized by a N-Tuple, where N corresponds to the dimensions.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"n1 = Node((0.0, 0.0))","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Cells are defined based on the Node IDs. Hence, they collect IDs in a N-Tuple. Consider the following 2D mesh:","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"(Image: global mesh)","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The cells of the grid can be described in the following way","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"cells = [Quadrilateral((1, 2, 5, 4)),\n Quadrilateral((2, 3, 6, 5)),\n Quadrilateral((4, 5, 8, 7)),\n Quadrilateral((5, 6, 9, 8))]","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"where each Quadrilateral <: AbstractCell is defined by the tuple of node IDs. Additionally, the data structure Grid contains node-, cell-, facet-, and vertexsets. Each of these sets is defined by a Dict{String, OrderedSet}.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Node- and cellsets are represented by an OrderedSet{Int}, giving a set of node or cell ID, respectively.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Facet- and vertexsets are represented by OrderedSet{<:BoundaryIndex}, where BoundaryIndex is a FacetIndex or VertexIndex respectively. FacetIndex and VertexIndex wraps a Tuple, (global_cell_id, local_facet_id) and (global_cell_id, local_vertex_id), where the local IDs are defined according to the reference shapes, see Reference shapes.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The highlighted facets, i.e. the two edges from node ID 3 to 6 and from 6 to 9, on the right hand side of our test mesh can now be described as","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"boundary_facets = [(3, 6), (6, 9)]","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"i.e. by using the node IDs of the reference shape vertices.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The first of these can be found as the 2nd facet of the 2nd cell.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"using Ferrite #hide\nFerrite.facets(Quadrilateral((2, 3, 6, 5)))","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The unique representation of an entity is given by the sorted version of this tuple. While we could use this information to construct a facet set, Ferrite can construct this set by filtering based on the coordinates, using addfacetset!.","category":"page"},{"location":"topics/grid/#AbstractGrid","page":"Grid","title":"AbstractGrid","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"It can be very useful to use a grid type for a certain special case, e.g. mixed cell types, adaptivity, IGA, etc. In order to define your own <: AbstractGrid you need to fulfill the AbstractGrid interface. In case that certain structures are preserved from the Ferrite.Grid type, you don't need to dispatch on your own type, but rather rely on the fallback AbstractGrid dispatch.","category":"page"},{"location":"topics/grid/#Example","page":"Grid","title":"Example","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"As a starting point, we choose a minimal working example from the test suite:","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"struct SmallGrid{dim,N,C<:Ferrite.AbstractCell} <: Ferrite.AbstractGrid{dim}\n nodes_test::Vector{NTuple{dim,Float64}}\n cells_test::NTuple{N,C}\nend","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Here, the names of the fields as well as their underlying datastructure changed compared to the Grid type. This would lead to the fact, that any usage with the utility functions and DoF management will not work. So, we need to feed into the interface how to handle this subtyped datastructure. We start with the utility functions that are associated with the cells of the grid:","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Ferrite.getcells(grid::SmallGrid) = grid.cells_test\nFerrite.getcells(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.cells_test[v]\nFerrite.getncells(grid::SmallGrid{dim,N}) where {dim,N} = N\nFerrite.getcelltype(grid::SmallGrid) = eltype(grid.cells_test)\nFerrite.getcelltype(grid::SmallGrid, i::Int) = typeof(grid.cells_test[i])","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Next, we define some helper functions that take care of the node handling.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Ferrite.getnodes(grid::SmallGrid) = grid.nodes_test\nFerrite.getnodes(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.nodes_test[v]\nFerrite.getnnodes(grid::SmallGrid) = length(grid.nodes_test)\nFerrite.get_coordinate_eltype(::SmallGrid) = Float64\nFerrite.get_coordinate_type(::SmallGrid{dim}) where dim = Vec{dim,Float64}\nFerrite.nnodes_per_cell(grid::SmallGrid, i::Int=1) = Ferrite.nnodes(grid.cells_test[i])","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"These definitions make many of Ferrite functions work out of the box, e.g. you can now call getcoordinates(grid, cellid) on the SmallGrid.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Now, you would be able to assemble the heat equation example over the new custom SmallGrid type. Note that this particular subtype isn't able to handle boundary entity sets and so, you can't describe boundaries with it. In order to use boundaries, e.g. for Dirichlet constraints in the ConstraintHandler, you would need to dispatch the AbstractGrid sets utility functions on SmallGrid.","category":"page"},{"location":"topics/grid/#Topology","page":"Grid","title":"Topology","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Ferrite.jl's Grid type offers experimental features w.r.t. topology information. The functions getneighborhood and facetskeleton are the interface to obtain topological information. The getneighborhood can construct lists of directly connected entities based on a given entity (CellIndex, FacetIndex, FaceIndex, EdgeIndex, or VertexIndex). The facetskeleton function can be used to evaluate integrals over material interfaces or computing element interface values such as jumps.","category":"page"},{"location":"topics/sparse_matrix/#topic-sparse-matrix","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"An important property of the finite element method is that it results in sparse matrices for the linear systems to be solved. On this page the topic of sparsity and sparse matrices are discussed.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Pages = [\"sparse_matrix.md\"]\nDepth = 2:2","category":"page"},{"location":"topics/sparse_matrix/#Sparsity-pattern","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The sparse structure of the linear system depends on many factors such as e.g. the weak form, the discretization, and the choice of interpolation(s). In the end it boils down to how the degrees of freedom (DoFs) couple with each other. The most common reason that two DoFs couple is because they belong to the same element. Note, however, that this is not guaranteed to result in a coupling since it depends on the specific weak form that is being discretized, see e.g. Increasing the sparsity. Boundary conditions and constraints can also result in additional DoF couplings.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"If DoFs i and j couple, then the computed value in the eventual matrix will be structurally nonzero[1]. In this case the entry (i, j) should be included in the sparsity pattern. Conversely, if DoFs i and j don't couple, then the computed value will be zero. In this case the entry (i, j) should not be included in the sparsity pattern since there is no need to allocate memory for entries that will be zero.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The sparsity, i.e. the ratio of zero-entries to the total number of entries, is often[2] very high and taking advantage of this results in huge savings in terms of memory. For example, in a problem with 10^6 DoFs there will be a matrix of size 10^6 times 10^6. If all 10^12 entries of this matrix had to be stored (0% sparsity) as double precision (Float64, 8 bytes) it would require 8 TB of memory. If instead the sparsity is 99.9973% (which is the case when solving the heat equation on a three dimensional hypercube with linear Lagrange interpolation) this would be reduced to 216 MB.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"[1]: Structurally nonzero means that there is a possibility of a nonzero value even though the computed value might become zero in the end for various reasons.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"[2]: At least for most practical problems using low order interpolations.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"details: Sparsity pattern example\nTo give an example, in this one-dimensional heat problem (see the Heat equation tutorial for the weak form) we have 4 nodes with 3 elements in between. For simplicitly DoF numbers and node numbers are the same but this is not true in general since nodes and DoFs can be numbered independently (and in fact are numbered independently in Ferrite).1 ----- 2 ----- 3 ----- 4Assuming we use linear Lagrange interpolation (the \"hat functions\") this will give the following connections according to the weak form:Trial function 1 couples with test functions 1 and 2 (entries (1, 1) and (1, 2) included in the sparsity pattern)\nTrial function 2 couples with test functions 1, 2, and 3 (entries (2, 1), (2, 2), and (2, 3) included in the sparsity pattern)\nTrial function 3 couples with test functions 2, 3, and 4 (entries (3, 2), (3, 3), and (3, 4) included in the sparsity pattern)\nTrial function 4 couples with test functions 3 and 4 (entries (4, 3) and (4, 4) included in the sparsity pattern)The resulting sparsity pattern would look like this:4×4 SparseArrays.SparseMatrixCSC{Float64, Int64} with 10 stored entries:\n 0.0 0.0 ⋅ ⋅\n 0.0 0.0 0.0 ⋅\n ⋅ 0.0 0.0 0.0\n ⋅ ⋅ 0.0 0.0Moreover, if the problem is solved with periodic boundary conditions, for example by constraining the value on the right side to the value on the left side, there will be additional couplings. In the example above, this means that DoF 4 should be equal to DoFSince DoF 4 is constrained it has to be eliminated from the system. Existing entriesthat include DoF 4 are (3, 4), (4, 3), and (4, 4). Given the simple constraint in this case we can simply replace DoF 4 with DoF 1 in these entries and we end up with entries (3, 1), (1, 3), and (1, 1). This results in two new entries: (3, 1) and (1, 3) (entry (1, 1) is already included).","category":"page"},{"location":"topics/sparse_matrix/#Creating-sparsity-patterns","page":"Sparsity pattern and sparse matrices","title":"Creating sparsity patterns","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Creating a sparsity pattern can be quite expensive if not done properly and therefore Ferrite provides efficient methods and data structures for this. In general the sparsity pattern is not known in advance and has to be created incrementally. To make this incremental construction efficient it is necessary to use a dynamic data structure which allow for fast insertions.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The sparsity pattern also serves as a \"matrix builder\". When all entries are inserted into the sparsity pattern the dynamic data structure is typically converted, or \"compressed\", into a sparse matrix format such as e.g. the compressed sparse row (CSR) format or the compressed sparse column (CSC) format, where the latter is the default sparse matrix type implemented in the SparseArrays standard library. These matrix formats allow for fast linear algebra operations, such as factorizations and matrix-vector multiplications, that are needed when the linear system is solved. See Instantiating the sparse matrix for more details.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"In summary, a dynamic structure is more efficient when incrementally building the pattern by inserting new entries, and a static or compressed structure is more efficient for linear algebra operations.","category":"page"},{"location":"topics/sparse_matrix/#Basic-sparsity-patterns-construction","page":"Sparsity pattern and sparse matrices","title":"Basic sparsity patterns construction","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Working with the sparsity pattern explicitly is in many cases not necessary. For basic usage (e.g. when only one matrix needed, when no customization of the pattern is required, etc) there exist convenience methods of allocate_matrix that return the matrix directly. Most examples in this documentation don't deal with the sparsity pattern explicitly because the basic method suffice. See also Instantiating the sparse matrix for more details.","category":"page"},{"location":"topics/sparse_matrix/#Custom-sparsity-pattern-construction","page":"Sparsity pattern and sparse matrices","title":"Custom sparsity pattern construction","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"In more advanced cases there might be a need for more fine grained control of the sparsity pattern. The following steps are typically taken when constructing a sparsity pattern in Ferrite:","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Initialize an empty pattern: This can be done by either using the init_sparsity_pattern(dh) function or by using a constructor directly. init_sparsity_pattern will return a default pattern type that is compatible with the DofHandler. In some cases you might require another type of pattern (for example a blocked pattern, see Blocked sparsity pattern) and in that case you can use the constructor directly.\nAdd entries to the pattern: There are a number of functions that add entries to the pattern:\nadd_sparsity_entries! is a convenience method for performing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries! after each other (see below).\nadd_cell_entries! adds entries for all couplings between the DoFs within each element. These entries correspond to assembling the standard element matrix and is thus almost always required.\nadd_interface_entries! adds entries for couplings between the DoFs in neighboring elements. These entries are required when integrating along internal interfaces between elements (e.g. for discontinuous Galerkin methods).\nadd_constraint_entries! adds entries required from constraints and boundary conditions in the ConstraintHandler. Note that this operation depends on existing entries in the pattern and must be called as the last operation on the pattern.\nFerrite.add_entry! adds a single entry to the pattern. This can be used if you need to add custom entries that are not covered by the other functions.\nInstantiate the matrix: A sparse matrix can be created from the sparsity pattern using allocate_matrix, see Instantiating the sparse matrix below for more details.","category":"page"},{"location":"topics/sparse_matrix/#Increasing-the-sparsity","page":"Sparsity pattern and sparse matrices","title":"Increasing the sparsity","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"By default, when creating a sparsity pattern, it is assumed that each DoF within an element couple with with all other DoFs in the element.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"todo: Todo\nDiscuss the coupling keyword argument.\nDiscuss the keep_constrained keyword argument.","category":"page"},{"location":"topics/sparse_matrix/#Blocked-sparsity-pattern","page":"Sparsity pattern and sparse matrices","title":"Blocked sparsity pattern","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"todo: Todo\nDiscuss BlockSparsityPattern and BlockArrays extension.","category":"page"},{"location":"topics/sparse_matrix/#Instantiating-the-sparse-matrix","page":"Sparsity pattern and sparse matrices","title":"Instantiating the sparse matrix","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"As mentioned above, for many simple cases there is no need to work with the sparsity pattern directly and using methods of allocate_matrix that take the DofHandler as input is enough, for example:","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"K = allocate_matrix(dh, ch)","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"allocate_matrix is also used to instantiate a matrix from a sparsity pattern, for example:","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"K = allocate_matrix(sp)","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"note: Multiple matrices with the same pattern\nFor some problems there is a need for multiple matrices with the same sparsity pattern, for example a mass matrix and a stiffness matrix. In this case it is more efficient to create the sparsity pattern once and then instantiate both matrices from it.","category":"page"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/dofhandler/#Degrees-of-Freedom","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"Degrees of freedom (dofs) are distributed by the DofHandler.","category":"page"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"DofHandler\nSubDofHandler","category":"page"},{"location":"reference/dofhandler/#Ferrite.DofHandler","page":"Degrees of Freedom","title":"Ferrite.DofHandler","text":"DofHandler(grid::Grid)\n\nConstruct a DofHandler based on the grid grid.\n\nAfter construction any number of discrete fields can be added to the DofHandler using add!. Construction is finalized by calling close!.\n\nBy default fields are added to all elements of the grid. Refer to SubDofHandler for restricting fields to subdomains of the grid.\n\nExamples\n\ndh = DofHandler(grid)\nip_u = Lagrange{RefTriangle, 2}()^2 # vector interpolation for a field u\nip_p = Lagrange{RefTriangle, 1}() # scalar interpolation for a field p\nadd!(dh, :u, ip_u)\nadd!(dh, :p, ip_p)\nclose!(dh)\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.SubDofHandler","page":"Degrees of Freedom","title":"Ferrite.SubDofHandler","text":"SubDofHandler(dh::AbstractDofHandler, cellset::AbstractVecOrSet{Int})\n\nCreate an sdh::SubDofHandler from the parent dh, pertaining to the cells in cellset. This allows you to add fields to parts of the domain, or using different interpolations or cell types (e.g. Triangles and Quadrilaterals). All fields and cell types must be the same in one SubDofHandler.\n\nAfter construction any number of discrete fields can be added to the SubDofHandler using add!. Construction is finalized by calling close! on the parent dh.\n\nExamples\n\nWe assume we have a grid containing \"Triangle\" and \"Quadrilateral\" cells, including the cellsets \"triangles\" and \"quadilaterals\" for to these cells.\n\ndh = DofHandler(grid)\n\nsdh_tri = SubDofHandler(dh, getcellset(grid, \"triangles\"))\nip_tri = Lagrange{RefTriangle, 2}()^2 # vector interpolation for a field u\nadd!(sdh_tri, :u, ip_tri)\n\nsdh_quad = SubDofHandler(dh, getcellset(grid, \"quadilaterals\"))\nip_quad = Lagrange{RefQuadrilateral, 2}()^2 # vector interpolation for a field u\nadd!(sdh_quad, :u, ip_quad)\n\nclose!(dh) # Finalize by closing the parent\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Adding-fields-to-the-DofHandlers","page":"Degrees of Freedom","title":"Adding fields to the DofHandlers","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"add!(::DofHandler, ::Symbol, ::Interpolation)\nadd!(::SubDofHandler, ::Symbol, ::Interpolation)\nclose!(::DofHandler)","category":"page"},{"location":"reference/dofhandler/#Ferrite.add!-Tuple{DofHandler, Symbol, Interpolation}","page":"Degrees of Freedom","title":"Ferrite.add!","text":"add!(dh::DofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the DofHandler dh.\n\nThe field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.\n\n\n\n\n\n","category":"method"},{"location":"reference/dofhandler/#Ferrite.add!-Tuple{SubDofHandler, Symbol, Interpolation}","page":"Degrees of Freedom","title":"Ferrite.add!","text":"add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the SubDofHandler sdh.\n\n\n\n\n\n","category":"method"},{"location":"reference/dofhandler/#Ferrite.close!-Tuple{DofHandler}","page":"Degrees of Freedom","title":"Ferrite.close!","text":"close!(dh::AbstractDofHandler)\n\nCloses dh and creates degrees of freedom for each cell.\n\n\n\n\n\n","category":"method"},{"location":"reference/dofhandler/#Dof-renumbering","page":"Degrees of Freedom","title":"Dof renumbering","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"renumber!\nDofOrder.FieldWise\nDofOrder.ComponentWise","category":"page"},{"location":"reference/dofhandler/#Ferrite.renumber!","page":"Degrees of Freedom","title":"Ferrite.renumber!","text":"renumber!(dh::AbstractDofHandler, order)\nrenumber!(dh::AbstractDofHandler, ch::ConstraintHandler, order)\n\nRenumber the degrees of freedom in the DofHandler and/or ConstraintHandler according to the ordering order.\n\norder can be given by one of the following options:\n\nA permutation vector perm::AbstractVector{Int} such that dof i is renumbered to perm[i].\nDofOrder.FieldWise() for renumbering dofs field wise.\nDofOrder.ComponentWise() for renumbering dofs component wise.\nDofOrder.Ext{T} for \"external\" renumber permutations, see documentation for DofOrder.Ext for details.\n\nwarning: Warning\nThe dof numbering in the DofHandler and ConstraintHandler must always be consistent. It is therefore necessary to either renumber before creating the ConstraintHandler in the first place, or to renumber the DofHandler and the ConstraintHandler together.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.DofOrder.FieldWise","page":"Degrees of Freedom","title":"Ferrite.DofOrder.FieldWise","text":"DofOrder.FieldWise()\nDofOrder.FieldWise(target_blocks::Vector{Int})\n\nDof order passed to renumber! to renumber global dofs field wise resulting in a globally blocked system.\n\nThe default behavior is to group dofs of each field into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of the same length as the total number of fields in the DofHandler (see getfieldnames(dh)) that maps each field to a \"target block\": to renumber a DofHandler with three fields :u, :v, :w such that dofs for :u and :w end up in the first global block, and dofs for :v in the second global block use DofOrder.FieldWise([1, 2, 1]).\n\nThis renumbering is stable such that the original relative ordering of dofs within each target block is maintained.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.DofOrder.ComponentWise","page":"Degrees of Freedom","title":"Ferrite.DofOrder.ComponentWise","text":"DofOrder.ComponentWise()\nDofOrder.ComponentWise(target_blocks::Vector{Int})\n\nDof order passed to renumber! to renumber global dofs component wise resulting in a globally blocked system.\n\nThe default behavior is to group dofs of each component into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of length ncomponents that maps each component to a \"target block\" (see DofOrder.FieldWise for details).\n\nThis renumbering is stable such that the original relative ordering of dofs within each target block is maintained.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Common-methods","page":"Degrees of Freedom","title":"Common methods","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"ndofs\nndofs_per_cell\ndof_range\ncelldofs\ncelldofs!","category":"page"},{"location":"reference/dofhandler/#Ferrite.ndofs","page":"Degrees of Freedom","title":"Ferrite.ndofs","text":"ndofs(dh::AbstractDofHandler)\n\nReturn the number of degrees of freedom in dh\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.ndofs_per_cell","page":"Degrees of Freedom","title":"Ferrite.ndofs_per_cell","text":"ndofs_per_cell(dh::AbstractDofHandler[, cell::Int=1])\n\nReturn the number of degrees of freedom for the cell with index cell.\n\nSee also ndofs.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.dof_range","page":"Degrees of Freedom","title":"Ferrite.dof_range","text":"dof_range(sdh::SubDofHandler, field_idx::Int)\ndof_range(sdh::SubDofHandler, field_name::Symbol)\ndof_range(dh:DofHandler, field_name::Symbol)\n\nReturn the local dof range for a given field. The field can be specified by its name or index, where field_idx represents the index of a field within a SubDofHandler and field_idxs is a tuple of the SubDofHandler-index within the DofHandler and the field_idx.\n\nnote: Note\nThe dof_range of a field can vary between different SubDofHandlers. Therefore, it is advised to use the field_idxs or refer to a given SubDofHandler directly in case several SubDofHandlers exist. Using the field_name will always refer to the first occurrence of field within the DofHandler.\n\nExample:\n\njulia> grid = generate_grid(Triangle, (3, 3))\nGrid{2, Triangle, Float64} with 18 Triangle cells and 16 nodes\n\njulia> dh = DofHandler(grid); add!(dh, :u, 3); add!(dh, :p, 1); close!(dh);\n\njulia> dof_range(dh, :u)\n1:9\n\njulia> dof_range(dh, :p)\n10:12\n\njulia> dof_range(dh, (1,1)) # field :u\n1:9\n\njulia> dof_range(dh.subdofhandlers[1], 2) # field :p\n10:12\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.celldofs","page":"Degrees of Freedom","title":"Ferrite.celldofs","text":"celldofs(dh::AbstractDofHandler, i::Int)\n\nReturn a vector with the degrees of freedom that belong to cell i.\n\nSee also celldofs!.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.celldofs!","page":"Degrees of Freedom","title":"Ferrite.celldofs!","text":"celldofs!(global_dofs::Vector{Int}, dh::AbstractDofHandler, i::Int)\n\nStore the degrees of freedom that belong to cell i in global_dofs.\n\nSee also celldofs.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Grid-iterators","page":"Degrees of Freedom","title":"Grid iterators","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"CellCache\nCellIterator\nFacetCache\nFacetIterator\nInterfaceCache\nInterfaceIterator","category":"page"},{"location":"reference/dofhandler/#Ferrite.CellCache","page":"Degrees of Freedom","title":"Ferrite.CellCache","text":"CellCache(grid::Grid)\nCellCache(dh::AbstractDofHandler)\n\nCreate a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell. The cache is updated for a new cell by calling reinit!(cache, cellid) where cellid::Int is the cell id.\n\nMethods with CellCache\n\nreinit!(cc, i): reinitialize the cache for cell i\ncellid(cc): get the cell id of the currently cached cell\ngetnodes(cc): get the global node ids of the cell\ngetcoordinates(cc): get the coordinates of the cell\ncelldofs(cc): get the global dof ids of the cell\nreinit!(fev, cc): reinitialize CellValues or FacetValues\n\nSee also CellIterator.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.CellIterator","page":"Degrees of Freedom","title":"Ferrite.CellIterator","text":"CellIterator(grid::Grid, cellset=1:getncells(grid))\nCellIterator(dh::AbstractDofHandler, cellset=1:getncells(dh))\n\nCreate a CellIterator to conveniently iterate over all, or a subset, of the cells in a grid. The elements of the iterator are CellCaches which are properly reinit!ialized. See CellCache for more details.\n\nLooping over a CellIterator, i.e.:\n\nfor cc in CellIterator(grid, cellset)\n # ...\nend\n\nis thus simply convenience for the following equivalent snippet:\n\ncc = CellCache(grid)\nfor idx in cellset\n reinit!(cc, idx)\n # ...\nend\n\nwarning: Warning\nCellIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.FacetCache","page":"Degrees of Freedom","title":"Ferrite.FacetCache","text":"FacetCache(grid::Grid)\nFacetCache(dh::AbstractDofHandler)\n\nCreate a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell suitable for looping over faces in a grid. The cache is updated for a new face by calling reinit!(cache, fi::FacetIndex).\n\nMethods with fc::FacetCache\n\nreinit!(fc, fi): reinitialize the cache for face fi::FacetIndex\ncellid(fc): get the current cellid\ngetnodes(fc): get the global node ids of the cell\ngetcoordinates(fc): get the coordinates of the cell\ncelldofs(fc): get the global dof ids of the cell\nreinit!(fv, fc): reinitialize FacetValues\n\nSee also FacetIterator.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.FacetIterator","page":"Degrees of Freedom","title":"Ferrite.FacetIterator","text":"FacetIterator(gridordh::Union{Grid,AbstractDofHandler}, facetset::AbstractVecOrSet{FacetIndex})\n\nCreate a FacetIterator to conveniently iterate over the faces in facestet. The elements of the iterator are FacetCaches which are properly reinit!ialized. See FacetCache for more details.\n\nLooping over a FacetIterator, i.e.:\n\nfor fc in FacetIterator(grid, facetset)\n # ...\nend\n\nis thus simply convenience for the following equivalent snippet: ```julia fc = FacetCache(grid) for faceindex in facetset reinit!(fc, faceindex) # ... end\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.InterfaceCache","page":"Degrees of Freedom","title":"Ferrite.InterfaceCache","text":"InterfaceCache(grid::Grid)\nInterfaceCache(dh::AbstractDofHandler)\n\nCreate a cache object with pre-allocated memory for the nodes, coordinates, and dofs of an interface. The cache is updated for a new cell by calling reinit!(cache, facet_a, facet_b) where facet_a::FacetIndex and facet_b::FacetIndex are the two interface faces.\n\nStruct fields of InterfaceCache\n\nic.a :: FacetCache: face cache for the first face of the interface\nic.b :: FacetCache: face cache for the second face of the interface\nic.dofs :: Vector{Int}: global dof ids for the interface (union of ic.a.dofs and ic.b.dofs)\n\nMethods with InterfaceCache\n\nreinit!(cache::InterfaceCache, facet_a::FacetIndex, facet_b::FacetIndex): reinitialize the cache for a new interface\ninterfacedofs(ic): get the global dof ids of the interface\n\nSee also InterfaceIterator.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.InterfaceIterator","page":"Degrees of Freedom","title":"Ferrite.InterfaceIterator","text":"InterfaceIterator(grid::Grid, [topology::ExclusiveTopology])\nInterfaceIterator(dh::AbstractDofHandler, [topology::ExclusiveTopology])\n\nCreate an InterfaceIterator to conveniently iterate over all the interfaces in a grid. The elements of the iterator are InterfaceCaches which are properly reinit!ialized. See InterfaceCache for more details. Looping over an InterfaceIterator, i.e.:\n\nfor ic in InterfaceIterator(grid, topology)\n # ...\nend\n\nis thus simply convenience for the following equivalent snippet for grids of dimensions > 1:\n\nic = InterfaceCache(grid, topology)\nfor face in topology.face_skeleton\n neighborhood = topology.face_face_neighbor[face[1], face[2]]\n isempty(neighborhood) && continue\n neighbor_face = neighborhood[1]\n reinit!(ic, face, neighbor_face)\n # ...\nend\n\nwarning: Warning\nInterfaceIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).\n\n\n\n\n\n","category":"type"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"using Ferrite","category":"page"},{"location":"topics/degrees_of_freedom/#Degrees-of-Freedom","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"","category":"section"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"The distribution and numbering of degrees of freedom (dofs) are handled by the DofHandler. The DofHandler will be used to query information about the dofs. For example we can obtain the dofs for a particular cell, which we need when assembling the system.","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"The DofHandler is based on the grid. Here we create a simple grid with Triangle cells, and then create a DofHandler based on the grid","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"grid = generate_grid(Triangle, (20, 20))\ndh = DofHandler(grid)\n# hide","category":"page"},{"location":"topics/degrees_of_freedom/#Fields","page":"Degrees of Freedom","title":"Fields","text":"","category":"section"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"Before we can distribute the dofs we need to specify fields. A field is simply the unknown function(s) we are solving for. To add a field we need a name (a Symbol) and the the interpolation describing the shape functions for the field. Here we add a scalar field :p, interpolated using linear (degree 1) shape functions on a triangle, and a vector field :u, also interpolated with linear shape functions on a triangle, but raised to the power 2 to indicate that it is a vector field with 2 components (for a 2D problem).","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"add!(dh, :p, Lagrange{RefTriangle, 1}())\nadd!(dh, :u, Lagrange{RefTriangle, 1}()^2)\n# hide","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"Finally, when we have added all the fields, we have to close! the DofHandler. When the DofHandler is closed it will traverse the grid and distribute all the dofs for the fields we added.","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"close!(dh)","category":"page"},{"location":"topics/degrees_of_freedom/#Ordering-of-Dofs","page":"Degrees of Freedom","title":"Ordering of Dofs","text":"","category":"section"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"todo: Todo\nDescribe dof ordering within elements (vertices -> edges -> faces -> volumes) and dof_range. Describe (global) dof renumbering","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"EditURL = \"../literate-tutorials/dg_heat_equation.jl\"","category":"page"},{"location":"tutorials/dg_heat_equation/#tutorial-dg-heat-equation","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"(Image: )","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Figure 1: Temperature field on the unit square with an internal uniform heat source solved with inhomogeneous Dirichlet boundary conditions on the left and right boundaries and flux on the top and bottom boundaries.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"tip: Tip\nThis example is also available as a Jupyter notebook: dg_heat_equation.ipynb.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"This example was developed as part of the Google summer of code funded project \"Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl\"","category":"page"},{"location":"tutorials/dg_heat_equation/#Introduction","page":"Discontinuous Galerkin heat equation","title":"Introduction","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"This tutorial extends Tutorial 1: Heat equation by using the discontinuous Galerkin method. The reader is expected to have gone through Tutorial 1: Heat equation before proceeding with this tutorial. The main differences between the two tutorials are the interface integral terms in the weak form, the boundary conditions, and some implementation differences explained in the commented program below.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The strong form considered in this tutorial is given as follows","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":" -boldsymbolnabla cdot boldsymbolnabla(u) = 1 quad textbfx in Omega","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"with the inhomogeneous Dirichlet boundary conditions","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"u(textbfx) = 1 quad textbfx in partial Omega_D^+ = lbracetextbfx x_1 = 10rbrace \nu(textbfx) = -1 quad textbfx in partial Omega_D^- = lbracetextbfx x_1 = -10rbrace","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"and Neumann boundary conditions","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"boldsymbolnabla (u(textbfx)) cdot boldsymboln = 1 quad textbfx in partial Omega_N^+ = lbracetextbfx x_2 = 10rbrace \nboldsymbolnabla (u(textbfx)) cdot boldsymboln = -1 quad textbfx in partial Omega_N^- = lbracetextbfx x_2 = -10rbrace","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The following definitions of average and jump on interfaces between elements are adopted in this tutorial:","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":" u = frac12(u^+ + u^-)quad llbracket urrbracket = u^+ boldsymboln^+ + u^- boldsymboln^-","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"where u^+ and u^- are the temperature on the two sides of the interface.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"details: Derivation of the weak form for homogeneous Dirichlet boundary condition\nDefining boldsymbolsigma as the gradient of the temperature field the equation can be expressed as boldsymbolsigma = boldsymbolnabla (u)\n -boldsymbolnabla cdot boldsymbolsigma = 1Multiplying by test functions $ \\boldsymbol{\\tau} $ and $ \\delta u $ respectively and integrating over the domain, int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = int_Omega boldsymbolnabla (u) cdot boldsymboltau mathrmdOmega\n -int_Omega boldsymbolnabla cdot boldsymbolsigma delta u mathrmdOmega = int_Omega delta u mathrmdOmegaIntegrating by parts and applying divergence theorem, int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = -int_Omega u (boldsymbolnabla cdot boldsymboltau) mathrmdOmega + int_Gamma hatu boldsymboltau cdot boldsymboln mathrmdGamma\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma delta u boldsymbolhatsigma cdot boldsymboln mathrmdGammaWhere boldsymboln is the outwards pointing normal, Gamma is the union of the elements' boundaries, and hatu hatsigma are the numerical fluxes. Substituting the integrals of form int_Gamma q boldsymbolphi cdot boldsymboln mathrmdGamma = int_Gamma llbracket qrrbracket cdot boldsymbolphi mathrmdGamma + int_Gamma^0 q llbracket boldsymbolphirrbracket mathrmdGamma^0where Gamma^0 Gamma setminus partial Omega, and the jump of the vector-valued field boldsymbolphi is defined as llbracket boldsymbolphirrbracket = boldsymbolphi^+ cdot boldsymboln^+ + boldsymbolphi^- cdot boldsymboln^-with the jumps and averages results in int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = -int_Omega u (boldsymbolnabla cdot boldsymboltau) mathrmdOmega + int_Gamma llbracket haturrbracket cdot boldsymboltau mathrmdGamma + int_Gamma^0 hatu llbracket boldsymboltaurrbracket mathrmdGamma^0\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma + int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0Integrating $ \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega $ by parts and applying divergence theorem without using numerical flux, then substitute in the equation to obtain a weak form. int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = int_Omega boldsymbolnabla (u) cdot boldsymboltau mathrmdOmega + int_Gamma llbracket hatu - urrbracket cdot boldsymboltau mathrmdGamma + int_Gamma^0 hatu - u llbracket boldsymboltaurrbracket mathrmdGamma^0\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma + int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0Substituting boldsymboltau = boldsymbolnabla (delta u)results in int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega + int_Gamma llbracket hatu - urrbracket cdot boldsymbolnabla (delta u) mathrmdGamma + int_Gamma^0 hatu - u llbracket boldsymbolnabla (delta u)rrbracket mathrmdGamma^0\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma + int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0Combining the two equations, int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega + int_Gamma llbracket hatu - urrbracket cdot boldsymbolnabla (delta u) mathrmdGamma + int_Gamma^0 hatu - u llbracket boldsymbolnabla (delta u)rrbracket mathrmdGamma^0 - int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma - int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0 = int_Omega delta u mathrmdOmegaThe numerical fluxes chosen for the interior penalty method are boldsymbolhatsigma = boldsymbolnabla (u) - alpha(llbracket urrbracket) on Gamma, hatu = u on the interfaces between elements Gamma^0 Gamma setminus partial Omega, and hatu = 0 on partial Omega. Such choice results in hatboldsymbolsigma = boldsymbolnabla (u) - alpha(llbracket urrbracket), llbracket haturrbracket = 0, hatu = u, llbracket hatboldsymbolsigmarrbracket = 0 and the equation becomes int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega - int_Gamma llbracket urrbracket cdot boldsymbolnabla (delta u) mathrmdGamma - int_Gamma llbracket delta urrbracket cdot boldsymbolnabla (u) - llbracket delta urrbracket cdot alpha(llbracket urrbracket) mathrmdGamma = int_Omega delta u mathrmdOmegaWhere alpha(llbracket urrbracket) = mu llbracket urrbracketWhere mu = eta h_e^-1, the weak form becomes int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega - int_Gamma llbracket u rrbracket cdot boldsymbolnabla (delta u) + llbracket delta u rrbracket cdot boldsymbolnabla (u) mathrmdGamma + int_Gamma fracetah_e llbracket urrbracket cdot llbracket delta urrbracket mathrmdGamma = int_Omega delta u mathrmdOmega","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Since partial Omega is constrained with both Dirichlet and Neumann boundary conditions the term int_partial Omega boldsymbolnabla (u) cdot boldsymboln delta u mathrmd Omega can be expressed as an integral over partial Omega_N, where partial Omega_N is the boundaries with only prescribed Neumann boundary condition, The resulting weak form is given given as follows: Find u in mathbbU such that","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":" int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega - int_Gamma^0 llbracket urrbracket cdot boldsymbolnabla (delta u) + llbracket delta urrbracket cdot boldsymbolnabla (u) mathrmdGamma^0 + int_Gamma^0 fracetah_e llbracket urrbracket cdot llbracket delta urrbracket mathrmdGamma^0 = int_Omega delta u mathrmdOmega + int_partial Omega_N (boldsymbolnabla (u) cdot boldsymboln) delta u mathrmd partial Omega_N","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"where h_e is the characteristic size (the diameter of the interface), and eta is a large enough positive number independent of h_e [3], delta u in mathbbT is a test function, and where mathbbU and mathbbT are suitable trial and test function sets, respectively. We use the value eta = (1 + O)^D, where O is the polynomial order and D the dimension, in this tutorial.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"More details on DG formulations for elliptic problems can be found in [4].","category":"page"},{"location":"tutorials/dg_heat_equation/#Commented-Program","page":"Discontinuous Galerkin heat equation","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"First we load Ferrite and other packages, and generate grid just like the heat equation tutorial","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"using Ferrite, SparseArrays\ndim = 2;\ngrid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim));\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We construct the topology information which is used later for generating the sparsity pattern for stiffness matrix.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"topology = ExclusiveTopology(grid);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Trial-and-test-functions","page":"Discontinuous Galerkin heat equation","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"CellValues, FacetValues, and InterfaceValues facilitate the process of evaluating values and gradients of test and trial functions (among other things). To define these we need to specify an interpolation space for the shape functions. We use DiscontinuousLagrange functions based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on the same reference element. We combine the interpolation and the quadrature rule to CellValues and InterfaceValues object. Note that InterfaceValues object contains two FacetValues objects which can be used individually.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"order = 1;\nip = DiscontinuousLagrange{RefQuadrilateral, order}();\nqr = QuadratureRule{RefQuadrilateral}(2);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"For FacetValues and InterfaceValues we use FacetQuadratureRule","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"facet_qr = FacetQuadratureRule{RefQuadrilateral}(2);\ncellvalues = CellValues(qr, ip);\nfacetvalues = FacetValues(facet_qr, ip);\ninterfacevalues = InterfaceValues(facet_qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Penalty-term-parameters","page":"Discontinuous Galerkin heat equation","title":"Penalty term parameters","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We define functions to calculate the diameter of a set of points, used to calculate the characteristic size h_e in the assembly routine.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"getdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2);\ngetdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :))));\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Degrees-of-freedom","page":"Discontinuous Galerkin heat equation","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Degrees of freedom distribution is handled using DofHandler as usual","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"However, when generating the sparsity pattern we need to pass the topology and the cross-element coupling matrix when we're using discontinuous interpolations. The cross-element coupling matrix is of size [1,1] in this case as we have only one field and one DofHandler.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"K = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1));\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Boundary-conditions","page":"Discontinuous Galerkin heat equation","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The Dirichlet boundary conditions are treated as usual by a ConstraintHandler.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"ch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> 1.0))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> -1.0))\nclose!(ch);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Furthermore, we define partial Omega_N as the union of the facet sets with Neumann boundary conditions for later use","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"∂Ωₙ = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Assembling-the-linear-system","page":"Discontinuous Galerkin heat equation","title":"Assembling the linear system","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Now we have all the pieces needed to assemble the linear system, K u = f. Assembling of the global system is done by looping over i) all the elements in order to compute the element contributions K_e and f_e, ii) all the interfaces to compute their contributions K_i, and iii) all the Neumann boundary facets to compute their contributions f_e. All these local contributions are then assembled into the appropriate place in the global K and f.","category":"page"},{"location":"tutorials/dg_heat_equation/#Local-assembly","page":"Discontinuous Galerkin heat equation","title":"Local assembly","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We define the functions","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"assemble_element! to compute the contributions K_e and f_e of volume integrals over an element using cellvalues.\nassemble_interface! to compute the contribution K_i of surface integrals over an interface using interfacevalues.\nassemble_boundary! to compute the contribution f_e of surface integrals over a boundary facet using FacetValues.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64)\n # Reset to 0\n fill!(Ki, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(iv)\n # Get the normal to facet A\n normal = getnormal(iv, q_point)\n # Get the quadrature weight\n dΓ = getdetJdV(iv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n δu_jump = shape_value_jump(iv, q_point, i) * (-normal)\n ∇δu_avg = shape_gradient_average(iv, q_point, i)\n # Loop over trial shape functions\n for j in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n u_jump = shape_value_jump(iv, q_point, j) * (-normal)\n ∇u_avg = shape_gradient_average(iv, q_point, j)\n # Add contribution to Ki\n Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ\n end\n end\n end\n return Ki\nend\n\nfunction assemble_boundary!(fe::Vector, fv::FacetValues)\n # Reset to 0\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(fv)\n # Get the normal to facet A\n normal = getnormal(fv, q_point)\n # Get the quadrature weight\n ∂Ω = getdetJdV(fv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(fv)\n δu = shape_value(fv, q_point, i)\n boundary_flux = normal[2]\n fe[i] = boundary_flux * δu * ∂Ω\n end\n end\n return fe\nend\nnothing # hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Global-assembly","page":"Discontinuous Galerkin heat equation","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We define the function assemble_global to loop over all elements and internal facets (interfaces), as well as the external facets involved in Neumann boundary conditions.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"function assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n Ki = zeros(n_basefuncs * 2, n_basefuncs * 2)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute volume integral contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n # Loop over all interfaces\n for ic in InterfaceIterator(dh)\n # Reinitialize interfacevalues for this interface\n reinit!(interfacevalues, ic)\n # Calculate the characteristic size hₑ as the face diameter\n interfacecoords = ∩(getcoordinates(ic)...)\n hₑ = getdiameter(interfacecoords)\n # Calculate μ\n μ = (1 + order)^dim / hₑ\n # Compute interface surface integrals contribution\n assemble_interface!(Ki, interfacevalues, μ)\n # Assemble Ki into K\n assemble!(assembler, interfacedofs(ic), Ki)\n end\n # Loop over domain boundaries with Neumann boundary conditions\n for fc in FacetIterator(dh, ∂Ωₙ)\n # Reinitialize facetvalues for this boundary facet\n reinit!(facetvalues, fc)\n # Compute boundary facet surface integrals contribution\n assemble_boundary!(fe, facetvalues)\n # Assemble fe into f\n assemble!(f, celldofs(fc), fe)\n end\n return K, f\nend\nK, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim);\nnothing # hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Solution-of-the-system","page":"Discontinuous Galerkin heat equation","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The solution of the system is independent of the discontinuous discretization and the application of constraints, linear solve, and exporting is done as usual.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"apply!(K, f, ch)\nu = K \\ f;\nVTKGridFile(\"dg_heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#References","page":"Discontinuous Galerkin heat equation","title":"References","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"L. Mu, J. Wang, Y. Wang and X. Ye. Interior penalty discontinuous Galerkin method on very general polygonal and polyhedral meshes. Journal of Computational and Applied Mathematics 255, 432–440 (2014).\n\n\n\nD. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.\n\n\n\n","category":"page"},{"location":"tutorials/dg_heat_equation/#heat_equation-DG-plain-program","page":"Discontinuous Galerkin heat equation","title":"Plain program","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Here follows a version of the program without any comments. The file is also available here: dg_heat_equation.jl.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"using Ferrite, SparseArrays\ndim = 2;\ngrid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim));\n\ntopology = ExclusiveTopology(grid);\n\norder = 1;\nip = DiscontinuousLagrange{RefQuadrilateral, order}();\nqr = QuadratureRule{RefQuadrilateral}(2);\n\nfacet_qr = FacetQuadratureRule{RefQuadrilateral}(2);\ncellvalues = CellValues(qr, ip);\nfacetvalues = FacetValues(facet_qr, ip);\ninterfacevalues = InterfaceValues(facet_qr, ip);\n\ngetdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2);\ngetdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :))));\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nK = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1));\n\nch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> 1.0))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> -1.0))\nclose!(ch);\n\n∂Ωₙ = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\n\nfunction assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64)\n # Reset to 0\n fill!(Ki, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(iv)\n # Get the normal to facet A\n normal = getnormal(iv, q_point)\n # Get the quadrature weight\n dΓ = getdetJdV(iv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n δu_jump = shape_value_jump(iv, q_point, i) * (-normal)\n ∇δu_avg = shape_gradient_average(iv, q_point, i)\n # Loop over trial shape functions\n for j in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n u_jump = shape_value_jump(iv, q_point, j) * (-normal)\n ∇u_avg = shape_gradient_average(iv, q_point, j)\n # Add contribution to Ki\n Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ\n end\n end\n end\n return Ki\nend\n\nfunction assemble_boundary!(fe::Vector, fv::FacetValues)\n # Reset to 0\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(fv)\n # Get the normal to facet A\n normal = getnormal(fv, q_point)\n # Get the quadrature weight\n ∂Ω = getdetJdV(fv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(fv)\n δu = shape_value(fv, q_point, i)\n boundary_flux = normal[2]\n fe[i] = boundary_flux * δu * ∂Ω\n end\n end\n return fe\nend\n\nfunction assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n Ki = zeros(n_basefuncs * 2, n_basefuncs * 2)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute volume integral contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n # Loop over all interfaces\n for ic in InterfaceIterator(dh)\n # Reinitialize interfacevalues for this interface\n reinit!(interfacevalues, ic)\n # Calculate the characteristic size hₑ as the face diameter\n interfacecoords = ∩(getcoordinates(ic)...)\n hₑ = getdiameter(interfacecoords)\n # Calculate μ\n μ = (1 + order)^dim / hₑ\n # Compute interface surface integrals contribution\n assemble_interface!(Ki, interfacevalues, μ)\n # Assemble Ki into K\n assemble!(assembler, interfacedofs(ic), Ki)\n end\n # Loop over domain boundaries with Neumann boundary conditions\n for fc in FacetIterator(dh, ∂Ωₙ)\n # Reinitialize facetvalues for this boundary facet\n reinit!(facetvalues, fc)\n # Compute boundary facet surface integrals contribution\n assemble_boundary!(fe, facetvalues)\n # Assemble fe into f\n assemble!(f, celldofs(fc), fe)\n end\n return K, f\nend\nK, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim);\n\napply!(K, f, ch)\nu = K \\ f;\nVTKGridFile(\"dg_heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend;","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"EditURL = \"../literate-tutorials/hyperelasticity.jl\"","category":"page"},{"location":"tutorials/hyperelasticity/#tutorial-hyperelasticity","page":"Hyperelasticity","title":"Hyperelasticity","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Keywords: hyperelasticity, finite strain, large deformations, Newton's method, conjugate gradient, automatic differentiation","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"(Image: hyperelasticity.png)","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Figure 1: Cube loaded in torsion modeled with a hyperelastic material model and finite strain.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: hyperelasticity.ipynb.","category":"page"},{"location":"tutorials/hyperelasticity/#Introduction","page":"Hyperelasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"In this example we will solve a problem in a finite strain setting using an hyperelastic material model. In order to compute the stress we will use automatic differentiation, to solve the non-linear system we use Newton's method, and for solving the Newton increment we use conjugate gradients.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The weak form is expressed in terms of the first Piola-Kirchoff stress mathbfP as follows: Find mathbfu in mathbbU such that","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"int_Omega nabla_mathbfX delta mathbfu mathbfP(mathbfu) mathrmdOmega =\nint_Omega delta mathbfu cdot mathbfb mathrmdOmega + int_Gamma_mathrmN\ndelta mathbfu cdot mathbft mathrmdGamma\nquad forall delta mathbfu in mathbbU^0","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where mathbfu is the unknown displacement field, mathbfb is the body force acting on the reference domain, mathbft is the traction acting on the Neumann part of the reference domain's boundary, and where mathbbU and mathbbU^0 are suitable trial and test sets. Omega denotes the reference (sometimes also called initial or material) domain. Gradients are defined with respect to the reference domain, here denoted with an mathbfX. Formally this is expressed as (nabla_mathbfX bullet)_ij = fracpartial(bullet)_ipartial X_j. Note that for large deformation problems it is also possible that gradients and integrals are defined on the deformed (sometimes also called current or spatial) domain, depending on the specific formulation.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The specific problem we will solve in this example is the cube from Figure 1: On one side we apply a rotation using Dirichlet boundary conditions, on the opposite side we fix the displacement with a homogeneous Dirichlet boundary condition, and on the remaining four sides we apply a traction in the normal direction of the surface. In addition, a body force is applied in one direction.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"In addition to Ferrite.jl and Tensors.jl, this examples uses TimerOutputs.jl for timing the program and print a summary at the end, ProgressMeter.jl for showing a simple progress bar, and IterativeSolvers.jl for solving the linear system using conjugate gradients.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers","category":"page"},{"location":"tutorials/hyperelasticity/#Hyperelastic-material-model","page":"Hyperelasticity","title":"Hyperelastic material model","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The stress can be derived from an energy potential, defined in terms of the right Cauchy-Green tensor mathbfC = mathbfF^mathrmT cdot mathbfF, where mathbfF = mathbfI + nabla_mathbfX mathbfu is the deformation gradient. We shall use the compressible neo-Hookean model from Wikipedia with the potential","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Psi(mathbfC) = underbracefracmu2 (I_1 - 3)_W(mathbfC) underbrace- mu ln(J) + fraclambda2 (J - 1)^2_U(J)","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where I_1 = mathrmtr(mathbfC) is the first invariant, J = sqrtdet(mathbfC) and mu and lambda material parameters.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"details: Extra details on compressible neo-Hookean formulations\nThe Neo-Hooke model is only a well defined terminology in the incompressible case. Thus, only W(mathbfC) specifies the neo-Hookean behavior, the volume penalty U(J) can vary in different formulations. In order to obtain a well-posed problem, it is crucial to choose a convex formulation of U(J). Other examples for U(J) can be found, e.g. in [1, Eq. (6.138)] beta^-2 (beta ln J + J^-beta -1)where [2, Eq. (2.37)] published a non-generalized version with beta=-2. This shows the possible variety of U(J) while all of them refer to compressible neo-Hookean models. Sometimes the modified first invariant overlineI_1=fracI_1I_3^13 is used in W(mathbfC) instead of I_1.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"From the potential we obtain the second Piola-Kirchoff stress mathbfS as","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"mathbfS = 2 fracpartial Psipartial mathbfC","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"and the tangent of mathbfS as","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"fracpartial mathbfSpartial mathbfC = 2 fracpartial^2 Psipartial mathbfC^2","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Finally, for the finite element problem we need mathbfP and fracpartial mathbfPpartial mathbfF, which can be obtained by using the following relations:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"beginalign*\nmathbfP = mathbfF cdot mathbfS\nfracpartial mathbfPpartial mathbfF = mathbfI barotimes mathbfS + 2 mathbfF barotimes mathbfI \nfracpartial mathbfSpartial mathbfC mathbfF^mathrmT barotimes mathbfI\nendalign*","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"
\n\nDerivation of $\\partial \\mathbf{P} / \\partial \\mathbf{F}$\n\n
","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Using the product rule, the chain rule, and the relations mathbfP = mathbfF cdot mathbfS and mathbfC = mathbfF^mathrmT cdot mathbfF, we obtain the following:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"beginaligned\nfracpartial mathbfPpartial mathbfF =\nfracpartial P_ijpartial F_kl =\nfracpartial (F_imS_mj)partial F_kl =\nfracpartial F_impartial F_klS_mj +\nF_imfracpartial S_mjpartial F_kl =\ndelta_ikdelta_ml S_mj +\nF_imfracpartial S_mjpartial C_nofracpartial C_nopartial F_kl =\ndelta_ikS_lj +\nF_imfracpartial S_mjpartial C_no\nfracpartial (F^mathrmT_npF_po)partial F_kl =\ndelta_ikS^mathrmT_jl +\nF_imdelta_jqfracpartial S_mqpartial C_no\nleft(\nfracpartial F^mathrmT_nppartial F_klF_po +\nF^mathrmT_npfracpartial F_popartial F_kl\nright) =\ndelta_ikS_jl +\nF_imdelta_jqfracpartial S_mqpartial C_no\n(delta_nl delta_pk F_po + F^mathrmT_npdelta_pk delta_ol) =\ndelta_ikS_lj +\nF_imdelta_jqfracpartial S_mqpartial C_no\n(F^mathrmT_ok delta_nl + F^mathrmT_nk delta_ol) =\ndelta_ikS_jl +\n2 F_imdelta_jq fracpartial S_mqpartial C_no\nF^mathrmT_nk delta_ol =\nmathbfIbarotimesmathbfS +\n2 mathbfFbarotimesmathbfI fracpartial mathbfSpartial mathbfC\n mathbfF^mathrmT barotimes mathbfI\nendaligned","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where we used the fact that mathbfS is symmetric (S_lj = S_jl) and that fracpartial mathbfSpartial mathbfC is minor symmetric (fracpartial S_mqpartial C_no = fracpartial S_mqpartial C_on).","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"
","category":"page"},{"location":"tutorials/hyperelasticity/#Implementation-of-material-model-using-automatic-differentiation","page":"Hyperelasticity","title":"Implementation of material model using automatic differentiation","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"We can implement the material model as follows, where we utilize automatic differentiation for the stress and the tangent, and thus only define the potential:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"struct NeoHooke\n μ::Float64\n λ::Float64\nend\n\nfunction Ψ(C, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(C)\n J = sqrt(det(C))\n return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2\nend\n\nfunction constitutive_driver(C, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all)\n S = 2.0 * ∂Ψ∂C\n ∂S∂C = 2.0 * ∂²Ψ∂C²\n return S, ∂S∂C\nend;\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/#Newton's-method","page":"Hyperelasticity","title":"Newton's method","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"As mentioned above, to deal with the non-linear weak form we first linearize the problem such that we can apply Newton's method, and then apply the FEM to discretize the problem. Skipping a detailed derivation, Newton's method can be expressed as: Given some initial guess for the degrees of freedom underlineu^0, find a sequence underlineu^k by iterating","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"underlineu^k+1 = underlineu^k - Delta underlineu^k","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"until some termination condition has been met. Therein we determine Delta underlineu^k from the linearized problem","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"underlineunderlineK(underlineu^k) Delta underlineu^k = underlineg(underlineu^k)","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where the global residual, underlineg, and the Jacobi matrix, underlineunderlineK = fracpartial underlinegpartial underlineu, are evaluated at the current guess underlineu^k. The entries of underlineg are given by","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"(underlineg)_i = int_Omega nabla_mathbfX delta mathbfu_i \nmathbfP mathrmd Omega - int_Omega delta mathbfu_i cdot mathbfb \nmathrmd Omega - int_Gamma_mathrmN delta mathbfu_i cdot mathbft\nmathrmdGamma","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"and the entries of underlineunderlineK are given by","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"(underlineunderlineK)_ij = int_Omega nabla_mathbfX delta\nmathbfu_i fracpartial mathbfPpartial mathbfF nabla_mathbfX\ndelta mathbfu_j mathrmd Omega","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"A detailed derivation can be found in every continuum mechanics book, which has a chapter about finite elasticity theory. We used \"Nonlinear solid mechanics: a continuum approach for engineering science.\" by Holzapfel [1], Chapter 8 as a reference.","category":"page"},{"location":"tutorials/hyperelasticity/#Finite-element-assembly","page":"Hyperelasticity","title":"Finite element assembly","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The element routine for assembling the residual and tangent stiffness is implemented as usual, with loops over quadrature points and shape functions:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n # Reinitialize cell values, and reset output arrays\n reinit!(cv, cell)\n fill!(ke, 0.0)\n fill!(ge, 0.0)\n\n b = Vec{3}((0.0, -0.5, 0.0)) # Body force\n tn = 0.1 # Traction (to be scaled with surface normal)\n ndofs = getnbasefunctions(cv)\n\n for qp in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, qp)\n # Compute deformation gradient F and right Cauchy-Green tensor C\n ∇u = function_gradient(cv, qp, ue)\n F = one(∇u) + ∇u\n C = tdot(F) # F' ⋅ F\n # Compute stress and tangent\n S, ∂S∂C = constitutive_driver(C, mp)\n P = F ⋅ S\n I = one(S)\n ∂P∂F = otimesu(I, S) + 2 * otimesu(F, I) ⊡ ∂S∂C ⊡ otimesu(F', I)\n\n # Loop over test functions\n for i in 1:ndofs\n # Test function and gradient\n δui = shape_value(cv, qp, i)\n ∇δui = shape_gradient(cv, qp, i)\n # Add contribution to the residual from this test function\n ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ\n\n ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation\n for j in 1:ndofs\n ∇δuj = shape_gradient(cv, qp, j)\n # Add contribution to the tangent\n ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ\n end\n end\n end\n\n # Surface integral for the traction\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) in ΓN\n reinit!(fv, cell, facet)\n for q_point in 1:getnquadpoints(fv)\n t = tn * getnormal(fv, q_point)\n dΓ = getdetJdV(fv, q_point)\n for i in 1:ndofs\n δui = shape_value(fv, q_point, i)\n ge[i] -= (δui ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Assembling global residual and tangent is also done in the usual way, just looping over the elements, call the element routine and assemble in the the global matrix K and residual g.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n n = ndofs_per_cell(dh)\n ke = zeros(n, n)\n ge = zeros(n)\n\n # start_assemble resets K and g\n assembler = start_assemble(K, g)\n\n # Loop over all cells in the grid\n return @timeit \"assemble\" for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n ue = u[global_dofs] # element dofs\n @timeit \"element assemble\" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n assemble!(assembler, global_dofs, ke, ge)\n end\nend;\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Finally, we define a main function which sets up everything and then performs Newton iterations until convergence.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"function solve()\n reset_timer!()\n\n # Generate a grid\n N = 10\n L = 1.0\n left = zero(Vec{3})\n right = L * ones(Vec{3})\n grid = generate_grid(Tetrahedron, (N, N, N), left, right)\n\n # Material parameters\n E = 10.0\n ν = 0.3\n μ = E / (2(1 + ν))\n λ = (E * ν) / ((1 + ν) * (1 - 2ν))\n mp = NeoHooke(μ, λ)\n\n # Finite element base\n ip = Lagrange{RefTetrahedron, 1}()^3\n qr = QuadratureRule{RefTetrahedron}(1)\n qr_facet = FacetQuadratureRule{RefTetrahedron}(1)\n cv = CellValues(qr, ip)\n fv = FacetValues(qr_facet, ip)\n\n # DofHandler\n dh = DofHandler(grid)\n add!(dh, :u, ip) # Add a displacement field\n close!(dh)\n\n function rotation(X, t)\n θ = pi / 3 # 60°\n x, y, z = X\n return t * Vec{3}(\n (\n 0.0,\n L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ),\n L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ),\n )\n )\n end\n\n dbcs = ConstraintHandler(dh)\n # Add a homogeneous boundary condition on the \"clamped\" edge\n dbc = Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3])\n add!(dbcs, dbc)\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> rotation(x, t), [1, 2, 3])\n add!(dbcs, dbc)\n close!(dbcs)\n t = 0.5\n Ferrite.update!(dbcs, t)\n\n # Neumann part of the boundary\n ΓN = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n getfacetset(grid, \"front\"),\n getfacetset(grid, \"back\"),\n )\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n un = zeros(_ndofs) # previous solution vector\n u = zeros(_ndofs)\n Δu = zeros(_ndofs)\n ΔΔu = zeros(_ndofs)\n apply!(un, dbcs)\n\n # Create sparse matrix and residual vector\n K = allocate_matrix(dh)\n g = zeros(_ndofs)\n\n # Perform Newton iterations\n newton_itr = -1\n NEWTON_TOL = 1.0e-8\n NEWTON_MAXITER = 30\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving:\")\n\n while true\n newton_itr += 1\n # Construct the current guess\n u .= un .+ Δu\n # Compute residual and tangent for current guess\n assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n # Apply boundary conditions\n apply_zero!(K, g, dbcs)\n # Compute the residual norm and compare with tolerance\n normg = norm(g)\n ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)])\n if normg < NEWTON_TOL\n break\n elseif newton_itr > NEWTON_MAXITER\n error(\"Reached maximum Newton iterations, aborting\")\n end\n\n # Compute increment using conjugate gradients\n @timeit \"linear solve\" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000)\n\n apply_zero!(ΔΔu, dbcs)\n Δu .-= ΔΔu\n end\n\n # Save the solution\n @timeit \"export\" begin\n VTKGridFile(\"hyperelasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n end\n end\n\n print_timer(title = \"Analysis with $(getncells(grid)) elements\", linechars = :ascii)\n return u\nend","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Run the simulation","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"u = solve();\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/#Plain-program","page":"Hyperelasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Here follows a version of the program without any comments. The file is also available here: hyperelasticity.jl.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers\n\nstruct NeoHooke\n μ::Float64\n λ::Float64\nend\n\nfunction Ψ(C, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(C)\n J = sqrt(det(C))\n return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2\nend\n\nfunction constitutive_driver(C, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all)\n S = 2.0 * ∂Ψ∂C\n ∂S∂C = 2.0 * ∂²Ψ∂C²\n return S, ∂S∂C\nend;\n\nfunction assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n # Reinitialize cell values, and reset output arrays\n reinit!(cv, cell)\n fill!(ke, 0.0)\n fill!(ge, 0.0)\n\n b = Vec{3}((0.0, -0.5, 0.0)) # Body force\n tn = 0.1 # Traction (to be scaled with surface normal)\n ndofs = getnbasefunctions(cv)\n\n for qp in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, qp)\n # Compute deformation gradient F and right Cauchy-Green tensor C\n ∇u = function_gradient(cv, qp, ue)\n F = one(∇u) + ∇u\n C = tdot(F) # F' ⋅ F\n # Compute stress and tangent\n S, ∂S∂C = constitutive_driver(C, mp)\n P = F ⋅ S\n I = one(S)\n ∂P∂F = otimesu(I, S) + 2 * otimesu(F, I) ⊡ ∂S∂C ⊡ otimesu(F', I)\n\n # Loop over test functions\n for i in 1:ndofs\n # Test function and gradient\n δui = shape_value(cv, qp, i)\n ∇δui = shape_gradient(cv, qp, i)\n # Add contribution to the residual from this test function\n ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ\n\n ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation\n for j in 1:ndofs\n ∇δuj = shape_gradient(cv, qp, j)\n # Add contribution to the tangent\n ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ\n end\n end\n end\n\n # Surface integral for the traction\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) in ΓN\n reinit!(fv, cell, facet)\n for q_point in 1:getnquadpoints(fv)\n t = tn * getnormal(fv, q_point)\n dΓ = getdetJdV(fv, q_point)\n for i in 1:ndofs\n δui = shape_value(fv, q_point, i)\n ge[i] -= (δui ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend;\n\nfunction assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n n = ndofs_per_cell(dh)\n ke = zeros(n, n)\n ge = zeros(n)\n\n # start_assemble resets K and g\n assembler = start_assemble(K, g)\n\n # Loop over all cells in the grid\n return @timeit \"assemble\" for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n ue = u[global_dofs] # element dofs\n @timeit \"element assemble\" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n assemble!(assembler, global_dofs, ke, ge)\n end\nend;\n\nfunction solve()\n reset_timer!()\n\n # Generate a grid\n N = 10\n L = 1.0\n left = zero(Vec{3})\n right = L * ones(Vec{3})\n grid = generate_grid(Tetrahedron, (N, N, N), left, right)\n\n # Material parameters\n E = 10.0\n ν = 0.3\n μ = E / (2(1 + ν))\n λ = (E * ν) / ((1 + ν) * (1 - 2ν))\n mp = NeoHooke(μ, λ)\n\n # Finite element base\n ip = Lagrange{RefTetrahedron, 1}()^3\n qr = QuadratureRule{RefTetrahedron}(1)\n qr_facet = FacetQuadratureRule{RefTetrahedron}(1)\n cv = CellValues(qr, ip)\n fv = FacetValues(qr_facet, ip)\n\n # DofHandler\n dh = DofHandler(grid)\n add!(dh, :u, ip) # Add a displacement field\n close!(dh)\n\n function rotation(X, t)\n θ = pi / 3 # 60°\n x, y, z = X\n return t * Vec{3}(\n (\n 0.0,\n L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ),\n L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ),\n )\n )\n end\n\n dbcs = ConstraintHandler(dh)\n # Add a homogeneous boundary condition on the \"clamped\" edge\n dbc = Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3])\n add!(dbcs, dbc)\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> rotation(x, t), [1, 2, 3])\n add!(dbcs, dbc)\n close!(dbcs)\n t = 0.5\n Ferrite.update!(dbcs, t)\n\n # Neumann part of the boundary\n ΓN = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n getfacetset(grid, \"front\"),\n getfacetset(grid, \"back\"),\n )\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n un = zeros(_ndofs) # previous solution vector\n u = zeros(_ndofs)\n Δu = zeros(_ndofs)\n ΔΔu = zeros(_ndofs)\n apply!(un, dbcs)\n\n # Create sparse matrix and residual vector\n K = allocate_matrix(dh)\n g = zeros(_ndofs)\n\n # Perform Newton iterations\n newton_itr = -1\n NEWTON_TOL = 1.0e-8\n NEWTON_MAXITER = 30\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving:\")\n\n while true\n newton_itr += 1\n # Construct the current guess\n u .= un .+ Δu\n # Compute residual and tangent for current guess\n assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n # Apply boundary conditions\n apply_zero!(K, g, dbcs)\n # Compute the residual norm and compare with tolerance\n normg = norm(g)\n ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)])\n if normg < NEWTON_TOL\n break\n elseif newton_itr > NEWTON_MAXITER\n error(\"Reached maximum Newton iterations, aborting\")\n end\n\n # Compute increment using conjugate gradients\n @timeit \"linear solve\" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000)\n\n apply_zero!(ΔΔu, dbcs)\n Δu .-= ΔΔu\n end\n\n # Save the solution\n @timeit \"export\" begin\n VTKGridFile(\"hyperelasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n end\n end\n\n print_timer(title = \"Analysis with $(getncells(grid)) elements\", linechars = :ascii)\n return u\nend\n\nu = solve();","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"EditURL = \"../literate-gallery/landau.jl\"","category":"page"},{"location":"gallery/landau/#tutorial-ginzburg-landau-minimizer","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"(Image: landau_orig.png)","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"Original","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"(Image: landau_opt.png)","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"Optimized","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"In this example a basic Ginzburg-Landau model is solved. This example gives an idea of how the API together with ForwardDiff can be leveraged to performantly solve non standard problems on a FEM grid. A large portion of the code is there only for performance reasons, but since this usually really matters and is what takes the most time to optimize, it is included.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"The key to using a method like this for minimizing a free energy function directly, rather than the weak form, as is usually done with FEM, is to split up the gradient and Hessian calculations. This means that they are performed for each cell separately instead of for the grid as a whole.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"using ForwardDiff\nimport ForwardDiff: GradientConfig, HessianConfig, Chunk\nusing Ferrite\nusing Optim, LineSearches\nusing SparseArrays\nusing Tensors\nusing Base.Threads","category":"page"},{"location":"gallery/landau/#Energy-terms","page":"Ginzburg-Landau model energy minimization","title":"Energy terms","text":"","category":"section"},{"location":"gallery/landau/#4th-order-Landau-free-energy","page":"Ginzburg-Landau model energy minimization","title":"4th order Landau free energy","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function Fl(P::Vec{3, T}, α::Vec{3}) where {T}\n P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2))\n return (\n α[1] * sum(P2) +\n α[2] * (P[1]^4 + P[2]^4 + P[3]^4)\n ) +\n α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3])\nend","category":"page"},{"location":"gallery/landau/#Ginzburg-free-energy","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg free energy","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"@inline Fg(∇P, G) = 0.5(∇P ⊡ G) ⊡ ∇P","category":"page"},{"location":"gallery/landau/#GL-free-energy","page":"Ginzburg-Landau model energy minimization","title":"GL free energy","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"F(P, ∇P, params) = Fl(P, params.α) + Fg(∇P, params.G)","category":"page"},{"location":"gallery/landau/#Parameters-that-characterize-the-model","page":"Ginzburg-Landau model energy minimization","title":"Parameters that characterize the model","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"struct ModelParams{V, T}\n α::V\n G::T\nend","category":"page"},{"location":"gallery/landau/#ThreadCache","page":"Ginzburg-Landau model energy minimization","title":"ThreadCache","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This holds the values that each thread will use during the assembly.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"struct ThreadCache{CV, T, DIM, F <: Function, GC <: GradientConfig, HC <: HessianConfig}\n cvP::CV\n element_indices::Vector{Int}\n element_dofs::Vector{T}\n element_gradient::Vector{T}\n element_hessian::Matrix{T}\n element_coords::Vector{Vec{DIM, T}}\n element_potential::F\n gradconf::GC\n hessconf::HC\nend\nfunction ThreadCache(dpc::Int, nodespercell, cvP::CellValues, modelparams, elpotential)\n element_indices = zeros(Int, dpc)\n element_dofs = zeros(dpc)\n element_gradient = zeros(dpc)\n element_hessian = zeros(dpc, dpc)\n element_coords = zeros(Vec{3, Float64}, nodespercell)\n potfunc = x -> elpotential(x, cvP, modelparams)\n gradconf = GradientConfig(potfunc, zeros(dpc), Chunk{12}())\n hessconf = HessianConfig(potfunc, zeros(dpc), Chunk{4}())\n return ThreadCache(cvP, element_indices, element_dofs, element_gradient, element_hessian, element_coords, potfunc, gradconf, hessconf)\nend","category":"page"},{"location":"gallery/landau/#The-Model","page":"Ginzburg-Landau model energy minimization","title":"The Model","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"everything is combined into a model.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"mutable struct LandauModel{T, DH <: DofHandler, CH <: ConstraintHandler, TC <: ThreadCache}\n dofs::Vector{T}\n dofhandler::DH\n boundaryconds::CH\n threadindices::Vector{Vector{Int}}\n threadcaches::Vector{TC}\nend\n\nfunction LandauModel(α, G, gridsize, left::Vec{DIM, T}, right::Vec{DIM, T}, elpotential) where {DIM, T}\n grid = generate_grid(Tetrahedron, gridsize, left, right)\n threadindices = Ferrite.create_coloring(grid)\n\n qr = QuadratureRule{RefTetrahedron}(2)\n ipP = Lagrange{RefTetrahedron, 1}()^3\n cvP = CellValues(qr, ipP)\n\n dofhandler = DofHandler(grid)\n add!(dofhandler, :P, ipP)\n close!(dofhandler)\n\n dofvector = zeros(ndofs(dofhandler))\n startingconditions!(dofvector, dofhandler)\n boundaryconds = ConstraintHandler(dofhandler)\n #boundary conditions can be added but aren't necessary for optimization\n #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, \"left\"), (x, t) -> [0.0,0.0,0.53], [1,2,3]))\n #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, \"right\"), (x, t) -> [0.0,0.0,-0.53], [1,2,3]))\n close!(boundaryconds)\n update!(boundaryconds, 0.0)\n\n apply!(dofvector, boundaryconds)\n\n hessian = allocate_matrix(dofhandler)\n dpc = ndofs_per_cell(dofhandler)\n cpc = length(grid.cells[1].nodes)\n caches = [ThreadCache(dpc, cpc, copy(cvP), ModelParams(α, G), elpotential) for t in 1:nthreads()]\n return LandauModel(dofvector, dofhandler, boundaryconds, threadindices, caches)\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"utility to quickly save a model","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function save_landau(path, model, dofs = model.dofs)\n return VTKGridFile(path, model.dofhandler) do vtk\n write_solution(vtk, model.dofhandler, dofs)\n end\nend","category":"page"},{"location":"gallery/landau/#Assembly","page":"Ginzburg-Landau model energy minimization","title":"Assembly","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This macro defines most of the assembly step, since the structure is the same for the energy, gradient and Hessian calculations.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"macro assemble!(innerbody)\n return esc(\n quote\n dofhandler = model.dofhandler\n for indices in model.threadindices\n @threads for i in indices\n cache = model.threadcaches[threadid()]\n eldofs = cache.element_dofs\n nodeids = dofhandler.grid.cells[i].nodes\n for j in 1:length(cache.element_coords)\n cache.element_coords[j] = dofhandler.grid.nodes[nodeids[j]].x\n end\n reinit!(cache.cvP, cache.element_coords)\n\n celldofs!(cache.element_indices, dofhandler, i)\n for j in 1:length(cache.element_dofs)\n eldofs[j] = dofvector[cache.element_indices[j]]\n end\n $innerbody\n end\n end\n end\n )\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This calculates the total energy calculation of the grid","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function F(dofvector::Vector{T}, model) where {T}\n outs = fill(zero(T), nthreads())\n @assemble! begin\n outs[threadid()] += cache.element_potential(eldofs)\n end\n return sum(outs)\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"The gradient calculation for each dof","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n fill!(∇f, zero(T))\n return @assemble! begin\n ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)\n @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient)\n end\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"The Hessian calculation for the whole grid","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n assemblers = [start_assemble(∇²f) for t in 1:nthreads()]\n return @assemble! begin\n ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)\n @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian)\n end\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"We can also calculate all things in one go!","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function calcall(∇²f::SparseMatrixCSC, ∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n outs = fill(zero(T), nthreads())\n fill!(∇f, zero(T))\n assemblers = [start_assemble(∇²f, ∇f) for t in 1:nthreads()]\n @assemble! begin\n outs[threadid()] += cache.element_potential(eldofs)\n ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)\n ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)\n @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_gradient, cache.element_hessian)\n end\n return sum(outs)\nend","category":"page"},{"location":"gallery/landau/#Minimization","page":"Ginzburg-Landau model energy minimization","title":"Minimization","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"Now everything can be combined to minimize the energy, and find the equilibrium configuration.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function minimize!(model; kwargs...)\n dh = model.dofhandler\n dofs = model.dofs\n ∇f = fill(0.0, length(dofs))\n ∇²f = allocate_matrix(dh)\n function g!(storage, x)\n ∇F!(storage, x, model)\n return apply_zero!(storage, model.boundaryconds)\n end\n function h!(storage, x)\n return ∇²F!(storage, x, model)\n #apply!(storage, model.boundaryconds)\n end\n f(x) = F(x, model)\n\n od = TwiceDifferentiable(f, g!, h!, model.dofs, 0.0, ∇f, ∇²f)\n\n # this way of minimizing is only beneficial when the initial guess is completely off,\n # then a quick couple of ConjuageGradient steps brings us easily closer to the minimum.\n # res = optimize(od, model.dofs, ConjugateGradient(linesearch=BackTracking()), Optim.Options(show_trace=true, show_every=1, g_tol=1e-20, iterations=10))\n # model.dofs .= res.minimizer\n # to get the final convergence, Newton's method is more ideal since the energy landscape should be almost parabolic\n ##+\n res = optimize(od, model.dofs, Newton(linesearch = BackTracking()), Optim.Options(show_trace = true, show_every = 1, g_tol = 1.0e-20))\n model.dofs .= res.minimizer\n return res\nend","category":"page"},{"location":"gallery/landau/#Testing-it","page":"Ginzburg-Landau model energy minimization","title":"Testing it","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This calculates the contribution of each element to the total energy, it is also the function that will be put through ForwardDiff for the gradient and Hessian.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function element_potential(eldofs::AbstractVector{T}, cvP, params) where {T}\n energy = zero(T)\n for qp in 1:getnquadpoints(cvP)\n P = function_value(cvP, qp, eldofs)\n ∇P = function_gradient(cvP, qp, eldofs)\n energy += F(P, ∇P, params) * getdetJdV(cvP, qp)\n end\n return energy\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"now we define some starting conditions","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function startingconditions!(dofvector, dofhandler)\n for cell in CellIterator(dofhandler)\n globaldofs = celldofs(cell)\n it = 1\n for i in 1:3:length(globaldofs)\n dofvector[globaldofs[i]] = -2.0\n dofvector[globaldofs[i + 1]] = 2.0\n dofvector[globaldofs[i + 2]] = -2.0tanh(cell.coords[it][1] / 20)\n it += 1\n end\n end\n return\nend\n\nδ(i, j) = i == j ? one(i) : zero(i)\nV2T(p11, p12, p44) = Tensor{4, 3}((i, j, k, l) -> p11 * δ(i, j) * δ(k, l) * δ(i, k) + p12 * δ(i, j) * δ(k, l) * (1 - δ(i, k)) + p44 * δ(i, k) * δ(j, l) * (1 - δ(i, j)))\n\nG = V2T(1.0e2, 0.0, 1.0e2)\nα = Vec{3}((-1.0, 1.0, 1.0))\nleft = Vec{3}((-75.0, -25.0, -2.0))\nright = Vec{3}((75.0, 25.0, 2.0))\nmodel = LandauModel(α, G, (50, 50, 2), left, right, element_potential)\n\nsave_landau(\"landauorig\", model)\n@time minimize!(model)\nsave_landau(\"landaufinal\", model)","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"as we can see this runs very quickly even for relatively large gridsizes. The key to get high performance like this is to minimize the allocations inside the threaded loops, ideally to 0.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"","page":"Home","title":"Home","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"#Ferrite.jl","page":"Home","title":"Ferrite.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for Ferrite.jl! Ferrite is a finite element toolbox that provides functionalities to implement finite element analysis in Julia. The aim is to be i) general, ii) performant, and iii) to keep mathematical abstractions.","category":"page"},{"location":"","page":"Home","title":"Home","text":"note: Upgrading code from version 0.3.x to version 1.0\nFerrite version 1.0 contains a number of breaking changes compared to version 0.3.x. The Changelog documents all changes and there is also a section specifically for Upgrading code from Ferrite 0.3 to 1.0.","category":"page"},{"location":"","page":"Home","title":"Home","text":"note: Note\nPlease help improve this documentation – if something confuses you, chances are you're not alone. It's easy to do as you read along: just click on the \"Edit on GitHub\" link at the top of each page, and then edit the files directly in your browser. Your changes will be vetted by developers before becoming permanent, so don't worry about whether you might say something wrong. See also Contributing to Ferrite for more details.","category":"page"},{"location":"#How-the-documentation-is-organized","page":"Home","title":"How the documentation is organized","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This high level view of the documentation structure will help you find what you are looking for. The document is organized as follows[1]:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tutorials are thoroughly documented examples which guides you through the process of solving partial differential equations using Ferrite.\nTopic guides contains more in-depth explanations and discussions about finite element programming concepts and ideas, and specifically how these are realized in Ferrite.\nReference contains the technical API reference of functions and methods (e.g. the documentation strings).\nHow-to guides will guide you through the steps involved in addressing common tasks and use-cases. These usually build on top of the tutorials and thus assume basic knowledge of how Ferrite works.","category":"page"},{"location":"","page":"Home","title":"Home","text":"[1]: The organization of the document follows the Diátaxis Framework.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The four sections above form the main user-facing parts of the documentation. In addition, the document also contain the following sections:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Code gallery contain user contributed example programs showcasing what can be done with Ferrite.\nChangelog contain release notes and information about how to upgrade between releases.\nDeveloper documentation contain documentation of Ferrite internal code and is mainly targeted at developers of Ferrite.","category":"page"},{"location":"#Getting-started","page":"Home","title":"Getting started","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"As a new user of Ferrite it is suggested to start working with the tutorials before using Ferrite to tackle the specific equation you ultimately want to solve. The tutorials start with explaining the basic concepts and then increase in complexity. Understanding the first tutorial program, solving the heat equation, is essential in order to understand how Ferrite works. Already this rather simple program discusses many of the important concepts. See the tutorials overview for suggestion on how to progress to more advanced usage.","category":"page"},{"location":"#Getting-help","page":"Home","title":"Getting help","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"If you have questions about Ferrite it is suggested to use the #ferrite-fem channel on the Julia Slack, or the #Ferrite.jl stream on Zulip. Alternatively you can use the discussion forum on the GitHub repository.","category":"page"},{"location":"#Installation","page":"Home","title":"Installation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"To use Ferrite you first need to install Julia, see https://julialang.org/ for details. Installing Ferrite can then be done from the Pkg REPL; press ] at the julia> promp to enter pkg> mode:","category":"page"},{"location":"","page":"Home","title":"Home","text":"pkg> add Ferrite","category":"page"},{"location":"","page":"Home","title":"Home","text":"This will install Ferrite and all necessary dependencies. Press backspace to get back to the julia> prompt. (See the documentation for Pkg, Julia's package manager, for more help regarding package installation and project management.)","category":"page"},{"location":"","page":"Home","title":"Home","text":"Finally, to load Ferrite, use","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Ferrite","category":"page"},{"location":"","page":"Home","title":"Home","text":"You are now all set to start using Ferrite!","category":"page"},{"location":"#Contributing-to-Ferrite","page":"Home","title":"Contributing to Ferrite","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Ferrite is still under active development. If you find a bug, or have ideas for improvements, you are encouraged to interact with the developers on the Ferrite GitHub repository. There is also a thorough contributor guide which can be found in CONTRIBUTING.md.","category":"page"},{"location":"devdocs/assembly/#devdocs-assembly","page":"Assembly","title":"Assembly","text":"","category":"section"},{"location":"devdocs/assembly/#Type-definitions","page":"Assembly","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/assembly/","page":"Assembly","title":"Assembly","text":"Ferrite.COOAssembler\nFerrite.CSCAssembler\nFerrite.SymmetricCSCAssembler","category":"page"},{"location":"devdocs/assembly/#Ferrite.COOAssembler","page":"Assembly","title":"Ferrite.COOAssembler","text":"struct COOAssembler{Tv, Ti}\n\nThis assembler creates a COO (coordinate format) representation of a sparse matrix during assembly and converts it into a SparseMatrixCSC{Tv, Ti} on finalization.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/assembly/#Ferrite.CSCAssembler","page":"Assembly","title":"Ferrite.CSCAssembler","text":"Assembler for sparse matrix with CSC storage type.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/assembly/#Ferrite.SymmetricCSCAssembler","page":"Assembly","title":"Ferrite.SymmetricCSCAssembler","text":"Assembler for symmetric sparse matrix with CSC storage type.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/assembly/#Utility-functions","page":"Assembly","title":"Utility functions","text":"","category":"section"},{"location":"devdocs/assembly/","page":"Assembly","title":"Assembly","text":"Ferrite.matrix_handle\nFerrite.vector_handle\nFerrite._sortdofs_for_assembly!\nFerrite.sortperm2!","category":"page"},{"location":"devdocs/assembly/#Ferrite.matrix_handle","page":"Assembly","title":"Ferrite.matrix_handle","text":"matrix_handle(a::AbstractAssembler)\nvector_handle(a::AbstractAssembler)\n\nReturn a reference to the underlying matrix/vector of the assembler used during assembly operations.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/assembly/#Ferrite.vector_handle","page":"Assembly","title":"Ferrite.vector_handle","text":"matrix_handle(a::AbstractAssembler)\nvector_handle(a::AbstractAssembler)\n\nReturn a reference to the underlying matrix/vector of the assembler used during assembly operations.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/assembly/#Ferrite._sortdofs_for_assembly!","page":"Assembly","title":"Ferrite._sortdofs_for_assembly!","text":"_sortdofs_for_assembly!(permutation::Vector{Int}, sorteddofs::Vector{Int}, dofs::AbstractVector)\n\nSorts the dofs into a separate buffer and returns it together with a permutation vector.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/assembly/#Ferrite.sortperm2!","page":"Assembly","title":"Ferrite.sortperm2!","text":"sortperm2!(data::AbstractVector, permutation::AbstractVector)\n\nSort the input vector inplace and compute the corresponding permutation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#devdocs-interpolations","page":"Interpolations","title":"Interpolations","text":"","category":"section"},{"location":"devdocs/interpolations/#Type-definitions","page":"Interpolations","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Interpolations are subtypes of Interpolation{shape, order}, i.e. they are parametrized by the reference element and its characteristic order.","category":"page"},{"location":"devdocs/interpolations/#Fallback-methods-applicable-for-all-subtypes-of-Interpolation","page":"Interpolations","title":"Fallback methods applicable for all subtypes of Interpolation","text":"","category":"section"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Ferrite.getrefshape(::Interpolation)\nFerrite.getorder(::Interpolation)\nFerrite.reference_shape_gradient(::Interpolation, ::Vec, ::Int)\nFerrite.reference_shape_gradient_and_value(::Interpolation, ::Vec, ::Int)\nFerrite.reference_shape_hessian_gradient_and_value(::Interpolation, ::Vec, ::Int)\nFerrite.boundarydof_indices\nFerrite.dirichlet_boundarydof_indices\nFerrite.reference_shape_values!\nFerrite.reference_shape_gradients!\nFerrite.reference_shape_gradients_and_values!\nFerrite.reference_shape_hessians_gradients_and_values!","category":"page"},{"location":"devdocs/interpolations/#Ferrite.getrefshape-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.getrefshape","text":"Ferrite.getrefshape(::Interpolation)::AbstractRefShape\n\nReturn the reference element shape of the interpolation.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.getorder-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.getorder","text":"Ferrite.getorder(::Interpolation)\n\nReturn order of the interpolation.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradient-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_gradient","text":"reference_shape_gradient(ip::Interpolation, ξ::Vec, i::Int)\n\nEvaluate the gradient of the ith shape function of the interpolation ip in reference coordinate ξ.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradient_and_value-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_gradient_and_value","text":"reference_shape_gradient_and_value(ip::Interpolation, ξ::Vec, i::Int)\n\nOptimized version combining the evaluation Ferrite.reference_shape_value(::Interpolation) and Ferrite.reference_shape_gradient(::Interpolation).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_hessian_gradient_and_value-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_hessian_gradient_and_value","text":"reference_shape_hessian_gradient_and_value(ip::Interpolation, ξ::Vec, i::Int)\n\nOptimized version combining the evaluation Ferrite.reference_shape_value(::Interpolation), Ferrite.reference_shape_gradient(::Interpolation), and the gradient of the latter.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.boundarydof_indices","page":"Interpolations","title":"Ferrite.boundarydof_indices","text":"boundarydof_indices(::Type{<:BoundaryIndex})\n\nHelper function to generically dispatch on the correct dof sets of a boundary entity.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_boundarydof_indices","page":"Interpolations","title":"Ferrite.dirichlet_boundarydof_indices","text":"dirichlet_boundarydof_indices(::Type{<:BoundaryIndex})\n\nHelper function to generically dispatch on the correct dof sets of a boundary entity. Used internally in ConstraintHandler and defaults to boundarydof_indices(ip::Interpolation) for continuous interpolation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_values!","page":"Interpolations","title":"Ferrite.reference_shape_values!","text":"reference_shape_values!(values::AbstractArray{T}, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape functions of ip at once at the reference point ξ and store them in values.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradients!","page":"Interpolations","title":"Ferrite.reference_shape_gradients!","text":"reference_shape_gradients!(gradients::AbstractArray, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape function gradients of ip at once at the reference point ξ and store them in gradients.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradients_and_values!","page":"Interpolations","title":"Ferrite.reference_shape_gradients_and_values!","text":"reference_shape_gradients_and_values!(gradients::AbstractArray, values::AbstractArray, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape function gradients and values of ip at once at the reference point ξ and store them in values.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_hessians_gradients_and_values!","page":"Interpolations","title":"Ferrite.reference_shape_hessians_gradients_and_values!","text":"reference_shape_hessians_gradients_and_values!(hessians::AbstractVector, gradients::AbstractVector, values::AbstractVector, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape function hessians, gradients and values of ip at once at the reference point ξ and store them in hessians, gradients, and values.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Required-methods-to-implement-for-all-subtypes-of-Interpolation-to-define-a-new-finite-element","page":"Interpolations","title":"Required methods to implement for all subtypes of Interpolation to define a new finite element","text":"","category":"section"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Depending on the dimension of the reference element the following functions have to be implemented","category":"page"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Ferrite.reference_shape_value(::Interpolation, ::Vec, ::Int)\nFerrite.vertexdof_indices(::Interpolation)\nFerrite.dirichlet_vertexdof_indices(::Interpolation)\nFerrite.facedof_indices(::Interpolation)\nFerrite.dirichlet_facedof_indices(::Interpolation)\nFerrite.facedof_interior_indices(::Interpolation)\nFerrite.edgedof_indices(::Interpolation)\nFerrite.dirichlet_edgedof_indices(::Interpolation)\nFerrite.edgedof_interior_indices(::Interpolation)\nFerrite.volumedof_interior_indices(::Interpolation)\nFerrite.getnbasefunctions(::Interpolation)\nFerrite.reference_coordinates(::Interpolation)\nFerrite.is_discontinuous(::Interpolation)\nFerrite.adjust_dofs_during_distribution(::Interpolation)\nFerrite.mapping_type","category":"page"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_value-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_value","text":"reference_shape_value(ip::Interpolation, ξ::Vec, i::Int)\n\nEvaluate the value of the ith shape function of the interpolation ip at a point ξ on the reference element. The index i must match the index in vertices(::Interpolation), faces(::Interpolation) and edges(::Interpolation).\n\nFor nodal interpolations the indices also must match the indices of reference_coordinates(::Interpolation).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.vertexdof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.vertexdof_indices","text":"vertexdof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_vertexdof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.dirichlet_vertexdof_indices","text":"dirichlet_vertexdof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to vertexdof_indices(ip::Interpolation) for continuous interpolation.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.facedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.facedof_indices","text":"facedof_indices(ip::Interpolation)\n\nA tuple containing tuples of all local dof indices for the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_facedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.dirichlet_facedof_indices","text":"dirichlet_facedof_indices(ip::Interpolation)\n\nA tuple containing tuples of all local dof indices for the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to facedof_indices(ip::Interpolation) for continuous interpolation.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.facedof_interior_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.facedof_interior_indices","text":"facedof_interior_indices(ip::Interpolation)\n\nA tuple containing tuples of the local dof indices on the interior of the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell. Note that the vertex and edge dofs are included here.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be the computed via \"last edge interior dof index + 1\", if face dofs exist.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.edgedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.edgedof_indices","text":"edgedof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell.\n\nThe dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_edgedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.dirichlet_edgedof_indices","text":"dirichlet_edgedof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to edgedof_indices(ip::Interpolation) for continuous interpolation.\n\nThe dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.edgedof_interior_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.edgedof_interior_indices","text":"edgedof_interior_indices(ip::Interpolation)\n\nA tuple containing tuples of the local dof indices on the interior of the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Note that the vertex dofs are included here.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be computed via \"last vertex dof index + 1\", if edge dofs exist.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.volumedof_interior_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.volumedof_interior_indices","text":"volumedof_interior_indices(ip::Interpolation)\n\nTuple containing the dof indices associated with the interior of a volume.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing, volumedofs are enumerated last.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.getnbasefunctions-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.getnbasefunctions","text":"Ferrite.getnbasefunctions(ip::Interpolation)\n\nReturn the number of base functions for the interpolation ip.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_coordinates-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.reference_coordinates","text":"reference_coordinates(ip::Interpolation)\n\nReturns a vector of coordinates with length getnbasefunctions(::Interpolation) and indices corresponding to the indices of a dof in vertices, faces and edges.\n\nOnly required for nodal interpolations.\n\nTODO: Separate nodal and non-nodal interpolations.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.is_discontinuous-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.is_discontinuous","text":"is_discontinuous(::Interpolation)\nis_discontinuous(::Type{<:Interpolation})\n\nChecks whether the interpolation is discontinuous (i.e. DiscontinuousLagrange)\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.adjust_dofs_during_distribution-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.adjust_dofs_during_distribution","text":"adjust_dofs_during_distribution(::Interpolation)\n\nThis function must return true if the dofs should be adjusted (i.e. permuted) during dof distribution. This is in contrast to i) adjusting the dofs during reinit! in the assembly loop, or ii) not adjusting at all (which is not needed for low order interpolations, generally).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.mapping_type","page":"Interpolations","title":"Ferrite.mapping_type","text":"mapping_type(ip::Interpolation)\n\nGet the type of mapping from the reference cell to the real cell for an interpolation ip. Subtypes of ScalarInterpolation and VectorizedInterpolation return IdentityMapping(), but other non-scalar interpolations may request different mapping types.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"for all entities which exist on that reference element. The dof functions default to having no dofs defined on a specific entity. Hence, not overloading of the dof functions will result in an element with zero dofs. Also, it should always be double checked that everything is consistent as specified in the docstring of the corresponding function, as inconsistent implementations can lead to bugs which are really difficult to track down.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/boundary_conditions/#Boundary-and-initial-conditions","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Every PDE is accompanied with boundary conditions. There are different types of boundary conditions, and they need to be handled in different ways. Below we discuss how to handle the most common ones, Dirichlet and Neumann boundary conditions, and how to do it in Ferrite.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"While boundary conditions can be applied directly to nodes, vertices, edges, or faces, they are most commonly applied to facets. Each facet is described by a FacetIndex. When adding boundary conditions to points instead, vertices are preferred over nodes.","category":"page"},{"location":"topics/boundary_conditions/#Dirichlet-boundary-conditions","page":"Boundary and initial conditions","title":"Dirichlet boundary conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"At a Dirichlet boundary the unknown field is prescribed to a given value. For the discrete FE-solution this means that there are some degrees of freedom that are fixed. To handle Dirichlet boundary conditions in Ferrite we use the ConstraintHandler. A constraint handler is created from a DoF handler:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"ch = ConstraintHandler(dh)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"We can now create Dirichlet constraints and add them to the constraint handler. To create a Dirichlet constraint we need to specify a field name, a part of the boundary, and a function for computing the prescribed value. Example:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"dbc1 = Dirichlet(\n :u, # Name of the field\n getfacetset(grid, \"left\"), # Part of the boundary\n x -> 1.0, # Function mapping coordinate to a prescribed value\n)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"The field name is given as a symbol, just like when the field was added to the dof handler, the part of the boundary where this constraint is active is given as a facet set, and the function computing the prescribed value should be of the form f(x) or f(x, t) (coordinate x and time t) and return the prescribed value(s).","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Multiple sets\nTo apply a constraint on multiple facet sets in the grid you can use union to join them, for exampleleft_right = union(getfacetset(grid, \"left\"), getfacetset(grid, \"right\"))creates a new facetset containing all facets in the \"left\" and \"right\" facetsets, which can be passed to the Dirichlet constructor.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"By default the constraint is added to all components of the given field. To add the constraint to selected components a fourth argument with the components should be passed to the constructor. Here is an example where a constraint is added to component 1 and 3 of a vector field :u:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"dbc2 = Dirichlet(\n :u, # Name of the field\n getfacetset(grid, \"left\"), # Part of the boundary\n x -> [0.0, 0.0], # Function mapping coordinate to prescribed values\n [1, 3], # Components\n)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Note that the return value of the function must match with the components – in the example above we prescribe components 1 and 3 to 0 so we return a vector of length 2.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Adding the constraints to the constraint handler is done with add!:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"add!(ch, dbc1)\nadd!(ch, dbc2)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Finally, just like for the dof handler, we need to use close! to finalize the constraint handler. Internally this will then compute the degrees-of-freedom that match the constraints we added.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"If one or more of the constraints depend on time, i.e. they are specified as f(x, t), the prescribed values can be recomputed in each new time step by calling update! with the proper time, e.g.:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"for t in 0.0:0.1:1.0\n update!(ch, t) # Compute prescribed values for this t\n # Solve for time t...\nend","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Examples\nMost examples make use of Dirichlet boundary conditions, for example Heat Equation.","category":"page"},{"location":"topics/boundary_conditions/#Neumann-boundary-conditions","page":"Boundary and initial conditions","title":"Neumann boundary conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"At the Neumann part of the boundary we know something about the gradient of the solution. Two different methods for applying these are described below. For complete examples that use Neumann boundary conditions, please see","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"von-Mises-plasticity\nHyperelasticity","category":"page"},{"location":"topics/boundary_conditions/#Using-the-FacetIterator","page":"Boundary and initial conditions","title":"Using the FacetIterator","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"A Neumann boundary contribution can be added by iterating over the relevant facetset by using the FacetIterator. For a scalar field, this can be done as","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"grid = generate_grid(Quadrilateral, (3,3))\ndh = DofHandler(grid); push!(dh, :u, 1); close!(dh)\nfv = FacetValues(QuadratureRule{RefQuadrilateral}(2), Lagrange{RefQuadrilateral, 1}())\nf = zeros(ndofs(dh))\nfe = zeros(ndofs_per_cell(dh))\nqn = 1.0 # Normal flux\nfor fc in FacetIterator(dh, getfacetset(grid, \"right\"))\n reinit!(fv, fc)\n fill!(fe, 0)\n for q_point in 1:getnquadpoints(fv)\n dΓ = getdetJdV(fv, q_point)\n for i in 1:getnbasefunctions(fv)\n δu = shape_value(fv, q_point, i)\n fe[i] += δu * qn * dΓ\n end\n end\n assemble!(f, celldofs(fc), fe)\nend","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Alternatively, it is possible to add the values directly to the global f (without going through the local fe vector and then using assemble!):","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"# ...\ndofs = celldofs(fc)\nfor i in 1:getnbasefunctions(fv)\n f[dofs[i]] += δu * qn * dΓ\nend","category":"page"},{"location":"topics/boundary_conditions/#In-the-element-routine","page":"Boundary and initial conditions","title":"In the element routine","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Alternatively, the following code snippet can be included in the element routine, to evaluate the boundary integral:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"for facet in 1:nfacets(cell)\n if (cellid(cell), facet) ∈ getfacetset(grid, \"Neumann Boundary\")\n reinit!(facetvalues, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:getnbasefunctions(facetvalues)\n δu = shape_value(facetvalues, q_point, i)\n fe[i] += δu * qn * dΓ\n end\n end\n end\nend","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"We start by looping over all the facets of the cell, next we check if this particular facet is located on our facetset of interest called \"Neumann Boundary\". If we have determined that the current facet is indeed on the boundary and in our facetset, then we reinitialize FacetValues for this facet, using reinit!. When reinit!ing FacetValues we also need to give the facet number in addition to the cell. Next we simply loop over the quadrature points of the facet, and then loop over all the test functions and assemble the contribution to the force vector.","category":"page"},{"location":"topics/boundary_conditions/#Periodic-boundary-conditions","page":"Boundary and initial conditions","title":"Periodic boundary conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Periodic boundary conditions ensure that the solution is periodic across two boundaries. To define the periodicity we first define the image boundary Gamma^+ and the mirror boundary Gamma^-. We also define a (unique) coordinate mapping between the image and the mirror: varphi Gamma^+ rightarrow Gamma^-. With the mapping we can, for every coordinate on the image, compute the corresponding coordinate on the mirror:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"boldsymbolx^- = varphi(boldsymbolx^+)quad boldsymbolx^- in Gamma^-\nboldsymbolx^+ in Gamma^+","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"We now want to ensure that the solution on the image Gamma^+ is mirrored on the mirror Gamma^-. This periodicity constraint can thus be described by","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"u(boldsymbolx^-) = u(boldsymbolx^+)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Sometimes this is written as","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"llbracket u rrbracket = 0","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"where llbracket bullet rrbracket = bullet(boldsymbolx^+) - bullet(boldsymbolx^-) is the \"jump operator\". Thus, this condition ensure that the jump, or difference, in the solution between the image and mirror boundary is the zero – the solution becomes periodic. For a vector valued problem the periodicity constraint can in general be written as","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"boldsymbolu(boldsymbolx^-) = boldsymbolR cdot boldsymbolu(boldsymbolx^+)\nquad Leftrightarrow quad llbracket boldsymbolu rrbracket =\nboldsymbolR cdot boldsymbolu(boldsymbolx^+) - boldsymbolu(boldsymbolx^-) =\nboldsymbol0","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"where boldsymbolR is a rotation matrix. If the mapping between mirror and image is simply a translation (e.g. sides of a cube) this matrix will be the identity matrix.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"In Ferrite this type of periodic Dirichlet boundary conditions can be added to the ConstraintHandler by constructing an instance of PeriodicDirichlet. This is usually done it two steps. First we compute the mapping between mirror and image facets using collect_periodic_facets. Here we specify the mirror set and image sets (the sets are usually known or can be constructed easily ) and the mapping varphi. Second we construct the constraint using the PeriodicDirichlet constructor. Here we specify which components of the function that should be constrained, and the rotation matrix boldsymbolR (when needed). When adding the constraint to the ConstraintHandler the resulting dof-mapping is computed.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Here is a simple example where periodicity is enforced for components 1 and 2 of the field :u between the mirror boundary set \"left\" and the image boundary set \"right\". Note that no rotation matrix is needed here since the mirror and image are parallel, just shifted in the x-direction (as seen by the mapping φ):","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"# Create a constraint handler from the dof handler\nch = ConstraintHandler(dofhandler)\n\n# Compute the facet mapping\nφ(x) = x - Vec{2}((1.0, 0.0))\nface_mapping = collect_periodic_facets(grid, \"left\", \"right\", φ)\n\n# Construct the periodic constraint for field :u\npdbc = PeriodicDirichlet(:u, face_mapping, [1, 2])\n\n# Add the constraint to the constraint handler\nadd!(ch, pdbc)\n\n# If no more constraints should be added we can close\nclose!(ch)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Note\nPeriodicDirichlet constraints are imposed in a strong sense, so note that this requires a periodic mesh such that it is possible to compute the facet mapping between facets on the mirror and boundary.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Examples\nPeriodic boundary conditions are used in the following examples Computational homogenization, Stokes flow.","category":"page"},{"location":"topics/boundary_conditions/#Heterogeneous-\"periodic\"-constraint","page":"Boundary and initial conditions","title":"Heterogeneous \"periodic\" constraint","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"It is also possible to define constraints of the form","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"llbracket u rrbracket = llbracket f rrbracket\nquad Leftrightarrow quad\nu(boldsymbolx^+) - u(boldsymbolx^-) =\nf(boldsymbolx^+) - f(boldsymbolx^-)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"where f is a prescribed function. Although the constraint in this case is not technically periodic, PeriodicDirichlet can be used for this too. This is done by passing a function to PeriodicDirichlet, similar to Dirichlet, which, given the coordinate boldsymbolx and time t, computes the prescribed values of f on the boundary.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Here is an example of how to implement this type of boundary condition, for a known function f:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"pdbc = PeriodicDirichlet(\n :u,\n face_mapping,\n (x, t) -> f(x),\n [1, 2],\n)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Note\nOne application for this type of boundary conditions is multiscale modeling and computational homogenization when solving the finite element problem for the subscale. In this case the unknown u is split into a macroscopic part u^mathrmM and a microscopic/fluctuation part u^mu, i.e. u = u^mathrmM + u^mu. Periodicity is then usually enforced for the fluctuation part, i.e. llbracket u^mu rrbracket = 0. The equivalent constraint for u then becomes llbracket u rrbracket = llbracket u^mathrmM rrbracket.As an example, consider first order homogenization where the macroscopic part is constructed as u^mathrmM = baru + boldsymbolnabla baru cdot boldsymbolx - barboldsymbolx for known baru and boldsymbolnabla baru. This could be implemented aspdbc = PeriodicDirichlet(\n :u,\n face_mapping,\n (x, t) -> ū + ∇ū ⋅ (x - x̄)\n)","category":"page"},{"location":"topics/boundary_conditions/#Initial-conditions","page":"Boundary and initial conditions","title":"Initial conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"When solving time-dependent problems, initial conditions, different from zero, may be required. For finite element formulations of ODE-type, i.e. boldsymbolu(t) = boldsymbolf(boldsymbolu(t)t), where boldsymbolu(t) are the degrees of freedom, initial conditions can be specified by the apply_analytical! function. For example, specify the initial pressure as a function of the y-coordinate","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"ρ = 1000; g = 9.81 # density [kg/m³] and gravity [N/kg]\ngrid = generate_grid(Quadrilateral, (10,10))\ndh = DofHandler(grid); add!(dh, :u, 2); add!(dh, :p, 1); close!(dh)\nu = zeros(ndofs(dh))\napply_analytical!(u, dh, :p, x -> ρ * g * x[2])","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"See also Transient heat equation for one example.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Consistency\napply_analytical! does not enforce consistency of the applied solution with the system of equations. Some problems, like for example differential-algebraic systems of equations (DAEs) need extra care during initialization. We refer to the paper \"Consistent Initial Condition Calculation for Differential-Algebraic Systems\" by Brown et al. for more details on this matter.","category":"page"},{"location":"tutorials/#Tutorials","page":"Tutorials overview","title":"Tutorials","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"On this page you find an overview of Ferrite tutorials. The tutorials explain and show how Ferrite can be used to solve a wide range of problems. See also the Code gallery for more examples.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"The tutorials all follow roughly the same structure:","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Introduction introduces the problem to be solved and discusses the learning outcomes of the tutorial.\nCommented program is the code for solving the problem with explanations and comments.\nPlain program is the raw source code of the program.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"When studying the tutorials it is a good idea to obtain a local copy of the code and run it on your own machine as you read along. Some of the tutorials also include suggestions for tweaks to the program that you can try out on your own.","category":"page"},{"location":"tutorials/#Tutorial-index","page":"Tutorials overview","title":"Tutorial index","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"The tutorials are listed in roughly increasing order of complexity. However, since they focus on different aspects, and solve different problems, it is suggested to have a look at the brief descriptions below to get an idea about what you will learn from each tutorial.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"If you are new to Ferrite then Tutorial 1 - Tutorial 6 is the best place to start. These tutorials introduces and teaches most of the basic finite element techniques (e.g. linear and non-linear problems, scalar- and vector-valued problems, Dirichlet and Neumann boundary conditions, mixed finite elements, time integration, direct and iterative linear solvers, etc). In particular the very first tutorial is essential in order to be able to follow any of the other tutorials. The remaining tutorials discuss more advanced topics.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-1:-Heat-equation](heat_equation.md)","page":"Tutorials overview","title":"Tutorial 1: Heat equation","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with homogeneous Dirichlet boundary conditions. This tutorial introduces and teaches many important parts of Ferrite: problem setup, degree of freedom management, assembly procedure, boundary conditions, solving the linear system, visualization of the result). Understanding this tutorial is essential to follow more complex tutorials.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: scalar-valued solution, Dirichlet boundary conditions.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-2:-Linear-elasticity](linear_elasticity.md)","page":"Tutorials overview","title":"Tutorial 2: Linear elasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"TBW.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: vector-valued solution, Dirichlet and Neumann boundary conditions.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-3:-Incompressible-elasticity](incompressible_elasticity.md)","page":"Tutorials overview","title":"Tutorial 3: Incompressible elasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial focuses on a mixed formulation of linear elasticity, with (vector) displacement and (scalar) pressure as the two unknowns, suitable for incompressibility. Thus, this tutorial guides you through the process of solving a problem with two unknowns from two coupled weak forms. The problem that is studied is Cook's membrane in the incompressible limit.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: mixed finite elements, Dirichlet and Neumann boundary conditions.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-4:-Hyperelasticity](hyperelasticity.md)","page":"Tutorials overview","title":"Tutorial 4: Hyperelasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial you will learn how to solve a non-linear finite element problem. In particular, a hyperelastic material model, in a finite strain setting, is used to solve the rotation of a cube. Automatic differentiatio (AD) is used for the consitutive relations. Newton's method is used for the non-linear iteration, and a conjugate gradient (CG) solver is used for the linear solution of the increment.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: non-linear finite element, finite strain, automatic differentiation (AD), Newton's method, conjugate gradient (CG).","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-5:-von-Mises-Plasticity](plasticity.md)","page":"Tutorials overview","title":"Tutorial 5: von Mises Plasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial revisits the cantilever beam problem from Tutorial 2: Linear elasticity, but instead of linear elasticity a plasticity model is used for the constitutive relation. You will learn how to solve a problem which require the solution of a local material problem, and the storage of material state, in each quadrature point. Newton's method is used both locally in the material routine, and globally on the finite element level.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: non-linear finite element, plasticity, material modeling, state variables, Newton’s method.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-6:-Transient-heat-equation](@ref-tutorial-transient-heat-equation)","page":"Tutorials overview","title":"Tutorial 6: Transient heat equation","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial the transient heat equation is solved on the unit square. The problem to be solved is thus similar to the one solved in the first tutorial, Heat equation, but with time-varying boundary conditions. In particular you will learn how to solve a time dependent problem with an implicit Euler scheme for the time integration.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: time dependent finite elements, implicit Euler time integration.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-7:-Computational-homogenization](computational_homogenization.md)","page":"Tutorials overview","title":"Tutorial 7: Computational homogenization","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial guides you through computational homogenization of an representative volume element (RVE) consisting of a soft matrix material with stiff inclusions. The computational mesh is read from an external mesh file generated with Gmsh. Dirichlet and periodic boundary conditions are used.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: Gmsh mesh reading, Dirichlet and periodic boundary conditions","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-8:-Stokes-flow](stokes-flow.md)","page":"Tutorials overview","title":"Tutorial 8: Stokes flow","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial Stokes flow with (vector) velocity and (scalar) pressure is solved on on a quarter circle. Rotationally periodic boundary conditions is used for the inlet/outlet coupling. To obtain a unique solution, a mean value constraint is applied on the pressure using an affine constraint. The computational mesh is generated directly using the Gmsh API.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: periodic boundary conditions, mean value constraint, mesh generation with Gmsh.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-9:-Porous-media-(SubDofHandler)](porous_media.md)","page":"Tutorials overview","title":"Tutorial 9: Porous media (SubDofHandler)","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial introduces how to solve a complex linear problem, where there are different fields on different subdomains, and different cell types in the grid. This requires using the SubDofHandler interface.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: Mixed grids, multiple fields, porous media, SubDofHandler","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-10:-Incompressible-Navier-Stokes-equations](ns_vs_diffeq.md)","page":"Tutorials overview","title":"Tutorial 10: Incompressible Navier-Stokes equations","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial the incompressible Navier-Stokes equations are solved. The domain is discretized in space with Ferrite as usual, and then forumalated in a way to be compatible with the OrdinaryDiffEq.jl package, which is used for the time-integration.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: non-linear time dependent problem","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-10:-Reactive-surface](@ref-tutorial-reactive-surface)","page":"Tutorials overview","title":"Tutorial 10: Reactive surface","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial a reaction diffusion system on a sphere surface embedded in 3D is solved. Ferrite is used to assemble the diffusion operators and the mass matrices. The problem is solved by using the usual first order reaction diffusion operator splitting.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: embedded elements, operator splitting, gmsh","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-11:-Linear-shell](@ref-tutorial-linear-shell)","page":"Tutorials overview","title":"Tutorial 11: Linear shell","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial a linear shell element formulation is set up as a two-dimensional domain embedded in three-dimensional space. This will teach, and perhaps inspire, you on how Ferrite can be used for non-standard things and how to add \"hacks\" that build on top of Ferrite.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: shell elements, automatic differentiation","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-12:-Discontinuous-Galerkin-heat-equation](@ref-tutorial-dg-heat-equation)","page":"Tutorials overview","title":"Tutorial 12: Discontinuous Galerkin heat equation","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with inhomogeneous Dirichlet and Neumann boundary conditions using the interior penalty discontinuous Galerkin method. This tutorial follows the heat equation tutorial, introducing face and interface iterators, jump and average operators, and cross-element coupling in sparsity patterns. This example was developed as part of the Google Summer of Code funded project \"Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl\".","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: scalar-valued solution, Dirichlet boundary conditions, Discontinuous Galerkin, Interior penalty","category":"page"},{"location":"howto/#How-to-guides","page":"How-to guide overview","title":"How-to guides","text":"","category":"section"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"This page gives an overview of the how-to guides. How-to guides address various common tasks one might want to do in a finite element program. Many of the guides are extensions, or build on top of, the tutorials and, therefore, some familiarity with Ferrite is assumed.","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"","category":"page"},{"location":"howto/#[Post-processing-and-visualization](postprocessing.md)","page":"How-to guide overview","title":"Post processing and visualization","text":"","category":"section"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"This guide builds on top of Tutorial 1: Heat equation and discusses various post processsing techniques with the goal of visualizing primary fields (the finite element solution) and secondary quantities (e.g. fluxes, stresses, etc.). Concretely, this guide answers:","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"How to visualize data from quadrature points?\nHow to evaluate the finite element solution, or secondary quantities, in arbitrary points of the domain?","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"","category":"page"},{"location":"howto/#[Multi-threaded-assembly](threaded_assembly.md)","page":"How-to guide overview","title":"Multi-threaded assembly","text":"","category":"section"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"This guide modifies Tutorial 2: Linear elasticity such that the program is using multi-threading to parallelize the assembly procedure. Concretely this shows how to use grid coloring and \"scratch values\" in order to use multi-threading without running into race-conditions.","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"","category":"page"},{"location":"reference/sparsity_pattern/#Sparsity-pattern-and-sparse-matrices","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"This is the reference documentation for sparsity patterns and sparse matrix instantiation. See the topic section on Sparsity pattern and sparse matrices.","category":"page"},{"location":"reference/sparsity_pattern/#Sparsity-patterns","page":"Sparsity pattern and sparse matrices","title":"Sparsity patterns","text":"","category":"section"},{"location":"reference/sparsity_pattern/#AbstractSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"AbstractSparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The following applies to all subtypes of AbstractSparsityPattern:","category":"page"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Ferrite.AbstractSparsityPattern\ninit_sparsity_pattern\nadd_sparsity_entries!\nadd_cell_entries!\nadd_interface_entries!\nadd_constraint_entries!\nFerrite.add_entry!","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.AbstractSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.AbstractSparsityPattern","text":"Ferrite.AbstractSparsityPattern\n\nSupertype for sparsity pattern implementations, e.g. SparsityPattern and BlockSparsityPattern.\n\n\n\n\n\n","category":"type"},{"location":"reference/sparsity_pattern/#Ferrite.init_sparsity_pattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.init_sparsity_pattern","text":"init_sparsity_pattern(dh::DofHandler; nnz_per_row::Int)\n\nInitialize an empty SparsityPattern with ndofs(dh) rows and ndofs(dh) columns.\n\nKeyword arguments\n\nnnz_per_row: memory optimization hint for the number of non-zero entries per row that will be added to the pattern.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_sparsity_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_sparsity_entries!","text":"add_sparsity_entries!(\n sp::AbstractSparsityPattern,\n dh::DofHandler,\n ch::Union{ConstraintHandler, Nothing} = nothing;\n topology = nothing,\n keep_constrained::Bool = true,\n coupling = nothing,\n interface_coupling = nothing,\n)\n\nConvenience method for doing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!, depending on what arguments are passed:\n\nadd_cell_entries! is always called\nadd_interface_entries! is called if topology is provided (i.e. not nothing)\nadd_constraint_entries! is called if the ConstraintHandler is provided\n\nFor more details about arguments and keyword arguments, see the respective functions.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_cell_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_cell_entries!","text":"add_cell_entries!(\n sp::AbstractSparsityPattern,\n dh::DofHandler,\n ch::Union{ConstraintHandler, Nothing} = nothing;\n keep_constrained::Bool = true,\n coupling::Union{AbstractMatrix{Bool}, Nothing}, = nothing\n)\n\nAdd entries to the sparsity pattern sp corresponding to DoF couplings within the cells as described by the DofHandler dh.\n\nKeyword arguments\n\nkeep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.\ncoupling: the coupling between fields/components within each cell. By default (coupling = nothing) it is assumed that all DoFs in each cell couple with each other.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_interface_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_interface_entries!","text":"add_interface_entries!(\n sp::SparsityPattern, dh::DofHandler, ch::Union{ConstraintHandler, Nothing};\n topology::ExclusiveTopology, keep_constrained::Bool = true,\n interface_coupling::AbstractMatrix{Bool},\n)\n\nAdd entries to the sparsity pattern sp corresponding to DoF couplings on the interface between cells as described by the DofHandler dh.\n\nKeyword arguments\n\ntopology: the topology corresponding to the grid.\nkeep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.\ninterface_coupling: the coupling between fields/components across the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_constraint_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_constraint_entries!","text":"add_constraint_entries!(\n sp::AbstractSparsityPattern, ch::ConstraintHandler;\n keep_constrained::Bool = true,\n)\n\nAdd all entries resulting from constraints in the ConstraintHandler ch to the sparsity pattern. Note that, since this operation depends on existing entries in the pattern, this function must be called as the last step when creating the sparsity pattern.\n\nKeyword arguments\n\nkeep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_entry!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_entry!","text":"add_entry!(sp::AbstractSparsityPattern, row::Int, col::Int)\n\nAdd an entry to the sparsity pattern sp at row row and column col.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#SparsityPattern","page":"Sparsity pattern and sparse matrices","title":"SparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"SparsityPattern(::Int, ::Int)\nallocate_matrix(::SparsityPattern)\nSparsityPattern","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.SparsityPattern-Tuple{Int64, Int64}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.SparsityPattern","text":"SparsityPattern(nrows::Int, ncols::Int; nnz_per_row::Int = 8)\n\nCreate an empty SparsityPattern with nrows rows and ncols columns. nnz_per_row is used as a memory hint for the number of non zero entries per row.\n\nSparsityPattern is the default sparsity pattern type for the standard DofHandler and is therefore commonly constructed using init_sparsity_pattern instead of with this constructor.\n\nExamples\n\n# Create a sparsity pattern for an 100 x 100 matrix, hinting at 10 entries per row\nsparsity_pattern = SparsityPattern(100, 100; nnz_per_row = 10)\n\nMethods\n\nThe following methods apply to SparsityPattern (see their respective documentation for more details):\n\nadd_sparsity_entries!: convenience method for calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!.\nadd_cell_entries!: add entries corresponding to DoF couplings within the cells.\nadd_interface_entries!: add entries corresponding to DoF couplings on the interface between cells.\nadd_constraint_entries!: add entries resulting from constraints.\nallocate_matrix: instantiate a matrix from the pattern. The default matrix type is SparseMatrixCSC{Float64, Int}.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Tuple{SparsityPattern}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(sp::SparsityPattern)\n\nAllocate a sparse matrix of type SparseMatrixCSC{Float64, Int} from the sparsity pattern sp.\n\nThis method is a shorthand for the equivalent allocate_matrix(SparseMatrixCSC{Float64, Int}, sp).\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.SparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.SparsityPattern","text":"struct SparsityPattern <: AbstractSparsityPattern\n\nData structure representing non-zero entries in the eventual sparse matrix.\n\nSee the constructor SparsityPattern(::Int, ::Int) for the user-facing documentation.\n\nStruct fields\n\nnrows::Int: number of rows\nncols::Int: number of column\nrows::Vector{Vector{Int}}: vector of length nrows, where rows[i] is a sorted vector of column indices for non zero entries in row i.\n\nwarning: Internal struct\nThe specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.\n\n\n\n\n\n","category":"type"},{"location":"reference/sparsity_pattern/#BlockSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"BlockSparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"note: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.","category":"page"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"BlockSparsityPattern(::Vector{Int})\nMain.FerriteBlockArrays.BlockSparsityPattern\nallocate_matrix(::Main.FerriteBlockArrays.BlockSparsityPattern)\nallocate_matrix(::Type{<:BlockMatrix{T, Matrix{S}}}, sp::Main.FerriteBlockArrays.BlockSparsityPattern) where {T, S <: AbstractMatrix{T}}","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.BlockSparsityPattern-Tuple{Vector{Int64}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.BlockSparsityPattern","text":"BlockSparsityPattern(block_sizes::Vector{Int})\n\nCreate an empty BlockSparsityPattern with row and column block sizes given by block_sizes.\n\nExamples\n\n# Create a block sparsity pattern with block size 10 x 5\nsparsity_pattern = BlockSparsityPattern([10, 5])\n\nMethods\n\nThe following methods apply to BlockSparsityPattern (see their respective documentation for more details):\n\nadd_sparsity_entries!: convenience method for calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!.\nadd_cell_entries!: add entries corresponding to DoF couplings within the cells.\nadd_interface_entries!: add entries corresponding to DoF couplings on the interface between cells.\nadd_constraint_entries!: add entries resulting from constraints.\nallocate_matrix: instantiate a (block) matrix from the pattern. The default matrix type is BlockMatrix{Float64, Matrix{SparseMatrixCSC{Float64, Int}}}, i.e. a BlockMatrix, where the individual blocks are of type SparseMatrixCSC{Float64, Int}.\n\nnote: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.BlockSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.BlockSparsityPattern","text":"struct BlockSparsityPattern <: AbstractSparsityPattern\n\nData structure representing non-zero entries for an eventual blocked sparse matrix.\n\nSee the constructor BlockSparsityPattern(::Vector{Int}) for the user-facing documentation.\n\nStruct fields\n\nnrows::Int: number of rows\nncols::Int: number of column\nblock_sizes::Vector{Int}: row and column block sizes\nblocks::Matrix{SparsityPattern}: matrix of size length(block_sizes) × length(block_sizes) where blocks[i, j] is a SparsityPattern corresponding to block (i, j).\n\nwarning: Internal struct\nThe specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.\n\n\n\n\n\n","category":"type"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Tuple{BlockSparsityPattern}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)\n\nAllocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.\n\n\n\n\n\nallocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)\n\nInstantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.\n\n\n\n\n\nallocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)\n\nAllocate a matrix of type MatrixType from the DofHandler dh.\n\nThis is a convenience method and is equivalent to:\n\njulia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`\n\nRefer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.\n\nnote: Note\nIf more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. usesp = init_sparsity_pattern(dh)\nadd_sparsity_entries!(sp, dh)\nK = allocate_matrix(sp)\nM = allocate_matrix(sp)instead ofK = allocate_matrix(dh)\nM = allocate_matrix(dh)Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.\n\n\n\n\n\nallocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)\nallocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)\n\nInstantiate a blocked sparse matrix from the blocked sparsity pattern sp.\n\nThe type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).\n\nExamples\n\n# Create a sparse matrix with default block type\nallocate_matrix(BlockMatrix, sparsity_pattern)\n\n# Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}\nallocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)\n\nnote: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{S}, Tuple{T}, Tuple{Type{<:BlockArray{T, 2, Matrix{S}, BS} where BS<:Tuple{AbstractUnitRange{<:Integer}, AbstractUnitRange{<:Integer}}}, BlockSparsityPattern}} where {T, S<:AbstractMatrix{T}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)\nallocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)\n\nInstantiate a blocked sparse matrix from the blocked sparsity pattern sp.\n\nThe type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).\n\nExamples\n\n# Create a sparse matrix with default block type\nallocate_matrix(BlockMatrix, sparsity_pattern)\n\n# Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}\nallocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)\n\nnote: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Sparse-matrices","page":"Sparsity pattern and sparse matrices","title":"Sparse matrices","text":"","category":"section"},{"location":"reference/sparsity_pattern/#Creating-matrix-from-SparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Creating matrix from SparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"allocate_matrix(::Type{S}, ::Ferrite.AbstractSparsityPattern) where {Tv, Ti, S <: SparseMatrixCSC{Tv, Ti}}\nallocate_matrix(::Type{Symmetric{Tv, S}}, ::Ferrite.AbstractSparsityPattern) where {Tv, Ti, S <: SparseMatrixCSC{Tv, Ti}}","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{S}, Tuple{Ti}, Tuple{Tv}, Tuple{Type{S}, Ferrite.AbstractSparsityPattern}} where {Tv, Ti, S<:SparseMatrixCSC{Tv, Ti}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)\n\nAllocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{S}, Tuple{Ti}, Tuple{Tv}, Tuple{Type{Symmetric{Tv, S}}, Ferrite.AbstractSparsityPattern}} where {Tv, Ti, S<:SparseMatrixCSC{Tv, Ti}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)\n\nInstantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Creating-matrix-from-DofHandler","page":"Sparsity pattern and sparse matrices","title":"Creating matrix from DofHandler","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"allocate_matrix(::Type{MatrixType}, ::DofHandler, args...; kwargs...) where {MatrixType}\nallocate_matrix(::DofHandler, args...; kwargs...)","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{MatrixType}, Tuple{Type{MatrixType}, DofHandler, Vararg{Any}}} where MatrixType","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)\n\nAllocate a matrix of type MatrixType from the DofHandler dh.\n\nThis is a convenience method and is equivalent to:\n\njulia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`\n\nRefer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.\n\nnote: Note\nIf more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. usesp = init_sparsity_pattern(dh)\nadd_sparsity_entries!(sp, dh)\nK = allocate_matrix(sp)\nM = allocate_matrix(sp)instead ofK = allocate_matrix(dh)\nM = allocate_matrix(dh)Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Tuple{DofHandler, Vararg{Any}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(dh::DofHandler, args...; kwargs...)\n\nAllocate a matrix of type SparseMatrixCSC{Float64, Int} from the DofHandler dh.\n\nThis method is a shorthand for the equivalent allocate_matrix(SparseMatrixCSC{Float64, Int}, dh, args...; kwargs...) – refer to that method for details.\n\n\n\n\n\n","category":"method"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/constraints/#Constraints","page":"Constraints","title":"Constraints","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"PDEs can in general be subjected to a number of constraints,","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"g_I(underlinea) = 0 quad I = 1 text to n_c","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"where g are (non-linear) constraint equations, underlinea is a vector of the degrees of freedom, and n_c is the number of constraints. There are many ways to enforce these constraints, e.g. penalty methods and Lagrange multiplier methods.","category":"page"},{"location":"topics/constraints/#Affine-constraints","page":"Constraints","title":"Affine constraints","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"Affine or linear constraints can be handled directly in Ferrite. Such constraints can typically be expressed as:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"a_1 = 5a_2 + 3a_3 + 1 \na_4 = 2a_3 + 6a_5 \ndots","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"where a_1, a_2 etc. are system degrees of freedom. In Ferrite, we can account for such constraint using the ConstraintHandler:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"ch = ConstraintHandler(dh)\nlc1 = AffineConstraint(1, [2 => 5.0, 3 => 3.0], 1)\nlc2 = AffineConstraint(4, [3 => 2.0, 5 => 6.0], 0)\nadd!(ch, lc1)\nadd!(ch, lc2)","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"Affine constraints will affect the sparsity pattern of the stiffness matrix, and as such, it is important to also include the ConstraintHandler as an argument when creating the sparsity pattern:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"K = allocate_matrix(dh, ch)","category":"page"},{"location":"topics/constraints/#Solving-linear-problems","page":"Constraints","title":"Solving linear problems","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"To solve the system underlineunderlineKunderlinea=underlinef, account for affine constraints the same way as for Dirichlet boundary conditions; first call apply!(K, f, ch). This will condense K and f inplace (i.e no new matrix will be created). Note however that we must also call apply! on the solution vector after solving the system to enforce the affine constraints:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"# ...\n# Assemble K and f...\n\napply!(K, f, ch)\na = K\\f\napply!(a, ch) # enforces affine constraints\n","category":"page"},{"location":"topics/constraints/#Solving-nonlinear-problems","page":"Constraints","title":"Solving nonlinear problems","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"It is important to check the residual after applying boundary conditions when solving nonlinear problems with affine constraints. apply_zero!(K, r, ch) modifies the residual entries for dofs that are involved in constraints to account for constraint forces. The following pseudo-code shows a typical pattern for solving a non-linear problem with Newton's method:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"a = initial_guess(...) # Make any initial guess for a here, e.g. `a=zeros(ndofs(dh))`\napply!(a, ch) # Make the guess fulfill all constraints in `ch`\nfor iter in 1:maxiter\n doassemble!(K, r, ...) # Assemble the residual, r, and stiffness, K=∂r/∂a.\n apply_zero!(K, r, ch) # Modify `K` and `r` to account for the constraints.\n check_convergence(r, ...) && break # Only check convergence after `apply_zero!(K, r, ch)`\n Δa = K \\ r # Calculate the (negative) update\n apply_zero!(Δa, ch) # Change the constrained values in `Δa` such that `a-Δa`\n # fulfills constraints if `a` did.\n a .-= Δa\nend","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"EditURL = \"../literate-howto/postprocessing.jl\"","category":"page"},{"location":"howto/postprocessing/#howto-postprocessing","page":"Post processing and visualization","title":"Post processing and visualization","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"(Image: )","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 1: Heat flux computed from the solution to the heat equation on the unit square, see previous example: Heat equation.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"tip: Tip\nThis example is also available as a Jupyter notebook: postprocessing.ipynb.","category":"page"},{"location":"howto/postprocessing/#Introduction","page":"Post processing and visualization","title":"Introduction","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"After running a simulation, we usually want to visualize the results in different ways. The L2Projector and the PointEvalHandler build a pipeline for doing so. With the L2Projector, integration point quantities can be projected to the nodes. The PointEvalHandler enables evaluation of the finite element approximated function in any coordinate in the domain. Thus with the combination of both functionalities, both nodal quantities and integration point quantities can be evaluated in any coordinate, allowing for example cut-planes through 3D structures or cut-lines through 2D-structures.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"This example continues from the Heat equation example, where the temperature field was determined on a square domain. In this example, we first compute the heat flux in each integration point (based on the solved temperature field) and then we do an L2-projection of the fluxes to the nodes of the mesh. By doing this, we can more easily visualize integration points quantities. Finally, we visualize the temperature field and the heat fluxes along a cut-line.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"The L2-projection is defined as follows: Find projection q(boldsymbolx) in U_h(Omega) such that","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"int v q mathrmdOmega = int v d mathrmdOmega quad forall v in U_h(Omega)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"where d is the quadrature data to project. Since the flux is a vector the projection function will be solved with multiple right hand sides, e.g. with d = q_x and d = q_y for this 2D problem. In this example, we use standard Lagrange interpolations, and the finite element space U_h is then a subset of the H^1 space (continuous functions).","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Ferrite has functionality for doing much of this automatically, as displayed in the code below. In particular L2Projector for assembling the left hand side, and project for assembling the right hand sides and solving for the projection.","category":"page"},{"location":"howto/postprocessing/#Implementation","page":"Post processing and visualization","title":"Implementation","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Start by simply running the Heat equation example to solve the problem","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"include(\"../tutorials/heat_equation.jl\");\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Next we define a function that computes the heat flux for each integration point in the domain. Fourier's law is adopted, where the conductivity tensor is assumed to be isotropic with unit conductivity lambda = 1 q = - nabla u, where u is the temperature.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"function compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}\n\n n = getnbasefunctions(cellvalues)\n cell_dofs = zeros(Int, n)\n nqp = getnquadpoints(cellvalues)\n\n # Allocate storage for the fluxes to store\n q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)]\n\n for (cell_num, cell) in enumerate(CellIterator(dh))\n q_cell = q[cell_num]\n celldofs!(cell_dofs, dh, cell_num)\n aᵉ = a[cell_dofs]\n reinit!(cellvalues, cell)\n\n for q_point in 1:nqp\n q_qp = - function_gradient(cellvalues, q_point, aᵉ)\n push!(q_cell, q_qp)\n end\n end\n return q\nend\nnothing # hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Now call the function to get all the fluxes.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"q_gp = compute_heat_fluxes(cellvalues, dh, u);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Next, create an L2Projector using the same interpolation as was used to approximate the temperature field. On instantiation, the projector assembles the coefficient matrix M and computes the Cholesky factorization of it. By doing so, the projector can be reused without having to invert M every time.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"projector = L2Projector(ip, grid);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Project the integration point values to the nodal values","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"q_projected = project(projector, q_gp, qr);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/#Exporting-to-VTK","page":"Post processing and visualization","title":"Exporting to VTK","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"To visualize the heat flux, we export the projected field q_projected to a VTK-file, which can be viewed in e.g. ParaView. The result is also visualized in Figure 1.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"VTKGridFile(\"heat_equation_flux\", grid) do vtk\n write_projection(vtk, projector, q_projected, \"q\")\nend;\nnothing #hide","category":"page"},{"location":"howto/postprocessing/#Point-evaluation","page":"Post processing and visualization","title":"Point evaluation","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"(Image: )","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 2: Visualization of the cut line where we want to compute the temperature and heat flux.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Consider a cut-line through the domain like the black line in Figure 2 above. We will evaluate the temperature and the heat flux distribution along a horizontal line.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"First, we need to generate a PointEvalHandler. This will find and store the cells containing the input points.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"ph = PointEvalHandler(grid, points);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"After the L2-Projection, the heat fluxes q_projected are stored in the DoF-ordering determined by the projector's internal DoFHandler, so to evaluate the flux q at our points we give the PointEvalHandler, the L2Projector and the values q_projected.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"q_points = evaluate_at_points(ph, projector, q_projected);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"We can also extract the field values, here the temperature, right away from the result vector of the simulation, that is stored in u. These values are stored in the order of our initial DofHandler so the input is not the PointEvalHandler, the original DofHandler, the dof-vector u, and (optionally for single-field problems) the name of the field. From the L2Projection, the values are stored in the order of the degrees of freedom.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"u_points = evaluate_at_points(ph, dh, u, :u);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Now, we can plot the temperature and flux values with the help of any plotting library, e.g. Plots.jl. To do this, we need to import the package:","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"import Plots","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Firstly, we are going to plot the temperature values along the given line.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Plots.plot(getindex.(points, 1), u_points, xlabel = \"x (coordinate)\", ylabel = \"u (temperature)\", label = nothing)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 3: Temperature along the cut line from Figure 2.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Secondly, the horizontal heat flux (i.e. the first component of the heat flux vector) is plotted.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = \"x (coordinate)\", ylabel = \"q_x (flux in x-direction)\", label = nothing)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 4: x-component of the flux along the cut line from Figure 2.","category":"page"},{"location":"howto/postprocessing/#postprocessing-plain-program","page":"Post processing and visualization","title":"Plain program","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Here follows a version of the program without any comments. The file is also available here: postprocessing.jl.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"include(\"../tutorials/heat_equation.jl\");\n\nfunction compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}\n\n n = getnbasefunctions(cellvalues)\n cell_dofs = zeros(Int, n)\n nqp = getnquadpoints(cellvalues)\n\n # Allocate storage for the fluxes to store\n q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)]\n\n for (cell_num, cell) in enumerate(CellIterator(dh))\n q_cell = q[cell_num]\n celldofs!(cell_dofs, dh, cell_num)\n aᵉ = a[cell_dofs]\n reinit!(cellvalues, cell)\n\n for q_point in 1:nqp\n q_qp = - function_gradient(cellvalues, q_point, aᵉ)\n push!(q_cell, q_qp)\n end\n end\n return q\nend\n\nq_gp = compute_heat_fluxes(cellvalues, dh, u);\n\nprojector = L2Projector(ip, grid);\n\nq_projected = project(projector, q_gp, qr);\n\nVTKGridFile(\"heat_equation_flux\", grid) do vtk\n write_projection(vtk, projector, q_projected, \"q\")\nend;\n\npoints = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];\n\nph = PointEvalHandler(grid, points);\n\nq_points = evaluate_at_points(ph, projector, q_projected);\n\nu_points = evaluate_at_points(ph, dh, u, :u);\n\nimport Plots\n\nPlots.plot(getindex.(points, 1), u_points, xlabel = \"x (coordinate)\", ylabel = \"u (temperature)\", label = nothing)\n\nPlots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = \"x (coordinate)\", ylabel = \"q_x (flux in x-direction)\", label = nothing)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/grid/#Grid-and-AbstractGrid","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"","category":"section"},{"location":"reference/grid/#Grid","page":"Grid & AbstractGrid","title":"Grid","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"generate_grid\nNode\nCellIndex\nVertexIndex\nEdgeIndex\nFaceIndex\nFacetIndex\nGrid","category":"page"},{"location":"reference/grid/#Ferrite.generate_grid","page":"Grid & AbstractGrid","title":"Ferrite.generate_grid","text":"generate_grid(celltype::Cell, nel::NTuple, [left::Vec, right::Vec)\n\nReturn a Grid for a rectangle in 1, 2 or 3 dimensions. celltype defined the type of cells, e.g. Triangle or Hexahedron. nel is a tuple of the number of elements in each direction. left and right are optional endpoints of the domain. Defaults to -1 and 1 in all directions.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.Node","page":"Grid & AbstractGrid","title":"Ferrite.Node","text":"Node{dim, T}\n\nA Node is a point in space.\n\nFields\n\nx::Vec{dim,T}: stores the coordinates\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.CellIndex","page":"Grid & AbstractGrid","title":"Ferrite.CellIndex","text":"A CellIndex wraps an Int and corresponds to a cell with that number in the mesh\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.VertexIndex","page":"Grid & AbstractGrid","title":"Ferrite.VertexIndex","text":"A VertexIndex wraps an (Int, Int) and defines a local vertex by pointing to a (cell, vert).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.EdgeIndex","page":"Grid & AbstractGrid","title":"Ferrite.EdgeIndex","text":"A EdgeIndex wraps an (Int, Int) and defines a local edge by pointing to a (cell, edge).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.FaceIndex","page":"Grid & AbstractGrid","title":"Ferrite.FaceIndex","text":"A FaceIndex wraps an (Int, Int) and defines a local face by pointing to a (cell, face).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.FacetIndex","page":"Grid & AbstractGrid","title":"Ferrite.FacetIndex","text":"A FacetIndex wraps an (Int, Int) and defines a local facet by pointing to a (cell, facet).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.Grid","page":"Grid & AbstractGrid","title":"Ferrite.Grid","text":"Grid{dim, C<:AbstractCell, T<:Real} <: AbstractGrid}\n\nA Grid is a collection of Ferrite.AbstractCells and Ferrite.Nodes which covers the computational domain. Helper structures for applying boundary conditions or define subdomains are gathered in cellsets, nodesets, facetsets, and vertexsets.\n\nFields\n\ncells::Vector{C}: stores all cells of the grid\nnodes::Vector{Node{dim,T}}: stores the dim dimensional nodes of the grid\ncellsets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of cell ids\nnodesets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of global node ids\nfacetsets::Dict{String, OrderedSet{FacetIndex}}: maps a String to an OrderedSet of FacetIndex\nvertexsets::Dict{String, OrderedSet{VertexIndex}}: maps a String key to an OrderedSet of VertexIndex\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Utility-Functions","page":"Grid & AbstractGrid","title":"Utility Functions","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"getcells\ngetncells\ngetnodes\ngetnnodes\nFerrite.nnodes_per_cell\ngetcellset\ngetnodeset\ngetfacetset\ngetvertexset\ntransform_coordinates!\ngetcoordinates\ngetcoordinates!\ngeometric_interpolation(::Ferrite.AbstractCell)\nget_node_coordinate\nFerrite.getspatialdim(::Ferrite.AbstractGrid)\nFerrite.getrefdim(::Ferrite.AbstractCell)","category":"page"},{"location":"reference/grid/#Ferrite.getcells","page":"Grid & AbstractGrid","title":"Ferrite.getcells","text":"getcells(grid::AbstractGrid)\ngetcells(grid::AbstractGrid, v::Union{Int,Vector{Int}}\ngetcells(grid::AbstractGrid, setname::String)\n\nReturns either all cells::Collection{C<:AbstractCell} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. Whereas the last option tries to call a cellset of the grid. Collection can be any indexable type, for Grid it is Vector{C<:AbstractCell}.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getncells","page":"Grid & AbstractGrid","title":"Ferrite.getncells","text":"Returns the number of cells in the <:AbstractGrid.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getnodes","page":"Grid & AbstractGrid","title":"Ferrite.getnodes","text":"getnodes(grid::AbstractGrid)\ngetnodes(grid::AbstractGrid, v::Union{Int,Vector{Int}}\ngetnodes(grid::AbstractGrid, setname::String)\n\nReturns either all nodes::Collection{N} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. The last option tries to call a nodeset of the <:AbstractGrid. Collection{N} refers to some indexable collection where each element corresponds to a Node.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getnnodes","page":"Grid & AbstractGrid","title":"Ferrite.getnnodes","text":"Returns the number of nodes in the grid.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.nnodes_per_cell","page":"Grid & AbstractGrid","title":"Ferrite.nnodes_per_cell","text":"Returns the number of nodes of the i-th cell.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getcellset","page":"Grid & AbstractGrid","title":"Ferrite.getcellset","text":"getcellset(grid::AbstractGrid, setname::String)\n\nReturns all cells as cellid in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getnodeset","page":"Grid & AbstractGrid","title":"Ferrite.getnodeset","text":"getnodeset(grid::AbstractGrid, setname::String)\n\nReturns all nodes as nodeid in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getfacetset","page":"Grid & AbstractGrid","title":"Ferrite.getfacetset","text":"getfacetset(grid::AbstractGrid, setname::String)\n\nReturns all faces as FacetIndex in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getvertexset","page":"Grid & AbstractGrid","title":"Ferrite.getvertexset","text":"getvertexset(grid::AbstractGrid, setname::String)\n\nReturns all vertices as VertexIndex in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.transform_coordinates!","page":"Grid & AbstractGrid","title":"Ferrite.transform_coordinates!","text":"transform_coordinates!(grid::Abstractgrid, f::Function)\n\nTransform the coordinates of all nodes of the grid based on some transformation function f(x).\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getcoordinates","page":"Grid & AbstractGrid","title":"Ferrite.getcoordinates","text":"getcoordinates(grid::AbstractGrid, idx::Union{Int,CellIndex})\ngetcoordinates(cache::CellCache)\n\nGet a vector with the coordinates of the cell corresponding to idx or cache\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getcoordinates!","page":"Grid & AbstractGrid","title":"Ferrite.getcoordinates!","text":"getcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, idx::Union{Int,CellIndex})\ngetcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, cell::AbstractCell)\n\nMutate x to the coordinates of the cell corresponding to idx or cell.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.geometric_interpolation-Tuple{Ferrite.AbstractCell}","page":"Grid & AbstractGrid","title":"Ferrite.geometric_interpolation","text":"geometric_interpolation(::AbstractCell)::ScalarInterpolation\ngeometric_interpolation(::Type{<:AbstractCell})::ScalarInterpolation\n\nEach AbstractCell type has a unique geometric interpolation describing its geometry. This function returns that interpolation, which is always a scalar interpolation.\n\n\n\n\n\n","category":"method"},{"location":"reference/grid/#Ferrite.get_node_coordinate","page":"Grid & AbstractGrid","title":"Ferrite.get_node_coordinate","text":"get_node_coordinate(::Node)\n\nGet the value of the node coordinate.\n\n\n\n\n\nget_node_coordinate(grid::AbstractGrid, n::Int)\n\nReturn the coordinate of the nth node in grid\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getspatialdim-Tuple{Ferrite.AbstractGrid}","page":"Grid & AbstractGrid","title":"Ferrite.getspatialdim","text":"Ferrite.getspatialdim(grid::AbstractGrid)\n\nGet the spatial dimension of the grid, corresponding to the vector dimension of the grid's coordinates.\n\n\n\n\n\n","category":"method"},{"location":"reference/grid/#Ferrite.getrefdim-Tuple{Ferrite.AbstractCell}","page":"Grid & AbstractGrid","title":"Ferrite.getrefdim","text":"Ferrite.getrefdim(cell::AbstractCell)\nFerrite.getrefdim(::Type{<:AbstractCell})\n\nGet the reference dimension of the cell, i.e. the dimension of the cell's reference shape.\n\n\n\n\n\n","category":"method"},{"location":"reference/grid/#Topology","page":"Grid & AbstractGrid","title":"Topology","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"ExclusiveTopology\ngetneighborhood\nfacetskeleton\nvertex_star_stencils\ngetstencil","category":"page"},{"location":"reference/grid/#Ferrite.ExclusiveTopology","page":"Grid & AbstractGrid","title":"Ferrite.ExclusiveTopology","text":"ExclusiveTopology(grid::AbstractGrid)\n\nThe experimental feature ExclusiveTopology saves topological (connectivity/neighborhood) data of the grid. Only the highest dimensional neighborhood is saved. I.e., if something is connected by a face and an edge, only the face neighborhood is saved. The lower dimensional neighborhood is recomputed when calling getneighborhood if needed.\n\nFields\n\nvertex_to_cell::AbstractArray{AbstractVector{Int}, 1}: global vertex id to all cells containing the vertex\ncell_neighbor::AbstractArray{AbstractVector{Int}, 1}: cellid to all connected cells\nface_neighbor::AbstractArray{AbstractVector{FaceIndex}, 2}: face_neighbor[cellid, local_face_id] -> neighboring faces\nedge_neighbor::AbstractArray{AbstractVector{EdgeIndex}, 2}: edge_neighbor[cellid, local_edge_id] -> neighboring edges\nvertex_neighbor::AbstractArray{AbstractVector{VertexIndex}, 2}: vertex_neighbor[cellid, local_vertex_id] -> neighboring vertices\nface_skeleton::Union{Vector{FaceIndex}, Nothing}: List of unique faces in the grid given as FaceIndex\nedge_skeleton::Union{Vector{EdgeIndex}, Nothing}: List of unique edges in the grid given as EdgeIndex\nvertex_skeleton::Union{Vector{VertexIndex}, Nothing}: List of unique vertices in the grid given as VertexIndex\n\nwarning: Limitations\nThe implementation only works with conforming grids, i.e. grids without \"hanging nodes\". Non-conforming grids will give unexpected results. Grids with embedded cells (different reference dimension compared to the spatial dimension) are not supported, and will error on construction.\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.getneighborhood","page":"Grid & AbstractGrid","title":"Ferrite.getneighborhood","text":"getneighborhood(topology, grid::AbstractGrid, cellidx::CellIndex, include_self=false)\ngetneighborhood(topology, grid::AbstractGrid, faceidx::FaceIndex, include_self=false)\ngetneighborhood(topology, grid::AbstractGrid, vertexidx::VertexIndex, include_self=false)\ngetneighborhood(topology, grid::AbstractGrid, edgeidx::EdgeIndex, include_self=false)\n\nReturns all connected entities of the same type as defined by the respective topology. If include_self is true, the given entity is included in the returned list as well.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.facetskeleton","page":"Grid & AbstractGrid","title":"Ferrite.facetskeleton","text":"facetskeleton(top::ExclusiveTopology, grid::AbstractGrid)\n\nMaterializes the skeleton from the neighborhood information by returning an iterable over the unique facets in the grid, described by FacetIndex.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.vertex_star_stencils","page":"Grid & AbstractGrid","title":"Ferrite.vertex_star_stencils","text":"vertex_star_stencils(top::ExclusiveTopology, grid::Grid) -> AbstractVector{AbstractVector{VertexIndex}}\n\nComputes the stencils induced by the edge connectivity of the vertices.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getstencil","page":"Grid & AbstractGrid","title":"Ferrite.getstencil","text":"getstencil(top::ArrayOfVectorViews{VertexIndex, 1}, grid::AbstractGrid, vertex_idx::VertexIndex) -> AbstractVector{VertexIndex}\n\nGet an iterateable over the stencil members for a given local entity.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Grid-Sets-Utility","page":"Grid & AbstractGrid","title":"Grid Sets Utility","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"addcellset!\naddfacetset!\naddboundaryfacetset!\naddvertexset!\naddboundaryvertexset!\naddnodeset!","category":"page"},{"location":"reference/grid/#Ferrite.addcellset!","page":"Grid & AbstractGrid","title":"Ferrite.addcellset!","text":"addcellset!(grid::AbstractGrid, name::String, cellid::AbstractVecOrSet{Int})\naddcellset!(grid::AbstractGrid, name::String, f::function; all::Bool=true)\n\nAdds a cellset to the grid with key name. Cellsets are typically used to define subdomains of the problem, e.g. two materials in the computational domain. The DofHandler can construct different fields which live not on the whole domain, but rather on a cellset. all=true implies that f(x) must return true for all nodal coordinates x in the cell if the cell should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\naddcellset!(grid, \"left\", Set((1,3))) #add cells with id 1 and 3 to cellset left\naddcellset!(grid, \"right\", x -> norm(x[1]) < 2.0 ) #add cell to cellset right, if x[1] of each cell's node is smaller than 2.0\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addfacetset!","page":"Grid & AbstractGrid","title":"Ferrite.addfacetset!","text":"addfacetset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FacetIndex})\naddfacetset!(grid::AbstractGrid, name::String, f::Function; all::Bool=true)\n\nAdds a facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\naddfacetset!(grid, \"right\", Set((FacetIndex(2,2), FacetIndex(4,2)))) #see grid manual example for reference\naddfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0) #see incompressible elasticity example for reference\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addboundaryfacetset!","page":"Grid & AbstractGrid","title":"Ferrite.addboundaryfacetset!","text":"addboundaryfacetset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)\n\nAdds a boundary facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets are used to initialize Dirichlet structs, that are needed to specify the boundary for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addvertexset!","page":"Grid & AbstractGrid","title":"Ferrite.addvertexset!","text":"addvertexset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FaceIndex})\naddvertexset!(grid::AbstractGrid, name::String, f::Function)\n\nAdds a vertexset to the grid with key name. A vertexset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). Vertexsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler.\n\naddvertexset!(grid, \"right\", Set((VertexIndex(2,2), VertexIndex(4,2))))\naddvertexset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addboundaryvertexset!","page":"Grid & AbstractGrid","title":"Ferrite.addboundaryvertexset!","text":"addboundaryvertexset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)\n\nAdds a boundary vertexset to the grid with key name. A vertexset maps a String key to an OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). all=true implies that f(x) must return true for all nodal coordinates x on the face if the face should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addnodeset!","page":"Grid & AbstractGrid","title":"Ferrite.addnodeset!","text":"addnodeset!(grid::AbstractGrid, name::String, nodeid::AbstractVecOrSet{Int})\naddnodeset!(grid::AbstractGrid, name::String, f::Function)\n\nAdds a nodeset::OrderedSet{Int} to the grid's nodesets with key name. Has the same interface as addcellset. However, instead of mapping a cell id to the String key, a set of node ids is returned.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Multithreaded-Assembly","page":"Grid & AbstractGrid","title":"Multithreaded Assembly","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"create_coloring","category":"page"},{"location":"reference/grid/#Ferrite.create_coloring","page":"Grid & AbstractGrid","title":"Ferrite.create_coloring","text":"create_coloring(g::Grid, cellset=1:getncells(g); alg::ColoringAlgorithm)\n\nCreate a coloring of the cells in grid g such that no neighboring cells have the same color. If only a subset of cells should be colored, the cells to color can be specified by cellset.\n\nReturns a vector of vectors with cell indexes, e.g.:\n\nret = [\n [1, 3, 5, 10, ...], # cells for color 1\n [2, 4, 6, 12, ...], # cells for color 2\n]\n\nTwo different algorithms are available, specified with the alg keyword argument:\n\nalg = ColoringAlgorithm.WorkStream (default): Three step algorithm from Turcksin et al. [11], albeit with a greedy coloring in the second step. Generally results in more colors than ColoringAlgorithm.Greedy, however the cells are more equally distributed among the colors.\nalg = ColoringAlgorithm.Greedy: greedy algorithm that works well for structured quadrilateral grids such as e.g. quadrilateral grids from generate_grid.\n\nThe resulting colors can be visualized using Ferrite.write_cell_colors.\n\nnote: Cell to color mapping\nIn a previous version of Ferrite this function returned a dictionary mapping cell ID to color numbers as the first argument. If you need this mapping you can create it using the following construct:colors = create_coloring(...)\ncell_colormap = Dict{Int,Int}(\n cellid => color for (color, cellids) in enumerate(final_colors) for cellid in cellids\n)\n\nReferences\n\n[11] Turcksin et al. ACM Trans. Math. Softw. 43 (2016).\n\n\n\n\n\n","category":"function"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"EditURL = \"../literate-tutorials/porous_media.jl\"","category":"page"},{"location":"tutorials/porous_media/#Porous-media","page":"Porous media","title":"Porous media","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Porous media is a two-phase material, consisting of solid parts and a liquid occupying the pores inbetween. Using the porous media theory, we can model such a material without explicitly resolving the microstructure, but by considering the interactions between the solid and liquid. In this example, we will additionally consider larger linear elastic solid aggregates that are impermeable. Hence, there is no liquids in these particles and the only unknown variable is the displacement field :u. In the porous media, denoted the matrix, we have both the displacement field, :u, as well as the liquid pressure, :p, as unknown. The simulation result is shown below","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"(Image: Pressure evolution.)","category":"page"},{"location":"tutorials/porous_media/#Theory-of-porous-media","page":"Porous media","title":"Theory of porous media","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The strong forms are given as","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nboldsymbolsigma(boldsymbolepsilon p) cdot boldsymbolnabla = boldsymbol0 \ndotPhi(boldsymbolepsilon p) + boldsymbolw(p) cdot boldsymbolnabla = 0\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"where boldsymbolepsilon = leftboldsymboluotimesboldsymbolnablaright^mathrmsym The constitutive relationships are","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nboldsymbolsigma = boldsymbolmathsfCboldsymbolepsilon - alpha p boldsymbolI \nboldsymbolw = - k boldsymbolnabla p \nPhi = phi + alpha mathrmtr(boldsymbolepsilon) + beta p\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"with boldsymbolmathsfC=2G boldsymbolmathsfI^mathrmdev + 3K boldsymbolIotimesboldsymbolI. The material parameters are then the shear modulus, G, bulk modulus, K, permeability, k, Biot's coefficient, alpha, and liquid compressibility, beta. The porosity, phi, doesn't enter into the equations (A different porosity leads to different skeleton stiffness and permeability).","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The variational (weak) form can then be derived for the variations boldsymboldelta u and delta p as","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nint_Omega leftleftboldsymboldelta uotimesboldsymbolnablaright^mathrmsym\nboldsymbolmathsfCboldsymbolepsilon - boldsymboldelta u cdot boldsymbolnabla alpha pright mathrmdOmega\n= int_Gamma boldsymboldelta u cdot boldsymbolt mathrmd Gamma \nint_Omega leftdelta p leftalpha dotboldsymbolu cdot boldsymbolnabla + beta dotpright +\nboldsymbolnabla(delta p) cdot k boldsymbolnablaright mathrmdOmega\n= int_Gamma delta p w_mathrmn mathrmd Gamma\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"where boldsymbolt=boldsymbolncdotboldsymbolsigma is the traction and w_mathrmn = boldsymbolncdotboldsymbolw is the normal flux.","category":"page"},{"location":"tutorials/porous_media/#Finite-element-form","page":"Porous media","title":"Finite element form","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Discretizing in space using finite elements, we obtain the vector equation r_i = f_i^mathrmint - f_i^mathrmext where f^mathrmext are the external \"forces\", and f_i^mathrmint are the internal \"forces\". We split this into the displacement part r_i^mathrmu = f_i^mathrmintu - f_i^mathrmextu and pressure part r_i^mathrmp = f_i^mathrmintp - f_i^mathrmextp to obtain the discretized equation system","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nf_i^mathrmintu = int_Omega boldsymboldelta N^mathrmu_iotimesboldsymbolnabla^mathrmsym boldsymbolmathsfC boldsymboluotimesboldsymbolnabla^mathrmsym \n- boldsymboldelta N^mathrmu_i cdot boldsymbolnabla alpha p mathrmdOmega\n= int_Gamma boldsymboldelta N^mathrmu_i cdot boldsymbolt mathrmd Gamma \nf_i^mathrmintp = int_Omega delta N_i^mathrmp alpha dotboldsymbolucdotboldsymbolnabla + betadotp + boldsymbolnabla(delta N_i^mathrmp) cdot k boldsymbolnabla(p) mathrmdOmega\n= int_Gamma delta N_i^mathrmp w_mathrmn mathrmd Gamma\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Approximating the time-derivatives, dotboldsymboluapprox leftboldsymbolu-^nboldsymbolurightDelta t and dotpapprox leftp-^nprightDelta t, we can implement the finite element equations in the residual form r_i(boldsymbola(t) t) = 0 where the vector boldsymbola contains all unknown displacements u_i and pressures p_i.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The jacobian, K_ij = partial r_ipartial a_j, is then split into four parts,","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nK_ij^mathrmuu = fracpartial r_i^mathrmupartial u_j = int_Omega boldsymboldelta N^mathrmu_iotimesboldsymbolnabla^mathrmsym boldsymbolmathsfC boldsymbolN_j^mathrmuotimesboldsymbolnabla^mathrmsym mathrmdOmega \nK_ij^mathrmup = fracpartial r_i^mathrmupartial p_j = - int_Omega boldsymboldelta N^mathrmu_i cdot boldsymbolnabla alpha N_j^mathrmp mathrmdOmega \nK_ij^mathrmpu = fracpartial r_i^mathrmppartial u_j = int_Omega delta N_i^mathrmp fracalphaDelta t boldsymbolN_j^mathrmu cdotboldsymbolnabla mathrmdOmega\nK_ij^mathrmpp = fracpartial r_i^mathrmppartial p_j = int_Omega delta N_i^mathrmp fracN_j^mathrmpDelta t + boldsymbolnabla(delta N_i^mathrmp) cdot k boldsymbolnabla(N_j^mathrmp) mathrmdOmega\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"We could assemble one stiffness matrix and one mass matrix, which would be constant, but for simplicity we only consider a single system matrix that depends on the time step, and assemble this for each step. The equations are still linear, so no iterations are required.","category":"page"},{"location":"tutorials/porous_media/#Implementation","page":"Porous media","title":"Implementation","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"We now solve the problem step by step. The full program with fewer comments is found in the final section","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Required packages","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"using Ferrite, FerriteMeshParser, Tensors, WriteVTK","category":"page"},{"location":"tutorials/porous_media/#Elasticity","page":"Porous media","title":"Elasticity","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"We start by defining the elastic material type, containing the elastic stiffness, for the linear elastic impermeable solid aggregates.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"struct Elastic{T}\n C::SymmetricTensor{4, 2, T, 9}\nend\nfunction Elastic(; E = 20.0e3, ν = 0.3)\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n I2 = one(SymmetricTensor{2, 2})\n I4vol = I2 ⊗ I2\n I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3\n return Elastic(2G * I4dev + K * I4vol)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Next, we define the element routine for the solid aggregates, where we dispatch on the Elastic material struct. Note that the unused inputs here are used for the porous matrix below.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function element_routine!(Ke, re, material::Elastic, cv, cell, a, args...)\n reinit!(cv, cell)\n n_basefuncs = getnbasefunctions(cv)\n\n for q_point in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, q_point)\n ϵ = function_symmetric_gradient(cv, q_point, a)\n σ = material.C ⊡ ϵ\n for i in 1:n_basefuncs\n δ∇N = shape_symmetric_gradient(cv, q_point, i)\n re[i] += (δ∇N ⊡ σ) * dΩ\n for j in 1:n_basefuncs\n ∇N = shape_symmetric_gradient(cv, q_point, j)\n Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#PoroElasticity","page":"Porous media","title":"PoroElasticity","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"To define the poroelastic material, we re-use the elastic part from above for the skeleton, and add the additional required material parameters.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"struct PoroElastic{T}\n elastic::Elastic{T} ## Skeleton stiffness\n k::T ## Permeability of liquid [mm^4/(Ns)]\n ϕ::T ## Porosity [-]\n α::T ## Biot's coefficient [-]\n β::T ## Liquid compressibility [1/MPa]\nend\nPoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β);\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The element routine requires a few more inputs since we have two fields, as well as the dependence on the rates of the displacements and pressure. Again, we dispatch on the material type.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh)\n # Setup cellvalues and give easier names\n reinit!.(cvs, (cell,))\n cv_u, cv_p = cvs\n dr_u = dof_range(sdh, :u)\n dr_p = dof_range(sdh, :p)\n\n C = m.elastic.C ## Elastic stiffness\n\n # Assemble stiffness and force vectors\n for q_point in 1:getnquadpoints(cv_u)\n dΩ = getdetJdV(cv_u, q_point)\n p = function_value(cv_p, q_point, a, dr_p)\n p_old = function_value(cv_p, q_point, a_old, dr_p)\n pdot = (p - p_old) / Δt\n ∇p = function_gradient(cv_p, q_point, a, dr_p)\n ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u)\n tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u)\n tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt\n σ_eff = C ⊡ ϵ\n # Variation of u_i\n for (iᵤ, Iᵤ) in pairs(dr_u)\n ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ)\n div_δNu = shape_divergence(cv_u, q_point, iᵤ)\n re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ)\n Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ\n end\n end\n # Variation of p_i\n for (iₚ, Iₚ) in pairs(dr_p)\n δNp = shape_value(cv_p, q_point, iₚ)\n ∇δNp = shape_gradient(cv_p, q_point, iₚ)\n re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n div_Nu = shape_divergence(cv_u, q_point, jᵤ)\n Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n ∇Np = shape_gradient(cv_p, q_point, jₚ)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Assembly","page":"Porous media","title":"Assembly","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"To organize the different domains, we'll first define a container type","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"struct FEDomain{M, CV, SDH <: SubDofHandler}\n material::M\n cellvalues::CV\n sdh::SDH\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"And then we can loop over a vector of such domains, allowing us to loop over each domain, to assemble the contributions from each cell in that domain (given by the SubDofHandler's cellset)","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt)\n assembler = start_assemble(K, r)\n for domain in domains\n doassemble!(assembler, domain, a, a_old, Δt)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"For one domain (corresponding to a specific SubDofHandler), we can then loop over all cells in its cellset. Doing this in a separate function (instead of a nested loop), ensures that the calls to the element_routine are type stable, which can be important for good performance.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function doassemble!(assembler, domain::FEDomain, a, a_old, Δt)\n material = domain.material\n cv = domain.cellvalues\n sdh = domain.sdh\n n = ndofs_per_cell(sdh)\n Ke = zeros(n, n)\n re = zeros(n)\n ae_old = zeros(n)\n ae = zeros(n)\n for cell in CellIterator(sdh)\n # copy values from a to ae\n map!(i -> a[i], ae, celldofs(cell))\n map!(i -> a_old[i], ae_old, celldofs(cell))\n fill!(Ke, 0)\n fill!(re, 0)\n element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh)\n assemble!(assembler, celldofs(cell), Ke, re)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Mesh-import","page":"Porous media","title":"Mesh import","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"In this example, we import the mesh from the Abaqus input file, porous_media_0p25.inp using FerriteMeshParser's get_ferrite_grid function. We then create one cellset for each phase (solid and porous) for each element type. These 4 sets will later be used in their own SubDofHandler","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function get_grid()\n # Import grid from abaqus mesh\n grid = get_ferrite_grid(joinpath(@__DIR__, \"porous_media_0p25.inp\"))\n\n # Create cellsets for each fieldhandler\n addcellset!(grid, \"solid3\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"solid4\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS4R\")))\n addcellset!(grid, \"porous3\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"porous4\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS4R\")))\n return grid\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Problem-setup","page":"Porous media","title":"Problem setup","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Define the finite element interpolation, integration, and boundary conditions.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function setup_problem(; t_rise = 0.1, u_max = -0.1)\n\n grid = get_grid()\n\n # Define materials\n m_solid = Elastic(; E = 20.0e3, ν = 0.3)\n m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8)\n\n # Define interpolations\n ipu_quad = Lagrange{RefQuadrilateral, 2}()^2\n ipu_tri = Lagrange{RefTriangle, 2}()^2\n ipp_quad = Lagrange{RefQuadrilateral, 1}()\n ipp_tri = Lagrange{RefTriangle, 1}()\n\n # Quadrature rules\n qr_quad = QuadratureRule{RefQuadrilateral}(2)\n qr_tri = QuadratureRule{RefTriangle}(2)\n\n # CellValues\n cvu_quad = CellValues(qr_quad, ipu_quad)\n cvu_tri = CellValues(qr_tri, ipu_tri)\n cvp_quad = CellValues(qr_quad, ipp_quad)\n cvp_tri = CellValues(qr_tri, ipp_tri)\n\n # Setup the DofHandler\n dh = DofHandler(grid)\n # Solid quads\n sdh_solid_quad = SubDofHandler(dh, getcellset(grid, \"solid4\"))\n add!(sdh_solid_quad, :u, ipu_quad)\n # Solid triangles\n sdh_solid_tri = SubDofHandler(dh, getcellset(grid, \"solid3\"))\n add!(sdh_solid_tri, :u, ipu_tri)\n # Porous quads\n sdh_porous_quad = SubDofHandler(dh, getcellset(grid, \"porous4\"))\n add!(sdh_porous_quad, :u, ipu_quad)\n add!(sdh_porous_quad, :p, ipp_quad)\n # Porous triangles\n sdh_porous_tri = SubDofHandler(dh, getcellset(grid, \"porous3\"))\n add!(sdh_porous_tri, :u, ipu_tri)\n add!(sdh_porous_tri, :p, ipp_tri)\n\n close!(dh)\n\n # Setup the domains\n domains = [\n FEDomain(m_solid, cvu_quad, sdh_solid_quad),\n FEDomain(m_solid, cvu_tri, sdh_solid_tri),\n FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad),\n FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri),\n ]\n\n # Boundary conditions\n # Sliding for u, except top which is compressed\n # Sealed for p, except top with prescribed zero pressure\n addfacetset!(dh.grid, \"sides\", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0)\n addfacetset!(dh.grid, \"top\", x -> x[2] ≈ 10.0)\n ch = ConstraintHandler(dh)\n add!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"sides\"), (x, t) -> zero(Vec{1}), [1]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"top\"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2]))\n add!(ch, Dirichlet(:p, getfacetset(grid, \"top_p\"), (x, t) -> 0.0))\n close!(ch)\n\n return dh, ch, domains\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Solving","page":"Porous media","title":"Solving","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Given the DofHandler, ConstraintHandler, and CellValues, we can solve the problem by stepping through the time history","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0)\n K = allocate_matrix(dh)\n r = zeros(ndofs(dh))\n a = zeros(ndofs(dh))\n a_old = copy(a)\n pvd = paraview_collection(\"porous_media\")\n step = 0\n for t in 0:Δt:t_total\n if t > 0\n update!(ch, t)\n apply!(a, ch)\n doassemble!(K, r, domains, a, a_old, Δt)\n apply_zero!(K, r, ch)\n Δa = -K \\ r\n apply_zero!(Δa, ch)\n a .+= Δa\n copyto!(a_old, a)\n end\n step += 1\n VTKGridFile(\"porous_media_$step\", dh) do vtk\n write_solution(vtk, dh, a)\n pvd[t] = vtk\n end\n end\n return vtk_save(pvd)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Finally we call the functions to actually run the code","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"dh, ch, domains = setup_problem()\nsolve(dh, ch, domains);\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#porous-media-plain-program","page":"Porous media","title":"Plain program","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Here follows a version of the program without any comments. The file is also available here: porous_media.jl.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"using Ferrite, FerriteMeshParser, Tensors, WriteVTK\n\nstruct Elastic{T}\n C::SymmetricTensor{4, 2, T, 9}\nend\nfunction Elastic(; E = 20.0e3, ν = 0.3)\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n I2 = one(SymmetricTensor{2, 2})\n I4vol = I2 ⊗ I2\n I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3\n return Elastic(2G * I4dev + K * I4vol)\nend;\n\nfunction element_routine!(Ke, re, material::Elastic, cv, cell, a, args...)\n reinit!(cv, cell)\n n_basefuncs = getnbasefunctions(cv)\n\n for q_point in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, q_point)\n ϵ = function_symmetric_gradient(cv, q_point, a)\n σ = material.C ⊡ ϵ\n for i in 1:n_basefuncs\n δ∇N = shape_symmetric_gradient(cv, q_point, i)\n re[i] += (δ∇N ⊡ σ) * dΩ\n for j in 1:n_basefuncs\n ∇N = shape_symmetric_gradient(cv, q_point, j)\n Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ\n end\n end\n end\n return\nend;\n\nstruct PoroElastic{T}\n elastic::Elastic{T} ## Skeleton stiffness\n k::T ## Permeability of liquid [mm^4/(Ns)]\n ϕ::T ## Porosity [-]\n α::T ## Biot's coefficient [-]\n β::T ## Liquid compressibility [1/MPa]\nend\nPoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β);\n\nfunction element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh)\n # Setup cellvalues and give easier names\n reinit!.(cvs, (cell,))\n cv_u, cv_p = cvs\n dr_u = dof_range(sdh, :u)\n dr_p = dof_range(sdh, :p)\n\n C = m.elastic.C ## Elastic stiffness\n\n # Assemble stiffness and force vectors\n for q_point in 1:getnquadpoints(cv_u)\n dΩ = getdetJdV(cv_u, q_point)\n p = function_value(cv_p, q_point, a, dr_p)\n p_old = function_value(cv_p, q_point, a_old, dr_p)\n pdot = (p - p_old) / Δt\n ∇p = function_gradient(cv_p, q_point, a, dr_p)\n ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u)\n tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u)\n tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt\n σ_eff = C ⊡ ϵ\n # Variation of u_i\n for (iᵤ, Iᵤ) in pairs(dr_u)\n ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ)\n div_δNu = shape_divergence(cv_u, q_point, iᵤ)\n re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ)\n Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ\n end\n end\n # Variation of p_i\n for (iₚ, Iₚ) in pairs(dr_p)\n δNp = shape_value(cv_p, q_point, iₚ)\n ∇δNp = shape_gradient(cv_p, q_point, iₚ)\n re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n div_Nu = shape_divergence(cv_u, q_point, jᵤ)\n Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n ∇Np = shape_gradient(cv_p, q_point, jₚ)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ\n end\n end\n end\n return\nend;\n\nstruct FEDomain{M, CV, SDH <: SubDofHandler}\n material::M\n cellvalues::CV\n sdh::SDH\nend;\n\nfunction doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt)\n assembler = start_assemble(K, r)\n for domain in domains\n doassemble!(assembler, domain, a, a_old, Δt)\n end\n return\nend;\n\nfunction doassemble!(assembler, domain::FEDomain, a, a_old, Δt)\n material = domain.material\n cv = domain.cellvalues\n sdh = domain.sdh\n n = ndofs_per_cell(sdh)\n Ke = zeros(n, n)\n re = zeros(n)\n ae_old = zeros(n)\n ae = zeros(n)\n for cell in CellIterator(sdh)\n # copy values from a to ae\n map!(i -> a[i], ae, celldofs(cell))\n map!(i -> a_old[i], ae_old, celldofs(cell))\n fill!(Ke, 0)\n fill!(re, 0)\n element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh)\n assemble!(assembler, celldofs(cell), Ke, re)\n end\n return\nend;\n\nfunction get_grid()\n # Import grid from abaqus mesh\n grid = get_ferrite_grid(joinpath(@__DIR__, \"porous_media_0p25.inp\"))\n\n # Create cellsets for each fieldhandler\n addcellset!(grid, \"solid3\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"solid4\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS4R\")))\n addcellset!(grid, \"porous3\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"porous4\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS4R\")))\n return grid\nend;\n\nfunction setup_problem(; t_rise = 0.1, u_max = -0.1)\n\n grid = get_grid()\n\n # Define materials\n m_solid = Elastic(; E = 20.0e3, ν = 0.3)\n m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8)\n\n # Define interpolations\n ipu_quad = Lagrange{RefQuadrilateral, 2}()^2\n ipu_tri = Lagrange{RefTriangle, 2}()^2\n ipp_quad = Lagrange{RefQuadrilateral, 1}()\n ipp_tri = Lagrange{RefTriangle, 1}()\n\n # Quadrature rules\n qr_quad = QuadratureRule{RefQuadrilateral}(2)\n qr_tri = QuadratureRule{RefTriangle}(2)\n\n # CellValues\n cvu_quad = CellValues(qr_quad, ipu_quad)\n cvu_tri = CellValues(qr_tri, ipu_tri)\n cvp_quad = CellValues(qr_quad, ipp_quad)\n cvp_tri = CellValues(qr_tri, ipp_tri)\n\n # Setup the DofHandler\n dh = DofHandler(grid)\n # Solid quads\n sdh_solid_quad = SubDofHandler(dh, getcellset(grid, \"solid4\"))\n add!(sdh_solid_quad, :u, ipu_quad)\n # Solid triangles\n sdh_solid_tri = SubDofHandler(dh, getcellset(grid, \"solid3\"))\n add!(sdh_solid_tri, :u, ipu_tri)\n # Porous quads\n sdh_porous_quad = SubDofHandler(dh, getcellset(grid, \"porous4\"))\n add!(sdh_porous_quad, :u, ipu_quad)\n add!(sdh_porous_quad, :p, ipp_quad)\n # Porous triangles\n sdh_porous_tri = SubDofHandler(dh, getcellset(grid, \"porous3\"))\n add!(sdh_porous_tri, :u, ipu_tri)\n add!(sdh_porous_tri, :p, ipp_tri)\n\n close!(dh)\n\n # Setup the domains\n domains = [\n FEDomain(m_solid, cvu_quad, sdh_solid_quad),\n FEDomain(m_solid, cvu_tri, sdh_solid_tri),\n FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad),\n FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri),\n ]\n\n # Boundary conditions\n # Sliding for u, except top which is compressed\n # Sealed for p, except top with prescribed zero pressure\n addfacetset!(dh.grid, \"sides\", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0)\n addfacetset!(dh.grid, \"top\", x -> x[2] ≈ 10.0)\n ch = ConstraintHandler(dh)\n add!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"sides\"), (x, t) -> zero(Vec{1}), [1]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"top\"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2]))\n add!(ch, Dirichlet(:p, getfacetset(grid, \"top_p\"), (x, t) -> 0.0))\n close!(ch)\n\n return dh, ch, domains\nend;\n\nfunction solve(dh, ch, domains; Δt = 0.025, t_total = 1.0)\n K = allocate_matrix(dh)\n r = zeros(ndofs(dh))\n a = zeros(ndofs(dh))\n a_old = copy(a)\n pvd = paraview_collection(\"porous_media\")\n step = 0\n for t in 0:Δt:t_total\n if t > 0\n update!(ch, t)\n apply!(a, ch)\n doassemble!(K, r, domains, a, a_old, Δt)\n apply_zero!(K, r, ch)\n Δa = -K \\ r\n apply_zero!(Δa, ch)\n a .+= Δa\n copyto!(a_old, a)\n end\n step += 1\n VTKGridFile(\"porous_media_$step\", dh) do vtk\n write_solution(vtk, dh, a)\n pvd[t] = vtk\n end\n end\n return vtk_save(pvd)\nend;\n\ndh, ch, domains = setup_problem()\nsolve(dh, ch, domains);","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"EditURL = \"../literate-tutorials/computational_homogenization.jl\"","category":"page"},{"location":"tutorials/computational_homogenization/#tutorial-computational-homogenization","page":"Computational homogenization","title":"Computational homogenization","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"(Image: )","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Figure 1: von Mises stress in an RVE with 5 stiff inclusions embedded in a softer matrix material that is loaded in shear. The problem is solved by using homogeneous Dirichlet boundary conditions (left) and (strong) periodic boundary conditions (right).","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"tip: Tip\nThis example is also available as a Jupyter notebook: computational_homogenization.ipynb.","category":"page"},{"location":"tutorials/computational_homogenization/#Introduction","page":"Computational homogenization","title":"Introduction","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"In this example we will solve the Representative Volume Element (RVE) problem for computational homogenization of linear elasticity and compute the effective/homogenized stiffness of an RVE with 5 stiff circular inclusions embedded in a softer matrix material (see Figure 1).","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"It is possible to obtain upper and lower bounds on the stiffness analytically, see for example Rule of mixtures. An upper bound is obtained from the Voigt model, where the strain is assumed to be the same in the two constituents,","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_mathrmVoigt = v_mathrmm mathsfE_mathrmm +\n(1 - v_mathrmm) mathsfE_mathrmi","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where v_mathrmm is the volume fraction of the matrix material, and where mathsfE_mathrmm and mathsfE_mathrmi are the individual stiffness for the matrix material and the inclusions, respectively. The lower bound is obtained from the Reuss model, where the stress is assumed to be the same in the two constituents,","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_mathrmReuss = left(v_mathrmm mathsfE_mathrmm^-1 +\n(1 - v_mathrmm) mathsfE_mathrmi^-1 right)^-1","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"However, neither of these assumptions are, in general, very close to the \"truth\" which is why it is of interest to computationally find the homogenized properties for a given RVE.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The canonical version of the RVE problem can be formulated as follows: For given homogenized field barboldsymbolu, barboldsymbolvarepsilon = boldsymbolvarepsilonbarboldsymbolu, find boldsymbolu in mathbbU_Box, boldsymbolt in mathbbT_Box such that","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"frac1Omega_Box int_Omega_Boxboldsymbolvarepsilondeltaboldsymbolu\n mathsfE boldsymbolvarepsilonboldsymbolu mathrmdOmega\n- frac1Omega_Box int_Gamma_Boxdelta boldsymbolu cdot\nboldsymbolt mathrmdGamma = 0 quad\nforall delta boldsymbolu in mathbbU_Boxquad (1mathrma)\n- frac1Omega_Box int_Gamma_Boxdelta boldsymbolt cdot\nboldsymbolu mathrmdGamma = - barboldsymbolvarepsilon \nleft frac1Omega_Box int_Gamma_Boxdelta boldsymbolt otimes\nboldsymbolx - barboldsymbolx mathrmdGamma right\nquad forall delta boldsymbolt in mathbbT_Box quad (1mathrmb)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where boldsymbolu = barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx + boldsymbolu^mu, where Omega_Box and Omega_Box are the domain and volume of the RVE, where Gamma_Box is the boundary, and where mathbbU_Box, mathbbT_Box are set of \"sufficiently regular\" functions defined on the RVE.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"This system is not solvable without introducing extra restrictions on mathbbU_Box, mathbbT_Box. In this example we will consider the common cases of Dirichlet boundary conditions and (strong) periodic boundary conditions.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Dirichlet boundary conditions","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can introduce the more restrictive sets of mathbbU_Box:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"beginalign*\nmathbbU_Box^mathrmD = leftboldsymbolu in mathbbU_Box boldsymbolu\n= barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx\n mathrmon Gamma_Boxright\nmathbbU_Box^mathrmD0 = leftboldsymbolu in mathbbU_Box boldsymbolu\n= boldsymbol0 mathrmon Gamma_Boxright\nendalign*","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"and use these as trial and test sets to obtain a solvable RVE problem pertaining to Dirichlet boundary conditions. Eq. (1mathrmb) is trivially fulfilled, the second term of Eq. (1mathrma) vanishes, and we are left with the following problem: Find boldsymbolu in mathbbU_Box^mathrmD that solve","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"frac1Omega_Box int_Omega_Boxboldsymbolvarepsilondeltaboldsymbolu\n mathsfE boldsymbolvarepsilonboldsymbolu mathrmdOmega = 0\nquad forall delta boldsymbolu in mathbbU_Box^mathrmD0","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Note that, since boldsymbolu = barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx + boldsymbolu^mu, this problem is equivalent to solving for boldsymbolu^mu in mathbbU_Box^mathrmD0, which is what we will do in the implementation.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Periodic boundary conditions","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The RVE problem pertaining to periodic boundary conditions is obtained by restricting boldsymbolu^mu to be periodic, and boldsymbolt anti-periodic across the RVE. Similarly as for Dirichlet boundary conditions, Eq. (1mathrmb) is directly fulfilled, and the second term in Eq. (1mathrma) vanishes, with these restrictions, and we are left with the following problem: Find boldsymbolu^mu in mathbbU_Box^mathrmP0 such that","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"frac1Omega_Box int_Omega_Boxboldsymbolvarepsilondeltaboldsymbolu\n mathsfE (barboldsymbolvarepsilon + boldsymbolvarepsilon\nboldsymbolu^mu) mathrmdOmega = 0\nquad forall delta boldsymbolu in mathbbU_Box^mathrmP0","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathbbU_Box^mathrmP0 = leftboldsymbolu in mathbbU_Box\n llbracket boldsymbolu rrbracket_Box = boldsymbol0\n mathrmon Gamma_Box^+right","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where llbracket bullet rrbracket_Box = bullet(boldsymbolx^+) - bullet(boldsymbolx^-) defines the \"jump\" over the RVE, i.e. the difference between the value on the image part Gamma_Box^+ (coordinate boldsymbolx^+) and the mirror part Gamma_Box^- (coordinate boldsymbolx^-) of the boundary. To make sure this restriction holds in a strong sense we need a periodic mesh.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Note that it would be possible to solve for the total boldsymbolu directly by instead enforcing the jump to be equal to the jump in the macroscopic part, boldsymbolu^mathrmM, i.e.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"llbracket boldsymbolu rrbracket_Box =\nllbracket boldsymbolu^mathrmM rrbracket_Box =\nllbracket barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx\nrrbracket_Box =\nbarboldsymbolvarepsilon cdot boldsymbolx^+ - boldsymbolx^-","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Homogenization of effective properties","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"In general it is necessary to compute the homogenized stress and the stiffness on the fly, but since we in this example consider linear elasticity it is possible to compute the effective properties once and for all for a given RVE configuration. We do this by computing sensitivity fields for every independent strain component (6 in 3D, 3 in 2D). Thus, for a 2D problem, as in the implementation below, we compute sensitivities hatboldsymbolu_11, hatboldsymbolu_22, and hatboldsymbolu_12 = hatboldsymbolu_21 by using","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"barboldsymbolvarepsilon = beginpmatrix1 0 0 0endpmatrix quad\nbarboldsymbolvarepsilon = beginpmatrix0 0 0 1endpmatrix quad\nbarboldsymbolvarepsilon = beginpmatrix0 05 05 0endpmatrix","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"as the input to the RVE problem. When the sensitivies are solved we can compute the entries of the homogenized stiffness as follows","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_ijkl = fracpartial barsigma_ijpartial barvarepsilon_kl\n= barsigma_ij(hatboldsymbolu_kl)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where the homogenized stress, barboldsymbolsigma(boldsymbolu), is computed as the volume average of the stress in the RVE, i.e.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"barboldsymbolsigma(boldsymbolu) =\nfrac1Omega_Box int_Omega_Box boldsymbolsigma mathrmdOmega =\nfrac1Omega_Box int_Omega_Box\nmathsfE boldsymbolvarepsilonboldsymbolu mathrmdOmega","category":"page"},{"location":"tutorials/computational_homogenization/#Commented-program","page":"Computational homogenization","title":"Commented program","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Now we will see how this can be implemented in Ferrite. What follows is a program with comments in between which describe the different steps. You can also find the same program without comments at the end of the page, see Plain program.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"using Ferrite, SparseArrays, LinearAlgebra","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We first load the mesh file periodic-rve.msh (periodic-rve-coarse.msh for a coarser mesh). The mesh is generated with Gmsh, and we read it in as a Ferrite Grid using the FerriteGmsh.jl package:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"using FerriteGmsh\n\ngrid = togrid(\"periodic-rve.msh\")","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"grid = redirect_stdout(devnull) do #hide\n togrid(\"periodic-rve-coarse.msh\") #hide\nend #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Next we construct the interpolation and quadrature rule, and combining them into cellvalues as usual:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"dim = 2\nip = Lagrange{RefTriangle, 1}()^dim\nqr = QuadratureRule{RefTriangle}(2)\ncellvalues = CellValues(qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We define a dof handler with a displacement field :u:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Now we need to define boundary conditions. As discussed earlier we will solve the problem using (i) homogeneous Dirichlet boundary conditions, and (ii) periodic Dirichlet boundary conditions. We construct two different constraint handlers, one for each case. The Dirichlet boundary condition we have seen in many other examples. Here we simply define the condition that the field, :u, should have both components prescribed to 0 on the full boundary:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ch_dirichlet = ConstraintHandler(dh)\ndirichlet = Dirichlet(\n :u,\n union(getfacetset.(Ref(grid), [\"left\", \"right\", \"top\", \"bottom\"])...),\n (x, t) -> [0, 0],\n [1, 2]\n)\nadd!(ch_dirichlet, dirichlet)\nclose!(ch_dirichlet)\nupdate!(ch_dirichlet, 0.0)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"For periodic boundary conditions we use the PeriodicDirichlet constraint type, which is very similar to the Dirichlet type, but instead of a passing a facetset we pass a vector with \"facet pairs\", i.e. the mapping between mirror and image parts of the boundary. In this example the \"left\" and \"bottom\" boundaries are mirrors, and the \"right\" and \"top\" boundaries are the mirrors.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ch_periodic = ConstraintHandler(dh);\nperiodic = PeriodicDirichlet(\n :u,\n [\"left\" => \"right\", \"bottom\" => \"top\"],\n [1, 2]\n)\nadd!(ch_periodic, periodic)\nclose!(ch_periodic)\nupdate!(ch_periodic, 0.0)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"This will now constrain any degrees of freedom located on the mirror boundaries to the matching degree of freedom on the image boundaries. Internally this will create a number of AffineConstraints of the form u_i = 1 * u_j + 0:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"a = AffineConstraint(u_m, [u_i => 1], 0)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where u_m is the degree of freedom on the mirror and u_i the matching one on the image part. PeriodicDirichlet is thus simply just a more convenient way of constructing such affine constraints since it computes the degree of freedom mapping automatically.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"To simplify things we group the constraint handlers into a named tuple","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ch = (dirichlet = ch_dirichlet, periodic = ch_periodic);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now construct the sparse matrix. Note that, since we are using affine constraints, which need to modify the matrix sparsity pattern in order to account for the constraint equations, we construct the matrix for the periodic case by passing both the dof handler and the constraint handler.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"K = (\n dirichlet = allocate_matrix(dh),\n periodic = allocate_matrix(dh, ch.periodic),\n);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We define the fourth order elasticity tensor for the matrix material, and define the inclusions to have 10 times higher stiffness","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"λ, μ = 1.0e10, 7.0e9 # Lamé parameters\nδ(i, j) = i == j ? 1.0 : 0.0\nEm = SymmetricTensor{4, 2}(\n (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n)\nEi = 10 * Em;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"As mentioned above, in order to compute the apparent/homogenized stiffness we will solve the problem repeatedly with different macroscale strain tensors to compute the sensitvity of the homogenized stress, barboldsymbolsigma, w.r.t. the macroscopic strain, barboldsymbolvarepsilon. The corresponding unit strains are defined below, and will result in three different right-hand-sides:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"εᴹ = [\n SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading\n SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading\n SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading\n];\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The assembly function is nothing strange, and in particular there is no impact from the choice of boundary conditions, so the same function can be used for both cases. Since we want to solve the system 3 times, once for each macroscopic strain component, we assemble 3 right-hand-sides.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"function doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n ndpc = ndofs_per_cell(dh)\n Ke = zeros(ndpc, ndpc)\n fe = zeros(ndpc, length(εᴹ))\n f = zeros(ndofs(dh), length(εᴹ))\n assembler = start_assemble(K)\n\n for cell in CellIterator(dh)\n\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n fill!(Ke, 0)\n fill!(fe, 0)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ\n end\n for (rhs, ε) in enumerate(εᴹ)\n σᴹ = E ⊡ ε\n fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ\n end\n end\n end\n\n cdofs = celldofs(cell)\n assemble!(assembler, cdofs, Ke)\n f[cdofs, :] .+= fe\n end\n return f\nend;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now assemble the system. The assembly function modifies the matrix in-place, but return the right hand side(s) which we collect in another named tuple.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"rhs = (\n dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),\n periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ),\n);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The next step is to solve the systems. Since application of boundary conditions, using the apply! function, modifies both the matrix and the right hand sides we can not use it directly in this case since we want to reuse the matrix again for the next right hand sides. We could of course re-assemble the matrix for every right hand side, but that would not be very efficient. Instead we will use the get_rhs_data function, together with apply_rhs! in a later step. This will extract the necessary data from the matrix such that we can apply it for all the different right hand sides. Note that we call apply! with just the matrix and no right hand side.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"rhsdata = (\n dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),\n periodic = get_rhs_data(ch.periodic, K.periodic),\n)\n\napply!(K.dirichlet, ch.dirichlet)\napply!(K.periodic, ch.periodic)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now solve the problem(s). Note that we only use apply_rhs! in the loops below. The boundary conditions are already applied to the matrix above, so we only need to modify the right hand side.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"u = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nfor i in 1:size(rhs.dirichlet, 2)\n rhs_i = @view rhs.dirichlet[:, i] # Extract this RHS\n apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC\n u_i = cholesky(Symmetric(K.dirichlet)) \\ rhs_i # Solve\n apply!(u_i, ch.dirichlet) # Apply BC on the solution\n push!(u.dirichlet, u_i) # Save the solution vector\nend\n\nfor i in 1:size(rhs.periodic, 2)\n rhs_i = @view rhs.periodic[:, i] # Extract this RHS\n apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic) # Apply BC\n u_i = cholesky(Symmetric(K.periodic)) \\ rhs_i # Solve\n apply!(u_i, ch.periodic) # Apply BC on the solution\n push!(u.periodic, u_i) # Save the solution vector\nend","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"When the solution(s) are known we can compute the averaged stress, barboldsymbolsigma in the RVE. We define a function that does this, and also returns the von Mise stress in every quadrature point for visualization.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"function compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ)\n σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))\n σ̄Ω = zero(SymmetricTensor{2, 2})\n Ω = 0.0 # Total volume\n for cell in CellIterator(dh)\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])\n σ = E ⊡ (εᴹ + εμ)\n σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ))\n Ω += dΩ # Update total volume\n σ̄Ω += σ * dΩ # Update integrated stress\n end\n end\n σ̄ = σ̄Ω / Ω\n return σvM_qpdata, σ̄\nend;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We now compute the homogenized stress and von Mise stress for all cases","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"σ̄ = (\n dirichlet = SymmetricTensor{2, 2}[],\n periodic = SymmetricTensor{2, 2}[],\n)\nσ = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nprojector = L2Projector(ip, grid)\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.dirichlet, proj)\n push!(σ̄.dirichlet, σ̄_i)\nend\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.periodic, proj)\n push!(σ̄.periodic, σ̄_i)\nend","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The remaining thing is to compute the homogenized stiffness. As mentioned in the introduction we can find all the components from the average stress of the sensitivity fields that we have solved for","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_ijkl = barsigma_ij(hatboldsymbolu_kl)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"So we have now already computed all the components, and just need to gather the data in a fourth order tensor:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"E_dirichlet = SymmetricTensor{4, 2}(\n (i, j, k, l) -> begin\n if k == l == 1\n σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11\n elseif k == l == 2\n σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22\n else\n σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21\n end\n end\n)\n\nE_periodic = SymmetricTensor{4, 2}(\n (i, j, k, l) -> begin\n if k == l == 1\n σ̄.periodic[1][i, j]\n elseif k == l == 2\n σ̄.periodic[2][i, j]\n else\n σ̄.periodic[3][i, j]\n end\n end\n);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can check that the result are what we expect, namely that the stiffness with Dirichlet boundary conditions is higher than when using periodic boundary conditions, and that the Reuss assumption is an lower bound, and the Voigt assumption a upper bound. We first compute the volume fraction of the matrix, and then the Voigt and Reuss bounds:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"function matrix_volume_fraction(grid, cellvalues)\n V = 0.0 # Total volume\n Vm = 0.0 # Volume of the matrix\n for c in CellIterator(grid)\n reinit!(cellvalues, c)\n is_matrix = !(cellid(c) in getcellset(grid, \"inclusions\"))\n for qp in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, qp)\n V += dΩ\n if is_matrix\n Vm += dΩ\n end\n end\n end\n return Vm / V\nend\n\nvm = matrix_volume_fraction(grid, cellvalues)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"E_voigt = vm * Em + (1 - vm) * Ei\nE_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei));\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now compare the different computed stiffness tensors. We expect E_mathrmReuss leq E_mathrmPeriodicBC leq E_mathrmDirichletBC leq E_mathrmVoigt. A simple thing to compare are the eigenvalues of the tensors. Here we look at the first eigenvalue:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))\nround.(ev; digits = -8)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Finally, we export the solution and the stress field to a VTK file. For the export we also compute the macroscopic part of the displacement.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"uM = zeros(ndofs(dh))\n\nVTKGridFile(\"homogenization\", dh) do vtk\n for i in 1:3\n # Compute macroscopic solution\n apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x)\n # Dirichlet\n write_solution(vtk, dh, uM + u.dirichlet[i], \"_dirichlet_$i\")\n write_projection(vtk, projector, σ.dirichlet[i], \"σvM_dirichlet_$i\")\n # Periodic\n write_solution(vtk, dh, uM + u.periodic[i], \"_periodic_$i\")\n write_projection(vtk, projector, σ.periodic[i], \"σvM_periodic_$i\")\n end\nend;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/#homogenization-plain-program","page":"Computational homogenization","title":"Plain program","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Here follows a version of the program without any comments. The file is also available here: computational_homogenization.jl.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"using Ferrite, SparseArrays, LinearAlgebra\n\nusing FerriteGmsh\n\n# grid = togrid(\"periodic-rve-coarse.msh\")\ngrid = togrid(\"periodic-rve.msh\")\n\ndim = 2\nip = Lagrange{RefTriangle, 1}()^dim\nqr = QuadratureRule{RefTriangle}(2)\ncellvalues = CellValues(qr, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nch_dirichlet = ConstraintHandler(dh)\ndirichlet = Dirichlet(\n :u,\n union(getfacetset.(Ref(grid), [\"left\", \"right\", \"top\", \"bottom\"])...),\n (x, t) -> [0, 0],\n [1, 2]\n)\nadd!(ch_dirichlet, dirichlet)\nclose!(ch_dirichlet)\nupdate!(ch_dirichlet, 0.0)\n\nch_periodic = ConstraintHandler(dh);\nperiodic = PeriodicDirichlet(\n :u,\n [\"left\" => \"right\", \"bottom\" => \"top\"],\n [1, 2]\n)\nadd!(ch_periodic, periodic)\nclose!(ch_periodic)\nupdate!(ch_periodic, 0.0)\n\nch = (dirichlet = ch_dirichlet, periodic = ch_periodic);\n\nK = (\n dirichlet = allocate_matrix(dh),\n periodic = allocate_matrix(dh, ch.periodic),\n);\n\nλ, μ = 1.0e10, 7.0e9 # Lamé parameters\nδ(i, j) = i == j ? 1.0 : 0.0\nEm = SymmetricTensor{4, 2}(\n (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n)\nEi = 10 * Em;\n\nεᴹ = [\n SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading\n SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading\n SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading\n];\n\nfunction doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n ndpc = ndofs_per_cell(dh)\n Ke = zeros(ndpc, ndpc)\n fe = zeros(ndpc, length(εᴹ))\n f = zeros(ndofs(dh), length(εᴹ))\n assembler = start_assemble(K)\n\n for cell in CellIterator(dh)\n\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n fill!(Ke, 0)\n fill!(fe, 0)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ\n end\n for (rhs, ε) in enumerate(εᴹ)\n σᴹ = E ⊡ ε\n fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ\n end\n end\n end\n\n cdofs = celldofs(cell)\n assemble!(assembler, cdofs, Ke)\n f[cdofs, :] .+= fe\n end\n return f\nend;\n\nrhs = (\n dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),\n periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ),\n);\n\nrhsdata = (\n dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),\n periodic = get_rhs_data(ch.periodic, K.periodic),\n)\n\napply!(K.dirichlet, ch.dirichlet)\napply!(K.periodic, ch.periodic)\n\nu = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nfor i in 1:size(rhs.dirichlet, 2)\n rhs_i = @view rhs.dirichlet[:, i] # Extract this RHS\n apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC\n u_i = cholesky(Symmetric(K.dirichlet)) \\ rhs_i # Solve\n apply!(u_i, ch.dirichlet) # Apply BC on the solution\n push!(u.dirichlet, u_i) # Save the solution vector\nend\n\nfor i in 1:size(rhs.periodic, 2)\n rhs_i = @view rhs.periodic[:, i] # Extract this RHS\n apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic) # Apply BC\n u_i = cholesky(Symmetric(K.periodic)) \\ rhs_i # Solve\n apply!(u_i, ch.periodic) # Apply BC on the solution\n push!(u.periodic, u_i) # Save the solution vector\nend\n\nfunction compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ)\n σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))\n σ̄Ω = zero(SymmetricTensor{2, 2})\n Ω = 0.0 # Total volume\n for cell in CellIterator(dh)\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])\n σ = E ⊡ (εᴹ + εμ)\n σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ))\n Ω += dΩ # Update total volume\n σ̄Ω += σ * dΩ # Update integrated stress\n end\n end\n σ̄ = σ̄Ω / Ω\n return σvM_qpdata, σ̄\nend;\n\nσ̄ = (\n dirichlet = SymmetricTensor{2, 2}[],\n periodic = SymmetricTensor{2, 2}[],\n)\nσ = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nprojector = L2Projector(ip, grid)\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.dirichlet, proj)\n push!(σ̄.dirichlet, σ̄_i)\nend\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.periodic, proj)\n push!(σ̄.periodic, σ̄_i)\nend\n\nE_dirichlet = SymmetricTensor{4, 2}(\n (i, j, k, l) -> begin\n if k == l == 1\n σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11\n elseif k == l == 2\n σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22\n else\n σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21\n end\n end\n)\n\nE_periodic = SymmetricTensor{4, 2}(\n (i, j, k, l) -> begin\n if k == l == 1\n σ̄.periodic[1][i, j]\n elseif k == l == 2\n σ̄.periodic[2][i, j]\n else\n σ̄.periodic[3][i, j]\n end\n end\n);\n\nfunction matrix_volume_fraction(grid, cellvalues)\n V = 0.0 # Total volume\n Vm = 0.0 # Volume of the matrix\n for c in CellIterator(grid)\n reinit!(cellvalues, c)\n is_matrix = !(cellid(c) in getcellset(grid, \"inclusions\"))\n for qp in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, qp)\n V += dΩ\n if is_matrix\n Vm += dΩ\n end\n end\n end\n return Vm / V\nend\n\nvm = matrix_volume_fraction(grid, cellvalues)\n\nE_voigt = vm * Em + (1 - vm) * Ei\nE_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei));\n\nev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))\nround.(ev; digits = -8)\n\nuM = zeros(ndofs(dh))\n\nVTKGridFile(\"homogenization\", dh) do vtk\n for i in 1:3\n # Compute macroscopic solution\n apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x)\n # Dirichlet\n write_solution(vtk, dh, uM + u.dirichlet[i], \"_dirichlet_$i\")\n write_projection(vtk, projector, σ.dirichlet[i], \"σvM_dirichlet_$i\")\n # Periodic\n write_solution(vtk, dh, uM + u.periodic[i], \"_periodic_$i\")\n write_projection(vtk, projector, σ.periodic[i], \"σvM_periodic_$i\")\n end\nend;","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"topics/reference_shapes/#Reference-shapes","page":"Reference shapes","title":"Reference shapes","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"The reference shapes in Ferrite are used to define grid cells, function interpolations (i.e. shape functions), and quadrature rules. Currently, the following reference shapes are defined","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"RefLine\nRefTriangle\nRefQuadrilateral\nRefTetrahedron\nRefHexahedron\nRefPrism\nRefPyramid","category":"page"},{"location":"topics/reference_shapes/#Entity-naming","page":"Reference shapes","title":"Entity naming","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Ferrite denotes the entities of a reference shape as follows","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Entity Description\nVertex 0-dimensional entity in the reference shape.\nEdge 1-dimensional entity connecting two vertices.\nFace 2-dimensional entity enclosed by edges.\nVolume 3-dimensional entity enclosed by faces.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Note that a node in Ferrite is not the same as a vertex. Vertices denote endpoints of edges, while nodes may also be located in the middle of edges (e.g. for a QuadraticLine cell).","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"To write dimensionally independent code, Ferrite also denotes entities by their codimension, defined relative the reference shape dimension. Specifically, Ferrite has the entities","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Entity Description\nCell 0-codimensional entity, i.e. the same as the reference shape.\nFacet 1-codimensional entity defining the boundary of cells.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Standard use cases mostly deal with these codimensional entities, such as CellValues and FacetValues.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"note: Definition of codimension\nIn Ferrite, codimension is defined relative to the reference dimension of the specific entity. Note that other finite element codes may define it differently (e.g. relative the highest reference dimension in the grid).","category":"page"},{"location":"topics/reference_shapes/#Entity-numbering","page":"Reference shapes","title":"Entity numbering","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Each reference shape defines the numbering of its vertices, edges, and faces entities, where the edge and face entities are defined from their vertex numbers.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"note: Note\nThe numbering and identification of entities is (mostly) for internal use and typically not something users of Ferrite need to interact with.","category":"page"},{"location":"topics/reference_shapes/#Example","page":"Reference shapes","title":"Example","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"The RefQuadrilateral is defined on the domain -1 1 times -1 1 in the local xi_1-xi_2 coordinate system.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"(Image: local element)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"The vertices of a RefQuadrilateral are then","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_vertices(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"and its edges are then defined as","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_edges(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"where the numbers refer to the vertex number. Finally, this reference shape is 2-dimensional, so it only has a single face, corresponding to the cell itself,","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_faces(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"also defined in terms of its vertices.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"As this is a 2-dimensional reference shape, the facets are the edges, i.e.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_facets(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"note: Not public API\nThe functions reference_vertices, reference_edges, reference_faces, and reference_facets are not public and only shown here to explain the numbering concept. The specific ordering may also change, and is therefore only documented in the Developer documentation.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"EditURL = \"../literate-tutorials/plasticity.jl\"","category":"page"},{"location":"tutorials/plasticity/#tutorial-plasticity","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"(Image: Shows the von Mises stress distribution in a cantilever beam.)","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Figure 1. A coarse mesh solution of a cantilever beam subjected to a load causing plastic deformations. The initial yield limit is 200 MPa but due to hardening it increases up to approximately 240 MPa.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: plasticity.ipynb.","category":"page"},{"location":"tutorials/plasticity/#Introduction","page":"Von Mises plasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This example illustrates the use of a nonlinear material model in Ferrite. The particular model is von Mises plasticity (also know as J₂-plasticity) with isotropic hardening. The model is fully 3D, meaning that no assumptions like plane stress or plane strain are introduced.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Also note that the theory of the model is not described here, instead one is referred to standard textbooks on material modeling.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"To illustrate the use of the plasticity model, we setup and solve a FE-problem consisting of a cantilever beam loaded at its free end. But first, we shortly describe the parts of the implementation dealing with the material modeling.","category":"page"},{"location":"tutorials/plasticity/#Material-modeling","page":"Von Mises plasticity","title":"Material modeling","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This section describes the structs and methods used to implement the material model","category":"page"},{"location":"tutorials/plasticity/#Material-parameters-and-state-variables","page":"Von Mises plasticity","title":"Material parameters and state variables","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Start by loading some necessary packages","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"We define a J₂-plasticity-material, containing material parameters and the elastic stiffness Dᵉ (since it is constant)","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"struct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}\n G::T # Shear modulus\n K::T # Bulk modulus\n σ₀::T # Initial yield limit\n H::T # Hardening modulus\n Dᵉ::S # Elastic stiffness tensor\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Next, we define a constructor for the material instance.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function J2Plasticity(E, ν, σ₀, H)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l))\n Dᵉ = SymmetricTensor{4, 3}(temp)\n return J2Plasticity(G, K, σ₀, H, Dᵉ)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"note: Note\nAbove, we defined a constructor J2Plasticity(E, ν, σ₀, H) in terms of the more common material parameters E and ν - simply as a convenience for the user.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Define a struct to store the material state for a Gauss point.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"struct MaterialState{T, S <: SecondOrderTensor{3, T}}\n # Store \"converged\" values\n ϵᵖ::S # plastic strain\n σ::S # stress\n k::T # hardening variable\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Constructor for initializing a material state. Every quantity is set to zero.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function MaterialState()\n return MaterialState(\n zero(SymmetricTensor{2, 3}),\n zero(SymmetricTensor{2, 3}),\n 0.0\n )\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"For later use, during the post-processing step, we define a function to compute the von Mises effective stress.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function vonMises(σ)\n s = dev(σ)\n return sqrt(3.0 / 2.0 * s ⊡ s)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/#Constitutive-driver","page":"Von Mises plasticity","title":"Constitutive driver","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This is the actual method which computes the stress and material tangent stiffness in a given integration point. Input is the current strain and the material state from the previous timestep.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState)\n # unpack some material parameters\n G = material.G\n H = material.H\n\n # We use (•)ᵗ to denote *trial*-values\n σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress\n sᵗ = dev(σᵗ) # deviatoric part of trial-stress\n J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ\n σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)\n σʸ = material.σ₀ + H * state.k # Previous yield limit\n\n φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface\n\n if φᵗ < 0.0 # elastic loading\n return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)\n else # plastic loading\n h = H + 3G\n μ = φᵗ / h # plastic multiplier\n\n c1 = 1 - 3G * μ / σᵗₑ\n s = c1 * sᵗ # updated deviatoric stress\n σ = s + vol(σᵗ) # updated stress\n\n # Compute algorithmic tangent stiffness ``D = \\frac{\\Delta \\sigma }{\\Delta \\epsilon}``\n κ = H * (state.k + μ) # drag stress\n σₑ = material.σ₀ + κ # updated yield surface\n\n δ(i, j) = i == j ? 1.0 : 0.0\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l]\n b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ)\n\n Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l]\n D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp)\n\n # Return new state\n Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain\n ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain\n k = state.k + μ # hardening variable\n return σ, D, MaterialState(ϵᵖ, σ, k)\n end\nend","category":"page"},{"location":"tutorials/plasticity/#FE-problem","page":"Von Mises plasticity","title":"FE-problem","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"What follows are methods for assembling and and solving the FE-problem.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function create_values(interpolation)\n # setup quadrature rules\n qr = QuadratureRule{RefTetrahedron}(2)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(3)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation)\n facetvalues_u = FacetValues(facet_qr, interpolation)\n\n return cellvalues_u, facetvalues_u\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/#Add-degrees-of-freedom","page":"Von Mises plasticity","title":"Add degrees of freedom","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function create_dofhandler(grid, interpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation) # add a displacement field with 3 components\n close!(dh)\n return dh\nend","category":"page"},{"location":"tutorials/plasticity/#Boundary-conditions","page":"Von Mises plasticity","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function create_bc(dh, grid)\n dbcs = ConstraintHandler(dh)\n # Clamped on the left side\n dofs = [1, 2, 3]\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> [0.0, 0.0, 0.0], dofs)\n add!(dbcs, dbc)\n close!(dbcs)\n return dbcs\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/#Assembling-of-element-contributions","page":"Von Mises plasticity","title":"Assembling of element contributions","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Residual vector r\nTangent stiffness K","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function doassemble!(\n K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler,\n material::J2Plasticity, u, states, states_old\n )\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n re = zeros(nu) # element residual vector\n ke = zeros(nu, nu) # element tangent matrix\n\n for (i, cell) in enumerate(CellIterator(dh))\n fill!(ke, 0)\n fill!(re, 0)\n eldofs = celldofs(cell)\n ue = u[eldofs]\n state = @view states[:, i]\n state_old = @view states_old[:, i]\n assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old)\n assemble!(assembler, eldofs, ke, re)\n end\n return K, r\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Compute element contribution to the residual and the tangent.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"note: Note\nDue to symmetry, we only compute the lower half of the tangent and then symmetrize it.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function assemble_cell!(\n Ke, re, cell, cellvalues, material,\n ue, state, state_old\n )\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n # For each integration point, compute stress and material stiffness\n ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain\n σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point])\n\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δϵ = shape_symmetric_gradient(cellvalues, q_point, i)\n re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual\n for j in 1:i # loop only over lower half\n Δϵ = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ\n end\n end\n end\n return symmetrize_lower!(Ke)\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Helper function to symmetrize the material tangent","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend;\n\nfunction doassemble_neumann!(r, dh, facetset, facetvalues, t)\n n_basefuncs = getnbasefunctions(facetvalues)\n re = zeros(n_basefuncs) # element residual vector\n for fc in FacetIterator(dh, facetset)\n # Add traction as a negative contribution to the element residual `re`:\n reinit!(facetvalues, fc)\n fill!(re, 0)\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] -= (δu ⋅ t) * dΓ\n end\n end\n assemble!(r, celldofs(fc), re)\n end\n return r\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Define a function which solves the FE-problem.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function solve()\n # Define material parameters\n E = 200.0e9 # [Pa]\n H = E / 20 # [Pa]\n ν = 0.3 # [-]\n σ₀ = 200.0e6 # [Pa]\n material = J2Plasticity(E, ν, σ₀, H)\n\n L = 10.0 # beam length [m]\n w = 1.0 # beam width [m]\n h = 1.0 # beam height[m]\n n_timesteps = 10\n u_max = zeros(n_timesteps)\n traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps)\n\n # Create geometry, dofs and boundary conditions\n n = 2\n nels = (10n, n, 2n) # number of elements in each spatial direction\n P1 = Vec((0.0, 0.0, 0.0)) # start point for geometry\n P2 = Vec((L, w, h)) # end point for geometry\n grid = generate_grid(Tetrahedron, nels, P1, P2)\n interpolation = Lagrange{RefTetrahedron, 1}()^3\n\n dh = create_dofhandler(grid, interpolation) # JuaFEM helper function\n dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions\n\n cellvalues, facetvalues = create_values(interpolation)\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n Δu = zeros(n_dofs) # displacement correction\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # tangent stiffness matrix\n\n # Create material states. One array for each cell, where each element is an array of material-\n # states - one for each integration point\n nqp = getnquadpoints(cellvalues)\n states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n\n # Newton-Raphson loop\n NEWTON_TOL = 1 # 1 N\n print(\"\\n Starting Netwon iterations:\\n\")\n\n for timestep in 1:n_timesteps\n t = timestep # actual time (used for evaluating d-bndc)\n traction = Vec((0.0, 0.0, traction_magnitude[timestep]))\n newton_itr = -1\n print(\"\\n Time step @time = $timestep:\\n\")\n update!(dbcs, t) # evaluates the D-bndc at time t\n apply!(u, dbcs) # set the prescribed values in the solution vector\n\n while true\n newton_itr += 1\n\n if newton_itr > 8\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n # Tangent and residual contribution from the cells (volume integral)\n doassemble!(K, r, cellvalues, dh, material, u, states, states_old)\n # Residual contribution from the Neumann boundary (surface integral)\n doassemble_neumann!(r, dh, getfacetset(grid, \"right\"), facetvalues, traction)\n norm_r = norm(r[Ferrite.free_dofs(dbcs)])\n\n print(\"Iteration: $newton_itr \\tresidual: $(@sprintf(\"%.8f\", norm_r))\\n\")\n if norm_r < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbcs)\n Δu = Symmetric(K) \\ r\n u -= Δu\n end\n\n # Update the old states with the converged values for next timestep\n states_old .= states\n\n u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep\n end\n\n # ## Postprocessing\n # Only a vtu-file corresponding to the last time-step is exported.\n #\n # The following is a quick (and dirty) way of extracting average cell data for export.\n mises_values = zeros(getncells(grid))\n κ_values = zeros(getncells(grid))\n for (el, cell_states) in enumerate(eachcol(states))\n for state in cell_states\n mises_values[el] += vonMises(state.σ)\n κ_values[el] += state.k * material.H\n end\n mises_values[el] /= length(cell_states) # average von Mises stress\n κ_values[el] /= length(cell_states) # average drag stress\n end\n VTKGridFile(\"plasticity\", dh) do vtk\n write_solution(vtk, dh, u) # displacement field\n write_cell_data(vtk, mises_values, \"von Mises [Pa]\")\n write_cell_data(vtk, κ_values, \"Drag stress [Pa]\")\n end\n\n return u_max, traction_magnitude\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Solve the FE-problem and for each time-step extract maximum displacement and the corresponding traction load. Also compute the limit-traction-load","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"u_max, traction_magnitude = solve();\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Finally we plot the load-displacement curve.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"using Plots\nplot(\n vcat(0.0, u_max), # add the origin as a point\n vcat(0.0, traction_magnitude),\n linewidth = 2,\n title = \"Traction-displacement\",\n label = nothing,\n markershape = :auto\n)\nylabel!(\"Traction [Pa]\")\nxlabel!(\"Maximum deflection [m]\")","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Figure 2. Load-displacement-curve for the beam, showing a clear decrease in stiffness as more material starts to yield.","category":"page"},{"location":"tutorials/plasticity/#plasticity-plain-program","page":"Von Mises plasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Here follows a version of the program without any comments. The file is also available here: plasticity.jl.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf\n\nstruct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}\n G::T # Shear modulus\n K::T # Bulk modulus\n σ₀::T # Initial yield limit\n H::T # Hardening modulus\n Dᵉ::S # Elastic stiffness tensor\nend;\n\nfunction J2Plasticity(E, ν, σ₀, H)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l))\n Dᵉ = SymmetricTensor{4, 3}(temp)\n return J2Plasticity(G, K, σ₀, H, Dᵉ)\nend;\n\nstruct MaterialState{T, S <: SecondOrderTensor{3, T}}\n # Store \"converged\" values\n ϵᵖ::S # plastic strain\n σ::S # stress\n k::T # hardening variable\nend\n\nfunction MaterialState()\n return MaterialState(\n zero(SymmetricTensor{2, 3}),\n zero(SymmetricTensor{2, 3}),\n 0.0\n )\nend\n\nfunction vonMises(σ)\n s = dev(σ)\n return sqrt(3.0 / 2.0 * s ⊡ s)\nend;\n\nfunction compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState)\n # unpack some material parameters\n G = material.G\n H = material.H\n\n # We use (•)ᵗ to denote *trial*-values\n σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress\n sᵗ = dev(σᵗ) # deviatoric part of trial-stress\n J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ\n σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)\n σʸ = material.σ₀ + H * state.k # Previous yield limit\n\n φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface\n\n if φᵗ < 0.0 # elastic loading\n return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)\n else # plastic loading\n h = H + 3G\n μ = φᵗ / h # plastic multiplier\n\n c1 = 1 - 3G * μ / σᵗₑ\n s = c1 * sᵗ # updated deviatoric stress\n σ = s + vol(σᵗ) # updated stress\n\n # Compute algorithmic tangent stiffness ``D = \\frac{\\Delta \\sigma }{\\Delta \\epsilon}``\n κ = H * (state.k + μ) # drag stress\n σₑ = material.σ₀ + κ # updated yield surface\n\n δ(i, j) = i == j ? 1.0 : 0.0\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l]\n b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ)\n\n Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l]\n D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp)\n\n # Return new state\n Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain\n ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain\n k = state.k + μ # hardening variable\n return σ, D, MaterialState(ϵᵖ, σ, k)\n end\nend\n\nfunction create_values(interpolation)\n # setup quadrature rules\n qr = QuadratureRule{RefTetrahedron}(2)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(3)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation)\n facetvalues_u = FacetValues(facet_qr, interpolation)\n\n return cellvalues_u, facetvalues_u\nend;\n\nfunction create_dofhandler(grid, interpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation) # add a displacement field with 3 components\n close!(dh)\n return dh\nend\n\nfunction create_bc(dh, grid)\n dbcs = ConstraintHandler(dh)\n # Clamped on the left side\n dofs = [1, 2, 3]\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> [0.0, 0.0, 0.0], dofs)\n add!(dbcs, dbc)\n close!(dbcs)\n return dbcs\nend;\n\nfunction doassemble!(\n K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler,\n material::J2Plasticity, u, states, states_old\n )\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n re = zeros(nu) # element residual vector\n ke = zeros(nu, nu) # element tangent matrix\n\n for (i, cell) in enumerate(CellIterator(dh))\n fill!(ke, 0)\n fill!(re, 0)\n eldofs = celldofs(cell)\n ue = u[eldofs]\n state = @view states[:, i]\n state_old = @view states_old[:, i]\n assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old)\n assemble!(assembler, eldofs, ke, re)\n end\n return K, r\nend\n\nfunction assemble_cell!(\n Ke, re, cell, cellvalues, material,\n ue, state, state_old\n )\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n # For each integration point, compute stress and material stiffness\n ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain\n σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point])\n\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δϵ = shape_symmetric_gradient(cellvalues, q_point, i)\n re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual\n for j in 1:i # loop only over lower half\n Δϵ = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ\n end\n end\n end\n return symmetrize_lower!(Ke)\nend\n\nfunction symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend;\n\nfunction doassemble_neumann!(r, dh, facetset, facetvalues, t)\n n_basefuncs = getnbasefunctions(facetvalues)\n re = zeros(n_basefuncs) # element residual vector\n for fc in FacetIterator(dh, facetset)\n # Add traction as a negative contribution to the element residual `re`:\n reinit!(facetvalues, fc)\n fill!(re, 0)\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] -= (δu ⋅ t) * dΓ\n end\n end\n assemble!(r, celldofs(fc), re)\n end\n return r\nend\n\nfunction solve()\n # Define material parameters\n E = 200.0e9 # [Pa]\n H = E / 20 # [Pa]\n ν = 0.3 # [-]\n σ₀ = 200.0e6 # [Pa]\n material = J2Plasticity(E, ν, σ₀, H)\n\n L = 10.0 # beam length [m]\n w = 1.0 # beam width [m]\n h = 1.0 # beam height[m]\n n_timesteps = 10\n u_max = zeros(n_timesteps)\n traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps)\n\n # Create geometry, dofs and boundary conditions\n n = 2\n nels = (10n, n, 2n) # number of elements in each spatial direction\n P1 = Vec((0.0, 0.0, 0.0)) # start point for geometry\n P2 = Vec((L, w, h)) # end point for geometry\n grid = generate_grid(Tetrahedron, nels, P1, P2)\n interpolation = Lagrange{RefTetrahedron, 1}()^3\n\n dh = create_dofhandler(grid, interpolation) # JuaFEM helper function\n dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions\n\n cellvalues, facetvalues = create_values(interpolation)\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n Δu = zeros(n_dofs) # displacement correction\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # tangent stiffness matrix\n\n # Create material states. One array for each cell, where each element is an array of material-\n # states - one for each integration point\n nqp = getnquadpoints(cellvalues)\n states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n\n # Newton-Raphson loop\n NEWTON_TOL = 1 # 1 N\n print(\"\\n Starting Netwon iterations:\\n\")\n\n for timestep in 1:n_timesteps\n t = timestep # actual time (used for evaluating d-bndc)\n traction = Vec((0.0, 0.0, traction_magnitude[timestep]))\n newton_itr = -1\n print(\"\\n Time step @time = $timestep:\\n\")\n update!(dbcs, t) # evaluates the D-bndc at time t\n apply!(u, dbcs) # set the prescribed values in the solution vector\n\n while true\n newton_itr += 1\n\n if newton_itr > 8\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n # Tangent and residual contribution from the cells (volume integral)\n doassemble!(K, r, cellvalues, dh, material, u, states, states_old)\n # Residual contribution from the Neumann boundary (surface integral)\n doassemble_neumann!(r, dh, getfacetset(grid, \"right\"), facetvalues, traction)\n norm_r = norm(r[Ferrite.free_dofs(dbcs)])\n\n print(\"Iteration: $newton_itr \\tresidual: $(@sprintf(\"%.8f\", norm_r))\\n\")\n if norm_r < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbcs)\n Δu = Symmetric(K) \\ r\n u -= Δu\n end\n\n # Update the old states with the converged values for next timestep\n states_old .= states\n\n u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep\n end\n\n # ## Postprocessing\n # Only a vtu-file corresponding to the last time-step is exported.\n #\n # The following is a quick (and dirty) way of extracting average cell data for export.\n mises_values = zeros(getncells(grid))\n κ_values = zeros(getncells(grid))\n for (el, cell_states) in enumerate(eachcol(states))\n for state in cell_states\n mises_values[el] += vonMises(state.σ)\n κ_values[el] += state.k * material.H\n end\n mises_values[el] /= length(cell_states) # average von Mises stress\n κ_values[el] /= length(cell_states) # average drag stress\n end\n VTKGridFile(\"plasticity\", dh) do vtk\n write_solution(vtk, dh, u) # displacement field\n write_cell_data(vtk, mises_values, \"von Mises [Pa]\")\n write_cell_data(vtk, κ_values, \"Drag stress [Pa]\")\n end\n\n return u_max, traction_magnitude\nend\n\nu_max, traction_magnitude = solve();\n\nusing Plots\nplot(\n vcat(0.0, u_max), # add the origin as a point\n vcat(0.0, traction_magnitude),\n linewidth = 2,\n title = \"Traction-displacement\",\n label = nothing,\n markershape = :auto\n)\nylabel!(\"Traction [Pa]\")\nxlabel!(\"Maximum deflection [m]\")","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/boundary_conditions/#Boundary-conditions","page":"Boundary conditions","title":"Boundary conditions","text":"","category":"section"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"Pages = [\"boundary_conditions.md\"]","category":"page"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"ConstraintHandler\nDirichlet\nPeriodicDirichlet\ncollect_periodic_facets\ncollect_periodic_facets!\nadd!\nclose!\nupdate!\napply!\napply_zero!\napply_local!\napply_assemble!\nget_rhs_data\napply_rhs!\nFerrite.RHSData","category":"page"},{"location":"reference/boundary_conditions/#Ferrite.ConstraintHandler","page":"Boundary conditions","title":"Ferrite.ConstraintHandler","text":"ConstraintHandler([T=Float64], dh::AbstractDofHandler)\n\nA collection of constraints associated with the dof handler dh. T is the numeric type for stored values.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Ferrite.Dirichlet","page":"Boundary conditions","title":"Ferrite.Dirichlet","text":"Dirichlet(u::Symbol, ∂Ω::AbstractVecOrSet, f::Function, components=nothing)\n\nCreate a Dirichlet boundary condition on u on the ∂Ω part of the boundary. f is a function of the form f(x) or f(x, t) where x is the spatial coordinate and t is the current time, and returns the prescribed value. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.\n\nThe set, ∂Ω, can be an AbstractSet or AbstractVector with elements of type FacetIndex, FaceIndex, EdgeIndex, VertexIndex, or Int. For most cases, the element type is FacetIndex, as shown below. To constrain a single point, using VertexIndex is recommended, but it is also possible to constrain a specific nodes by giving the node numbers via Int elements. To constrain e.g. an edge in 3d EdgeIndex elements can be given.\n\nFor example, here we create a Dirichlet condition for the :u field, on the facetset called ∂Ω and the value given by the sin function:\n\nExamples\n\n# Obtain the facetset from the grid\n∂Ω = getfacetset(grid, \"boundary-1\")\n\n# Prescribe scalar field :s on ∂Ω to sin(t)\ndbc = Dirichlet(:s, ∂Ω, (x, t) -> sin(t))\n\n# Prescribe all components of vector field :v on ∂Ω to 0\ndbc = Dirichlet(:v, ∂Ω, x -> 0 * x)\n\n# Prescribe component 2 and 3 of vector field :v on ∂Ω to [sin(t), cos(t)]\ndbc = Dirichlet(:v, ∂Ω, (x, t) -> [sin(t), cos(t)], [2, 3])\n\nDirichlet boundary conditions are added to a ConstraintHandler which applies the condition via apply! and/or apply_zero!.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Ferrite.PeriodicDirichlet","page":"Boundary conditions","title":"Ferrite.PeriodicDirichlet","text":"PeriodicDirichlet(u::Symbol, facet_mapping, components=nothing)\nPeriodicDirichlet(u::Symbol, facet_mapping, R::AbstractMatrix, components=nothing)\nPeriodicDirichlet(u::Symbol, facet_mapping, f::Function, components=nothing)\n\nCreate a periodic Dirichlet boundary condition for the field u on the facet-pairs given in facet_mapping. The mapping can be computed with collect_periodic_facets. The constraint ensures that degrees-of-freedom on the mirror facet are constrained to the corresponding degrees-of-freedom on the image facet. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.\n\nIf the mapping is not aligned with the coordinate axis (e.g. rotated) a rotation matrix R should be passed to the constructor. This matrix rotates dofs on the mirror facet to the image facet. Note that this is only applicable for vector-valued problems.\n\nTo construct an inhomogeneous periodic constraint it is possible to pass a function f. Note that this is currently only supported when the periodicity is aligned with the coordinate axes.\n\nSee the manual section on Periodic boundary conditions for more information.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Ferrite.collect_periodic_facets","page":"Boundary conditions","title":"Ferrite.collect_periodic_facets","text":"collect_periodic_facets(grid::Grid, mset, iset, transform::Union{Function,Nothing}=nothing; tol=1e-12)\n\nMatch all mirror facets in mset with a corresponding image facet in iset. Return a dictionary which maps each mirror facet to a image facet. The result can then be passed to PeriodicDirichlet.\n\nmset and iset can be given as a String (an existing facet set in the grid) or as a AbstractSet{FacetIndex} directly.\n\nBy default this function looks for a matching facet in the directions of the coordinate system. For other types of periodicities the transform function can be used. The transform function is applied on the coordinates of the image facet, and is expected to transform the coordinates to the matching locations in the mirror set.\n\nThe keyword tol specifies the tolerance (i.e. distance and deviation in facet-normals) between a image-facet and mirror-facet, for them to be considered matched.\n\nSee also: collect_periodic_facets!, PeriodicDirichlet.\n\n\n\n\n\ncollect_periodic_facets(grid::Grid, all_facets::Union{AbstractSet{FacetIndex},String,Nothing}=nothing; tol=1e-12)\n\nSplit all facets in all_facets into image and mirror sets. For each matching pair, the facet located further along the vector (1, 1, 1) becomes the image facet.\n\nIf no set is given, all facets on the outer boundary of the grid (i.e. all facets that do not have a neighbor) is used.\n\nSee also: collect_periodic_facets!, PeriodicDirichlet.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.collect_periodic_facets!","page":"Boundary conditions","title":"Ferrite.collect_periodic_facets!","text":"collect_periodic_facets!(facet_map::Vector{PeriodicFacetPair}, grid::Grid, mset, iset, transform::Union{Function,Nothing}; tol=1e-12)\n\nSame as collect_periodic_facets but adds all matches to the existing facet_map.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.add!","page":"Boundary conditions","title":"Ferrite.add!","text":"add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the SubDofHandler sdh.\n\n\n\n\n\nadd!(dh::DofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the DofHandler dh.\n\nThe field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.\n\n\n\n\n\nadd!(ch::ConstraintHandler, ac::AffineConstraint)\n\nAdd the AffineConstraint to the ConstraintHandler.\n\n\n\n\n\nadd!(ch::ConstraintHandler, dbc::Dirichlet)\n\nAdd a Dirichlet boundary condition to the ConstraintHandler.\n\n\n\n\n\nadd!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;\n qr_rhs, [qr_lhs])\n\nAdd an interpolation ip on the cells in set to the L2Projector proj.\n\nqr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.\nThe optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.close!","page":"Boundary conditions","title":"Ferrite.close!","text":"close!(dh::AbstractDofHandler)\n\nCloses dh and creates degrees of freedom for each cell.\n\n\n\n\n\nclose!(ch::ConstraintHandler)\n\nClose and finalize the ConstraintHandler.\n\n\n\n\n\nclose!(proj::L2Projector)\n\nClose proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.update!","page":"Boundary conditions","title":"Ferrite.update!","text":"update!(ch::ConstraintHandler, time::Real=0.0)\n\nUpdate time-dependent inhomogeneities for the new time. This calls f(x) or f(x, t) when applicable, where f is the function(s) corresponding to the constraints in the handler, to compute the inhomogeneities.\n\nNote that this is called implicitly in close!(::ConstraintHandler).\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply!","page":"Boundary conditions","title":"Ferrite.apply!","text":"apply!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)\n\nAdjust the matrix K and right hand side rhs to account for the Dirichlet boundary conditions specified in ch such that K \\ rhs gives the expected solution.\n\nnote: Note\napply!(K, rhs, ch) essentially calculatesrhs[free] = rhs[free] - K[constrained, constrained] * a[constrained]where a[constrained] are the inhomogeneities. Consequently, the sign of rhs matters (in contrast with apply_zero!).\n\napply!(v::AbstractVector, ch::ConstraintHandler)\n\nApply Dirichlet boundary conditions and affine constraints, specified in ch, to the solution vector v.\n\nExamples\n\nK, f = assemble_system(...) # Assemble system\napply!(K, f, ch) # Adjust K and f to account for boundary conditions\nu = K \\ f # Solve the system, u should be \"approximately correct\"\napply!(u, ch) # Explicitly make sure bcs are correct\n\nnote: Note\nThe last operation is not strictly necessary since the boundary conditions should already be fulfilled after apply!(K, f, ch). However, solvers of linear systems are not exact, and thus apply!(u, ch) can be used to make sure the boundary conditions are fulfilled exactly.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_zero!","page":"Boundary conditions","title":"Ferrite.apply_zero!","text":"apply_zero!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)\n\nAdjust the matrix K and the right hand side rhs to account for prescribed Dirichlet boundary conditions and affine constraints such that du = K \\ rhs gives the expected result (e.g. du zero for all prescribed degrees of freedom).\n\napply_zero!(v::AbstractVector, ch::ConstraintHandler)\n\nZero-out values in v corresponding to prescribed degrees of freedom and update values prescribed by affine constraints, such that if a fulfills the constraints, a ± v also will.\n\nThese methods are typically used in e.g. a Newton solver where the increment, du, should be prescribed to zero even for non-homogeneouos boundary conditions.\n\nSee also: apply!.\n\nExamples\n\nu = un + Δu # Current guess\nK, g = assemble_system(...) # Assemble residual and tangent for current guess\napply_zero!(K, g, ch) # Adjust tangent and residual to take prescribed values into account\nΔΔu = K \\ g # Compute the (negative) increment, prescribed values are \"approximately\" zero\napply_zero!(ΔΔu, ch) # Make sure values are exactly zero\nΔu .-= ΔΔu # Update current guess\n\nnote: Note\nThe last call to apply_zero! is only strictly necessary for affine constraints. However, even if the Dirichlet boundary conditions should be fulfilled after apply!(K, g, ch), solvers of linear systems are not exact. apply!(ΔΔu, ch) can be used to make sure the values for the prescribed degrees of freedom are fulfilled exactly.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_local!","page":"Boundary conditions","title":"Ferrite.apply_local!","text":"apply_local!(\n local_matrix::AbstractMatrix, local_vector::AbstractVector,\n global_dofs::AbstractVector, ch::ConstraintHandler;\n apply_zero::Bool = false\n)\n\nSimilar to apply! but perform condensation of constrained degrees-of-freedom locally in local_matrix and local_vector before they are to be assembled into the global system.\n\nWhen the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).\n\nThis method can only be used if all constraints are \"local\", i.e. no constraint couples with dofs outside of the element dofs (global_dofs) since condensation of such constraints requires writing to entries in the global matrix/vector. For such a case, apply_assemble! can be used instead.\n\nNote that this method is destructive since it, by definition, modifies local_matrix and local_vector.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_assemble!","page":"Boundary conditions","title":"Ferrite.apply_assemble!","text":"apply_assemble!(\n assembler::AbstractAssembler, ch::ConstraintHandler,\n global_dofs::AbstractVector{Int},\n local_matrix::AbstractMatrix, local_vector::AbstractVector;\n apply_zero::Bool = false\n)\n\nAssemble local_matrix and local_vector into the global system in assembler by first doing constraint condensation using apply_local!.\n\nThis is similar to using apply_local! followed by assemble! with the advantage that non-local constraints can be handled, since this method can write to entries of the global matrix and vector outside of the indices in global_dofs.\n\nWhen the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).\n\nNote that this method is destructive since it modifies local_matrix and local_vector.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.get_rhs_data","page":"Boundary conditions","title":"Ferrite.get_rhs_data","text":"get_rhs_data(ch::ConstraintHandler, A::SparseMatrixCSC) -> RHSData\n\nReturns the needed RHSData for apply_rhs!.\n\nThis must be used when the same stiffness matrix is reused for multiple steps, for example when timestepping, with different non-homogeneouos Dirichlet boundary conditions.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_rhs!","page":"Boundary conditions","title":"Ferrite.apply_rhs!","text":"apply_rhs!(data::RHSData, f::AbstractVector, ch::ConstraintHandler, applyzero::Bool=false)\n\nApplies the boundary condition to the right-hand-side vector without modifying the stiffness matrix.\n\nSee also: get_rhs_data.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.RHSData","page":"Boundary conditions","title":"Ferrite.RHSData","text":"RHSData\n\nStores the constrained columns and mean of the diagonal of stiffness matrix A.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Initial-conditions","page":"Boundary conditions","title":"Initial conditions","text":"","category":"section"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"apply_analytical!","category":"page"},{"location":"reference/boundary_conditions/#Ferrite.apply_analytical!","page":"Boundary conditions","title":"Ferrite.apply_analytical!","text":"apply_analytical!(\n a::AbstractVector, dh::AbstractDofHandler, fieldname::Symbol,\n f::Function, cellset=1:getncells(get_grid(dh)))\n\nApply a solution f(x) by modifying the values in the degree of freedom vector a pertaining to the field fieldname for all cells in cellset. The function f(x) are given the spatial coordinate of the degree of freedom. For scalar fields, f(x)::Number, and for vector fields with dimension dim, f(x)::Vec{dim}.\n\nThis function can be used to apply initial conditions for time dependent problems.\n\nnote: Note\nThis function only works for standard nodal finite element interpolations when the function value at the (algebraic) node is equal to the corresponding degree of freedom value. This holds for e.g. Lagrange and Serendipity interpolations, including sub- and superparametric elements.\n\n\n\n\n\n","category":"function"},{"location":"cited-literature/#Cited-literature","page":"Cited literature","title":"Cited literature","text":"","category":"section"},{"location":"cited-literature/","page":"Cited literature","title":"Cited literature","text":"G. A. Holzapfel. Nonlinear Solid Mechanics: A Continuum Approach for Engineering (Wiley, Chichester ; New York, 2000).\n\n\n\nJ. Simo and C. Miehe. Associative coupled thermoplasticity at finite strains: Formulation, numerical analysis and implementation. Computer Methods in Applied Mechanics and Engineering 98, 41–104 (1992).\n\n\n\nL. Mu, J. Wang, Y. Wang and X. Ye. Interior penalty discontinuous Galerkin method on very general polygonal and polyhedral meshes. Journal of Computational and Applied Mathematics 255, 432–440 (2014).\n\n\n\nD. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.\n\n\n\nR. C. Kirby. A general approach to transforming finite elements (2017), arXiv:1706.09017 [math.NA].\n\n\n\nD. Dunavant. High degree efficient symmetrical Gaussian quadrature rules for the triangle. International journal for numerical methods in engineering 21, 1129–1148 (1985).\n\n\n\nP. Keast. Moderate-degree tetrahedral quadrature formulas. Computer methods in applied mechanics and engineering 55, 339–348 (1986).\n\n\n\nF. D. Witherden and P. E. Vincent. On the identification of symmetric quadrature rules for finite element methods. Computers & Mathematics with Applications 69, 1232–1241 (2015).\n\n\n\nM. Crouzeix and P.-A. Raviart. Conforming and nonconforming finite element methods for solving the stationary Stokes equations I. Revue française d'automatique informatique recherche opérationnelle. Mathématique 7, 33–75 (1973).\n\n\n\nR. Rannacher and S. Turek. Simple nonconforming quadrilateral Stokes element. Numerical Methods for Partial Differential Equations 8, 97–111 (1992).\n\n\n\nB. Turcksin, M. Kronbichler and W. Bangerth. WorkStream – A Design Pattern for Multicore-Enabled Finite Element Computations. ACM Trans. Math. Softw. 43 (2016).\n\n\n\nM. Cenanovic. Finite element methods for surface problems. Ph.D. Thesis, Jönköping University, School of Engineering (2017).\n\n\n\nM. W. Scroggs, J. S. Dokken, C. N. Richardson and G. N. Wells. Construction of Arbitrary Order Finite Element Degree-of-Freedom Maps on Polygonal and Polyhedral Cell Meshes. ACM Trans. Math. Softw. 48 (2022).\n\n\n\nD. R. Jantos, K. Hackl and P. Junker. An accurate and fast regularization approach to thermodynamic topology optimization. International Journal for Numerical Methods in Engineering 117, 991–1017 (2019).\n\n\n\nM. Blaszczyk, D. R. Jantos and P. Junker. Application of Taylor series combined with the weighted least square method to thermodynamic topology optimization. Computer Methods in Applied Mechanics and Engineering 393, 114698 (2022).\n\n\n\n","category":"page"},{"location":"cited-literature/","page":"Cited literature","title":"Cited literature","text":"","category":"page"},{"location":"devdocs/reference_cells/#Reference-cells","page":"Reference cells","title":"Reference cells","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"The reference cells are used to i) define grid cells, ii) define shape functions, and iii) define quadrature rules. The numbering of vertices, edges, faces are visualized below. See also FerriteViz.elementinfo.","category":"page"},{"location":"devdocs/reference_cells/#AbstractRefShape-subtypes","page":"Reference cells","title":"AbstractRefShape subtypes","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.AbstractRefShape\nFerrite.RefLine\nFerrite.RefTriangle\nFerrite.RefQuadrilateral\nFerrite.RefTetrahedron\nFerrite.RefHexahedron\nFerrite.RefPrism","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.AbstractRefShape","page":"Reference cells","title":"Ferrite.AbstractRefShape","text":"AbstractRefShape{refdim}\n\nSupertype for all reference shapes, with reference dimension refdim. Reference shapes are used to define grid cells, shape functions, and quadrature rules. Currently existing reference shapes are: RefLine, RefTriangle, RefQuadrilateral, RefTetrahedron, RefHexahedron, RefPrism.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefLine","page":"Reference cells","title":"Ferrite.RefLine","text":"RefLine <: AbstractRefShape{1}\n\nReference line/interval, reference dimension 1.\n\n----------------+--------------------\nVertex numbers: | Vertex coordinates:\n 1-------2 | v1: 𝛏 = (-1.0,)\n --> ξ₁ | v2: 𝛏 = ( 1.0,)\n----------------+--------------------\nFace numbers: | Face identifiers:\n 1-------2 | f1: (v1,)\n | f2: (v2,)\n----------------+--------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefTriangle","page":"Reference cells","title":"Ferrite.RefTriangle","text":"RefTriangle <: AbstractRefShape{2}\n\nReference triangle, reference dimension 2.\n\n----------------+--------------------\nVertex numbers: | Vertex coordinates:\n 2 |\n | \\ | v1: 𝛏 = (1.0, 0.0)\n | \\ | v2: 𝛏 = (0.0, 1.0)\nξ₂^ | \\ | v3: 𝛏 = (0.0, 0.0)\n | 3-------1 |\n +--> ξ₁ |\n----------------+--------------------\nFace numbers: | Face identifiers:\n + |\n | \\ | f1: (v1, v2)\n 2 1 | f2: (v2, v3)\n | \\ | f3: (v3, v1)\n +---3---+ |\n----------------+--------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefQuadrilateral","page":"Reference cells","title":"Ferrite.RefQuadrilateral","text":"RefQuadrilateral <: AbstractRefShape{2}\n\nReference quadrilateral, reference dimension 2.\n\n----------------+---------------------\nVertex numbers: | Vertex coordinates:\n 4-------3 |\n | | | v1: 𝛏 = (-1.0, -1.0)\n | | | v2: 𝛏 = ( 1.0, -1.0)\nξ₂^ | | | v3: 𝛏 = ( 1.0, 1.0)\n | 1-------2 | v4: 𝛏 = (-1.0, 1.0)\n +--> ξ₁ |\n----------------+---------------------\nFace numbers: | Face identifiers:\n +---3---+ | f1: (v1, v2)\n | | | f2: (v2, v3)\n 4 2 | f3: (v3, v4)\n | | | f4: (v4, v1)\n +---1---+ |\n----------------+---------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefTetrahedron","page":"Reference cells","title":"Ferrite.RefTetrahedron","text":"RefTetrahedron <: AbstractRefShape{3}\n\nReference tetrahedron, reference dimension 3.\n\n---------------------------------------+-------------------------\nVertex numbers: | Vertex coordinates:\n 4 4 |\n ^ ξ₃ / \\ /| \\ | v1: 𝛏 = (0.0, 0.0, 0.0)\n | / \\ / | \\ | v2: 𝛏 = (1.0, 0.0, 0.0)\n +-> ξ₂ / \\ / 1___ \\ | v3: 𝛏 = (0.0, 1.0, 0.0)\n / / __--3 / / __‾-3 | v4: 𝛏 = (0.0, 0.0, 1.0)\nξ₁ 2 __--‾‾ 2/__--‾‾ |\n---------------------------------------+-------------------------\nEdge numbers: | Edge identifiers:\n + + | e1: (v1, v2)\n / \\ /| \\ | e2: (v2, v3)\n 5 / \\ 6 5 / |4 \\ 6 | e3: (v3, v1)\n / \\ / +__3 \\ | e4: (v1, v4)\n / __--+ / /1 __‾-+ | e5: (v2, v4)\n + __--‾‾2 +/__--‾‾2 | e6: (v3, v4)\n---------------------------------------+-------------------------\nFace numbers: | Face identifiers:\n + + |\n / \\ /| \\ | f1: (v1, v3, v2)\n / \\ / | 4 \\ | f2: (v1, v2, v4)\n / 3 \\ /2 +___ \\ | f3: (v2, v3, v4)\n / __--+ / / 1 __‾-+ | f4: (v1, v4, v3)\n + __--‾‾ +/__--‾‾ |\n---------------------------------------+-------------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefHexahedron","page":"Reference cells","title":"Ferrite.RefHexahedron","text":"RefHexahedron <: AbstractRefShape{3}\n\nReference hexahedron, reference dimension 3.\n\n-----------------------------------------+----------------------------\nVertex numbers: | Vertex coordinates:\n 5--------8 5--------8 | v1: 𝛏 = (-1.0, -1.0, -1.0)\n / /| /| | | v2: 𝛏 = ( 1.0, -1.0, -1.0)\n / / | / | | | v3: 𝛏 = ( 1.0, 1.0, -1.0)\n ^ ξ₃ 6--------7 | 6 | | | v4: 𝛏 = (-1.0, 1.0, -1.0)\n | | | 4 | 1--------4 | v5: 𝛏 = (-1.0, -1.0, 1.0)\n +-> ξ₂ | | / | / / | v6: 𝛏 = ( 1.0, -1.0, 1.0)\n / | |/ |/ / | v7: 𝛏 = ( 1.0, 1.0, 1.0)\nξ₁ 2--------3 2--------3 | v8: 𝛏 = (-1.0, 1.0, 1.0)\n-----------------------------------------+-----------------------------\nEdge numbers: | Edge identifiers:\n +----8---+ +----8---+ |\n 5/ /| 5/| | | e1: (v1, v2), e2: (v2, v3)\n / 7/ |12 / |9 12| | e3: (v3, v4), e4: (v4, v1)\n +----6---+ | + | | | e5: (v5, v6), e6: (v6, v7)\n | | + | +---4----+ | e7: (v7, v8), e8: (v8, v5)\n 10| 11| / 10| /1 / | e9: (v1, v5), e10: (v2, v6)\n | |/3 |/ /3 | e11: (v3, v7), e12: (v4, v8)\n +---2----+ +---2----+ |\n-----------------------------------------+-----------------------------\nFace numbers: | Face identifiers:\n +--------+ +--------+ |\n / 6 /| /| | | f1: (v1, v4, v3, v2)\n / / | / | 5 | | f2: (v1, v2, v6, v5)\n +--------+ 4| + | | | f3: (v2, v3, v7, v6)\n | | + |2 +--------+ | f4: (v3, v4, v8, v7)\n | 3 | / | / / | f5: (v1, v5, v8, v4)\n | |/ |/ 1 / | f6: (v5, v6, v7, v8)\n +--------+ +--------+ |\n-----------------------------------------+-----------------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefPrism","page":"Reference cells","title":"Ferrite.RefPrism","text":"RefPrism <: AbstractRefShape{3}\n\nReference prism, reference dimension 3.\n\n-----------------------------------------+----------------------------\nVertex numbers: | Vertex coordinates:\n 4-------/6 4--------6 |\n / / | /| | | v1: 𝛏 = (0.0, 0.0, 0.0)\n / / | / | | | v2: 𝛏 = (1.0, 0.0, 0.0)\n ^ ξ₃ 5 / | 5 | | | v3: 𝛏 = (0.0, 1.0, 0.0)\n | | /3 | 1-------/3 | v4: 𝛏 = (0.0, 0.0, 1.0)\n +-> ξ₂ | / | / / | v5: 𝛏 = (1.0, 0.0, 1.0)\n / | / |/ / | v6: 𝛏 = (0.0, 1.0, 1.0)\nξ₁ 2 / 2 / |\n-----------------------------------------+----------------------------\nEdge numbers: | Edge identifiers:\n +---8---/+ +---8----+ |\n 7/ / | 7/| | | e1: (v2, v1), e2: (v1, v3)\n / / 9 |6 / |3 |6 | e3: (v1, v4), e4: (v3, v2)\n + / | + | | | e5: (v2, v5), e6: (v3, v6)\n | /+ | +--2----/+ | e7: (v4, v5), e8: (v4, v6)\n 5| / 5| /1 / | e9: (v6, v5)\n | / 4 |/ / 4 |\n + / + / |\n-----------------------------------------+----------------------------\nFace numbers: | Face identifiers:\n +-------/+ +--------+ |\n / 5 / | /| | | f1: (v1, v3, v2)\n / / | / | 3 | | f2: (v1, v2, v5, v4)\n + / | + | | | f3: (v3, v1, v4, v6)\n | 4 /+ |2 +-------/+ | f4: (v2, v3, v6, v5)\n | / | / 1 / | f5: (v4, v5, v6)\n | / |/ / |\n + / + / |\n-----------------------------------------+----------------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Required-methods-to-implement-for-all-subtypes-of-AbstractRefShape-to-define-a-new-reference-shape","page":"Reference cells","title":"Required methods to implement for all subtypes of AbstractRefShape to define a new reference shape","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.reference_vertices(::Type{<:Ferrite.AbstractRefShape})\nFerrite.reference_edges(::Type{<:Ferrite.AbstractRefShape})\nFerrite.reference_faces(::Type{<:Ferrite.AbstractRefShape})","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.reference_vertices-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_vertices","text":"reference_vertices(::Type{<:AbstractRefShape})\nreference_vertices(::AbstractCell)\n\nReturns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/#Ferrite.reference_edges-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_edges","text":"reference_edges(::Type{<:AbstractRefShape})\nreference_edges(::AbstractCell)\n\nReturns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/#Ferrite.reference_faces-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_faces","text":"reference_faces(::Type{<:AbstractRefShape})\nreference_faces(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"which automatically defines","category":"page"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.reference_facets(::Type{<:Ferrite.AbstractRefShape})","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.reference_facets-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_facets","text":"Ferrite.reference_facets(::Type{<:AbstractRefShape})\nFerrite.reference_facets(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a facet.\n\nSee also reference_vertices, reference_edges, and reference_faces.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/#Applicable-methods-to-AbstractRefShapes","page":"Reference cells","title":"Applicable methods to AbstractRefShapes","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.getrefdim(::Type{<:Ferrite.AbstractRefShape})","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.getrefdim-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.getrefdim","text":"Ferrite.getrefdim(RefShape::Type{<:AbstractRefShape})\n\nGet the dimension of the reference shape\n\n\n\n\n\n","category":"method"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"EditURL = \"../literate-tutorials/incompressible_elasticity.jl\"","category":"page"},{"location":"tutorials/incompressible_elasticity/#tutorial-incompressible-elasticity","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: incompressible_elasticity.ipynb.","category":"page"},{"location":"tutorials/incompressible_elasticity/#Introduction","page":"Incompressible elasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Mixed elements can be used to overcome locking when the material becomes incompressible. However, for an element to be stable, it needs to fulfill the LBB condition. In this example we will consider two different element formulations","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"linear displacement with linear pressure approximation (does not fulfill LBB)\nquadratic displacement with linear pressure approximation (does fulfill LBB)","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"The quadratic/linear element is also known as the Taylor-Hood element. We will consider Cook's Membrane with an applied traction on the right hand side.","category":"page"},{"location":"tutorials/incompressible_elasticity/#Commented-program","page":"Incompressible elasticity","title":"Commented program","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"using Ferrite, Tensors\nusing BlockArrays, SparseArrays, LinearAlgebra","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"First we generate a simple grid, specifying the 4 corners of Cooks membrane.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_cook_grid(nx, ny)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((48.0, 44.0)),\n Vec{2}((48.0, 60.0)),\n Vec{2}((0.0, 44.0)),\n ]\n grid = generate_grid(Triangle, (nx, ny), corners)\n # facesets for boundary conditions\n addfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> norm(x[1]) ≈ 48.0)\n return grid\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Next we define a function to set up our cell- and FacetValues.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTriangle}(3)\n facet_qr = FacetQuadratureRule{RefTriangle}(3)\n\n # cell and FacetValues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"We create a DofHandler, with two fields, :u and :p, with possibly different interpolations","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement\n add!(dh, :p, ipp) # pressure\n close!(dh)\n return dh\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"We also need to add Dirichlet boundary conditions on the \"clamped\" facetset. We specify a homogeneous Dirichlet bc on the displacement field, :u.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"clamped\"), x -> zero(x), [1, 2]))\n close!(dbc)\n return dbc\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"The material is linear elastic, which is here specified by the shear and bulk moduli","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"struct LinearElasticity{T}\n G::T\n K::T\nend","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Now to the assembling of the stiffness matrix. This mixed formulation leads to a blocked element matrix. Since Ferrite does not force us to use any particular matrix type we will use a BlockedArray from BlockArrays.jl.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function doassemble(\n cellvalues_u::CellValues,\n cellvalues_p::CellValues,\n facetvalues_u::FacetValues,\n K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity\n )\n f = zeros(ndofs(dh))\n assembler = start_assemble(K, f)\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n # traction vector\n t = Vec{2}((0.0, 1 / 16))\n # cache ɛdev outside the element routine to avoid some unnecessary allocations\n ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)]\n\n for cell in CellIterator(dh)\n fill!(ke, 0)\n fill!(fe, 0)\n assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n\n return K, f\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"The element routine integrates the local stiffness and force vector for all elements. Since the problem results in a symmetric matrix we choose to only assemble the lower part, and then symmetrize it after the loop over the quadrature points.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n u▄, p▄ = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n for q_point in 1:getnquadpoints(cellvalues_u)\n for i in 1:n_basefuncs_u\n ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))\n end\n dΩ = getdetJdV(cellvalues_u, q_point)\n for i in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, i)\n δu = shape_value(cellvalues_u, q_point, i)\n for j in 1:i\n Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ\n end\n end\n\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, q_point, i)\n for j in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, j)\n Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ\n end\n for j in 1:i\n p = shape_value(cellvalues_p, q_point, j)\n Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ\n end\n\n end\n end\n\n symmetrize_lower!(Ke)\n\n # We integrate the Neumann boundary using the FacetValues.\n # We loop over all the facets in the cell, then check if the facet\n # is in our `\"traction\"` facetset.\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues_u, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues_u)\n dΓ = getdetJdV(facetvalues_u, q_point)\n for i in 1:n_basefuncs_u\n δu = shape_value(facetvalues_u, q_point, i)\n fe[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(Ke)\n for i in 1:size(Ke, 1)\n for j in (i + 1):size(Ke, 1)\n Ke[i, j] = Ke[j, i]\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"To evaluate the stresses after solving the problem we once again loop over the cells in the grid. Stresses are evaluated in the quadrature points, however, for export/visualization you typically want values in the nodes of the mesh, or as single data points per cell. For the former you can project the quadrature point data to a finite element space (see the example with the L2Projector in Post processing and visualization). In this example we choose to compute the mean value of the stress within each cell, and thus end up with one data point per cell. The mean value is computed as","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"barboldsymbolsigma_i = frac1 Omega_i\nint_Omega_i boldsymbolsigma mathrmdOmega quad\nOmega_i = int_Omega_i 1 mathrmdOmega","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"where Omega_i is the domain occupied by cell number i, and Omega_i the volume (area) of the cell. The integrals are evaluated using numerical quadrature with the help of cellvalues for u and p, just like in the assembly procedure.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Note that even though all strain components in the out-of-plane direction are zero (plane strain) the stress components are not. Specifically, sigma_33 will be non-zero in this formulation. Therefore we expand the strain to a 3D tensor, and then compute the (3D) stress tensor.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function compute_stresses(\n cellvalues_u::CellValues, cellvalues_p::CellValues,\n dh::DofHandler, mp::LinearElasticity, a::Vector\n )\n ae = zeros(ndofs_per_cell(dh)) # local solution vector\n u_range = dof_range(dh, :u) # local range of dofs corresponding to u\n p_range = dof_range(dh, :p) # local range of dofs corresponding to p\n # Allocate storage for the stresses\n σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid))\n # Loop over the cells and compute the cell-average stress\n for cc in CellIterator(dh)\n # Update cellvalues\n reinit!(cellvalues_u, cc)\n reinit!(cellvalues_p, cc)\n # Extract the cell local part of the solution\n for (i, I) in pairs(celldofs(cc))\n ae[i] = a[I]\n end\n # Loop over the quadrature points\n σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell\n Ωi = 0.0 # cell volume (area)\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Evaluate the strain and the pressure\n ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range)\n p = function_value(cellvalues_p, qp, ae, p_range)\n # Expand strain to 3D\n ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0)\n # Compute the stress in this quadrature point\n σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p\n σΩi += σqp * dΩ\n Ωi += dΩ\n end\n # Store the value\n σ[cellid(cc)] = σΩi / Ωi\n end\n return σ\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Now we have constructed all the necessary components, we just need a function to put it all together.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function solve(ν, interpolation_u, interpolation_p)\n # material\n Emod = 1.0\n Gmod = Emod / 2(1 + ν)\n Kmod = Emod * ν / ((1 + ν) * (1 - 2ν))\n mp = LinearElasticity(Gmod, Kmod)\n\n # Grid, dofhandler, boundary condition\n n = 50\n grid = create_cook_grid(n, n)\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n dbc = create_bc(dh)\n\n # CellValues\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Assembly and solve\n K = allocate_matrix(dh)\n K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp)\n apply!(K, f, dbc)\n u = K \\ f\n\n # Compute the stress\n σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u)\n σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress\n\n # Export the solution and the stress\n filename = \"cook_\" * (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? \"linear\" : \"quadratic\") *\n \"_linear\"\n\n VTKGridFile(filename, grid) do vtk\n write_solution(vtk, dh, u)\n for i in 1:3, j in 1:3\n σij = [x[i, j] for x in σ]\n write_cell_data(vtk, σij, \"sigma_$(i)$(j)\")\n end\n write_cell_data(vtk, σvM, \"sigma von Mises\")\n end\n return u\nend\nnothing # hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"We now define the interpolation for displacement and pressure. We use (scalar) Lagrange interpolation as a basis for both, and for the displacement, which is a vector, we vectorize it to 2 dimensions such that we obtain vector shape functions (and 2nd order tensors for the gradients).","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"linear_p = Lagrange{RefTriangle, 1}()\nlinear_u = Lagrange{RefTriangle, 1}()^2\nquadratic_u = Lagrange{RefTriangle, 2}()^2\nnothing # hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"All that is left is to solve the problem. We choose a value of Poissons ratio that results in incompressibility (ν = 05) and thus expect the linear/linear approximation to return garbage, and the quadratic/linear approximation to be stable.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"u1 = solve(0.5, linear_u, linear_p);\nu2 = solve(0.5, quadratic_u, linear_p);\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/#incompressible_elasticity-plain-program","page":"Incompressible elasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Here follows a version of the program without any comments. The file is also available here: incompressible_elasticity.jl.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"using Ferrite, Tensors\nusing BlockArrays, SparseArrays, LinearAlgebra\n\nfunction create_cook_grid(nx, ny)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((48.0, 44.0)),\n Vec{2}((48.0, 60.0)),\n Vec{2}((0.0, 44.0)),\n ]\n grid = generate_grid(Triangle, (nx, ny), corners)\n # facesets for boundary conditions\n addfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> norm(x[1]) ≈ 48.0)\n return grid\nend;\n\nfunction create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTriangle}(3)\n facet_qr = FacetQuadratureRule{RefTriangle}(3)\n\n # cell and FacetValues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\n\nfunction create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement\n add!(dh, :p, ipp) # pressure\n close!(dh)\n return dh\nend;\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"clamped\"), x -> zero(x), [1, 2]))\n close!(dbc)\n return dbc\nend;\n\nstruct LinearElasticity{T}\n G::T\n K::T\nend\n\nfunction doassemble(\n cellvalues_u::CellValues,\n cellvalues_p::CellValues,\n facetvalues_u::FacetValues,\n K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity\n )\n f = zeros(ndofs(dh))\n assembler = start_assemble(K, f)\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n # traction vector\n t = Vec{2}((0.0, 1 / 16))\n # cache ɛdev outside the element routine to avoid some unnecessary allocations\n ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)]\n\n for cell in CellIterator(dh)\n fill!(ke, 0)\n fill!(fe, 0)\n assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n\n return K, f\nend;\n\nfunction assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n u▄, p▄ = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n for q_point in 1:getnquadpoints(cellvalues_u)\n for i in 1:n_basefuncs_u\n ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))\n end\n dΩ = getdetJdV(cellvalues_u, q_point)\n for i in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, i)\n δu = shape_value(cellvalues_u, q_point, i)\n for j in 1:i\n Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ\n end\n end\n\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, q_point, i)\n for j in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, j)\n Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ\n end\n for j in 1:i\n p = shape_value(cellvalues_p, q_point, j)\n Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ\n end\n\n end\n end\n\n symmetrize_lower!(Ke)\n\n # We integrate the Neumann boundary using the FacetValues.\n # We loop over all the facets in the cell, then check if the facet\n # is in our `\"traction\"` facetset.\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues_u, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues_u)\n dΓ = getdetJdV(facetvalues_u, q_point)\n for i in 1:n_basefuncs_u\n δu = shape_value(facetvalues_u, q_point, i)\n fe[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(Ke)\n for i in 1:size(Ke, 1)\n for j in (i + 1):size(Ke, 1)\n Ke[i, j] = Ke[j, i]\n end\n end\n return\nend;\n\nfunction compute_stresses(\n cellvalues_u::CellValues, cellvalues_p::CellValues,\n dh::DofHandler, mp::LinearElasticity, a::Vector\n )\n ae = zeros(ndofs_per_cell(dh)) # local solution vector\n u_range = dof_range(dh, :u) # local range of dofs corresponding to u\n p_range = dof_range(dh, :p) # local range of dofs corresponding to p\n # Allocate storage for the stresses\n σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid))\n # Loop over the cells and compute the cell-average stress\n for cc in CellIterator(dh)\n # Update cellvalues\n reinit!(cellvalues_u, cc)\n reinit!(cellvalues_p, cc)\n # Extract the cell local part of the solution\n for (i, I) in pairs(celldofs(cc))\n ae[i] = a[I]\n end\n # Loop over the quadrature points\n σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell\n Ωi = 0.0 # cell volume (area)\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Evaluate the strain and the pressure\n ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range)\n p = function_value(cellvalues_p, qp, ae, p_range)\n # Expand strain to 3D\n ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0)\n # Compute the stress in this quadrature point\n σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p\n σΩi += σqp * dΩ\n Ωi += dΩ\n end\n # Store the value\n σ[cellid(cc)] = σΩi / Ωi\n end\n return σ\nend;\n\nfunction solve(ν, interpolation_u, interpolation_p)\n # material\n Emod = 1.0\n Gmod = Emod / 2(1 + ν)\n Kmod = Emod * ν / ((1 + ν) * (1 - 2ν))\n mp = LinearElasticity(Gmod, Kmod)\n\n # Grid, dofhandler, boundary condition\n n = 50\n grid = create_cook_grid(n, n)\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n dbc = create_bc(dh)\n\n # CellValues\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Assembly and solve\n K = allocate_matrix(dh)\n K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp)\n apply!(K, f, dbc)\n u = K \\ f\n\n # Compute the stress\n σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u)\n σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress\n\n # Export the solution and the stress\n filename = \"cook_\" * (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? \"linear\" : \"quadratic\") *\n \"_linear\"\n\n VTKGridFile(filename, grid) do vtk\n write_solution(vtk, dh, u)\n for i in 1:3, j in 1:3\n σij = [x[i, j] for x in σ]\n write_cell_data(vtk, σij, \"sigma_$(i)$(j)\")\n end\n write_cell_data(vtk, σvM, \"sigma von Mises\")\n end\n return u\nend\n\nlinear_p = Lagrange{RefTriangle, 1}()\nlinear_u = Lagrange{RefTriangle, 1}()^2\nquadratic_u = Lagrange{RefTriangle, 2}()^2\n\nu1 = solve(0.5, linear_u, linear_p);\nu2 = solve(0.5, quadratic_u, linear_p);","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"CurrentModule = Ferrite\nDocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/fevalues/#FEValues","page":"FEValues","title":"FEValues","text":"","category":"section"},{"location":"reference/fevalues/#Main-types","page":"FEValues","title":"Main types","text":"","category":"section"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"CellValues and FacetValues are the most common subtypes of Ferrite.AbstractValues. For more details about how these work, please see the related topic guide.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"CellValues\nFacetValues","category":"page"},{"location":"reference/fevalues/#Ferrite.CellValues","page":"FEValues","title":"Ferrite.CellValues","text":"CellValues([::Type{T},] quad_rule::QuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])\n\nA CellValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. in the finite element cell.\n\nArguments:\n\nT: an optional argument (default to Float64) to determine the type the internal data is stored as.\nquad_rule: an instance of a QuadratureRule\nfunc_interpol: an instance of an Interpolation used to interpolate the approximated function\ngeom_interpol: an optional instance of a Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used. For embedded elements the geometric interpolations should be vectorized to the spatial dimension.\n\nKeyword arguments: The following keyword arguments are experimental and may change in future minor releases\n\nupdate_gradients: Specifies if the gradients of the shape functions should be updated (default true)\nupdate_hessians: Specifies if the hessians of the shape functions should be updated (default false)\nupdate_detJdV: Specifies if the volume associated with each quadrature point should be updated (default true)\n\nCommon methods:\n\nreinit!\ngetnquadpoints\ngetdetJdV\nshape_value\nshape_gradient\nshape_symmetric_gradient\nshape_divergence\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nspatial_coordinate\n\n\n\n\n\n","category":"type"},{"location":"reference/fevalues/#Ferrite.FacetValues","page":"FEValues","title":"Ferrite.FacetValues","text":"FacetValues([::Type{T}], quad_rule::FacetQuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])\n\nA FacetValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. on the facets of finite elements.\n\nArguments:\n\nT: an optional argument (default to Float64) to determine the type the internal data is stored as.\nquad_rule: an instance of a FacetQuadratureRule\nfunc_interpol: an instance of an Interpolation used to interpolate the approximated function\ngeom_interpol: an optional instance of an Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used.\n\nKeyword arguments: The following keyword arguments are experimental and may change in future minor releases\n\nupdate_gradients: Specifies if the gradients of the shape functions should be updated (default true)\nupdate_hessians: Specifies if the hessians of the shape functions should be updated (default false)\n\nCommon methods:\n\nreinit!\ngetnquadpoints\ngetdetJdV\nshape_value\nshape_gradient\nshape_symmetric_gradient\nshape_divergence\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nspatial_coordinate\n\n\n\n\n\n","category":"type"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"warning: Embedded API\nCurrently, embedded FEValues returns SArrays, which behave differently from the Tensors for normal value. In the future, we expect to return an AbstractTensor, this change may happen in a minor release, and the API for embedded FEValues should therefore be considered experimental.","category":"page"},{"location":"reference/fevalues/#Applicable-functions","page":"FEValues","title":"Applicable functions","text":"","category":"section"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"The following functions are applicable to both CellValues and FacetValues.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"reinit!\ngetnquadpoints\ngetdetJdV\n\nshape_value(::Ferrite.AbstractValues, ::Int, ::Int)\nshape_gradient(::Ferrite.AbstractValues, ::Int, ::Int)\nshape_symmetric_gradient\nshape_divergence\nshape_curl\ngeometric_value\n\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nfunction_curl\nspatial_coordinate","category":"page"},{"location":"reference/fevalues/#Ferrite.reinit!","page":"FEValues","title":"Ferrite.reinit!","text":"reinit!(cv::CellValues, cell::AbstractCell, x::AbstractVector)\nreinit!(cv::CellValues, x::AbstractVector)\nreinit!(fv::FacetValues, cell::AbstractCell, x::AbstractVector, facet::Int)\nreinit!(fv::FacetValues, x::AbstractVector, function_gradient::Int)\n\nUpdate the CellValues/FacetValues object for a cell or facet with cell coordinates x. The derivatives of the shape functions, and the new integration weights are computed. For interpolations with non-identity mappings, the current cell is also required.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.getnquadpoints","page":"FEValues","title":"Ferrite.getnquadpoints","text":"getnquadpoints(fe_v::AbstractValues)\n\nReturn the number of quadrature points. For FacetValues, this is the number for the current facet.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.getdetJdV","page":"FEValues","title":"Ferrite.getdetJdV","text":"getdetJdV(fe_v::AbstractValues, q_point::Int)\n\nReturn the product between the determinant of the Jacobian and the quadrature point weight for the given quadrature point: det(J(mathbfx)) w_q.\n\nThis value is typically used when one integrates a function on a finite element cell or facet as\n\nintlimits_Omega f(mathbfx) d Omega approx sumlimits_q = 1^n_q f(mathbfx_q) det(J(mathbfx)) w_q intlimits_Gamma f(mathbfx) d Gamma approx sumlimits_q = 1^n_q f(mathbfx_q) det(J(mathbfx)) w_q\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_value-Tuple{Ferrite.AbstractValues, Int64, Int64}","page":"FEValues","title":"Ferrite.shape_value","text":"shape_value(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the value of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"method"},{"location":"reference/fevalues/#Ferrite.shape_gradient-Tuple{Ferrite.AbstractValues, Int64, Int64}","page":"FEValues","title":"Ferrite.shape_gradient","text":"shape_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the gradient of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"method"},{"location":"reference/fevalues/#Ferrite.shape_symmetric_gradient","page":"FEValues","title":"Ferrite.shape_symmetric_gradient","text":"shape_symmetric_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the symmetric gradient of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_divergence","page":"FEValues","title":"Ferrite.shape_divergence","text":"shape_divergence(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the divergence of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_curl","page":"FEValues","title":"Ferrite.shape_curl","text":"shape_curl(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the curl of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.geometric_value","page":"FEValues","title":"Ferrite.geometric_value","text":"geometric_value(fe_v::AbstractValues, q_point, base_function::Int)\n\nReturn the value of the geometric shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_value","page":"FEValues","title":"Ferrite.function_value","text":"function_value(iv::InterfaceValues, q_point::Int, u; here::Bool)\nfunction_value(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)\n\nCompute the value of the function in quadrature point q_point on the \"here\" (here=true) or \"there\" (here=false) side of the interface. u_here and u_there are the values of the degrees of freedom for the respective element.\n\nu is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.\n\nhere determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.\n\nThe value of a scalar valued function is computed as u(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) u_i where u_i are the value of u in the nodes. For a vector valued function the value is calculated as mathbfu(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) mathbfu_i where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\nfunction_value(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the value of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).\n\nThe value of a scalar valued function is computed as u(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) u_i where u_i are the value of u in the nodes. For a vector valued function the value is calculated as mathbfu(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) mathbfu_i where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_gradient","page":"FEValues","title":"Ferrite.function_gradient","text":"function_gradient(iv::InterfaceValues, q_point::Int, u; here::Bool)\nfunction_gradient(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)\n\nCompute the gradient of the function in a quadrature point. u is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.\n\nhere determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.\n\nThe gradient of a scalar function or a vector valued function with use of VectorValues is computed as mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla N_i (mathbfx) u_i or mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla mathbfN_i (mathbfx) u_i respectively, where u_i are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as mathbfnabla mathbfu(mathbfx) = sumlimits_i = 1^n mathbfu_i otimes mathbfnabla N_i (mathbfx) where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\nfunction_gradient(fe_v::AbstractValues{dim}, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the gradient of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).\n\nThe gradient of a scalar function or a vector valued function with use of VectorValues is computed as mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla N_i (mathbfx) u_i or mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla mathbfN_i (mathbfx) u_i respectively, where u_i are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as mathbfnabla mathbfu(mathbfx) = sumlimits_i = 1^n mathbfu_i otimes mathbfnabla N_i (mathbfx) where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_symmetric_gradient","page":"FEValues","title":"Ferrite.function_symmetric_gradient","text":"function_symmetric_gradient(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the symmetric gradient of the function, see function_gradient. Return a SymmetricTensor.\n\nThe symmetric gradient of a scalar function is computed as left mathbfnabla mathbfu(mathbfx_q) right^textsym = sumlimits_i = 1^n frac12 left mathbfnabla N_i (mathbfx_q) otimes mathbfu_i + mathbfu_i otimes mathbfnabla N_i (mathbfx_q) right where mathbfu_i are the nodal values of the function.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_divergence","page":"FEValues","title":"Ferrite.function_divergence","text":"function_divergence(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the divergence of the vector valued function in a quadrature point.\n\nThe divergence of a vector valued functions in the quadrature point mathbfx_q) is computed as mathbfnabla cdot mathbfu(mathbfx_q) = sumlimits_i = 1^n mathbfnabla N_i (mathbfx_q) cdot mathbfu_i where mathbfu_i are the nodal values of the function.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_curl","page":"FEValues","title":"Ferrite.function_curl","text":"function_curl(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the curl of the vector valued function in a quadrature point.\n\nThe curl of a vector valued functions in the quadrature point mathbfx_q) is computed as mathbfnabla times mathbfu(mathbfx_q) = sumlimits_i = 1^n mathbfnabla N_i times (mathbfx_q) cdot mathbfu_i where mathbfu_i are the nodal values of the function.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.spatial_coordinate","page":"FEValues","title":"Ferrite.spatial_coordinate","text":"spatial_coordinate(fe_v::AbstractValues, q_point::Int, x::AbstractVector)\n\nCompute the spatial coordinate in a quadrature point. x contains the nodal coordinates of the cell.\n\nThe coordinate is computed, using the geometric interpolation, as mathbfx = sumlimits_i = 1^n M_i (mathbfxi) mathbfhatx_i.\n\nwhere xiis the coordinate of the given quadrature point q_point of the associated quadrature rule.\n\n\n\n\n\nspatial_coordinate(ip::ScalarInterpolation, ξ::Vec, x::AbstractVector{<:Vec{sdim, T}})\n\nCompute the spatial coordinate in a given quadrature point. x contains the nodal coordinates of the cell.\n\nThe coordinate is computed, using the geometric interpolation, as mathbfx = sumlimits_i = 1^n M_i (mathbfxi) mathbfhatx_i\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"In addition, there are some methods that are unique for FacetValues.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"Ferrite.getcurrentfacet\ngetnormal","category":"page"},{"location":"reference/fevalues/#Ferrite.getcurrentfacet","page":"FEValues","title":"Ferrite.getcurrentfacet","text":"getcurrentfacet(fv::FacetValues)\n\nReturn the current active facet of the FacetValues object (from last reinit!).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.getnormal","page":"FEValues","title":"Ferrite.getnormal","text":"getnormal(fv::FacetValues, qp::Int)\n\nReturn the normal at the quadrature point qp for the active facet of the FacetValues object(from last reinit!).\n\n\n\n\n\ngetnormal(iv::InterfaceValues, qp::Int; here::Bool=true)\n\nReturn the normal vector in the quadrature point qp on the interface. If here = true (default) the outward normal to the \"here\" element is returned, otherwise the outward normal to the \"there\" element.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#reference-interfacevalues","page":"FEValues","title":"InterfaceValues","text":"","category":"section"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"All of the methods for FacetValues apply for InterfaceValues as well. In addition, there are some methods that are unique for InterfaceValues:","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"InterfaceValues\nshape_value_average\nshape_value_jump\nshape_gradient_average\nshape_gradient_jump\nfunction_value_average\nfunction_value_jump\nfunction_gradient_average\nfunction_gradient_jump","category":"page"},{"location":"reference/fevalues/#Ferrite.InterfaceValues","page":"FEValues","title":"Ferrite.InterfaceValues","text":"InterfaceValues\n\nAn InterfaceValues object facilitates the process of evaluating values, averages, jumps and gradients of shape functions and function on the interfaces between elements.\n\nThe first element of the interface is denoted \"here\" and the second element \"there\".\n\nConstructors\n\nInterfaceValues(qr::FacetQuadratureRule, ip::Interpolation): same quadrature rule and interpolation on both sides, default linear Lagrange geometric interpolation.\nInterfaceValues(qr::FacetQuadratureRule, ip::Interpolation, ip_geo::Interpolation): same as above but with given geometric interpolation.\nInterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation): different quadrature rule and interpolation on the two sides, default linear Lagrange geometric interpolation.\nInterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, ip_geo_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation, ip_geo_there::Interpolation): same as above but with given geometric interpolation.\nInterfaceValues(fv::FacetValues): quadrature rule and interpolations from face values (same on both sides).\nInterfaceValues(fv_here::FacetValues, fv_there::FacetValues): quadrature rule and interpolations from the face values.\n\nAssociated methods:\n\nshape_value_average\nshape_value_jump\nshape_gradient_average\nshape_gradient_jump\n\nCommon methods:\n\nreinit!\ngetnquadpoints\ngetdetJdV\nshape_value\nshape_gradient\nshape_divergence\nshape_curl\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nfunction_curl\nspatial_coordinate\n\n\n\n\n\n","category":"type"},{"location":"reference/fevalues/#Ferrite.shape_value_average","page":"FEValues","title":"Ferrite.shape_value_average","text":"shape_value_average(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the average of the value of shape function i at quadrature point qp across the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_value_jump","page":"FEValues","title":"Ferrite.shape_value_jump","text":"shape_value_jump(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the jump of the value of shape function i at quadrature point qp across the interface in the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere cdot vecn^textthere + vecv^texthere cdot vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_gradient_average","page":"FEValues","title":"Ferrite.shape_gradient_average","text":"shape_gradient_average(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the average of the gradient of shape function i at quadrature point qp across the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_gradient_jump","page":"FEValues","title":"Ferrite.shape_gradient_jump","text":"shape_gradient_jump(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the jump of the gradient of shape function i at quadrature point qp across the interface in the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere vecn^textthere + vecv^texthere vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_value_average","page":"FEValues","title":"Ferrite.function_value_average","text":"function_value_average(iv::InterfaceValues, q_point::Int, u)\nfunction_value_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the average of the function value at the quadrature point on the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_value_jump","page":"FEValues","title":"Ferrite.function_value_jump","text":"function_value_jump(iv::InterfaceValues, q_point::Int, u)\nfunction_value_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the jump of the function value at the quadrature point over the interface along the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere vecn^textthere + vecv^texthere vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_gradient_average","page":"FEValues","title":"Ferrite.function_gradient_average","text":"function_gradient_average(iv::InterfaceValues, q_point::Int, u)\nfunction_gradient_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the average of the function gradient at the quadrature point on the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_gradient_jump","page":"FEValues","title":"Ferrite.function_gradient_jump","text":"function_gradient_jump(iv::InterfaceValues, q_point::Int, u)\nfunction_gradient_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the jump of the function gradient at the quadrature point over the interface along the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere vecn^textthere + vecv^texthere vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/assembly/","page":"Assembly","title":"Assembly","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/assembly/#Assembly","page":"Assembly","title":"Assembly","text":"","category":"section"},{"location":"reference/assembly/","page":"Assembly","title":"Assembly","text":"start_assemble\nassemble!\nfinish_assemble","category":"page"},{"location":"reference/assembly/#Ferrite.start_assemble","page":"Assembly","title":"Ferrite.start_assemble","text":"start_assemble(K::AbstractSparseMatrixCSC; fillzero::Bool=true) -> CSCAssembler\nstart_assemble(K::AbstractSparseMatrixCSC, f::Vector; fillzero::Bool=true) -> CSCAssembler\n\nCreate a CSCAssembler from the matrix K and optional vector f.\n\nstart_assemble(K::Symmetric{AbstractSparseMatrixCSC}; fillzero::Bool=true) -> SymmetricCSCAssembler\nstart_assemble(K::Symmetric{AbstractSparseMatrixCSC}, f::Vector=Td[]; fillzero::Bool=true) -> SymmetricCSCAssembler\n\nCreate a SymmetricCSCAssembler from the matrix K and optional vector f.\n\nCSCAssembler and SymmetricCSCAssembler allocate workspace necessary for efficient matrix assembly. To assemble the contribution from an element, use assemble!.\n\nThe keyword argument fillzero can be set to false if K and f should not be zeroed out, but instead keep their current values.\n\n\n\n\n\n","category":"function"},{"location":"reference/assembly/#Ferrite.assemble!","page":"Assembly","title":"Ferrite.assemble!","text":"assemble!(a::COOAssembler, dofs, Ke)\nassemble!(a::COOAssembler, dofs, Ke, fe)\n\nAssembles the element matrix Ke and element vector fe into a.\n\n\n\n\n\nassemble!(a::COOAssembler, rowdofs, coldofs, Ke)\n\nAssembles the matrix Ke into a according to the dofs specified by rowdofs and coldofs.\n\n\n\n\n\nassemble!(g, dofs, ge)\n\nAssembles the element residual ge into the global residual vector g.\n\n\n\n\n\nassemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix)\nassemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix, fe::AbstractVector)\n\nAssemble the element stiffness matrix Ke (and optional force vector fe) into the global stiffness (and force) in A, given the element degrees of freedom dofs.\n\nThis is equivalent to K[dofs, dofs] += Ke and f[dofs] += fe, where K is the global stiffness matrix and f the global force/residual vector, but more efficient.\n\n\n\n\n\n","category":"function"},{"location":"reference/assembly/#Ferrite.finish_assemble","page":"Assembly","title":"Ferrite.finish_assemble","text":"finish_assemble(a::COOAssembler) -> K, f\n\nFinalize the assembly and return the sparse matrix K::SparseMatrixCSC and vector f::Vector. If the assembler have not been used for vector assembly, f is an empty vector.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/performance/#devdocs-performance","page":"Performance analysis","title":"Performance analysis","text":"","category":"section"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"In the benchmark folder we provide basic infrastructure to analyze the performance of Ferrite to help tracking down performance regression issues. Two basic tools can be directly executed via make: A basic benchmark for the current branch and a comparison between two commits. To execute the benchmark on the current branch only open a shell in the benchmark folder and call","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"make benchmark","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"whereas for the comparison of two commits simply call","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"make compare target= baseline=","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"If you have a custom julia executable that is not accessible via the julia command, then you can pass the executable via","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"JULIA_CMD= make compare target= baseline=","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"note: Note\nFor the performance comparison between two commits you must not have any uncommitted or untracked files in your Ferrite.jl folder! Otherwise the PkgBenchmark.jl will fail to setup the comparison.","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"For more fine grained control you can run subsets of the benchmarks via by appending - to compare or benchmark, e.g.","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"make benchmark-mesh","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"to benchmark only the mesh functionality. The following subsets are currently available:","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"assembly\nboundary-conditions\ndofs\nmesh","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"note: Note\nIt is recommended to run all benchmarks before running subsets to get the correct tuning parameters for each benchmark.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"EditURL = \"../literate-gallery/topology_optimization.jl\"","category":"page"},{"location":"gallery/topology_optimization/#tutorial-topology-optimization","page":"Topology optimization","title":"Topology optimization","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Keywords: Topology optimization, weak and strong form, non-linear problem, Laplacian, grid topology","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"(Image: )","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Figure 1: Optimization of the bending beam. Evolution of the density for fixed total mass.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"tip: Tip\nThis example is also available as a Jupyter notebook: topology_optimization.ipynb.","category":"page"},{"location":"gallery/topology_optimization/#Introduction","page":"Topology optimization","title":"Introduction","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Topology optimization is the task of finding structures that are mechanically ideal. In this example we cover the bending beam, where we specify a load, boundary conditions and the total mass. Then, our objective is to find the most suitable geometry within the design space minimizing the compliance (i.e. the inverse stiffness) of the structure. We shortly introduce our simplified model for regular meshes. A detailed derivation of the method and advanced techniques can be found in [14] and [15].","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We start by introducing the local, elementwise density chi in chi_textmin 1 of the material, where we choose chi_textmin slightly above zero to prevent numerical instabilities. Here, chi = chi_textmin means void and chi=1 means bulk material. Then, we use a SIMP ansatz (solid isotropic material with penalization) for the stiffness tensor C(chi) = chi^p C_0, where C_0 is the stiffness of the bulk material. The SIMP exponent p1 ensures that the model prefers the density values void and bulk before the intermediate values. The variational formulation then yields the modified Gibbs energy","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"G = int_Omega frac12 chi^p varepsilon C varepsilon textdV - int_Omega boldsymbolf cdot boldsymbolu textdV - int_partialOmega boldsymbolt cdot boldsymbolu textdA","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Furthermore, we receive the evolution equation of the density and the additional Neumann boundary condition in the strong form","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"p_chi + eta dotchi + lambda + gamma - beta nabla^2 chi ni 0 quad forall textbfx in Omega","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"beta nabla chi cdot textbfn = 0 quad forall textbfx in partial Omega","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"with the thermodynamic driving force","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"p_chi = frac12 p chi^p-1 varepsilon C varepsilon","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We obtain the mechanical displacement field by applying the Finite Element Method to the weak form of the Gibbs energy using Ferrite. In contrast, we use the evolution equation (i.e. the strong form) to calculate the value of the density field chi. The advantage of this \"split\" approach is the very high computation speed. The evolution equation consists of the driving force, the damping parameter eta, the regularization parameter beta times the Laplacian, which is necessary to avoid numerical issues like mesh dependence or checkerboarding, and the constraint parameters lambda, to keep the mass constant, and gamma, to avoid leaving the set chi_textmin 1. By including gradient regularization, it becomes necessary to calculate the Laplacian. The Finite Difference Method for square meshes with the edge length Delta h approximates the Laplacian as follows:","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"nabla^2 chi_p = frac1(Delta h)^2 (chi_n + chi_s + chi_w + chi_e - 4 chi_p)","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Here, the indices refer to the different cardinal directions. Boundary element do not have neighbors in each direction. However, we can calculate the central difference to fulfill Neumann boundary condition. For example, if the element is on the left boundary, we have to fulfill","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"nabla chi_p cdot textbfn = frac1Delta h (chi_w - chi_e) = 0","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"from which follows chi_w = chi_e. Thus for boundary elements we can replace the value for the missing neighbor by the value of the opposite neighbor. In order to find the corresponding neighbor elements, we will make use of Ferrites grid topology funcionalities.","category":"page"},{"location":"gallery/topology_optimization/#Commented-Program","page":"Topology optimization","title":"Commented Program","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We now solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"First we load all necessary packages.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Next, we create a simple square grid of the size 2x1. We apply a fixed Dirichlet boundary condition to the left facet set, called clamped. On the right facet, we create a small set traction, where we will later apply a force in negative y-direction.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function create_grid(n)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((2.0, 0.0)),\n Vec{2}((2.0, 1.0)),\n Vec{2}((0.0, 1.0)),\n ]\n grid = generate_grid(Quadrilateral, (2 * n, n), corners)\n\n # node-/facesets for boundary conditions\n addnodeset!(grid, \"clamped\", x -> x[1] ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05)\n return grid\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Next, we create the FE values, the DofHandler and the Dirichlet boundary condition.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function create_values()\n # quadrature rules\n qr = QuadratureRule{RefQuadrilateral}(2)\n facet_qr = FacetQuadratureRule{RefQuadrilateral}(2)\n\n # cell and facetvalues for u\n ip = Lagrange{RefQuadrilateral, 1}()^2\n cellvalues = CellValues(qr, ip)\n facetvalues = FacetValues(facet_qr, ip)\n\n return cellvalues, facetvalues\nend\n\nfunction create_dofhandler(grid)\n dh = DofHandler(grid)\n add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement\n close!(dh)\n return dh\nend\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getnodeset(dh.grid, \"clamped\"), (x, t) -> zero(Vec{2}), [1, 2]))\n close!(dbc)\n t = 0.0\n update!(dbc, t)\n return dbc\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Now, we define a struct to store all necessary material parameters (stiffness tensor of the bulk material and the parameters for topology optimization) and add a constructor to the struct to initialize it by using the common material parameters Young's modulus and Poisson number.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"struct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}}\n C::S\n χ_min::T\n p::T\n β::T\n η::T\nend\nnothing # hide\n\nfunction MaterialParameters(E, ν, χ_min, p, β, η)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n\n G = E / 2(1 + ν) # =μ\n λ = E * ν / (1 - ν^2) # correction for plane stress included\n\n C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)))\n return MaterialParameters(C, χ_min, p, β, η)\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"To store the density and the strain required to calculate the driving forces, we create the struct MaterialState. We add a constructor to initialize the struct. The function update_material_states! updates the density values once we calculated the new values.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"mutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}}\n χ::T # density\n ε::S # strain in each quadrature point\nend\n\nfunction MaterialState(ρ, n_qp)\n return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp))\nend\n\nfunction update_material_states!(χn1, states, dh)\n for (element, state) in zip(CellIterator(dh), states)\n state.χ = χn1[cellid(element)]\n end\n return\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Next, we define a function to calculate the driving forces for all elements. For this purpose, we iterate through all elements and calculate the average strain in each element. Then, we compute the driving force from the formula introduced at the beginning. We create a second function to collect the density in each element.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function compute_driving_forces(states, mp, dh, χn)\n pΨ = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n ε = sum(state.ε) / length(state.ε) # average element strain\n pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε)\n end\n return pΨ\nend\n\nfunction compute_densities(states, dh)\n χn = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n χn[i] = state.χ\n end\n return χn\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"For the Laplacian we need some neighboorhood information which is constant throughout the analysis so we compute it once and cache it. We iterate through each facet of each element, obtaining the neighboring element by using the getneighborhood function. For boundary facets, the function call will return an empty object. In that case we use the dictionary to instead find the opposite facet, as discussed in the introduction.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function cache_neighborhood(dh, topology)\n nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid))\n _nfacets = nfacets(dh.grid.cells[1])\n opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2)\n\n for element in CellIterator(dh)\n nbg = zeros(Int, _nfacets)\n i = cellid(element)\n for j in 1:_nfacets\n nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))\n if (!isempty(nbg_cellid))\n nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell\n else # boundary facet\n nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]\n end\n end\n\n nbgs[i] = nbg\n end\n\n return nbgs\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Now we calculate the Laplacian using the previously cached neighboorhood information.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function approximate_laplacian(nbgs, χn, Δh)\n ∇²χ = zeros(length(nbgs))\n for i in 1:length(nbgs)\n nbg = nbgs[i]\n ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2)\n end\n\n return ∇²χ\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"For the iterative computation of the solution, a function is needed to update the densities in each element. To ensure that the mass is kept constant, we have to calculate the constraint parameter lambda, which we do via the bisection method. We repeat the calculation until the difference between the average density (calculated from the element-wise trial densities) and the target density nearly vanishes. By using the extremal values of Delta chi as the starting interval, we guarantee that the method converges eventually.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function compute_χn1(χn, Δχ, ρ, ηs, χ_min)\n n_el = length(χn)\n\n χ_trial = zeros(n_el)\n ρ_trial = 0.0\n\n λ_lower = minimum(Δχ) - ηs\n λ_upper = maximum(Δχ) + ηs\n λ_trial = 0.0\n\n while (abs(ρ - ρ_trial) > 1.0e-7)\n for i in 1:n_el\n Δχt = 1 / ηs * (Δχ[i] - λ_trial)\n χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))\n end\n\n ρ_trial = 0.0\n for i in 1:n_el\n ρ_trial += χ_trial[i] / n_el\n end\n\n if (ρ_trial > ρ)\n λ_lower = λ_trial\n elseif (ρ_trial < ρ)\n λ_upper = λ_trial\n end\n λ_trial = 1 / 2 * (λ_upper + λ_lower)\n end\n\n return χ_trial\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Lastly, we use the following helper function to compute the average driving force, which is later used to normalize the driving forces. This makes the used material parameters and numerical parameters independent of the problem.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function compute_average_driving_force(mp, pΨ, χn)\n n = length(pΨ)\n w = zeros(n)\n\n for i in 1:n\n w[i] = (χn[i] - mp.χ_min) * (1 - χn[i])\n end\n\n p_Ω = sum(w .* pΨ) / sum(w) # average driving force\n\n return p_Ω\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Finally, we put everything together to update the density. The loop ensures the stability of the updated solution.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability\n χn = compute_densities(states, dh) # old density field\n χn1 = zeros(length(χn))\n\n for j in 1:n_j\n ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian\n pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces\n p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force\n\n Δχ = pΨ / p_Ω + mp.β * ∇²χ\n\n χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)\n\n if (j < n_j)\n χn[:] = χn1[:]\n end\n end\n\n return χn1\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Now, we move on to the Finite Element part of the program. We use the following function to assemble our linear system.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states)\n r = zeros(ndofs(dh))\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n\n re = zeros(nu) # local residual vector\n Ke = zeros(nu, nu) # local stiffness matrix\n\n for (element, state) in zip(CellIterator(dh), states)\n fill!(Ke, 0)\n fill!(re, 0)\n\n eldofs = celldofs(element)\n ue = u[eldofs]\n\n elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n assemble!(assembler, celldofs(element), Ke, re)\n end\n\n return K, r\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"The element routine is used to calculate the elementwise stiffness matrix and the residual. In contrast to a purely elastomechanic problem, for topology optimization we additionally use our material state to receive the density value of the element and to store the strain at each quadrature point.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, element)\n χ = state.χ\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n @inbounds for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue)\n\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:i\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ\n end\n re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ\n end\n end\n\n symmetrize_lower!(Ke)\n\n return @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))\n if (cellid(element), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues, element, facet)\n t = Vec((0.0, -1.0)) # force pointing downwards\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n\nend\n\nfunction symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We put everything together in the main function. Here the user may choose the radius parameter, which is related to the regularization parameter as beta = ra^2, the starting density, the number of elements in vertical direction and finally the name of the output. Additionally, the user may choose whether only the final design (default) or every iteration step is saved.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"First, we compute the material parameters and create the grid, DofHandler, boundary condition and FE values. Then we prepare the iterative Newton-Raphson method by pre-allocating all important vectors. Furthermore, we create material states for each element and construct the topology of the grid.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"During each iteration step, first we solve our FE problem in the Newton-Raphson loop. With the solution of the elastomechanic problem, we check for convergence of our topology design. The criteria has to be fulfilled twice in a row to avoid oscillations. If no convergence is reached yet, we update our design and prepare the next iteration step. Finally, we output the results in paraview and calculate the relative stiffness of the final design, i.e. how much how the stiffness increased compared to the starting point.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function topopt(ra, ρ, n, filename; output = :false)\n # material\n mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0)\n\n # grid, dofhandler, boundary condition\n grid = create_grid(n)\n dh = create_dofhandler(grid)\n Δh = 1 / n # element edge length\n dbc = create_bc(dh)\n\n # cellvalues\n cellvalues, facetvalues = create_values()\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n un = zeros(n_dofs) # previous solution vector\n\n Δu = zeros(n_dofs) # previous displacement correction\n ΔΔu = zeros(n_dofs) # new displacement correction\n\n # create material states\n states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)]\n\n χ = zeros(getncells(dh.grid))\n\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # stiffness matrix\n\n i_max = 300 ## maximum number of iteration steps\n tol = 1.0e-4\n compliance = 0.0\n compliance_0 = 0.0\n compliance_n = 0.0\n conv = :false\n\n topology = ExclusiveTopology(grid)\n neighboorhoods = cache_neighborhood(dh, topology)\n\n # Newton-Raphson loop\n NEWTON_TOL = 1.0e-8\n print(\"\\n Starting Newton iterations\\n\")\n\n for it in 1:i_max\n apply_zero!(u, dbc)\n newton_itr = -1\n\n while true\n newton_itr += 1\n\n if newton_itr > 10\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n\n # current guess\n u .= un .+ Δu\n K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states)\n norm_r = norm(r[Ferrite.free_dofs(dbc)])\n\n if (norm_r) < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbc)\n ΔΔu = Symmetric(K) \\ r\n\n apply_zero!(ΔΔu, dbc)\n Δu .+= ΔΔu\n end # of loop while NR-Iteration\n\n # calculate compliance\n compliance = 1 / 2 * u' * K * u\n\n if (it == 1)\n compliance_0 = compliance\n end\n\n # check convergence criterium (twice!)\n if (abs(compliance - compliance_n) / compliance < tol)\n if (conv)\n println(\"Converged at iteration number: \", it)\n break\n else\n conv = :true\n end\n else\n conv = :false\n end\n\n # update density\n χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n\n # update old displacement, density and compliance\n un .= u\n Δu .= 0.0\n update_material_states!(χ, states, dh)\n compliance_n = compliance\n\n # output during calculation\n if (output)\n i = @sprintf(\"%3.3i\", it)\n filename_it = string(filename, \"_\", i)\n\n VTKGridFile(filename_it, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n end\n\n # export converged results\n if (!output)\n VTKGridFile(filename, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n @printf \"Rel. stiffness: %.4f \\n\" compliance^(-1) / compliance_0^(-1)\n\n return\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Lastly, we call our main function and compare the results. To create the complete output with all iteration steps, it is possible to set the output parameter to true.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"grid, χ =topopt(0.02, 0.5, 60, \"small_radius\"; output=:false);","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"@time topopt(0.03, 0.5, 60, \"large_radius\"; output = :false);\n#topopt(0.02, 0.5, 60, \"topopt_animation\"; output=:true); # can be used to create animations","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We observe, that the stiffness for the lower value of ra is higher, but also requires more iterations until convergence and finer structures to be manufactured, as can be seen in Figure 2:","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"(Image: )","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Figure 2: Optimization results of the bending beam for smaller (left) and larger (right) value of the regularization parameter beta.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"To prove mesh independence, the user could vary the mesh resolution and compare the results.","category":"page"},{"location":"gallery/topology_optimization/#References","page":"Topology optimization","title":"References","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"D. R. Jantos, K. Hackl and P. Junker. An accurate and fast regularization approach to thermodynamic topology optimization. International Journal for Numerical Methods in Engineering 117, 991–1017 (2019).\n\n\n\nM. Blaszczyk, D. R. Jantos and P. Junker. Application of Taylor series combined with the weighted least square method to thermodynamic topology optimization. Computer Methods in Applied Mechanics and Engineering 393, 114698 (2022).\n\n\n\n","category":"page"},{"location":"gallery/topology_optimization/#topology_optimization-plain-program","page":"Topology optimization","title":"Plain program","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Here follows a version of the program without any comments. The file is also available here: topology_optimization.jl.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf\n\nfunction create_grid(n)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((2.0, 0.0)),\n Vec{2}((2.0, 1.0)),\n Vec{2}((0.0, 1.0)),\n ]\n grid = generate_grid(Quadrilateral, (2 * n, n), corners)\n\n # node-/facesets for boundary conditions\n addnodeset!(grid, \"clamped\", x -> x[1] ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05)\n return grid\nend\n\nfunction create_values()\n # quadrature rules\n qr = QuadratureRule{RefQuadrilateral}(2)\n facet_qr = FacetQuadratureRule{RefQuadrilateral}(2)\n\n # cell and facetvalues for u\n ip = Lagrange{RefQuadrilateral, 1}()^2\n cellvalues = CellValues(qr, ip)\n facetvalues = FacetValues(facet_qr, ip)\n\n return cellvalues, facetvalues\nend\n\nfunction create_dofhandler(grid)\n dh = DofHandler(grid)\n add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement\n close!(dh)\n return dh\nend\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getnodeset(dh.grid, \"clamped\"), (x, t) -> zero(Vec{2}), [1, 2]))\n close!(dbc)\n t = 0.0\n update!(dbc, t)\n return dbc\nend\n\nstruct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}}\n C::S\n χ_min::T\n p::T\n β::T\n η::T\nend\n\nfunction MaterialParameters(E, ν, χ_min, p, β, η)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n\n G = E / 2(1 + ν) # =μ\n λ = E * ν / (1 - ν^2) # correction for plane stress included\n\n C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)))\n return MaterialParameters(C, χ_min, p, β, η)\nend\n\nmutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}}\n χ::T # density\n ε::S # strain in each quadrature point\nend\n\nfunction MaterialState(ρ, n_qp)\n return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp))\nend\n\nfunction update_material_states!(χn1, states, dh)\n for (element, state) in zip(CellIterator(dh), states)\n state.χ = χn1[cellid(element)]\n end\n return\nend\n\nfunction compute_driving_forces(states, mp, dh, χn)\n pΨ = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n ε = sum(state.ε) / length(state.ε) # average element strain\n pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε)\n end\n return pΨ\nend\n\nfunction compute_densities(states, dh)\n χn = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n χn[i] = state.χ\n end\n return χn\nend\n\nfunction cache_neighborhood(dh, topology)\n nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid))\n _nfacets = nfacets(dh.grid.cells[1])\n opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2)\n\n for element in CellIterator(dh)\n nbg = zeros(Int, _nfacets)\n i = cellid(element)\n for j in 1:_nfacets\n nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))\n if (!isempty(nbg_cellid))\n nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell\n else # boundary facet\n nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]\n end\n end\n\n nbgs[i] = nbg\n end\n\n return nbgs\nend\n\nfunction approximate_laplacian(nbgs, χn, Δh)\n ∇²χ = zeros(length(nbgs))\n for i in 1:length(nbgs)\n nbg = nbgs[i]\n ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2)\n end\n\n return ∇²χ\nend\n\nfunction compute_χn1(χn, Δχ, ρ, ηs, χ_min)\n n_el = length(χn)\n\n χ_trial = zeros(n_el)\n ρ_trial = 0.0\n\n λ_lower = minimum(Δχ) - ηs\n λ_upper = maximum(Δχ) + ηs\n λ_trial = 0.0\n\n while (abs(ρ - ρ_trial) > 1.0e-7)\n for i in 1:n_el\n Δχt = 1 / ηs * (Δχ[i] - λ_trial)\n χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))\n end\n\n ρ_trial = 0.0\n for i in 1:n_el\n ρ_trial += χ_trial[i] / n_el\n end\n\n if (ρ_trial > ρ)\n λ_lower = λ_trial\n elseif (ρ_trial < ρ)\n λ_upper = λ_trial\n end\n λ_trial = 1 / 2 * (λ_upper + λ_lower)\n end\n\n return χ_trial\nend\n\nfunction compute_average_driving_force(mp, pΨ, χn)\n n = length(pΨ)\n w = zeros(n)\n\n for i in 1:n\n w[i] = (χn[i] - mp.χ_min) * (1 - χn[i])\n end\n\n p_Ω = sum(w .* pΨ) / sum(w) # average driving force\n\n return p_Ω\nend\n\nfunction update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability\n χn = compute_densities(states, dh) # old density field\n χn1 = zeros(length(χn))\n\n for j in 1:n_j\n ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian\n pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces\n p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force\n\n Δχ = pΨ / p_Ω + mp.β * ∇²χ\n\n χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)\n\n if (j < n_j)\n χn[:] = χn1[:]\n end\n end\n\n return χn1\nend\n\nfunction doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states)\n r = zeros(ndofs(dh))\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n\n re = zeros(nu) # local residual vector\n Ke = zeros(nu, nu) # local stiffness matrix\n\n for (element, state) in zip(CellIterator(dh), states)\n fill!(Ke, 0)\n fill!(re, 0)\n\n eldofs = celldofs(element)\n ue = u[eldofs]\n\n elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n assemble!(assembler, celldofs(element), Ke, re)\n end\n\n return K, r\nend\n\nfunction elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, element)\n χ = state.χ\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n @inbounds for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue)\n\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:i\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ\n end\n re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ\n end\n end\n\n symmetrize_lower!(Ke)\n\n return @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))\n if (cellid(element), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues, element, facet)\n t = Vec((0.0, -1.0)) # force pointing downwards\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n\nend\n\nfunction symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend\n\nfunction topopt(ra, ρ, n, filename; output = :false)\n # material\n mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0)\n\n # grid, dofhandler, boundary condition\n grid = create_grid(n)\n dh = create_dofhandler(grid)\n Δh = 1 / n # element edge length\n dbc = create_bc(dh)\n\n # cellvalues\n cellvalues, facetvalues = create_values()\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n un = zeros(n_dofs) # previous solution vector\n\n Δu = zeros(n_dofs) # previous displacement correction\n ΔΔu = zeros(n_dofs) # new displacement correction\n\n # create material states\n states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)]\n\n χ = zeros(getncells(dh.grid))\n\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # stiffness matrix\n\n i_max = 300 ## maximum number of iteration steps\n tol = 1.0e-4\n compliance = 0.0\n compliance_0 = 0.0\n compliance_n = 0.0\n conv = :false\n\n topology = ExclusiveTopology(grid)\n neighboorhoods = cache_neighborhood(dh, topology)\n\n # Newton-Raphson loop\n NEWTON_TOL = 1.0e-8\n print(\"\\n Starting Newton iterations\\n\")\n\n for it in 1:i_max\n apply_zero!(u, dbc)\n newton_itr = -1\n\n while true\n newton_itr += 1\n\n if newton_itr > 10\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n\n # current guess\n u .= un .+ Δu\n K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states)\n norm_r = norm(r[Ferrite.free_dofs(dbc)])\n\n if (norm_r) < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbc)\n ΔΔu = Symmetric(K) \\ r\n\n apply_zero!(ΔΔu, dbc)\n Δu .+= ΔΔu\n end # of loop while NR-Iteration\n\n # calculate compliance\n compliance = 1 / 2 * u' * K * u\n\n if (it == 1)\n compliance_0 = compliance\n end\n\n # check convergence criterium (twice!)\n if (abs(compliance - compliance_n) / compliance < tol)\n if (conv)\n println(\"Converged at iteration number: \", it)\n break\n else\n conv = :true\n end\n else\n conv = :false\n end\n\n # update density\n χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n\n # update old displacement, density and compliance\n un .= u\n Δu .= 0.0\n update_material_states!(χ, states, dh)\n compliance_n = compliance\n\n # output during calculation\n if (output)\n i = @sprintf(\"%3.3i\", it)\n filename_it = string(filename, \"_\", i)\n\n VTKGridFile(filename_it, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n end\n\n # export converged results\n if (!output)\n VTKGridFile(filename, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n @printf \"Rel. stiffness: %.4f \\n\" compliance^(-1) / compliance_0^(-1)\n\n return\nend\n\n@time topopt(0.03, 0.5, 60, \"large_radius\"; output = :false);\n#topopt(0.02, 0.5, 60, \"topopt_animation\"; output=:true); # can be used to create animations","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"EditURL = \"../literate-gallery/helmholtz.jl\"","category":"page"},{"location":"gallery/helmholtz/#tutorial-helmholtz","page":"Helmholtz equation","title":"Helmholtz equation","text":"","category":"section"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"In this example, we want to solve a (variant of) of the Helmholtz equation. The example is inspired by an dealii step_7 on the standard square.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":" - Delta u + u = f","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"With boundary conditions given by","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"u = g_1 quad x in Gamma_1","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"and","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"n cdot nabla u = g_2 quad x in Gamma_2","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"Here Γ₁ is the union of the top and the right boundary of the square, while Γ₂ is the union of the bottom and the left boundary.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"(Image: )","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"We will use the following weak formulation:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"int_Omega nabla δu cdot nabla u dOmega\n+ int_Omega δu cdot u dOmega\n- int_Omega δu cdot f dOmega\n- int_Gamma_2 δu g_2 dGamma = 0 quad forall δu","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"where δu is a suitable test function that satisfies:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"δu = 0 quad x in Gamma_1","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"and u is a suitable function that satisfies:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"u = g_1 quad x in Gamma_1","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"The example highlights the following interesting features:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"There are two kinds of boundary conditions, \"Dirichlet\" and \"Von Neumann\"\nThe example contains boundary integrals\nThe Dirichlet condition is imposed strongly and the Von Neumann condition is imposed weakly.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"using Ferrite\nusing Tensors\nusing SparseArrays\nusing LinearAlgebra\n\nconst ∇ = Tensors.gradient\nconst Δ = Tensors.hessian;\n\ngrid = generate_grid(Quadrilateral, (150, 150))\n\nip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\nqr_facet = FacetQuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\nfacetvalues = FacetValues(qr_facet, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh)","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"We will set things up, so that a known analytic solution is approximately reproduced. This is a good testing strategy for PDE codes and known as the method of manufactured solutions.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"function u_ana(x::Vec{2, T}) where {T}\n xs = (\n Vec{2}((-0.5, 0.5)),\n Vec{2}((-0.5, -0.5)),\n Vec{2}((0.5, -0.5)),\n )\n σ = 1 / 8\n s = zero(eltype(x))\n for i in 1:3\n s += exp(- norm(x - xs[i])^2 / σ^2)\n end\n return max(1.0e-15 * one(T), s) # Denormals, be gone\nend;\n\ndbcs = ConstraintHandler(dh)","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"The (strong) Dirichlet boundary condition can be handled automatically by the Ferrite library.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"dbc = Dirichlet(:u, union(getfacetset(grid, \"top\"), getfacetset(grid, \"right\")), (x, t) -> u_ana(x))\nadd!(dbcs, dbc)\nclose!(dbcs)\nupdate!(dbcs, 0.0)\n\nK = allocate_matrix(dh);\n\nfunction doassemble(\n cellvalues::CellValues, facetvalues::FacetValues,\n K::SparseMatrixCSC, dh::DofHandler\n )\n b = 1.0\n f = zeros(ndofs(dh))\n assembler = start_assemble(K, f)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n\n fe = zeros(n_basefuncs) # Local force vector\n Ke = zeros(n_basefuncs, n_basefuncs) # Local stiffness mastrix\n\n for (cellcount, cell) in enumerate(CellIterator(dh))\n fill!(Ke, 0)\n fill!(fe, 0)\n coords = getcoordinates(cell)\n\n reinit!(cellvalues, cell)","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"First we derive the non boundary part of the variation problem from the destined solution u_ana","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"int_Omega nabla δu cdot nabla u dOmega\n+ int_Omega δu cdot u dOmega\n- int_Omega δu cdot f dOmega","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":" for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n coords_qp = spatial_coordinate(cellvalues, q_point, coords)\n f_true = -LinearAlgebra.tr(hessian(u_ana, coords_qp)) + u_ana(coords_qp)\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n fe[i] += (δu * f_true) * dΩ\n for j in 1:n_basefuncs\n u = shape_value(cellvalues, q_point, j)\n ∇u = shape_gradient(cellvalues, q_point, j)\n Ke[i, j] += (∇δu ⋅ ∇u + δu * u) * dΩ\n end\n end\n end","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"Now we manually add the von Neumann boundary terms","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"int_Gamma_2 δu g_2 dGamma","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":" for facet in 1:nfacets(cell)\n if (cellcount, facet) ∈ getfacetset(grid, \"left\") ||\n (cellcount, facet) ∈ getfacetset(grid, \"bottom\")\n reinit!(facetvalues, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues)\n coords_qp = spatial_coordinate(facetvalues, q_point, coords)\n n = getnormal(facetvalues, q_point)\n g_2 = gradient(u_ana, coords_qp) ⋅ n\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n fe[i] += (δu * g_2) * dΓ\n end\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend;\n\nK, f = doassemble(cellvalues, facetvalues, K, dh);\napply!(K, f, dbcs)\nu = Symmetric(K) \\ f;\n\nvtk = VTKGridFile(\"helmholtz\", dh)\nwrite_solution(vtk, dh, u)\nclose(vtk)\nprintln(\"Helmholtz successful\")","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"EditURL = \"../literate-tutorials/stokes-flow.jl\"","category":"page"},{"location":"tutorials/stokes-flow/#tutorial-stokes-flow","page":"Stokes flow","title":"Stokes flow","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Keywords: periodic boundary conditions, multiple fields, mean value constraint","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"tip: Tip\nThis example is also available as a Jupyter notebook: stokes-flow.ipynb.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"(Image: ) Figure 1: Left: Computational domain Omega with boundaries Gamma_1, Gamma_3 (periodic boundary conditions) and Gamma_2, Gamma_4 (homogeneous Dirichlet boundary conditions). Right: Magnitude of the resulting velocity field.","category":"page"},{"location":"tutorials/stokes-flow/#Introduction-and-problem-formulation","page":"Stokes flow","title":"Introduction and problem formulation","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"This example is a translation of the step-45 example from deal.ii which solves Stokes flow on a quarter circle. In particular it shows how to use periodic boundary conditions, how to solve a problem with multiple unknown fields, and how to enforce a specific mean value of the solution. For the mesh generation we use Gmsh.jl and then use FerriteGmsh.jl to import the mesh into Ferrite's format.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The strong form of Stokes flow with velocity boldsymbolu and pressure p can be written as follows:","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\n-Delta boldsymbolu + boldsymbolnabla p = bigl(exp(-100boldsymbolx - (075 01)^2) 0bigr) =\nboldsymbolb quad forall boldsymbolx in Omega\n-boldsymbolnabla cdot boldsymbolu = 0 quad forall boldsymbolx in Omega\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"where the domain is defined as Omega = boldsymbolx in (0 1)^2 boldsymbolx in (05 1), see Figure 1. For the velocity we use periodic boundary conditions on the inlet Gamma_1 and outlet Gamma_3:","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\nu_x(0nu) = -u_y(nu 0) quad nu in 05 1\nu_y(0nu) = u_x(nu 0) quad nu in 05 1\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"and homogeneous Dirichlet boundary conditions for Gamma_2 and Gamma_4:","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"boldsymbolu = boldsymbol0 quad forall boldsymbolx in\nGamma_2 cup Gamma_4 = boldsymbolx boldsymbolx in 05 1","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The corresponding weak form reads as follows: Find (boldsymbolu p) in mathbbU times mathrmL_2 s.t.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\nint_Omega Bigldeltaboldsymboluotimesboldsymbolnablaboldsymboluotimesboldsymbolnabla -\n(boldsymbolnablacdotdeltaboldsymbolu) p Bigr mathrmdOmega =\nint_Omega deltaboldsymbolu cdot boldsymbolb mathrmdOmega quad forall\ndelta boldsymbolu in mathbbU\nint_Omega - (boldsymbolnablacdotboldsymbolu) delta p mathrmdOmega = 0\nquad forall delta p in mathrmL_2\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"where mathbbU is a suitable function space, that, in particular, enforces the Dirichlet boundary conditions, and the periodicity constraints. This formulation is a saddle point problem, and, just like the example with Incompressible Elasticity, we need our formulation to fulfill the LBB condition. We ensure this by using a quadratic approximation for the velocity field, and a linear approximation for the pressure.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"With this formulation and boundary conditions for boldsymbolu the pressure will only be determined up to a constant. We will therefore add an additional constraint which fixes this constant (see deal.ii step-11 for some more discussion around this). In particular, we will enforce the mean value of the pressure on the boundary to be 0, i.e. int_Gamma p mathrmdGamma = 0. One option is to enforce this using a Lagrange multiplier. This would give a contribution lambda int_Gamma delta p mathrmdGamma to the second equation in the weak form above, and a third equation deltalambda int_Gamma p mathrmdGamma = 0 so that we can solve for lambda. However, since we in this case are not interested in computing lambda, and since the constraint is linear, we can directly embed this constraint using an AffineConstraint in Ferrite.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"After FE discretization we obtain a linear system of the form underlineunderlineK underlinea = underlinef, where","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"underlineunderlineK =\nbeginbmatrix\nunderlineunderlineK_uu underlineunderlineK_pu^textrmT \nunderlineunderlineK_pu underlineunderline0\nendbmatrix quad\nunderlinea = beginbmatrix\nunderlinea_u \nunderlinea_p\nendbmatrix quad\nunderlinef = beginbmatrix\nunderlinef_u \nunderline0\nendbmatrix","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"and where","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\n(underlineunderlineK_uu)_ij = int_Omega boldsymbolphi^u_iotimesboldsymbolnablaboldsymbolphi^u_jotimesboldsymbolnabla mathrmdOmega \n(underlineunderlineK_pu)_ij = int_Omega - (boldsymbolnablacdotboldsymbolphi^u_j) phi^p_i mathrmdOmega \n(underlinef_u)_i = int_Omega boldsymbolphi^u_i cdot boldsymbolb mathrmdOmega\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The affine constraint to enforce zero mean pressure on the boundary is obtained from underlineunderlineC_p underlinea_p = underline0, where","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"(underlineunderlineC_p)_1j = int_Gamma phi^p_j mathrmdGamma","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"note: Note\nThe constraint matrix underlineunderlineC_p is the same matrix we would have obtained when assembling the system with the Lagrange multiplier. In that case the full system would beunderlineunderlineK =\nbeginbmatrix\nunderlineunderlineK_uu underlineunderlineK_pu^textrmT \nunderlineunderline0\nunderlineunderlineK_pu underlineunderline0 underlineunderlineC_p^mathrmT \nunderlineunderline0 underlineunderlineC_p 0 \nendbmatrix quad\nunderlinea = beginbmatrix\nunderlinea_u \nunderlinea_p \nunderlinea_lambda\nendbmatrix quad\nunderlinef = beginbmatrix\nunderlinef_u \nunderline0 \nunderline0\nendbmatrix","category":"page"},{"location":"tutorials/stokes-flow/#Commented-program","page":"Stokes flow","title":"Commented program","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays","category":"page"},{"location":"tutorials/stokes-flow/#Geometry-and-mesh-generation-with-Gmsh.jl","page":"Stokes flow","title":"Geometry and mesh generation with Gmsh.jl","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"In the setup_grid function below we use the Gmsh.jl package for setting up the geometry and performing the meshing. We will not discuss this part in much detail but refer to the Gmsh API documentation instead. The most important thing to note is the mesh periodicity constraint that is applied between the \"inlet\" and \"outlet\" parts using gmsh.model.set_periodic. This is necessary to later on apply a periodicity constraint for the approximated velocity field.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_grid(h = 0.05)\n # Initialize gmsh\n Gmsh.initialize()\n gmsh.option.set_number(\"General.Verbosity\", 2)\n\n # Add the points\n o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h)\n p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h)\n p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h)\n p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h)\n p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h)\n\n # Add the lines\n l1 = gmsh.model.geo.add_line(p1, p2)\n l2 = gmsh.model.geo.add_circle_arc(p2, o, p3)\n l3 = gmsh.model.geo.add_line(p3, p4)\n l4 = gmsh.model.geo.add_circle_arc(p4, o, p1)\n\n # Create the closed curve loop and the surface\n loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4])\n surf = gmsh.model.geo.add_plane_surface([loop])\n\n # Synchronize the model\n gmsh.model.geo.synchronize()\n\n # Create the physical domains\n gmsh.model.add_physical_group(1, [l1], -1, \"Γ1\")\n gmsh.model.add_physical_group(1, [l2], -1, \"Γ2\")\n gmsh.model.add_physical_group(1, [l3], -1, \"Γ3\")\n gmsh.model.add_physical_group(1, [l4], -1, \"Γ4\")\n gmsh.model.add_physical_group(2, [surf])\n\n # Add the periodicity constraint using 4x4 affine transformation matrix,\n # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations\n transformation_matrix = zeros(4, 4)\n transformation_matrix[1, 2] = 1 # -sin(-pi/2)\n transformation_matrix[2, 1] = -1 # cos(-pi/2)\n transformation_matrix[3, 3] = 1\n transformation_matrix[4, 4] = 1\n transformation_matrix = vec(transformation_matrix')\n gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix)\n\n # Generate a 2D mesh\n gmsh.model.mesh.generate(2)\n\n # Save the mesh, and read back in as a Ferrite Grid\n grid = mktempdir() do dir\n path = joinpath(dir, \"mesh.msh\")\n gmsh.write(path)\n togrid(path)\n end\n\n # Finalize the Gmsh library\n Gmsh.finalize()\n\n return grid\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Degrees-of-freedom","page":"Stokes flow","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"As mentioned in the introduction we will use a quadratic approximation for the velocity field and a linear approximation for the pressure to ensure that we fulfill the LBB condition. We create the corresponding FE values with interpolations ipu for the velocity and ipp for the pressure. Note that we specify linear geometric mapping (ipg) for both the velocity and pressure because our grid contains linear triangles. However, since linear mapping is default this could have been skipped. We also construct facet-values for the pressure since we need to integrate along the boundary when assembling the constraint matrix underlineunderlineC.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_fevalues(ipu, ipp, ipg)\n qr = QuadratureRule{RefTriangle}(2)\n cvu = CellValues(qr, ipu, ipg)\n cvp = CellValues(qr, ipp, ipg)\n qr_facet = FacetQuadratureRule{RefTriangle}(2)\n fvp = FacetValues(qr_facet, ipp, ipg)\n return cvu, cvp, fvp\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The setup_dofs function creates the DofHandler, and adds the two fields: a vector field :u with interpolation ipu, and a scalar field :p with interpolation ipp.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_dofs(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu)\n add!(dh, :p, ipp)\n close!(dh)\n return dh\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Boundary-conditions-and-constraints","page":"Stokes flow","title":"Boundary conditions and constraints","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Now it is time to setup the ConstraintHandler and add our boundary conditions and the mean value constraint. This is perhaps the most interesting section in this example, and deserves some attention.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Let's first discuss the assembly of the constraint matrix underlineunderlineC and how to create an AffineConstraint from it. This is done in the setup_mean_constraint function below. Assembling this is not so different from standard assembly in Ferrite: we loop over all the facets, loop over the quadrature points, and loop over the shape functions. Note that since there is only one constraint the matrix will only have one row. After assembling C we construct an AffineConstraint from it. We select the constrained dof to be the one with the highest weight (just to avoid selecting one with 0 or a very small weight), then move the remaining to the right hand side. As an example, consider the case where the constraint equation underlineunderlineC_p underlinea_p is","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"w_10 p_10 + w_23 p_23 + w_154 p_154 = 0","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"i.e. dofs 10, 23, and 154, are the ones located on the boundary (all other dofs naturally gives 0 contribution). If w_23 is the largest weight, then we select p_23 to be the constrained one, and thus reorder the constraint to the form","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"p_23 = -fracw_10w_23 p_10 -fracw_154w_23 p_154 + 0","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"which is the form the AffineConstraint constructor expects.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"note: Note\nIf all nodes along the boundary are equidistant all the weights would be the same. In this case we can construct the constraint without having to do any integration by simply finding all degrees of freedom that are located along the boundary (and using 1 as the weight). This is what is done in the deal.ii step-11 example.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_mean_constraint(dh, fvp)\n assembler = Ferrite.COOAssembler()\n # All external boundaries\n set = union(\n getfacetset(dh.grid, \"Γ1\"),\n getfacetset(dh.grid, \"Γ2\"),\n getfacetset(dh.grid, \"Γ3\"),\n getfacetset(dh.grid, \"Γ4\"),\n )\n # Allocate buffers\n range_p = dof_range(dh, :p)\n element_dofs = zeros(Int, ndofs_per_cell(dh))\n element_dofs_p = view(element_dofs, range_p)\n element_coords = zeros(Vec{2}, 3)\n Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row)\n # Loop over all the boundaries\n for (ci, fi) in set\n Ce .= 0\n getcoordinates!(element_coords, dh.grid, ci)\n reinit!(fvp, element_coords, fi)\n celldofs!(element_dofs, dh, ci)\n for qp in 1:getnquadpoints(fvp)\n dΓ = getdetJdV(fvp, qp)\n for i in 1:getnbasefunctions(fvp)\n Ce[1, i] += shape_value(fvp, qp, i) * dΓ\n end\n end\n # Assemble to row 1\n assemble!(assembler, [1], element_dofs_p, Ce)\n end\n C, _ = finish_assemble(assembler)\n # Create an AffineConstraint from the C-matrix\n _, J, V = findnz(C)\n _, constrained_dof_idx = findmax(abs2, V)\n constrained_dof = J[constrained_dof_idx]\n V ./= V[constrained_dof_idx]\n mean_value_constraint = AffineConstraint(\n constrained_dof,\n Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof],\n 0.0,\n )\n return mean_value_constraint\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"We now setup all the boundary conditions in the setup_constraints function below. Since the periodicity constraint for this example is between two boundaries which are not parallel to each other we need to i) compute the mapping between each mirror facet and the corresponding image facet (on the element level) and ii) describe the dof relation between dofs on these two facets. In Ferrite this is done by defining a transformation of entities on the image boundary such that they line up with the matching entities on the mirror boundary. In this example we consider the inlet Gamma_1 to be the image, and the outlet Gamma_3 to be the mirror. The necessary transformation to apply then becomes a rotation of pi2 radians around the out-of-plane axis. We set up the rotation matrix R, and then compute the mapping between mirror and image facets using collect_periodic_facets where the rotation is applied to the coordinates. In the next step we construct the constraint using the PeriodicDirichlet constructor. We pass the constructor the computed mapping, and also the rotation matrix. This matrix is used to rotate the dofs on the mirror surface such that we properly constrain boldsymbolu_x-dofs on the mirror to -boldsymbolu_y-dofs on the image, and boldsymbolu_y-dofs on the mirror to boldsymbolu_x-dofs on the image.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"For the remaining part of the boundary we add a homogeneous Dirichlet boundary condition on both components of the velocity field. This is done using the Dirichlet constructor, which we have discussed in other tutorials.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_constraints(dh, fvp)\n ch = ConstraintHandler(dh)\n # Periodic BC\n R = rotation_tensor(π / 2)\n periodic_faces = collect_periodic_facets(dh.grid, \"Γ3\", \"Γ1\", x -> R ⋅ x)\n periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2])\n add!(ch, periodic)\n # Dirichlet BC\n Γ24 = union(getfacetset(dh.grid, \"Γ2\"), getfacetset(dh.grid, \"Γ4\"))\n dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2])\n add!(ch, dbc)\n # Compute mean value constraint and add it\n mean_value_constraint = setup_mean_constraint(dh, fvp)\n add!(ch, mean_value_constraint)\n # Finalize\n close!(ch)\n update!(ch, 0)\n return ch\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Global-and-local-assembly","page":"Stokes flow","title":"Global and local assembly","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Assembly of the global system is also something that we have seen in many previous tutorials. One interesting thing to note here is that, since we have two unknown fields, we use the dof_range function to make sure we assemble the element contributions to the correct block of the local stiffness matrix ke.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function assemble_system!(K, f, dh, cvu, cvp)\n assembler = start_assemble(K, f)\n ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n fe = zeros(ndofs_per_cell(dh))\n range_u = dof_range(dh, :u)\n ndofs_u = length(range_u)\n range_p = dof_range(dh, :p)\n ndofs_p = length(range_p)\n ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u)\n ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u)\n divϕᵤ = Vector{Float64}(undef, ndofs_u)\n ϕₚ = Vector{Float64}(undef, ndofs_p)\n for cell in CellIterator(dh)\n reinit!(cvu, cell)\n reinit!(cvp, cell)\n ke .= 0\n fe .= 0\n for qp in 1:getnquadpoints(cvu)\n dΩ = getdetJdV(cvu, qp)\n for i in 1:ndofs_u\n ϕᵤ[i] = shape_value(cvu, qp, i)\n ∇ϕᵤ[i] = shape_gradient(cvu, qp, i)\n divϕᵤ[i] = shape_divergence(cvu, qp, i)\n end\n for i in 1:ndofs_p\n ϕₚ[i] = shape_value(cvp, qp, i)\n end\n # u-u\n for (i, I) in pairs(range_u), (j, J) in pairs(range_u)\n ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ\n end\n # u-p\n for (i, I) in pairs(range_u), (j, J) in pairs(range_p)\n ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ\n end\n # p-u\n for (i, I) in pairs(range_p), (j, J) in pairs(range_u)\n ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ\n end\n # rhs\n for (i, I) in pairs(range_u)\n x = spatial_coordinate(cvu, qp, getcoordinates(cell))\n b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2)\n bv = Vec{2}((b, 0.0))\n fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ\n end\n end\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n return K, f\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Running-the-simulation","page":"Stokes flow","title":"Running the simulation","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"We now have all the puzzle pieces, and just need to define the main function, which puts them all together.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function main()\n # Grid\n h = 0.05 # approximate element size\n grid = setup_grid(h)\n # Interpolations\n ipu = Lagrange{RefTriangle, 2}()^2 # quadratic\n ipp = Lagrange{RefTriangle, 1}() # linear\n # Dofs\n dh = setup_dofs(grid, ipu, ipp)\n # FE values\n ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation\n cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg)\n # Boundary conditions\n ch = setup_constraints(dh, fvp)\n # Global tangent matrix and rhs\n coupling = [true true; true false] # no coupling between pressure test/trial functions\n K = allocate_matrix(dh, ch; coupling = coupling)\n f = zeros(ndofs(dh))\n # Assemble system\n assemble_system!(K, f, dh, cvu, cvp)\n # Apply boundary conditions and solve\n apply!(K, f, ch)\n u = K \\ f\n apply!(u, ch)\n # Export the solution\n VTKGridFile(\"stokes-flow\", grid) do vtk\n write_solution(vtk, dh, u)\n end\n\n\n return\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Run it!","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"main()","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The resulting magnitude of the velocity field is visualized in Figure 1.","category":"page"},{"location":"tutorials/stokes-flow/#stokes-flow-plain-program","page":"Stokes flow","title":"Plain program","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Here follows a version of the program without any comments. The file is also available here: stokes-flow.jl.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays\n\nfunction setup_grid(h = 0.05)\n # Initialize gmsh\n Gmsh.initialize()\n gmsh.option.set_number(\"General.Verbosity\", 2)\n\n # Add the points\n o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h)\n p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h)\n p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h)\n p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h)\n p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h)\n\n # Add the lines\n l1 = gmsh.model.geo.add_line(p1, p2)\n l2 = gmsh.model.geo.add_circle_arc(p2, o, p3)\n l3 = gmsh.model.geo.add_line(p3, p4)\n l4 = gmsh.model.geo.add_circle_arc(p4, o, p1)\n\n # Create the closed curve loop and the surface\n loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4])\n surf = gmsh.model.geo.add_plane_surface([loop])\n\n # Synchronize the model\n gmsh.model.geo.synchronize()\n\n # Create the physical domains\n gmsh.model.add_physical_group(1, [l1], -1, \"Γ1\")\n gmsh.model.add_physical_group(1, [l2], -1, \"Γ2\")\n gmsh.model.add_physical_group(1, [l3], -1, \"Γ3\")\n gmsh.model.add_physical_group(1, [l4], -1, \"Γ4\")\n gmsh.model.add_physical_group(2, [surf])\n\n # Add the periodicity constraint using 4x4 affine transformation matrix,\n # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations\n transformation_matrix = zeros(4, 4)\n transformation_matrix[1, 2] = 1 # -sin(-pi/2)\n transformation_matrix[2, 1] = -1 # cos(-pi/2)\n transformation_matrix[3, 3] = 1\n transformation_matrix[4, 4] = 1\n transformation_matrix = vec(transformation_matrix')\n gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix)\n\n # Generate a 2D mesh\n gmsh.model.mesh.generate(2)\n\n # Save the mesh, and read back in as a Ferrite Grid\n grid = mktempdir() do dir\n path = joinpath(dir, \"mesh.msh\")\n gmsh.write(path)\n togrid(path)\n end\n\n # Finalize the Gmsh library\n Gmsh.finalize()\n\n return grid\nend\n\nfunction setup_fevalues(ipu, ipp, ipg)\n qr = QuadratureRule{RefTriangle}(2)\n cvu = CellValues(qr, ipu, ipg)\n cvp = CellValues(qr, ipp, ipg)\n qr_facet = FacetQuadratureRule{RefTriangle}(2)\n fvp = FacetValues(qr_facet, ipp, ipg)\n return cvu, cvp, fvp\nend\n\nfunction setup_dofs(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu)\n add!(dh, :p, ipp)\n close!(dh)\n return dh\nend\n\nfunction setup_mean_constraint(dh, fvp)\n assembler = Ferrite.COOAssembler()\n # All external boundaries\n set = union(\n getfacetset(dh.grid, \"Γ1\"),\n getfacetset(dh.grid, \"Γ2\"),\n getfacetset(dh.grid, \"Γ3\"),\n getfacetset(dh.grid, \"Γ4\"),\n )\n # Allocate buffers\n range_p = dof_range(dh, :p)\n element_dofs = zeros(Int, ndofs_per_cell(dh))\n element_dofs_p = view(element_dofs, range_p)\n element_coords = zeros(Vec{2}, 3)\n Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row)\n # Loop over all the boundaries\n for (ci, fi) in set\n Ce .= 0\n getcoordinates!(element_coords, dh.grid, ci)\n reinit!(fvp, element_coords, fi)\n celldofs!(element_dofs, dh, ci)\n for qp in 1:getnquadpoints(fvp)\n dΓ = getdetJdV(fvp, qp)\n for i in 1:getnbasefunctions(fvp)\n Ce[1, i] += shape_value(fvp, qp, i) * dΓ\n end\n end\n # Assemble to row 1\n assemble!(assembler, [1], element_dofs_p, Ce)\n end\n C, _ = finish_assemble(assembler)\n # Create an AffineConstraint from the C-matrix\n _, J, V = findnz(C)\n _, constrained_dof_idx = findmax(abs2, V)\n constrained_dof = J[constrained_dof_idx]\n V ./= V[constrained_dof_idx]\n mean_value_constraint = AffineConstraint(\n constrained_dof,\n Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof],\n 0.0,\n )\n return mean_value_constraint\nend\n\nfunction setup_constraints(dh, fvp)\n ch = ConstraintHandler(dh)\n # Periodic BC\n R = rotation_tensor(π / 2)\n periodic_faces = collect_periodic_facets(dh.grid, \"Γ3\", \"Γ1\", x -> R ⋅ x)\n periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2])\n add!(ch, periodic)\n # Dirichlet BC\n Γ24 = union(getfacetset(dh.grid, \"Γ2\"), getfacetset(dh.grid, \"Γ4\"))\n dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2])\n add!(ch, dbc)\n # Compute mean value constraint and add it\n mean_value_constraint = setup_mean_constraint(dh, fvp)\n add!(ch, mean_value_constraint)\n # Finalize\n close!(ch)\n update!(ch, 0)\n return ch\nend\n\nfunction assemble_system!(K, f, dh, cvu, cvp)\n assembler = start_assemble(K, f)\n ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n fe = zeros(ndofs_per_cell(dh))\n range_u = dof_range(dh, :u)\n ndofs_u = length(range_u)\n range_p = dof_range(dh, :p)\n ndofs_p = length(range_p)\n ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u)\n ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u)\n divϕᵤ = Vector{Float64}(undef, ndofs_u)\n ϕₚ = Vector{Float64}(undef, ndofs_p)\n for cell in CellIterator(dh)\n reinit!(cvu, cell)\n reinit!(cvp, cell)\n ke .= 0\n fe .= 0\n for qp in 1:getnquadpoints(cvu)\n dΩ = getdetJdV(cvu, qp)\n for i in 1:ndofs_u\n ϕᵤ[i] = shape_value(cvu, qp, i)\n ∇ϕᵤ[i] = shape_gradient(cvu, qp, i)\n divϕᵤ[i] = shape_divergence(cvu, qp, i)\n end\n for i in 1:ndofs_p\n ϕₚ[i] = shape_value(cvp, qp, i)\n end\n # u-u\n for (i, I) in pairs(range_u), (j, J) in pairs(range_u)\n ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ\n end\n # u-p\n for (i, I) in pairs(range_u), (j, J) in pairs(range_p)\n ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ\n end\n # p-u\n for (i, I) in pairs(range_p), (j, J) in pairs(range_u)\n ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ\n end\n # rhs\n for (i, I) in pairs(range_u)\n x = spatial_coordinate(cvu, qp, getcoordinates(cell))\n b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2)\n bv = Vec{2}((b, 0.0))\n fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ\n end\n end\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n return K, f\nend\n\nfunction main()\n # Grid\n h = 0.05 # approximate element size\n grid = setup_grid(h)\n # Interpolations\n ipu = Lagrange{RefTriangle, 2}()^2 # quadratic\n ipp = Lagrange{RefTriangle, 1}() # linear\n # Dofs\n dh = setup_dofs(grid, ipu, ipp)\n # FE values\n ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation\n cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg)\n # Boundary conditions\n ch = setup_constraints(dh, fvp)\n # Global tangent matrix and rhs\n coupling = [true true; true false] # no coupling between pressure test/trial functions\n K = allocate_matrix(dh, ch; coupling = coupling)\n f = zeros(ndofs(dh))\n # Assemble system\n assemble_system!(K, f, dh, cvu, cvp)\n # Apply boundary conditions and solve\n apply!(K, f, ch)\n u = K \\ f\n apply!(u, ch)\n # Export the solution\n VTKGridFile(\"stokes-flow\", grid) do vtk\n write_solution(vtk, dh, u)\n end\n\n\n return\nend\n\nmain()","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/#Reference","page":"Reference overview","title":"Reference","text":"","category":"section"},{"location":"reference/","page":"Reference overview","title":"Reference overview","text":"Pages = [\n \"quadrature.md\",\n \"interpolations.md\",\n \"fevalues.md\",\n \"dofhandler.md\",\n \"assembly.md\",\n \"boundary_conditions.md\",\n \"grid.md\",\n \"export.md\",\n \"utils.md\",\n]","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"EditURL = \"../literate-tutorials/reactive_surface.jl\"","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"if isdefined(Main, :is_ci) #hide\n IS_CI = Main.is_ci #hide\nelse #hide\n IS_CI = false #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/#tutorial-reactive-surface","page":"Reactive surface","title":"Reactive surface","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"(Image: )","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Figure 1: Reactant concentration field of the Gray-Scott model on the unit sphere.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"tip: Tip\nThis example is also available as a Jupyter notebook: reactive_surface.ipynb.","category":"page"},{"location":"tutorials/reactive_surface/#Introduction","page":"Reactive surface","title":"Introduction","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"This tutorial gives a quick tutorial on how to assemble and solve time-dependent problems on embedded surfaces.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"For this showcase we use the well known Gray-Scott model, which is a well-known reaction-diffusion system to study pattern formation. The strong form is given by","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" beginaligned\n partial_t r_1 = nabla cdot (D_1 nabla r_1) - r_1*r_2^2 + F *(1 - r_1) quad textbfx in Omega \n partial_t r_2 = nabla cdot (D_2 nabla r_2) + r_1*r_2^2 - r_2*(F + k ) quad textbfx in Omega\n endaligned","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"where r_1 and r_2 are the reaction fields, D_1 and D_2 the diffusion tensors, k is the conversion rate, F is the feed rate and Omega the domain. Depending on the choice of parameters a different pattern can be observed. Please also note that the domain does not have a boundary. The corresponding weak form can be derived as usual.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"For simplicity we will solve the problem with the Lie-Troter-Godunov operator splitting technique with the classical reaction-diffusion split. In this method we split our problem in two problems, i.e. a heat problem and a pointwise reaction problem, and solve them alternatingly to advance in time.","category":"page"},{"location":"tutorials/reactive_surface/#Solver-details","page":"Reactive surface","title":"Solver details","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"The main idea for the Lie-Trotter-Godunov scheme is simple. We can write down the reaction diffusion problem in an abstract way as","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" partial_t mathbfr = mathcalDmathbfr + R(mathbfr) quad textbfx in Omega","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"where mathcalD is the diffusion operator and R is the reaction operator. Notice that the right hand side is just the sum of two operators. Now with our operator splitting scheme we can advance a solution mathbfr(t_1) to mathbfr(t_2) by first solving a heat problem","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" partial_t mathbfr^mathrmmathrmA = mathcalDmathbfr^mathrmA quad textbfx in Omega","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"with mathbfr^mathrmA(t_1) = mathbfr(t_1) on the time interval t_1 to t_2 and use the solution as the initial condition to solve the reaction problem","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" partial_t mathbfr^mathrmB = R(mathbfr^mathrmB) quad textbfx in Omega","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"with mathbfr^mathrmB(t_1) = mathbfr^mathrmA(t_2). This way we obtain a solution approximation mathbfr(t_2) approx mathbfr^mathrmB(t_2).","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"note: Note\nThe operator splitting itself is an approximation, so even if we solve the subproblems analytically we end up with having only a solution approximation. We also do not have a beginner friendly reference for the theory behind operator splitting and can only refer to the original papers for each method.","category":"page"},{"location":"tutorials/reactive_surface/#Commented-Program","page":"Reactive surface","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"First we load Ferrite, and some other packages we need","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"using Ferrite, FerriteGmsh\nusing BlockArrays, SparseArrays, LinearAlgebra, WriteVTK","category":"page"},{"location":"tutorials/reactive_surface/#Assembly-routines","page":"Reactive surface","title":"Assembly routines","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Before we head into the assembly, we define a helper struct to control the dispatches.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"struct GrayScottMaterial{T}\n D₁::T\n D₂::T\n F::T\n k::T\nend;\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"The following assembly routines are written analogue to these found in previous tutorials.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function assemble_element_mass!(Me::Matrix, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n Me₁ = @view Me[r₁range, r₁range]\n Me₂ = @view Me[r₂range, r₂range]\n # Reset to 0\n fill!(Me, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δuᵢ = shape_value(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n δuⱼ = shape_value(cellvalues, q_point, j)\n # Add contribution to Ke\n Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ\n Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n D₁ = material.D₁\n D₂ = material.D₂\n # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n De₁ = @view De[r₁range, r₁range]\n De₂ = @view De[r₂range, r₂range]\n # Reset to 0\n fill!(De, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n ∇δuᵢ = shape_gradient(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇δuⱼ = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n\n # Allocate the element stiffness matrix and element force vector\n Me = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n De = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n\n # Create an assembler\n M_assembler = start_assemble(M)\n D_assembler = start_assemble(D)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element_mass!(Me, cellvalues)\n assemble!(M_assembler, celldofs(cell), Me)\n\n assemble_element_diffusion!(De, cellvalues, material)\n assemble!(D_assembler, celldofs(cell), De)\n end\n return nothing\nend;\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/#Initial-condition-setup","page":"Reactive surface","title":"Initial condition setup","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Time-dependent problems always need an initial condition from which the time evolution starts. In this tutorial we set the concentration of reactant 1 to 1 and the concentration of reactant 2 to 0 for all nodal dof with associated coordinate z leq 09 on the sphere. Since the simulation would be pretty boring with a steady-state initial condition, we introduce some heterogeneity by setting the dofs associated to top part of the sphere (i.e. dofs with z 09 to store the reactant concentrations of 05 and 025 for the reactants 1 and 2 respectively.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler)\n u₀ .= ones(ndofs(dh))\n u₀[2:2:end] .= 0.0\n\n n_basefuncs = getnbasefunctions(cellvalues)\n\n for cell in CellIterator(dh)\n reinit!(cellvalues, cell)\n\n coords = getcoordinates(cell)\n dofs = celldofs(cell)\n uₑ = @view u₀[dofs]\n rv₀ₑ = reshape(uₑ, (2, n_basefuncs))\n\n for i in 1:n_basefuncs\n if coords[i][3] > 0.9\n rv₀ₑ[1, i] = 0.5\n rv₀ₑ[2, i] = 0.25\n end\n end\n end\n\n return u₀ .+= 0.01 * rand(ndofs(dh))\nend;\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/#Mesh-generation","page":"Reactive surface","title":"Mesh generation","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"In this section we define a routine to create a surface mesh with the help of FerriteGmsh.jl.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function create_embedded_sphere(refinements)\n gmsh.initialize()\n\n # Add a unit sphere in 3D space\n gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0)\n gmsh.model.occ.synchronize()\n\n # Generate nodes and surface elements only, hence we need to pass 2 into generate\n gmsh.model.mesh.generate(2)\n\n # To get good solution quality refine the elements several times\n for _ in 1:refinements\n gmsh.model.mesh.refine()\n end\n\n # Now we create a Ferrite grid out of it. Note that we also call toelements\n # with our surface element dimension to obtain these.\n nodes = tonodes()\n elements, _ = toelements(2)\n gmsh.finalize()\n return grid = Grid(elements, nodes)\nend","category":"page"},{"location":"tutorials/reactive_surface/#Simulation-routines","page":"Reactive surface","title":"Simulation routines","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Now we define a function to setup and solve the problem with given feed and conversion rates F and k, as well as the time step length and for how long we want to solve the model.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)\n # We start by setting up grid, dof handler and the matrices for the heat problem.\n grid = create_embedded_sphere(refinements)\n\n # Next we are creating our element assembly helper for surface elements.\n # The only change which we need to introduce here is to pass in a geometrical\n # interpolation with the same dimension as the physical space into which our\n # elements are embedded into, which is in this example 3.\n ip = Lagrange{RefTriangle, 1}()\n qr = QuadratureRule{RefTriangle}(2)\n cellvalues = CellValues(qr, ip, ip^3)\n\n # We have two options to add the reactants to the dof handler, which will give us slightly\n # different resulting dof distributions:\n # A) We can add a scalar-valued interpolation for each reactant.\n # B) We can add one vectorized interpolation whose dimension is the number of reactants\n # number of reactants.\n # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or\n # to be specific for this tutorial, we use an isoparametric concept such that the nodes\n # of our grid and the nodes of our solution approximation coincide. This way a reaction\n # we can create simply reshape the solution vector u to a matrix where the inner index\n # corresponds to the index of the reactant. Note that we will still use the scalar\n # interpolation for the assembly procedure.\n dh = DofHandler(grid)\n add!(dh, :reactants, ip^2)\n close!(dh)\n\n # We can save some memory by telling the sparsity pattern that the matrices are not coupled.\n M = allocate_matrix(dh; coupling = [true false; false true])\n D = allocate_matrix(dh; coupling = [true false; false true])\n\n # Since the heat problem is linear and has no time dependent parameters, we precompute the\n # decomposition of the system matrix to speed up the linear system solver.\n assemble_matrices!(M, D, cellvalues, dh, material)\n A = M + Δt .* D\n cholA = cholesky(A)\n\n # Now we setup buffers for the time dependent solution and fill the initial condition.\n uₜ = zeros(ndofs(dh))\n uₜ₋₁ = ones(ndofs(dh))\n setup_initial_conditions!(uₜ₋₁, cellvalues, dh)\n\n # And prepare output for visualization.\n pvd = paraview_collection(\"reactive-surface\")\n VTKGridFile(\"reactive-surface-0\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[0.0] = vtk\n end\n\n # This is now the main solve loop.\n F = material.F\n k = material.k\n for (iₜ, t) in enumerate(Δt:Δt:T)\n # First we solve the heat problem\n uₜ .= cholA \\ (M * uₜ₋₁)\n\n # Then we solve the point-wise reaction problem with the solution of\n # the heat problem as initial guess. 2 is the number of reactants.\n num_individual_reaction_dofs = ndofs(dh) ÷ 2\n rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs))\n for i in 1:num_individual_reaction_dofs\n r₁ = rvₜ[1, i]\n r₂ = rvₜ[2, i]\n rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁))\n rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k))\n end\n\n # The solution is then stored every 10th step to vtk files for\n # later visualization purposes.\n if (iₜ % 10) == 0\n VTKGridFile(\"reactive-surface-$(iₜ)\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[t] = vtk\n end\n end\n\n # Finally we totate the solution to initialize the next timestep.\n uₜ₋₁ .= uₜ\n end\n\n return vtk_save(pvd)\nend\n\n# This parametrization gives the spot pattern shown in the gif above.\nmaterial = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)\n gray_scott_on_sphere(material, 10.0, 32000.0, 3)","category":"page"},{"location":"tutorials/reactive_surface/#reactive_surface-plain-program","page":"Reactive surface","title":"Plain program","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Here follows a version of the program without any comments. The file is also available here: reactive_surface.jl.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"using Ferrite, FerriteGmsh\nusing BlockArrays, SparseArrays, LinearAlgebra, WriteVTK\n\nstruct GrayScottMaterial{T}\n D₁::T\n D₂::T\n F::T\n k::T\nend;\n\nfunction assemble_element_mass!(Me::Matrix, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n Me₁ = @view Me[r₁range, r₁range]\n Me₂ = @view Me[r₂range, r₂range]\n # Reset to 0\n fill!(Me, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δuᵢ = shape_value(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n δuⱼ = shape_value(cellvalues, q_point, j)\n # Add contribution to Ke\n Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ\n Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n D₁ = material.D₁\n D₂ = material.D₂\n # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n De₁ = @view De[r₁range, r₁range]\n De₂ = @view De[r₂range, r₂range]\n # Reset to 0\n fill!(De, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n ∇δuᵢ = shape_gradient(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇δuⱼ = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n\n # Allocate the element stiffness matrix and element force vector\n Me = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n De = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n\n # Create an assembler\n M_assembler = start_assemble(M)\n D_assembler = start_assemble(D)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element_mass!(Me, cellvalues)\n assemble!(M_assembler, celldofs(cell), Me)\n\n assemble_element_diffusion!(De, cellvalues, material)\n assemble!(D_assembler, celldofs(cell), De)\n end\n return nothing\nend;\n\nfunction setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler)\n u₀ .= ones(ndofs(dh))\n u₀[2:2:end] .= 0.0\n\n n_basefuncs = getnbasefunctions(cellvalues)\n\n for cell in CellIterator(dh)\n reinit!(cellvalues, cell)\n\n coords = getcoordinates(cell)\n dofs = celldofs(cell)\n uₑ = @view u₀[dofs]\n rv₀ₑ = reshape(uₑ, (2, n_basefuncs))\n\n for i in 1:n_basefuncs\n if coords[i][3] > 0.9\n rv₀ₑ[1, i] = 0.5\n rv₀ₑ[2, i] = 0.25\n end\n end\n end\n\n return u₀ .+= 0.01 * rand(ndofs(dh))\nend;\n\nfunction create_embedded_sphere(refinements)\n gmsh.initialize()\n\n # Add a unit sphere in 3D space\n gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0)\n gmsh.model.occ.synchronize()\n\n # Generate nodes and surface elements only, hence we need to pass 2 into generate\n gmsh.model.mesh.generate(2)\n\n # To get good solution quality refine the elements several times\n for _ in 1:refinements\n gmsh.model.mesh.refine()\n end\n\n # Now we create a Ferrite grid out of it. Note that we also call toelements\n # with our surface element dimension to obtain these.\n nodes = tonodes()\n elements, _ = toelements(2)\n gmsh.finalize()\n return grid = Grid(elements, nodes)\nend\n\nfunction gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)\n # We start by setting up grid, dof handler and the matrices for the heat problem.\n grid = create_embedded_sphere(refinements)\n\n # Next we are creating our element assembly helper for surface elements.\n # The only change which we need to introduce here is to pass in a geometrical\n # interpolation with the same dimension as the physical space into which our\n # elements are embedded into, which is in this example 3.\n ip = Lagrange{RefTriangle, 1}()\n qr = QuadratureRule{RefTriangle}(2)\n cellvalues = CellValues(qr, ip, ip^3)\n\n # We have two options to add the reactants to the dof handler, which will give us slightly\n # different resulting dof distributions:\n # A) We can add a scalar-valued interpolation for each reactant.\n # B) We can add one vectorized interpolation whose dimension is the number of reactants\n # number of reactants.\n # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or\n # to be specific for this tutorial, we use an isoparametric concept such that the nodes\n # of our grid and the nodes of our solution approximation coincide. This way a reaction\n # we can create simply reshape the solution vector u to a matrix where the inner index\n # corresponds to the index of the reactant. Note that we will still use the scalar\n # interpolation for the assembly procedure.\n dh = DofHandler(grid)\n add!(dh, :reactants, ip^2)\n close!(dh)\n\n # We can save some memory by telling the sparsity pattern that the matrices are not coupled.\n M = allocate_matrix(dh; coupling = [true false; false true])\n D = allocate_matrix(dh; coupling = [true false; false true])\n\n # Since the heat problem is linear and has no time dependent parameters, we precompute the\n # decomposition of the system matrix to speed up the linear system solver.\n assemble_matrices!(M, D, cellvalues, dh, material)\n A = M + Δt .* D\n cholA = cholesky(A)\n\n # Now we setup buffers for the time dependent solution and fill the initial condition.\n uₜ = zeros(ndofs(dh))\n uₜ₋₁ = ones(ndofs(dh))\n setup_initial_conditions!(uₜ₋₁, cellvalues, dh)\n\n # And prepare output for visualization.\n pvd = paraview_collection(\"reactive-surface\")\n VTKGridFile(\"reactive-surface-0\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[0.0] = vtk\n end\n\n # This is now the main solve loop.\n F = material.F\n k = material.k\n for (iₜ, t) in enumerate(Δt:Δt:T)\n # First we solve the heat problem\n uₜ .= cholA \\ (M * uₜ₋₁)\n\n # Then we solve the point-wise reaction problem with the solution of\n # the heat problem as initial guess. 2 is the number of reactants.\n num_individual_reaction_dofs = ndofs(dh) ÷ 2\n rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs))\n for i in 1:num_individual_reaction_dofs\n r₁ = rvₜ[1, i]\n r₂ = rvₜ[2, i]\n rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁))\n rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k))\n end\n\n # The solution is then stored every 10th step to vtk files for\n # later visualization purposes.\n if (iₜ % 10) == 0\n VTKGridFile(\"reactive-surface-$(iₜ)\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[t] = vtk\n end\n end\n\n # Finally we totate the solution to initialize the next timestep.\n uₜ₋₁ .= uₜ\n end\n\n return vtk_save(pvd)\nend\n\n# This parametrization gives the spot pattern shown in the gif above.\nmaterial = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)\n gray_scott_on_sphere(material, 10.0, 32000.0, 3)","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"This page was generated using Literate.jl.","category":"page"},{"location":"devdocs/special_datastructures/#Special-data-structures","page":"Special data structures","title":"Special data structures","text":"","category":"section"},{"location":"devdocs/special_datastructures/#ArrayOfVectorViews","page":"Special data structures","title":"ArrayOfVectorViews","text":"","category":"section"},{"location":"devdocs/special_datastructures/","page":"Special data structures","title":"Special data structures","text":"ArrayOfVectorViews is a data structure representing an Array of vector views (specifically SubArray{T, 1} where T). By arranging all data (of type T) continuously in memory, this will significantly reduce the garbage collection time compared to using an Array{AbstractVector{T}}. While the data in each view can be mutated, the length of each view is fixed after construction. This data structure provides two features not provided by ArraysOfArrays.jl: Support of matrices and higher order arrays for storing vectors of different dimensions and efficient construction when the number of elements in each view is not known in advance.","category":"page"},{"location":"devdocs/special_datastructures/","page":"Special data structures","title":"Special data structures","text":"Ferrite.ArrayOfVectorViews\nFerrite.ConstructionBuffer\nFerrite.push_at_index!","category":"page"},{"location":"devdocs/special_datastructures/#Ferrite.CollectionsOfViews.ArrayOfVectorViews","page":"Special data structures","title":"Ferrite.CollectionsOfViews.ArrayOfVectorViews","text":"ArrayOfVectorViews(f!::Function, data::Vector{T}, dims::NTuple{N, Int}; sizehint)\n\nCreate an ArrayOfVectorViews to store many vector views of potentially different sizes, emulating an Array{Vector{T}, N} with size dims. However, it avoids allocating each vector individually by storing all data in data, and instead of Vector{T}, the each element is a typeof(view(data, 1:2)).\n\nWhen the length of each vector is unknown, the ArrayOfVectorViews can be created reasonably efficient with the following do-block, which creates an intermediate buffer::ConstructionBuffer supporting the push_at_index! function.\n\nvector_views = ArrayOfVectorViews(data, dims; sizehint) do buffer\n for (ind, val) in some_data\n push_at_index!(buffer, val, ind)\n end\nend\n\nsizehint tells how much space to allocate for the index ind if no val has been added to that index before, or how much more space to allocate in case all previously allocated space for ind has been used up.\n\n\n\n\n\nArrayOfVectorViews(b::CollectionsOfViews.ConstructionBuffer)\n\nCreates the ArrayOfVectorViews directly from the ConstructionBuffer that was manually created and filled.\n\n\n\n\n\nArrayOfVectorViews(indices::Vector{Int}, data::Vector{T}, lin_idx::LinearIndices{N}; checkargs = true)\n\nCreates the ArrayOfVectorViews directly where the user is responsible for having the correct input data. Checking of the argument dimensions can be elided by setting checkargs = false, but incorrect dimensions may lead to illegal out of bounds access later.\n\ndata is indexed by indices[i]:indices[i+1], where i = lin_idx[idx...] and idx... are the user-provided indices to the ArrayOfVectorViews.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/special_datastructures/#Ferrite.CollectionsOfViews.ConstructionBuffer","page":"Special data structures","title":"Ferrite.CollectionsOfViews.ConstructionBuffer","text":"ConstructionBuffer(data::Vector, dims::NTuple{N, Int}, sizehint)\n\nCreate a buffer for creating an ArrayOfVectorViews, representing an array with N axes. sizehint sets the number of elements in data allocated when a new index is added via push_at_index!, or when the current storage for the index is full, how much many additional elements are reserved for that index. Any content in data is overwritten, but performance is improved by pre-allocating it to a reasonable size or by sizehint!ing it.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/special_datastructures/#Ferrite.CollectionsOfViews.push_at_index!","page":"Special data structures","title":"Ferrite.CollectionsOfViews.push_at_index!","text":"push_at_index!(b::ConstructionBuffer, val, indices::Int...)\n\npush! the value val to the Vector view at the index given by indices, typically called inside the ArrayOfVectorViews constructor do-block. But can also be used when manually creating a ConstructionBuffer.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"EditURL = \"../literate-tutorials/ns_vs_diffeq.jl\"","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"if isdefined(Main, :is_ci) #hide\n IS_CI = Main.is_ci #hide\nelse #hide\n IS_CI = false #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#tutorial-ins-ordinarydiffeq","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"(Image: nsdiffeq)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"In this example we focus on a simple but visually appealing problem from fluid dynamics, namely vortex shedding. This problem is also known as von-Karman vortex streets. Within this example, we show how to utilize DifferentialEquations.jl in tandem with Ferrite to solve this space-time problem. To keep things simple we use a naive approach to discretize the system.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Remarks-on-DifferentialEquations.jl","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Remarks on DifferentialEquations.jl","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Required Version\nThis example will only work with OrdinaryDiffEq@v6.80.1. or above","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Many \"time step solvers\" of DifferentialEquations.jl assume that that the problem is provided in mass matrix form. The incompressible Navier-Stokes equations as stated above yield a DAE in this form after applying a spatial discretization technique - in our case FEM. The mass matrix form of ODEs and DAEs is given as:","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" M(t) mathrmd_t u = f(ut)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where M is a possibly time-dependent and not necessarily invertible mass matrix, u the vector of unknowns and f the right-hand-side (RHS). For us f can be interpreted as the spatial discretization of all linear and nonlinear operators depending on u and t, but not on the time derivative of u.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Some-theory-on-the-incompressible-Navier-Stokes-equations","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Some theory on the incompressible Navier-Stokes equations","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/#Problem-description-in-strong-form","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Problem description in strong form","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"The incompressible Navier-Stokes equations can be stated as the system","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" beginaligned\n partial_t v = underbracenu Delta v_textviscosity - underbrace(v cdot nabla) v_textadvection - underbracenabla p_textpressure \n 0 = underbracenabla cdot v_textincompressibility\n endaligned","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where v is the unknown velocity field, p the unknown pressure field, nu the dynamic viscosity and Delta the Laplacian. In the derivation we assumed a constant density of 1 for the fluid and negligible coupling between the velocity components.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Our setup is derived from Turek's DFG benchmark. We model a channel with size 041 times 11 and a hole of radius 005 centered at (02 02). The left side has a parabolic inflow profile, which is ramped up over time, modeled as the time dependent Dirichlet condition","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" v(xyt)\n =\n beginbmatrix\n 4 v_in(t) y (041-y)041^2 \n 0\n endbmatrix","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where v_in(t) = textclamp(t 00 15). With a dynamic viscosity of nu = 0001 this is enough to induce turbulence behind the cylinder which leads to vortex shedding. The top and bottom of our channel have no-slip conditions, i.e. v = 00^textrmT, while the right boundary has the do-nothing boundary condition nu partial_textrmn v - p n = 0 to model outflow. With these boundary conditions we can choose the zero solution as a feasible initial condition.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Derivation-of-Semi-Discrete-Weak-Form","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Derivation of Semi-Discrete Weak Form","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"By multiplying test functions varphi and psi from a suitable test function space on the strong form, followed by integrating over the domain and applying partial integration to the pressure and viscosity terms we can obtain the following weak form","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" beginaligned\n int_Omega partial_t v cdot varphi = - int_Omega nu nabla v nabla varphi - int_Omega (v cdot nabla) v cdot varphi + int_Omega p (nabla cdot varphi) + int_partial Omega_N underbrace(nu partial_n v - p n )_=0 cdot varphi \n 0 = int_Omega (nabla cdot v) psi\n endaligned","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"for all possible test functions from the suitable space.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we can discretize the problem as usual with the finite element method utilizing Taylor-Hood elements (Q2Q1) to yield a stable discretization in mass matrix form:","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" underbracebeginbmatrix\n M_v 0 \n 0 0\n endbmatrix_=M\n beginbmatrix\n mathrmd_thatv \n mathrmd_thatp\n endbmatrix\n =\n underbracebeginbmatrix\n A B^textrmT \n B 0\n endbmatrix_=K\n beginbmatrix\n hatv \n hatp\n endbmatrix\n +\n beginbmatrix\n N(hatv hatv hatvarphi) \n 0\n endbmatrix","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Here M is the singular block mass matrix, K is the discretized Stokes operator and N the nonlinear advection term, which is also called trilinear form. hatv and hatp represent the time-dependent vectors of nodal values of the discretizations of v and p respectively, while hatvarphi is the choice for the test function in the discretization. The hats are dropped in the implementation and only stated for clarity in this section.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Commented-implementation","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Commented implementation","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we solve the problem with Ferrite and DifferentialEquations.jl. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"First we load Ferrite and some other packages we need","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Since we do not need the complete DifferentialEquations suite, we just load the required ODE infrastructure, which can also handle DAEs in mass matrix form.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using OrdinaryDiffEq","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We start off by defining our only material parameter.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"ν = 1.0 / 1000.0; #dynamic viscosity\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Next a rectangular grid with a cylinder in it has to be generated. We use Gmsh.jl for the creation of the mesh and FerriteGmsh.jl to translate it to a Ferrite.Grid. Note that the mesh is pretty fine, leading to a high memory consumption when feeding the equation system to direct solvers.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using FerriteGmsh\nusing FerriteGmsh: Gmsh\nGmsh.initialize()\ngmsh.option.set_number(\"General.Verbosity\", 2)\ndim = 2;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We specify first the rectangle, the cylinder, the surface spanned by the cylinder and the boolean difference of rectangle and cylinder.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"if !IS_CI #hide\n rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41)\n circle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05)\n circle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag])\n circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])\n gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])\nelse #hide\n rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now, the geometrical entities need to be synchronized in order to be available outside of gmsh.model.occ","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"gmsh.model.occ.synchronize()","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"In the next lines, we add the physical groups needed to define boundary conditions.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"if !IS_CI #hide\n bottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, \"bottom\")\n lefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, \"left\")\n righttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, \"right\")\n toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, \"top\")\n holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, \"hole\")\nelse #hide\n gmsh.model.model.add_physical_group(dim - 1, [4], 7, \"left\") #hide\n gmsh.model.model.add_physical_group(dim - 1, [3], 8, \"top\") #hide\n gmsh.model.model.add_physical_group(dim - 1, [2], 9, \"right\") #hide\n gmsh.model.model.add_physical_group(dim - 1, [1], 10, \"bottom\") #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Since we want a quad mesh, we specify the meshing algorithm to the quasi structured quad one. For a complete list, see the Gmsh docs.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"gmsh.option.setNumber(\"Mesh.Algorithm\", 11)\ngmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20)\ngmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.05)\nif IS_CI #hide\n gmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20) #hide\n gmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.15) #hide\nend #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"In the next step, the mesh is generated and finally translated.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"gmsh.model.mesh.generate(dim)\ngrid = togrid()\nGmsh.finalize();\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Function-Space","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Function Space","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"To ensure stability we utilize the Taylor-Hood element pair Q2-Q1. We have to utilize the same quadrature rule for the pressure as for the velocity, because in the weak form the linear pressure term is tested against a quadratic function.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"ip_v = Lagrange{RefQuadrilateral, 2}()^dim\nqr = QuadratureRule{RefQuadrilateral}(4)\ncellvalues_v = CellValues(qr, ip_v);\n\nip_p = Lagrange{RefQuadrilateral, 1}()\ncellvalues_p = CellValues(qr, ip_p);\n\ndh = DofHandler(grid)\nadd!(dh, :v, ip_v)\nadd!(dh, :p, ip_p)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Boundary-conditions","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"As in the DFG benchmark we apply no-slip conditions to the top, bottom and cylinder boundary. The no-slip condition states that the velocity of the fluid on this portion of the boundary is fixed to be zero.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"ch = ConstraintHandler(dh);\n\nnosplip_facet_names = [\"top\", \"bottom\", \"hole\"];\nif IS_CI #hide\n nosplip_facet_names = [\"top\", \"bottom\"] #hide\nend #hide\n∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);\nnoslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])\nadd!(ch, noslip_bc);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"The left boundary has a parabolic inflow with peak velocity of 1.5. This ensures that for the given geometry the Reynolds number is 100, which is already enough to obtain some simple vortex streets. By increasing the velocity further we can obtain stronger vortices - which may need additional refinement of the grid.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"∂Ω_inflow = getfacetset(grid, \"left\");\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Note\nThe kink in the velocity profile will lead to a discontinuity in the pressure at t=1. This needs to be considered in the DiffEq init by providing the keyword argument d_discontinuities=[1.0].","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"vᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity\n\nparabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0))\ninflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2])\nadd!(ch, inflow_bc);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"The outflow boundary condition has been applied on the right side of the cylinder when the weak form has been derived by setting the boundary integral to zero. It is also called the do-nothing condition. Other outflow conditions are also possible.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"∂Ω_free = getfacetset(grid, \"right\");\n\nclose!(ch)\nupdate!(ch, 0.0);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Linear-System-Assembly","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Linear System Assembly","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Next we describe how the block mass matrix and the Stokes matrix are assembled.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"For the block mass matrix M we remember that only the first equation had a time derivative and that the block mass matrix corresponds to the term arising from discretizing the time derivatives. Hence, only the upper left block has non-zero components.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"function assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler)\n # Allocate a buffer for the local matrix and some helpers, together with the assembler.\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # It follows the assembly loop as explained in the basic tutorials.\n mass_assembler = start_assemble(M)\n for cell in CellIterator(dh)\n fill!(Mₑ, 0)\n Ferrite.reinit!(cellvalues_v, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n # Remember that we assemble a vector mass term, hence the dot product.\n # There is only one time derivative on the left hand side, so only one mass block is non-zero.\n for i in 1:n_basefuncs_v\n φᵢ = shape_value(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n φⱼ = shape_value(cellvalues_v, q_point, j)\n Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ\n end\n end\n end\n assemble!(mass_assembler, celldofs(cell), Mₑ)\n end\n\n return M\nend;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Next we discuss the assembly of the Stokes matrix appearing on the right hand side. Remember that we use the same function spaces for trial and test, hence the matrix has the following block form","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" K = beginbmatrix\n A B^textrmT \n B 0\n endbmatrix","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"which is also called saddle point matrix. These problems are known to have a non-trivial kernel, which is a reflection of the strong form as discussed in the theory portion if this example.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"function assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler)\n # Again, some buffers and helpers\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # Assembly loop\n stiffness_assembler = start_assemble(K)\n for cell in CellIterator(dh)\n # Don't forget to initialize everything\n fill!(Kₑ, 0)\n\n Ferrite.reinit!(cellvalues_v, cell)\n Ferrite.reinit!(cellvalues_p, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Assemble local viscosity block of A","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" for i in 1:n_basefuncs_v\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n ∇φⱼ = shape_gradient(cellvalues_v, q_point, j)\n Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ\n end\n end","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Assemble local pressure and incompressibility blocks of B^textrmT and B.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" for j in 1:n_basefuncs_p\n ψ = shape_value(cellvalues_p, q_point, j)\n for i in 1:n_basefuncs_v\n divφ = shape_divergence(cellvalues_v, q_point, i)\n Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ\n Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ\n end\n end\n end\n\n # Assemble `Kₑ` into the Stokes matrix `K`.\n assemble!(stiffness_assembler, celldofs(cell), Kₑ)\n end\n return K\nend;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Solution-of-the-semi-discretized-system-via-DifferentialEquations.jl","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Solution of the semi-discretized system via DifferentialEquations.jl","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"First we assemble the linear portions for efficiency. These matrices are assumed to be constant over time.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Note\nTo obtain the vortex street a small time step is important to resolve the small oscillation forming. The mesh size becomes important to \"only\" resolve the smaller vertices forming, but less important for the initial formation.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"T = 6.0\nΔt₀ = 0.001\nif IS_CI #hide\n Δt₀ = 0.1 #hide\nend #hide\nΔt_save = 0.1\n\nM = allocate_matrix(dh);\nM = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh);\n\nK = allocate_matrix(dh);\nK = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"These are our initial conditions. We start from the zero solution, because it is trivially admissible if the Dirichlet conditions are zero everywhere on the Dirichlet boundary for t=0. Note that the time stepper is also doing fine if the Dirichlet condition is non-zero and not too pathological.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"u₀ = zeros(ndofs(dh))\napply!(u₀, ch);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"DifferentialEquations assumes dense matrices by default, which is not feasible for semi-discretization of finite element models. We communicate that a sparse matrix with specified pattern should be utilized through the jac_prototyp argument. It is simple to see that the Jacobian and the stiffness matrix share the same sparsity pattern, since they share the same relation between trial and test functions.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"jac_sparsity = sparse(K);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"To apply the nonlinear portion of the Navier-Stokes problem we simply hand over the dof handler and cell values to the right-hand-side (RHS) as a parameter. Furthermore the pre-assembled linear part, our Stokes opeartor (which is time independent) is passed to save some additional runtime. To apply the time-dependent Dirichlet BCs, we also need to hand over the constraint handler. The basic idea to apply the Dirichlet BCs consistently is that we copy the current solution u, apply the Dirichlet BCs on the copy, evaluate the discretized RHS of the Navier-Stokes equations with this vector. Furthermore we pass down the Jacobian assembly manually. For the Jacobian we eliminate all rows and columns associated with constrained dofs. Also note that we eliminate the mass matrix beforehand in a similar fashion. This decouples the time evolution of the constrained dofs from the true unknowns. The correct solution is enforced by utilizing step and stage limiters. The correct norms are computed by passing down a custom norm which simply ignores all constrained dofs.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Note\nAn alternative strategy is to hook into the nonlinear and linear solvers and enforce the solution therein. However, this is not possible at the time of writing this tutorial.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"apply!(M, ch)\n\nstruct RHSparams\n K::SparseMatrixCSC\n ch::ConstraintHandler\n dh::DofHandler\n cellvalues_v::CellValues\n u::Vector\nend\np = RHSparams(K, ch, dh, cellvalues_v, copy(u₀))\n\nfunction ferrite_limiter!(u, _, p, t)\n update!(p.ch, t)\n return apply!(u, p.ch)\nend\n\nfunction navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Note that in Tensors.jl the definition textrmgrad v = nabla v holds. With this information it can be quickly shown in index notation that","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"(v cdot nabla) v_textrmi = v_textrmj (partial_textrmj v_textrmi) = v (nabla v)^textrmT_textrmi","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where we should pay attentation to the transpose of the gradient.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ\n end\n end\n return\nend\n\nfunction navierstokes!(du, u_uc, p::RHSparams, t)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Unpack the struct to save some allocations.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" @unpack K, ch, dh, cellvalues_v, u = p","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We start by applying the time-dependent Dirichlet BCs. Note that we are not allowed to mutate u_uc! Furthermore not that we also can not pre- allocate a buffer for this variable variable if we want to use AD to derive the Jacobian matrix, which appears in stiff solvers. Therefore, for efficiency reasons, we simply pass down the jacobian analytically.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" u .= u_uc\n update!(ch, t)\n apply!(u, ch)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we apply the rhs of the Navier-Stokes equations","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" # Linear contribution (Stokes operator)\n mul!(du, K, u) # du .= K * u\n\n # nonlinear contribution\n v_range = dof_range(dh, :v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n vₑ = zeros(n_basefuncs)\n duₑ = zeros(n_basefuncs)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n vₑ .= @views u[v_celldofs]\n fill!(duₑ, 0.0)\n navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v)\n assemble!(du, v_celldofs, duₑ)\n end\n return\nend;\n\nfunction navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Note that in Tensors.jl the definition textrmgrad v = nabla v holds. With this information it can be quickly shown in index notation that","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"(v cdot nabla) v_textrmi = v_textrmj (partial_textrmj v_textrmi) = v (nabla v)^textrmT_textrmi","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where we should pay attentation to the transpose of the gradient.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" for i in 1:n_basefuncs\n φᵢ = shape_value(cellvalues_v, q_point, i)\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ\n end\n end\n end\n return\nend\n\nfunction navierstokes_jac!(J, u_uc, p, t)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Unpack the struct to save some allocations.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" @unpack K, ch, dh, cellvalues_v, u = p","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We start by applying the time-dependent Dirichlet BCs. Note that we are not allowed to mutate u_uc, so we use our buffer again.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" u .= u_uc\n update!(ch, t)\n apply!(u, ch)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we apply the Jacobian of the Navier-Stokes equations.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" # Linear contribution (Stokes operator)\n # Here we assume that J has exactly the same structure as K by construction\n nonzeros(J) .= nonzeros(K)\n\n assembler = start_assemble(J; fillzero = false)\n\n # Assemble variation of the nonlinear term\n n_basefuncs = getnbasefunctions(cellvalues_v)\n Jₑ = zeros(n_basefuncs, n_basefuncs)\n vₑ = zeros(n_basefuncs)\n v_range = dof_range(dh, :v)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n\n vₑ .= @views u[v_celldofs]\n fill!(Jₑ, 0.0)\n navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n assemble!(assembler, v_celldofs, Jₑ)\n end","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Finally we eliminate the constrained dofs from the Jacobian to decouple them in the nonlinear solver from the remaining system.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" return apply!(J, ch)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Finally, together with our pre-assembled mass matrix, we are now able to define our problem in mass matrix form.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"rhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity)\nproblem = ODEProblem(rhs, u₀, (0.0, T), p);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"All norms must not depend on constrained dofs. A problem with the presented implementation is that we are currently unable to strictly enforce constraint everywhere in the internal time integration process of DifferentialEquations.jl, hence the values might differ, resulting in worse error estimates. We try to resolve this issue in the future. Volunteers are also welcome to take a look into this!","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"struct FreeDofErrorNorm\n ch::ConstraintHandler\nend\n(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t)\n(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we can put everything together by specifying how to solve the problem. We want to use an adaptive variant of the implicit Euler method. Further we enable the progress bar with the progress and progress_steps arguments. Finally we have to communicate the time step length and initialization algorithm. Since we start with a valid initial state we do not use one of DifferentialEquations.jl initialization algorithms.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: DAE initialization\nAt the time of writing this no Hessenberg index 2 initialization is implemented.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"To visualize the result we export the grid and our fields to VTK-files, which can be viewed in ParaView by utilizing the corresponding pvd file.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"timestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"info: Debugging convergence issues\nWe can obtain some debug information from OrdinaryDiffEq by wrapping the following section into a debug logger.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"integrator = init(\n problem, timestepper; initializealg = NoInit(), dt = Δt₀,\n adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5,\n progress = true, progress_steps = 1,\n verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0]\n);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Export of solution\nExporting interpolated solutions of problems containing mass matrices is currently broken. Thus, the intervals iterator is used. Note that solve holds all solutions in the memory.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"pvd = paraview_collection(\"vortex-street\")\nfor (step, (u, t)) in enumerate(intervals(integrator))\n VTKGridFile(\"vortex-street-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\nend\nvtk_save(pvd);\n\n\nusing Test #hide\nif IS_CI #hide\n function compute_divergence(dh, u, cellvalues_v) #hide\n divv = 0.0 #hide\n for cell in CellIterator(dh) #hide\n Ferrite.reinit!(cellvalues_v, cell) #hide\n for q_point in 1:getnquadpoints(cellvalues_v) #hide\n dΩ = getdetJdV(cellvalues_v, q_point) #hide\n #hide\n all_celldofs = celldofs(cell) #hide\n v_celldofs = all_celldofs[dof_range(dh, :v)] #hide\n v_cell = u[v_celldofs] #hide\n #hide\n divv += function_divergence(cellvalues_v, q_point, v_cell) * dΩ #hide\n end #hide\n end #hide\n return divv #hide\n end #hide\n let #hide\n u = copy(integrator.u) #hide\n Δdivv = abs(compute_divergence(dh, u, cellvalues_v)) #hide\n @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide\n #hide\n Δv = 0.0 #hide\n for cell in CellIterator(dh) #hide\n Ferrite.reinit!(cellvalues_v, cell) #hide\n all_celldofs = celldofs(cell) #hide\n v_celldofs = all_celldofs[dof_range(dh, :v)] #hide\n v_cell = u[v_celldofs] #hide\n coords = getcoordinates(cell) #hide\n for q_point in 1:getnquadpoints(cellvalues_v) #hide\n dΩ = getdetJdV(cellvalues_v, q_point) #hide\n coords_qp = spatial_coordinate(cellvalues_v, q_point, coords) #hide\n v = function_value(cellvalues_v, q_point, v_cell) #hide\n Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide\n end #hide\n end #hide\n @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide\n end #hide\n nothing #hide\nend #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#ns_vs_diffeq-plain-program","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Plain program","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Here follows a version of the program without any comments. The file is also available here: ns_vs_diffeq.jl.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK\n\nusing OrdinaryDiffEq\n\nν = 1.0 / 1000.0; #dynamic viscosity\n\nusing FerriteGmsh\nusing FerriteGmsh: Gmsh\nGmsh.initialize()\ngmsh.option.set_number(\"General.Verbosity\", 2)\ndim = 2;\n\n rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41)\n circle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05)\n circle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag])\n circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])\n gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])\n\ngmsh.model.occ.synchronize()\n\n bottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, \"bottom\")\n lefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, \"left\")\n righttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, \"right\")\n toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, \"top\")\n holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, \"hole\")\n\ngmsh.option.setNumber(\"Mesh.Algorithm\", 11)\ngmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20)\ngmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.05)\n\ngmsh.model.mesh.generate(dim)\ngrid = togrid()\nGmsh.finalize();\n\nip_v = Lagrange{RefQuadrilateral, 2}()^dim\nqr = QuadratureRule{RefQuadrilateral}(4)\ncellvalues_v = CellValues(qr, ip_v);\n\nip_p = Lagrange{RefQuadrilateral, 1}()\ncellvalues_p = CellValues(qr, ip_p);\n\ndh = DofHandler(grid)\nadd!(dh, :v, ip_v)\nadd!(dh, :p, ip_p)\nclose!(dh);\n\nch = ConstraintHandler(dh);\n\nnosplip_facet_names = [\"top\", \"bottom\", \"hole\"];\n∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);\nnoslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])\nadd!(ch, noslip_bc);\n\n∂Ω_inflow = getfacetset(grid, \"left\");\n\nvᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity\n\nparabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0))\ninflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2])\nadd!(ch, inflow_bc);\n\n∂Ω_free = getfacetset(grid, \"right\");\n\nclose!(ch)\nupdate!(ch, 0.0);\n\nfunction assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler)\n # Allocate a buffer for the local matrix and some helpers, together with the assembler.\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # It follows the assembly loop as explained in the basic tutorials.\n mass_assembler = start_assemble(M)\n for cell in CellIterator(dh)\n fill!(Mₑ, 0)\n Ferrite.reinit!(cellvalues_v, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n # Remember that we assemble a vector mass term, hence the dot product.\n # There is only one time derivative on the left hand side, so only one mass block is non-zero.\n for i in 1:n_basefuncs_v\n φᵢ = shape_value(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n φⱼ = shape_value(cellvalues_v, q_point, j)\n Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ\n end\n end\n end\n assemble!(mass_assembler, celldofs(cell), Mₑ)\n end\n\n return M\nend;\n\nfunction assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler)\n # Again, some buffers and helpers\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # Assembly loop\n stiffness_assembler = start_assemble(K)\n for cell in CellIterator(dh)\n # Don't forget to initialize everything\n fill!(Kₑ, 0)\n\n Ferrite.reinit!(cellvalues_v, cell)\n Ferrite.reinit!(cellvalues_p, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n\n for i in 1:n_basefuncs_v\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n ∇φⱼ = shape_gradient(cellvalues_v, q_point, j)\n Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ\n end\n end\n\n for j in 1:n_basefuncs_p\n ψ = shape_value(cellvalues_p, q_point, j)\n for i in 1:n_basefuncs_v\n divφ = shape_divergence(cellvalues_v, q_point, i)\n Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ\n Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ\n end\n end\n end\n\n # Assemble `Kₑ` into the Stokes matrix `K`.\n assemble!(stiffness_assembler, celldofs(cell), Kₑ)\n end\n return K\nend;\n\nT = 6.0\nΔt₀ = 0.001\nΔt_save = 0.1\n\nM = allocate_matrix(dh);\nM = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh);\n\nK = allocate_matrix(dh);\nK = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);\n\nu₀ = zeros(ndofs(dh))\napply!(u₀, ch);\n\njac_sparsity = sparse(K);\n\napply!(M, ch)\n\nstruct RHSparams\n K::SparseMatrixCSC\n ch::ConstraintHandler\n dh::DofHandler\n cellvalues_v::CellValues\n u::Vector\nend\np = RHSparams(K, ch, dh, cellvalues_v, copy(u₀))\n\nfunction ferrite_limiter!(u, _, p, t)\n update!(p.ch, t)\n return apply!(u, p.ch)\nend\n\nfunction navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)\n\n dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ\n end\n end\n return\nend\n\nfunction navierstokes!(du, u_uc, p::RHSparams, t)\n\n @unpack K, ch, dh, cellvalues_v, u = p\n\n u .= u_uc\n update!(ch, t)\n apply!(u, ch)\n\n # Linear contribution (Stokes operator)\n mul!(du, K, u) # du .= K * u\n\n # nonlinear contribution\n v_range = dof_range(dh, :v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n vₑ = zeros(n_basefuncs)\n duₑ = zeros(n_basefuncs)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n vₑ .= @views u[v_celldofs]\n fill!(duₑ, 0.0)\n navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v)\n assemble!(du, v_celldofs, duₑ)\n end\n return\nend;\n\nfunction navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)\n\n for i in 1:n_basefuncs\n φᵢ = shape_value(cellvalues_v, q_point, i)\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ\n end\n end\n end\n return\nend\n\nfunction navierstokes_jac!(J, u_uc, p, t)\n\n @unpack K, ch, dh, cellvalues_v, u = p\n\n u .= u_uc\n update!(ch, t)\n apply!(u, ch)\n\n # Linear contribution (Stokes operator)\n # Here we assume that J has exactly the same structure as K by construction\n nonzeros(J) .= nonzeros(K)\n\n assembler = start_assemble(J; fillzero = false)\n\n # Assemble variation of the nonlinear term\n n_basefuncs = getnbasefunctions(cellvalues_v)\n Jₑ = zeros(n_basefuncs, n_basefuncs)\n vₑ = zeros(n_basefuncs)\n v_range = dof_range(dh, :v)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n\n vₑ .= @views u[v_celldofs]\n fill!(Jₑ, 0.0)\n navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n assemble!(assembler, v_celldofs, Jₑ)\n end\n\n return apply!(J, ch)\nend;\n\nrhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity)\nproblem = ODEProblem(rhs, u₀, (0.0, T), p);\n\nstruct FreeDofErrorNorm\n ch::ConstraintHandler\nend\n(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t)\n(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t)\n\ntimestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!);\n\nintegrator = init(\n problem, timestepper; initializealg = NoInit(), dt = Δt₀,\n adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5,\n progress = true, progress_steps = 1,\n verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0]\n);\n\npvd = paraview_collection(\"vortex-street\")\nfor (step, (u, t)) in enumerate(intervals(integrator))\n VTKGridFile(\"vortex-street-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\nend\nvtk_save(pvd);","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"EditURL = \"../literate-tutorials/transient_heat_equation.jl\"","category":"page"},{"location":"tutorials/transient_heat_equation/#tutorial-transient-heat-equation","page":"Transient heat equation","title":"Transient heat equation","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"(Image: ) (Image: )","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Figure 1: Visualization of the temperature time evolution on a unit square where the prescribed temperature on the upper and lower parts of the boundary increase with time.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"tip: Tip\nThis example is also available as a Jupyter notebook: transient_heat_equation.ipynb.","category":"page"},{"location":"tutorials/transient_heat_equation/#Introduction","page":"Transient heat equation","title":"Introduction","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In this example we extend the heat equation by a time dependent term, i.e.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":" fracpartial upartial t-nabla cdot (k nabla u) = f quad x in Omega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where u is the unknown temperature field, k the heat conductivity, f the heat source and Omega the domain. For simplicity, we hard code f = 01 and k = 10^-3. We define homogeneous Dirichlet boundary conditions along the left and right edge of the domain.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"u(xt) = 0 quad x in partial Omega_1","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where partial Omega_1 denotes the left and right boundary of Omega.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Further, we define heterogeneous Dirichlet boundary conditions at the top and bottom edge partial Omega_2. We choose a linearly increasing function a(t) that describes the temperature at this boundary","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"u(xt) = a(t) quad x in partial Omega_2","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"The semidiscrete weak form is given by","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"int_Omegav fracpartial upartial t mathrmdOmega + int_Omega k nabla v cdot nabla u mathrmdOmega = int_Omega f v mathrmdOmega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where v is a suitable test function. Now, we still need to discretize the time derivative. An implicit Euler scheme is applied, which yields:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"int_Omega v u_n+1 mathrmdOmega + Delta tint_Omega k nabla v cdot nabla u_n+1 mathrmdOmega = Delta tint_Omega f v mathrmdOmega + int_Omega v u_n mathrmdOmega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"If we assemble the discrete operators, we get the following algebraic system:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"mathbfM mathbfu_n+1 + Δt mathbfK mathbfu_n+1 = Δt mathbff + mathbfM mathbfu_n","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In this example we apply the boundary conditions to the assembled discrete operators (mass matrix mathbfM and stiffnes matrix mathbfK) only once. We utilize the fact that in finite element computations Dirichlet conditions can be applied by zero out rows and columns that correspond to a prescribed dof in the system matrix (mathbfA = Δt mathbfK + mathbfM) and setting the value of the right-hand side vector to the value of the Dirichlet condition. Thus, we only need to apply in every time step the Dirichlet condition to the right-hand side of the problem.","category":"page"},{"location":"tutorials/transient_heat_equation/#Commented-Program","page":"Transient heat equation","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"First we load Ferrite, and some other packages we need.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"using Ferrite, SparseArrays, WriteVTK","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We create the same grid as in the heat equation example.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"grid = generate_grid(Quadrilateral, (100, 100));\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Trial-and-test-functions","page":"Transient heat equation","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Again, we define the structs that are responsible for the shape_value and shape_gradient evaluation.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"ip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Degrees-of-freedom","page":"Transient heat equation","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"After this, we can define the DofHandler and distribute the DOFs of the problem.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"By means of the DofHandler we can allocate the needed SparseMatrixCSC. M refers here to the so called mass matrix, which always occurs in time related terms, i.e.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"M_ij = int_Omega v_i u_j mathrmdOmega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where u_i and v_j are trial and test functions, respectively.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"K = allocate_matrix(dh);\nM = allocate_matrix(dh);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We also preallocate the right hand side","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"f = zeros(ndofs(dh));\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Boundary-conditions","page":"Transient heat equation","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In order to define the time dependent problem, we need some end time T and something that describes the linearly increasing Dirichlet boundary condition on partial Omega_2.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"max_temp = 100\nΔt = 1\nT = 200\nt_rise = 100\nch = ConstraintHandler(dh);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Here, we define the boundary condition related to partial Omega_1.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"∂Ω₁ = union(getfacetset.((grid,), [\"left\", \"right\"])...)\ndbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0)\nadd!(ch, dbc);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"While the next code block corresponds to the linearly increasing temperature description on partial Omega_2 until t=t_rise, and then keep constant","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"∂Ω₂ = union(getfacetset.((grid,), [\"top\", \"bottom\"])...)\ndbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1))\nadd!(ch, dbc)\nclose!(ch)\nupdate!(ch, 0.0);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Assembling-the-linear-system","page":"Transient heat equation","title":"Assembling the linear system","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"As in the heat equation example we define a doassemble! function that assembles the diffusion parts of the equation:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"function doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n\n assembler = start_assemble(K, f)\n\n for cell in CellIterator(dh)\n\n fill!(Ke, 0)\n fill!(fe, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n ∇v = shape_gradient(cellvalues, q_point, i)\n fe[i] += 0.1 * v * dΩ\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In addition to the diffusive part, we also need a function that assembles the mass matrix M.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"function doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Me = zeros(n_basefuncs, n_basefuncs)\n\n assembler = start_assemble(M)\n\n for cell in CellIterator(dh)\n\n fill!(Me, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n u = shape_value(cellvalues, q_point, j)\n Me[i, j] += (v * u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Me)\n end\n return M\nend\nnothing # hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Solution-of-the-system","page":"Transient heat equation","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We first assemble all parts in the prior allocated SparseMatrixCSC.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"K, f = doassemble_K!(K, f, cellvalues, dh)\nM = doassemble_M!(M, cellvalues, dh)\nA = (Δt .* K) + M;\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Now, we need to save all boundary condition related values of the unaltered system matrix A, which is done by get_rhs_data. The function returns a RHSData struct, which contains all needed information to apply the boundary conditions solely on the right-hand-side vector of the problem.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"rhsdata = get_rhs_data(ch, A);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We set the values at initial time step, denoted by uₙ, to a bubble-shape described by (x_1^2-1)(x_2^2-1), such that it is zero at the boundaries and the maximum temperature in the center.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"uₙ = zeros(length(f));\napply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Here, we apply once the boundary conditions to the system matrix A.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"apply!(A, ch);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"To store the solution, we initialize a paraview collection (.pvd) file,","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"pvd = paraview_collection(\"transient-heat\")\nVTKGridFile(\"transient-heat-0\", dh) do vtk\n write_solution(vtk, dh, uₙ)\n pvd[0.0] = vtk\nend","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"At this point everything is set up and we can finally approach the time loop.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"for (step, t) in enumerate(Δt:Δt:T)\n #First of all, we need to update the Dirichlet boundary condition values.\n update!(ch, t)\n\n #Secondly, we compute the right-hand-side of the problem.\n b = Δt .* f .+ M * uₙ\n #Then, we can apply the boundary conditions of the current time step.\n apply_rhs!(rhsdata, b, ch)\n\n #Finally, we can solve the time step and save the solution afterwards.\n u = A \\ b\n\n VTKGridFile(\"transient-heat-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\n #At the end of the time loop, we set the previous solution to the current one and go to the next time step.\n uₙ .= u\nend","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In order to use the .pvd file we need to store it to the disk, which is done by:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"vtk_save(pvd);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#transient_heat_equation-plain-program","page":"Transient heat equation","title":"Plain program","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Here follows a version of the program without any comments. The file is also available here: transient_heat_equation.jl.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"using Ferrite, SparseArrays, WriteVTK\n\ngrid = generate_grid(Quadrilateral, (100, 100));\n\nip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nK = allocate_matrix(dh);\nM = allocate_matrix(dh);\n\nf = zeros(ndofs(dh));\n\nmax_temp = 100\nΔt = 1\nT = 200\nt_rise = 100\nch = ConstraintHandler(dh);\n\n∂Ω₁ = union(getfacetset.((grid,), [\"left\", \"right\"])...)\ndbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0)\nadd!(ch, dbc);\n\n∂Ω₂ = union(getfacetset.((grid,), [\"top\", \"bottom\"])...)\ndbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1))\nadd!(ch, dbc)\nclose!(ch)\nupdate!(ch, 0.0);\n\nfunction doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n\n assembler = start_assemble(K, f)\n\n for cell in CellIterator(dh)\n\n fill!(Ke, 0)\n fill!(fe, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n ∇v = shape_gradient(cellvalues, q_point, i)\n fe[i] += 0.1 * v * dΩ\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\n\nfunction doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Me = zeros(n_basefuncs, n_basefuncs)\n\n assembler = start_assemble(M)\n\n for cell in CellIterator(dh)\n\n fill!(Me, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n u = shape_value(cellvalues, q_point, j)\n Me[i, j] += (v * u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Me)\n end\n return M\nend\n\nK, f = doassemble_K!(K, f, cellvalues, dh)\nM = doassemble_M!(M, cellvalues, dh)\nA = (Δt .* K) + M;\n\nrhsdata = get_rhs_data(ch, A);\n\nuₙ = zeros(length(f));\napply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp);\n\napply!(A, ch);\n\npvd = paraview_collection(\"transient-heat\")\nVTKGridFile(\"transient-heat-0\", dh) do vtk\n write_solution(vtk, dh, uₙ)\n pvd[0.0] = vtk\nend\n\nfor (step, t) in enumerate(Δt:Δt:T)\n #First of all, we need to update the Dirichlet boundary condition values.\n update!(ch, t)\n\n #Secondly, we compute the right-hand-side of the problem.\n b = Δt .* f .+ M * uₙ\n #Then, we can apply the boundary conditions of the current time step.\n apply_rhs!(rhsdata, b, ch)\n\n #Finally, we can solve the time step and save the solution afterwards.\n u = A \\ b\n\n VTKGridFile(\"transient-heat-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\n #At the end of the time loop, we set the previous solution to the current one and go to the next time step.\n uₙ .= u\nend\n\nvtk_save(pvd);","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"gallery/#Code-gallery","page":"Code gallery","title":"Code gallery","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"This page gives an overview of the code gallery. Compared to the tutorials, these programs do not focus on teaching Ferrite, but rather focus on showing how Ferrite can be used \"in the wild\".","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"note: Contribute to the gallery!\nMost of the gallery is user contributed. If you use Ferrite, and have something you want to share, please contribute to the gallery! This could, for example, be your research code for a published paper, some interesting application, or just some nice trick.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Helmholtz-equation](helmholtz.md)","page":"Code gallery","title":"Helmholtz equation","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Solves the Helmholtz equation on the unit square using a combination of Dirichlet and Neumann boundary conditions and the method of manufactured solutions.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Kristoffer Carlsson (@KristofferC).","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Nearly-incompressible-hyperelasticity](quasi_incompressible_hyperelasticity.md)","page":"Code gallery","title":"Nearly incompressible hyperelasticity","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"This program combines the ideas from Tutorial 3: Incompressible elasticity and Tutorial 4: Hyperelasticity to construct a mixed element solving three-dimensional displacement-pressure equations.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Bhavesh Shrimali (@bhaveshshrimali).","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Ginzburg-Landau-model-energy-minimization](landau.md)","page":"Code gallery","title":"Ginzburg-Landau model energy minimization","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"A basic Ginzburg-Landau model is solved. ForwardDiff.jl is used to compute the gradient and hessian of the energy function. Multi-threading is used to parallelize the assembly procedure.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Louis Ponet (@louisponet).","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Topology-optimization](topology_optimization.md)","page":"Code gallery","title":"Topology optimization","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Topology optimization is shown for the bending problem by using a SIMP material model. To avoid numerical instabilities, a regularization scheme requiring the calculation of the Laplacian is imposed, which is done by using the grid topology functionalities.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Mischa Blaszczyk (@blaszm).","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"EditURL = \"../literate-gallery/quasi_incompressible_hyperelasticity.jl\"","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#tutorial-nearly-incompressible-hyperelasticity","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"(Image: )","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: quasi_incompressible_hyperelasticity.ipynb","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#Introduction","page":"Nearly Incompressible Hyperelasticity","title":"Introduction","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"In this example we study quasi- or nearly-incompressible hyperelasticity using the stable Taylor-Hood approximation. In spirit, this example is the nonlinear analogue of incompressible_elasticity and the incompressible analogue of hyperelasticity. Much of the code therefore follows from the above two examples. The problem is formulated in the undeformed or reference configuration with the displacement mathbfu and pressure p being the unknown fields. We now briefly outline the formulation. Consider the standard hyperelasticity problem","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" mathbfu = argmin_mathbfvinmathcalK(Omega)Pi(mathbfv)quad textwherequad Pi(mathbfv) = int_Omega Psi(mathbfv) mathrmdOmega ","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"where mathcalK(Omega) is a suitable function space.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"For clarity of presentation we ignore any non-zero surface tractions and body forces and instead consider only applied displacements (i.e. non-homogeneous dirichlet boundary conditions). Moreover we stick our attention to the standard Neo-Hookean stored energy density","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" Psi(mathbfu) = fracmu2left(I_1 - 3 right) - mu log(J) + fraclambda2left( J - 1right)^2","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"where I_1 = mathrmtr(mathbfC) = mathrmtr(mathbfF^mathrmT mathbfF) = F_ijF_ij and J = det(mathbfF) denote the standard invariants of the deformation gradient tensor mathbfF = mathbfI+nabla_mathbfX mathbfu. The above problem is ill-posed in the limit of incompressibility (or near-incompressibility), namely when","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" lambdamu rightarrow +infty","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"In order to alleviate the problem, we consider the partial legendre transform of the strain energy density Psi with respect to J = det(mathbfF), namely","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" widehatPsi(mathbfu p) = sup_J left p(J - 1) - fracmu2left(I_1 - 3 right) + mu log(J) - fraclambda2left( J - 1right)^2 right","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The supremum, say J^star, can be calculated in closed form by the first order optimailty condition partialwidehatPsipartial J = 0. This gives","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" J^star(p) = fraclambda + p + sqrt(lambda + p)^2 + 4 lambda mu (2 lambda)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Furthermore, taking the partial legendre transform of widehatPsi once again, gives us back the original problem, i.e.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" Psi(mathbfu) = Psi^star(mathbfu p) = sup_p left p(J - 1) - p(J^star - 1) + fracmu2left(I_1 - 3 right) - mu log(J^star) + fraclambda2left( J^star - 1right)^2 right","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Therefore our original hyperelasticity problem can now be reformulated as","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" inf_mathbfuinmathcalK(Omega)sup_p int_OmegaPsi^star (mathbfu p) mathrmdOmega","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The total (modified) energy Pi^star can then be written as","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" Pi^star(mathbfu p) = int_Omega p (J - J^star) mathrmdOmega + int_Omega fracmu2 left( I_1 - 3right) mathrmdOmega - int_Omega mulog(J^star) mathrmdOmega + int_Omega fraclambda2left( J^star - 1 right)^2 mathrmdOmega","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The Euler-Lagrange equations corresponding to the above energy give us our governing PDEs in the weak form, namely","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" int_Omega fracpartialPsi^starpartial mathbfFdelta mathbfF mathrmdOmega = 0","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" int_Omega fracpartial Psi^starpartial pdelta p mathrmdOmega = 0","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"where delta mathrmF = delta mathrmgrad_0(mathbfu) = mathrmgrad_0(delta mathbfu) and delta mathbfu and delta p denote arbitrary variations with respect to displacement and pressure (or the test functions). See the references below for a more detailed explanation of the above mathematical trick. Now, in order to apply Newton's method to the above problem, we further need to linearize the above equations and calculate the respective hessians (or tangents), namely, partial^2Psi^starpartial mathbfF^2, partial^2Psi^starpartial p^2 and partial^2Psi^starpartial mathbfFpartial p which, using Tensors.jl, can be determined conveniently using automatic differentiation (see the code below). Hence we only need to define the above potential. The remaineder of the example follows similarly.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#References","page":"Nearly Incompressible Hyperelasticity","title":"References","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"A paradigm for higher-order polygonal elements in finite elasticity using a gradient correction scheme, CMAME 2016, 306, 216–251\nApproximation of incompressible large deformation elastic problems: some unresolved issues, Computational Mechanics, 2013","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#Implementation","page":"Nearly Incompressible Hyperelasticity","title":"Implementation","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We now get to the actual code. First, we import the respective packages","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"using Ferrite, Tensors, ProgressMeter, WriteVTK\nusing BlockArrays, SparseArrays, LinearAlgebra","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and the corresponding struct to store our material properties.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"struct NeoHooke\n μ::Float64\n λ::Float64\nend","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We then create a function to generate a simple test mesh on which to compute FE solution. We also mark the boundaries to later assign Dirichlet boundary conditions","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function importTestGrid()\n grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3}))\n addfacetset!(grid, \"myBottom\", x -> norm(x[2]) ≈ 0.0)\n addfacetset!(grid, \"myBack\", x -> norm(x[3]) ≈ 0.0)\n addfacetset!(grid, \"myRight\", x -> norm(x[1]) ≈ 1.0)\n addfacetset!(grid, \"myLeft\", x -> norm(x[1]) ≈ 0.0)\n return grid\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The function to create corresponding cellvalues for the displacement field u and pressure p follows in a similar fashion from the incompressible_elasticity example","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTetrahedron}(4)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(4)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We now create the function for Ψ*","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function Ψ(F, p, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(tdot(F))\n J = det(F)\n Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ)\n return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and it's derivatives (required in computing the jacobian and hessian respectively)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function constitutive_driver(F, p, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all)\n ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all)\n ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p)\n return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The functions to create the DofHandler and ConstraintHandler (to assign corresponding boundary conditions) follow likewise from the incompressible elasticity example, namely","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement dim = 3\n add!(dh, :p, ipp) # pressure dim = 1\n close!(dh)\n return dh\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We are simulating a uniaxial tensile loading of a unit cube. Hence we apply a displacement field (:u) in x direction on the right face. The left, bottom and back facets are fixed in the x, y and z components of the displacement so as to emulate the uniaxial nature of the loading.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myLeft\"), (x, t) -> zero(Vec{1}), [1]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBack\"), (x, t) -> zero(Vec{1}), [3]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myRight\"), (x, t) -> t * ones(Vec{1}), [1]))\n close!(dbc)\n Ferrite.update!(dbc, 0.0)\n return dbc\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Also, since we are considering incompressible hyperelasticity, an interesting quantity that we can compute is the deformed volume of the solid. It is easy to show that this is equal to ∫J*dΩ where J=det(F). This can be done at the level of each element (cell)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function calculate_element_volume(cell, cellvalues_u, ue)\n reinit!(cellvalues_u, cell)\n evol::Float64 = 0.0\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n ∇u = function_gradient(cellvalues_u, qp, ue)\n F = one(∇u) + ∇u\n J = det(F)\n evol += J * dΩ\n end\n return evol\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and then assembled over all the cells (elements)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u)\n evol::Float64 = 0.0\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n nu = getnbasefunctions(cellvalues_u)\n global_dofs_u = global_dofs[1:nu]\n ue = w[global_dofs_u]\n δevol = calculate_element_volume(cell, cellvalues_u, ue)\n evol += δevol\n end\n return evol\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The function to assemble the element stiffness matrix for each element in the mesh now has a block structure like in incompressible_elasticity.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n # Reinitialize cell values, and reset output arrays\n ublock, pblock = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n fill!(Ke, 0.0)\n fill!(fe, 0.0)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Compute deformation gradient F\n ∇u = function_gradient(cellvalues_u, qp, ue)\n p = function_value(cellvalues_p, qp, pe)\n F = one(∇u) + ∇u\n\n # Compute first Piola-Kirchhoff stress and tangent modulus\n ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp)\n\n # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks\n for i in 1:n_basefuncs_u\n # gradient of the test function\n ∇δui = shape_gradient(cellvalues_u, qp, i)\n # Add contribution to the residual from this test function\n fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ\n\n ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F²\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ\n end\n # Loop over the `p`-test functions\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ\n end\n end\n # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, i)\n fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ\n\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ\n end\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The only thing that changes in the assembly of the global stiffness matrix is slicing the corresponding element dofs for the displacement (see global_dofsu) and pressure (global_dofsp).","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function assemble_global!(\n K::SparseMatrixCSC, f, cellvalues_u::CellValues,\n cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w\n )\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n # start_assemble resets K and f\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n assembler = start_assemble(K, f)\n # Loop over all cells in the grid\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n global_dofsu = global_dofs[1:nu] # first nu dofs are displacement\n global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure\n @assert size(global_dofs, 1) == nu + np # sanity check\n ue = w[global_dofsu] # displacement dofs for the current cell\n pe = w[global_dofsp] # pressure dofs for the current cell\n assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n assemble!(assembler, global_dofs, ke, fe)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We now define a main function solve. For nonlinear quasistatic problems we often like to parameterize the solution in terms of a pseudo time like parameter, which in this case is used to gradually apply the boundary displacement on the right face. Also for definitenessm we consider λ/μ = 10⁴","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function solve(interpolation_u, interpolation_p)\n\n # import the mesh\n grid = importTestGrid()\n\n # Material parameters\n μ = 1.0\n λ = 1.0e4 * μ\n mp = NeoHooke(μ, λ)\n\n # Create the DofHandler and CellValues\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Create the DirichletBCs\n dbc = create_bc(dh)\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n w = zeros(_ndofs)\n ΔΔw = zeros(_ndofs)\n apply!(w, dbc)\n\n # Create the sparse matrix and residual vector\n K = allocate_matrix(dh)\n f = zeros(_ndofs)\n\n # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value\n # of this parameter, and Δt denotes its increment in each step\n Tf = 2.0\n Δt = 0.1\n NEWTON_TOL = 1.0e-8\n\n pvd = paraview_collection(\"hyperelasticity_incomp_mixed\")\n for (step, t) in enumerate(0.0:Δt:Tf)\n # Perform Newton iterations\n Ferrite.update!(dbc, t)\n apply!(w, dbc)\n newton_itr = -1\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving @ time $t of $Tf;\")\n fill!(ΔΔw, 0.0)\n while true\n newton_itr += 1\n assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w)\n norm_res = norm(f[Ferrite.free_dofs(dbc)])\n apply_zero!(K, f, dbc)\n # Only display output at specific load steps\n if t % (5 * Δt) == 0\n ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)])\n end\n if norm_res < NEWTON_TOL\n break\n elseif newton_itr > 30\n error(\"Reached maximum Newton iterations, aborting\")\n end\n # Compute the incremental `dof`-vector (both displacement and pressure)\n ΔΔw .= K \\ f\n\n apply_zero!(ΔΔw, dbc)\n w .-= ΔΔw\n end\n\n # Save the solution fields\n VTKGridFile(\"hyperelasticity_incomp_mixed_$step\", grid) do vtk\n write_solution(vtk, dh, w)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u)\n print(\"Deformed volume is $vol_def\")\n return vol_def\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We can now test the solution using the Taylor-Hood approximation","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"quadratic_u = Lagrange{RefTetrahedron, 2}()^3\nlinear_p = Lagrange{RefTetrahedron, 1}()\nvol_def = solve(quadratic_u, linear_p)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The deformed volume is indeed close to 1 (as should be for a nearly incompressible material).","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#Plain-program","page":"Nearly Incompressible Hyperelasticity","title":"Plain program","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Here follows a version of the program without any comments. The file is also available here: quasi_incompressible_hyperelasticity.jl.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"using Ferrite, Tensors, ProgressMeter, WriteVTK\nusing BlockArrays, SparseArrays, LinearAlgebra\n\nstruct NeoHooke\n μ::Float64\n λ::Float64\nend\n\nfunction importTestGrid()\n grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3}))\n addfacetset!(grid, \"myBottom\", x -> norm(x[2]) ≈ 0.0)\n addfacetset!(grid, \"myBack\", x -> norm(x[3]) ≈ 0.0)\n addfacetset!(grid, \"myRight\", x -> norm(x[1]) ≈ 1.0)\n addfacetset!(grid, \"myLeft\", x -> norm(x[1]) ≈ 0.0)\n return grid\nend;\n\nfunction create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTetrahedron}(4)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(4)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\n\nfunction Ψ(F, p, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(tdot(F))\n J = det(F)\n Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ)\n return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2\nend;\n\nfunction constitutive_driver(F, p, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all)\n ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all)\n ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p)\n return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p\nend;\n\nfunction create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement dim = 3\n add!(dh, :p, ipp) # pressure dim = 1\n close!(dh)\n return dh\nend;\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myLeft\"), (x, t) -> zero(Vec{1}), [1]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBack\"), (x, t) -> zero(Vec{1}), [3]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myRight\"), (x, t) -> t * ones(Vec{1}), [1]))\n close!(dbc)\n Ferrite.update!(dbc, 0.0)\n return dbc\nend;\n\nfunction calculate_element_volume(cell, cellvalues_u, ue)\n reinit!(cellvalues_u, cell)\n evol::Float64 = 0.0\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n ∇u = function_gradient(cellvalues_u, qp, ue)\n F = one(∇u) + ∇u\n J = det(F)\n evol += J * dΩ\n end\n return evol\nend;\n\nfunction calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u)\n evol::Float64 = 0.0\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n nu = getnbasefunctions(cellvalues_u)\n global_dofs_u = global_dofs[1:nu]\n ue = w[global_dofs_u]\n δevol = calculate_element_volume(cell, cellvalues_u, ue)\n evol += δevol\n end\n return evol\nend;\n\nfunction assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n # Reinitialize cell values, and reset output arrays\n ublock, pblock = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n fill!(Ke, 0.0)\n fill!(fe, 0.0)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Compute deformation gradient F\n ∇u = function_gradient(cellvalues_u, qp, ue)\n p = function_value(cellvalues_p, qp, pe)\n F = one(∇u) + ∇u\n\n # Compute first Piola-Kirchhoff stress and tangent modulus\n ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp)\n\n # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks\n for i in 1:n_basefuncs_u\n # gradient of the test function\n ∇δui = shape_gradient(cellvalues_u, qp, i)\n # Add contribution to the residual from this test function\n fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ\n\n ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F²\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ\n end\n # Loop over the `p`-test functions\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ\n end\n end\n # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, i)\n fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ\n\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ\n end\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ\n end\n end\n end\n return\nend;\n\nfunction assemble_global!(\n K::SparseMatrixCSC, f, cellvalues_u::CellValues,\n cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w\n )\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n # start_assemble resets K and f\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n assembler = start_assemble(K, f)\n # Loop over all cells in the grid\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n global_dofsu = global_dofs[1:nu] # first nu dofs are displacement\n global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure\n @assert size(global_dofs, 1) == nu + np # sanity check\n ue = w[global_dofsu] # displacement dofs for the current cell\n pe = w[global_dofsp] # pressure dofs for the current cell\n assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n assemble!(assembler, global_dofs, ke, fe)\n end\n return\nend;\n\nfunction solve(interpolation_u, interpolation_p)\n\n # import the mesh\n grid = importTestGrid()\n\n # Material parameters\n μ = 1.0\n λ = 1.0e4 * μ\n mp = NeoHooke(μ, λ)\n\n # Create the DofHandler and CellValues\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Create the DirichletBCs\n dbc = create_bc(dh)\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n w = zeros(_ndofs)\n ΔΔw = zeros(_ndofs)\n apply!(w, dbc)\n\n # Create the sparse matrix and residual vector\n K = allocate_matrix(dh)\n f = zeros(_ndofs)\n\n # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value\n # of this parameter, and Δt denotes its increment in each step\n Tf = 2.0\n Δt = 0.1\n NEWTON_TOL = 1.0e-8\n\n pvd = paraview_collection(\"hyperelasticity_incomp_mixed\")\n for (step, t) in enumerate(0.0:Δt:Tf)\n # Perform Newton iterations\n Ferrite.update!(dbc, t)\n apply!(w, dbc)\n newton_itr = -1\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving @ time $t of $Tf;\")\n fill!(ΔΔw, 0.0)\n while true\n newton_itr += 1\n assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w)\n norm_res = norm(f[Ferrite.free_dofs(dbc)])\n apply_zero!(K, f, dbc)\n # Only display output at specific load steps\n if t % (5 * Δt) == 0\n ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)])\n end\n if norm_res < NEWTON_TOL\n break\n elseif newton_itr > 30\n error(\"Reached maximum Newton iterations, aborting\")\n end\n # Compute the incremental `dof`-vector (both displacement and pressure)\n ΔΔw .= K \\ f\n\n apply_zero!(ΔΔw, dbc)\n w .-= ΔΔw\n end\n\n # Save the solution fields\n VTKGridFile(\"hyperelasticity_incomp_mixed_$step\", grid) do vtk\n write_solution(vtk, dh, w)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u)\n print(\"Deformed volume is $vol_def\")\n return vol_def\nend;\n\nquadratic_u = Lagrange{RefTetrahedron, 2}()^3\nlinear_p = Lagrange{RefTetrahedron, 1}()\nvol_def = solve(quadratic_u, linear_p)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/utils/","page":"Development utility functions","title":"Development utility functions","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/utils/#Development-utility-functions","page":"Development utility functions","title":"Development utility functions","text":"","category":"section"},{"location":"reference/utils/","page":"Development utility functions","title":"Development utility functions","text":"Ferrite.debug_mode","category":"page"},{"location":"reference/utils/#Ferrite.debug_mode","page":"Development utility functions","title":"Ferrite.debug_mode","text":"Ferrite.debug_mode(; enable=true)\n\nHelper to turn on (enable=true) or off (enable=false) debug expressions in Ferrite.\n\nDebug mode influences Ferrite.@debug expr: when debug mode is enabled, expr is evaluated, and when debug mode is disabled expr is ignored.\n\n\n\n\n\n","category":"function"}] +[{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"EditURL = \"../literate-tutorials/linear_shell.jl\"","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"runic: off","category":"page"},{"location":"tutorials/linear_shell/#tutorial-linear-shell","page":"Linear shell","title":"Linear shell","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"(Image: )","category":"page"},{"location":"tutorials/linear_shell/#Introduction","page":"Linear shell","title":"Introduction","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"In this example we show how shell elements can be analyzed with Ferrite. The shell implemented here comes from the book \"The finite element method - Linear static and dynamic finite element analysis\" by Hughes (1987), and a brief description of it is given at the end of this tutorial. The first part of the tutorial explains how to set up the problem.","category":"page"},{"location":"tutorials/linear_shell/#Setting-up-the-problem","page":"Linear shell","title":"Setting up the problem","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"using Ferrite\nusing ForwardDiff\n\nfunction main() #wrap everything in a function...","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"First we generate a flat rectangular mesh. There is currently no built-in function for generating shell meshes in Ferrite, so we have to create our own simple mesh generator (see the function generate_shell_grid further down in this file).","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"nels = (10,10)\nsize = (10.0, 10.0)\ngrid = generate_shell_grid(nels, size)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Here we define the bi-linear interpolation used for the geometrical description of the shell. We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use under integration for the inplane integration, to avoid shear locking.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"ip = Lagrange{RefQuadrilateral,1}()\nqr_inplane = QuadratureRule{RefQuadrilateral}(1)\nqr_ooplane = QuadratureRule{RefLine}(2)\ncv = CellValues(qr_inplane, ip, ip^3)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Next we distribute displacement dofs,:u = (x,y,z) and rotational dofs, :θ = (θ₁, θ₂).","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip^3)\nadd!(dh, :θ, ip^2)\nclose!(dh)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This is done with addfacetset! and addvertexset!","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"addfacetset!(grid, \"left\", (x) -> x[1] ≈ 0.0)\naddfacetset!(grid, \"right\", (x) -> x[1] ≈ size[1])\naddvertexset!(grid, \"corner\", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"ch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1,3]) )\nadd!(ch, Dirichlet(:θ, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1,2]) )","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"add!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> (0.0, 0.0), [1,3]) )\nadd!(ch, Dirichlet(:θ, getfacetset(grid, \"right\"), (x, t) -> (0.0, pi/10), [1,2]) )","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"In order to not get rigid body motion, we lock the y-displacement in one of the corners.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"add!(ch, Dirichlet(:θ, getvertexset(grid, \"corner\"), (x, t) -> (0.0), [2]) )\n\nclose!(ch)\nupdate!(ch, 0.0)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material. In this linear shell, plane stress is assumed, ie sigma_zz = 0. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6).","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"κ = 5/6 # Shear correction factor\nE = 210.0\nν = 0.3\na = (1-ν)/2\nC = E/(1-ν^2) * [1 ν 0 0 0;\n ν 1 0 0 0;\n 0 0 a*κ 0 0;\n 0 0 0 a*κ 0;\n 0 0 0 0 a*κ]\n\n\ndata = (thickness = 1.0, C = C); #Named tuple\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"We now assemble the problem in standard finite element fashion","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"nnodes = getnbasefunctions(ip)\nndofs_shell = ndofs_per_cell(dh)\n\nK = allocate_matrix(dh)\nf = zeros(Float64, ndofs(dh))\n\nke = zeros(ndofs_shell, ndofs_shell)\nfe = zeros(ndofs_shell)\n\ncelldofs = zeros(Int, ndofs_shell)\ncellcoords = zeros(Vec{3,Float64}, nnodes)\n\nassembler = start_assemble(K, f)\nfor cell in CellIterator(grid)\n fill!(ke, 0.0)\n reinit!(cv, cell)\n celldofs!(celldofs, dh, cellid(cell))\n getcoordinates!(cellcoords, grid, cellid(cell))\n\n #Call the element routine\n integrate_shell!(ke, cv, qr_ooplane, cellcoords, data)\n\n assemble!(assembler, celldofs, ke, fe)\nend","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Apply BC and solve.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"apply!(K, f, ch)\na = K\\f","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Output results.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"VTKGridFile(\"linear_shell\", dh) do vtk\n write_solution(vtk, dh, a)\nend\n\nend; #end main functions\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Below is the function that creates the shell mesh. It simply generates a 2d-quadrature mesh, and appends a third coordinate (z-direction) to the node-positions.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function generate_shell_grid(nels, size)\n _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size))\n nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node for n in _grid.nodes]\n\n grid = Grid(_grid.cells, nodes)\n\n return grid\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#The-shell-element","page":"Linear shell","title":"The shell element","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The shell presented here comes from the book \"The finite element method - Linear static and dynamic finite element analysis\" by Hughes (1987). The shell is a so called degenerate shell element, meaning it is based on a continuum element. A brief description of the shell is given here.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"note: Note\nThis element might experience various locking phenomenas, and should only be seen as a proof of concept.","category":"page"},{"location":"tutorials/linear_shell/#Fiber-coordinate-system","page":"Linear shell","title":"Fiber coordinate system","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The element uses two coordinate systems. The first coordianate system, called the fiber system, is created for each element node, and is used as a reference frame for the rotations. The function below implements an algorithm that return the fiber directions, boldsymbole^f_a1, boldsymbole^f_a2 and boldsymbole^f_a3, at each node a.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function fiber_coordsys(Ps::Vector{Vec{3,Float64}})\n\n ef1 = Vec{3,Float64}[]\n ef2 = Vec{3,Float64}[]\n ef3 = Vec{3,Float64}[]\n for P in Ps\n a = abs.(P)\n j = 1\n if a[1] > a[3]; a[3] = a[1]; j = 2; end\n if a[2] > a[3]; j = 3; end\n\n e3 = P\n e2 = Tensors.cross(P, basevec(Vec{3}, j))\n e2 /= norm(e2)\n e1 = Tensors.cross(e2, P)\n\n push!(ef1, e1)\n push!(ef2, e2)\n push!(ef3, e3)\n end\n return ef1, ef2, ef3\n\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Lamina-coordinate-system","page":"Linear shell","title":"Lamina coordinate system","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The second coordinate system is the so called Lamina Coordinate system. It is created for each integration point, and is defined to be tangent to the mid-surface. It is in this system that we enforce that plane stress assumption, i.e. sigma_zz = 0. The function below returns the rotation matrix, boldsymbolq, for this coordinate system.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function lamina_coordsys(dNdξ, ζ, x, p, h)\n\n e1 = zero(Vec{3})\n e2 = zero(Vec{3})\n\n for i in 1:length(dNdξ)\n e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]\n e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]\n end\n\n e1 /= norm(e1)\n e2 /= norm(e2)\n\n ez = Tensors.cross(e1,e2)\n ez /= norm(ez)\n\n a = 0.5*(e1 + e2)\n a /= norm(a)\n\n b = Tensors.cross(ez,a)\n b /= norm(b)\n\n ex = sqrt(2)/2 * (a - b)\n ey = sqrt(2)/2 * (a + b)\n\n return Tensor{2,3}(hcat(ex,ey,ez))\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Geometrical-description","page":"Linear shell","title":"Geometrical description","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"A material point in the shell is defined as","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"boldsymbol x(xi eta zeta) = sum_a=1^N_textnodes N_a(xi eta) boldsymbolbarx_a + ζ frach2 boldsymbolbarp_a","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"where boldsymbolbarx_a are nodal positions on the mid-surface, and boldsymbolbarp_a is an vector that defines the fiber direction on the reference surface. N_a arethe shape functions.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Based on the definition of the position vector, we create an function for obtaining the Jacobian-matrix,","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"J_ij = fracpartial x_ipartial xi_j","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function getjacobian(q, N, dNdξ, ζ, X, p, h)\n J = zeros(3,3)\n for a in 1:length(N)\n for i in 1:3, j in 1:3\n _dNdξ = (j==3) ? 0.0 : dNdξ[a][j]\n _dζdξ = (j==3) ? 1.0 : 0.0\n _N = N[a]\n\n J[i,j] += _dNdξ * X[a][i] + (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i]\n end\n end\n\n return (q' * J) |> Tensor{2,3,Float64}\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Strains","page":"Linear shell","title":"Strains","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Small deformation is assumed,","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"varepsilon_ij= frac12(fracpartial u_ipartial x_j + fracpartial u_jpartial x_i)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The displacement field is calculated as:","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"boldsymbol u = sum_a=1^N_textnodes N_a barboldsymbol u_a +\n N_a ζfrach2(theta_a2 boldsymbol e^f_a1 - theta_a1 boldsymbol e^f_a2)\n","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"The gradient of the displacement (in the lamina coordinate system), then becomes:","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"fracpartial u_ipartial x_j = sum_m=1^3 q_im sum_a=1^N_textnodes fracpartial N_apartial x_j baru_am +\n fracpartial(N_a ζ)partial x_j frach2 (theta_a2 e^f_am1 - theta_a1 e^f_am2)","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T\n\n u = reinterpret(Vec{3,T}, dofvec[1:12])\n θ = reinterpret(Vec{2,T}, dofvec[13:20])\n\n dudx = zeros(T, 3, 3)\n for m in 1:3, j in 1:3\n for a in 1:length(N)\n dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m])\n end\n end\n\n dudx = q*dudx\n ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]]\n return ε\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/#Main-element-routine","page":"Linear shell","title":"Main element routine","text":"","category":"section"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Below is the main routine that calculates the stiffness matrix of the shell element. Since it is a so called degenerate shell element, the code is similar to that for an standard continuum element.","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_point]\n\nfunction integrate_shell!(ke, cv, qr_ooplane, X, data)\n nnodes = getnbasefunctions(cv)\n ndofs = nnodes*5\n h = data.thickness\n\n #Create the directors in each node.\n #Note: For a more general case, the directors should\n #be input parameters for the element routine.\n p = zeros(Vec{3}, nnodes)\n for i in 1:nnodes\n a = Vec{3}((0.0, 0.0, 1.0))\n p[i] = a/norm(a)\n end\n\n ef1, ef2, ef3 = fiber_coordsys(p)\n\n for iqp in 1:getnquadpoints(cv)\n N = [shape_value(cv, iqp, i) for i in 1:nnodes]\n dNdξ = [shape_reference_gradient(cv, iqp, i) for i in 1:nnodes]\n dNdx = [shape_gradient(cv, iqp, i) for i in 1:nnodes]\n\n for oqp in 1:length(qr_ooplane.weights)\n ζ = qr_ooplane.points[oqp][1]\n q = lamina_coordsys(dNdξ, ζ, X, p, h)\n\n J = getjacobian(q, N, dNdξ, ζ, X, p, h)\n Jinv = inv(J)\n dζdx = Vec{3}((0.0, 0.0, 1.0)) ⋅ Jinv\n\n #For simplicity, use automatic differentiation to construct the B-matrix from the strain.\n B = ForwardDiff.jacobian(\n (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) )\n\n dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp)\n ke .+= B'*data.C*B * dV\n end\n end\nend;\nnothing #hide","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"Run everything:","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"main()","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"","category":"page"},{"location":"tutorials/linear_shell/","page":"Linear shell","title":"Linear shell","text":"This page was generated using Literate.jl.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"using Ferrite\ngrid = generate_grid(Triangle, (2, 2))\ndh = DofHandler(grid); add!(dh, :u, Lagrange{RefTriangle,1}()); close!(dh)\nu = rand(ndofs(dh)); σ = rand(getncells(grid))","category":"page"},{"location":"topics/export/#Export","page":"Export","title":"Export","text":"","category":"section"},{"location":"topics/export/","page":"Export","title":"Export","text":"When the problem is solved, and the solution vector u is known we typically want to visualize it. The simplest way to do this is to write the solution to a VTK-file, which can be viewed in e.g. Paraview. To write VTK-files, Ferrite comes with an export interface with a WriteVTK.jl backend to simplify the exporting.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"The following structure can be used to write various output to a vtk-file:","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"VTKGridFile(\"my_solution\", grid) do vtk\n write_solution(vtk, dh, u)\nend;","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"where write_solution is just one example of the following functions that can be used","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"write_solution\nwrite_cell_data\nwrite_node_data\nwrite_projection\nFerrite.write_cellset\nFerrite.write_nodeset\nFerrite.write_constraints\nFerrite.write_cell_colors","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"Instead of using the do-block, it is also possible to do","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"vtk = VTKGridFile(\"my_solution\", grid)\nwrite_solution(vtk, dh, u)\n# etc.\nclose(vtk);","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"The data written by write_solution, write_cell_data, write_node_data, and write_projection may be either scalar (Vector{<:Number}) or tensor (Vector{<:AbstractTensor}) data.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"For simulations with multiple time steps, typically one VTK (.vtu) file is written for each time step. In order to connect the actual time with each of these files, the paraview_collection can function from WriteVTK.jl can be used. This will create one paraview datafile (.pvd) file and one VTKGridFile (.vtu) for each time step.","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"using WriteVTK\npvd = paraview_collection(\"my_results\")\nfor (step, t) in enumerate(range(0, 1, 5))\n # Do calculations to update u\n VTKGridFile(\"my_results_$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\nend\nvtk_save(pvd);","category":"page"},{"location":"topics/export/","page":"Export","title":"Export","text":"See Transient heat equation for an example","category":"page"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/export/#Postprocessing","page":"Postprocessing","title":"Postprocessing","text":"","category":"section"},{"location":"reference/export/#Projection-of-quadrature-point-data","page":"Postprocessing","title":"Projection of quadrature point data","text":"","category":"section"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"L2Projector(::Ferrite.AbstractGrid)\nadd!(::L2Projector, ::Ferrite.AbstractVecOrSet{Int}, ::Interpolation; kwargs...)\nclose!(::L2Projector)\nL2Projector(::Interpolation, ::Ferrite.AbstractGrid; kwargs...)\nproject","category":"page"},{"location":"reference/export/#Ferrite.L2Projector-Tuple{Ferrite.AbstractGrid}","page":"Postprocessing","title":"Ferrite.L2Projector","text":"L2Projector(grid::AbstractGrid)\n\nInitiate an L2Projector for projecting quadrature data onto a function space. To define the function space, add interpolations for differents cell sets with add! before close!ing the projector, see the example below.\n\nThe L2Projector acts as the integrated left hand side of the projection equation: Find projection u in U_h(Omega) subset L_2(Omega) such that\n\nint v u mathrmdOmega = int v f mathrmdOmega quad forall v in U_h(Omega)\n\nwhere f in L_2(Omega) is the data to project. The function space U_h(Omega) is the finite element approximation given by the interpolations add!ed to the L2Projector.\n\nExample\n\nproj = L2Projector(grid)\nqr_quad = QuadratureRule{RefQuadrilateral}(2)\nadd!(proj, quad_set, Lagrange{RefQuadrilateral, 1}(); qr_rhs = qr_quad)\nqr_tria = QuadratureRule{RefTriangle}(1)\nadd!(proj, tria_set, Lagrange{RefTriangle, 1}(); qr_rhs = qr_tria)\nclose!(proj)\n\nvals = Dict{Int, Vector{Float64}}() # Can also be Vector{Vector},\n # indexed with cellnr\nfor (set, qr) in ((quad_set, qr_quad), (tria_set, qr_tria))\n nqp = getnquadpoints(qr)\n for cellnr in set\n vals[cellnr] = rand(nqp)\n end\nend\n\nprojected = project(proj, vals)\n\nwhere projected can be used in e.g. evaluate_at_points with the PointEvalHandler, or with evaluate_at_grid_nodes.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.add!-Tuple{L2Projector, Union{AbstractSet{Int64}, AbstractVector{Int64}}, Interpolation}","page":"Postprocessing","title":"Ferrite.add!","text":"add!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;\n qr_rhs, [qr_lhs])\n\nAdd an interpolation ip on the cells in set to the L2Projector proj.\n\nqr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.\nThe optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.close!-Tuple{L2Projector}","page":"Postprocessing","title":"Ferrite.close!","text":"close!(proj::L2Projector)\n\nClose proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.L2Projector-Tuple{Interpolation, Ferrite.AbstractGrid}","page":"Postprocessing","title":"Ferrite.L2Projector","text":"L2Projector(ip::Interpolation, grid::AbstractGrid; [qr_lhs], [set])\n\nA quick way to initiate an L2Projector, add an interpolation ip on the set to it, and then close! it so that it can be used to project. The optional keyword argument set defaults to all cells in the grid, while qr_lhs defaults to a quadrature rule that integrates the mass matrix exactly for the interpolation ip.\n\n\n\n\n\n","category":"method"},{"location":"reference/export/#Ferrite.project","page":"Postprocessing","title":"Ferrite.project","text":"project(proj::L2Projector, vals, [qr_rhs::QuadratureRule])\n\nMakes a L2 projection of data vals to the nodes of the grid using the projector proj (see L2Projector).\n\nproject integrates the right hand side, and solves the projection u from the following projection equation: Find projection u in U_h(Omega) subset L_2(Omega) such that\n\nint v u mathrmdOmega = int v f mathrmdOmega quad forall v in U_h(Omega)\n\nwhere f in L_2(Omega) is the data to project. The function space U_h(Omega) is the finite element approximation given by the interpolations in proj.\n\nThe data vals should be an AbstractVector or AbstractDict that is indexed by the cell number. Each index in vals should give an AbstractVector with one element for each cell quadrature point.\n\nIf proj was created by calling L2Projector(ip, grid, set), qr_rhs must be given. Otherwise, this is added for each domain when calling add!(proj, args...).\n\nAlternatively, vals can be a matrix, with the column index referring the cell number, and the row index corresponding to quadrature point number. Example (scalar) input data:\n\nvals = [\n [0.44, 0.98, 0.32], # data for quadrature point 1, 2, 3 of element 1\n [0.29, 0.48, 0.55], # data for quadrature point 1, 2, 3 of element 2\n # ...\n]\n\nor equivalent in matrix form:\n\nvals = [\n 0.44 0.29 # ...\n 0.98 0.48 # ...\n 0.32 0.55 # ...\n]\n\nSupported data types to project are Numbers and AbstractTensors.\n\nnote: Note\nThe order of the returned data correspond to the order of the L2Projector's internal DofHandler. The data can be further analyzed with evaluate_at_points and evaluate_at_grid_nodes. Use write_projection to export the result.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Evaluation-at-points","page":"Postprocessing","title":"Evaluation at points","text":"","category":"section"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"evaluate_at_grid_nodes\nPointEvalHandler\nevaluate_at_points\nPointValues\nPointIterator\nPointLocation","category":"page"},{"location":"reference/export/#Ferrite.evaluate_at_grid_nodes","page":"Postprocessing","title":"Ferrite.evaluate_at_grid_nodes","text":"evaluate_at_grid_nodes(dh::AbstractDofHandler, u::AbstractVector{T}, fieldname::Symbol) where T\n\nEvaluate the approximated solution for field fieldname at the node coordinates of the grid given the Dof handler dh and the solution vector u.\n\nReturn a vector of length getnnodes(grid) where entry i contains the evaluation of the approximation in the coordinate of node i. If the field does not live on parts of the grid, the corresponding values for those nodes will be returned as NaNs.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.PointEvalHandler","page":"Postprocessing","title":"Ferrite.PointEvalHandler","text":"PointEvalHandler(grid::Grid, points::AbstractVector{Vec{dim,T}}; kwargs...) where {dim, T}\n\nThe PointEvalHandler can be used for function evaluation in arbitrary points in the domain – not just in quadrature points or nodes.\n\nThe constructor takes a grid and a vector of coordinates for the points. The PointEvalHandler computes i) the corresponding cell, and ii) the (local) coordinate within the cell, for each point. The fields of the PointEvalHandler are:\n\ncells::Vector{Union{Int,Nothing}}: vector with cell IDs for the points, with nothing for points that could not be found.\nlocal_coords::Vector{Union{Vec,Nothing}}: vector with the local coordinates (i.e. coordinates in the reference configuration) for the points, with nothing for points that could not be found.\n\nThere are two ways to use the PointEvalHandler to evaluate functions:\n\nevaluate_at_points: can be used when the function is described by i) a dh::DofHandler + uh::Vector (for example the FE-solution), or ii) a p::L2Projector + ph::Vector (for projected data).\nIteration with PointIterator + PointValues: can be used for more flexible evaluation in the points, for example to compute gradients.\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.evaluate_at_points","page":"Postprocessing","title":"Ferrite.evaluate_at_points","text":"evaluate_at_points(ph::PointEvalHandler, dh::AbstractDofHandler, dof_values::Vector{T}, [fieldname::Symbol]) where T\nevaluate_at_points(ph::PointEvalHandler, proj::L2Projector, dof_values::Vector{T}) where T\n\nReturn a Vector{T} (for a 1-dimensional field) or a Vector{Vec{fielddim, T}} (for a vector field) with the field values of field fieldname in the points of the PointEvalHandler. The fieldname can be omitted if only one field is stored in dh. The field values are computed based on the dof_values and interpolated to the local coordinates by the function interpolation of the corresponding field stored in the AbstractDofHandler or the L2Projector.\n\nPoints that could not be found in the domain when constructing the PointEvalHandler will have NaNs for the corresponding entries in the output vector.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.PointValues","page":"Postprocessing","title":"Ferrite.PointValues","text":"PointValues(cv::CellValues)\nPointValues([::Type{T}], func_interpol::Interpolation, [geom_interpol::Interpolation])\n\nSimilar to CellValues but with a single updateable \"quadrature point\". PointValues are used for evaluation of functions/gradients in arbitrary points of the domain together with a PointEvalHandler.\n\nPointValues can be created from CellValues, or from the interpolations directly.\n\nPointValues are reinitialized like other CellValues, but since the local reference coordinate of the \"quadrature point\" changes this needs to be passed to reinit!, in addition to the element coordinates: reinit!(pv, coords, local_coord). Alternatively, it can be reinitialized with a PointLocation when iterating a PointEvalHandler with a PointIterator.\n\nFor function/gradient evaluation, PointValues are used in the same way as CellValues, i.e. by using function_value, function_gradient, etc, with the exception that there is no need to specify the quadrature point index (since PointValues only have 1, this is the default).\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.PointIterator","page":"Postprocessing","title":"Ferrite.PointIterator","text":"PointIterator(ph::PointEvalHandler)\n\nCreate an iterator over the points in the PointEvalHandler. The elements of the iterator are either a PointLocation, if the corresponding point could be found in the grid, or nothing, if the point was not found.\n\nA PointLocation can be used to query the cell ID with the cellid function, and can be used to reinitialize PointValues with reinit!.\n\nExamples\n\nph = PointEvalHandler(grid, points)\n\nfor point in PointIterator(ph)\n point === nothing && continue # Skip any points that weren't found\n reinit!(pointvalues, point) # Update pointvalues\n # ...\nend\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.PointLocation","page":"Postprocessing","title":"Ferrite.PointLocation","text":"PointLocation\n\nElement of a PointIterator, typically used to reinitialize PointValues. Fields:\n\ncid::Int: ID of the cell containing the point\nlocal_coord::Vec: the local (reference) coordinate of the point\ncoords::Vector{Vec}: the coordinates of the cell\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#VTK-export","page":"Postprocessing","title":"VTK export","text":"","category":"section"},{"location":"reference/export/","page":"Postprocessing","title":"Postprocessing","text":"VTKGridFile\nwrite_solution\nwrite_projection\nwrite_cell_data\nwrite_node_data\nFerrite.write_cellset\nFerrite.write_nodeset\nFerrite.write_constraints\nFerrite.write_cell_colors","category":"page"},{"location":"reference/export/#Ferrite.VTKGridFile","page":"Postprocessing","title":"Ferrite.VTKGridFile","text":"VTKGridFile(filename::AbstractString, grid::AbstractGrid; kwargs...)\nVTKGridFile(filename::AbstractString, dh::DofHandler; kwargs...)\n\nCreate a VTKGridFile that contains an unstructured VTK grid. The keyword arguments are forwarded to WriteVTK.vtk_grid, see Data Formatting Options\n\nThis file handler can be used to to write data with\n\nwrite_solution\nwrite_cell_data\nwrite_projection\nwrite_node_data.\nFerrite.write_cellset\nFerrite.write_nodeset\nFerrite.write_constraints\n\nIt is necessary to call close(::VTKGridFile) to save the data after writing to the file handler. Using the supported do-block does this automatically:\n\nVTKGridFile(filename, grid) do vtk\n write_solution(vtk, dh, u)\n write_cell_data(vtk, celldata)\nend\n\n\n\n\n\n","category":"type"},{"location":"reference/export/#Ferrite.write_solution","page":"Postprocessing","title":"Ferrite.write_solution","text":"write_solution(vtk::VTKGridFile, dh::AbstractDofHandler, u::Vector, suffix=\"\")\n\nSave the values at the nodes in the degree of freedom vector u to vtk. Each field in dh will be saved separately, and suffix can be used to append to the fieldname.\n\nu can also contain tensorial values, but each entry in u must correspond to a degree of freedom in dh, see write_node_data for details. Use write_node_data directly when exporting values that are already sorted by the nodes in the grid.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_projection","page":"Postprocessing","title":"Ferrite.write_projection","text":"write_projection(vtk::VTKGridFile, proj::L2Projector, vals::Vector, name::AbstractString)\n\nProject vals to the grid nodes with proj and save to vtk.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_cell_data","page":"Postprocessing","title":"Ferrite.write_cell_data","text":"write_cell_data(vtk::VTKGridFile, celldata::AbstractVector, name::String)\n\nWrite the celldata that is ordered by the cells in the grid to the vtk file.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_node_data","page":"Postprocessing","title":"Ferrite.write_node_data","text":"write_node_data(vtk::VTKGridFile, nodedata::Vector{Real}, name)\nwrite_node_data(vtk::VTKGridFile, nodedata::Vector{<:AbstractTensor}, name)\n\nWrite the nodedata that is ordered by the nodes in the grid to vtk.\n\nWhen nodedata contains Tensors.Vecs, each component is exported. Two-dimensional vectors are padded with zeros.\n\nWhen nodedata contains second order tensors, the index order, [11, 22, 33, 23, 13, 12, 32, 31, 21], follows the default Voigt order in Tensors.jl.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_cellset","page":"Postprocessing","title":"Ferrite.write_cellset","text":"write_cellset(vtk, grid::AbstractGrid)\nwrite_cellset(vtk, grid::AbstractGrid, cellset::String)\nwrite_cellset(vtk, grid::AbstractGrid, cellsets::Union{AbstractVector{String},AbstractSet{String})\n\nWrite all cell sets in the grid with name according to their keys and celldata 1 if the cell is in the set, and 0 otherwise. It is also possible to only export a single cellset, or multiple cellsets.\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_nodeset","page":"Postprocessing","title":"Ferrite.write_nodeset","text":"write_nodeset(vtk::VTKGridFile, grid::AbstractGrid, nodeset::String)\n\nWrite nodal values of 1 for nodes in nodeset, and 0 otherwise\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_constraints","page":"Postprocessing","title":"Ferrite.write_constraints","text":"write_constraints(vtk::VTKGridFile, ch::ConstraintHandler)\n\nSaves the dirichlet boundary conditions to a vtkfile. Values will have a 1 where bcs are active and 0 otherwise\n\n\n\n\n\n","category":"function"},{"location":"reference/export/#Ferrite.write_cell_colors","page":"Postprocessing","title":"Ferrite.write_cell_colors","text":"write_cell_colors(vtk::VTKGridFile, grid::AbstractGrid, cell_colors, name=\"coloring\")\n\nWrite cell colors (see create_coloring) to a VTK file for visualization.\n\nIn case of coloring a subset, the cells which are not part of the subset are represented as color 0.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"EditURL = \"../literate-tutorials/linear_elasticity.jl\"","category":"page"},{"location":"tutorials/linear_elasticity/#tutorial-linear-elasticity","page":"Linear elasticity","title":"Linear elasticity","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"(Image: )","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Figure 1: Linear elastically deformed 1mm times 1mm Ferrite logo.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"tip: Tip\nThis tutorial is also available as a Jupyter notebook: linear_elasticity.ipynb.","category":"page"},{"location":"tutorials/linear_elasticity/#Introduction","page":"Linear elasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The classical first finite element problem to solve in solid mechanics is a linear balance of momentum problem. We will use this to introduce a vector valued field, the displacements boldsymbolu(boldsymbolx). In addition, some features of the Tensors.jl toolbox are demonstrated.","category":"page"},{"location":"tutorials/linear_elasticity/#Strong-form","page":"Linear elasticity","title":"Strong form","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The strong form of the balance of momentum for quasi-static loading is given by","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"beginalignat*2\n mathrmdiv(boldsymbolsigma) + boldsymbolb = 0 quad boldsymbolx in Omega \n boldsymbolu = boldsymbolu_mathrmD quad boldsymbolx in Gamma_mathrmD \n boldsymboln cdot boldsymbolsigma = boldsymbolt_mathrmN quad boldsymbolx in Gamma_mathrmN\nendalignat*","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where boldsymbolsigma is the (Cauchy) stress tensor and boldsymbolb the body force. The domain, Omega, has the boundary Gamma, consisting of a Dirichlet part, Gamma_mathrmD, and a Neumann part, Gamma_mathrmN, with outward pointing normal vector boldsymboln. boldsymbolu_mathrmD denotes prescribed displacements on Gamma_mathrmD, while boldsymbolt_mathrmN the known tractions on Gamma_mathrmN.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In this tutorial, we use linear elasticity, such that","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolsigma = mathsfC boldsymbolvarepsilon quad\nboldsymbolvarepsilon = leftmathrmgrad(boldsymbolu)right^mathrmsym","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where mathsfC is the 4th order elastic stiffness tensor and boldsymbolvarepsilon the small strain tensor. The colon, , represents the double contraction, sigma_ij = mathsfC_ijkl varepsilon_kl, and the superscript mathrmsym denotes the symmetric part.","category":"page"},{"location":"tutorials/linear_elasticity/#Weak-form","page":"Linear elasticity","title":"Weak form","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The resulting weak form is given given as follows: Find boldsymbolu in mathbbU such that","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"int_Omega\n mathrmgrad(delta boldsymbolu) boldsymbolsigma\n mathrmdOmega\n=\nint_Gamma\n delta boldsymbolu cdot boldsymbolt\n mathrmdGamma\n+\nint_Omega\n delta boldsymbolu cdot boldsymbolb\n mathrmdOmega\nquad forall delta boldsymbolu in mathbbT","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where mathbbU and mathbbT denote suitable trial and test function spaces. delta boldsymbolu is a vector valued test function and boldsymbolt = boldsymbolsigmacdotboldsymboln is the traction vector on the boundary. In this tutorial, we will neglect body forces (i.e. boldsymbolb = boldsymbol0) and the weak form reduces to","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"int_Omega\n mathrmgrad(delta boldsymbolu) boldsymbolsigma\n mathrmdOmega\n=\nint_Gamma\n delta boldsymbolu cdot boldsymbolt\n mathrmdGamma ","category":"page"},{"location":"tutorials/linear_elasticity/#Finite-element-form","page":"Linear elasticity","title":"Finite element form","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Finally, the finite element form is obtained by introducing the finite element shape functions. Since the displacement field, boldsymbolu, is vector valued, we use vector valued shape functions deltaboldsymbolN_i and boldsymbolN_i to approximate the test and trial functions:","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolu approx sum_i=1^N boldsymbolN_i (boldsymbolx) hatu_i\nqquad\ndelta boldsymbolu approx sum_i=1^N deltaboldsymbolN_i (boldsymbolx) delta hatu_i","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Here N is the number of nodal variables, with hatu_i and deltahatu_i representing the i-th nodal value. Using the Einstein summation convention, we can write this in short form as boldsymbolu approx boldsymbolN_i hatu_i and deltaboldsymbolu approx deltaboldsymbolN_i deltahatu_i.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Inserting the these into the weak form, and noting that that the equation should hold for all delta hatu_i, we get","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"underbraceint_Omega mathrmgrad(delta boldsymbolN_i) boldsymbolsigma mathrmdOmega_f_i^mathrmint = underbraceint_Gamma delta boldsymbolN_i cdot boldsymbolt mathrmdGamma_f_i^mathrmext","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Inserting the linear constitutive relationship, boldsymbolsigma = mathsfCboldsymbolvarepsilon, in the internal force vector, f_i^mathrmint, yields the linear equation","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"underbraceleftint_Omega mathrmgrad(delta boldsymbolN_i) mathsfC leftmathrmgrad(boldsymbolN_j)right^mathrmsym mathrmdOmegaright_K_ij hatu_j = f_i^mathrmext","category":"page"},{"location":"tutorials/linear_elasticity/#Implementation","page":"Linear elasticity","title":"Implementation","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"First we load Ferrite, and some other packages we need.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Ferrite, FerriteGmsh, SparseArrays","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"As in the heat equation tutorial, we will use a unit square - but here we'll load the grid of the Ferrite logo! This is done by downloading logo.geo and loading it using FerriteGmsh.jl,","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Downloads: download\nlogo_mesh = \"logo.geo\"\nasset_url = \"https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/\"\nisfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh)\n\nFerriteGmsh.Gmsh.initialize() #hide\nFerriteGmsh.Gmsh.gmsh.option.set_number(\"General.Verbosity\", 2) #hide\ngrid = togrid(logo_mesh);\nFerriteGmsh.Gmsh.finalize(); #hide\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The generated grid lacks the facetsets for the boundaries, so we add them by using Ferrite's addfacetset!. It allows us to add facetsets to the grid based on coordinates. Note that approximate comparison to 0.0 doesn't work well, so we use a tolerance instead.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"addfacetset!(grid, \"top\", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes\naddfacetset!(grid, \"left\", x -> abs(x[1]) < 1.0e-6)\naddfacetset!(grid, \"bottom\", x -> abs(x[2]) < 1.0e-6);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Trial-and-test-functions","page":"Linear elasticity","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In this tutorial, we use the same linear Lagrange shape functions to approximate both the test and trial spaces, i.e. deltaboldsymbolN_i = boldsymbolN_i. As our grid is composed of triangular elements, we need the Lagrange functions defined on a RefTriangle. All currently available interpolations can be found under Interpolation.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Here we use linear triangular elements (also called constant strain triangles). The vector valued shape functions are constructed by raising the interpolation to the power dim (the dimension) since the displacement field has one component in each spatial dimension.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"dim = 2\norder = 1 # linear interpolation\nip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In order to evaluate the integrals, we need to specify the quadrature rules to use. Due to the linear interpolation, a single quadrature point suffices, both inside the cell and on the facet. In 2d, a facet is the edge of the element.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"qr = QuadratureRule{RefTriangle}(1) # 1 quadrature point\nqr_face = FacetQuadratureRule{RefTriangle}(1);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Finally, we collect the interpolations and quadrature rules into the CellValues and FacetValues buffers, which we will later use to evaluate the integrals over the cells and facets.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"cellvalues = CellValues(qr, ip)\nfacetvalues = FacetValues(qr_face, ip);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Degrees-of-freedom","page":"Linear elasticity","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"For distributing degrees of freedom, we define a DofHandler. The DofHandler knows that u has two degrees of freedom per node because we vectorized the interpolation above.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Boundary-conditions","page":"Linear elasticity","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We set Dirichlet boundary conditions by fixing the motion normal to the bottom and left boundaries. The last argument to Dirichlet determines which components of the field should be constrained. If no argument is given, all components are constrained by default.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"ch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> 0.0, 2))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> 0.0, 1))\nclose!(ch);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In addition, we will use Neumann boundary conditions on the top surface, where we add a traction vector of the form","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolt_mathrmN(boldsymbolx) = (20e3) x_1 boldsymbole_2 mathrmNmathrmmm^2","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"traction(x) = Vec(0.0, 20.0e3 * x[1]);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"On the right boundary, we don't do anything, resulting in a zero traction Neumann boundary. In order to assemble the external forces, f_i^mathrmext, we need to iterate over all facets in the relevant facetset. We do this by using the FacetIterator.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction)\n # Create a temporary array for the facet's local contributions to the external force vector\n fe_ext = zeros(getnbasefunctions(facetvalues))\n for facet in FacetIterator(dh, facetset)\n # Update the facetvalues to the correct facet number\n reinit!(facetvalues, facet)\n # Reset the temporary array for the next facet\n fill!(fe_ext, 0.0)\n # Access the cell's coordinates\n cell_coordinates = getcoordinates(facet)\n for qp in 1:getnquadpoints(facetvalues)\n # Calculate the global coordinate of the quadrature point.\n x = spatial_coordinate(facetvalues, qp, cell_coordinates)\n tₚ = prescribed_traction(x)\n # Get the integration weight for the current quadrature point.\n dΓ = getdetJdV(facetvalues, qp)\n for i in 1:getnbasefunctions(facetvalues)\n Nᵢ = shape_value(facetvalues, qp, i)\n fe_ext[i] += tₚ ⋅ Nᵢ * dΓ\n end\n end\n # Add the local contributions to the correct indices in the global external force vector\n assemble!(f_ext, celldofs(facet), fe_ext)\n end\n return f_ext\nend\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Material-behavior","page":"Linear elasticity","title":"Material behavior","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Next, we need to define the material behavior, specifically the elastic stiffness tensor, mathsfC. In this tutorial, we use plane strain linear isotropic elasticity, with Hooke's law as","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"boldsymbolsigma = 2G boldsymbolvarepsilon^mathrmdev + 3K boldsymbolvarepsilon^mathrmvol","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"where G is the shear modulus and K the bulk modulus. This expression can be written as boldsymbolsigma = mathsfCboldsymbolvarepsilon, with","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":" mathsfC = fracpartial boldsymbolsigmapartial boldsymbolvarepsilon","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The volumetric, boldsymbolvarepsilon^mathrmvol, and deviatoric, boldsymbolvarepsilon^mathrmdev strains, are defined as","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"beginalign*\nboldsymbolvarepsilon^mathrmvol = fracmathrmtr(boldsymbolvarepsilon)3boldsymbolI quad\nboldsymbolvarepsilon^mathrmdev = boldsymbolvarepsilon - boldsymbolvarepsilon^mathrmvol\nendalign*","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Starting from Young's modulus, E, and Poisson's ratio, nu, the shear and bulk modulus are","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"G = fracE2(1 + nu) quad K = fracE3(1 - 2nu)","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Emod = 200.0e3 # Young's modulus [MPa]\nν = 0.3 # Poisson's ratio [-]\n\nGmod = Emod / (2(1 + ν)) # Shear modulus\nKmod = Emod / (3(1 - 2ν)) # Bulk modulus","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Finally, we demonstrate Tensors.jl's automatic differentiation capabilities when calculating the elastic stiffness tensor","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"C = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"details: Plane stress instead of plane strain?\nIn order to change this tutorial to consider plane stress instead of plane strain, the elastic stiffness tensor should be changed to reflect this. The plane stress elasticity stiffness matrix in Voigt notation for engineering shear strains, is given asunderlineunderlineboldsymbolE = fracE1 - nu^2beginbmatrix\n1 nu 0 \nnu 1 0 \n0 0 (1 - nu)2\nendbmatrixThis matrix can be converted into the 4th order elastic stiffness tensor asC_voigt = Emod * [1.0 ν 0.0; ν 1.0 0.0; 0.0 0.0 (1-ν)/2] / (1 - ν^2)\nC = fromvoigt(SymmetricTensor{4,2}, E_voigt)","category":"page"},{"location":"tutorials/linear_elasticity/#Element-routine","page":"Linear elasticity","title":"Element routine","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"To calculate the global stiffness matrix, K_ij, the element routine computes the local stiffness matrix ke for a single element and assembles it into the global matrix. ke is pre-allocated and reused for all elements.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Note that the elastic stiffness tensor mathsfC is constant. Thus is needs to be computed and once and can then be used for all integration points.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function assemble_cell!(ke, cellvalues, C)\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the integration weight for the quadrature point\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n # Gradient of the test function\n ∇Nᵢ = shape_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n # Symmetric gradient of the trial function\n ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j)\n ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ\n end\n end\n end\n return ke\nend\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Global-assembly","page":"Linear elasticity","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We define the function assemble_global to loop over the elements and do the global assembly. The function takes the preallocated sparse matrix K, our DofHandler dh, our cellvalues and the elastic stiffness tensor C as input arguments and computes the global stiffness matrix K.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function assemble_global!(K, dh, cellvalues, C)\n # Allocate the element stiffness matrix\n n_basefuncs = getnbasefunctions(cellvalues)\n ke = zeros(n_basefuncs, n_basefuncs)\n # Create an assembler\n assembler = start_assemble(K)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Update the shape function gradients based on the cell coordinates\n reinit!(cellvalues, cell)\n # Reset the element stiffness matrix\n fill!(ke, 0.0)\n # Compute element contribution\n assemble_cell!(ke, cellvalues, C)\n # Assemble ke into K\n assemble!(assembler, celldofs(cell), ke)\n end\n return K\nend\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Solution-of-the-system","page":"Linear elasticity","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"The last step is to solve the system. First we allocate the global stiffness matrix K and assemble it.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"K = allocate_matrix(dh)\nassemble_global!(K, dh, cellvalues, C);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Then we allocate and assemble the external force vector.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"f_ext = zeros(ndofs(dh))\nassemble_external_forces!(f_ext, dh, getfacetset(grid, \"top\"), facetvalues, traction);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"To account for the Dirichlet boundary conditions we use the apply! function. This modifies elements in K and f, such that we can get the correct solution vector u by using solving the linear equation system K_ij hatu_j = f^mathrmext_i,","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"apply!(K, f_ext, ch)\nu = K \\ f_ext;\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#Postprocessing","page":"Linear elasticity","title":"Postprocessing","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"In this case, we want to analyze the displacements, as well as the stress field. We calculate the stress in each quadrature point, and then export it in two different ways:","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Constant in each cell (matching the approximation of constant strains in each element). Note that a current limitation is that cell data for second order tensors must be exported component-wise (see issue #768)\nInterpolated using the linear lagrange ansatz functions via the L2Projector.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"function calculate_stresses(grid, dh, cv, u, C)\n qp_stresses = [\n [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)]\n for _ in 1:getncells(grid)\n ]\n avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...)\n for cell in CellIterator(dh)\n reinit!(cv, cell)\n cell_stresses = qp_stresses[cellid(cell)]\n for q_point in 1:getnquadpoints(cv)\n ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell))\n cell_stresses[q_point] = C ⊡ ε\n end\n σ_avg = sum(cell_stresses) / getnquadpoints(cv)\n avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1]\n avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2]\n avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2]\n end\n return qp_stresses, avg_cell_stresses\nend\n\nqp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C);\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We now use the the L2Projector to project the stress-field onto the piecewise linear finite element space that we used to solve the problem.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"proj = L2Projector(Lagrange{RefTriangle, 1}(), grid)\nstress_field = project(proj, qp_stresses, qr);\n\ncolor_data = zeros(Int, getncells(grid)) #hide\ncolors = [ #hide\n \"1\" => 1, \"5\" => 1, # purple #hide\n \"2\" => 2, \"3\" => 2, # red #hide\n \"4\" => 3, # blue #hide\n \"6\" => 4, # green #hide\n] #hide\nfor (key, color) in colors #hide\n for i in getcellset(grid, key) #hide\n color_data[i] = color #hide\n end #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"To visualize the result we export to a VTK-file. Specifically, an unstructured grid file, .vtu, is created, which can be viewed in e.g. ParaView.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"VTKGridFile(\"linear_elasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n for (i, key) in enumerate((\"11\", \"22\", \"12\"))\n write_cell_data(vtk, avg_cell_stresses[i], \"sigma_\" * key)\n end\n write_projection(vtk, proj, stress_field, \"stress field\")\n Ferrite.write_cellset(vtk, grid)\n write_cell_data(vtk, color_data, \"colors\") #hide\nend","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"We used the displacement field to visualize the deformed logo in Figure 1, and in Figure 2, we demonstrate the difference between the interpolated stress field and the constant stress in each cell.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"(Image: )","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Figure 2: Vertical normal stresses (MPa) exported using the L2Projector (left) and constant stress in each cell (right).","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Test #hide\nlinux_result = 0.31742879147646924 #hide\n@test abs(norm(u) - linux_result) < 0.01 #hide\nSys.islinux() && @test norm(u) ≈ linux_result #hide\nnothing #hide","category":"page"},{"location":"tutorials/linear_elasticity/#linear_elasticity-plain-program","page":"Linear elasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"Here follows a version of the program without any comments. The file is also available here: linear_elasticity.jl.","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"using Ferrite, FerriteGmsh, SparseArrays\n\nusing Downloads: download\nlogo_mesh = \"logo.geo\"\nasset_url = \"https://raw.githubusercontent.com/Ferrite-FEM/Ferrite.jl/gh-pages/assets/\"\nisfile(logo_mesh) || download(string(asset_url, logo_mesh), logo_mesh)\n\ngrid = togrid(logo_mesh);\n\naddfacetset!(grid, \"top\", x -> x[2] ≈ 1.0) # facets for which x[2] ≈ 1.0 for all nodes\naddfacetset!(grid, \"left\", x -> abs(x[1]) < 1.0e-6)\naddfacetset!(grid, \"bottom\", x -> abs(x[2]) < 1.0e-6);\n\ndim = 2\norder = 1 # linear interpolation\nip = Lagrange{RefTriangle, order}()^dim; # vector valued interpolation\n\nqr = QuadratureRule{RefTriangle}(1) # 1 quadrature point\nqr_face = FacetQuadratureRule{RefTriangle}(1);\n\ncellvalues = CellValues(qr, ip)\nfacetvalues = FacetValues(qr_face, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> 0.0, 2))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> 0.0, 1))\nclose!(ch);\n\ntraction(x) = Vec(0.0, 20.0e3 * x[1]);\n\nfunction assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_traction)\n # Create a temporary array for the facet's local contributions to the external force vector\n fe_ext = zeros(getnbasefunctions(facetvalues))\n for facet in FacetIterator(dh, facetset)\n # Update the facetvalues to the correct facet number\n reinit!(facetvalues, facet)\n # Reset the temporary array for the next facet\n fill!(fe_ext, 0.0)\n # Access the cell's coordinates\n cell_coordinates = getcoordinates(facet)\n for qp in 1:getnquadpoints(facetvalues)\n # Calculate the global coordinate of the quadrature point.\n x = spatial_coordinate(facetvalues, qp, cell_coordinates)\n tₚ = prescribed_traction(x)\n # Get the integration weight for the current quadrature point.\n dΓ = getdetJdV(facetvalues, qp)\n for i in 1:getnbasefunctions(facetvalues)\n Nᵢ = shape_value(facetvalues, qp, i)\n fe_ext[i] += tₚ ⋅ Nᵢ * dΓ\n end\n end\n # Add the local contributions to the correct indices in the global external force vector\n assemble!(f_ext, celldofs(facet), fe_ext)\n end\n return f_ext\nend\n\nEmod = 200.0e3 # Young's modulus [MPa]\nν = 0.3 # Poisson's ratio [-]\n\nGmod = Emod / (2(1 + ν)) # Shear modulus\nKmod = Emod / (3(1 - 2ν)) # Bulk modulus\n\nC = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));\n\nfunction assemble_cell!(ke, cellvalues, C)\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the integration weight for the quadrature point\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n # Gradient of the test function\n ∇Nᵢ = shape_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n # Symmetric gradient of the trial function\n ∇ˢʸᵐNⱼ = shape_symmetric_gradient(cellvalues, q_point, j)\n ke[i, j] += (∇Nᵢ ⊡ C ⊡ ∇ˢʸᵐNⱼ) * dΩ\n end\n end\n end\n return ke\nend\n\nfunction assemble_global!(K, dh, cellvalues, C)\n # Allocate the element stiffness matrix\n n_basefuncs = getnbasefunctions(cellvalues)\n ke = zeros(n_basefuncs, n_basefuncs)\n # Create an assembler\n assembler = start_assemble(K)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Update the shape function gradients based on the cell coordinates\n reinit!(cellvalues, cell)\n # Reset the element stiffness matrix\n fill!(ke, 0.0)\n # Compute element contribution\n assemble_cell!(ke, cellvalues, C)\n # Assemble ke into K\n assemble!(assembler, celldofs(cell), ke)\n end\n return K\nend\n\nK = allocate_matrix(dh)\nassemble_global!(K, dh, cellvalues, C);\n\nf_ext = zeros(ndofs(dh))\nassemble_external_forces!(f_ext, dh, getfacetset(grid, \"top\"), facetvalues, traction);\n\napply!(K, f_ext, ch)\nu = K \\ f_ext;\n\nfunction calculate_stresses(grid, dh, cv, u, C)\n qp_stresses = [\n [zero(SymmetricTensor{2, 2}) for _ in 1:getnquadpoints(cv)]\n for _ in 1:getncells(grid)\n ]\n avg_cell_stresses = tuple((zeros(getncells(grid)) for _ in 1:3)...)\n for cell in CellIterator(dh)\n reinit!(cv, cell)\n cell_stresses = qp_stresses[cellid(cell)]\n for q_point in 1:getnquadpoints(cv)\n ε = function_symmetric_gradient(cv, q_point, u, celldofs(cell))\n cell_stresses[q_point] = C ⊡ ε\n end\n σ_avg = sum(cell_stresses) / getnquadpoints(cv)\n avg_cell_stresses[1][cellid(cell)] = σ_avg[1, 1]\n avg_cell_stresses[2][cellid(cell)] = σ_avg[2, 2]\n avg_cell_stresses[3][cellid(cell)] = σ_avg[1, 2]\n end\n return qp_stresses, avg_cell_stresses\nend\n\nqp_stresses, avg_cell_stresses = calculate_stresses(grid, dh, cellvalues, u, C);\n\nproj = L2Projector(Lagrange{RefTriangle, 1}(), grid)\nstress_field = project(proj, qp_stresses, qr);\n\n\nVTKGridFile(\"linear_elasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n for (i, key) in enumerate((\"11\", \"22\", \"12\"))\n write_cell_data(vtk, avg_cell_stresses[i], \"sigma_\" * key)\n end\n write_projection(vtk, proj, stress_field, \"stress field\")\n Ferrite.write_cellset(vtk, grid)\nend","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"","category":"page"},{"location":"tutorials/linear_elasticity/","page":"Linear elasticity","title":"Linear elasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"EditURL = \"../literate-tutorials/heat_equation.jl\"","category":"page"},{"location":"tutorials/heat_equation/#tutorial-heat-equation","page":"Heat equation","title":"Heat equation","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"(Image: )","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Figure 1: Temperature field on the unit square with an internal uniform heat source solved with homogeneous Dirichlet boundary conditions on the boundary.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"tip: Tip\nThis example is also available as a Jupyter notebook: heat_equation.ipynb.","category":"page"},{"location":"tutorials/heat_equation/#Introduction","page":"Heat equation","title":"Introduction","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"The heat equation is the \"Hello, world!\" equation of finite elements. Here we solve the equation on a unit square, with a uniform internal source. The strong form of the (linear) heat equation is given by","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":" -nabla cdot (k nabla u) = f quad textbfx in Omega","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"where u is the unknown temperature field, k the heat conductivity, f the heat source and Omega the domain. For simplicity we set f = 1 and k = 1. We will consider homogeneous Dirichlet boundary conditions such that","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"u(textbfx) = 0 quad textbfx in partial Omega","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"where partial Omega denotes the boundary of Omega. The resulting weak form is given given as follows: Find u in mathbbU such that","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"int_Omega nabla delta u cdot nabla u dOmega = int_Omega delta u dOmega quad forall delta u in mathbbT","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"where delta u is a test function, and where mathbbU and mathbbT are suitable trial and test function sets, respectively.","category":"page"},{"location":"tutorials/heat_equation/#Commented-Program","page":"Heat equation","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"First we load Ferrite, and some other packages we need","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"using Ferrite, SparseArrays","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"We start by generating a simple grid with 20x20 quadrilateral elements using generate_grid. The generator defaults to the unit square, so we don't need to specify the corners of the domain.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"grid = generate_grid(Quadrilateral, (20, 20));\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/#Trial-and-test-functions","page":"Heat equation","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"A CellValues facilitates the process of evaluating values and gradients of test and trial functions (among other things). To define this we need to specify an interpolation space for the shape functions. We use Lagrange functions based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on the same reference element. We combine the interpolation and the quadrature rule to a CellValues object.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"ip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/#Degrees-of-freedom","page":"Heat equation","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Next we need to define a DofHandler, which will take care of numbering and distribution of degrees of freedom for our approximated fields. We create the DofHandler and then add a single scalar field called :u based on our interpolation ip defined above. Lastly we close! the DofHandler, it is now that the dofs are distributed for all the elements.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now that we have distributed all our dofs we can create our tangent matrix, using allocate_matrix. This function returns a sparse matrix with the correct entries stored.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"K = allocate_matrix(dh)","category":"page"},{"location":"tutorials/heat_equation/#Boundary-conditions","page":"Heat equation","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"In Ferrite constraints like Dirichlet boundary conditions are handled by a ConstraintHandler.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"ch = ConstraintHandler(dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Next we need to add constraints to ch. For this problem we define homogeneous Dirichlet boundary conditions on the whole boundary, i.e. the union of all the facet sets on the boundary.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"∂Ω = union(\n getfacetset(grid, \"left\"),\n getfacetset(grid, \"right\"),\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now we are set up to define our constraint. We specify which field the condition is for, and our combined facet set ∂Ω. The last argument is a function of the form f(textbfx) or f(textbfx t), where textbfx is the spatial coordinate and t the current time, and returns the prescribed value. Since the boundary condition in this case do not depend on time we define our function as f(textbfx) = 0, i.e. no matter what textbfx we return 0. When we have specified our constraint we add! it to ch.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"dbc = Dirichlet(:u, ∂Ω, (x, t) -> 0)\nadd!(ch, dbc);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Finally we also need to close! our constraint handler. When we call close! the dofs corresponding to our constraints are calculated and stored in our ch object.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"close!(ch)","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Note that if one or more of the constraints are time dependent we would use update! to recompute prescribed values in each new timestep.","category":"page"},{"location":"tutorials/heat_equation/#Assembling-the-linear-system","page":"Heat equation","title":"Assembling the linear system","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Now we have all the pieces needed to assemble the linear system, K u = f. Assembling of the global system is done by looping over all the elements in order to compute the element contributions K_e and f_e, which are then assembled to the appropriate place in the global K and f.","category":"page"},{"location":"tutorials/heat_equation/#Element-assembly","page":"Heat equation","title":"Element assembly","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"We define the function assemble_element! (see below) which computes the contribution for an element. The function takes pre-allocated ke and fe (they are allocated once and then reused for all elements) so we first need to make sure that they are all zeroes at the start of the function by using fill!. Then we loop over all the quadrature points, and for each quadrature point we loop over all the (local) shape functions. We need the value and gradient of the test function, δu and also the gradient of the trial function u. We get all of these from cellvalues.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"note: Notation\nComparing with the brief finite element introduction in Introduction to FEM, the variables δu, ∇δu and ∇u are actually phi_i(textbfx_q), nabla phi_i(textbfx_q) and nabla phi_j(textbfx_q), i.e. the evaluation of the trial and test functions in the quadrature point textbfx_q. However, to underline the strong parallel between the weak form and the implementation, this example uses the symbols appearing in the weak form.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\nnothing # hide","category":"page"},{"location":"tutorials/heat_equation/#Global-assembly","page":"Heat equation","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"We define the function assemble_global to loop over the elements and do the global assembly. The function takes our cellvalues, the sparse matrix K, and our DofHandler as input arguments and returns the assembled global stiffness matrix, and the assembled global force vector. We start by allocating Ke, fe, and the global force vector f. We also create an assembler by using start_assemble. The assembler lets us assemble into K and f efficiently. We then start the loop over all the elements. In each loop iteration we reinitialize cellvalues (to update derivatives of shape functions etc.), compute the element contribution with assemble_element!, and then assemble into the global K and f with assemble!.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"note: Notation\nComparing again with Introduction to FEM, f and u correspond to underlinehatf and underlinehatu, since they represent the discretized versions. However, through the code we use f and u instead to reflect the strong connection between the weak form and the Ferrite implementation.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"function assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"tutorials/heat_equation/#Solution-of-the-system","page":"Heat equation","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"The last step is to solve the system. First we call assemble_global to obtain the global stiffness matrix K and force vector f.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"K, f = assemble_global(cellvalues, K, dh);\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"To account for the boundary conditions we use the apply! function. This modifies elements in K and f respectively, such that we can get the correct solution vector u by using \\.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"apply!(K, f, ch)\nu = K \\ f;\nnothing #hide","category":"page"},{"location":"tutorials/heat_equation/#Exporting-to-VTK","page":"Heat equation","title":"Exporting to VTK","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"To visualize the result we export the grid and our field u to a VTK-file, which can be viewed in e.g. ParaView.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"VTKGridFile(\"heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend","category":"page"},{"location":"tutorials/heat_equation/#heat_equation-plain-program","page":"Heat equation","title":"Plain program","text":"","category":"section"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"Here follows a version of the program without any comments. The file is also available here: heat_equation.jl.","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"using Ferrite, SparseArrays\n\ngrid = generate_grid(Quadrilateral, (20, 20));\n\nip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nK = allocate_matrix(dh)\n\nch = ConstraintHandler(dh);\n\n∂Ω = union(\n getfacetset(grid, \"left\"),\n getfacetset(grid, \"right\"),\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\n\ndbc = Dirichlet(:u, ∂Ω, (x, t) -> 0)\nadd!(ch, dbc);\n\nclose!(ch)\n\nfunction assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_global(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\n\nK, f = assemble_global(cellvalues, K, dh);\n\napply!(K, f, ch)\nu = K \\ f;\n\nVTKGridFile(\"heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"","category":"page"},{"location":"tutorials/heat_equation/","page":"Heat equation","title":"Heat equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/assembly/#man-assembly","page":"Assembly","title":"Assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"When the local stiffness matrix and force vector have been calculated they should be assembled into the global stiffness matrix and the global force vector. This is just a matter of adding the local matrix and vector to the global one, at the correct place. Consider e.g. assembling the local stiffness matrix ke and the local force vector fe into the global K and f respectively. These should be assembled into the row/column which corresponds to the degrees of freedom for the cell:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K[celldofs, celldofs] += ke\nf[celldofs] += fe","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"where celldofs is the vector containing the degrees of freedom for the cell. The method above is very inefficient – it is especially costly to index into the sparse matrix K directly (see Comparison of assembly strategies for details). Therefore we will instead use an Assembler that will help with the assembling of both the global stiffness matrix and the global force vector. It is also often convenient to create the sparse matrix just once, and reuse the allocated matrix. This is useful for e.g. iterative solvers or time dependent problems where the sparse matrix structure, or Sparsity Pattern will stay the same in every iteration/time step.","category":"page"},{"location":"topics/assembly/#Assembler","page":"Assembly","title":"Assembler","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Assembling efficiently into the sparse matrix requires some extra workspace. This workspace is allocated in an Assembler. start_assemble is used to create an Assembler:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"A = start_assemble(K)\nA = start_assemble(K, f)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"where K is the global stiffness matrix, and f the global force vector. It is optional to pass the force vector to the assembler – sometimes there is no need to assemble a global force vector.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The assemble! function is used to assemble element contributions to the assembler. For example, to assemble the element tangent stiffness ke and the element force vector fe to the assembler A, the following code can be used:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"assemble!(A, celldofs, ke)\nassemble!(A, celldofs, ke, fe)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"which perform the following operations in an efficient manner:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K[celldofs, celldofs] += ke\nf[celldofs] += fe","category":"page"},{"location":"topics/assembly/#Pseudo-code-for-efficient-assembly","page":"Assembly","title":"Pseudo-code for efficient assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Quite often the same sparsity pattern can be reused multiple times. For example:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"For time-dependent problems the pattern can be reused for all timesteps\nFor non-linear problems the pattern can be reused for all iterations","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"In such cases it is enough to construct the global matrix K once. Below is some pseudo-code for how to do this for a time-dependent problem:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K = allocate_matrix(dh)\nf = zeros(ndofs(dh))\n\nfor t in 1:timesteps\n A = start_assemble(K, f) # start_assemble zeroes K and f\n for cell in CellIterator(dh)\n ke, fe = element_routine(...)\n assemble!(A, celldofs(cell), ke, fe)\n end\n # Apply boundary conditions and solve for u(t)\n # ...\nend","category":"page"},{"location":"topics/assembly/#Comparison-of-assembly-strategies","page":"Assembly","title":"Comparison of assembly strategies","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"As discussed above there are various ways to assemble the local matrix into the global one. In particular, it was mentioned that naive indexing is very inefficient and that using an assembler is faster. To put some concrete numbers to these statements we will compare some strategies in this section. First we compare just a single assembly operation (e.g. assembling an already computed local matrix) and then to relate this to a more realistic scenario we compare the full matrix assembly including the integration of all the elements.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"note: Pre-allocated global matrix\nAll strategies that we compare below uses a pre-allocated global matrix K with the correct sparsity pattern. Starting with something like K = spzeros(ndofs(dh), ndofs(dh)) and then inserting entries is excruciatingly slow due to the sparse data structure so this method is not even considered.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"For the comparison we need a representative global matrix to assemble into. In the following setup code we create a grid with triangles and a DofHandler with a quadratic scalar field. From this we instantiate the global matrix.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"using Ferrite\n\n# Quadratic scalar interpolation\nip = Lagrange{RefTriangle, 2}()\n\n# DofHandler\nconst N = 100\ngrid = generate_grid(Triangle, (N, N))\nconst dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh)\n\n# Global matrix and a corresponding assembler\nconst K = allocate_matrix(dh)\nnothing # hide","category":"page"},{"location":"topics/assembly/#Strategy-1:-matrix-indexing","page":"Assembly","title":"Strategy 1: matrix indexing","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The first strategy is to index directly, using the vector of global dofs, into the global matrix:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v1(_, K, dofs, Ke)\n K[dofs, dofs] += Ke\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"This looks very simple, but it is very inefficient (as the numbers will show later). To understand why the operation K[dofs, dofs] += Ke (with K being a sparse matrix) is so slow we can dig into the details.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"In Julia there is no \"+=\"-operation and so x += y is identical to x = x + y. Translating this to our example we have","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"K[dofs, dofs] = K[dofs, dofs] + Ke","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We can break down this a bit further into these equivalent three steps:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"tmp1 = K[dofs, dofs] # 1\ntmp2 = tmp1 + Ke # 2\nK[dofs, dofs] = tmp2 # 3","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Now the problem with this strategy becomes a bit more obvious:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"In line 1 there is first an allocation of a new matrix (tmp1) followed by indexing into K to copy elements from K to tmp1. Both of these operations are rather costly: allocations should always be minimized in tight loops, and indexing into a sparse matrix is non-trivial due to the data structure. In addition, since the dofs vector contains the global indices (which are neither sorted nor consecutive) we have a random access pattern.\nIn line 2 there is another allocation of a matrix (tmp2) for the result of the addition of tmp1 and Ke.\nIn line 3 we again need to index into the sparse matrix to copy over the elements from tmp2 to K. This essentially duplicates the indexing effort from line 1 since we need to lookup the same locations in K again.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"note: Broadcasting\nUsing broadcasting, e.g. K[dofs, dofs] .+= Ke is an alternative to the above, and resembles a +=-operation. In theory this should be as efficient as the explicit loop presented in the next section.","category":"page"},{"location":"topics/assembly/#Strategy-2:-scalar-indexing","page":"Assembly","title":"Strategy 2: scalar indexing","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"A variant of the first strategy is to explicitly loop over the indices and add the elements individually as scalars:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v2(_, K, dofs, Ke)\n for (i, I) in pairs(dofs)\n for (j, J) in pairs(dofs)\n K[I, J] += Ke[i, j]\n end\n end\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The core operation, K[I, J] += Ke[i, j], can still be broken down into three equivalent steps:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"tmp1 = K[I, J]\ntmp2 = tmp1 + Ke[i, j]\nK[I, J] = tmp2","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The key difference here is that we index using integers (I, J, i, and j) which means that tmp1 and tmp2 are scalars which don't need to be allocated on the heap. This stragety thus eliminates all allocations that were present in the first strategy. However, we still lookup the same location in K twice, and we still have a random access pattern.","category":"page"},{"location":"topics/assembly/#Strategy-3:-scalar-indexing-with-single-lookup","page":"Assembly","title":"Strategy 3: scalar indexing with single lookup","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"To improve on the second strategy we will get rid of the double lookup into the sparse matrix K. While Julia doesn't have a \"+=\"-operation, Ferrite has an internal addindex!-function which does exactly what we want: it adds a value to a specific location in a sparse matrix using a single lookup.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v3(_, K, dofs, Ke)\n for (i, I) in pairs(dofs)\n for (j, J) in pairs(dofs)\n Ferrite.addindex!(K, Ke[i, j], I, J)\n end\n end\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"With this method we remove the double lookup, but the issue of random access patterns still remains.","category":"page"},{"location":"topics/assembly/#Strategy-4:-using-an-assembler","page":"Assembly","title":"Strategy 4: using an assembler","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Finally, the last strategy we consider uses an assembler. The assembler is a specific datastructure that pre-allocates some workspace to make the assembly more efficient:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_v4(assembler, _, dofs, Ke)\n assemble!(assembler, dofs, Ke)\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The extra workspace inside the assembler is used to sort the dofs when assemble! is called. After sorting it is possible to loop over the sparse matrix data structure and insert all elements of Ke in one go instead of having to lookup locations randomly.","category":"page"},{"location":"topics/assembly/#Single-element-assembly","page":"Assembly","title":"Single element assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"First we will compare the four functions above for a single assembly operation, i.e. inserting one local matrix into the global matrix. For this we simply create a random local matrix since we are not conserned with the actual values. We also pick the \"middle\" element and extract the dofs for that element. Finally, an assembler is created with start_assemble to use with the fourth strategy.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"dofs_per_cell = ndofs_per_cell(dh)\nconst Ke = rand(dofs_per_cell, dofs_per_cell)\nconst dofs = celldofs(dh, N * N ÷ 2)\n\nconst assembler = start_assemble(K)\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We use BenchmarkTools to measure the performance:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"assemble_v1(assembler, K, dofs, Ke) # hide\nassemble_v2(assembler, K, dofs, Ke) # hide\nassemble_v3(assembler, K, dofs, Ke) # hide\nassemble_v4(assembler, K, dofs, Ke) # hide\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"using BenchmarkTools\n\n@btime assemble_v1(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)\n@btime assemble_v2(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)\n@btime assemble_v3(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)\n@btime assemble_v4(assembler, K, dofs, Ke) evals = 10 setup = fill!(K, 0)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The results below are obtained on an Macbook Pro with an Apple M3 CPU.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"606.438 μs (36 allocations: 7.67 MiB)\n283.300 ns (0 allocations: 0 bytes)\n158.300 ns (0 allocations: 0 bytes)\n 83.400 ns (0 allocations: 0 bytes)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The results match what we expect based on the explanations above:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Between strategy 1 and 2 we got rid of the allocations completely and decreased the time with a factor of 2100(!).\nBetween strategy 2 and 3 we got rid of the double lookup and decreased the time with another factor of almost 2.\nBetween strategy 3 and 4 we got rid of the random lookup order and decreased the time with another factor of almost 2.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"The most important thing for this benchmark is to get rid of the allocations. By using an assembler instead of doing the naive thing we reduce the runtime with a factor of more than 7000(!!) in total.","category":"page"},{"location":"topics/assembly/#Full-system-assembly","page":"Assembly","title":"Full system assembly","text":"","category":"section"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We will now compare the four strategies in a more realistic scenario where we assemble all elements. This is to put the assembly performance in relation to other operations in the finite element program. After all, assembly performance might not matter in the end if other things dominate the runtime anyway.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"For this comparison we simply consider the heat equation (see Tutorial 1: Heat equation) and assemble the global matrix.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"function assemble_system!(assembler_function::F, K, dh, cv) where {F}\n assembler = start_assemble(K)\n ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n n = getnbasefunctions(cv)\n for cell in CellIterator(dh)\n reinit!(cv, cell)\n ke .= 0\n for qp in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, qp)\n for i in 1:n\n ∇ϕi = shape_gradient(cv, qp, i)\n for j in 1:n\n ∇ϕj = shape_gradient(cv, qp, j)\n ke[i, j] += ( ∇ϕi ⋅ ∇ϕj ) * dΩ\n end\n end\n end\n assembler_function(assembler, K, celldofs(cell), ke)\n end\n return\nend\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"Finally, we need cellvalues for the field in order to perform the integration:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"qr = QuadratureRule{RefTriangle}(2)\nconst cellvalues = CellValues(qr, ip)\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We can now time the four assembly strategies:","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"res = 1138.8803468514259 # hide\n# assemble_system!(assemble_v1, K, dh, cellvalues) # hide\n# @assert norm(K.nzval) ≈ res # hide\nassemble_system!(assemble_v2, K, dh, cellvalues) # hide\n@assert norm(K.nzval) ≈ res # hide\nassemble_system!(assemble_v3, K, dh, cellvalues) # hide\n@assert norm(K.nzval) ≈ res # hide\nassemble_system!(assemble_v4, K, dh, cellvalues) # hide\n@assert norm(K.nzval) ≈ res # hide\nnothing # hide","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"@time assemble_system!(assemble_v1, K, dh, cellvalues)\n@time assemble_system!(assemble_v2, K, dh, cellvalues)\n@time assemble_system!(assemble_v3, K, dh, cellvalues)\n@time assemble_system!(assemble_v4, K, dh, cellvalues)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"We then obtain the following results (running on the same machine as above):","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"12.175625 seconds (719.99 k allocations: 149.809 GiB, 11.59% gc time)\n 0.009313 seconds (8 allocations: 928 bytes)\n 0.006055 seconds (8 allocations: 928 bytes)\n 0.004530 seconds (10 allocations: 1.062 KiB)","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"This follows the same trend as for the benchmarks for individual cell assembly and shows that the efficiency of the assembly strategy is crucial for the overall performance of the program. In particular this benchmark shows that allocations in such a tight loop from the first strategy is very costly and puts a strain on the garbage collector: 11% of the time is spent in GC instead of crunching numbers.","category":"page"},{"location":"topics/assembly/","page":"Assembly","title":"Assembly","text":"It should of course be noted that the more expensive the element routine is, the less the performance of the assembly strategy matters for the total runtime. However, there are no reason not to use the fastest method given that it is readily available in Ferrite.","category":"page"},{"location":"devdocs/elements/#devdocs-elements","page":"Elements and cells","title":"Elements and cells","text":"","category":"section"},{"location":"devdocs/elements/#Type-definitions","page":"Elements and cells","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Elements or cells are subtypes of AbstractCell{<:AbstractRefShape}. As shown, they are parametrized by the associated reference element.","category":"page"},{"location":"devdocs/elements/#Required-methods-to-implement-for-all-subtypes-of-AbstractCell-to-define-a-new-element","page":"Elements and cells","title":"Required methods to implement for all subtypes of AbstractCell to define a new element","text":"","category":"section"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Ferrite.get_node_ids","category":"page"},{"location":"devdocs/elements/#Ferrite.get_node_ids","page":"Elements and cells","title":"Ferrite.get_node_ids","text":"Ferrite.get_node_ids(c::AbstractCell)\n\nReturn the node id's for cell c in the order determined by the cell's reference cell.\n\nDefault implementation: c.nodes.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Common-utilities-and-definitions-when-working-with-grids-internally.","page":"Elements and cells","title":"Common utilities and definitions when working with grids internally.","text":"","category":"section"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"First we have some topological queries on the element","category":"page"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Ferrite.vertices(::Ferrite.AbstractCell)\nFerrite.edges(::Ferrite.AbstractCell)\nFerrite.faces(::Ferrite.AbstractCell)\nFerrite.facets(::Ferrite.AbstractCell)\nFerrite.boundaryfunction(::Type{<:Ferrite.BoundaryIndex})\nFerrite.reference_vertices(::Ferrite.AbstractCell)\nFerrite.reference_edges(::Ferrite.AbstractCell)\nFerrite.reference_faces(::Ferrite.AbstractCell)","category":"page"},{"location":"devdocs/elements/#Ferrite.vertices-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.vertices","text":"Ferrite.vertices(::AbstractCell)\n\nReturns a tuple with the node indices (of the nodes in a grid) for each vertex in a given cell. This function induces the VertexIndex, where the second index corresponds to the local index into this tuple.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.edges-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.edges","text":"Ferrite.edges(::AbstractCell)\n\nReturns a tuple of 2-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented edge. This function induces the EdgeIndex, where the second index corresponds to the local index into this tuple.\n\nNote that the vertices are sufficient to define an edge uniquely.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.faces-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.faces","text":"Ferrite.faces(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented face. This function induces the FaceIndex, where the second index corresponds to the local index into this tuple.\n\nAn oriented face is a face with the first node having the local index and the other nodes spanning such that the normal to the face is pointing outwards.\n\nNote that the vertices are sufficient to define a face uniquely.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.facets-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.facets","text":"Ferrite.facets(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered node indices (of the nodes in a grid) corresponding to the vertices that define an oriented facet. This function induces the FacetIndex, where the second index corresponds to the local index into this tuple.\n\nSee also vertices, edges, and faces\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.boundaryfunction-Tuple{Type{<:Ferrite.BoundaryIndex}}","page":"Elements and cells","title":"Ferrite.boundaryfunction","text":"boundaryfunction(::Type{<:BoundaryIndex})\n\nHelper function to dispatch on the correct entity from a given boundary index.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.reference_vertices-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.reference_vertices","text":"reference_vertices(::Type{<:AbstractRefShape})\nreference_vertices(::AbstractCell)\n\nReturns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.reference_edges-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.reference_edges","text":"reference_edges(::Type{<:AbstractRefShape})\nreference_edges(::AbstractCell)\n\nReturns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.reference_faces-Tuple{Ferrite.AbstractCell}","page":"Elements and cells","title":"Ferrite.reference_faces","text":"reference_faces(::Type{<:AbstractRefShape})\nreference_faces(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"and some generic utils which are commonly found in finite element codes","category":"page"},{"location":"devdocs/elements/","page":"Elements and cells","title":"Elements and cells","text":"Ferrite.BoundaryIndex\nFerrite.get_coordinate_eltype(::Ferrite.AbstractGrid)\nFerrite.get_coordinate_eltype(::Node)\nFerrite.toglobal\nFerrite.sortface\nFerrite.sortface_fast\nFerrite.sortedge\nFerrite.sortedge_fast\nFerrite.element_to_facet_transformation\nFerrite.facet_to_element_transformation\nFerrite.InterfaceOrientationInfo\nFerrite.transform_interface_points!\nFerrite.get_transformation_matrix","category":"page"},{"location":"devdocs/elements/#Ferrite.BoundaryIndex","page":"Elements and cells","title":"Ferrite.BoundaryIndex","text":"Abstract type which is used as identifier for faces, edges and verices\n\n\n\n\n\n","category":"type"},{"location":"devdocs/elements/#Ferrite.get_coordinate_eltype-Tuple{Ferrite.AbstractGrid}","page":"Elements and cells","title":"Ferrite.get_coordinate_eltype","text":"Return the number type of the nodal coordinates.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.get_coordinate_eltype-Tuple{Node}","page":"Elements and cells","title":"Ferrite.get_coordinate_eltype","text":"get_coordinate_eltype(::Node)\n\nGet the data type of the components of the nodes coordinate.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/elements/#Ferrite.toglobal","page":"Elements and cells","title":"Ferrite.toglobal","text":"toglobal(grid::AbstractGrid, vertexidx::VertexIndex) -> Int\ntoglobal(grid::AbstractGrid, vertexidx::Vector{VertexIndex}) -> Vector{Int}\n\nThis function takes the local vertex representation (a VertexIndex) and looks up the unique global id (an Int).\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortface","page":"Elements and cells","title":"Ferrite.sortface","text":"sortface(face::Tuple{Int})\nsortface(face::Tuple{Int,Int})\nsortface(face::Tuple{Int,Int,Int})\nsortface(face::Tuple{Int,Int,Int,Int})\n\nReturns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortface_fast","page":"Elements and cells","title":"Ferrite.sortface_fast","text":"sortface_fast(face::Tuple{Int})\nsortface_fast(face::Tuple{Int,Int})\nsortface_fast(face::Tuple{Int,Int,Int})\nsortface_fast(face::Tuple{Int,Int,Int,Int})\n\nReturns the unique representation of a face. Here the unique representation is the sorted node index tuple. Note that in 3D we only need indices to uniquely identify a face, so the unique representation is always a tuple length 3.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortedge","page":"Elements and cells","title":"Ferrite.sortedge","text":"sortedge(edge::Tuple{Int,Int})\n\nReturns the unique representation of an edge and its orientation. Here the unique representation is the sorted node index tuple. The orientation is true if the edge is not flipped, where it is false if the edge is flipped.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.sortedge_fast","page":"Elements and cells","title":"Ferrite.sortedge_fast","text":"sortedge_fast(edge::Tuple{Int,Int})\n\nReturns the unique representation of an edge. Here the unique representation is the sorted node index tuple.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.element_to_facet_transformation","page":"Elements and cells","title":"Ferrite.element_to_facet_transformation","text":"element_to_facet_transformation(point::AbstractVector, ::Type{<:AbstractRefShape}, facet::Int)\n\nTransform quadrature point from the cell's coordinates to the facet's reference coordinates, decreasing the number of dimensions by one. This is the inverse of facet_to_element_transformation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.facet_to_element_transformation","page":"Elements and cells","title":"Ferrite.facet_to_element_transformation","text":"facet_to_element_transformation(point::Vec, ::Type{<:AbstractRefShape}, facet::Int)\n\nTransform quadrature point from the facet's reference coordinates to coordinates on the cell's facet, increasing the number of dimensions by one.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.InterfaceOrientationInfo","page":"Elements and cells","title":"Ferrite.InterfaceOrientationInfo","text":"InterfaceOrientationInfo\n\nRelative orientation information for 1D and 2D interfaces in 2D and 3D elements respectively. This information is used to construct the transformation matrix to transform the quadrature points from faceta to facetb achieving synced spatial coordinates. Face B's orientation relative to Face A's can possibly be flipped (i.e. the vertices indices order is reversed) and the vertices can be rotated against each other. The reference orientation of face B is such that the first node has the lowest vertex index. Thus, this structure also stores the shift of the lowest vertex index which is used to reorient the face in case of flipping transform_interface_points!.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/elements/#Ferrite.transform_interface_points!","page":"Elements and cells","title":"Ferrite.transform_interface_points!","text":"transform_interface_points!(dst::AbstractVector{Vec{3, Float64}}, points::AbstractVector{Vec{3, Float64}}, interface_transformation::InterfaceOrientationInfo)\n\nTransform the points from face A to face B using the orientation information of the interface and store it in the vector dst. For 3D, the faces are transformed into regular polygons such that the rotation angle is the shift in reference node index × 2π ÷ number of edges in face. If the face is flipped then the flipping is about the axis that preserves the position of the first node (which is the reference node after being rotated to be in the first position, it's rotated back in the opposite direction after flipping). Take for example the interface\n\n 2 3\n | \\ | \\\n | \\ | \\\ny | A \\ | B \\\n↑ | \\ | \\\n→ x 1-----3 1-----2\n\nTransforming A to an equilateral triangle and translating it such that {0,0} is equidistant to all nodes\n\n 3\n +\n / \\\n / \\\n / x \\\n / ↑ \\\n / ← \\\n / y \\\n2+-------------+1\n\nRotating it -270° (or 120°) such that the reference node (the node with the smallest index) is at index 1\n\n 1\n +\n / \\\n / \\\n / x \\\n / ↑ \\\n / ← \\\n / y \\\n3+-------------+2\n\nFlipping about the x axis (such that the position of the reference node doesn't change) and rotating 270° (or -120°)\n\n 2\n +\n / \\\n / \\\n / x \\\n / ↑ \\\n / ← \\\n / y \\\n3+-------------+1\n\nTransforming back to triangle B\n\n 3\n | \\\n | \\\ny | \\\n↑ | \\\n→ x 1-----2\n\n\n\n\n\n","category":"function"},{"location":"devdocs/elements/#Ferrite.get_transformation_matrix","page":"Elements and cells","title":"Ferrite.get_transformation_matrix","text":"get_transformation_matrix(interface_transformation::InterfaceOrientationInfo)\n\nReturns the transformation matrix corresponding to the interface orientation information stored in InterfaceOrientationInfo. The transformation matrix is constructed using a combination of affine transformations defined for each interface reference shape. The transformation for a flipped face is a function of both relative orientation and the orientation of the second face. If the face is not flipped then the transformation is a function of relative orientation only.\n\n\n\n\n\n","category":"function"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"EditURL = \"https://github.com/Ferrite-FEM/Ferrite.jl/blob/master/CHANGELOG.md\"","category":"page"},{"location":"changelog/#Ferrite-changelog","page":"Changelog","title":"Ferrite changelog","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"All notable changes to this project will be documented in this file.","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.","category":"page"},{"location":"changelog/#[Unreleased]","page":"Changelog","title":"[Unreleased]","text":"","category":"section"},{"location":"changelog/#Removed","page":"Changelog","title":"Removed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The deprecated third type parameter for interpolations have been removed. Old code which tries to use three parameters will now throw the somewhat cryptic error:\njulia> Lagrange{2, RefCube, 1}()\nERROR: too many parameters for type\n(#1083)","category":"page"},{"location":"changelog/#[v1.0.0](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v1.0.0)-2024-09-30","page":"Changelog","title":"v1.0.0 - 2024-09-30","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite version 1.0 is a relatively large release, with a lot of new features, improvements, deprecations and some removals. These changes are made to make the code base more consistent and more suitable for future improvements. With this 1.0 release we are aiming for long time stability, and there is no breaking release 2.0 on the horizon.","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Unfortunately this means that code written for Ferrite version 0.3 will have to be updated. All changes, with upgrade paths, are listed in the sections below. Since these sections include a lot of other information as well (new features, internal changes, ...) there is also a dedicated section about upgrading code from Ferrite 0.3 to 1.0 (see below) which include the most common changes that are required. In addition, in all cases where possible, you will be presented with a descriptive error message telling you what needs to change.","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Deprecations for 1.0 will be removed during the 1.x release series. When upgrading old code it is therefore recommended to use Ferrite 1.0 as a first stepping stone since this release contain descriptive deprecation error messages that might not exist in e.g. Ferrite version 1.2.","category":"page"},{"location":"changelog/#Upgrading-code-from-Ferrite-0.3-to-1.0","page":"Changelog","title":"Upgrading code from Ferrite 0.3 to 1.0","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"This section give a short overview of the most common required changes. More details and motivation are included in the following sections (with links to issues/pull request for more discussion).","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Interpolations: remove the first parameter (the reference dimension) and use new reference shapes.\nExamples:\n# Linear Lagrange interpolation for a line\n- Lagrange{1, RefCube, 1}()\n+ Lagrange{RefLine, 1}()\n\n# Linear Lagrange interpolation for a quadrilateral\n- Lagrange{2, RefCube, 1}()\n+ Lagrange{RefQuadrilateral, 1}()\n\n# Quadratic Lagrange interpolation for a triangle\n- Lagrange{2, RefTetrahedron, 2}()\n+ Lagrange{RefTriangle, 2}()\nFor vector valued problems it is now required to explicitly vectorize the interpolation using the new VectorizedInterpolation. This is required when passing the interpolation to CellValues and when adding fields to the DofHandler using add!. In both of these places the interpolation was implicitly vectorized in Ferrite 0.3.\nExamples:\n# Linear Lagrange interpolation for a vector problem on the triangle (vector dimension\n# same as the reference dimension)\nip_scalar = Lagrange{RefTriangle, 1}()\nip_vector = ip_scalar ^ 2 # or VectorizedInterpolation{2}(ip_scalar)\nQuadrature: remove the first parameter (the reference dimension) and use new reference shapes.\nExamples:\n# Quadrature for a line\n- QuadratureRule{1, RefCube}(quadrature_order)\n+ QuadratureRule{RefLine}(quadrature_order)\n\n# Quadrature for a quadrilateral\n- QuadratureRule{2, RefCube}(quadrature_order)\n+ QuadratureRule{RefQuadrilateral}(quadrature_order)\n\n# Quadrature for a tetrahedron\n- QuadratureRule{3, RefTetrahedron}(quadrature_order)\n+ QuadratureRule{RefTetrahedron}(quadrature_order)\nQuadrature for face integration (FacetValues): replace QuadratureRule{dim-1, reference_shape}(quadrature_order) with FacetQuadratureRule{reference_shape}(quadrature_order).\nExamples:\n# Quadrature for the facets of a quadrilateral\n- QuadratureRule{1, RefCube}(quadrature_order)\n+ FacetQuadratureRule{RefQuadrilateral}(quadrature_order)\n\n# Quadrature for the facets of a triangle\n- QuadratureRule{1, RefTetrahedron}(quadrature_order)\n+ FacetQuadratureRule{RefTriangle}(quadrature_order)\n\n# Quadrature for the facets of a hexhedron\n- QuadratureRule{2, RefCube}(quadrature_order)\n+ FacetQuadratureRule{RefHexahedron}(quadrature_order)\nCellValues: replace usage of CellScalarValues and CellVectorValues with CellValues. For vector valued problems the interpolation passed to CellValues should be vectorized to a VectorizedInterpolation (see above).\nExamples:\n# CellValues for a scalar problem with triangle elements\n- qr = QuadratureRule{2, RefTetrahedron}(quadrature_order)\n- ip = Lagrange{2, RefTetrahedron, 1}()\n- cv = CellScalarValues(qr, ip)\n+ qr = QuadratureRule{RefTriangle}(quadrature_order)\n+ ip = Lagrange{RefTriangle, 1}()\n+ cv = CellValues(qr, ip)\n\n# CellValues for a vector problem with hexahedronal elements\n- qr = QuadratureRule{3, RefCube}(quadrature_order)\n- ip = Lagrange{3, RefCube, 1}()\n- cv = CellVectorValues(qr, ip)\n+ qr = QuadratureRule{RefHexahedron}(quadrature_order)\n+ ip = Lagrange{RefHexahedron, 1}() ^ 3\n+ cv = CellValues(qr, ip)\nIf you use CellScalarValues or CellVectorValues in method signature you must replace them with CellValues. Note that the type parameters are different.\nExamples:\n- function do_something(cvs::CellScalarValues, cvv::CellVectorValues)\n+ function do_something(cvs::CellValues, cvv::CellValues)\nThe default geometric interpolation have changed from the function interpolation to always use linear Lagrange interpolation. If you use linear elements in the grid, and a higher order interpolation for the function you can now rely on the new default:\nqr = QuadratureRule(...)\n- ip_function = Lagrange{2, RefTetrahedron, 2}()\n- ip_geometry = Lagrange{2, RefTetrahedron, 1}()\n- cv = CellScalarValues(qr, ip_function, ip_geometry)\n+ ip_function = Lagrange{2, RefTetrahedron, 2}()\n+ cv = CellValues(qr, ip_function)\nand if you have quadratic (or higher order) elements in the grid you must now pass the corresponding interpolation to the constructor:\nqr = QuadratureRule(...)\n- ip_function = Lagrange{2, RefTetrahedron, 2}()\n- cv = CellScalarValues(qr, ip_function)\n+ ip_function = Lagrange{2, RefTetrahedron, 2}()\n+ ip_geometry = Lagrange{2, RefTetrahedron, 1}()\n+ cv = CellValues(qr, ip_function, ip_geometry)\nFacetValues: replace usage of FaceScalarValues and FaceVectorValues with FacetValues. For vector valued problems the interpolation passed to FacetValues should be vectorized to a VectorizedInterpolation (see above). The input quadrature rule should be a FacetQuadratureRule instead of a QuadratureRule.\nExamples:\n# FacetValues for a scalar problem with triangle elements\n- qr = QuadratureRule{1, RefTetrahedron}(quadrature_order)\n- ip = Lagrange{2, RefTetrahedron, 1}()\n- cv = FaceScalarValues(qr, ip)\n+ qr = FacetQuadratureRule{RefTriangle}(quadrature_order)\n+ ip = Lagrange{RefTriangle, 1}()\n+ cv = FacetValues(qr, ip)\n\n# FaceValues for a vector problem with hexahedronal elements\n- qr = QuadratureRule{2, RefCube}(quadrature_order)\n- ip = Lagrange{3, RefCube, 1}()\n- cv = FaceVectorValues(qr, ip)\n+ qr = FacetQuadratureRule{RefHexahedron}(quadrature_order)\n+ ip = Lagrange{RefHexahedron, 1}() ^ 3\n+ cv = FacetValues(qr, ip)\nDofHandler construction: it is now required to pass the interpolation explicitly when adding new fields using add! (previously it was optional, defaulting to the default interpolation of the elements in the grid). For vector-valued fields the interpolation should be vectorized, instead of passing the number of components to add! as an integer.\nExamples:\ndh = DofHandler(grid) # grid with triangles\n\n# Vector field :u\n- add!(dh, :u, 2)\n+ add!(dh, :u, Lagrange{RefTriangle, 1}()^2)\n\n# Scalar field :p\n- add!(dh, :u, 1)\n+ add!(dh, :u, Lagrange{RefTriangle, 1}())\nBoundary conditions: The entity enclosing a cell was previously called face, but is now denoted a facet. When applying boundary conditions, rename getfaceset to getfacetset and addfaceset! is now addfacetset!. These sets are now described by FacetIndex instead of FaceIndex. When looping over the facets of a cell, change nfaces to nfacets.\nExamples:\n# Dirichlet boundary conditions\n- addfaceset!(grid, \"dbc\", x -> x[1] ≈ 1.0)\n+ addfacetset!(grid, \"dbc\", x -> x[1] ≈ 1.0)\n\n- dbc = Dirichlet(:u, getfaceset(grid, \"dbc\"), Returns(0.0))\n+ dbc = Dirichlet(:u, getfacetset(grid, \"dbc\"), Returns(0.0))\n\n# Neumann boundary conditions\n- for facet in 1:nfaces(cell)\n- if (cellid(cell), facet) ∈ getfaceset(grid, \"Neumann Boundary\")\n+ for facet in 1:nfacets(cell)\n+ if (cellid(cell), facet) ∈ getfacetset(grid, \"Neumann Boundary\")\n # ...\nVTK Export: The VTK export has been changed #692.\n- vtk_grid(name, dh) do vtk\n- vtk_point_data(vtk, dh, a)\n- vtk_point_data(vtk, nodal_data, \"my node data\")\n- vtk_point_data(vtk, proj, projected_data, \"my projected data\")\n- vtk_cell_data(vtk, proj, projected_data, \"my projected data\")\n+ VTKGridFile(name, dh) do vtk\n+ write_solution(vtk, dh, a)\n+ write_node_data(vtk, nodal_data, \"my node data\")\n+ write_projection(vtk, proj, projected_data, \"my projected data\")\n+ write_cell_data(vtk, cell_data, \"my projected data\")\nend\nWhen using a paraview_collection collection for e.g. multiple timesteps the VTKGridFile object can be used instead of the previous type returned from vtk_grid.\nSparsity pattern and global matrix construction: since there is now explicit support for working with the sparsity pattern before instantiating a matrix the function create_sparsity_pattern has been removed. To recover the old functionality that return a sparse matrix from the DofHandler directly use allocate_matrix instead.\nExamples:\n# Create sparse matrix from DofHandler\n- K = create_sparsity_pattern(dh)\n+ K = allocate_matrix(dh)\n\n# Create condensed sparse matrix from DofHandler + ConstraintHandler\n- K = create_sparsity_pattern(dh, ch)\n+ K = allocate_matrix(dh, ch)","category":"page"},{"location":"changelog/#Added","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"InterfaceValues for computing jumps and averages over interfaces. (#743)\nInterfaceIterator and InterfaceCache for iterating over interfaces. (#747)\nFacetQuadratureRule implementation for RefPrism and RefPyramid. (#779)\nThe DofHandler now support selectively adding fields on sub-domains (rather than the full domain). This new functionality is included with the new SubDofHandler struct, which, as the name suggest, is a DofHandler for a subdomain. (#624, #667, #735)\nNew reference shape structs RefLine, RefTriangle, RefQuadrilateral, RefTetrahedron, RefHexahedron, and RefPrism have been added. These encode the reference dimension, and will thus replace the old reference shapes for which it was necessary to always pair with an explicit dimension (i.e. RefLine replaces (RefCube, 1), RefTriangle replaces (RefTetrahedron, 2), etc.). For writing \"dimension independent code\" it is possible to use Ferrite.RefHypercube{dim} and Ferrite.RefSimplex{dim}. (#679)\nNew methods for adding entitysets that are located on the boundary of the grid: addboundaryfacetset! and addboundaryvertexset!. These work similar to addfacetset! and addvertexset!, but filters out all instances not on the boundary (this can be used to avoid accidental inclusion of internal entities in sets used for boundary conditions, for example). (#606)\nNew interpolation VectorizedInterpolation which vectorizes scalar interpolations for vector-valued problems. A VectorizedInterpolation is created from a (scalar) interpolation ip using either ip ^ dim or VectorizedInterpolation{dim}(ip). For convenience, the method VectorizedInterpolation(ip) vectorizes the interpolation to the reference dimension of the interpolation. (#694, #736)\nNew (scalar) interpolation Lagrange{RefQuadrilateral, 3}(), i.e. third order Lagrange interpolation for 2D quadrilaterals. (#701, #731)\nCellValues now support embedded elements. Specifically you can now embed elements with reference dimension 1 into spatial dimension 2 or 3, and elements with reference dimension 2 in to spatial dimension 3. (#651)\nCellValues now support (vector) interpolations with dimension different from the spatial dimension. (#651)\nFacetQuadratureRule have been added and should be used for FacetValues. A FacetQuadratureRule for integration of the facets of e.g. a triangle can be constructed by FacetQuadratureRule{RefTriangle}(order) (similar to how QuadratureRule is constructed). (#716)\nNew functions Ferrite.reference_shape_value(::Interpolation, ξ::Vec, i::Int) and Ferrite.reference_shape_gradient(::Interpolation, ξ::Vec, i::Int) for evaluating the value/gradient of the ith shape function of an interpolation in local reference coordinate ξ. These methods are public but not exported. (Note that these methods return the value/gradient wrt. the reference coordinate ξ, whereas the corresponding methods for CellValues etc return the value/gradient wrt the spatial coordinate x.) (#721)\nFacetIterator and FacetCache have been added. These work similarly to CellIterator and CellCache but are used to iterate over (boundary) face sets instead. These simplify boundary integrals in general, and in particular Neumann boundary conditions are more convenient to implement now that you can loop directly over the face set instead of checking all faces of a cell inside the element routine. (#495)\nThe ConstraintHandler now support adding Dirichlet boundary conditions on discontinuous interpolations. (#729)\ncollect_periodic_faces now have a keyword argument tol that can be used to relax the default tolerance when necessary. (#749)\nVTK export now work with QuadraticHexahedron elements. (#714)\nThe function bounding_box(::AbstractGrid) has been added. It computes the bounding box for a given grid (based on its node coordinates), and returns the minimum and maximum vertices of the bounding box. (#880)\nSupport for working with sparsity patterns has been added. This means that Ferrite exposes the intermediate \"state\" between the DofHandler and the instantiated matrix as the new struct SparsityPattern. This make it possible to insert custom equations or couplings in the pattern before instantiating the matrix. The function create_sparsity_pattern have been removed. The new function allocate_matrix is instead used to instantiate the matrix. Refer to the documentation for more details. (#888)\nTo upgrade: if you want to recover the old functionality and don't need to work with the pattern, replace any usage of create_sparsity_pattern with allocate_matrix.\nA new function, geometric_interpolation, is exported, which gives the geometric interpolation for each cell type. This is equivalent to the deprecated Ferrite.default_interpolation function. (#953)\nCellValues and FacetValues can now store and map second order gradients (Hessians). The number of gradients computed in CellValues/FacetValues is specified using the keyword arguments update_gradients::Bool (default true) and update_hessians::Bool (default false) in the constructors, i.e. CellValues(...; update_hessians=true). (#953)\nL2Projector supports projecting on grids with mixed celltypes. (#949)","category":"page"},{"location":"changelog/#Changed","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"It is now possible to create sparsity patterns with interface couplings, see the new function add_interface_entries! and the rework of sparsity pattern construction. (#710)\nThe AbstractCell interface has been reworked. This change should not affect user code, but may in some cases be relevant for code parsing external mesh files. In particular, the generic Cell struct have been removed in favor of concrete cell implementations (Line, Triangle, ...). (#679, #712)\nTo upgrade replace any usage of Cell{...}(...) with calls to the concrete implementations.\nThe default geometric mapping in CellValues and FacetValues have changed. The new default is to always use Lagrange{refshape, 1}(), i.e. linear Lagrange polynomials, for the geometric interpolation. Previously, the function interpolation was (re) used also for the geometry interpolation. (#695)\nTo upgrade, if you relied on the previous default, simply pass the function interpolation also as the third argument (the geometric interpolation).\nAll interpolations are now categorized as either scalar or vector interpolations. All (previously) existing interpolations are scalar. (Scalar) interpolations must now be explicitly vectorized, using the new VectorizedInterpolation, when used for vector problems. (Previously implicit vectorization happened in the CellValues constructor, and when adding fields to the DofHandler). (#694)\nIt is now required to explicitly pass the interpolation to the DofHandler when adding a new field using add!. For vector fields the interpolation should be vectorized, instead of passing number of components as an integer. (#694)\nTo upgrade don't pass the dimension as an integer, and pass the interpolation explicitly. See more details in Upgrading code from Ferrite 0.3 to 1.0.\nInterpolations should now be constructed using the new reference shapes. Since the new reference shapes encode the reference dimension the first type parameter of interpolations have been removed. (#711) To upgrade replace e.g. Lagrange{1, RefCube, 1}() with Lagrange{RefLine, 1}(), and Lagrange{2, RefTetrahedron, 1}() with Lagrange{RefTriangle, 1}(), etc.\nQuadratureRules should now be constructed using the new reference shapes. Since the new reference shapes encode the reference dimension the first type parameter of QuadratureRule have been removed. (#711, #716) To upgrade replace e.g. QuadratureRule{1, RefCube}(order) with QuadratureRule{RefLine}(order), and QuadratureRule{2, RefTetrahedron}(1) with Lagrange{RefTriangle}(order), etc.\nCellScalarValues and CellVectorValues have been merged into CellValues, FaceScalarValues and FaceVectorValues have been merged into FacetValues, and PointScalarValues and PointVectorValues have been merged into PointValues. The differentiation between scalar and vector have thus been moved to the interpolation (see above). Note that previously CellValues, FaceValues, and PointValues where abstract types, but they are now concrete implementations with different type parameters, except FaceValues which is now FacetValues (#708) To upgrade, for scalar problems, it is enough to replace CellScalarValues with CellValues, FaceScalarValues with FacetValues and PointScalarValues with PointValues, respectively. For vector problems, make sure to vectorize the interpolation (see above) and then replace CellVectorValues with CellValues, FaceVectorValues with FacetValues, and PointVectorValues with PointValues.\nThe quadrature rule passed to FacetValues should now be of type FacetQuadratureRule rather than of type QuadratureRule. (#716) To upgrade replace the quadrature rule passed to FacetValues with a FacetQuadratureRule.\nChecking if a face (ele_id, local_face_id) ∈ faceset has been previously implemented by type piracy. In order to be invariant to the underlying Set datatype as well as omitting type piracy, (#835) implemented isequal and hash for BoundaryIndex datatypes.\nVTK export: Ferrite no longer extends WriteVTK.vtk_grid and associated functions, instead the new type VTKGridFile should be used instead. New methods exists for writing to a VTKGridFile, e.g. write_solution, write_cell_data, write_node_data, and write_projection. See #692.\nDefinitions: Previously, face and edge referred to codimension 1 relative reference shape. In Ferrite v1, volume, face, edge, and vertex refer to 3, 2, 1, and 0 dimensional entities, and facet replaces the old definition of face. No direct replacement for edges exits. See #914 and #914. The main implications of this change are\nFaceIndex -> FacetIndex (FaceIndex still exists, but has a different meaning)\nFaceValues -> FacetValues\nnfaces -> nfacets (nfaces is now an internal method with different meaning)\naddfaceset! -> addfacetset\ngetfaceset -> getfacetset\nFurthermore, subtypes of Interpolation should now define vertexdof_indices, edgedof_indices, facedof_indices, volumedof_indices (and similar) according to these definitions.\nFerrite.getdim has been changed into Ferrite.getrefdim for getting the dimension of the reference shape and Ferrite.getspatialdim to get the spatial dimension (of the grid). (#943)\nFerrite.getfielddim(::AbstractDofHandler, args...) has been renamed to Ferrite.n_components. (#943)\nThe constructor for ExclusiveTopology only accept an AbstractGrid as input, removing the alternative of providing a Vector{<:AbstractCell}, as knowing the spatial dimension is required for correct code paths. Furthermore, it uses a new internal data structure, ArrayOfVectorViews, to store the neighborhood information more efficiently The datatype for the neighborhood has thus changed to a view of a vector, instead of the now removed EntityNeighborhood container. This also applies to vertex_star_stencils. (#974).\nproject(::L2Projector, data, qr_rhs) now expects data to be indexed by the cellid, as opposed to the index in the vector of cellids passed to the L2Projector. The data may be passed as an AbstractDict{Int, <:AbstractVector}, as an alternative to AbstractArray{<:AbstractVector}. (#949)","category":"page"},{"location":"changelog/#Deprecated","page":"Changelog","title":"Deprecated","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The rarely (if ever) used methods of function_value, function_gradient, function_divergence, and function_curl taking vectorized dof values as in put have been deprecated. (#698)\nThe function reshape_to_nodes have been deprecated in favor of evaluate_at_grid_nodes. (#703)\nstart_assemble([n::Int]) has been deprecated in favor of calling Ferrite.COOAssembler() directly (#916, #1058).\nstart_assemble(f, K) have been deprecated in favor of the \"canonical\" start_assemble(K, f). (#707)\nassemble!(assembler, dofs, fe, Ke) have been deprecated in favor of the \"canonical\" assemble!(assembler, dofs, Ke, fe). (#1059)\nend_assemble have been deprecated in favor of finish_assemble. (#754)\nget_point_values have been deprecated in favor of evaluate_at_points. (#754)\ntransform! have been deprecated in favor of transform_coordinates!. (#754)\nFerrite.default_interpolation has been deprecated in favor of geometric_interpolation. (#953)","category":"page"},{"location":"changelog/#Removed-2","page":"Changelog","title":"Removed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"MixedDofHandler + FieldHandler have been removed in favor of DofHandler + SubDofHandler. Note that the syntax has changed, and note that SubDofHandler is much more capable compared to FieldHandler. Previously it was often required to pass both the MixedDofHandler and the FieldHandler to e.g. the assembly routine, but now it is enough to pass the SubDofHandler since it can be used for e.g. DoF queries etc. (#624, #667, #735)\nSome old methods to construct the L2Projector have been removed after being deprecated for several releases. (#697)\nThe option project_to_nodes have been removed from project(::L2Projector, ...). The returned values are now always ordered according to the projectors internal DofHandler. (#699)\nThe function compute_vertex_values have been removed. (#700)\nThe names getweights, getpoints, getcellsets, getnodesets, getfacesets, getedgesets, and getvertexsets have been removed from the list of exported names. (For now you can still use them by prefixing Ferrite., e.g. Ferrite.getweights.) (#754)\nThe onboundary function (and the associated boundary_matrix property of the Grid datastructure) have been removed (#924). Instead of first checking onboundary and then check whether a facet belong to a specific facetset, check the facetset directly. For example:\n- if onboundary(cell, local_face_id) && (cell_id, local_face_id) in getfacesets(grid, \"traction_boundary\")\n+ if (cell_id, local_face_id) in getfacesets(grid, \"traction_boundary\")\n # integrate the \"traction_boundary\" boundary\n end","category":"page"},{"location":"changelog/#Fixed","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Benchmarks now work with master branch. (#751, #855)\nTopology construction have been generalized to, in particular, fix construction for 1D and for wedge elements. (#641, #670, #684)","category":"page"},{"location":"changelog/#Other-improvements","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Documentation:\nThe documentation is now structured according to the Diataxis framework. There is now also clear separation between tutorials (for teaching) and code gallery (for showing off). (#737, #756)\nNew section in the developer documentation that describes the (new) reference shapes and their numbering scheme. (#688)\nPerformance:\nFerrite.transform!(grid, f) (for transforming the node coordinates in the grid according to a function f) is now faster and allocates less. (#675)\nSlight performance improvement in construction of PointEvalHandler (faster reverse coordinate lookup). (#719)\nVarious performance improvements to topology construction. (#753, #759)\nInternal improvements:\nThe dof distribution interface have been updated to support higher order elements (future work). (#627, #732, #733)\nThe AbstractGrid and AbstractDofHandler interfaces are now used more consistently internally. This will help with the implementation of distributed grids and DofHandlers. (#655)\nVTK export now uses the (geometric) interpolation directly when evaluating the finite element field instead of trying to work backwards how DoFs map to nodes. (#703)\nImproved bounds checking in assemble!. (#706)\nInternal methods Ferrite.value and Ferrite.derivative for computing the value/gradient of all shape functions have been removed. (#720)\nFerrite.create_incidence_matrix now work with any AbstractGrid (not just Grid). (#726)","category":"page"},{"location":"changelog/#[v0.3.14](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.14)-2023-04-03","page":"Changelog","title":"v0.3.14 - 2023-04-03","text":"","category":"section"},{"location":"changelog/#Added-2","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Support reordering dofs of a MixedDofHandler by the built-in orderings FieldWise and ComponentWise. This includes support for reordering dofs of fields on subdomains. (#645)\nSupport specifying the coupling between fields in a MixedDofHandler when creating the sparsity pattern. (#650)\nSupport Metis dof reordering with coupling information for MixedDofHandler. (#650)\nPretty printing for MixedDofHandler and L2Projector. (#465)","category":"page"},{"location":"changelog/#Other-improvements-2","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The MixedDofHandler have gone through a performance review (see #629) and now performs the same as DofHandler. This was part of the push to merge the two DoF handlers. Since MixedDofHandler is strictly more flexible, and now equally performant, it will replace DofHandler in the next breaking release. (#637, #639, #642, #643, #656, #660)","category":"page"},{"location":"changelog/#Internal-changes","page":"Changelog","title":"Internal changes","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Changes listed here should not affect regular usage, but listed here in case you have been poking into Ferrite internals:","category":"page"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite.ndim(dh, fieldname) has been removed, use Ferrite.getfielddim(dh, fieldname) instead. (#658)\nFerrite.nfields(dh) has been removed, use length(Ferrite.getfieldnames(dh)) instead. (#444, #653)\ngetfielddims(::FieldHandler) and getfieldinterpolations(::FieldHandler) have been removed (#647, #659)","category":"page"},{"location":"changelog/#[v0.3.13](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.13)-2023-03-23","page":"Changelog","title":"v0.3.13 - 2023-03-23","text":"","category":"section"},{"location":"changelog/#Added-3","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Support for classical trilinear and triquadratic wedge elements. (#581)\nSymmetric quadrature rules up to order 10 for prismatic elements. (#581)\nFiner granulation of dof distribution, allowing to distribute different amounts of dofs per entity. (#581)","category":"page"},{"location":"changelog/#Fixed-2","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Dof distribution for embedded elements. (#581)\nImprove numerical accuracy in shape function evaluation for the Lagrange{2,Tetrahedron,(3|4|5)} interpolations. (#582, #633)","category":"page"},{"location":"changelog/#Other-improvements-3","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Documentation:\nNew \"Developer documentation\" section in the manual for documenting Ferrite.jl internals and developer tools. (#611)\nFix a bug in constraint computation in Stoke's flow example. (#614)\nPerformance:\nBenchmarking infrastructure to help tracking performance changes. (#388)\nPerformance improvements for various accessor functions for MixedDofHandler. (#621)","category":"page"},{"location":"changelog/#Internal-changes-2","page":"Changelog","title":"Internal changes","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"To clarify the dof management vertices(ip), edges(ip) and faces(ip) has been deprecated in favor of vertexdof_indices(ip), edgedof_indices(ip) and facedof_indices(ip). (#581)\nDuplicate grid representation has been removed from the MixedDofHandler. (#577)","category":"page"},{"location":"changelog/#[v0.3.12](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.12)-2023-02-28","page":"Changelog","title":"v0.3.12 - 2023-02-28","text":"","category":"section"},{"location":"changelog/#Added-4","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Added a basic show method for assemblers. (#598)","category":"page"},{"location":"changelog/#Fixed-3","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix an issue in constraint application of Symmetric-wrapped sparse matrices (i.e. obtained from create_symmatric_sparsity_pattern). In particular, apply!(K::Symmetric, f, ch) would incorrectly modify f if any of the constraints were inhomogeneous. (#592)\nProperly disable the Metis extension on Julia 1.9 instead of causing precompilation errors. (#588)\nFix adding Dirichlet boundary conditions on nodes when using MixedDofHandler. (#593, #594)\nFix accidentally slow implementation of show for Grids. (#599)\nFixes to topology functionality. (#453, #518, #455)\nFix grid coloring for cell sets with 0 or 1 cells. (#600)","category":"page"},{"location":"changelog/#Other-improvements-4","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Documentation improvements:\nSimplications and clarifications to hyperelasticity example. (#591)\nRemove duplicate docstring entry for vtk_point_data. (#602)\nUpdate documentation about initial conditions. (#601, #604)","category":"page"},{"location":"changelog/#[v0.3.11](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.11)-2023-01-17","page":"Changelog","title":"v0.3.11 - 2023-01-17","text":"","category":"section"},{"location":"changelog/#Added-5","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Metis.jl extension for fill-reducing DoF permutation. This uses Julias new package extension mechanism (requires Julia 1.10) to support a new DoF renumbering order DofOrder.Ext{Metis}() that can be passed to renumber! to renumber DoFs using the Metis.jl library. (#393, #549)\nBlockArrays.jl extension for creating a globally blocked system matrix. create_sparsity_pattern(BlockMatrix, dh, ch; kwargs...) return a matrix that is blocked by field (requires DoFs to be (re)numbered by field, i.e. renumber!(dh, DofOrder.FieldWise())). For custom blocking it is possible to pass an uninitialized BlockMatrix with the correct block sizes (see BlockArrays.jl docs). This functionality is useful for e.g. special solvers where individual blocks need to be extracted. Requires Julia version 1.9 or above. (#567)\nNew function apply_analytical! for setting the values of the degrees of freedom for a specific field according to a spatial function f(x). (#532)\nNew cache struct CellCache to be used when iterating over the cells in a grid or DoF handler. CellCache caches nodes, coordinates, and DoFs, for the cell. The cache cc can be re-initialized for a new cell index ci by calling reinit!(cc, ci). This can be used as an alternative to CellIterator when more control over which element to loop over is needed. See documentation for CellCache for more information. (#546)\nIt is now possible to create the sparsity pattern without constrained entries (they will be zeroed out later anyway) by passing keep_constrained=false to create_sparsity_pattern. This naturally only works together with local condensation of constraints since there won't be space allocated in the global matrix for the full (i.e. \"non-condensed\") element matrix. Creating the matrix without constrained entries reduces the memory footprint, but unless a significant amount of DoFs are constrained (e.g. high mesh resolution at a boundary) the savings are negligible. (#539)","category":"page"},{"location":"changelog/#Changed-2","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"ConstraintHandler: update! is now called implicitly in close!. This was easy to miss, and somewhat of a strange requirement when solving problems without time stepping. (#459)\nThe function for computing the inhomogeneity in a Dirichlet constraint can now be specified as either f(x) or f(x, t), where x is the spatial coordinate and t the time. (#459)\nThe elements of a CellIterator are now CellCache instead of the iterator itself, which was confusing in some cases. This change does not affect typical user code. (#546)","category":"page"},{"location":"changelog/#Deprecated-2","page":"Changelog","title":"Deprecated","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Adding fields to a DoF handler with push!(dh, ...) has been deprecated in favor of add!(dh, ...). This is to make it consistent with how constraints are added to a constraint handler. (#578)","category":"page"},{"location":"changelog/#Fixed-4","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix shape_value for the linear, discontinuous Lagrange interpolation. (#553)\nFix reference_coordinate dispatch for discontinuous Lagrange interpolations. (#559)\nFix show(::Grid) for custom cell types. (#570)\nFix apply_zero!(Δa, ch) when using inhomogeneous affine constraints (#575)","category":"page"},{"location":"changelog/#Other-improvements-5","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Internal changes defining a new global matrix/vector \"interface\". These changes make it easy to enable more array types (e.g. BlockMatrix support added in this release) and solvers in the future. (#562, #571)\nPerformance improvements:\nReduced time and memory allocations for global sparse matrix creation (Julia >= 1.10). (#563)\nDocumentation improvements:\nAdded an overview of the Examples section. (#531)\nAdded an example showing topology optimization. (#531)\nVarious typo fixes. (#574)\nFix broken links. (#583)","category":"page"},{"location":"changelog/#[v0.3.10](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.10)-2022-12-11","page":"Changelog","title":"v0.3.10 - 2022-12-11","text":"","category":"section"},{"location":"changelog/#Added-6","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"New functions apply_local! and apply_assemble! for applying constraints locally on the element level before assembling to the global system. (#528)\nNew functionality to renumber DoFs by fields or by components. This is useful when you need the global matrix to be blocked. (#378, #545)\nFunctionality to renumber DoFs in DofHandler and ConstraintHandler simultaneously: renumber!(dh::DofHandler, ch::ConstraintHandler, order). Previously renumbering had to be done before creating the ConstraintHandler since otherwise DoF numbers would be inconsistent. However, this was inconvenient in cases where the constraints impact the new DoF order permutation. (#542)\nThe coupling between fields can now be specified when creating the global matrix with create_sparsity_pattern by passing a Matrix{Bool}. For example, in a problem with unknowns (u, p) and corresponding test functions (v, q), if there is no coupling between p and q it is unnecessary to allocate entries in the global matrix corresponding to these DoFs. This can now be communicated to create_sparsity_pattern by passing the coupling matrix [true true; true false] in the keyword argument coupling. (#544)","category":"page"},{"location":"changelog/#Changed-3","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Runtime and allocations for application of boundary conditions in apply! and apply_zero! have been improved. As a result, the strategy keyword argument is obsolete and thus ignored. (#489)\nThe internal representation of Dirichlet boundary conditions and AffineConstraints in the ConstraintHandler have been unified. As a result, conflicting constraints on DoFs are handled more consistently: the constraint added last to the ConstraintHandler now always override any previous constraints. Conflicting constraints could previously cause problems when a DoF where prescribed by both Dirichlet and AffineConstraint. (#529)\nEntries in local matrix/vector are now ignored in the assembly procedure. This allows, for example, using a dense local matrix [a b; c d] even if no entries exist in the global matrix for the d block, i.e. in [A B; C D] the D block is zero, and these global entries might not exist in the sparse matrix. (Such sparsity patterns can now be created by create_sparsity_pattern, see #544.) (#543)","category":"page"},{"location":"changelog/#Fixed-5","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix affine constraints with prescribed DoFs in the right-hand-side. In particular, DoFs that are prescribed by just an inhomogeneity are now handled correctly, and nested affine constraints now give an error instead of silently giving the wrong result. (#530, #535)\nFixed internal inconsistency in edge ordering for 2nd order RefTetrahedron and RefCube. (#520, #523)","category":"page"},{"location":"changelog/#Other-improvements-6","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Performance improvements:\nReduced time and memory allocations in DoF distribution for MixedDofHandler. (#533)\nReduced time and memory allocations reductions in getcoordinates!. (#536)\nReduced time and memory allocations in affine constraint condensation. (#537, #541, #550)\nDocumentation improvements:\nUse :static scheduling for threaded for-loop (#534)\nRemove use of @inbounds (#547)\nUnification of create_sparsity_pattern methods to remove code duplication between DofHandler and MixedDofHandler. (#538, #540)","category":"page"},{"location":"changelog/#[v0.3.9](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.9)-2022-10-19","page":"Changelog","title":"v0.3.9 - 2022-10-19","text":"","category":"section"},{"location":"changelog/#Added-7","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"New higher order function interpolations for triangles (Lagrange{2,RefTetrahedron,3}, Lagrange{2,RefTetrahedron,4}, and Lagrange{2,RefTetrahedron,5}). (#482, #512)\nNew Gaussian quadrature formula for triangles up to order 15. (#514)\nAdd debug mode for working with Ferrite internals. (#524)","category":"page"},{"location":"changelog/#Changed-4","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"The default components to constrain in Dirichlet and PeriodicDirichlet have changed from component 1 to all components of the field. For scalar problems this has no effect. (#506, #509)","category":"page"},{"location":"changelog/#[v0.3.8](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.8)-2022-10-05","page":"Changelog","title":"v0.3.8 - 2022-10-05","text":"","category":"section"},{"location":"changelog/#Added-8","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Ferrite.jl now has a logo! (#464)\nNew keyword argument search_nneighbors::Int in PointEvalHandler for specifying how many neighboring elements to consider in the kNN search. The default is still 3 (usually sufficient). (#466)\nThe IJV-assembler now support assembling non-square matrices. (#471)\nPeriodic boundary conditions have been reworked and generalized. It now supports arbitrary relations between the mirror and image boundaries (e.g. not only translations in x/y/z direction). (#478, #481, #496, #501)","category":"page"},{"location":"changelog/#Fixed-6","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix PointEvalHandler when the first point is missing. (#466)\nFix the ordering of nodes on the face for (Quadratic)Tetrahedron cells. (#475)","category":"page"},{"location":"changelog/#Other-improvements-7","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Many improvements to the documentation. (#467, #473, #487, #494, #500)\nImproved error messages in reinit! when number of geometric base functions and number of element coordinates mismatch. (#469)\nRemove some unnecessary function parametrizations. (#503)\nRemove some unnecessary allocations in grid coloring. (#505)\nMore efficient way of creating the sparsity pattern when using AffineConstraints and/or PeriodicDirichlet. (#436)","category":"page"},{"location":"changelog/#[v0.3.7](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.7)-2022-07-05","page":"Changelog","title":"v0.3.7 - 2022-07-05","text":"","category":"section"},{"location":"changelog/#Fixed-7","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix tests for newer version of WriteVTK (no functional change). (#462)","category":"page"},{"location":"changelog/#Other-improvements-8","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Various improvements to the heat equation example and the hyperelasticity example in the documentation. (#460, #461)","category":"page"},{"location":"changelog/#[v0.3.6](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.6)-2022-06-30","page":"Changelog","title":"v0.3.6 - 2022-06-30","text":"","category":"section"},{"location":"changelog/#Fixed-8","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix a bug with L2Projection of mixed grid. (#456)","category":"page"},{"location":"changelog/#Other-improvements-9","page":"Changelog","title":"Other improvements","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Expanded manual section of Dirichlet BCs. (#458)","category":"page"},{"location":"changelog/#[v0.3.5](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.5)-2022-05-30","page":"Changelog","title":"v0.3.5 - 2022-05-30","text":"","category":"section"},{"location":"changelog/#Added-9","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Functionality for querying information about the grid topology (e.g. neighboring cells, boundaries, ...). (#363)","category":"page"},{"location":"changelog/#Fixed-9","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Fix application of boundary conditions when combining RHSData and affine constraints. (#431)","category":"page"},{"location":"changelog/#[v0.3.4](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.4)-2022-02-25","page":"Changelog","title":"v0.3.4 - 2022-02-25","text":"","category":"section"},{"location":"changelog/#Added-10","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Affine (linear) constraints between degrees-of-freedom. (#401)\nPeriodic Dirichlet boundary conditions. (#418)\nEvaluation of arbitrary quantities in FE space. (#425)","category":"page"},{"location":"changelog/#Changed-5","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Interpolation(s) and the quadrature rule are now stored as part of the CellValues structs (cv.func_interp, cv.geo_interp, and cv.qr). (#428)","category":"page"},{"location":"changelog/#[v0.3.3](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.3)-2022-02-04","page":"Changelog","title":"v0.3.3 - 2022-02-04","text":"","category":"section"},{"location":"changelog/#Changed-6","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Verify user input in various functions to eliminate possible out-of-bounds accesses. (#407, #411)","category":"page"},{"location":"changelog/#[v0.3.2](https://github.com/Ferrite-FEM/Ferrite.jl/releases/tag/v0.3.2)-2022-01-18","page":"Changelog","title":"v0.3.2 - 2022-01-18","text":"","category":"section"},{"location":"changelog/#Added-11","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Support for new interpolation types: DiscontinuousLagrange, BubbleEnrichedLagrange, and CrouzeixRaviart. (#352, #392)","category":"page"},{"location":"changelog/#Changed-7","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Julia version 1.0 is no longer supported for Ferrite versions >= 0.3.2. Use Julia version >= 1.6. (#385)\nQuadrature data for L2 projection can now be given as a matrix of size \"number of elements\" x \"number of quadrature points per element\". (#386)\nProjected values from L2 projection can now be exported directly to VTK. (#390)\nGrid coloring can now act on a subset of cells. (#402)\nVarious functions related to cell values now use traits to make it easier to extend and reuse functionality in external code. (#404)","category":"page"},{"location":"changelog/#Fixed-10","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Changelog","title":"Changelog","text":"Exporting tensors to VTK now use correct names for the components. (#406)","category":"page"},{"location":"reference/quadrature/","page":"Quadrature","title":"Quadrature","text":"CurrentModule = Ferrite\nDocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/quadrature/#Quadrature","page":"Quadrature","title":"Quadrature","text":"","category":"section"},{"location":"reference/quadrature/","page":"Quadrature","title":"Quadrature","text":"QuadratureRule\nFacetQuadratureRule\ngetnquadpoints(::QuadratureRule)\ngetnquadpoints(::FacetQuadratureRule, ::Int)\ngetpoints\ngetweights","category":"page"},{"location":"reference/quadrature/#Ferrite.QuadratureRule","page":"Quadrature","title":"Ferrite.QuadratureRule","text":"QuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)\nQuadratureRule{shape}(weights::AbstractVector{T}, points::AbstractVector{Vec{rdim, T}})\n\nCreate a QuadratureRule used for integration on the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. quad_rule_type is an optional argument determining the type of quadrature rule, currently the :legendre and :lobatto rules are implemented for hypercubes. For triangles up to order 8 the default rule is the one by :dunavant (see [6]) and for tetrahedra the default rule is keast_minimal (see [7]). Wedges and pyramids default to :polyquad (see [8]). Furthermore we have implemented\n\n:gaussjacobi for triangles (order 9-15)\n:keast_minimal (see [7]) for tetrahedra (order 1-5), containing negative weights\n:keast_positive (see [7]) for tetrahedra (order 1-5), containing only positive weights\n\nA QuadratureRule is used to approximate an integral on a domain by a weighted sum of function values at specific points:\n\nintlimits_Omega f(mathbfx) textd Omega approx sumlimits_q = 1^n_q f(mathbfx_q) w_q\n\nThe quadrature rule consists of n_q points in space mathbfx_q with corresponding weights w_q.\n\nIn Ferrite, the QuadratureRule type is mostly used as one of the components to create CellValues.\n\nCommon methods:\n\ngetpoints : the points of the quadrature rule\ngetweights : the weights of the quadrature rule\n\nExample:\n\njulia> qr = QuadratureRule{RefTriangle}(1)\nQuadratureRule{RefTriangle, Float64, 2}([0.5], Vec{2, Float64}[[0.33333333333333, 0.33333333333333]])\n\njulia> getpoints(qr)\n1-element Vector{Vec{2, Float64}}:\n [0.33333333333333, 0.33333333333333]\n\n\n\n\n\n","category":"type"},{"location":"reference/quadrature/#Ferrite.FacetQuadratureRule","page":"Quadrature","title":"Ferrite.FacetQuadratureRule","text":"FacetQuadratureRule{shape}([::Type{T},] [quad_rule_type::Symbol,] order::Int)\nFacetQuadratureRule{shape}(face_rules::NTuple{<:Any, <:QuadratureRule{shape}})\nFacetQuadratureRule{shape}(face_rules::AbstractVector{<:QuadratureRule{shape}})\n\nCreate a FacetQuadratureRule used for integration of the faces of the refshape shape (of type AbstractRefShape). order is the order of the quadrature rule. If no symbol is provided, the default quad_rule_type for each facet's reference shape is used (see QuadratureRule). For non-default quad_rule_types on cells with mixed facet types (e.g. RefPrism and RefPyramid), the face_rules must be provided explicitly.\n\nFacetQuadratureRule is used as one of the components to create FacetValues.\n\n\n\n\n\n","category":"type"},{"location":"reference/quadrature/#Ferrite.getnquadpoints-Tuple{QuadratureRule}","page":"Quadrature","title":"Ferrite.getnquadpoints","text":"getnquadpoints(qr::QuadratureRule)\n\nReturn the number of quadrature points in qr.\n\n\n\n\n\n","category":"method"},{"location":"reference/quadrature/#Ferrite.getnquadpoints-Tuple{FacetQuadratureRule, Int64}","page":"Quadrature","title":"Ferrite.getnquadpoints","text":"getnquadpoints(qr::FacetQuadratureRule, face::Int)\n\nReturn the number of quadrature points in qr for local face index face.\n\n\n\n\n\n","category":"method"},{"location":"reference/quadrature/#Ferrite.getpoints","page":"Quadrature","title":"Ferrite.getpoints","text":"getpoints(qr::QuadratureRule)\ngetpoints(qr::FacetQuadratureRule, face::Int)\n\nReturn the points of the quadrature rule.\n\nExamples\n\njulia> qr = QuadratureRule{RefTriangle}(:legendre, 2);\n\njulia> getpoints(qr)\n3-element Vector{Vec{2, Float64}}:\n [0.16666666666667, 0.16666666666667]\n [0.16666666666667, 0.66666666666667]\n [0.66666666666667, 0.16666666666667]\n\n\n\n\n\n","category":"function"},{"location":"reference/quadrature/#Ferrite.getweights","page":"Quadrature","title":"Ferrite.getweights","text":"getweights(qr::QuadratureRule)\ngetweights(qr::FacetQuadratureRule, face::Int)\n\nReturn the weights of the quadrature rule.\n\nExamples\n\njulia> qr = QuadratureRule{RefTriangle}(:legendre, 2);\n\njulia> getweights(qr)\n3-element Array{Float64,1}:\n 0.166667\n 0.166667\n 0.166667\n\n\n\n\n\n","category":"function"},{"location":"topics/#Topic-guides","page":"Topic guide overview","title":"Topic guides","text":"","category":"section"},{"location":"topics/","page":"Topic guide overview","title":"Topic guide overview","text":"This is an overview of the topic guides.","category":"page"},{"location":"topics/","page":"Topic guide overview","title":"Topic guide overview","text":"Pages = [\n \"fe_intro.md\",\n \"reference_shapes.md\",\n \"FEValues.md\",\n \"degrees_of_freedom.md\",\n \"assembly.md\",\n \"boundary_conditions.md\",\n \"constraints.md\",\n \"grid.md\",\n \"export.md\"\n]","category":"page"},{"location":"devdocs/dofhandler/#dofhandler-interpolations","page":"Dof Handler","title":"Dof Handler","text":"","category":"section"},{"location":"devdocs/dofhandler/#Type-definitions","page":"Dof Handler","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"Dof handlers are subtypes of AbstractDofhandler{sdim}, i.e. they are parametrized by the spatial dimension. Internally a helper struct InterpolationInfo is utilized to enforce type stability during dof distribution, because the interpolations are not available as concrete types.","category":"page"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"Ferrite.InterpolationInfo\nFerrite.PathOrientationInfo\nFerrite.SurfaceOrientationInfo","category":"page"},{"location":"devdocs/dofhandler/#Ferrite.InterpolationInfo","page":"Dof Handler","title":"Ferrite.InterpolationInfo","text":"InterpolationInfo\n\nGathers all the information needed to distribute dofs for a given interpolation. Note that this cache is of the same type no matter the interpolation: the purpose is to make dof-distribution type-stable.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/dofhandler/#Ferrite.PathOrientationInfo","page":"Dof Handler","title":"Ferrite.PathOrientationInfo","text":"PathOrientationInfo\n\nOrientation information for 1D entities.\n\nThe orientation for 1D entities is defined by the indices of the grid nodes associated to the vertices. To give an example, the oriented path\n\n1 ---> 2\n\nis called regular, indicated by regular=true, while the oriented path\n\n2 ---> 1\n\nis called inverted, indicated by regular=false.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/dofhandler/#Ferrite.SurfaceOrientationInfo","page":"Dof Handler","title":"Ferrite.SurfaceOrientationInfo","text":"SurfaceOrientationInfo\n\nOrientation information for 2D entities. Such an entity can be possibly flipped (i.e. the defining vertex order is reverse to the spanning vertex order) and the vertices can be rotated against each other. Take for example the faces\n\n1---2 2---3\n| A | | B |\n4---3 1---4\n\nwhich are rotated against each other by 90° (shift index is 1) or the faces\n\n1---2 2---1\n| A | | B |\n4---3 3---4\n\nwhich are flipped against each other. Any combination of these can happen. The combination to map this local face to the defining face is encoded with this data structure via rotate circ flip where the rotation is indiced by the shift index. !!!NOTE TODO implement me.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/dofhandler/#Internal-API","page":"Dof Handler","title":"Internal API","text":"","category":"section"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"The main entry point for dof distribution is __close!.","category":"page"},{"location":"devdocs/dofhandler/","page":"Dof Handler","title":"Dof Handler","text":"Ferrite.__close!\nFerrite.get_grid\nFerrite.find_field\nFerrite._find_field\nFerrite._close_subdofhandler!\nFerrite._distribute_dofs_for_cell!\nFerrite.permute_and_push!","category":"page"},{"location":"devdocs/dofhandler/#Ferrite.__close!","page":"Dof Handler","title":"Ferrite.__close!","text":"__close!(dh::DofHandler)\n\nInternal entry point for dof distribution.\n\nDofs are distributed as follows: For the DofHandler each SubDofHandler is visited in the order they were added. For each field in the SubDofHandler create dofs for the cell. This means that dofs on a particular cell will be numbered in groups for each field, so first the dofs for field 1 are distributed, then field 2, etc. For each cell dofs are first distributed on its vertices, then on the interior of edges (if applicable), then on the interior of faces (if applicable), and finally on the cell interior. The entity ordering follows the geometrical ordering found in vertices, faces and edges.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite.get_grid","page":"Dof Handler","title":"Ferrite.get_grid","text":"get_grid(dh::AbstractDofHandler)\n\nAccess some grid representation for the dof handler.\n\nnote: Note\nThis API function is currently not well-defined. It acts as the interface between distributed assembly and assembly on a single process, because most parts of the functionality can be handled by only acting on the locally owned cell set.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite.find_field","page":"Dof Handler","title":"Ferrite.find_field","text":"find_field(dh::DofHandler, field_name::Symbol)::NTuple{2,Int}\n\nReturn the index of the field with name field_name in a DofHandler. The index is a NTuple{2,Int}, where the 1st entry is the index of the SubDofHandler within which the field was found and the 2nd entry is the index of the field within the SubDofHandler.\n\nnote: Note\nAlways finds the 1st occurrence of a field within DofHandler.\n\nSee also: find_field(sdh::SubDofHandler, field_name::Symbol), Ferrite._find_field(sdh::SubDofHandler, field_name::Symbol).\n\n\n\n\n\nfind_field(sdh::SubDofHandler, field_name::Symbol)::Int\n\nReturn the index of the field with name field_name in a SubDofHandler. Throw an error if the field is not found.\n\nSee also: find_field(dh::DofHandler, field_name::Symbol), _find_field(sdh::SubDofHandler, field_name::Symbol).\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite._find_field","page":"Dof Handler","title":"Ferrite._find_field","text":"_find_field(sdh::SubDofHandler, field_name::Symbol)::Int\n\nReturn the index of the field with name field_name in the SubDofHandler sdh. Return nothing if the field is not found.\n\nSee also: find_field(dh::DofHandler, field_name::Symbol), find_field(sdh::SubDofHandler, field_name::Symbol).\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite._close_subdofhandler!","page":"Dof Handler","title":"Ferrite._close_subdofhandler!","text":"_close_subdofhandler!(dh::DofHandler{sdim}, sdh::SubDofHandler, sdh_index::Int, nextdof::Int, vertexdicts, edgedicts, facedicts) where {sdim}\n\nMain entry point to distribute dofs for a single SubDofHandler on its subdomain.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite._distribute_dofs_for_cell!","page":"Dof Handler","title":"Ferrite._distribute_dofs_for_cell!","text":"_distribute_dofs_for_cell!(dh::DofHandler{sdim}, cell::AbstractCell, ip_info::InterpolationInfo, nextdof::Int, vertexdict, edgedict, facedict) where {sdim}\n\nMain entry point to distribute dofs for a single cell.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/dofhandler/#Ferrite.permute_and_push!","page":"Dof Handler","title":"Ferrite.permute_and_push!","text":"permute_and_push!\n\nFor interpolations with more than one interior dof per edge it may be necessary to adjust the dofs. Since dofs are (initially) enumerated according to the local edge direction there can be a direction mismatch with the neighboring element. For example, in the following nodal interpolation example, with three interior dofs on each edge, the initial pass have distributed dofs 4, 5, 6 according to the local edge directions:\n\n+-----------+\n| A |\n+--4--5--6->+ local edge on element A\n\n ----------> global edge\n\n+<-6--5--4--+ local edge on element B\n| B |\n+-----------+\n\nFor most scalar-valued interpolations we can simply compensate for this by reversing the numbering on all edges that do not match the global edge direction, i.e. for the edge on element B in the example.\n\nIn addition, we also have to preserve the ordering at each dof location.\n\nFor more details we refer to Scroggs et al. [13] as we follow the methodology described therein.\n\nReferences\n\n[13] Scroggs et al. ACM Trans. Math. Softw. 48 (2022).\n\n\n\n\n\n!!!NOTE TODO implement me.\n\nFor more details we refer to [1] as we follow the methodology described therein.\n\n[1] Scroggs, M. W., Dokken, J. S., Richardson, C. N., & Wells, G. N. (2022). Construction of arbitrary order finite element degree-of-freedom maps on polygonal and polyhedral cell meshes. ACM Transactions on Mathematical Software (TOMS), 48(2), 1-23.\n\n!!!TODO citation via software.\n\n!!!TODO Investigate if we can somehow pass the interpolation into this function in a typestable way.\n\n\n\n\n\n","category":"function"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"EditURL = \"../literate-howto/threaded_assembly.jl\"","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Draft = false","category":"page"},{"location":"howto/threaded_assembly/#tutorial-threaded-assembly","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"tip: Tip\nThis example is also available as a Jupyter notebook: threaded_assembly.ipynb.","category":"page"},{"location":"howto/threaded_assembly/#Introduction","page":"Multi-threaded assembly","title":"Introduction","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"In this howto we will explore how to use task based multithreading (shared memory parallelism) to speed up the analysis. Some parts of a finite element simulation are trivially parallelizable such as the computation of the local element contributions since each element can be processed independently. However, two things need to be considered in order to parallelize safely:","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Modification of shared data: Although the contributions from all the elements can be computed independently, eventually they need to be assembled into the global matrix and vector. Letting each task assemble their own contribution would lead to race conditions since elements share degrees of freedom with each other. There are various ways to remedy this, for example:\nLocking: By using a lock around the call to assemble! we can ensure that only one task assembles at a time. This is simple to implement but can lead to lock contention and thus poor performance. Another drawback is that the results will not be deterministic since floating point operations are neither associative nor commutative.\nAssembler task: By using a designated task for the assembling we (obviously) ensure that only a single task assembles. The worker tasks (the tasks computing the element contributions) would then hand off their results to the assemly task. This can be a useful approach if computing the element contributions is much slower than the assembly – otherwise the assembler task can't keep up with the worker tasks. There might also be some extra overhead because of task switching in the scheduler. The problem with non-deterministic results still remains.\nGrid coloring: By \"coloring\" the grid such that, within each color, no two elements share degrees of freedom, we can safely assemble each color in parallel. Even if concurrently running tasks will write to the global matrix and vector they will not write to the same memory locations. Note also that this procedure gives predictable results because for a memory location which, for example, a \"red\", a \"blue\", and a \"green\" element will contribute to we will always add the red first, then the blue, and finally the green.\nScratch data: In order to speed up the computation of the element contributions we typically pre-allocate some data structures that can be reused for every element. Such scratch data include, for example, the local matrix and vector, and the CellValues. Each task need their own copy of the scratch data since they will be modified for each element.","category":"page"},{"location":"howto/threaded_assembly/#Grid-coloring","page":"Multi-threaded assembly","title":"Grid coloring","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Ferrite include functionality to color the grid with the create_coloring function. Here we create a simple 2D grid, color it, and export the colors to a VTK file to visualize the result (see Figure 1.). Note that no cells with the same color has any shared nodes (dofs). This means that it is safe to assemble in parallel as long as we only assemble one color at a time.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"There are two coloring algorithms implemented: the \"workstream\" algorithm (from Turcksin et al. [11]) and a \"greedy\" algorithm. For this structured grid the greedy algorithm uses fewer colors, but both algorithms result in colors that contain roughly the same number of elements. The workstream algorithm is the default one since it in general results in more balanced colors. For unstructured grids the greedy algorithm can result in colors with very few elements, for example.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"using Ferrite, SparseArrays\n\nfunction create_example_2d_grid()\n grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))\n colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)\n colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)\n VTKGridFile(\"colored\", grid) do vtk\n Ferrite.write_cell_colors(vtk, grid, colors_workstream, \"workstream-coloring\")\n Ferrite.write_cell_colors(vtk, grid, colors_greedy, \"greedy-coloring\")\n end\n return\nend\n\ncreate_example_2d_grid()","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"(Image: )","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Figure 1: Element coloring using the \"workstream\"-algorithm (left) and the \"greedy\"- algorithm (right).","category":"page"},{"location":"howto/threaded_assembly/#Multithreaded-assembly-of-a-cantilever-beam-in-3D","page":"Multi-threaded assembly","title":"Multithreaded assembly of a cantilever beam in 3D","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We will now look at an example where we assemble the stiffness matrix and right hand side using multiple threads. The problem setup is a cantilever beam in 3D with a linear elastic material behavior. For this exercise we only focus on the multithreading and are not bothered with boundary conditions. For more details refer to the tutorial on linear elasticity.","category":"page"},{"location":"howto/threaded_assembly/#Setup","page":"Multi-threaded assembly","title":"Setup","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We define the element routine, material stiffness, grid and DofHandler just like in the tutorial on linear elasticity without discussing it further here.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"# Element routine\nfunction assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)\n fill!(Ke, 0)\n fill!(fe, 0)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n δui = shape_value(cellvalues, q_point, i)\n fe[i] += (δui ⋅ b) * dΩ\n ∇δui = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n ∇uj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\n# Material stiffness\nfunction create_material_stiffness()\n E = 200.0e9\n ν = 0.3\n λ = E * ν / ((1 + ν) * (1 - 2ν))\n μ = E / (2(1 + ν))\n δ(i, j) = i == j ? 1.0 : 0.0\n C = SymmetricTensor{4, 3}() do i, j, k, l\n return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n end\n return C\nend\n\n# Grid and grid coloring\nfunction create_cantilever_grid(n::Int)\n xmin = Vec{3}((0.0, 0.0, 0.0))\n xmax = Vec{3}((10.0, 1.0, 1.0))\n grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax)\n colors = create_coloring(grid)\n return grid, colors\nend\n\n# DofHandler with displacement field u\nfunction create_dofhandler(grid::Grid, interpolation::VectorInterpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation)\n close!(dh)\n return dh\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/#Task-local-scratch-data","page":"Multi-threaded assembly","title":"Task local scratch data","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We group everything that needs to be duplicated for each task in the struct ScratchData:","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"cell_cache::CellCache: contain buffers for coordinates and (global) dofs which will be reinit!ed for each cell.\ncellvalues::CellValues: the cell values which will be reinit!ed for each cell using the cell_cache\nKe::Matrix: the local matrix\nfe::Vector: the local vector\nassembler: the assembler (which needs to be duplicated because it contains buffers that are modified during the call to assemble!)","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"struct ScratchData{CC, CV, T, A}\n cell_cache::CC\n cellvalues::CV\n Ke::Matrix{T}\n fe::Vector{T}\n assembler::A\nend","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"This constructor will be called within each task to create a independent ScratchData object. For cell_cache, Ke, and fe we simply call the constructors to allocate independent objects. For cellvalues we use copy which Ferrite defines for this purpose. Finally, for the assembler we call start_assemble to create a new assembler but note that we set fillzero = false because we don't want to risk that a task that starts a bit later will zero out data that another task have already assembled.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"function ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues)\n cell_cache = CellCache(dh)\n n = ndofs_per_cell(dh)\n Ke = zeros(n, n)\n fe = zeros(n)\n asm = start_assemble(K, f; fillzero = false)\n return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm)\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/#Global-assembly-routine","page":"Multi-threaded assembly","title":"Global assembly routine","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Finally we define the global assemble routine, which is where the parallelization happens. The main difference from all previous assemble_global! functions is that we now have an outer loop over the colors, and then the inner loop over the cells in each color, which can be parallelized.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"For the scheduling of parallel tasks we use the OhMyThreads.jl package. OhMyThreads provides a macro based and a functional API. Here we use the macro based API because it is slightly more convenient when using task local values since they can be defined with the @local macro.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"note: Schedulers and load balancing\nOhMyThreads provides a number of different schedulers. In this example we use the DynamicScheduler (which is the default one). The DynamicScheduler will spawn ntasks tasks where each task will process a chunk of (roughly) equal number of cells (i.e. length(color) ÷ ntasks). This should be a good choice for this example because we expect all cells to take the same time to process and we don't need any load balancing.For a different problem setup where some cells might take longer to process (perhaps they experience plastic deformation and we need to solve a local problem) we might benefit from load balancing. The DynamicScheduler can be used also for load balancing by specifiying nchunks or chunksize. However, the DynamicScheduler will always spawn nchunks tasks which can become costly since we are allocating scratch data for every task. To limit the number of tasks, while allowing for more than ntasks chunks, we can use the GreedyScheduler with chunking. For example, scheduler = OhMyThreads.GreedyScheduler(; ntasks = ntasks, nchunks = 10 * ntasks) will split the work into 10 * ntasks chunks and spawn ntasks tasks to process them. Refer to the OhMyThreads documentation for details.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"using OhMyThreads, TaskLocalValues\n\nfunction assemble_global!(\n K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors,\n cellvalues_template::CellValues; ntasks = Threads.nthreads()\n )\n # Zero-out existing data in K and f\n _ = start_assemble(K, f)\n # Body force and material stiffness\n b = Vec{3}((0.0, 0.0, -1.0))\n C = create_material_stiffness()\n # Loop over the colors\n for color in colors\n # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of\n # (roughly) equal number of cells (`length(color) ÷ ntasks`).\n scheduler = OhMyThreads.DynamicScheduler(; ntasks)\n # Parallelize the loop over the cells in this color\n OhMyThreads.@tasks for cellidx in color\n # Tell the @tasks loop to use the scheduler defined above\n @set scheduler = scheduler\n # Obtain a task local scratch and unpack it\n @local scratch = ScratchData(dh, K, f, cellvalues_template)\n (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n # Reinitialize the cell cache and then the cellvalues\n reinit!(cell_cache, cellidx)\n reinit!(cellvalues, cell_cache)\n # Compute the local contribution of the cell\n assemble_cell!(Ke, fe, cellvalues, C, b)\n # Assemble local contribution\n assemble!(assembler, celldofs(cell_cache), Ke, fe)\n end\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"details: OhMyThreads functional API: OhMyThreads.tforeach\nThe OhMyThreads.@tasks block above corresponds to a call to OhMyThreads.tforeach. Using the functional API directly would look like below. The main difference is that we need to manually create a TaskLocalValue for the scratch data.# using TaskLocalValues\nscratches = TaskLocalValue() do\n ScratchData(dh, K, f, cellvalues)\nend\nOhMyThreads.tforeach(color; scheduler) do cellidx\n # Obtain a task local scratch and unpack it\n scratch = scratches[]\n (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n # Reinitialize the cell cache and then the cellvalues\n reinit!(cell_cache, cellidx)\n reinit!(cellvalues, cell_cache)\n # Compute the local contribution of the cell\n assemble_cell!(Ke, fe, cellvalues, C, b)\n # Assemble local contribution\n assemble!(assembler, celldofs(cell_cache), Ke, fe)\nend","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"We define the main function to setup everything and then time the call to assemble_global!.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"function main(; n = 20, ntasks = Threads.nthreads())\n # Interpolation, quadrature and cellvalues\n interpolation = Lagrange{RefHexahedron, 1}()^3\n quadrature = QuadratureRule{RefHexahedron}(2)\n cellvalues = CellValues(quadrature, interpolation)\n # Grid, colors and DofHandler\n grid, colors = create_cantilever_grid(n)\n dh = create_dofhandler(grid, interpolation)\n # Global matrix and vector\n K = allocate_matrix(dh)\n f = zeros(ndofs(dh))\n # Compile it\n assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n # Time it\n @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n return\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"On a machine with 4 cores, starting julia with --threads=auto, we obtain the following timings:","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"main(; ntasks = 1) # 1.970784 seconds (902 allocations: 816.172 KiB)\nmain(; ntasks = 2) # 1.025065 seconds (1.64 k allocations: 1.564 MiB)\nmain(; ntasks = 3) # 0.700423 seconds (2.38 k allocations: 2.332 MiB)\nmain(; ntasks = 4) # 0.548356 seconds (3.12 k allocations: 3.099 MiB)","category":"page"},{"location":"howto/threaded_assembly/#threaded_assembly-plain-program","page":"Multi-threaded assembly","title":"Plain program","text":"","category":"section"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"Here follows a version of the program without any comments. The file is also available here: threaded_assembly.jl.","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"using Ferrite, SparseArrays\n\nfunction create_example_2d_grid()\n grid = generate_grid(Quadrilateral, (10, 10), Vec{2}((0.0, 0.0)), Vec{2}((10.0, 10.0)))\n colors_workstream = create_coloring(grid; alg = ColoringAlgorithm.WorkStream)\n colors_greedy = create_coloring(grid; alg = ColoringAlgorithm.Greedy)\n VTKGridFile(\"colored\", grid) do vtk\n Ferrite.write_cell_colors(vtk, grid, colors_workstream, \"workstream-coloring\")\n Ferrite.write_cell_colors(vtk, grid, colors_greedy, \"greedy-coloring\")\n end\n return\nend\n\ncreate_example_2d_grid()\n\n# Element routine\nfunction assemble_cell!(Ke::Matrix, fe::Vector, cellvalues::CellValues, C::SymmetricTensor, b::Vec)\n fill!(Ke, 0)\n fill!(fe, 0)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:getnbasefunctions(cellvalues)\n δui = shape_value(cellvalues, q_point, i)\n fe[i] += (δui ⋅ b) * dΩ\n ∇δui = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:getnbasefunctions(cellvalues)\n ∇uj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (∇δui ⊡ C ⊡ ∇uj) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\n# Material stiffness\nfunction create_material_stiffness()\n E = 200.0e9\n ν = 0.3\n λ = E * ν / ((1 + ν) * (1 - 2ν))\n μ = E / (2(1 + ν))\n δ(i, j) = i == j ? 1.0 : 0.0\n C = SymmetricTensor{4, 3}() do i, j, k, l\n return λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n end\n return C\nend\n\n# Grid and grid coloring\nfunction create_cantilever_grid(n::Int)\n xmin = Vec{3}((0.0, 0.0, 0.0))\n xmax = Vec{3}((10.0, 1.0, 1.0))\n grid = generate_grid(Hexahedron, (10 * n, n, n), xmin, xmax)\n colors = create_coloring(grid)\n return grid, colors\nend\n\n# DofHandler with displacement field u\nfunction create_dofhandler(grid::Grid, interpolation::VectorInterpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation)\n close!(dh)\n return dh\nend\nnothing # hide\n\nstruct ScratchData{CC, CV, T, A}\n cell_cache::CC\n cellvalues::CV\n Ke::Matrix{T}\n fe::Vector{T}\n assembler::A\nend\n\nfunction ScratchData(dh::DofHandler, K::SparseMatrixCSC, f::Vector, cellvalues::CellValues)\n cell_cache = CellCache(dh)\n n = ndofs_per_cell(dh)\n Ke = zeros(n, n)\n fe = zeros(n)\n asm = start_assemble(K, f; fillzero = false)\n return ScratchData(cell_cache, copy(cellvalues), Ke, fe, asm)\nend\nnothing # hide\n\nusing OhMyThreads, TaskLocalValues\n\nfunction assemble_global!(\n K::SparseMatrixCSC, f::Vector, dh::DofHandler, colors,\n cellvalues_template::CellValues; ntasks = Threads.nthreads()\n )\n # Zero-out existing data in K and f\n _ = start_assemble(K, f)\n # Body force and material stiffness\n b = Vec{3}((0.0, 0.0, -1.0))\n C = create_material_stiffness()\n # Loop over the colors\n for color in colors\n # Dynamic scheduler spawning `ntasks` tasks where each task will process a chunk of\n # (roughly) equal number of cells (`length(color) ÷ ntasks`).\n scheduler = OhMyThreads.DynamicScheduler(; ntasks)\n # Parallelize the loop over the cells in this color\n OhMyThreads.@tasks for cellidx in color\n # Tell the @tasks loop to use the scheduler defined above\n @set scheduler = scheduler\n # Obtain a task local scratch and unpack it\n @local scratch = ScratchData(dh, K, f, cellvalues_template)\n (; cell_cache, cellvalues, Ke, fe, assembler) = scratch\n # Reinitialize the cell cache and then the cellvalues\n reinit!(cell_cache, cellidx)\n reinit!(cellvalues, cell_cache)\n # Compute the local contribution of the cell\n assemble_cell!(Ke, fe, cellvalues, C, b)\n # Assemble local contribution\n assemble!(assembler, celldofs(cell_cache), Ke, fe)\n end\n end\n return K, f\nend\nnothing # hide\n\nfunction main(; n = 20, ntasks = Threads.nthreads())\n # Interpolation, quadrature and cellvalues\n interpolation = Lagrange{RefHexahedron, 1}()^3\n quadrature = QuadratureRule{RefHexahedron}(2)\n cellvalues = CellValues(quadrature, interpolation)\n # Grid, colors and DofHandler\n grid, colors = create_cantilever_grid(n)\n dh = create_dofhandler(grid, interpolation)\n # Global matrix and vector\n K = allocate_matrix(dh)\n f = zeros(ndofs(dh))\n # Compile it\n assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n # Time it\n @time assemble_global!(K, f, dh, colors, cellvalues; ntasks = ntasks)\n return\nend\nnothing # hide","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"","category":"page"},{"location":"howto/threaded_assembly/","page":"Multi-threaded assembly","title":"Multi-threaded assembly","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"CurrentModule = Ferrite\nDocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/interpolations/#reference-interpolation","page":"Interpolation","title":"Interpolation","text":"","category":"section"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"Interpolation\ngetnbasefunctions\ngetrefdim(::Interpolation)\ngetrefshape\ngetorder","category":"page"},{"location":"reference/interpolations/#Ferrite.Interpolation","page":"Interpolation","title":"Ferrite.Interpolation","text":"Interpolation{ref_shape, order}()\n\nAbstract type for interpolations defined on ref_shape (see AbstractRefShape). order corresponds to the order of the interpolation. The interpolation is used to define shape functions to interpolate a function between nodes.\n\nThe following interpolations are implemented:\n\nLagrange{RefLine,1}\nLagrange{RefLine,2}\nLagrange{RefQuadrilateral,1}\nLagrange{RefQuadrilateral,2}\nLagrange{RefQuadrilateral,3}\nLagrange{RefTriangle,1}\nLagrange{RefTriangle,2}\nLagrange{RefTriangle,3}\nLagrange{RefTriangle,4}\nLagrange{RefTriangle,5}\nBubbleEnrichedLagrange{RefTriangle,1}\nCrouzeixRaviart{RefTriangle, 1}\nCrouzeixRaviart{RefTetrahedron, 1}\nRannacherTurek{RefQuadrilateral, 1}\nRannacherTurek{RefHexahedron, 1}\nLagrange{RefHexahedron,1}\nLagrange{RefHexahedron,2}\nLagrange{RefTetrahedron,1}\nLagrange{RefTetrahedron,2}\nLagrange{RefPrism,1}\nLagrange{RefPrism,2}\nLagrange{RefPyramid,1}\nLagrange{RefPyramid,2}\nSerendipity{RefQuadrilateral,2}\nSerendipity{RefHexahedron,2}\n\nExamples\n\njulia> ip = Lagrange{RefTriangle, 2}()\nLagrange{RefTriangle, 2}()\n\njulia> getnbasefunctions(ip)\n6\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.getnbasefunctions","page":"Interpolation","title":"Ferrite.getnbasefunctions","text":"Ferrite.getnbasefunctions(ip::Interpolation)\n\nReturn the number of base functions for the interpolation ip.\n\n\n\n\n\n","category":"function"},{"location":"reference/interpolations/#Ferrite.getrefdim-Tuple{Interpolation}","page":"Interpolation","title":"Ferrite.getrefdim","text":"Ferrite.getrefdim(::Interpolation)\n\nReturn the dimension of the reference element for a given interpolation.\n\n\n\n\n\n","category":"method"},{"location":"reference/interpolations/#Ferrite.getrefshape","page":"Interpolation","title":"Ferrite.getrefshape","text":"Ferrite.getrefshape(::Interpolation)::AbstractRefShape\n\nReturn the reference element shape of the interpolation.\n\n\n\n\n\n","category":"function"},{"location":"reference/interpolations/#Ferrite.getorder","page":"Interpolation","title":"Ferrite.getorder","text":"Ferrite.getorder(::Interpolation)\n\nReturn order of the interpolation.\n\n\n\n\n\n","category":"function"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"Implemented interpolations:","category":"page"},{"location":"reference/interpolations/","page":"Interpolation","title":"Interpolation","text":"Lagrange\nSerendipity\nDiscontinuousLagrange\nBubbleEnrichedLagrange\nCrouzeixRaviart\nRannacherTurek","category":"page"},{"location":"reference/interpolations/#Ferrite.Lagrange","page":"Interpolation","title":"Ferrite.Lagrange","text":"Lagrange{refshape, order} <: ScalarInterpolation\n\nStandard continuous Lagrange polynomials with equidistant node placement.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.Serendipity","page":"Interpolation","title":"Ferrite.Serendipity","text":"Serendipity{refshape, order} <: ScalarInterpolation\n\nSerendipity element on hypercubes. Currently only second order variants are implemented.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.DiscontinuousLagrange","page":"Interpolation","title":"Ferrite.DiscontinuousLagrange","text":"Piecewise discontinuous Lagrange basis via Gauss-Lobatto points.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.BubbleEnrichedLagrange","page":"Interpolation","title":"Ferrite.BubbleEnrichedLagrange","text":"Lagrange element with bubble stabilization.\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.CrouzeixRaviart","page":"Interpolation","title":"Ferrite.CrouzeixRaviart","text":"CrouzeixRaviart{refshape, order} <: ScalarInterpolation\n\nClassical non-conforming Crouzeix–Raviart element.\n\nFor details we refer to the original paper [9].\n\n\n\n\n\n","category":"type"},{"location":"reference/interpolations/#Ferrite.RannacherTurek","page":"Interpolation","title":"Ferrite.RannacherTurek","text":"RannacherTurek{refshape, order} <: ScalarInterpolation\n\nClassical non-conforming Rannacher-Turek element.\n\nThis element is basically the idea from Crouzeix and Raviart applied to hypercubes. For details see the original paper [10].\n\n\n\n\n\n","category":"type"},{"location":"devdocs/#Developer-documentation","page":"Developer documentation","title":"Developer documentation","text":"","category":"section"},{"location":"devdocs/","page":"Developer documentation","title":"Developer documentation","text":"Here you can find some documentation of the internals of Ferrite which are useful when developing the library.","category":"page"},{"location":"devdocs/","page":"Developer documentation","title":"Developer documentation","text":"Depth = 1\nPages = [\"reference_cells.md\", \"interpolations.md\", \"elements.md\", \"FEValues.md\", \"dofhandler.md\", \"assembly.md\", \"performance.md\", \"special_datastructures.md\"]","category":"page"},{"location":"topics/fe_intro/#Introduction-to-FEM","page":"Introduction to FEM","title":"Introduction to FEM","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Here we will present a very brief introduction to partial differential equations (PDEs) and to the finite element method (FEM). Perhaps the simplest PDE of all is the (steady-state, linear) heat equation, also known as the Poisson equation. We will use this equation as a demonstrative example of the method, and demonstrate how we go from the strong form of the equation, to the weak form, and then finally to the discrete FE problem.","category":"page"},{"location":"topics/fe_intro/#Strong-form","page":"Introduction to FEM","title":"Strong form","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"The strong form of the heat equation may be written as:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"- nabla cdot mathbfq(u) = f quad forall mathbfx in Omega","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where u is the unknown temperature field, mathbfq is the heat flux, f is an internal heat source, and Omega is the domain on which the equation is defined. To complete the problem we need to specify what happens at the domain boundary Gamma. This set of specifications is called boundary conditions. There are different types of boundary conditions, where the most common ones are Dirichlet – which means that the solution u is known at some part of the boundary, and Neumann – which means that the gradient of the solution, nabla u is known. Formally we write for our example","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"u = u^mathrmp quad forall mathbfx in Gamma_mathrmD\nmathbfq cdot mathbfn = q^mathrmp quad forall mathbfx in Gamma_mathrmN","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"i.e. the temperature is prescribed to a known function u^mathrmp at the Dirichlet part of the boundary, Gamma_mathrmD, and the heat flux is prescribed to q^mathrmp at the Neumann part of the boundary, Gamma_mathrmN, where mathbfn describes the outward pointing normal vector at the boundary.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"We also need a constitutive equation which links the temperature field, u, to the heat flux, mathbfq. The simplest case is to use Fourier's law","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"mathbfq(u) = -k nabla u","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where k is the conductivity of the material. In general the conductivity can vary throughout the domain as a function of the coordinate, i.e. k = k(mathbfx), but for simplicity we will consider only constant conductivity k.","category":"page"},{"location":"topics/fe_intro/#Weak-form","page":"Introduction to FEM","title":"Weak form","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"The solution to the equation above is usually calculated from the corresponding weak form. By multiplying the equation with an arbitrary test function delta u, integrating over the domain and using partial integration we obtain the weak form. Now our problem can be stated as:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Find u in mathbbU s.t.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"int_Omega nabla delta u cdot (k nabla u) mathrmdOmega =\nint_Gamma_mathrmN delta u q^mathrmp mathrmdGamma +\nint_Omega delta u f mathrmdOmega quad forall delta u in mathbbT","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where mathbbU mathbbT are suitable function spaces with sufficiently regular functions. Under very general assumptions it can be shown that the solution to the weak form is identical to the solution to the strong form.","category":"page"},{"location":"topics/fe_intro/#Finite-Element-approximation","page":"Introduction to FEM","title":"Finite Element approximation","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Using the finite element method to solve partial differential equations is usually preceded with the construction of a discretization of the domain Omega into a finite set of elements or cells. We call this geometric discretization grid (or mesh) and denote it with Omega_h. In this example the corners of the triangles are called nodes.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Next we introduce the finite element approximation u_mathrmh approx u as a sum of N nodal shape functions, where we denote each of these function by phi_i and the corresponding nodal values hatu_i. Note that shape functions are sometimes referred to as basis functions or trial functions, and instead of phi_i they are sometimes denoted N_i. In this example we choose to approximate the test function in the same way. This approach is known as the Galerkin finite element method. Formally we write the evaluation of our approximations at a specific point mathbfx in our domain Omega as:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"u_mathrmh(mathbfx) = sum_i=1^mathrmN phi_i(mathbfx) hatu_iqquad\ndelta u_mathrmh(mathbfx) = sum_i=1^mathrmN phi_i(mathbfx) delta hatu_i ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Since test and trial functions are usually chosen in such a way, that they build the basis of some function space (basis as in basis of a vector space), sometimes are they are also called basis functions. In the following the argument mathbfx is dropped to keep the notation compact. We may now insert these approximations in the weak form, which results in","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"sum_i^N delta hatu_i left(sum_j^N int_Omega_mathrmh nabla phi_i cdot (k nabla phi_j) mathrmdOmega hatu_j right) =\nsum_i^N delta hatu_i left( int_Gamma_mathrmN phi_i q^mathrmp mathrmdGamma +\nint_Omega_mathrmh phi_i f mathrmdOmega right) ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Since this equation must hold for arbitrary delta u_mathrmh, the equation must especially hold for the specific choice that only one of the nodal values delta hatu_i is fixed to 1 while an all other coefficients are fixed to 0. Repeating this argument for all i from 1 to N we obtain N linear equations. This way the discrete problem can be written as a system of linear equations","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"underlineunderlineK underlinehatu = underlinehatf ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where we call underlineunderlineK the (tangent) stiffness matrix, underlinehatu the solution vector with the nodal values and underlinehatf the force vector. The specific naming is for historical reasons, because the finite element method has its origins in mechanics. The elements of underlineunderlineK and underlinehatf are given by","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"(underlineunderlineK)_ij =\n int_Omega_mathrmh nabla phi_i cdot (k nabla phi_j) mathrmdOmega \n\n(underlinehatf)_i =\n int_Gamma_mathrmN phi_i q^mathrmp mathrmdGamma + int_Omega_mathrmh phi_i f mathrmdOmega ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Finally we also need to take care of the Dirichlet boundary conditions. These are enforced by setting the corresponding hatu_i to the prescribed values and eliminating the associated equations from the system. Now, solving this equation system yields the nodal values and thus an approximation to the true solution.","category":"page"},{"location":"topics/fe_intro/#Notes-on-the-implementation","page":"Introduction to FEM","title":"Notes on the implementation","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"In practice, the shape functions phi_i are only non-zero on parts of the domain Omega_mathrmh. Thus, the integrals are evaluated on sub-domains, called elements or cells.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Each cell gives a contribution to the global stiffness matrix and force vector. The process of constructing the system of equations is also called assembly. For clarification, let us rewrite the formula for the stiffness matrix entries as follows:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"(underlineunderlineK)_ij\n = int_Omega_mathrmh nabla phi_i cdot (k nabla phi_j) mathrmdOmega\n = sum_E in Omega_mathrmh int_E nabla phi_i cdot (k nabla phi_j) mathrmdOmega ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"This formulation underlines the element-centric perspective of finite element methods and reflects how it is usually implemented in software.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Computing the element integrals by hand can become a tedious task. To avoid this issue we approximate the element integrals with a technique called numerical integration. Skipping any of the mathematical details, the basic idea is to evaluate the function under the integral at specific points and weighting the evaluations accordingly, such that their sum approximates the volume properly. A very nice feature of these techniques is, that under quite general circumstances the formula is not just an approximation, but the exact evaluation of the integral. To avoid the recomputation of the just mentioned evaluation positions of the integral for each individual element, we perform a coordinate transformation onto a so-called reference element. Formally we write","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":" int_E nabla phi_i cdot (k nabla phi_j) mathrmdOmega\n approx sum_q nabla phi_i(textbfx_q) cdot (k(textbfx_q) nabla phi_j(textbfx_q)) w_q textrmdet(J(textbfx_q)) ","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"where J is the Jacobian of the coordinate transformation function. The computation of the transformation, weights, positions and of the Jacobi determinant is handled by Ferrite. On an intuitive level, and to explain the notation used in the implementation, we think of","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":" mathrmdOmega approx w textrmdet(J)","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"being the chosen approximation when changing from the integral to the finite summation.","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"For an example of the implementation to solve a heat problem with Ferrite check out this thoroughly commented example.","category":"page"},{"location":"topics/fe_intro/#More-details","page":"Introduction to FEM","title":"More details","text":"","category":"section"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"We finally want to note that this quick introduction barely scratches the surface of the finite element method. Also, we presented some things in a simplified way for the sake of keeping this article short and concise. There is a large corpus of literature and online tutorials containing more details about the finite element method. To give a few recommendations there is:","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"Hans Petter Langtangen's Script\nWolfgang Bangerth's Lecture Series\nIntroduction to the Finite Element Method by Niels Ottosen and Hans Petersson\nThe Finite Element Method for Elliptic Problems by Philippe Ciarlet\nFinite Elements: Theory, Fast Solvers, and Applications in Elasticity Theory by Dietrich Braess\nAn Analysis of the Finite Element Method by Gilbert Strang and George Fix\nFinite Element Procedures by Klaus-Jürgen Bathe\nThe Finite Element Method: Its Basis and Fundamentals by Olgierd Cecil Zienkiewicz, Robert Taylor and J.Z. Zhu\nHigher-Order Finite Element Methods by Pavel Šolín, Karel Segeth and Ivo Doležel","category":"page"},{"location":"topics/fe_intro/","page":"Introduction to FEM","title":"Introduction to FEM","text":"This list is neither meant to be exhaustive, nor does the absence of a work mean that it is in any way bad or not recommendable. The ordering of the articles also has no particular meaning.","category":"page"},{"location":"devdocs/FEValues/#devdocs-fevalues","page":"FEValues","title":"FEValues","text":"","category":"section"},{"location":"devdocs/FEValues/#Type-definitions","page":"FEValues","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"AbstractValues\nAbstractCellValues\nCellValues\nAbstractFacetValues\nFacetValues\nBCValues\nPointValues\nInterfaceValues","category":"page"},{"location":"devdocs/FEValues/#Internal-types","page":"FEValues","title":"Internal types","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.GeometryMapping\nFerrite.MappingValues\nFerrite.FunctionValues\nFerrite.BCValues","category":"page"},{"location":"devdocs/FEValues/#Ferrite.GeometryMapping","page":"FEValues","title":"Ferrite.GeometryMapping","text":"GeometryMapping{DiffOrder}(::Type{T}, ip_geo, qr::QuadratureRule)\n\nCreate a GeometryMapping object which contains the geometric\n\nshape values\ngradient values (if DiffOrder ≥ 1)\nhessians values (if DiffOrder ≥ 2)\n\nT<:AbstractFloat gives the numeric type of the values.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Ferrite.MappingValues","page":"FEValues","title":"Ferrite.MappingValues","text":"MappingValues(J, H)\n\nThe mapping values are calculated based on a geometric_mapping::GeometryMapping along with the cell coordinates, and the stored jacobian, J, and potentially hessian, H, are used when mapping the FunctionValues to the current cell during reinit!.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Ferrite.FunctionValues","page":"FEValues","title":"Ferrite.FunctionValues","text":"FunctionValues{DiffOrder}(::Type{T}, ip_fun, qr::QuadratureRule, ip_geo::VectorizedInterpolation)\n\nCreate a FunctionValues object containing the shape values and gradients (up to order DiffOrder) for both the reference cell (precalculated) and the real cell (updated in reinit!).\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Ferrite.BCValues","page":"FEValues","title":"Ferrite.BCValues","text":"BCValues(func_interpol::Interpolation, geom_interpol::Interpolation, boundary_type::Union{Type{<:BoundaryIndex}})\n\nBCValues stores the shape values at all facet/faces/edges/vertices (depending on boundary_type) for the geometric interpolation (geom_interpol), for each dof-position determined by the func_interpol. Used mainly by the ConstraintHandler.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Internal-utilities","page":"FEValues","title":"Internal utilities","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.embedding_det\nFerrite.shape_value_type\nFerrite.shape_gradient_type\nFerrite.ValuesUpdateFlags","category":"page"},{"location":"devdocs/FEValues/#Ferrite.embedding_det","page":"FEValues","title":"Ferrite.embedding_det","text":"embedding_det(J::SMatrix{3, 2})\n\nEmbedding determinant for surfaces in 3D.\n\nTLDR: \"det(J) =\" ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂\n\nThe transformation theorem for some function f on a 2D surface in 3D space leads to ∫ f ⋅ dS = ∫ f ⋅ (∂x/∂ξ₁ × ∂x/∂ξ₂) dξ₁dξ₂ = ∫ f ⋅ n ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ dξ₁dξ₂ where ||∂x/∂ξ₁ × ∂x/∂ξ₂||₂ is \"detJ\" and n is the unit normal. See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation. For more details see e.g. the doctoral thesis by Mirza Cenanovic Tangential Calculus [12].\n\n\n\n\n\nembedding_det(J::Union{SMatrix{2, 1}, SMatrix{3, 1}})\n\nEmbedding determinant for curves in 2D and 3D.\n\nTLDR: \"det(J) =\" ||∂x/∂ξ||₂\n\nThe transformation theorem for some function f on a 1D curve in 2D and 3D space leads to ∫ f ⋅ dE = ∫ f ⋅ ∂x/∂ξ dξ = ∫ f ⋅ t ||∂x/∂ξ||₂ dξ where ||∂x/∂ξ||₂ is \"detJ\" and t is \"the unit tangent\". See e.g. https://scicomp.stackexchange.com/questions/41741/integration-of-d-1-dimensional-functions-on-finite-element-surfaces for simple explanation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/FEValues/#Ferrite.shape_value_type","page":"FEValues","title":"Ferrite.shape_value_type","text":"shape_value_type(fe_v::AbstractValues)\n\nReturn the type of shape_value(fe_v, q_point, base_function)\n\n\n\n\n\n","category":"function"},{"location":"devdocs/FEValues/#Ferrite.shape_gradient_type","page":"FEValues","title":"Ferrite.shape_gradient_type","text":"shape_gradient_type(fe_v::AbstractValues)\n\nReturn the type of shape_gradient(fe_v, q_point, base_function)\n\n\n\n\n\n","category":"function"},{"location":"devdocs/FEValues/#Ferrite.ValuesUpdateFlags","page":"FEValues","title":"Ferrite.ValuesUpdateFlags","text":"ValuesUpdateFlags(ip_fun::Interpolation; update_gradients = Val(true), update_hessians = Val(false), update_detJdV = Val(true))\n\nCreates a singelton type for specifying what parts of the AbstractValues should be updated. Note that this is internal API used to get type-stable construction. Keyword arguments in AbstractValues constructors are forwarded, and the public API is passing these as Bool, while the ValuesUpdateFlags method supports both boolean and Val(::Bool) keyword args.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/FEValues/#Custom-FEValues","page":"FEValues","title":"Custom FEValues","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Custom FEValues, fe_v::AbstractValues, should normally implement the reinit! method. Subtypes of AbstractValues have default implementations for some functions, but require some lower-level access functions, specifically","category":"page"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"function_value, requires\nshape_value\ngetnquadpoints\ngetnbasefunctions\nfunction_gradient, function_divergence, function_symmetric_gradient, and function_curl requires\nshape_gradient\ngetnquadpoints\ngetnbasefunctions\nspatial_coordinate, requires\ngeometric_value\ngetngeobasefunctions\ngetnquadpoints","category":"page"},{"location":"devdocs/FEValues/#Array-bounds","page":"FEValues","title":"Array bounds","text":"","category":"section"},{"location":"devdocs/FEValues/","page":"FEValues","title":"FEValues","text":"Asking for the nth quadrature point must be inside array bounds if 1 <= n <= getnquadpoints(fe_v). (checkquadpoint can, alternatively, be dispatched to check that n is inbounds.)\nAsking for the ith shape value or gradient must be inside array bounds if 1 <= i <= getnbasefunctions(fe_v)\nAsking for the ith geometric value must be inside array bounds if 1 <= i <= getngeobasefunctions(fe_v)","category":"page"},{"location":"topics/FEValues/#fevalues_topicguide","page":"FEValues","title":"FEValues","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"A key type of object in Ferrite is the so-called FEValues, where the most common ones are CellValues and FacetValues. These objects are used inside the element routines and are used to query the integration weights, shape function values and gradients, and much more; see CellValues and FacetValues. For these values to be correct, it is necessary to reinitialize these for the current cell by using the reinit! function. This function maps the values from the reference cell to the actual cell, a process described in detail below, see Mapping of finite elements. After that, we show an implementation of a SimpleCellValues type to illustrate how CellValues work for the most standard case, excluding the generalizations and optimization that complicates the actual code.","category":"page"},{"location":"topics/FEValues/#mapping_theory","page":"FEValues","title":"Mapping of finite elements","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The shape functions and gradients stored in an FEValues object, are reinitialized for each cell by calling the reinit! function. The main part of this calculation, considers how to map the values and derivatives of the shape functions, defined on the reference cell, to the actual cell.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The geometric mapping of a finite element from the reference coordinates to the real coordinates is shown in the following illustration.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"(Image: mapping_figure)","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"This mapping is given by the geometric shape functions, hatN_i^g(boldsymbolxi), such that","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n boldsymbolx(boldsymbolxi) = sum_alpha=1^N hatboldsymbolx_alpha hatN_alpha^g(boldsymbolxi) \n boldsymbolJ = fracmathrmdboldsymbolxmathrmdboldsymbolxi = sum_alpha=1^N hatboldsymbolx_alpha otimes fracmathrmd hatN_alpha^gmathrmdboldsymbolxi\n boldsymbolmathcalH =\n fracmathrmd boldsymbolJmathrmd boldsymbolxi = sum_alpha=1^N hatboldsymbolx_alpha otimes fracmathrmd^2 hatN^g_alphamathrmd boldsymbolxi^2\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"where the defined boldsymbolJ is the jacobian of the mapping, and in some cases we will also need the corresponding hessian, boldsymbolmathcalH (3rd order tensor).","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"We require that the mapping from reference coordinates to real coordinates is diffeomorphic, meaning that we can express boldsymbolx = boldsymbolx(boldsymbolxi(boldsymbolx)), such that","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n fracmathrmdboldsymbolxmathrmdboldsymbolx = boldsymbolI = fracmathrmdboldsymbolxmathrmdboldsymbolxi cdot fracmathrmdboldsymbolximathrmdboldsymbolx\n quadRightarrowquad\n fracmathrmdboldsymbolximathrmdboldsymbolx = leftfracmathrmdboldsymbolxmathrmdboldsymbolxiright^-1 = boldsymbolJ^-1\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Depending on the function interpolation, we may want different types of mappings to conserve certain properties of the fields. This results in the different mapping types described below.","category":"page"},{"location":"topics/FEValues/#Identity-mapping","page":"FEValues","title":"Identity mapping","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.IdentityMapping","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"For scalar fields, we always use scalar base functions. For tensorial fields (non-scalar, e.g. vector-fields), the base functions can be constructed from scalar base functions, by using e.g. VectorizedInterpolation. From the perspective of the mapping, however, each component is mapped as an individual scalar base function. And for scalar base functions, we only require that the value of the base function is invariant to the element shape (real coordinate), and only depends on the reference coordinate, i.e.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n N(boldsymbolx) = hatN(boldsymbolxi(boldsymbolx))nonumber \n mathrmgrad(N(boldsymbolx)) = fracmathrmdhatNmathrmdboldsymbolxi cdot boldsymbolJ^-1\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Second order gradients of the shape functions are computed as","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n mathrmgrad(mathrmgrad(N(boldsymbolx))) = fracmathrmd^2 Nmathrmdboldsymbolx^2 = boldsymbolJ^-T cdot fracmathrmd^2hatNmathrmdboldsymbolxi^2 cdot boldsymbolJ^-1 - boldsymbolJ^-T cdotmathrmgrad(N) cdot boldsymbolmathcalH cdot boldsymbolJ^-1\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"details: Derivation\nThe gradient of the shape functions is obtained using the chain rule:beginalign*\n fracmathrmd Nmathrmdx_i = fracmathrmd hat Nmathrmd xi_rfracmathrmd xi_rmathrmd x_i = fracmathrmd hat Nmathrmd xi_r J^-1_ri\nendalign*For the second order gradients, we first use the product rule on the equation above:beginalign\n fracmathrmd^2 Nmathrmdx_i mathrmdx_j = fracmathrmdmathrmdx_jleftfracmathrmd hat Nmathrmd xi_rright J^-1_ri + fracmathrmd hat Nmathrmd xi_r fracmathrmdJ^-1_rimathrmdx_j\nendalignUsing the fact that fracmathrmdhatf(boldsymbolxi)mathrmdx_j = fracmathrmdhatf(boldsymbolxi)mathrmdxi_s J^-1_sj, the first term in the equation above can be expressed as:beginalign*\n fracmathrmdmathrmdx_jleftfracmathrmd hat Nmathrmd xi_rright J^-1_ri = J^-1_sjfracmathrmdmathrmdxi_sleftfracmathrmd hat Nmathrmd xi_rright J^-1_ri = J^-1_sjleftfracmathrmd^2 hat Nmathrmd xi_smathrmd xi_rright J^-1_ri\nendalign*The second term can be written as:beginalign*\n fracmathrmd hat Nmathrmd xi_rfracmathrmdJ^-1_rimathrmdx_j = fracmathrmd hat Nmathrmd xi_rleftfracmathrmdJ^-1_rimathrmdxi_srightJ^-1_sj = fracmathrmd hat Nmathrmd xi_rleft- J^-1_rkmathcalH_kps J^-1_piright J^-1_sj = - fracmathrmd hat Nmathrmd x_kmathcalH_kps J^-1_piJ^-1_sj\nendalign*where we have used that the inverse of the jacobian can be computed as:beginalign*\n0 = fracmathrmdmathrmdxi_s (J_kr J^-1_ri ) = fracmathrmdJ_kpmathrmdxi_s J^-1_pi + J_kr fracmathrmdJ^-1_rimathrmdxi_s = 0 quad Rightarrow \nendalign*beginalign*\nfracmathrmdJ^-1_rimathrmdxi_s = - J^-1_rkfracmathrmdJ_kpmathrmdxi_s J^-1_pi = - J^-1_rkmathcalH_kps J^-1_pi\nendalign*","category":"page"},{"location":"topics/FEValues/#Covariant-Piola-mapping,-H(curl)","page":"FEValues","title":"Covariant Piola mapping, H(curl)","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.CovariantPiolaMapping","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The covariant Piola mapping of a vectorial base function preserves the tangential components. For the value, the mapping is defined as","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n boldsymbolN(boldsymbolx) = boldsymbolJ^-mathrmT cdot hatboldsymbolN(boldsymbolxi(boldsymbolx))\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"which yields the gradient,","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n mathrmgrad(boldsymbolN(boldsymbolx)) = boldsymbolJ^-T cdot fracmathrmd hatboldsymbolNmathrmd boldsymbolxi cdot boldsymbolJ^-1 - boldsymbolJ^-T cdot lefthatboldsymbolN(boldsymbolxi(boldsymbolx))cdot boldsymbolJ^-1 cdot boldsymbolmathcalHcdot boldsymbolJ^-1right\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"details: Derivation\nExpressing the gradient, mathrmgrad(boldsymbolN), in index notation,beginalign*\n fracmathrmd N_imathrmd x_j = fracmathrmdmathrmd x_j leftJ^-mathrmT_ik hatN_kright = fracmathrmd J^-mathrmT_ikmathrmd x_j hatN_k + J^-mathrmT_ik fracmathrmd hatN_kmathrmd xi_l J_lj^-1\nendalign*Except for a few elements, boldsymbolJ varies as a function of boldsymbolx. The derivative can be calculated asbeginalign*\n fracmathrmd J^-mathrmT_ikmathrmd x_j = fracmathrmd J^-mathrmT_ikmathrmd J_mn fracmathrmd J_mnmathrmd x_j = - J_km^-1 J_in^-T fracmathrmd J_mnmathrmd x_j nonumber \n fracmathrmd J_mnmathrmd x_j = mathcalH_mno J_oj^-1\nendalign*","category":"page"},{"location":"topics/FEValues/#Contravariant-Piola-mapping,-H(div)","page":"FEValues","title":"Contravariant Piola mapping, H(div)","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"Ferrite.ContravariantPiolaMapping","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"The covariant Piola mapping of a vectorial base function preserves the normal components. For the value, the mapping is defined as","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n boldsymbolN(boldsymbolx) = fracboldsymbolJdet(boldsymbolJ) cdot hatboldsymbolN(boldsymbolxi(boldsymbolx))\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"This gives the gradient","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"beginalign*\n mathrmgrad(boldsymbolN(boldsymbolx)) = boldsymbolmathcalHcdotboldsymbolJ^-1 fracboldsymbolI underlineotimes boldsymbolI cdot hatboldsymbolNdet(boldsymbolJ)\n - leftfracboldsymbolJ cdot hatboldsymbolNdet(boldsymbolJ)right otimes leftboldsymbolJ^-T boldsymbolmathcalH cdot boldsymbolJ^-1right\n + boldsymbolJ cdot fracmathrmd hatboldsymbolNmathrmd boldsymbolxi cdot fracboldsymbolJ^-1det(boldsymbolJ)\nendalign*","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"details: Derivation\nExpressing the gradient, mathrmgrad(boldsymbolN), in index notation,beginalign*\n fracmathrmd N_imathrmd x_j = fracmathrmdmathrmd x_j leftfracJ_ikdet(boldsymbolJ) hatN_kright =nonumber\n = fracmathrmd J_ikmathrmd x_j frachatN_kdet(boldsymbolJ)\n - fracmathrmd det(boldsymbolJ)mathrmd x_j fracJ_ik hatN_kdet(boldsymbolJ)^2\n + fracJ_ikdet(boldsymbolJ) fracmathrmd hatN_kmathrmd xi_l J_lj^-1 \n = mathcalH_ikl J^-1_lj frachatN_kdet(boldsymbolJ)\n - J^-T_mn mathcalH_mnl J^-1_lj fracJ_ik hatN_kdet(boldsymbolJ)\n + fracJ_ikdet(boldsymbolJ) fracmathrmd hatN_kmathrmd xi_l J_lj^-1\nendalign*","category":"page"},{"location":"topics/FEValues/#SimpleCellValues","page":"FEValues","title":"Walkthrough: Creating SimpleCellValues","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"In the following, we walk through how to create a SimpleCellValues type which works similar to Ferrite's CellValues, but is not performance optimized and not as general. The main purpose is to explain how the CellValues works for the standard case of IdentityMapping described above. Please note that several internal functions are used, and these may change without a major version increment. Please see the Developer documentation for their documentation.","category":"page"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"# Include the example here, but modify the Literate output to suit being embedded\nusing Literate, Markdown\nbase_name = \"SimpleCellValues_literate\"\nLiterate.markdown(string(base_name, \".jl\"); name = base_name, execute = true, credit = false, documenter=false)\ncontent = read(string(base_name, \".md\"), String)\nrm(string(base_name, \".md\"))\nrm(string(base_name, \".jl\"))\nMarkdown.parse(content)","category":"page"},{"location":"topics/FEValues/#Further-reading","page":"FEValues","title":"Further reading","text":"","category":"section"},{"location":"topics/FEValues/","page":"FEValues","title":"FEValues","text":"defelement.com\nKirby (2017) [5]","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/grid/#Grid","page":"Grid","title":"Grid","text":"","category":"section"},{"location":"topics/grid/#Mesh-reading","page":"Grid","title":"Mesh reading","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"A Ferrite Grid can be generated with the generate_grid function. More advanced meshes can be imported with the FerriteMeshParser.jl (from Abaqus input files), or even created and translated with the Gmsh.jl and FerriteGmsh.jl package, respectively.","category":"page"},{"location":"topics/grid/#FerriteGmsh.jl","page":"Grid","title":"FerriteGmsh.jl","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteGmsh.jl supports all defined cells with an alias in Ferrite.jl as well as the 3D Serendipity Cell{3,20,6}. Either, a mesh is created on the fly with the gmsh API or a mesh in .msh or .geo format can be read and translated with the FerriteGmsh.togrid function.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteGmsh.togrid","category":"page"},{"location":"topics/grid/#FerriteGmsh.togrid","page":"Grid","title":"FerriteGmsh.togrid","text":"togrid(filename::String; domain=\"\")\n\nOpen the Gmsh file filename (ie a .geo or .msh file) and return the corresponding Ferrite.Grid.\n\n\n\n\n\ntogrid(; domain=\"\")\n\nGenerate a Ferrite.Grid from the current active/open model in the Gmsh library.\n\n\n\n\n\n","category":"function"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteGmsh supports currently the translation of cellsets and facetsets. Such sets are defined in Gmsh as PhysicalGroups of dimension dim and dim-1, respectively. In case only a part of the mesh is the domain, the domain can be specified by providing the keyword argument domain the name of the PhysicalGroups in the FerriteGmsh.togrid function.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"note: Why you should read a .msh file\nReading a .msh file is the advertised way, since otherwise you remesh whenever you run the code. Further, if you choose to read the grid directly from the current model of the gmsh API you get artificial nodes, which doesn't harm the FE computation, but maybe distort your sophisticated grid operations (if present). For more information, see this issue.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"If you want to read another, not yet supported cell from gmsh, consider to open a PR at FerriteGmsh that extends the gmshtoferritecell dict and if needed, reorder the element nodes by dispatching FerriteGmsh.translate_elements. The reordering of nodes is necessary if the Gmsh ordering doesn't match the one from Ferrite. Gmsh ordering is documented here. For an exemplary usage of Gmsh.jl and FerriteGmsh.jl, consider the Stokes flow and Incompressible Navier-Stokes Equations via DifferentialEquations.jl example.","category":"page"},{"location":"topics/grid/#FerriteMeshParser.jl","page":"Grid","title":"FerriteMeshParser.jl","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteMeshParser.jl converts the mesh in an Abaqus input file (.inp) to a Ferrite.Grid with its function get_ferrite_grid. The translations for most of Abaqus' standard 2d and 3d continuum elements to a Ferrite.AbstractCell are defined. Custom translations can be given as input, which can be used to import other (custom) elements or to override the default translation.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"FerriteMeshParser.get_ferrite_grid","category":"page"},{"location":"topics/grid/#FerriteMeshParser.get_ferrite_grid","page":"Grid","title":"FerriteMeshParser.get_ferrite_grid","text":"function get_ferrite_grid(\n filename; \n meshformat=AutomaticMeshFormat(), \n user_elements=Dict{String,DataType}(), \n generate_facetsets=true\n )\n\nCreate a Ferrite.Grid by reading in the file specified by filename.\n\nOptional arguments:\n\nmeshformat: Which format the mesh is given in, normally automatically detected by the file extension\nuser_elements: Used to add extra elements not supported, might require a separate cell constructor.\ngenerate_facetsets: Should facesets be automatically generated from all nodesets?\n\n\n\n\n\n","category":"function"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"If you are missing the translation of an Abaqus element that is equivalent to a Ferrite.AbstractCell, consider to open an issue or a pull request.","category":"page"},{"location":"topics/grid/#Grid-datastructure","page":"Grid","title":"Grid datastructure","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"In Ferrite a Grid is a collection of Nodes and Cells and is parameterized in its physical dimensionality and cell type. Nodes are points in the physical space and can be initialized by a N-Tuple, where N corresponds to the dimensions.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"n1 = Node((0.0, 0.0))","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Cells are defined based on the Node IDs. Hence, they collect IDs in a N-Tuple. Consider the following 2D mesh:","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"(Image: global mesh)","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The cells of the grid can be described in the following way","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"cells = [Quadrilateral((1, 2, 5, 4)),\n Quadrilateral((2, 3, 6, 5)),\n Quadrilateral((4, 5, 8, 7)),\n Quadrilateral((5, 6, 9, 8))]","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"where each Quadrilateral <: AbstractCell is defined by the tuple of node IDs. Additionally, the data structure Grid contains node-, cell-, facet-, and vertexsets. Each of these sets is defined by a Dict{String, OrderedSet}.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Node- and cellsets are represented by an OrderedSet{Int}, giving a set of node or cell ID, respectively.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Facet- and vertexsets are represented by OrderedSet{<:BoundaryIndex}, where BoundaryIndex is a FacetIndex or VertexIndex respectively. FacetIndex and VertexIndex wraps a Tuple, (global_cell_id, local_facet_id) and (global_cell_id, local_vertex_id), where the local IDs are defined according to the reference shapes, see Reference shapes.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The highlighted facets, i.e. the two edges from node ID 3 to 6 and from 6 to 9, on the right hand side of our test mesh can now be described as","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"boundary_facets = [(3, 6), (6, 9)]","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"i.e. by using the node IDs of the reference shape vertices.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The first of these can be found as the 2nd facet of the 2nd cell.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"using Ferrite #hide\nFerrite.facets(Quadrilateral((2, 3, 6, 5)))","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"The unique representation of an entity is given by the sorted version of this tuple. While we could use this information to construct a facet set, Ferrite can construct this set by filtering based on the coordinates, using addfacetset!.","category":"page"},{"location":"topics/grid/#AbstractGrid","page":"Grid","title":"AbstractGrid","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"It can be very useful to use a grid type for a certain special case, e.g. mixed cell types, adaptivity, IGA, etc. In order to define your own <: AbstractGrid you need to fulfill the AbstractGrid interface. In case that certain structures are preserved from the Ferrite.Grid type, you don't need to dispatch on your own type, but rather rely on the fallback AbstractGrid dispatch.","category":"page"},{"location":"topics/grid/#Example","page":"Grid","title":"Example","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"As a starting point, we choose a minimal working example from the test suite:","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"struct SmallGrid{dim,N,C<:Ferrite.AbstractCell} <: Ferrite.AbstractGrid{dim}\n nodes_test::Vector{NTuple{dim,Float64}}\n cells_test::NTuple{N,C}\nend","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Here, the names of the fields as well as their underlying datastructure changed compared to the Grid type. This would lead to the fact, that any usage with the utility functions and DoF management will not work. So, we need to feed into the interface how to handle this subtyped datastructure. We start with the utility functions that are associated with the cells of the grid:","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Ferrite.getcells(grid::SmallGrid) = grid.cells_test\nFerrite.getcells(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.cells_test[v]\nFerrite.getncells(grid::SmallGrid{dim,N}) where {dim,N} = N\nFerrite.getcelltype(grid::SmallGrid) = eltype(grid.cells_test)\nFerrite.getcelltype(grid::SmallGrid, i::Int) = typeof(grid.cells_test[i])","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Next, we define some helper functions that take care of the node handling.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Ferrite.getnodes(grid::SmallGrid) = grid.nodes_test\nFerrite.getnodes(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.nodes_test[v]\nFerrite.getnnodes(grid::SmallGrid) = length(grid.nodes_test)\nFerrite.get_coordinate_eltype(::SmallGrid) = Float64\nFerrite.get_coordinate_type(::SmallGrid{dim}) where dim = Vec{dim,Float64}\nFerrite.nnodes_per_cell(grid::SmallGrid, i::Int=1) = Ferrite.nnodes(grid.cells_test[i])","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"These definitions make many of Ferrite functions work out of the box, e.g. you can now call getcoordinates(grid, cellid) on the SmallGrid.","category":"page"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Now, you would be able to assemble the heat equation example over the new custom SmallGrid type. Note that this particular subtype isn't able to handle boundary entity sets and so, you can't describe boundaries with it. In order to use boundaries, e.g. for Dirichlet constraints in the ConstraintHandler, you would need to dispatch the AbstractGrid sets utility functions on SmallGrid.","category":"page"},{"location":"topics/grid/#Topology","page":"Grid","title":"Topology","text":"","category":"section"},{"location":"topics/grid/","page":"Grid","title":"Grid","text":"Ferrite.jl's Grid type offers experimental features w.r.t. topology information. The functions getneighborhood and facetskeleton are the interface to obtain topological information. The getneighborhood can construct lists of directly connected entities based on a given entity (CellIndex, FacetIndex, FaceIndex, EdgeIndex, or VertexIndex). The facetskeleton function can be used to evaluate integrals over material interfaces or computing element interface values such as jumps.","category":"page"},{"location":"topics/sparse_matrix/#topic-sparse-matrix","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"An important property of the finite element method is that it results in sparse matrices for the linear systems to be solved. On this page the topic of sparsity and sparse matrices are discussed.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Pages = [\"sparse_matrix.md\"]\nDepth = 2:2","category":"page"},{"location":"topics/sparse_matrix/#Sparsity-pattern","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The sparse structure of the linear system depends on many factors such as e.g. the weak form, the discretization, and the choice of interpolation(s). In the end it boils down to how the degrees of freedom (DoFs) couple with each other. The most common reason that two DoFs couple is because they belong to the same element. Note, however, that this is not guaranteed to result in a coupling since it depends on the specific weak form that is being discretized, see e.g. Increasing the sparsity. Boundary conditions and constraints can also result in additional DoF couplings.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"If DoFs i and j couple, then the computed value in the eventual matrix will be structurally nonzero[1]. In this case the entry (i, j) should be included in the sparsity pattern. Conversely, if DoFs i and j don't couple, then the computed value will be zero. In this case the entry (i, j) should not be included in the sparsity pattern since there is no need to allocate memory for entries that will be zero.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The sparsity, i.e. the ratio of zero-entries to the total number of entries, is often[2] very high and taking advantage of this results in huge savings in terms of memory. For example, in a problem with 10^6 DoFs there will be a matrix of size 10^6 times 10^6. If all 10^12 entries of this matrix had to be stored (0% sparsity) as double precision (Float64, 8 bytes) it would require 8 TB of memory. If instead the sparsity is 99.9973% (which is the case when solving the heat equation on a three dimensional hypercube with linear Lagrange interpolation) this would be reduced to 216 MB.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"[1]: Structurally nonzero means that there is a possibility of a nonzero value even though the computed value might become zero in the end for various reasons.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"[2]: At least for most practical problems using low order interpolations.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"details: Sparsity pattern example\nTo give an example, in this one-dimensional heat problem (see the Heat equation tutorial for the weak form) we have 4 nodes with 3 elements in between. For simplicitly DoF numbers and node numbers are the same but this is not true in general since nodes and DoFs can be numbered independently (and in fact are numbered independently in Ferrite).1 ----- 2 ----- 3 ----- 4Assuming we use linear Lagrange interpolation (the \"hat functions\") this will give the following connections according to the weak form:Trial function 1 couples with test functions 1 and 2 (entries (1, 1) and (1, 2) included in the sparsity pattern)\nTrial function 2 couples with test functions 1, 2, and 3 (entries (2, 1), (2, 2), and (2, 3) included in the sparsity pattern)\nTrial function 3 couples with test functions 2, 3, and 4 (entries (3, 2), (3, 3), and (3, 4) included in the sparsity pattern)\nTrial function 4 couples with test functions 3 and 4 (entries (4, 3) and (4, 4) included in the sparsity pattern)The resulting sparsity pattern would look like this:4×4 SparseArrays.SparseMatrixCSC{Float64, Int64} with 10 stored entries:\n 0.0 0.0 ⋅ ⋅\n 0.0 0.0 0.0 ⋅\n ⋅ 0.0 0.0 0.0\n ⋅ ⋅ 0.0 0.0Moreover, if the problem is solved with periodic boundary conditions, for example by constraining the value on the right side to the value on the left side, there will be additional couplings. In the example above, this means that DoF 4 should be equal to DoFSince DoF 4 is constrained it has to be eliminated from the system. Existing entriesthat include DoF 4 are (3, 4), (4, 3), and (4, 4). Given the simple constraint in this case we can simply replace DoF 4 with DoF 1 in these entries and we end up with entries (3, 1), (1, 3), and (1, 1). This results in two new entries: (3, 1) and (1, 3) (entry (1, 1) is already included).","category":"page"},{"location":"topics/sparse_matrix/#Creating-sparsity-patterns","page":"Sparsity pattern and sparse matrices","title":"Creating sparsity patterns","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Creating a sparsity pattern can be quite expensive if not done properly and therefore Ferrite provides efficient methods and data structures for this. In general the sparsity pattern is not known in advance and has to be created incrementally. To make this incremental construction efficient it is necessary to use a dynamic data structure which allow for fast insertions.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The sparsity pattern also serves as a \"matrix builder\". When all entries are inserted into the sparsity pattern the dynamic data structure is typically converted, or \"compressed\", into a sparse matrix format such as e.g. the compressed sparse row (CSR) format or the compressed sparse column (CSC) format, where the latter is the default sparse matrix type implemented in the SparseArrays standard library. These matrix formats allow for fast linear algebra operations, such as factorizations and matrix-vector multiplications, that are needed when the linear system is solved. See Instantiating the sparse matrix for more details.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"In summary, a dynamic structure is more efficient when incrementally building the pattern by inserting new entries, and a static or compressed structure is more efficient for linear algebra operations.","category":"page"},{"location":"topics/sparse_matrix/#Basic-sparsity-patterns-construction","page":"Sparsity pattern and sparse matrices","title":"Basic sparsity patterns construction","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Working with the sparsity pattern explicitly is in many cases not necessary. For basic usage (e.g. when only one matrix needed, when no customization of the pattern is required, etc) there exist convenience methods of allocate_matrix that return the matrix directly. Most examples in this documentation don't deal with the sparsity pattern explicitly because the basic method suffice. See also Instantiating the sparse matrix for more details.","category":"page"},{"location":"topics/sparse_matrix/#Custom-sparsity-pattern-construction","page":"Sparsity pattern and sparse matrices","title":"Custom sparsity pattern construction","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"In more advanced cases there might be a need for more fine grained control of the sparsity pattern. The following steps are typically taken when constructing a sparsity pattern in Ferrite:","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Initialize an empty pattern: This can be done by either using the init_sparsity_pattern(dh) function or by using a constructor directly. init_sparsity_pattern will return a default pattern type that is compatible with the DofHandler. In some cases you might require another type of pattern (for example a blocked pattern, see Blocked sparsity pattern) and in that case you can use the constructor directly.\nAdd entries to the pattern: There are a number of functions that add entries to the pattern:\nadd_sparsity_entries! is a convenience method for performing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries! after each other (see below).\nadd_cell_entries! adds entries for all couplings between the DoFs within each element. These entries correspond to assembling the standard element matrix and is thus almost always required.\nadd_interface_entries! adds entries for couplings between the DoFs in neighboring elements. These entries are required when integrating along internal interfaces between elements (e.g. for discontinuous Galerkin methods).\nadd_constraint_entries! adds entries required from constraints and boundary conditions in the ConstraintHandler. Note that this operation depends on existing entries in the pattern and must be called as the last operation on the pattern.\nFerrite.add_entry! adds a single entry to the pattern. This can be used if you need to add custom entries that are not covered by the other functions.\nInstantiate the matrix: A sparse matrix can be created from the sparsity pattern using allocate_matrix, see Instantiating the sparse matrix below for more details.","category":"page"},{"location":"topics/sparse_matrix/#Increasing-the-sparsity","page":"Sparsity pattern and sparse matrices","title":"Increasing the sparsity","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"By default, when creating a sparsity pattern, it is assumed that each DoF within an element couple with with all other DoFs in the element.","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"todo: Todo\nDiscuss the coupling keyword argument.\nDiscuss the keep_constrained keyword argument.","category":"page"},{"location":"topics/sparse_matrix/#Blocked-sparsity-pattern","page":"Sparsity pattern and sparse matrices","title":"Blocked sparsity pattern","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"todo: Todo\nDiscuss BlockSparsityPattern and BlockArrays extension.","category":"page"},{"location":"topics/sparse_matrix/#Instantiating-the-sparse-matrix","page":"Sparsity pattern and sparse matrices","title":"Instantiating the sparse matrix","text":"","category":"section"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"As mentioned above, for many simple cases there is no need to work with the sparsity pattern directly and using methods of allocate_matrix that take the DofHandler as input is enough, for example:","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"K = allocate_matrix(dh, ch)","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"allocate_matrix is also used to instantiate a matrix from a sparsity pattern, for example:","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"K = allocate_matrix(sp)","category":"page"},{"location":"topics/sparse_matrix/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"note: Multiple matrices with the same pattern\nFor some problems there is a need for multiple matrices with the same sparsity pattern, for example a mass matrix and a stiffness matrix. In this case it is more efficient to create the sparsity pattern once and then instantiate both matrices from it.","category":"page"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/dofhandler/#Degrees-of-Freedom","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"Degrees of freedom (dofs) are distributed by the DofHandler.","category":"page"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"DofHandler\nSubDofHandler","category":"page"},{"location":"reference/dofhandler/#Ferrite.DofHandler","page":"Degrees of Freedom","title":"Ferrite.DofHandler","text":"DofHandler(grid::Grid)\n\nConstruct a DofHandler based on the grid grid.\n\nAfter construction any number of discrete fields can be added to the DofHandler using add!. Construction is finalized by calling close!.\n\nBy default fields are added to all elements of the grid. Refer to SubDofHandler for restricting fields to subdomains of the grid.\n\nExamples\n\ndh = DofHandler(grid)\nip_u = Lagrange{RefTriangle, 2}()^2 # vector interpolation for a field u\nip_p = Lagrange{RefTriangle, 1}() # scalar interpolation for a field p\nadd!(dh, :u, ip_u)\nadd!(dh, :p, ip_p)\nclose!(dh)\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.SubDofHandler","page":"Degrees of Freedom","title":"Ferrite.SubDofHandler","text":"SubDofHandler(dh::AbstractDofHandler, cellset::AbstractVecOrSet{Int})\n\nCreate an sdh::SubDofHandler from the parent dh, pertaining to the cells in cellset. This allows you to add fields to parts of the domain, or using different interpolations or cell types (e.g. Triangles and Quadrilaterals). All fields and cell types must be the same in one SubDofHandler.\n\nAfter construction any number of discrete fields can be added to the SubDofHandler using add!. Construction is finalized by calling close! on the parent dh.\n\nExamples\n\nWe assume we have a grid containing \"Triangle\" and \"Quadrilateral\" cells, including the cellsets \"triangles\" and \"quadilaterals\" for to these cells.\n\ndh = DofHandler(grid)\n\nsdh_tri = SubDofHandler(dh, getcellset(grid, \"triangles\"))\nip_tri = Lagrange{RefTriangle, 2}()^2 # vector interpolation for a field u\nadd!(sdh_tri, :u, ip_tri)\n\nsdh_quad = SubDofHandler(dh, getcellset(grid, \"quadilaterals\"))\nip_quad = Lagrange{RefQuadrilateral, 2}()^2 # vector interpolation for a field u\nadd!(sdh_quad, :u, ip_quad)\n\nclose!(dh) # Finalize by closing the parent\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Adding-fields-to-the-DofHandlers","page":"Degrees of Freedom","title":"Adding fields to the DofHandlers","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"add!(::DofHandler, ::Symbol, ::Interpolation)\nadd!(::SubDofHandler, ::Symbol, ::Interpolation)\nclose!(::DofHandler)","category":"page"},{"location":"reference/dofhandler/#Ferrite.add!-Tuple{DofHandler, Symbol, Interpolation}","page":"Degrees of Freedom","title":"Ferrite.add!","text":"add!(dh::DofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the DofHandler dh.\n\nThe field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.\n\n\n\n\n\n","category":"method"},{"location":"reference/dofhandler/#Ferrite.add!-Tuple{SubDofHandler, Symbol, Interpolation}","page":"Degrees of Freedom","title":"Ferrite.add!","text":"add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the SubDofHandler sdh.\n\n\n\n\n\n","category":"method"},{"location":"reference/dofhandler/#Ferrite.close!-Tuple{DofHandler}","page":"Degrees of Freedom","title":"Ferrite.close!","text":"close!(dh::AbstractDofHandler)\n\nCloses dh and creates degrees of freedom for each cell.\n\n\n\n\n\n","category":"method"},{"location":"reference/dofhandler/#Dof-renumbering","page":"Degrees of Freedom","title":"Dof renumbering","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"renumber!\nDofOrder.FieldWise\nDofOrder.ComponentWise","category":"page"},{"location":"reference/dofhandler/#Ferrite.renumber!","page":"Degrees of Freedom","title":"Ferrite.renumber!","text":"renumber!(dh::AbstractDofHandler, order)\nrenumber!(dh::AbstractDofHandler, ch::ConstraintHandler, order)\n\nRenumber the degrees of freedom in the DofHandler and/or ConstraintHandler according to the ordering order.\n\norder can be given by one of the following options:\n\nA permutation vector perm::AbstractVector{Int} such that dof i is renumbered to perm[i].\nDofOrder.FieldWise() for renumbering dofs field wise.\nDofOrder.ComponentWise() for renumbering dofs component wise.\nDofOrder.Ext{T} for \"external\" renumber permutations, see documentation for DofOrder.Ext for details.\n\nwarning: Warning\nThe dof numbering in the DofHandler and ConstraintHandler must always be consistent. It is therefore necessary to either renumber before creating the ConstraintHandler in the first place, or to renumber the DofHandler and the ConstraintHandler together.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.DofOrder.FieldWise","page":"Degrees of Freedom","title":"Ferrite.DofOrder.FieldWise","text":"DofOrder.FieldWise()\nDofOrder.FieldWise(target_blocks::Vector{Int})\n\nDof order passed to renumber! to renumber global dofs field wise resulting in a globally blocked system.\n\nThe default behavior is to group dofs of each field into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of the same length as the total number of fields in the DofHandler (see getfieldnames(dh)) that maps each field to a \"target block\": to renumber a DofHandler with three fields :u, :v, :w such that dofs for :u and :w end up in the first global block, and dofs for :v in the second global block use DofOrder.FieldWise([1, 2, 1]).\n\nThis renumbering is stable such that the original relative ordering of dofs within each target block is maintained.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.DofOrder.ComponentWise","page":"Degrees of Freedom","title":"Ferrite.DofOrder.ComponentWise","text":"DofOrder.ComponentWise()\nDofOrder.ComponentWise(target_blocks::Vector{Int})\n\nDof order passed to renumber! to renumber global dofs component wise resulting in a globally blocked system.\n\nThe default behavior is to group dofs of each component into their own block, with the same order as in the DofHandler. This can be customized by passing a vector of length ncomponents that maps each component to a \"target block\" (see DofOrder.FieldWise for details).\n\nThis renumbering is stable such that the original relative ordering of dofs within each target block is maintained.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Common-methods","page":"Degrees of Freedom","title":"Common methods","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"ndofs\nndofs_per_cell\ndof_range\ncelldofs\ncelldofs!","category":"page"},{"location":"reference/dofhandler/#Ferrite.ndofs","page":"Degrees of Freedom","title":"Ferrite.ndofs","text":"ndofs(dh::AbstractDofHandler)\n\nReturn the number of degrees of freedom in dh\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.ndofs_per_cell","page":"Degrees of Freedom","title":"Ferrite.ndofs_per_cell","text":"ndofs_per_cell(dh::AbstractDofHandler[, cell::Int=1])\n\nReturn the number of degrees of freedom for the cell with index cell.\n\nSee also ndofs.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.dof_range","page":"Degrees of Freedom","title":"Ferrite.dof_range","text":"dof_range(sdh::SubDofHandler, field_idx::Int)\ndof_range(sdh::SubDofHandler, field_name::Symbol)\ndof_range(dh:DofHandler, field_name::Symbol)\n\nReturn the local dof range for a given field. The field can be specified by its name or index, where field_idx represents the index of a field within a SubDofHandler and field_idxs is a tuple of the SubDofHandler-index within the DofHandler and the field_idx.\n\nnote: Note\nThe dof_range of a field can vary between different SubDofHandlers. Therefore, it is advised to use the field_idxs or refer to a given SubDofHandler directly in case several SubDofHandlers exist. Using the field_name will always refer to the first occurrence of field within the DofHandler.\n\nExample:\n\njulia> grid = generate_grid(Triangle, (3, 3))\nGrid{2, Triangle, Float64} with 18 Triangle cells and 16 nodes\n\njulia> dh = DofHandler(grid); add!(dh, :u, 3); add!(dh, :p, 1); close!(dh);\n\njulia> dof_range(dh, :u)\n1:9\n\njulia> dof_range(dh, :p)\n10:12\n\njulia> dof_range(dh, (1,1)) # field :u\n1:9\n\njulia> dof_range(dh.subdofhandlers[1], 2) # field :p\n10:12\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.celldofs","page":"Degrees of Freedom","title":"Ferrite.celldofs","text":"celldofs(dh::AbstractDofHandler, i::Int)\n\nReturn a vector with the degrees of freedom that belong to cell i.\n\nSee also celldofs!.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Ferrite.celldofs!","page":"Degrees of Freedom","title":"Ferrite.celldofs!","text":"celldofs!(global_dofs::Vector{Int}, dh::AbstractDofHandler, i::Int)\n\nStore the degrees of freedom that belong to cell i in global_dofs.\n\nSee also celldofs.\n\n\n\n\n\n","category":"function"},{"location":"reference/dofhandler/#Grid-iterators","page":"Degrees of Freedom","title":"Grid iterators","text":"","category":"section"},{"location":"reference/dofhandler/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"CellCache\nCellIterator\nFacetCache\nFacetIterator\nInterfaceCache\nInterfaceIterator","category":"page"},{"location":"reference/dofhandler/#Ferrite.CellCache","page":"Degrees of Freedom","title":"Ferrite.CellCache","text":"CellCache(grid::Grid)\nCellCache(dh::AbstractDofHandler)\n\nCreate a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell. The cache is updated for a new cell by calling reinit!(cache, cellid) where cellid::Int is the cell id.\n\nMethods with CellCache\n\nreinit!(cc, i): reinitialize the cache for cell i\ncellid(cc): get the cell id of the currently cached cell\ngetnodes(cc): get the global node ids of the cell\ngetcoordinates(cc): get the coordinates of the cell\ncelldofs(cc): get the global dof ids of the cell\nreinit!(fev, cc): reinitialize CellValues or FacetValues\n\nSee also CellIterator.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.CellIterator","page":"Degrees of Freedom","title":"Ferrite.CellIterator","text":"CellIterator(grid::Grid, cellset=1:getncells(grid))\nCellIterator(dh::AbstractDofHandler, cellset=1:getncells(dh))\n\nCreate a CellIterator to conveniently iterate over all, or a subset, of the cells in a grid. The elements of the iterator are CellCaches which are properly reinit!ialized. See CellCache for more details.\n\nLooping over a CellIterator, i.e.:\n\nfor cc in CellIterator(grid, cellset)\n # ...\nend\n\nis thus simply convenience for the following equivalent snippet:\n\ncc = CellCache(grid)\nfor idx in cellset\n reinit!(cc, idx)\n # ...\nend\n\nwarning: Warning\nCellIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.FacetCache","page":"Degrees of Freedom","title":"Ferrite.FacetCache","text":"FacetCache(grid::Grid)\nFacetCache(dh::AbstractDofHandler)\n\nCreate a cache object with pre-allocated memory for the nodes, coordinates, and dofs of a cell suitable for looping over faces in a grid. The cache is updated for a new face by calling reinit!(cache, fi::FacetIndex).\n\nMethods with fc::FacetCache\n\nreinit!(fc, fi): reinitialize the cache for face fi::FacetIndex\ncellid(fc): get the current cellid\ngetnodes(fc): get the global node ids of the cell\ngetcoordinates(fc): get the coordinates of the cell\ncelldofs(fc): get the global dof ids of the cell\nreinit!(fv, fc): reinitialize FacetValues\n\nSee also FacetIterator.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.FacetIterator","page":"Degrees of Freedom","title":"Ferrite.FacetIterator","text":"FacetIterator(gridordh::Union{Grid,AbstractDofHandler}, facetset::AbstractVecOrSet{FacetIndex})\n\nCreate a FacetIterator to conveniently iterate over the faces in facestet. The elements of the iterator are FacetCaches which are properly reinit!ialized. See FacetCache for more details.\n\nLooping over a FacetIterator, i.e.:\n\nfor fc in FacetIterator(grid, facetset)\n # ...\nend\n\nis thus simply convenience for the following equivalent snippet: ```julia fc = FacetCache(grid) for faceindex in facetset reinit!(fc, faceindex) # ... end\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.InterfaceCache","page":"Degrees of Freedom","title":"Ferrite.InterfaceCache","text":"InterfaceCache(grid::Grid)\nInterfaceCache(dh::AbstractDofHandler)\n\nCreate a cache object with pre-allocated memory for the nodes, coordinates, and dofs of an interface. The cache is updated for a new cell by calling reinit!(cache, facet_a, facet_b) where facet_a::FacetIndex and facet_b::FacetIndex are the two interface faces.\n\nStruct fields of InterfaceCache\n\nic.a :: FacetCache: face cache for the first face of the interface\nic.b :: FacetCache: face cache for the second face of the interface\nic.dofs :: Vector{Int}: global dof ids for the interface (union of ic.a.dofs and ic.b.dofs)\n\nMethods with InterfaceCache\n\nreinit!(cache::InterfaceCache, facet_a::FacetIndex, facet_b::FacetIndex): reinitialize the cache for a new interface\ninterfacedofs(ic): get the global dof ids of the interface\n\nSee also InterfaceIterator.\n\n\n\n\n\n","category":"type"},{"location":"reference/dofhandler/#Ferrite.InterfaceIterator","page":"Degrees of Freedom","title":"Ferrite.InterfaceIterator","text":"InterfaceIterator(grid::Grid, [topology::ExclusiveTopology])\nInterfaceIterator(dh::AbstractDofHandler, [topology::ExclusiveTopology])\n\nCreate an InterfaceIterator to conveniently iterate over all the interfaces in a grid. The elements of the iterator are InterfaceCaches which are properly reinit!ialized. See InterfaceCache for more details. Looping over an InterfaceIterator, i.e.:\n\nfor ic in InterfaceIterator(grid, topology)\n # ...\nend\n\nis thus simply convenience for the following equivalent snippet for grids of dimensions > 1:\n\nic = InterfaceCache(grid, topology)\nfor face in topology.face_skeleton\n neighborhood = topology.face_face_neighbor[face[1], face[2]]\n isempty(neighborhood) && continue\n neighbor_face = neighborhood[1]\n reinit!(ic, face, neighbor_face)\n # ...\nend\n\nwarning: Warning\nInterfaceIterator is stateful and should not be used for things other than for-looping (e.g. broadcasting over, or collecting the iterator may yield unexpected results).\n\n\n\n\n\n","category":"type"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"using Ferrite","category":"page"},{"location":"topics/degrees_of_freedom/#Degrees-of-Freedom","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"","category":"section"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"The distribution and numbering of degrees of freedom (dofs) are handled by the DofHandler. The DofHandler will be used to query information about the dofs. For example we can obtain the dofs for a particular cell, which we need when assembling the system.","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"The DofHandler is based on the grid. Here we create a simple grid with Triangle cells, and then create a DofHandler based on the grid","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"grid = generate_grid(Triangle, (20, 20))\ndh = DofHandler(grid)\n# hide","category":"page"},{"location":"topics/degrees_of_freedom/#Fields","page":"Degrees of Freedom","title":"Fields","text":"","category":"section"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"Before we can distribute the dofs we need to specify fields. A field is simply the unknown function(s) we are solving for. To add a field we need a name (a Symbol) and the the interpolation describing the shape functions for the field. Here we add a scalar field :p, interpolated using linear (degree 1) shape functions on a triangle, and a vector field :u, also interpolated with linear shape functions on a triangle, but raised to the power 2 to indicate that it is a vector field with 2 components (for a 2D problem).","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"add!(dh, :p, Lagrange{RefTriangle, 1}())\nadd!(dh, :u, Lagrange{RefTriangle, 1}()^2)\n# hide","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"Finally, when we have added all the fields, we have to close! the DofHandler. When the DofHandler is closed it will traverse the grid and distribute all the dofs for the fields we added.","category":"page"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"close!(dh)","category":"page"},{"location":"topics/degrees_of_freedom/#Ordering-of-Dofs","page":"Degrees of Freedom","title":"Ordering of Dofs","text":"","category":"section"},{"location":"topics/degrees_of_freedom/","page":"Degrees of Freedom","title":"Degrees of Freedom","text":"todo: Todo\nDescribe dof ordering within elements (vertices -> edges -> faces -> volumes) and dof_range. Describe (global) dof renumbering","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"EditURL = \"../literate-tutorials/dg_heat_equation.jl\"","category":"page"},{"location":"tutorials/dg_heat_equation/#tutorial-dg-heat-equation","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"(Image: )","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Figure 1: Temperature field on the unit square with an internal uniform heat source solved with inhomogeneous Dirichlet boundary conditions on the left and right boundaries and flux on the top and bottom boundaries.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"tip: Tip\nThis example is also available as a Jupyter notebook: dg_heat_equation.ipynb.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"This example was developed as part of the Google summer of code funded project \"Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl\"","category":"page"},{"location":"tutorials/dg_heat_equation/#Introduction","page":"Discontinuous Galerkin heat equation","title":"Introduction","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"This tutorial extends Tutorial 1: Heat equation by using the discontinuous Galerkin method. The reader is expected to have gone through Tutorial 1: Heat equation before proceeding with this tutorial. The main differences between the two tutorials are the interface integral terms in the weak form, the boundary conditions, and some implementation differences explained in the commented program below.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The strong form considered in this tutorial is given as follows","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":" -boldsymbolnabla cdot boldsymbolnabla(u) = 1 quad textbfx in Omega","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"with the inhomogeneous Dirichlet boundary conditions","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"u(textbfx) = 1 quad textbfx in partial Omega_D^+ = lbracetextbfx x_1 = 10rbrace \nu(textbfx) = -1 quad textbfx in partial Omega_D^- = lbracetextbfx x_1 = -10rbrace","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"and Neumann boundary conditions","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"boldsymbolnabla (u(textbfx)) cdot boldsymboln = 1 quad textbfx in partial Omega_N^+ = lbracetextbfx x_2 = 10rbrace \nboldsymbolnabla (u(textbfx)) cdot boldsymboln = -1 quad textbfx in partial Omega_N^- = lbracetextbfx x_2 = -10rbrace","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The following definitions of average and jump on interfaces between elements are adopted in this tutorial:","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":" u = frac12(u^+ + u^-)quad llbracket urrbracket = u^+ boldsymboln^+ + u^- boldsymboln^-","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"where u^+ and u^- are the temperature on the two sides of the interface.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"details: Derivation of the weak form for homogeneous Dirichlet boundary condition\nDefining boldsymbolsigma as the gradient of the temperature field the equation can be expressed as boldsymbolsigma = boldsymbolnabla (u)\n -boldsymbolnabla cdot boldsymbolsigma = 1Multiplying by test functions $ \\boldsymbol{\\tau} $ and $ \\delta u $ respectively and integrating over the domain, int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = int_Omega boldsymbolnabla (u) cdot boldsymboltau mathrmdOmega\n -int_Omega boldsymbolnabla cdot boldsymbolsigma delta u mathrmdOmega = int_Omega delta u mathrmdOmegaIntegrating by parts and applying divergence theorem, int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = -int_Omega u (boldsymbolnabla cdot boldsymboltau) mathrmdOmega + int_Gamma hatu boldsymboltau cdot boldsymboln mathrmdGamma\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma delta u boldsymbolhatsigma cdot boldsymboln mathrmdGammaWhere boldsymboln is the outwards pointing normal, Gamma is the union of the elements' boundaries, and hatu hatsigma are the numerical fluxes. Substituting the integrals of form int_Gamma q boldsymbolphi cdot boldsymboln mathrmdGamma = int_Gamma llbracket qrrbracket cdot boldsymbolphi mathrmdGamma + int_Gamma^0 q llbracket boldsymbolphirrbracket mathrmdGamma^0where Gamma^0 Gamma setminus partial Omega, and the jump of the vector-valued field boldsymbolphi is defined as llbracket boldsymbolphirrbracket = boldsymbolphi^+ cdot boldsymboln^+ + boldsymbolphi^- cdot boldsymboln^-with the jumps and averages results in int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = -int_Omega u (boldsymbolnabla cdot boldsymboltau) mathrmdOmega + int_Gamma llbracket haturrbracket cdot boldsymboltau mathrmdGamma + int_Gamma^0 hatu llbracket boldsymboltaurrbracket mathrmdGamma^0\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma + int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0Integrating $ \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega $ by parts and applying divergence theorem without using numerical flux, then substitute in the equation to obtain a weak form. int_Omega boldsymbolsigma cdot boldsymboltau mathrmdOmega = int_Omega boldsymbolnabla (u) cdot boldsymboltau mathrmdOmega + int_Gamma llbracket hatu - urrbracket cdot boldsymboltau mathrmdGamma + int_Gamma^0 hatu - u llbracket boldsymboltaurrbracket mathrmdGamma^0\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma + int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0Substituting boldsymboltau = boldsymbolnabla (delta u)results in int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega + int_Gamma llbracket hatu - urrbracket cdot boldsymbolnabla (delta u) mathrmdGamma + int_Gamma^0 hatu - u llbracket boldsymbolnabla (delta u)rrbracket mathrmdGamma^0\n int_Omega boldsymbolsigma cdot boldsymbolnabla (delta u) mathrmdOmega = int_Omega delta u mathrmdOmega + int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma + int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0Combining the two equations, int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega + int_Gamma llbracket hatu - urrbracket cdot boldsymbolnabla (delta u) mathrmdGamma + int_Gamma^0 hatu - u llbracket boldsymbolnabla (delta u)rrbracket mathrmdGamma^0 - int_Gamma llbracket delta urrbracket cdot hatboldsymbolsigma mathrmdGamma - int_Gamma^0 delta u llbracket hatboldsymbolsigmarrbracket mathrmdGamma^0 = int_Omega delta u mathrmdOmegaThe numerical fluxes chosen for the interior penalty method are boldsymbolhatsigma = boldsymbolnabla (u) - alpha(llbracket urrbracket) on Gamma, hatu = u on the interfaces between elements Gamma^0 Gamma setminus partial Omega, and hatu = 0 on partial Omega. Such choice results in hatboldsymbolsigma = boldsymbolnabla (u) - alpha(llbracket urrbracket), llbracket haturrbracket = 0, hatu = u, llbracket hatboldsymbolsigmarrbracket = 0 and the equation becomes int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega - int_Gamma llbracket urrbracket cdot boldsymbolnabla (delta u) mathrmdGamma - int_Gamma llbracket delta urrbracket cdot boldsymbolnabla (u) - llbracket delta urrbracket cdot alpha(llbracket urrbracket) mathrmdGamma = int_Omega delta u mathrmdOmegaWhere alpha(llbracket urrbracket) = mu llbracket urrbracketWhere mu = eta h_e^-1, the weak form becomes int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega - int_Gamma llbracket u rrbracket cdot boldsymbolnabla (delta u) + llbracket delta u rrbracket cdot boldsymbolnabla (u) mathrmdGamma + int_Gamma fracetah_e llbracket urrbracket cdot llbracket delta urrbracket mathrmdGamma = int_Omega delta u mathrmdOmega","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Since partial Omega is constrained with both Dirichlet and Neumann boundary conditions the term int_partial Omega boldsymbolnabla (u) cdot boldsymboln delta u mathrmd Omega can be expressed as an integral over partial Omega_N, where partial Omega_N is the boundaries with only prescribed Neumann boundary condition, The resulting weak form is given given as follows: Find u in mathbbU such that","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":" int_Omega boldsymbolnabla (u) cdot boldsymbolnabla (delta u) mathrmdOmega - int_Gamma^0 llbracket urrbracket cdot boldsymbolnabla (delta u) + llbracket delta urrbracket cdot boldsymbolnabla (u) mathrmdGamma^0 + int_Gamma^0 fracetah_e llbracket urrbracket cdot llbracket delta urrbracket mathrmdGamma^0 = int_Omega delta u mathrmdOmega + int_partial Omega_N (boldsymbolnabla (u) cdot boldsymboln) delta u mathrmd partial Omega_N","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"where h_e is the characteristic size (the diameter of the interface), and eta is a large enough positive number independent of h_e [3], delta u in mathbbT is a test function, and where mathbbU and mathbbT are suitable trial and test function sets, respectively. We use the value eta = (1 + O)^D, where O is the polynomial order and D the dimension, in this tutorial.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"More details on DG formulations for elliptic problems can be found in [4].","category":"page"},{"location":"tutorials/dg_heat_equation/#Commented-Program","page":"Discontinuous Galerkin heat equation","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"First we load Ferrite and other packages, and generate grid just like the heat equation tutorial","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"using Ferrite, SparseArrays\ndim = 2;\ngrid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim));\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We construct the topology information which is used later for generating the sparsity pattern for stiffness matrix.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"topology = ExclusiveTopology(grid);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Trial-and-test-functions","page":"Discontinuous Galerkin heat equation","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"CellValues, FacetValues, and InterfaceValues facilitate the process of evaluating values and gradients of test and trial functions (among other things). To define these we need to specify an interpolation space for the shape functions. We use DiscontinuousLagrange functions based on the two-dimensional reference quadrilateral. We also define a quadrature rule based on the same reference element. We combine the interpolation and the quadrature rule to CellValues and InterfaceValues object. Note that InterfaceValues object contains two FacetValues objects which can be used individually.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"order = 1;\nip = DiscontinuousLagrange{RefQuadrilateral, order}();\nqr = QuadratureRule{RefQuadrilateral}(2);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"For FacetValues and InterfaceValues we use FacetQuadratureRule","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"facet_qr = FacetQuadratureRule{RefQuadrilateral}(2);\ncellvalues = CellValues(qr, ip);\nfacetvalues = FacetValues(facet_qr, ip);\ninterfacevalues = InterfaceValues(facet_qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Penalty-term-parameters","page":"Discontinuous Galerkin heat equation","title":"Penalty term parameters","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We define functions to calculate the diameter of a set of points, used to calculate the characteristic size h_e in the assembly routine.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"getdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2);\ngetdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :))));\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Degrees-of-freedom","page":"Discontinuous Galerkin heat equation","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Degrees of freedom distribution is handled using DofHandler as usual","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"However, when generating the sparsity pattern we need to pass the topology and the cross-element coupling matrix when we're using discontinuous interpolations. The cross-element coupling matrix is of size [1,1] in this case as we have only one field and one DofHandler.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"K = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1));\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Boundary-conditions","page":"Discontinuous Galerkin heat equation","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The Dirichlet boundary conditions are treated as usual by a ConstraintHandler.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"ch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> 1.0))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> -1.0))\nclose!(ch);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Furthermore, we define partial Omega_N as the union of the facet sets with Neumann boundary conditions for later use","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"∂Ωₙ = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Assembling-the-linear-system","page":"Discontinuous Galerkin heat equation","title":"Assembling the linear system","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Now we have all the pieces needed to assemble the linear system, K u = f. Assembling of the global system is done by looping over i) all the elements in order to compute the element contributions K_e and f_e, ii) all the interfaces to compute their contributions K_i, and iii) all the Neumann boundary facets to compute their contributions f_e. All these local contributions are then assembled into the appropriate place in the global K and f.","category":"page"},{"location":"tutorials/dg_heat_equation/#Local-assembly","page":"Discontinuous Galerkin heat equation","title":"Local assembly","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We define the functions","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"assemble_element! to compute the contributions K_e and f_e of volume integrals over an element using cellvalues.\nassemble_interface! to compute the contribution K_i of surface integrals over an interface using interfacevalues.\nassemble_boundary! to compute the contribution f_e of surface integrals over a boundary facet using FacetValues.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64)\n # Reset to 0\n fill!(Ki, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(iv)\n # Get the normal to facet A\n normal = getnormal(iv, q_point)\n # Get the quadrature weight\n dΓ = getdetJdV(iv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n δu_jump = shape_value_jump(iv, q_point, i) * (-normal)\n ∇δu_avg = shape_gradient_average(iv, q_point, i)\n # Loop over trial shape functions\n for j in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n u_jump = shape_value_jump(iv, q_point, j) * (-normal)\n ∇u_avg = shape_gradient_average(iv, q_point, j)\n # Add contribution to Ki\n Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ\n end\n end\n end\n return Ki\nend\n\nfunction assemble_boundary!(fe::Vector, fv::FacetValues)\n # Reset to 0\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(fv)\n # Get the normal to facet A\n normal = getnormal(fv, q_point)\n # Get the quadrature weight\n ∂Ω = getdetJdV(fv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(fv)\n δu = shape_value(fv, q_point, i)\n boundary_flux = normal[2]\n fe[i] = boundary_flux * δu * ∂Ω\n end\n end\n return fe\nend\nnothing # hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Global-assembly","page":"Discontinuous Galerkin heat equation","title":"Global assembly","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"We define the function assemble_global to loop over all elements and internal facets (interfaces), as well as the external facets involved in Neumann boundary conditions.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"function assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n Ki = zeros(n_basefuncs * 2, n_basefuncs * 2)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute volume integral contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n # Loop over all interfaces\n for ic in InterfaceIterator(dh)\n # Reinitialize interfacevalues for this interface\n reinit!(interfacevalues, ic)\n # Calculate the characteristic size hₑ as the face diameter\n interfacecoords = ∩(getcoordinates(ic)...)\n hₑ = getdiameter(interfacecoords)\n # Calculate μ\n μ = (1 + order)^dim / hₑ\n # Compute interface surface integrals contribution\n assemble_interface!(Ki, interfacevalues, μ)\n # Assemble Ki into K\n assemble!(assembler, interfacedofs(ic), Ki)\n end\n # Loop over domain boundaries with Neumann boundary conditions\n for fc in FacetIterator(dh, ∂Ωₙ)\n # Reinitialize facetvalues for this boundary facet\n reinit!(facetvalues, fc)\n # Compute boundary facet surface integrals contribution\n assemble_boundary!(fe, facetvalues)\n # Assemble fe into f\n assemble!(f, celldofs(fc), fe)\n end\n return K, f\nend\nK, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim);\nnothing # hide","category":"page"},{"location":"tutorials/dg_heat_equation/#Solution-of-the-system","page":"Discontinuous Galerkin heat equation","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"The solution of the system is independent of the discontinuous discretization and the application of constraints, linear solve, and exporting is done as usual.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"apply!(K, f, ch)\nu = K \\ f;\nVTKGridFile(\"dg_heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/dg_heat_equation/#References","page":"Discontinuous Galerkin heat equation","title":"References","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"L. Mu, J. Wang, Y. Wang and X. Ye. Interior penalty discontinuous Galerkin method on very general polygonal and polyhedral meshes. Journal of Computational and Applied Mathematics 255, 432–440 (2014).\n\n\n\nD. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.\n\n\n\n","category":"page"},{"location":"tutorials/dg_heat_equation/#heat_equation-DG-plain-program","page":"Discontinuous Galerkin heat equation","title":"Plain program","text":"","category":"section"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"Here follows a version of the program without any comments. The file is also available here: dg_heat_equation.jl.","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"using Ferrite, SparseArrays\ndim = 2;\ngrid = generate_grid(Quadrilateral, ntuple(_ -> 20, dim));\n\ntopology = ExclusiveTopology(grid);\n\norder = 1;\nip = DiscontinuousLagrange{RefQuadrilateral, order}();\nqr = QuadratureRule{RefQuadrilateral}(2);\n\nfacet_qr = FacetQuadratureRule{RefQuadrilateral}(2);\ncellvalues = CellValues(qr, ip);\nfacetvalues = FacetValues(facet_qr, ip);\ninterfacevalues = InterfaceValues(facet_qr, ip);\n\ngetdistance(p1::Vec{N, T}, p2::Vec{N, T}) where {N, T} = norm(p1 - p2);\ngetdiameter(cell_coords::Vector{Vec{N, T}}) where {N, T} = maximum(getdistance.(cell_coords, reshape(cell_coords, (1, :))));\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nK = allocate_matrix(dh, topology = topology, interface_coupling = trues(1, 1));\n\nch = ConstraintHandler(dh)\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> 1.0))\nadd!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> -1.0))\nclose!(ch);\n\n∂Ωₙ = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n);\n\nfunction assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # Reset to 0\n fill!(Ke, 0)\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n # Add contribution to fe\n fe[i] += δu * dΩ\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n Ke[i, j] += (∇δu ⋅ ∇u) * dΩ\n end\n end\n end\n return Ke, fe\nend\n\nfunction assemble_interface!(Ki::Matrix, iv::InterfaceValues, μ::Float64)\n # Reset to 0\n fill!(Ki, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(iv)\n # Get the normal to facet A\n normal = getnormal(iv, q_point)\n # Get the quadrature weight\n dΓ = getdetJdV(iv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n δu_jump = shape_value_jump(iv, q_point, i) * (-normal)\n ∇δu_avg = shape_gradient_average(iv, q_point, i)\n # Loop over trial shape functions\n for j in 1:getnbasefunctions(iv)\n # Multiply the jump by the negative normal to get the definition from the theory section.\n u_jump = shape_value_jump(iv, q_point, j) * (-normal)\n ∇u_avg = shape_gradient_average(iv, q_point, j)\n # Add contribution to Ki\n Ki[i, j] += -(δu_jump ⋅ ∇u_avg + ∇δu_avg ⋅ u_jump) * dΓ + μ * (δu_jump ⋅ u_jump) * dΓ\n end\n end\n end\n return Ki\nend\n\nfunction assemble_boundary!(fe::Vector, fv::FacetValues)\n # Reset to 0\n fill!(fe, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(fv)\n # Get the normal to facet A\n normal = getnormal(fv, q_point)\n # Get the quadrature weight\n ∂Ω = getdetJdV(fv, q_point)\n # Loop over test shape functions\n for i in 1:getnbasefunctions(fv)\n δu = shape_value(fv, q_point, i)\n boundary_flux = normal[2]\n fe[i] = boundary_flux * δu * ∂Ω\n end\n end\n return fe\nend\n\nfunction assemble_global(cellvalues::CellValues, facetvalues::FacetValues, interfacevalues::InterfaceValues, K::SparseMatrixCSC, dh::DofHandler, order::Int, dim::Int)\n # Allocate the element stiffness matrix and element force vector\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n Ki = zeros(n_basefuncs * 2, n_basefuncs * 2)\n # Allocate global force vector f\n f = zeros(ndofs(dh))\n # Create an assembler\n assembler = start_assemble(K, f)\n # Loop over all cells\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute volume integral contribution\n assemble_element!(Ke, fe, cellvalues)\n # Assemble Ke and fe into K and f\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n # Loop over all interfaces\n for ic in InterfaceIterator(dh)\n # Reinitialize interfacevalues for this interface\n reinit!(interfacevalues, ic)\n # Calculate the characteristic size hₑ as the face diameter\n interfacecoords = ∩(getcoordinates(ic)...)\n hₑ = getdiameter(interfacecoords)\n # Calculate μ\n μ = (1 + order)^dim / hₑ\n # Compute interface surface integrals contribution\n assemble_interface!(Ki, interfacevalues, μ)\n # Assemble Ki into K\n assemble!(assembler, interfacedofs(ic), Ki)\n end\n # Loop over domain boundaries with Neumann boundary conditions\n for fc in FacetIterator(dh, ∂Ωₙ)\n # Reinitialize facetvalues for this boundary facet\n reinit!(facetvalues, fc)\n # Compute boundary facet surface integrals contribution\n assemble_boundary!(fe, facetvalues)\n # Assemble fe into f\n assemble!(f, celldofs(fc), fe)\n end\n return K, f\nend\nK, f = assemble_global(cellvalues, facetvalues, interfacevalues, K, dh, order, dim);\n\napply!(K, f, ch)\nu = K \\ f;\nVTKGridFile(\"dg_heat_equation\", dh) do vtk\n write_solution(vtk, dh, u)\nend;","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"","category":"page"},{"location":"tutorials/dg_heat_equation/","page":"Discontinuous Galerkin heat equation","title":"Discontinuous Galerkin heat equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"EditURL = \"../literate-tutorials/hyperelasticity.jl\"","category":"page"},{"location":"tutorials/hyperelasticity/#tutorial-hyperelasticity","page":"Hyperelasticity","title":"Hyperelasticity","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Keywords: hyperelasticity, finite strain, large deformations, Newton's method, conjugate gradient, automatic differentiation","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"(Image: hyperelasticity.png)","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Figure 1: Cube loaded in torsion modeled with a hyperelastic material model and finite strain.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: hyperelasticity.ipynb.","category":"page"},{"location":"tutorials/hyperelasticity/#Introduction","page":"Hyperelasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"In this example we will solve a problem in a finite strain setting using an hyperelastic material model. In order to compute the stress we will use automatic differentiation, to solve the non-linear system we use Newton's method, and for solving the Newton increment we use conjugate gradients.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The weak form is expressed in terms of the first Piola-Kirchoff stress mathbfP as follows: Find mathbfu in mathbbU such that","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"int_Omega nabla_mathbfX delta mathbfu mathbfP(mathbfu) mathrmdOmega =\nint_Omega delta mathbfu cdot mathbfb mathrmdOmega + int_Gamma_mathrmN\ndelta mathbfu cdot mathbft mathrmdGamma\nquad forall delta mathbfu in mathbbU^0","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where mathbfu is the unknown displacement field, mathbfb is the body force acting on the reference domain, mathbft is the traction acting on the Neumann part of the reference domain's boundary, and where mathbbU and mathbbU^0 are suitable trial and test sets. Omega denotes the reference (sometimes also called initial or material) domain. Gradients are defined with respect to the reference domain, here denoted with an mathbfX. Formally this is expressed as (nabla_mathbfX bullet)_ij = fracpartial(bullet)_ipartial X_j. Note that for large deformation problems it is also possible that gradients and integrals are defined on the deformed (sometimes also called current or spatial) domain, depending on the specific formulation.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The specific problem we will solve in this example is the cube from Figure 1: On one side we apply a rotation using Dirichlet boundary conditions, on the opposite side we fix the displacement with a homogeneous Dirichlet boundary condition, and on the remaining four sides we apply a traction in the normal direction of the surface. In addition, a body force is applied in one direction.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"In addition to Ferrite.jl and Tensors.jl, this examples uses TimerOutputs.jl for timing the program and print a summary at the end, ProgressMeter.jl for showing a simple progress bar, and IterativeSolvers.jl for solving the linear system using conjugate gradients.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers","category":"page"},{"location":"tutorials/hyperelasticity/#Hyperelastic-material-model","page":"Hyperelasticity","title":"Hyperelastic material model","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The stress can be derived from an energy potential, defined in terms of the right Cauchy-Green tensor mathbfC = mathbfF^mathrmT cdot mathbfF, where mathbfF = mathbfI + nabla_mathbfX mathbfu is the deformation gradient. We shall use the compressible neo-Hookean model from Wikipedia with the potential","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Psi(mathbfC) = underbracefracmu2 (I_1 - 3)_W(mathbfC) underbrace- mu ln(J) + fraclambda2 (J - 1)^2_U(J)","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where I_1 = mathrmtr(mathbfC) is the first invariant, J = sqrtdet(mathbfC) and mu and lambda material parameters.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"details: Extra details on compressible neo-Hookean formulations\nThe Neo-Hooke model is only a well defined terminology in the incompressible case. Thus, only W(mathbfC) specifies the neo-Hookean behavior, the volume penalty U(J) can vary in different formulations. In order to obtain a well-posed problem, it is crucial to choose a convex formulation of U(J). Other examples for U(J) can be found, e.g. in [1, Eq. (6.138)] beta^-2 (beta ln J + J^-beta -1)where [2, Eq. (2.37)] published a non-generalized version with beta=-2. This shows the possible variety of U(J) while all of them refer to compressible neo-Hookean models. Sometimes the modified first invariant overlineI_1=fracI_1I_3^13 is used in W(mathbfC) instead of I_1.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"From the potential we obtain the second Piola-Kirchoff stress mathbfS as","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"mathbfS = 2 fracpartial Psipartial mathbfC","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"and the tangent of mathbfS as","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"fracpartial mathbfSpartial mathbfC = 2 fracpartial^2 Psipartial mathbfC^2","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Finally, for the finite element problem we need mathbfP and fracpartial mathbfPpartial mathbfF, which can be obtained by using the following relations:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"beginalign*\nmathbfP = mathbfF cdot mathbfS\nfracpartial mathbfPpartial mathbfF = mathbfI barotimes mathbfS + 2 mathbfF cdot\nfracpartial mathbfSpartial mathbfC mathbfF^mathrmT barotimes mathbfI\nendalign*","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"details: Derivation of $\\partial \\mathbf{P} / \\partial \\mathbf{F}$\nTip: See knutam.github.io/tensors for an explanation of the index notation used in this derivation.Using the product rule, the chain rule, and the relations mathbfP = mathbfF cdot mathbfS and mathbfC = mathbfF^mathrmT cdot mathbfF, we obtain the following:beginaligned\nfracpartial P_ijpartial F_kl =\nfracpartial (F_imS_mj)partial F_kl =\nfracpartial F_impartial F_klS_mj +\nF_imfracpartial S_mjpartial F_kl =\ndelta_ikdelta_ml S_mj +\nF_imfracpartial S_mjpartial C_nofracpartial C_nopartial F_kl =\ndelta_ikS_lj +\nF_imfracpartial S_mjpartial C_no\nfracpartial (F^mathrmT_npF_po)partial F_kl =\ndelta_ikS^mathrmT_jl +\nF_imfracpartial S_mjpartial C_no\nleft(\nfracpartial F^mathrmT_nppartial F_klF_po +\nF^mathrmT_npfracpartial F_popartial F_kl\nright) =\ndelta_ikS_jl +\nF_imfracpartial S_mjpartial C_no\n(delta_nl delta_pk F_po + F^mathrmT_npdelta_pk delta_ol) =\ndelta_ikS_lj +\nF_imfracpartial S_mjpartial C_no\n(F^mathrmT_ok delta_nl + F^mathrmT_nk delta_ol) =\ndelta_ikS_jl +\n2 F_im fracpartial S_mjpartial C_no\nF^mathrmT_nk delta_ol \nfracpartial mathbfPpartial mathbfF =\nmathbfIbarotimesmathbfS +\n2 mathbfF cdot fracpartial mathbfSpartial mathbfC\n mathbfF^mathrmT barotimes mathbfI\nendalignedwhere we used the fact that mathbfS is symmetric (S_lj = S_jl) and that fracpartial mathbfSpartial mathbfC is minor symmetric (fracpartial S_mjpartial C_no = fracpartial S_mjpartial C_on).","category":"page"},{"location":"tutorials/hyperelasticity/#Implementation-of-material-model-using-automatic-differentiation","page":"Hyperelasticity","title":"Implementation of material model using automatic differentiation","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"We can implement the material model as follows, where we utilize automatic differentiation for the stress and the tangent, and thus only define the potential:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"struct NeoHooke\n μ::Float64\n λ::Float64\nend\n\nfunction Ψ(C, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(C)\n J = sqrt(det(C))\n return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2\nend\n\nfunction constitutive_driver(C, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all)\n S = 2.0 * ∂Ψ∂C\n ∂S∂C = 2.0 * ∂²Ψ∂C²\n return S, ∂S∂C\nend;\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/#Newton's-method","page":"Hyperelasticity","title":"Newton's method","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"As mentioned above, to deal with the non-linear weak form we first linearize the problem such that we can apply Newton's method, and then apply the FEM to discretize the problem. Skipping a detailed derivation, Newton's method can be expressed as: Given some initial guess for the degrees of freedom underlineu^0, find a sequence underlineu^k by iterating","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"underlineu^k+1 = underlineu^k - Delta underlineu^k","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"until some termination condition has been met. Therein we determine Delta underlineu^k from the linearized problem","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"underlineunderlineK(underlineu^k) Delta underlineu^k = underlineg(underlineu^k)","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"where the global residual, underlineg, and the Jacobi matrix, underlineunderlineK = fracpartial underlinegpartial underlineu, are evaluated at the current guess underlineu^k. The entries of underlineg are given by","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"(underlineg)_i = int_Omega nabla_mathbfX delta mathbfu_i \nmathbfP mathrmd Omega - int_Omega delta mathbfu_i cdot mathbfb \nmathrmd Omega - int_Gamma_mathrmN delta mathbfu_i cdot mathbft\nmathrmdGamma","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"and the entries of underlineunderlineK are given by","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"(underlineunderlineK)_ij = int_Omega nabla_mathbfX delta\nmathbfu_i fracpartial mathbfPpartial mathbfF nabla_mathbfX\ndelta mathbfu_j mathrmd Omega","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"A detailed derivation can be found in every continuum mechanics book, which has a chapter about finite elasticity theory. We used \"Nonlinear solid mechanics: a continuum approach for engineering science.\" by Holzapfel [1], Chapter 8 as a reference.","category":"page"},{"location":"tutorials/hyperelasticity/#Finite-element-assembly","page":"Hyperelasticity","title":"Finite element assembly","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"The element routine for assembling the residual and tangent stiffness is implemented as usual, with loops over quadrature points and shape functions:","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n # Reinitialize cell values, and reset output arrays\n reinit!(cv, cell)\n fill!(ke, 0.0)\n fill!(ge, 0.0)\n\n b = Vec{3}((0.0, -0.5, 0.0)) # Body force\n tn = 0.1 # Traction (to be scaled with surface normal)\n ndofs = getnbasefunctions(cv)\n\n for qp in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, qp)\n # Compute deformation gradient F and right Cauchy-Green tensor C\n ∇u = function_gradient(cv, qp, ue)\n F = one(∇u) + ∇u\n C = tdot(F) # F' ⋅ F\n # Compute stress and tangent\n S, ∂S∂C = constitutive_driver(C, mp)\n P = F ⋅ S\n I = one(S)\n ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)\n\n # Loop over test functions\n for i in 1:ndofs\n # Test function and gradient\n δui = shape_value(cv, qp, i)\n ∇δui = shape_gradient(cv, qp, i)\n # Add contribution to the residual from this test function\n ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ\n\n ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation\n for j in 1:ndofs\n ∇δuj = shape_gradient(cv, qp, j)\n # Add contribution to the tangent\n ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ\n end\n end\n end\n\n # Surface integral for the traction\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) in ΓN\n reinit!(fv, cell, facet)\n for q_point in 1:getnquadpoints(fv)\n t = tn * getnormal(fv, q_point)\n dΓ = getdetJdV(fv, q_point)\n for i in 1:ndofs\n δui = shape_value(fv, q_point, i)\n ge[i] -= (δui ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Assembling global residual and tangent is also done in the usual way, just looping over the elements, call the element routine and assemble in the the global matrix K and residual g.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n n = ndofs_per_cell(dh)\n ke = zeros(n, n)\n ge = zeros(n)\n\n # start_assemble resets K and g\n assembler = start_assemble(K, g)\n\n # Loop over all cells in the grid\n @timeit \"assemble\" for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n ue = u[global_dofs] # element dofs\n @timeit \"element assemble\" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n assemble!(assembler, global_dofs, ke, ge)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Finally, we define a main function which sets up everything and then performs Newton iterations until convergence.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"function solve()\n reset_timer!()\n\n # Generate a grid\n N = 10\n L = 1.0\n left = zero(Vec{3})\n right = L * ones(Vec{3})\n grid = generate_grid(Tetrahedron, (N, N, N), left, right)\n\n # Material parameters\n E = 10.0\n ν = 0.3\n μ = E / (2(1 + ν))\n λ = (E * ν) / ((1 + ν) * (1 - 2ν))\n mp = NeoHooke(μ, λ)\n\n # Finite element base\n ip = Lagrange{RefTetrahedron, 1}()^3\n qr = QuadratureRule{RefTetrahedron}(1)\n qr_facet = FacetQuadratureRule{RefTetrahedron}(1)\n cv = CellValues(qr, ip)\n fv = FacetValues(qr_facet, ip)\n\n # DofHandler\n dh = DofHandler(grid)\n add!(dh, :u, ip) # Add a displacement field\n close!(dh)\n\n function rotation(X, t)\n θ = pi / 3 # 60°\n x, y, z = X\n return t * Vec{3}(\n (\n 0.0,\n L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ),\n L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ),\n )\n )\n end\n\n dbcs = ConstraintHandler(dh)\n # Add a homogeneous boundary condition on the \"clamped\" edge\n dbc = Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3])\n add!(dbcs, dbc)\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> rotation(x, t), [1, 2, 3])\n add!(dbcs, dbc)\n close!(dbcs)\n t = 0.5\n Ferrite.update!(dbcs, t)\n\n # Neumann part of the boundary\n ΓN = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n getfacetset(grid, \"front\"),\n getfacetset(grid, \"back\"),\n )\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n un = zeros(_ndofs) # previous solution vector\n u = zeros(_ndofs)\n Δu = zeros(_ndofs)\n ΔΔu = zeros(_ndofs)\n apply!(un, dbcs)\n\n # Create sparse matrix and residual vector\n K = allocate_matrix(dh)\n g = zeros(_ndofs)\n\n # Perform Newton iterations\n newton_itr = -1\n NEWTON_TOL = 1.0e-8\n NEWTON_MAXITER = 30\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving:\")\n\n while true\n newton_itr += 1\n # Construct the current guess\n u .= un .+ Δu\n # Compute residual and tangent for current guess\n assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n # Apply boundary conditions\n apply_zero!(K, g, dbcs)\n # Compute the residual norm and compare with tolerance\n normg = norm(g)\n ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)])\n if normg < NEWTON_TOL\n break\n elseif newton_itr > NEWTON_MAXITER\n error(\"Reached maximum Newton iterations, aborting\")\n end\n\n # Compute increment using conjugate gradients\n @timeit \"linear solve\" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000)\n\n apply_zero!(ΔΔu, dbcs)\n Δu .-= ΔΔu\n end\n\n # Save the solution\n @timeit \"export\" begin\n VTKGridFile(\"hyperelasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n end\n end\n\n print_timer(title = \"Analysis with $(getncells(grid)) elements\", linechars = :ascii)\n return u\nend","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Run the simulation","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"u = solve();\nnothing #hide","category":"page"},{"location":"tutorials/hyperelasticity/#Plain-program","page":"Hyperelasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"Here follows a version of the program without any comments. The file is also available here: hyperelasticity.jl.","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers\n\nstruct NeoHooke\n μ::Float64\n λ::Float64\nend\n\nfunction Ψ(C, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(C)\n J = sqrt(det(C))\n return μ / 2 * (Ic - 3 - 2 * log(J)) + λ / 2 * (J - 1)^2\nend\n\nfunction constitutive_driver(C, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂C², ∂Ψ∂C = Tensors.hessian(y -> Ψ(y, mp), C, :all)\n S = 2.0 * ∂Ψ∂C\n ∂S∂C = 2.0 * ∂²Ψ∂C²\n return S, ∂S∂C\nend;\n\nfunction assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n # Reinitialize cell values, and reset output arrays\n reinit!(cv, cell)\n fill!(ke, 0.0)\n fill!(ge, 0.0)\n\n b = Vec{3}((0.0, -0.5, 0.0)) # Body force\n tn = 0.1 # Traction (to be scaled with surface normal)\n ndofs = getnbasefunctions(cv)\n\n for qp in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, qp)\n # Compute deformation gradient F and right Cauchy-Green tensor C\n ∇u = function_gradient(cv, qp, ue)\n F = one(∇u) + ∇u\n C = tdot(F) # F' ⋅ F\n # Compute stress and tangent\n S, ∂S∂C = constitutive_driver(C, mp)\n P = F ⋅ S\n I = one(S)\n ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)\n\n # Loop over test functions\n for i in 1:ndofs\n # Test function and gradient\n δui = shape_value(cv, qp, i)\n ∇δui = shape_gradient(cv, qp, i)\n # Add contribution to the residual from this test function\n ge[i] += (∇δui ⊡ P - δui ⋅ b) * dΩ\n\n ∇δui∂P∂F = ∇δui ⊡ ∂P∂F # Hoisted computation\n for j in 1:ndofs\n ∇δuj = shape_gradient(cv, qp, j)\n # Add contribution to the tangent\n ke[i, j] += (∇δui∂P∂F ⊡ ∇δuj) * dΩ\n end\n end\n end\n\n # Surface integral for the traction\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) in ΓN\n reinit!(fv, cell, facet)\n for q_point in 1:getnquadpoints(fv)\n t = tn * getnormal(fv, q_point)\n dΓ = getdetJdV(fv, q_point)\n for i in 1:ndofs\n δui = shape_value(fv, q_point, i)\n ge[i] -= (δui ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend;\n\nfunction assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n n = ndofs_per_cell(dh)\n ke = zeros(n, n)\n ge = zeros(n)\n\n # start_assemble resets K and g\n assembler = start_assemble(K, g)\n\n # Loop over all cells in the grid\n @timeit \"assemble\" for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n ue = u[global_dofs] # element dofs\n @timeit \"element assemble\" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n assemble!(assembler, global_dofs, ke, ge)\n end\n return\nend;\n\nfunction solve()\n reset_timer!()\n\n # Generate a grid\n N = 10\n L = 1.0\n left = zero(Vec{3})\n right = L * ones(Vec{3})\n grid = generate_grid(Tetrahedron, (N, N, N), left, right)\n\n # Material parameters\n E = 10.0\n ν = 0.3\n μ = E / (2(1 + ν))\n λ = (E * ν) / ((1 + ν) * (1 - 2ν))\n mp = NeoHooke(μ, λ)\n\n # Finite element base\n ip = Lagrange{RefTetrahedron, 1}()^3\n qr = QuadratureRule{RefTetrahedron}(1)\n qr_facet = FacetQuadratureRule{RefTetrahedron}(1)\n cv = CellValues(qr, ip)\n fv = FacetValues(qr_facet, ip)\n\n # DofHandler\n dh = DofHandler(grid)\n add!(dh, :u, ip) # Add a displacement field\n close!(dh)\n\n function rotation(X, t)\n θ = pi / 3 # 60°\n x, y, z = X\n return t * Vec{3}(\n (\n 0.0,\n L / 2 - y + (y - L / 2) * cos(θ) - (z - L / 2) * sin(θ),\n L / 2 - z + (y - L / 2) * sin(θ) + (z - L / 2) * cos(θ),\n )\n )\n end\n\n dbcs = ConstraintHandler(dh)\n # Add a homogeneous boundary condition on the \"clamped\" edge\n dbc = Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> [0.0, 0.0, 0.0], [1, 2, 3])\n add!(dbcs, dbc)\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> rotation(x, t), [1, 2, 3])\n add!(dbcs, dbc)\n close!(dbcs)\n t = 0.5\n Ferrite.update!(dbcs, t)\n\n # Neumann part of the boundary\n ΓN = union(\n getfacetset(grid, \"top\"),\n getfacetset(grid, \"bottom\"),\n getfacetset(grid, \"front\"),\n getfacetset(grid, \"back\"),\n )\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n un = zeros(_ndofs) # previous solution vector\n u = zeros(_ndofs)\n Δu = zeros(_ndofs)\n ΔΔu = zeros(_ndofs)\n apply!(un, dbcs)\n\n # Create sparse matrix and residual vector\n K = allocate_matrix(dh)\n g = zeros(_ndofs)\n\n # Perform Newton iterations\n newton_itr = -1\n NEWTON_TOL = 1.0e-8\n NEWTON_MAXITER = 30\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving:\")\n\n while true\n newton_itr += 1\n # Construct the current guess\n u .= un .+ Δu\n # Compute residual and tangent for current guess\n assemble_global!(K, g, dh, cv, fv, mp, u, ΓN)\n # Apply boundary conditions\n apply_zero!(K, g, dbcs)\n # Compute the residual norm and compare with tolerance\n normg = norm(g)\n ProgressMeter.update!(prog, normg; showvalues = [(:iter, newton_itr)])\n if normg < NEWTON_TOL\n break\n elseif newton_itr > NEWTON_MAXITER\n error(\"Reached maximum Newton iterations, aborting\")\n end\n\n # Compute increment using conjugate gradients\n @timeit \"linear solve\" IterativeSolvers.cg!(ΔΔu, K, g; maxiter = 1000)\n\n apply_zero!(ΔΔu, dbcs)\n Δu .-= ΔΔu\n end\n\n # Save the solution\n @timeit \"export\" begin\n VTKGridFile(\"hyperelasticity\", dh) do vtk\n write_solution(vtk, dh, u)\n end\n end\n\n print_timer(title = \"Analysis with $(getncells(grid)) elements\", linechars = :ascii)\n return u\nend\n\nu = solve();","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"","category":"page"},{"location":"tutorials/hyperelasticity/","page":"Hyperelasticity","title":"Hyperelasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"EditURL = \"../literate-gallery/landau.jl\"","category":"page"},{"location":"gallery/landau/#tutorial-ginzburg-landau-minimizer","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"(Image: landau_orig.png)","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"Original","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"(Image: landau_opt.png)","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"Optimized","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"In this example a basic Ginzburg-Landau model is solved. This example gives an idea of how the API together with ForwardDiff can be leveraged to performantly solve non standard problems on a FEM grid. A large portion of the code is there only for performance reasons, but since this usually really matters and is what takes the most time to optimize, it is included.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"The key to using a method like this for minimizing a free energy function directly, rather than the weak form, as is usually done with FEM, is to split up the gradient and Hessian calculations. This means that they are performed for each cell separately instead of for the grid as a whole.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"using ForwardDiff\nimport ForwardDiff: GradientConfig, HessianConfig, Chunk\nusing Ferrite\nusing Optim, LineSearches\nusing SparseArrays\nusing Tensors\nusing Base.Threads","category":"page"},{"location":"gallery/landau/#Energy-terms","page":"Ginzburg-Landau model energy minimization","title":"Energy terms","text":"","category":"section"},{"location":"gallery/landau/#4th-order-Landau-free-energy","page":"Ginzburg-Landau model energy minimization","title":"4th order Landau free energy","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function Fl(P::Vec{3, T}, α::Vec{3}) where {T}\n P2 = Vec{3, T}((P[1]^2, P[2]^2, P[3]^2))\n return α[1] * sum(P2) +\n α[2] * (P[1]^4 + P[2]^4 + P[3]^4) +\n α[3] * ((P2[1] * P2[2] + P2[2] * P2[3]) + P2[1] * P2[3])\nend","category":"page"},{"location":"gallery/landau/#Ginzburg-free-energy","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg free energy","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"@inline Fg(∇P, G) = 0.5(∇P ⊡ G) ⊡ ∇P","category":"page"},{"location":"gallery/landau/#GL-free-energy","page":"Ginzburg-Landau model energy minimization","title":"GL free energy","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"F(P, ∇P, params) = Fl(P, params.α) + Fg(∇P, params.G)","category":"page"},{"location":"gallery/landau/#Parameters-that-characterize-the-model","page":"Ginzburg-Landau model energy minimization","title":"Parameters that characterize the model","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"struct ModelParams{V, T}\n α::V\n G::T\nend","category":"page"},{"location":"gallery/landau/#ThreadCache","page":"Ginzburg-Landau model energy minimization","title":"ThreadCache","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This holds the values that each thread will use during the assembly.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"struct ThreadCache{CV, T, DIM, F <: Function, GC <: GradientConfig, HC <: HessianConfig}\n cvP::CV\n element_indices::Vector{Int}\n element_dofs::Vector{T}\n element_gradient::Vector{T}\n element_hessian::Matrix{T}\n element_coords::Vector{Vec{DIM, T}}\n element_potential::F\n gradconf::GC\n hessconf::HC\nend\nfunction ThreadCache(dpc::Int, nodespercell, cvP::CellValues, modelparams, elpotential)\n element_indices = zeros(Int, dpc)\n element_dofs = zeros(dpc)\n element_gradient = zeros(dpc)\n element_hessian = zeros(dpc, dpc)\n element_coords = zeros(Vec{3, Float64}, nodespercell)\n potfunc = x -> elpotential(x, cvP, modelparams)\n gradconf = GradientConfig(potfunc, zeros(dpc), Chunk{12}())\n hessconf = HessianConfig(potfunc, zeros(dpc), Chunk{4}())\n return ThreadCache(cvP, element_indices, element_dofs, element_gradient, element_hessian, element_coords, potfunc, gradconf, hessconf)\nend","category":"page"},{"location":"gallery/landau/#The-Model","page":"Ginzburg-Landau model energy minimization","title":"The Model","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"everything is combined into a model.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"mutable struct LandauModel{T, DH <: DofHandler, CH <: ConstraintHandler, TC <: ThreadCache}\n dofs::Vector{T}\n dofhandler::DH\n boundaryconds::CH\n threadindices::Vector{Vector{Int}}\n threadcaches::Vector{TC}\nend\n\nfunction LandauModel(α, G, gridsize, left::Vec{DIM, T}, right::Vec{DIM, T}, elpotential) where {DIM, T}\n grid = generate_grid(Tetrahedron, gridsize, left, right)\n threadindices = Ferrite.create_coloring(grid)\n\n qr = QuadratureRule{RefTetrahedron}(2)\n ipP = Lagrange{RefTetrahedron, 1}()^3\n cvP = CellValues(qr, ipP)\n\n dofhandler = DofHandler(grid)\n add!(dofhandler, :P, ipP)\n close!(dofhandler)\n\n dofvector = zeros(ndofs(dofhandler))\n startingconditions!(dofvector, dofhandler)\n boundaryconds = ConstraintHandler(dofhandler)\n #boundary conditions can be added but aren't necessary for optimization\n #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, \"left\"), (x, t) -> [0.0,0.0,0.53], [1,2,3]))\n #add!(boundaryconds, Dirichlet(:P, getfacetset(grid, \"right\"), (x, t) -> [0.0,0.0,-0.53], [1,2,3]))\n close!(boundaryconds)\n update!(boundaryconds, 0.0)\n\n apply!(dofvector, boundaryconds)\n\n hessian = allocate_matrix(dofhandler)\n dpc = ndofs_per_cell(dofhandler)\n cpc = length(grid.cells[1].nodes)\n caches = [ThreadCache(dpc, cpc, copy(cvP), ModelParams(α, G), elpotential) for t in 1:nthreads()]\n return LandauModel(dofvector, dofhandler, boundaryconds, threadindices, caches)\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"utility to quickly save a model","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function save_landau(path, model, dofs = model.dofs)\n VTKGridFile(path, model.dofhandler) do vtk\n write_solution(vtk, model.dofhandler, dofs)\n end\n return\nend","category":"page"},{"location":"gallery/landau/#Assembly","page":"Ginzburg-Landau model energy minimization","title":"Assembly","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This macro defines most of the assembly step, since the structure is the same for the energy, gradient and Hessian calculations.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"macro assemble!(innerbody)\n return esc(\n quote\n dofhandler = model.dofhandler\n for indices in model.threadindices\n @threads for i in indices\n cache = model.threadcaches[threadid()]\n eldofs = cache.element_dofs\n nodeids = dofhandler.grid.cells[i].nodes\n for j in 1:length(cache.element_coords)\n cache.element_coords[j] = dofhandler.grid.nodes[nodeids[j]].x\n end\n reinit!(cache.cvP, cache.element_coords)\n\n celldofs!(cache.element_indices, dofhandler, i)\n for j in 1:length(cache.element_dofs)\n eldofs[j] = dofvector[cache.element_indices[j]]\n end\n $innerbody\n end\n end\n end\n )\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This calculates the total energy calculation of the grid","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function F(dofvector::Vector{T}, model) where {T}\n outs = fill(zero(T), nthreads())\n @assemble! begin\n outs[threadid()] += cache.element_potential(eldofs)\n end\n return sum(outs)\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"The gradient calculation for each dof","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function ∇F!(∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n fill!(∇f, zero(T))\n @assemble! begin\n ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)\n @inbounds assemble!(∇f, cache.element_indices, cache.element_gradient)\n end\n return\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"The Hessian calculation for the whole grid","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function ∇²F!(∇²f::SparseMatrixCSC, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n assemblers = [start_assemble(∇²f) for t in 1:nthreads()]\n @assemble! begin\n ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)\n @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_hessian)\n end\n return\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"We can also calculate all things in one go!","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function calcall(∇²f::SparseMatrixCSC, ∇f::Vector{T}, dofvector::Vector{T}, model::LandauModel{T}) where {T}\n outs = fill(zero(T), nthreads())\n fill!(∇f, zero(T))\n assemblers = [start_assemble(∇²f, ∇f) for t in 1:nthreads()]\n @assemble! begin\n outs[threadid()] += cache.element_potential(eldofs)\n ForwardDiff.hessian!(cache.element_hessian, cache.element_potential, eldofs, cache.hessconf)\n ForwardDiff.gradient!(cache.element_gradient, cache.element_potential, eldofs, cache.gradconf)\n @inbounds assemble!(assemblers[threadid()], cache.element_indices, cache.element_gradient, cache.element_hessian)\n end\n return sum(outs)\nend","category":"page"},{"location":"gallery/landau/#Minimization","page":"Ginzburg-Landau model energy minimization","title":"Minimization","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"Now everything can be combined to minimize the energy, and find the equilibrium configuration.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function minimize!(model; kwargs...)\n dh = model.dofhandler\n dofs = model.dofs\n ∇f = fill(0.0, length(dofs))\n ∇²f = allocate_matrix(dh)\n function g!(storage, x)\n ∇F!(storage, x, model)\n return apply_zero!(storage, model.boundaryconds)\n end\n function h!(storage, x)\n return ∇²F!(storage, x, model)\n # apply!(storage, model.boundaryconds)\n end\n f(x) = F(x, model)\n\n od = TwiceDifferentiable(f, g!, h!, model.dofs, 0.0, ∇f, ∇²f)\n\n # this way of minimizing is only beneficial when the initial guess is completely off,\n # then a quick couple of ConjuageGradient steps brings us easily closer to the minimum.\n # res = optimize(od, model.dofs, ConjugateGradient(linesearch=BackTracking()), Optim.Options(show_trace=true, show_every=1, g_tol=1e-20, iterations=10))\n # model.dofs .= res.minimizer\n # to get the final convergence, Newton's method is more ideal since the energy landscape should be almost parabolic\n ##+\n res = optimize(od, model.dofs, Newton(linesearch = BackTracking()), Optim.Options(show_trace = true, show_every = 1, g_tol = 1.0e-20))\n model.dofs .= res.minimizer\n return res\nend","category":"page"},{"location":"gallery/landau/#Testing-it","page":"Ginzburg-Landau model energy minimization","title":"Testing it","text":"","category":"section"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This calculates the contribution of each element to the total energy, it is also the function that will be put through ForwardDiff for the gradient and Hessian.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function element_potential(eldofs::AbstractVector{T}, cvP, params) where {T}\n energy = zero(T)\n for qp in 1:getnquadpoints(cvP)\n P = function_value(cvP, qp, eldofs)\n ∇P = function_gradient(cvP, qp, eldofs)\n energy += F(P, ∇P, params) * getdetJdV(cvP, qp)\n end\n return energy\nend","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"now we define some starting conditions","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"function startingconditions!(dofvector, dofhandler)\n for cell in CellIterator(dofhandler)\n globaldofs = celldofs(cell)\n it = 1\n for i in 1:3:length(globaldofs)\n dofvector[globaldofs[i]] = -2.0\n dofvector[globaldofs[i + 1]] = 2.0\n dofvector[globaldofs[i + 2]] = -2.0tanh(cell.coords[it][1] / 20)\n it += 1\n end\n end\n return\nend\n\nδ(i, j) = i == j ? one(i) : zero(i)\nV2T(p11, p12, p44) = Tensor{4, 3}((i, j, k, l) -> p11 * δ(i, j) * δ(k, l) * δ(i, k) + p12 * δ(i, j) * δ(k, l) * (1 - δ(i, k)) + p44 * δ(i, k) * δ(j, l) * (1 - δ(i, j)))\n\nG = V2T(1.0e2, 0.0, 1.0e2)\nα = Vec{3}((-1.0, 1.0, 1.0))\nleft = Vec{3}((-75.0, -25.0, -2.0))\nright = Vec{3}((75.0, 25.0, 2.0))\nmodel = LandauModel(α, G, (50, 50, 2), left, right, element_potential)\n\nsave_landau(\"landauorig\", model)\n@time minimize!(model)\nsave_landau(\"landaufinal\", model)","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"as we can see this runs very quickly even for relatively large gridsizes. The key to get high performance like this is to minimize the allocations inside the threaded loops, ideally to 0.","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"","category":"page"},{"location":"gallery/landau/","page":"Ginzburg-Landau model energy minimization","title":"Ginzburg-Landau model energy minimization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"","page":"Home","title":"Home","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"#Ferrite.jl","page":"Home","title":"Ferrite.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for Ferrite.jl! Ferrite is a finite element toolbox that provides functionalities to implement finite element analysis in Julia. The aim is to be i) general, ii) performant, and iii) to keep mathematical abstractions.","category":"page"},{"location":"","page":"Home","title":"Home","text":"note: Upgrading code from version 0.3.x to version 1.0\nFerrite version 1.0 contains a number of breaking changes compared to version 0.3.x. The Changelog documents all changes and there is also a section specifically for Upgrading code from Ferrite 0.3 to 1.0.","category":"page"},{"location":"","page":"Home","title":"Home","text":"note: Note\nPlease help improve this documentation – if something confuses you, chances are you're not alone. It's easy to do as you read along: just click on the \"Edit on GitHub\" link at the top of each page, and then edit the files directly in your browser. Your changes will be vetted by developers before becoming permanent, so don't worry about whether you might say something wrong. See also Contributing to Ferrite for more details.","category":"page"},{"location":"#How-the-documentation-is-organized","page":"Home","title":"How the documentation is organized","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This high level view of the documentation structure will help you find what you are looking for. The document is organized as follows[1]:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tutorials are thoroughly documented examples which guides you through the process of solving partial differential equations using Ferrite.\nTopic guides contains more in-depth explanations and discussions about finite element programming concepts and ideas, and specifically how these are realized in Ferrite.\nReference contains the technical API reference of functions and methods (e.g. the documentation strings).\nHow-to guides will guide you through the steps involved in addressing common tasks and use-cases. These usually build on top of the tutorials and thus assume basic knowledge of how Ferrite works.","category":"page"},{"location":"","page":"Home","title":"Home","text":"[1]: The organization of the document follows the Diátaxis Framework.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The four sections above form the main user-facing parts of the documentation. In addition, the document also contain the following sections:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Code gallery contain user contributed example programs showcasing what can be done with Ferrite.\nChangelog contain release notes and information about how to upgrade between releases.\nDeveloper documentation contain documentation of Ferrite internal code and is mainly targeted at developers of Ferrite.","category":"page"},{"location":"#Getting-started","page":"Home","title":"Getting started","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"As a new user of Ferrite it is suggested to start working with the tutorials before using Ferrite to tackle the specific equation you ultimately want to solve. The tutorials start with explaining the basic concepts and then increase in complexity. Understanding the first tutorial program, solving the heat equation, is essential in order to understand how Ferrite works. Already this rather simple program discusses many of the important concepts. See the tutorials overview for suggestion on how to progress to more advanced usage.","category":"page"},{"location":"#Getting-help","page":"Home","title":"Getting help","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"If you have questions about Ferrite it is suggested to use the #ferrite-fem channel on the Julia Slack, or the #Ferrite.jl stream on Zulip. Alternatively you can use the discussion forum on the GitHub repository.","category":"page"},{"location":"#Installation","page":"Home","title":"Installation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"To use Ferrite you first need to install Julia, see https://julialang.org/ for details. Installing Ferrite can then be done from the Pkg REPL; press ] at the julia> promp to enter pkg> mode:","category":"page"},{"location":"","page":"Home","title":"Home","text":"pkg> add Ferrite","category":"page"},{"location":"","page":"Home","title":"Home","text":"This will install Ferrite and all necessary dependencies. Press backspace to get back to the julia> prompt. (See the documentation for Pkg, Julia's package manager, for more help regarding package installation and project management.)","category":"page"},{"location":"","page":"Home","title":"Home","text":"Finally, to load Ferrite, use","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Ferrite","category":"page"},{"location":"","page":"Home","title":"Home","text":"You are now all set to start using Ferrite!","category":"page"},{"location":"#Contributing-to-Ferrite","page":"Home","title":"Contributing to Ferrite","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Ferrite is still under active development. If you find a bug, or have ideas for improvements, you are encouraged to interact with the developers on the Ferrite GitHub repository. There is also a thorough contributor guide which can be found in CONTRIBUTING.md.","category":"page"},{"location":"devdocs/assembly/#devdocs-assembly","page":"Assembly","title":"Assembly","text":"","category":"section"},{"location":"devdocs/assembly/#Type-definitions","page":"Assembly","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/assembly/","page":"Assembly","title":"Assembly","text":"Ferrite.COOAssembler\nFerrite.CSCAssembler\nFerrite.SymmetricCSCAssembler","category":"page"},{"location":"devdocs/assembly/#Ferrite.COOAssembler","page":"Assembly","title":"Ferrite.COOAssembler","text":"struct COOAssembler{Tv, Ti}\n\nThis assembler creates a COO (coordinate format) representation of a sparse matrix during assembly and converts it into a SparseMatrixCSC{Tv, Ti} on finalization.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/assembly/#Ferrite.CSCAssembler","page":"Assembly","title":"Ferrite.CSCAssembler","text":"Assembler for sparse matrix with CSC storage type.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/assembly/#Ferrite.SymmetricCSCAssembler","page":"Assembly","title":"Ferrite.SymmetricCSCAssembler","text":"Assembler for symmetric sparse matrix with CSC storage type.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/assembly/#Utility-functions","page":"Assembly","title":"Utility functions","text":"","category":"section"},{"location":"devdocs/assembly/","page":"Assembly","title":"Assembly","text":"Ferrite.matrix_handle\nFerrite.vector_handle\nFerrite._sortdofs_for_assembly!\nFerrite.sortperm2!","category":"page"},{"location":"devdocs/assembly/#Ferrite.matrix_handle","page":"Assembly","title":"Ferrite.matrix_handle","text":"matrix_handle(a::AbstractAssembler)\nvector_handle(a::AbstractAssembler)\n\nReturn a reference to the underlying matrix/vector of the assembler used during assembly operations.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/assembly/#Ferrite.vector_handle","page":"Assembly","title":"Ferrite.vector_handle","text":"matrix_handle(a::AbstractAssembler)\nvector_handle(a::AbstractAssembler)\n\nReturn a reference to the underlying matrix/vector of the assembler used during assembly operations.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/assembly/#Ferrite._sortdofs_for_assembly!","page":"Assembly","title":"Ferrite._sortdofs_for_assembly!","text":"_sortdofs_for_assembly!(permutation::Vector{Int}, sorteddofs::Vector{Int}, dofs::AbstractVector)\n\nSorts the dofs into a separate buffer and returns it together with a permutation vector.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/assembly/#Ferrite.sortperm2!","page":"Assembly","title":"Ferrite.sortperm2!","text":"sortperm2!(data::AbstractVector, permutation::AbstractVector)\n\nSort the input vector inplace and compute the corresponding permutation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#devdocs-interpolations","page":"Interpolations","title":"Interpolations","text":"","category":"section"},{"location":"devdocs/interpolations/#Type-definitions","page":"Interpolations","title":"Type definitions","text":"","category":"section"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Interpolations are subtypes of Interpolation{shape, order}, i.e. they are parametrized by the reference element and its characteristic order.","category":"page"},{"location":"devdocs/interpolations/#Fallback-methods-applicable-for-all-subtypes-of-Interpolation","page":"Interpolations","title":"Fallback methods applicable for all subtypes of Interpolation","text":"","category":"section"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Ferrite.getrefshape(::Interpolation)\nFerrite.getorder(::Interpolation)\nFerrite.reference_shape_gradient(::Interpolation, ::Vec, ::Int)\nFerrite.reference_shape_gradient_and_value(::Interpolation, ::Vec, ::Int)\nFerrite.reference_shape_hessian_gradient_and_value(::Interpolation, ::Vec, ::Int)\nFerrite.boundarydof_indices\nFerrite.dirichlet_boundarydof_indices\nFerrite.reference_shape_values!\nFerrite.reference_shape_gradients!\nFerrite.reference_shape_gradients_and_values!\nFerrite.reference_shape_hessians_gradients_and_values!","category":"page"},{"location":"devdocs/interpolations/#Ferrite.getrefshape-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.getrefshape","text":"Ferrite.getrefshape(::Interpolation)::AbstractRefShape\n\nReturn the reference element shape of the interpolation.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.getorder-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.getorder","text":"Ferrite.getorder(::Interpolation)\n\nReturn order of the interpolation.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradient-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_gradient","text":"reference_shape_gradient(ip::Interpolation, ξ::Vec, i::Int)\n\nEvaluate the gradient of the ith shape function of the interpolation ip in reference coordinate ξ.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradient_and_value-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_gradient_and_value","text":"reference_shape_gradient_and_value(ip::Interpolation, ξ::Vec, i::Int)\n\nOptimized version combining the evaluation Ferrite.reference_shape_value(::Interpolation) and Ferrite.reference_shape_gradient(::Interpolation).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_hessian_gradient_and_value-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_hessian_gradient_and_value","text":"reference_shape_hessian_gradient_and_value(ip::Interpolation, ξ::Vec, i::Int)\n\nOptimized version combining the evaluation Ferrite.reference_shape_value(::Interpolation), Ferrite.reference_shape_gradient(::Interpolation), and the gradient of the latter.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.boundarydof_indices","page":"Interpolations","title":"Ferrite.boundarydof_indices","text":"boundarydof_indices(::Type{<:BoundaryIndex})\n\nHelper function to generically dispatch on the correct dof sets of a boundary entity.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_boundarydof_indices","page":"Interpolations","title":"Ferrite.dirichlet_boundarydof_indices","text":"dirichlet_boundarydof_indices(::Type{<:BoundaryIndex})\n\nHelper function to generically dispatch on the correct dof sets of a boundary entity. Used internally in ConstraintHandler and defaults to boundarydof_indices(ip::Interpolation) for continuous interpolation.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_values!","page":"Interpolations","title":"Ferrite.reference_shape_values!","text":"reference_shape_values!(values::AbstractArray{T}, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape functions of ip at once at the reference point ξ and store them in values.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradients!","page":"Interpolations","title":"Ferrite.reference_shape_gradients!","text":"reference_shape_gradients!(gradients::AbstractArray, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape function gradients of ip at once at the reference point ξ and store them in gradients.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_gradients_and_values!","page":"Interpolations","title":"Ferrite.reference_shape_gradients_and_values!","text":"reference_shape_gradients_and_values!(gradients::AbstractArray, values::AbstractArray, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape function gradients and values of ip at once at the reference point ξ and store them in values.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_hessians_gradients_and_values!","page":"Interpolations","title":"Ferrite.reference_shape_hessians_gradients_and_values!","text":"reference_shape_hessians_gradients_and_values!(hessians::AbstractVector, gradients::AbstractVector, values::AbstractVector, ip::Interpolation, ξ::Vec)\n\nEvaluate all shape function hessians, gradients and values of ip at once at the reference point ξ and store them in hessians, gradients, and values.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/#Required-methods-to-implement-for-all-subtypes-of-Interpolation-to-define-a-new-finite-element","page":"Interpolations","title":"Required methods to implement for all subtypes of Interpolation to define a new finite element","text":"","category":"section"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Depending on the dimension of the reference element the following functions have to be implemented","category":"page"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"Ferrite.reference_shape_value(::Interpolation, ::Vec, ::Int)\nFerrite.vertexdof_indices(::Interpolation)\nFerrite.dirichlet_vertexdof_indices(::Interpolation)\nFerrite.facedof_indices(::Interpolation)\nFerrite.dirichlet_facedof_indices(::Interpolation)\nFerrite.facedof_interior_indices(::Interpolation)\nFerrite.edgedof_indices(::Interpolation)\nFerrite.dirichlet_edgedof_indices(::Interpolation)\nFerrite.edgedof_interior_indices(::Interpolation)\nFerrite.volumedof_interior_indices(::Interpolation)\nFerrite.getnbasefunctions(::Interpolation)\nFerrite.reference_coordinates(::Interpolation)\nFerrite.is_discontinuous(::Interpolation)\nFerrite.adjust_dofs_during_distribution(::Interpolation)\nFerrite.mapping_type","category":"page"},{"location":"devdocs/interpolations/#Ferrite.reference_shape_value-Tuple{Interpolation, Vec, Int64}","page":"Interpolations","title":"Ferrite.reference_shape_value","text":"reference_shape_value(ip::Interpolation, ξ::Vec, i::Int)\n\nEvaluate the value of the ith shape function of the interpolation ip at a point ξ on the reference element. The index i must match the index in vertices(::Interpolation), faces(::Interpolation) and edges(::Interpolation).\n\nFor nodal interpolations the indices also must match the indices of reference_coordinates(::Interpolation).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.vertexdof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.vertexdof_indices","text":"vertexdof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_vertexdof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.dirichlet_vertexdof_indices","text":"dirichlet_vertexdof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective vertex in local enumeration on a cell defined by vertices(::Cell). The vertex enumeration must match the vertex enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to vertexdof_indices(ip::Interpolation) for continuous interpolation.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be the 1, as vertex dofs are enumerated first.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.facedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.facedof_indices","text":"facedof_indices(ip::Interpolation)\n\nA tuple containing tuples of all local dof indices for the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_facedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.dirichlet_facedof_indices","text":"dirichlet_facedof_indices(ip::Interpolation)\n\nA tuple containing tuples of all local dof indices for the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to facedof_indices(ip::Interpolation) for continuous interpolation.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.facedof_interior_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.facedof_interior_indices","text":"facedof_interior_indices(ip::Interpolation)\n\nA tuple containing tuples of the local dof indices on the interior of the respective face in local enumeration on a cell defined by faces(::Cell). The face enumeration must match the face enumeration of the corresponding geometrical cell. Note that the vertex and edge dofs are included here.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be the computed via \"last edge interior dof index + 1\", if face dofs exist.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.edgedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.edgedof_indices","text":"edgedof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell.\n\nThe dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.dirichlet_edgedof_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.dirichlet_edgedof_indices","text":"dirichlet_edgedof_indices(ip::Interpolation)\n\nA tuple containing tuples of local dof indices for the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Used internally in ConstraintHandler and defaults to edgedof_indices(ip::Interpolation) for continuous interpolation.\n\nThe dofs are guaranteed to be aligned with the local ordering of the entities on the oriented edge. Here the first entries are the vertex dofs, followed by the edge interior dofs.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.edgedof_interior_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.edgedof_interior_indices","text":"edgedof_interior_indices(ip::Interpolation)\n\nA tuple containing tuples of the local dof indices on the interior of the respective edge in local enumeration on a cell defined by edges(::Cell). The edge enumeration must match the edge enumeration of the corresponding geometrical cell. Note that the vertex dofs are included here.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing! The first dof must be computed via \"last vertex dof index + 1\", if edge dofs exist.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.volumedof_interior_indices-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.volumedof_interior_indices","text":"volumedof_interior_indices(ip::Interpolation)\n\nTuple containing the dof indices associated with the interior of a volume.\n\nnote: Note\nThe dofs appearing in the tuple must be continuous and increasing, volumedofs are enumerated last.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.getnbasefunctions-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.getnbasefunctions","text":"Ferrite.getnbasefunctions(ip::Interpolation)\n\nReturn the number of base functions for the interpolation ip.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.reference_coordinates-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.reference_coordinates","text":"reference_coordinates(ip::Interpolation)\n\nReturns a vector of coordinates with length getnbasefunctions(::Interpolation) and indices corresponding to the indices of a dof in vertices, faces and edges.\n\nOnly required for nodal interpolations.\n\nTODO: Separate nodal and non-nodal interpolations.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.is_discontinuous-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.is_discontinuous","text":"is_discontinuous(::Interpolation)\nis_discontinuous(::Type{<:Interpolation})\n\nChecks whether the interpolation is discontinuous (i.e. DiscontinuousLagrange)\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.adjust_dofs_during_distribution-Tuple{Interpolation}","page":"Interpolations","title":"Ferrite.adjust_dofs_during_distribution","text":"adjust_dofs_during_distribution(::Interpolation)\n\nThis function must return true if the dofs should be adjusted (i.e. permuted) during dof distribution. This is in contrast to i) adjusting the dofs during reinit! in the assembly loop, or ii) not adjusting at all (which is not needed for low order interpolations, generally).\n\n\n\n\n\n","category":"method"},{"location":"devdocs/interpolations/#Ferrite.mapping_type","page":"Interpolations","title":"Ferrite.mapping_type","text":"mapping_type(ip::Interpolation)\n\nGet the type of mapping from the reference cell to the real cell for an interpolation ip. Subtypes of ScalarInterpolation and VectorizedInterpolation return IdentityMapping(), but other non-scalar interpolations may request different mapping types.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/interpolations/","page":"Interpolations","title":"Interpolations","text":"for all entities which exist on that reference element. The dof functions default to having no dofs defined on a specific entity. Hence, not overloading of the dof functions will result in an element with zero dofs. Also, it should always be double checked that everything is consistent as specified in the docstring of the corresponding function, as inconsistent implementations can lead to bugs which are really difficult to track down.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/boundary_conditions/#Boundary-and-initial-conditions","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Every PDE is accompanied with boundary conditions. There are different types of boundary conditions, and they need to be handled in different ways. Below we discuss how to handle the most common ones, Dirichlet and Neumann boundary conditions, and how to do it in Ferrite.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"While boundary conditions can be applied directly to nodes, vertices, edges, or faces, they are most commonly applied to facets. Each facet is described by a FacetIndex. When adding boundary conditions to points instead, vertices are preferred over nodes.","category":"page"},{"location":"topics/boundary_conditions/#Dirichlet-boundary-conditions","page":"Boundary and initial conditions","title":"Dirichlet boundary conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"At a Dirichlet boundary the unknown field is prescribed to a given value. For the discrete FE-solution this means that there are some degrees of freedom that are fixed. To handle Dirichlet boundary conditions in Ferrite we use the ConstraintHandler. A constraint handler is created from a DoF handler:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"ch = ConstraintHandler(dh)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"We can now create Dirichlet constraints and add them to the constraint handler. To create a Dirichlet constraint we need to specify a field name, a part of the boundary, and a function for computing the prescribed value. Example:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"dbc1 = Dirichlet(\n :u, # Name of the field\n getfacetset(grid, \"left\"), # Part of the boundary\n x -> 1.0, # Function mapping coordinate to a prescribed value\n)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"The field name is given as a symbol, just like when the field was added to the dof handler, the part of the boundary where this constraint is active is given as a facet set, and the function computing the prescribed value should be of the form f(x) or f(x, t) (coordinate x and time t) and return the prescribed value(s).","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Multiple sets\nTo apply a constraint on multiple facet sets in the grid you can use union to join them, for exampleleft_right = union(getfacetset(grid, \"left\"), getfacetset(grid, \"right\"))creates a new facetset containing all facets in the \"left\" and \"right\" facetsets, which can be passed to the Dirichlet constructor.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"By default the constraint is added to all components of the given field. To add the constraint to selected components a fourth argument with the components should be passed to the constructor. Here is an example where a constraint is added to component 1 and 3 of a vector field :u:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"dbc2 = Dirichlet(\n :u, # Name of the field\n getfacetset(grid, \"left\"), # Part of the boundary\n x -> [0.0, 0.0], # Function mapping coordinate to prescribed values\n [1, 3], # Components\n)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Note that the return value of the function must match with the components – in the example above we prescribe components 1 and 3 to 0 so we return a vector of length 2.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Adding the constraints to the constraint handler is done with add!:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"add!(ch, dbc1)\nadd!(ch, dbc2)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Finally, just like for the dof handler, we need to use close! to finalize the constraint handler. Internally this will then compute the degrees-of-freedom that match the constraints we added.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"If one or more of the constraints depend on time, i.e. they are specified as f(x, t), the prescribed values can be recomputed in each new time step by calling update! with the proper time, e.g.:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"for t in 0.0:0.1:1.0\n update!(ch, t) # Compute prescribed values for this t\n # Solve for time t...\nend","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Examples\nMost examples make use of Dirichlet boundary conditions, for example Heat Equation.","category":"page"},{"location":"topics/boundary_conditions/#Neumann-boundary-conditions","page":"Boundary and initial conditions","title":"Neumann boundary conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"At the Neumann part of the boundary we know something about the gradient of the solution. Two different methods for applying these are described below. For complete examples that use Neumann boundary conditions, please see","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"von-Mises-plasticity\nHyperelasticity","category":"page"},{"location":"topics/boundary_conditions/#Using-the-FacetIterator","page":"Boundary and initial conditions","title":"Using the FacetIterator","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"A Neumann boundary contribution can be added by iterating over the relevant facetset by using the FacetIterator. For a scalar field, this can be done as","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"grid = generate_grid(Quadrilateral, (3,3))\ndh = DofHandler(grid); push!(dh, :u, 1); close!(dh)\nfv = FacetValues(QuadratureRule{RefQuadrilateral}(2), Lagrange{RefQuadrilateral, 1}())\nf = zeros(ndofs(dh))\nfe = zeros(ndofs_per_cell(dh))\nqn = 1.0 # Normal flux\nfor fc in FacetIterator(dh, getfacetset(grid, \"right\"))\n reinit!(fv, fc)\n fill!(fe, 0)\n for q_point in 1:getnquadpoints(fv)\n dΓ = getdetJdV(fv, q_point)\n for i in 1:getnbasefunctions(fv)\n δu = shape_value(fv, q_point, i)\n fe[i] += δu * qn * dΓ\n end\n end\n assemble!(f, celldofs(fc), fe)\nend","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Alternatively, it is possible to add the values directly to the global f (without going through the local fe vector and then using assemble!):","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"# ...\ndofs = celldofs(fc)\nfor i in 1:getnbasefunctions(fv)\n f[dofs[i]] += δu * qn * dΓ\nend","category":"page"},{"location":"topics/boundary_conditions/#In-the-element-routine","page":"Boundary and initial conditions","title":"In the element routine","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Alternatively, the following code snippet can be included in the element routine, to evaluate the boundary integral:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"for facet in 1:nfacets(cell)\n if (cellid(cell), facet) ∈ getfacetset(grid, \"Neumann Boundary\")\n reinit!(facetvalues, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:getnbasefunctions(facetvalues)\n δu = shape_value(facetvalues, q_point, i)\n fe[i] += δu * qn * dΓ\n end\n end\n end\nend","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"We start by looping over all the facets of the cell, next we check if this particular facet is located on our facetset of interest called \"Neumann Boundary\". If we have determined that the current facet is indeed on the boundary and in our facetset, then we reinitialize FacetValues for this facet, using reinit!. When reinit!ing FacetValues we also need to give the facet number in addition to the cell. Next we simply loop over the quadrature points of the facet, and then loop over all the test functions and assemble the contribution to the force vector.","category":"page"},{"location":"topics/boundary_conditions/#Periodic-boundary-conditions","page":"Boundary and initial conditions","title":"Periodic boundary conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Periodic boundary conditions ensure that the solution is periodic across two boundaries. To define the periodicity we first define the image boundary Gamma^+ and the mirror boundary Gamma^-. We also define a (unique) coordinate mapping between the image and the mirror: varphi Gamma^+ rightarrow Gamma^-. With the mapping we can, for every coordinate on the image, compute the corresponding coordinate on the mirror:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"boldsymbolx^- = varphi(boldsymbolx^+)quad boldsymbolx^- in Gamma^-\nboldsymbolx^+ in Gamma^+","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"We now want to ensure that the solution on the image Gamma^+ is mirrored on the mirror Gamma^-. This periodicity constraint can thus be described by","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"u(boldsymbolx^-) = u(boldsymbolx^+)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Sometimes this is written as","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"llbracket u rrbracket = 0","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"where llbracket bullet rrbracket = bullet(boldsymbolx^+) - bullet(boldsymbolx^-) is the \"jump operator\". Thus, this condition ensure that the jump, or difference, in the solution between the image and mirror boundary is the zero – the solution becomes periodic. For a vector valued problem the periodicity constraint can in general be written as","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"boldsymbolu(boldsymbolx^-) = boldsymbolR cdot boldsymbolu(boldsymbolx^+)\nquad Leftrightarrow quad llbracket boldsymbolu rrbracket =\nboldsymbolR cdot boldsymbolu(boldsymbolx^+) - boldsymbolu(boldsymbolx^-) =\nboldsymbol0","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"where boldsymbolR is a rotation matrix. If the mapping between mirror and image is simply a translation (e.g. sides of a cube) this matrix will be the identity matrix.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"In Ferrite this type of periodic Dirichlet boundary conditions can be added to the ConstraintHandler by constructing an instance of PeriodicDirichlet. This is usually done it two steps. First we compute the mapping between mirror and image facets using collect_periodic_facets. Here we specify the mirror set and image sets (the sets are usually known or can be constructed easily ) and the mapping varphi. Second we construct the constraint using the PeriodicDirichlet constructor. Here we specify which components of the function that should be constrained, and the rotation matrix boldsymbolR (when needed). When adding the constraint to the ConstraintHandler the resulting dof-mapping is computed.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Here is a simple example where periodicity is enforced for components 1 and 2 of the field :u between the mirror boundary set \"left\" and the image boundary set \"right\". Note that no rotation matrix is needed here since the mirror and image are parallel, just shifted in the x-direction (as seen by the mapping φ):","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"# Create a constraint handler from the dof handler\nch = ConstraintHandler(dofhandler)\n\n# Compute the facet mapping\nφ(x) = x - Vec{2}((1.0, 0.0))\nface_mapping = collect_periodic_facets(grid, \"left\", \"right\", φ)\n\n# Construct the periodic constraint for field :u\npdbc = PeriodicDirichlet(:u, face_mapping, [1, 2])\n\n# Add the constraint to the constraint handler\nadd!(ch, pdbc)\n\n# If no more constraints should be added we can close\nclose!(ch)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Note\nPeriodicDirichlet constraints are imposed in a strong sense, so note that this requires a periodic mesh such that it is possible to compute the facet mapping between facets on the mirror and boundary.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Examples\nPeriodic boundary conditions are used in the following examples Computational homogenization, Stokes flow.","category":"page"},{"location":"topics/boundary_conditions/#Heterogeneous-\"periodic\"-constraint","page":"Boundary and initial conditions","title":"Heterogeneous \"periodic\" constraint","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"It is also possible to define constraints of the form","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"llbracket u rrbracket = llbracket f rrbracket\nquad Leftrightarrow quad\nu(boldsymbolx^+) - u(boldsymbolx^-) =\nf(boldsymbolx^+) - f(boldsymbolx^-)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"where f is a prescribed function. Although the constraint in this case is not technically periodic, PeriodicDirichlet can be used for this too. This is done by passing a function to PeriodicDirichlet, similar to Dirichlet, which, given the coordinate boldsymbolx and time t, computes the prescribed values of f on the boundary.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"Here is an example of how to implement this type of boundary condition, for a known function f:","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"pdbc = PeriodicDirichlet(\n :u,\n face_mapping,\n (x, t) -> f(x),\n [1, 2],\n)","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Note\nOne application for this type of boundary conditions is multiscale modeling and computational homogenization when solving the finite element problem for the subscale. In this case the unknown u is split into a macroscopic part u^mathrmM and a microscopic/fluctuation part u^mu, i.e. u = u^mathrmM + u^mu. Periodicity is then usually enforced for the fluctuation part, i.e. llbracket u^mu rrbracket = 0. The equivalent constraint for u then becomes llbracket u rrbracket = llbracket u^mathrmM rrbracket.As an example, consider first order homogenization where the macroscopic part is constructed as u^mathrmM = baru + boldsymbolnabla baru cdot boldsymbolx - barboldsymbolx for known baru and boldsymbolnabla baru. This could be implemented aspdbc = PeriodicDirichlet(\n :u,\n face_mapping,\n (x, t) -> ū + ∇ū ⋅ (x - x̄)\n)","category":"page"},{"location":"topics/boundary_conditions/#Initial-conditions","page":"Boundary and initial conditions","title":"Initial conditions","text":"","category":"section"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"When solving time-dependent problems, initial conditions, different from zero, may be required. For finite element formulations of ODE-type, i.e. boldsymbolu(t) = boldsymbolf(boldsymbolu(t)t), where boldsymbolu(t) are the degrees of freedom, initial conditions can be specified by the apply_analytical! function. For example, specify the initial pressure as a function of the y-coordinate","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"ρ = 1000; g = 9.81 # density [kg/m³] and gravity [N/kg]\ngrid = generate_grid(Quadrilateral, (10,10))\ndh = DofHandler(grid); add!(dh, :u, 2); add!(dh, :p, 1); close!(dh)\nu = zeros(ndofs(dh))\napply_analytical!(u, dh, :p, x -> ρ * g * x[2])","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"See also Transient heat equation for one example.","category":"page"},{"location":"topics/boundary_conditions/","page":"Boundary and initial conditions","title":"Boundary and initial conditions","text":"note: Consistency\napply_analytical! does not enforce consistency of the applied solution with the system of equations. Some problems, like for example differential-algebraic systems of equations (DAEs) need extra care during initialization. We refer to the paper \"Consistent Initial Condition Calculation for Differential-Algebraic Systems\" by Brown et al. for more details on this matter.","category":"page"},{"location":"tutorials/#Tutorials","page":"Tutorials overview","title":"Tutorials","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"On this page you find an overview of Ferrite tutorials. The tutorials explain and show how Ferrite can be used to solve a wide range of problems. See also the Code gallery for more examples.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"The tutorials all follow roughly the same structure:","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Introduction introduces the problem to be solved and discusses the learning outcomes of the tutorial.\nCommented program is the code for solving the problem with explanations and comments.\nPlain program is the raw source code of the program.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"When studying the tutorials it is a good idea to obtain a local copy of the code and run it on your own machine as you read along. Some of the tutorials also include suggestions for tweaks to the program that you can try out on your own.","category":"page"},{"location":"tutorials/#Tutorial-index","page":"Tutorials overview","title":"Tutorial index","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"The tutorials are listed in roughly increasing order of complexity. However, since they focus on different aspects, and solve different problems, it is suggested to have a look at the brief descriptions below to get an idea about what you will learn from each tutorial.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"If you are new to Ferrite then Tutorial 1 - Tutorial 6 is the best place to start. These tutorials introduces and teaches most of the basic finite element techniques (e.g. linear and non-linear problems, scalar- and vector-valued problems, Dirichlet and Neumann boundary conditions, mixed finite elements, time integration, direct and iterative linear solvers, etc). In particular the very first tutorial is essential in order to be able to follow any of the other tutorials. The remaining tutorials discuss more advanced topics.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-1:-Heat-equation](heat_equation.md)","page":"Tutorials overview","title":"Tutorial 1: Heat equation","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with homogeneous Dirichlet boundary conditions. This tutorial introduces and teaches many important parts of Ferrite: problem setup, degree of freedom management, assembly procedure, boundary conditions, solving the linear system, visualization of the result). Understanding this tutorial is essential to follow more complex tutorials.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: scalar-valued solution, Dirichlet boundary conditions.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-2:-Linear-elasticity](linear_elasticity.md)","page":"Tutorials overview","title":"Tutorial 2: Linear elasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"TBW.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: vector-valued solution, Dirichlet and Neumann boundary conditions.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-3:-Incompressible-elasticity](incompressible_elasticity.md)","page":"Tutorials overview","title":"Tutorial 3: Incompressible elasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial focuses on a mixed formulation of linear elasticity, with (vector) displacement and (scalar) pressure as the two unknowns, suitable for incompressibility. Thus, this tutorial guides you through the process of solving a problem with two unknowns from two coupled weak forms. The problem that is studied is Cook's membrane in the incompressible limit.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: mixed finite elements, Dirichlet and Neumann boundary conditions.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-4:-Hyperelasticity](hyperelasticity.md)","page":"Tutorials overview","title":"Tutorial 4: Hyperelasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial you will learn how to solve a non-linear finite element problem. In particular, a hyperelastic material model, in a finite strain setting, is used to solve the rotation of a cube. Automatic differentiatio (AD) is used for the consitutive relations. Newton's method is used for the non-linear iteration, and a conjugate gradient (CG) solver is used for the linear solution of the increment.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: non-linear finite element, finite strain, automatic differentiation (AD), Newton's method, conjugate gradient (CG).","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-5:-von-Mises-Plasticity](plasticity.md)","page":"Tutorials overview","title":"Tutorial 5: von Mises Plasticity","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial revisits the cantilever beam problem from Tutorial 2: Linear elasticity, but instead of linear elasticity a plasticity model is used for the constitutive relation. You will learn how to solve a problem which require the solution of a local material problem, and the storage of material state, in each quadrature point. Newton's method is used both locally in the material routine, and globally on the finite element level.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: non-linear finite element, plasticity, material modeling, state variables, Newton’s method.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-6:-Transient-heat-equation](@ref-tutorial-transient-heat-equation)","page":"Tutorials overview","title":"Tutorial 6: Transient heat equation","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial the transient heat equation is solved on the unit square. The problem to be solved is thus similar to the one solved in the first tutorial, Heat equation, but with time-varying boundary conditions. In particular you will learn how to solve a time dependent problem with an implicit Euler scheme for the time integration.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: time dependent finite elements, implicit Euler time integration.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-7:-Computational-homogenization](computational_homogenization.md)","page":"Tutorials overview","title":"Tutorial 7: Computational homogenization","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial guides you through computational homogenization of an representative volume element (RVE) consisting of a soft matrix material with stiff inclusions. The computational mesh is read from an external mesh file generated with Gmsh. Dirichlet and periodic boundary conditions are used.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: Gmsh mesh reading, Dirichlet and periodic boundary conditions","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-8:-Stokes-flow](stokes-flow.md)","page":"Tutorials overview","title":"Tutorial 8: Stokes flow","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial Stokes flow with (vector) velocity and (scalar) pressure is solved on on a quarter circle. Rotationally periodic boundary conditions is used for the inlet/outlet coupling. To obtain a unique solution, a mean value constraint is applied on the pressure using an affine constraint. The computational mesh is generated directly using the Gmsh API.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: periodic boundary conditions, mean value constraint, mesh generation with Gmsh.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-9:-Porous-media-(SubDofHandler)](porous_media.md)","page":"Tutorials overview","title":"Tutorial 9: Porous media (SubDofHandler)","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial introduces how to solve a complex linear problem, where there are different fields on different subdomains, and different cell types in the grid. This requires using the SubDofHandler interface.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: Mixed grids, multiple fields, porous media, SubDofHandler","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-10:-Incompressible-Navier-Stokes-equations](ns_vs_diffeq.md)","page":"Tutorials overview","title":"Tutorial 10: Incompressible Navier-Stokes equations","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial the incompressible Navier-Stokes equations are solved. The domain is discretized in space with Ferrite as usual, and then forumalated in a way to be compatible with the OrdinaryDiffEq.jl package, which is used for the time-integration.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: non-linear time dependent problem","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-10:-Reactive-surface](@ref-tutorial-reactive-surface)","page":"Tutorials overview","title":"Tutorial 10: Reactive surface","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial a reaction diffusion system on a sphere surface embedded in 3D is solved. Ferrite is used to assemble the diffusion operators and the mass matrices. The problem is solved by using the usual first order reaction diffusion operator splitting.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: embedded elements, operator splitting, gmsh","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-11:-Linear-shell](@ref-tutorial-linear-shell)","page":"Tutorials overview","title":"Tutorial 11: Linear shell","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"In this tutorial a linear shell element formulation is set up as a two-dimensional domain embedded in three-dimensional space. This will teach, and perhaps inspire, you on how Ferrite can be used for non-standard things and how to add \"hacks\" that build on top of Ferrite.","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: shell elements, automatic differentiation","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"","category":"page"},{"location":"tutorials/#[Tutorial-12:-Discontinuous-Galerkin-heat-equation](@ref-tutorial-dg-heat-equation)","page":"Tutorials overview","title":"Tutorial 12: Discontinuous Galerkin heat equation","text":"","category":"section"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with inhomogeneous Dirichlet and Neumann boundary conditions using the interior penalty discontinuous Galerkin method. This tutorial follows the heat equation tutorial, introducing face and interface iterators, jump and average operators, and cross-element coupling in sparsity patterns. This example was developed as part of the Google Summer of Code funded project \"Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl\".","category":"page"},{"location":"tutorials/","page":"Tutorials overview","title":"Tutorials overview","text":"Keywords: scalar-valued solution, Dirichlet boundary conditions, Discontinuous Galerkin, Interior penalty","category":"page"},{"location":"howto/#How-to-guides","page":"How-to guide overview","title":"How-to guides","text":"","category":"section"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"This page gives an overview of the how-to guides. How-to guides address various common tasks one might want to do in a finite element program. Many of the guides are extensions, or build on top of, the tutorials and, therefore, some familiarity with Ferrite is assumed.","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"","category":"page"},{"location":"howto/#[Post-processing-and-visualization](postprocessing.md)","page":"How-to guide overview","title":"Post processing and visualization","text":"","category":"section"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"This guide builds on top of Tutorial 1: Heat equation and discusses various post processsing techniques with the goal of visualizing primary fields (the finite element solution) and secondary quantities (e.g. fluxes, stresses, etc.). Concretely, this guide answers:","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"How to visualize data from quadrature points?\nHow to evaluate the finite element solution, or secondary quantities, in arbitrary points of the domain?","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"","category":"page"},{"location":"howto/#[Multi-threaded-assembly](threaded_assembly.md)","page":"How-to guide overview","title":"Multi-threaded assembly","text":"","category":"section"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"This guide modifies Tutorial 2: Linear elasticity such that the program is using multi-threading to parallelize the assembly procedure. Concretely this shows how to use grid coloring and \"scratch values\" in order to use multi-threading without running into race-conditions.","category":"page"},{"location":"howto/","page":"How-to guide overview","title":"How-to guide overview","text":"","category":"page"},{"location":"reference/sparsity_pattern/#Sparsity-pattern-and-sparse-matrices","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"This is the reference documentation for sparsity patterns and sparse matrix instantiation. See the topic section on Sparsity pattern and sparse matrices.","category":"page"},{"location":"reference/sparsity_pattern/#Sparsity-patterns","page":"Sparsity pattern and sparse matrices","title":"Sparsity patterns","text":"","category":"section"},{"location":"reference/sparsity_pattern/#AbstractSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"AbstractSparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"The following applies to all subtypes of AbstractSparsityPattern:","category":"page"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"Ferrite.AbstractSparsityPattern\ninit_sparsity_pattern\nadd_sparsity_entries!\nadd_cell_entries!\nadd_interface_entries!\nadd_constraint_entries!\nFerrite.add_entry!","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.AbstractSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.AbstractSparsityPattern","text":"Ferrite.AbstractSparsityPattern\n\nSupertype for sparsity pattern implementations, e.g. SparsityPattern and BlockSparsityPattern.\n\n\n\n\n\n","category":"type"},{"location":"reference/sparsity_pattern/#Ferrite.init_sparsity_pattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.init_sparsity_pattern","text":"init_sparsity_pattern(dh::DofHandler; nnz_per_row::Int)\n\nInitialize an empty SparsityPattern with ndofs(dh) rows and ndofs(dh) columns.\n\nKeyword arguments\n\nnnz_per_row: memory optimization hint for the number of non-zero entries per row that will be added to the pattern.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_sparsity_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_sparsity_entries!","text":"add_sparsity_entries!(\n sp::AbstractSparsityPattern,\n dh::DofHandler,\n ch::Union{ConstraintHandler, Nothing} = nothing;\n topology = nothing,\n keep_constrained::Bool = true,\n coupling = nothing,\n interface_coupling = nothing,\n)\n\nConvenience method for doing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!, depending on what arguments are passed:\n\nadd_cell_entries! is always called\nadd_interface_entries! is called if topology is provided (i.e. not nothing)\nadd_constraint_entries! is called if the ConstraintHandler is provided\n\nFor more details about arguments and keyword arguments, see the respective functions.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_cell_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_cell_entries!","text":"add_cell_entries!(\n sp::AbstractSparsityPattern,\n dh::DofHandler,\n ch::Union{ConstraintHandler, Nothing} = nothing;\n keep_constrained::Bool = true,\n coupling::Union{AbstractMatrix{Bool}, Nothing}, = nothing\n)\n\nAdd entries to the sparsity pattern sp corresponding to DoF couplings within the cells as described by the DofHandler dh.\n\nKeyword arguments\n\nkeep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.\ncoupling: the coupling between fields/components within each cell. By default (coupling = nothing) it is assumed that all DoFs in each cell couple with each other.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_interface_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_interface_entries!","text":"add_interface_entries!(\n sp::SparsityPattern, dh::DofHandler, ch::Union{ConstraintHandler, Nothing};\n topology::ExclusiveTopology, keep_constrained::Bool = true,\n interface_coupling::AbstractMatrix{Bool},\n)\n\nAdd entries to the sparsity pattern sp corresponding to DoF couplings on the interface between cells as described by the DofHandler dh.\n\nKeyword arguments\n\ntopology: the topology corresponding to the grid.\nkeep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern. keep_constrained = false requires passing the ConstraintHandler ch.\ninterface_coupling: the coupling between fields/components across the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_constraint_entries!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_constraint_entries!","text":"add_constraint_entries!(\n sp::AbstractSparsityPattern, ch::ConstraintHandler;\n keep_constrained::Bool = true,\n)\n\nAdd all entries resulting from constraints in the ConstraintHandler ch to the sparsity pattern. Note that, since this operation depends on existing entries in the pattern, this function must be called as the last step when creating the sparsity pattern.\n\nKeyword arguments\n\nkeep_constrained: whether or not entries for constrained DoFs should be kept (keep_constrained = true) or eliminated (keep_constrained = false) from the sparsity pattern.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#Ferrite.add_entry!","page":"Sparsity pattern and sparse matrices","title":"Ferrite.add_entry!","text":"add_entry!(sp::AbstractSparsityPattern, row::Int, col::Int)\n\nAdd an entry to the sparsity pattern sp at row row and column col.\n\n\n\n\n\n","category":"function"},{"location":"reference/sparsity_pattern/#SparsityPattern","page":"Sparsity pattern and sparse matrices","title":"SparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"SparsityPattern(::Int, ::Int)\nallocate_matrix(::SparsityPattern)\nSparsityPattern","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.SparsityPattern-Tuple{Int64, Int64}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.SparsityPattern","text":"SparsityPattern(nrows::Int, ncols::Int; nnz_per_row::Int = 8)\n\nCreate an empty SparsityPattern with nrows rows and ncols columns. nnz_per_row is used as a memory hint for the number of non zero entries per row.\n\nSparsityPattern is the default sparsity pattern type for the standard DofHandler and is therefore commonly constructed using init_sparsity_pattern instead of with this constructor.\n\nExamples\n\n# Create a sparsity pattern for an 100 x 100 matrix, hinting at 10 entries per row\nsparsity_pattern = SparsityPattern(100, 100; nnz_per_row = 10)\n\nMethods\n\nThe following methods apply to SparsityPattern (see their respective documentation for more details):\n\nadd_sparsity_entries!: convenience method for calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!.\nadd_cell_entries!: add entries corresponding to DoF couplings within the cells.\nadd_interface_entries!: add entries corresponding to DoF couplings on the interface between cells.\nadd_constraint_entries!: add entries resulting from constraints.\nallocate_matrix: instantiate a matrix from the pattern. The default matrix type is SparseMatrixCSC{Float64, Int}.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Tuple{SparsityPattern}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(sp::SparsityPattern)\n\nAllocate a sparse matrix of type SparseMatrixCSC{Float64, Int} from the sparsity pattern sp.\n\nThis method is a shorthand for the equivalent allocate_matrix(SparseMatrixCSC{Float64, Int}, sp).\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.SparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.SparsityPattern","text":"struct SparsityPattern <: AbstractSparsityPattern\n\nData structure representing non-zero entries in the eventual sparse matrix.\n\nSee the constructor SparsityPattern(::Int, ::Int) for the user-facing documentation.\n\nStruct fields\n\nnrows::Int: number of rows\nncols::Int: number of column\nrows::Vector{Vector{Int}}: vector of length nrows, where rows[i] is a sorted vector of column indices for non zero entries in row i.\n\nwarning: Internal struct\nThe specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.\n\n\n\n\n\n","category":"type"},{"location":"reference/sparsity_pattern/#BlockSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"BlockSparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"note: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.","category":"page"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"BlockSparsityPattern(::Vector{Int})\nMain.FerriteBlockArrays.BlockSparsityPattern\nallocate_matrix(::Main.FerriteBlockArrays.BlockSparsityPattern)\nallocate_matrix(::Type{<:BlockMatrix{T, Matrix{S}}}, sp::Main.FerriteBlockArrays.BlockSparsityPattern) where {T, S <: AbstractMatrix{T}}","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.BlockSparsityPattern-Tuple{Vector{Int64}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.BlockSparsityPattern","text":"BlockSparsityPattern(block_sizes::Vector{Int})\n\nCreate an empty BlockSparsityPattern with row and column block sizes given by block_sizes.\n\nExamples\n\n# Create a block sparsity pattern with block size 10 x 5\nsparsity_pattern = BlockSparsityPattern([10, 5])\n\nMethods\n\nThe following methods apply to BlockSparsityPattern (see their respective documentation for more details):\n\nadd_sparsity_entries!: convenience method for calling add_cell_entries!, add_interface_entries!, and add_constraint_entries!.\nadd_cell_entries!: add entries corresponding to DoF couplings within the cells.\nadd_interface_entries!: add entries corresponding to DoF couplings on the interface between cells.\nadd_constraint_entries!: add entries resulting from constraints.\nallocate_matrix: instantiate a (block) matrix from the pattern. The default matrix type is BlockMatrix{Float64, Matrix{SparseMatrixCSC{Float64, Int}}}, i.e. a BlockMatrix, where the individual blocks are of type SparseMatrixCSC{Float64, Int}.\n\nnote: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.BlockSparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Ferrite.BlockSparsityPattern","text":"struct BlockSparsityPattern <: AbstractSparsityPattern\n\nData structure representing non-zero entries for an eventual blocked sparse matrix.\n\nSee the constructor BlockSparsityPattern(::Vector{Int}) for the user-facing documentation.\n\nStruct fields\n\nnrows::Int: number of rows\nncols::Int: number of column\nblock_sizes::Vector{Int}: row and column block sizes\nblocks::Matrix{SparsityPattern}: matrix of size length(block_sizes) × length(block_sizes) where blocks[i, j] is a SparsityPattern corresponding to block (i, j).\n\nwarning: Internal struct\nThe specific implementation of this struct, such as struct fields, type layout and type parameters, are internal and should not be relied upon.\n\n\n\n\n\n","category":"type"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Tuple{BlockSparsityPattern}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)\n\nAllocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.\n\n\n\n\n\nallocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)\n\nInstantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.\n\n\n\n\n\nallocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)\n\nAllocate a matrix of type MatrixType from the DofHandler dh.\n\nThis is a convenience method and is equivalent to:\n\njulia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`\n\nRefer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.\n\nnote: Note\nIf more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. usesp = init_sparsity_pattern(dh)\nadd_sparsity_entries!(sp, dh)\nK = allocate_matrix(sp)\nM = allocate_matrix(sp)instead ofK = allocate_matrix(dh)\nM = allocate_matrix(dh)Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.\n\n\n\n\n\nallocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)\nallocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)\n\nInstantiate a blocked sparse matrix from the blocked sparsity pattern sp.\n\nThe type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).\n\nExamples\n\n# Create a sparse matrix with default block type\nallocate_matrix(BlockMatrix, sparsity_pattern)\n\n# Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}\nallocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)\n\nnote: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{S}, Tuple{T}, Tuple{Type{<:BlockArray{T, 2, Matrix{S}, BS} where BS<:Tuple{AbstractUnitRange{<:Integer}, AbstractUnitRange{<:Integer}}}, BlockSparsityPattern}} where {T, S<:AbstractMatrix{T}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{BlockMatrix}, sp::BlockSparsityPattern)\nallocate_matrix(::Type{BlockMatrix{T, Matrix{S}}}, sp::BlockSparsityPattern)\n\nInstantiate a blocked sparse matrix from the blocked sparsity pattern sp.\n\nThe type of the returned matrix is a BlockMatrix with blocks of type S (defaults to SparseMatrixCSC{T, Int}).\n\nExamples\n\n# Create a sparse matrix with default block type\nallocate_matrix(BlockMatrix, sparsity_pattern)\n\n# Create a sparse matrix with blocks of type SparseMatrixCSC{Float32, Int}\nallocate_matrix(BlockMatrix{Float32, Matrix{SparseMatrixCSC{Float32, Int}}}, sparsity_pattern)\n\nnote: Package extension\nThis functionality is only enabled when the package BlockArrays.jl is installed (pkg> add BlockArrays) and loaded (using BlockArrays) in the session.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Sparse-matrices","page":"Sparsity pattern and sparse matrices","title":"Sparse matrices","text":"","category":"section"},{"location":"reference/sparsity_pattern/#Creating-matrix-from-SparsityPattern","page":"Sparsity pattern and sparse matrices","title":"Creating matrix from SparsityPattern","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"allocate_matrix(::Type{S}, ::Ferrite.AbstractSparsityPattern) where {Tv, Ti, S <: SparseMatrixCSC{Tv, Ti}}\nallocate_matrix(::Type{Symmetric{Tv, S}}, ::Ferrite.AbstractSparsityPattern) where {Tv, Ti, S <: SparseMatrixCSC{Tv, Ti}}","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{S}, Tuple{Ti}, Tuple{Tv}, Tuple{Type{S}, Ferrite.AbstractSparsityPattern}} where {Tv, Ti, S<:SparseMatrixCSC{Tv, Ti}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::SparsityPattern)\n\nAllocate a sparse matrix of type SparseMatrixCSC{Tv, Ti} from the sparsity pattern sp.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{S}, Tuple{Ti}, Tuple{Tv}, Tuple{Type{Symmetric{Tv, S}}, Ferrite.AbstractSparsityPattern}} where {Tv, Ti, S<:SparseMatrixCSC{Tv, Ti}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(::Type{Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}}, sp::SparsityPattern)\n\nInstantiate a sparse matrix of type Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, i.e. a LinearAlgebra.Symmetric-wrapped SparseMatrixCSC, from the sparsity pattern sp. The resulting matrix will only store entries above, and including, the diagonal.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Creating-matrix-from-DofHandler","page":"Sparsity pattern and sparse matrices","title":"Creating matrix from DofHandler","text":"","category":"section"},{"location":"reference/sparsity_pattern/","page":"Sparsity pattern and sparse matrices","title":"Sparsity pattern and sparse matrices","text":"allocate_matrix(::Type{MatrixType}, ::DofHandler, args...; kwargs...) where {MatrixType}\nallocate_matrix(::DofHandler, args...; kwargs...)","category":"page"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Union{Tuple{MatrixType}, Tuple{Type{MatrixType}, DofHandler, Vararg{Any}}} where MatrixType","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(MatrixType, dh::DofHandler, args...; kwargs...)\n\nAllocate a matrix of type MatrixType from the DofHandler dh.\n\nThis is a convenience method and is equivalent to:\n\njulia sp = init_sparsity_pattern(dh) add_sparsity_entries!(sp, dh, args...; kwargs...) allocate_matrix(MatrixType, sp)`\n\nRefer to allocate_matrix for supported matrix types, and to init_sparsity_pattern for details about supported arguments args and keyword arguments kwargs.\n\nnote: Note\nIf more than one sparse matrix is needed (e.g. a stiffness and a mass matrix) it is more efficient to explicitly create the sparsity pattern instead of using this method, i.e. usesp = init_sparsity_pattern(dh)\nadd_sparsity_entries!(sp, dh)\nK = allocate_matrix(sp)\nM = allocate_matrix(sp)instead ofK = allocate_matrix(dh)\nM = allocate_matrix(dh)Note that for some matrix types it is possible to copy the instantiated matrix (M = copy(K)) instead.\n\n\n\n\n\n","category":"method"},{"location":"reference/sparsity_pattern/#Ferrite.allocate_matrix-Tuple{DofHandler, Vararg{Any}}","page":"Sparsity pattern and sparse matrices","title":"Ferrite.allocate_matrix","text":"allocate_matrix(dh::DofHandler, args...; kwargs...)\n\nAllocate a matrix of type SparseMatrixCSC{Float64, Int} from the DofHandler dh.\n\nThis method is a shorthand for the equivalent allocate_matrix(SparseMatrixCSC{Float64, Int}, dh, args...; kwargs...) – refer to that method for details.\n\n\n\n\n\n","category":"method"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"topics/constraints/#Constraints","page":"Constraints","title":"Constraints","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"PDEs can in general be subjected to a number of constraints,","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"g_I(underlinea) = 0 quad I = 1 text to n_c","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"where g are (non-linear) constraint equations, underlinea is a vector of the degrees of freedom, and n_c is the number of constraints. There are many ways to enforce these constraints, e.g. penalty methods and Lagrange multiplier methods.","category":"page"},{"location":"topics/constraints/#Affine-constraints","page":"Constraints","title":"Affine constraints","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"Affine or linear constraints can be handled directly in Ferrite. Such constraints can typically be expressed as:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"a_1 = 5a_2 + 3a_3 + 1 \na_4 = 2a_3 + 6a_5 \ndots","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"where a_1, a_2 etc. are system degrees of freedom. In Ferrite, we can account for such constraint using the ConstraintHandler:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"ch = ConstraintHandler(dh)\nlc1 = AffineConstraint(1, [2 => 5.0, 3 => 3.0], 1)\nlc2 = AffineConstraint(4, [3 => 2.0, 5 => 6.0], 0)\nadd!(ch, lc1)\nadd!(ch, lc2)","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"Affine constraints will affect the sparsity pattern of the stiffness matrix, and as such, it is important to also include the ConstraintHandler as an argument when creating the sparsity pattern:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"K = allocate_matrix(dh, ch)","category":"page"},{"location":"topics/constraints/#Solving-linear-problems","page":"Constraints","title":"Solving linear problems","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"To solve the system underlineunderlineKunderlinea=underlinef, account for affine constraints the same way as for Dirichlet boundary conditions; first call apply!(K, f, ch). This will condense K and f inplace (i.e no new matrix will be created). Note however that we must also call apply! on the solution vector after solving the system to enforce the affine constraints:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"# ...\n# Assemble K and f...\n\napply!(K, f, ch)\na = K\\f\napply!(a, ch) # enforces affine constraints\n","category":"page"},{"location":"topics/constraints/#Solving-nonlinear-problems","page":"Constraints","title":"Solving nonlinear problems","text":"","category":"section"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"It is important to check the residual after applying boundary conditions when solving nonlinear problems with affine constraints. apply_zero!(K, r, ch) modifies the residual entries for dofs that are involved in constraints to account for constraint forces. The following pseudo-code shows a typical pattern for solving a non-linear problem with Newton's method:","category":"page"},{"location":"topics/constraints/","page":"Constraints","title":"Constraints","text":"a = initial_guess(...) # Make any initial guess for a here, e.g. `a=zeros(ndofs(dh))`\napply!(a, ch) # Make the guess fulfill all constraints in `ch`\nfor iter in 1:maxiter\n doassemble!(K, r, ...) # Assemble the residual, r, and stiffness, K=∂r/∂a.\n apply_zero!(K, r, ch) # Modify `K` and `r` to account for the constraints.\n check_convergence(r, ...) && break # Only check convergence after `apply_zero!(K, r, ch)`\n Δa = K \\ r # Calculate the (negative) update\n apply_zero!(Δa, ch) # Change the constrained values in `Δa` such that `a-Δa`\n # fulfills constraints if `a` did.\n a .-= Δa\nend","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"EditURL = \"../literate-howto/postprocessing.jl\"","category":"page"},{"location":"howto/postprocessing/#howto-postprocessing","page":"Post processing and visualization","title":"Post processing and visualization","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"(Image: )","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 1: Heat flux computed from the solution to the heat equation on the unit square, see previous example: Heat equation.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"tip: Tip\nThis example is also available as a Jupyter notebook: postprocessing.ipynb.","category":"page"},{"location":"howto/postprocessing/#Introduction","page":"Post processing and visualization","title":"Introduction","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"After running a simulation, we usually want to visualize the results in different ways. The L2Projector and the PointEvalHandler build a pipeline for doing so. With the L2Projector, integration point quantities can be projected to the nodes. The PointEvalHandler enables evaluation of the finite element approximated function in any coordinate in the domain. Thus with the combination of both functionalities, both nodal quantities and integration point quantities can be evaluated in any coordinate, allowing for example cut-planes through 3D structures or cut-lines through 2D-structures.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"This example continues from the Heat equation example, where the temperature field was determined on a square domain. In this example, we first compute the heat flux in each integration point (based on the solved temperature field) and then we do an L2-projection of the fluxes to the nodes of the mesh. By doing this, we can more easily visualize integration points quantities. Finally, we visualize the temperature field and the heat fluxes along a cut-line.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"The L2-projection is defined as follows: Find projection q(boldsymbolx) in U_h(Omega) such that","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"int v q mathrmdOmega = int v d mathrmdOmega quad forall v in U_h(Omega)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"where d is the quadrature data to project. Since the flux is a vector the projection function will be solved with multiple right hand sides, e.g. with d = q_x and d = q_y for this 2D problem. In this example, we use standard Lagrange interpolations, and the finite element space U_h is then a subset of the H^1 space (continuous functions).","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Ferrite has functionality for doing much of this automatically, as displayed in the code below. In particular L2Projector for assembling the left hand side, and project for assembling the right hand sides and solving for the projection.","category":"page"},{"location":"howto/postprocessing/#Implementation","page":"Post processing and visualization","title":"Implementation","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Start by simply running the Heat equation example to solve the problem","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"include(\"../tutorials/heat_equation.jl\");\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Next we define a function that computes the heat flux for each integration point in the domain. Fourier's law is adopted, where the conductivity tensor is assumed to be isotropic with unit conductivity lambda = 1 q = - nabla u, where u is the temperature.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"function compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}\n\n n = getnbasefunctions(cellvalues)\n cell_dofs = zeros(Int, n)\n nqp = getnquadpoints(cellvalues)\n\n # Allocate storage for the fluxes to store\n q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)]\n\n for (cell_num, cell) in enumerate(CellIterator(dh))\n q_cell = q[cell_num]\n celldofs!(cell_dofs, dh, cell_num)\n aᵉ = a[cell_dofs]\n reinit!(cellvalues, cell)\n\n for q_point in 1:nqp\n q_qp = - function_gradient(cellvalues, q_point, aᵉ)\n push!(q_cell, q_qp)\n end\n end\n return q\nend\nnothing # hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Now call the function to get all the fluxes.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"q_gp = compute_heat_fluxes(cellvalues, dh, u);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Next, create an L2Projector using the same interpolation as was used to approximate the temperature field. On instantiation, the projector assembles the coefficient matrix M and computes the Cholesky factorization of it. By doing so, the projector can be reused without having to invert M every time.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"projector = L2Projector(ip, grid);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Project the integration point values to the nodal values","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"q_projected = project(projector, q_gp, qr);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/#Exporting-to-VTK","page":"Post processing and visualization","title":"Exporting to VTK","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"To visualize the heat flux, we export the projected field q_projected to a VTK-file, which can be viewed in e.g. ParaView. The result is also visualized in Figure 1.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"VTKGridFile(\"heat_equation_flux\", grid) do vtk\n write_projection(vtk, projector, q_projected, \"q\")\nend;\nnothing #hide","category":"page"},{"location":"howto/postprocessing/#Point-evaluation","page":"Post processing and visualization","title":"Point evaluation","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"(Image: )","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 2: Visualization of the cut line where we want to compute the temperature and heat flux.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Consider a cut-line through the domain like the black line in Figure 2 above. We will evaluate the temperature and the heat flux distribution along a horizontal line.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"points = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"First, we need to generate a PointEvalHandler. This will find and store the cells containing the input points.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"ph = PointEvalHandler(grid, points);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"After the L2-Projection, the heat fluxes q_projected are stored in the DoF-ordering determined by the projector's internal DoFHandler, so to evaluate the flux q at our points we give the PointEvalHandler, the L2Projector and the values q_projected.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"q_points = evaluate_at_points(ph, projector, q_projected);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"We can also extract the field values, here the temperature, right away from the result vector of the simulation, that is stored in u. These values are stored in the order of our initial DofHandler so the input is not the PointEvalHandler, the original DofHandler, the dof-vector u, and (optionally for single-field problems) the name of the field. From the L2Projection, the values are stored in the order of the degrees of freedom.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"u_points = evaluate_at_points(ph, dh, u, :u);\nnothing #hide","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Now, we can plot the temperature and flux values with the help of any plotting library, e.g. Plots.jl. To do this, we need to import the package:","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"import Plots","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Firstly, we are going to plot the temperature values along the given line.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Plots.plot(getindex.(points, 1), u_points, xlabel = \"x (coordinate)\", ylabel = \"u (temperature)\", label = nothing)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 3: Temperature along the cut line from Figure 2.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Secondly, the horizontal heat flux (i.e. the first component of the heat flux vector) is plotted.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Plots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = \"x (coordinate)\", ylabel = \"q_x (flux in x-direction)\", label = nothing)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Figure 4: x-component of the flux along the cut line from Figure 2.","category":"page"},{"location":"howto/postprocessing/#postprocessing-plain-program","page":"Post processing and visualization","title":"Plain program","text":"","category":"section"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"Here follows a version of the program without any comments. The file is also available here: postprocessing.jl.","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"include(\"../tutorials/heat_equation.jl\");\n\nfunction compute_heat_fluxes(cellvalues::CellValues, dh::DofHandler, a::AbstractVector{T}) where {T}\n\n n = getnbasefunctions(cellvalues)\n cell_dofs = zeros(Int, n)\n nqp = getnquadpoints(cellvalues)\n\n # Allocate storage for the fluxes to store\n q = [Vec{2, T}[] for _ in 1:getncells(dh.grid)]\n\n for (cell_num, cell) in enumerate(CellIterator(dh))\n q_cell = q[cell_num]\n celldofs!(cell_dofs, dh, cell_num)\n aᵉ = a[cell_dofs]\n reinit!(cellvalues, cell)\n\n for q_point in 1:nqp\n q_qp = - function_gradient(cellvalues, q_point, aᵉ)\n push!(q_cell, q_qp)\n end\n end\n return q\nend\n\nq_gp = compute_heat_fluxes(cellvalues, dh, u);\n\nprojector = L2Projector(ip, grid);\n\nq_projected = project(projector, q_gp, qr);\n\nVTKGridFile(\"heat_equation_flux\", grid) do vtk\n write_projection(vtk, projector, q_projected, \"q\")\nend;\n\npoints = [Vec((x, 0.75)) for x in range(-1.0, 1.0, length = 101)];\n\nph = PointEvalHandler(grid, points);\n\nq_points = evaluate_at_points(ph, projector, q_projected);\n\nu_points = evaluate_at_points(ph, dh, u, :u);\n\nimport Plots\n\nPlots.plot(getindex.(points, 1), u_points, xlabel = \"x (coordinate)\", ylabel = \"u (temperature)\", label = nothing)\n\nPlots.plot(getindex.(points, 1), getindex.(q_points, 1), xlabel = \"x (coordinate)\", ylabel = \"q_x (flux in x-direction)\", label = nothing)","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"","category":"page"},{"location":"howto/postprocessing/","page":"Post processing and visualization","title":"Post processing and visualization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/grid/#Grid-and-AbstractGrid","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"","category":"section"},{"location":"reference/grid/#Grid","page":"Grid & AbstractGrid","title":"Grid","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"generate_grid\nNode\nCellIndex\nVertexIndex\nEdgeIndex\nFaceIndex\nFacetIndex\nGrid","category":"page"},{"location":"reference/grid/#Ferrite.generate_grid","page":"Grid & AbstractGrid","title":"Ferrite.generate_grid","text":"generate_grid(celltype::Cell, nel::NTuple, [left::Vec, right::Vec)\n\nReturn a Grid for a rectangle in 1, 2 or 3 dimensions. celltype defined the type of cells, e.g. Triangle or Hexahedron. nel is a tuple of the number of elements in each direction. left and right are optional endpoints of the domain. Defaults to -1 and 1 in all directions.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.Node","page":"Grid & AbstractGrid","title":"Ferrite.Node","text":"Node{dim, T}\n\nA Node is a point in space.\n\nFields\n\nx::Vec{dim,T}: stores the coordinates\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.CellIndex","page":"Grid & AbstractGrid","title":"Ferrite.CellIndex","text":"A CellIndex wraps an Int and corresponds to a cell with that number in the mesh\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.VertexIndex","page":"Grid & AbstractGrid","title":"Ferrite.VertexIndex","text":"A VertexIndex wraps an (Int, Int) and defines a local vertex by pointing to a (cell, vert).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.EdgeIndex","page":"Grid & AbstractGrid","title":"Ferrite.EdgeIndex","text":"A EdgeIndex wraps an (Int, Int) and defines a local edge by pointing to a (cell, edge).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.FaceIndex","page":"Grid & AbstractGrid","title":"Ferrite.FaceIndex","text":"A FaceIndex wraps an (Int, Int) and defines a local face by pointing to a (cell, face).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.FacetIndex","page":"Grid & AbstractGrid","title":"Ferrite.FacetIndex","text":"A FacetIndex wraps an (Int, Int) and defines a local facet by pointing to a (cell, facet).\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.Grid","page":"Grid & AbstractGrid","title":"Ferrite.Grid","text":"Grid{dim, C<:AbstractCell, T<:Real} <: AbstractGrid}\n\nA Grid is a collection of Ferrite.AbstractCells and Ferrite.Nodes which covers the computational domain. Helper structures for applying boundary conditions or define subdomains are gathered in cellsets, nodesets, facetsets, and vertexsets.\n\nFields\n\ncells::Vector{C}: stores all cells of the grid\nnodes::Vector{Node{dim,T}}: stores the dim dimensional nodes of the grid\ncellsets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of cell ids\nnodesets::Dict{String, OrderedSet{Int}}: maps a String key to an OrderedSet of global node ids\nfacetsets::Dict{String, OrderedSet{FacetIndex}}: maps a String to an OrderedSet of FacetIndex\nvertexsets::Dict{String, OrderedSet{VertexIndex}}: maps a String key to an OrderedSet of VertexIndex\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Utility-Functions","page":"Grid & AbstractGrid","title":"Utility Functions","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"getcells\ngetncells\ngetnodes\ngetnnodes\nFerrite.nnodes_per_cell\ngetcellset\ngetnodeset\ngetfacetset\ngetvertexset\ntransform_coordinates!\ngetcoordinates\ngetcoordinates!\ngeometric_interpolation(::Ferrite.AbstractCell)\nget_node_coordinate\nFerrite.getspatialdim(::Ferrite.AbstractGrid)\nFerrite.getrefdim(::Ferrite.AbstractCell)","category":"page"},{"location":"reference/grid/#Ferrite.getcells","page":"Grid & AbstractGrid","title":"Ferrite.getcells","text":"getcells(grid::AbstractGrid)\ngetcells(grid::AbstractGrid, v::Union{Int,Vector{Int}}\ngetcells(grid::AbstractGrid, setname::String)\n\nReturns either all cells::Collection{C<:AbstractCell} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. Whereas the last option tries to call a cellset of the grid. Collection can be any indexable type, for Grid it is Vector{C<:AbstractCell}.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getncells","page":"Grid & AbstractGrid","title":"Ferrite.getncells","text":"Returns the number of cells in the <:AbstractGrid.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getnodes","page":"Grid & AbstractGrid","title":"Ferrite.getnodes","text":"getnodes(grid::AbstractGrid)\ngetnodes(grid::AbstractGrid, v::Union{Int,Vector{Int}}\ngetnodes(grid::AbstractGrid, setname::String)\n\nReturns either all nodes::Collection{N} of a <:AbstractGrid or a subset based on an Int, Vector{Int} or String. The last option tries to call a nodeset of the <:AbstractGrid. Collection{N} refers to some indexable collection where each element corresponds to a Node.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getnnodes","page":"Grid & AbstractGrid","title":"Ferrite.getnnodes","text":"Returns the number of nodes in the grid.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.nnodes_per_cell","page":"Grid & AbstractGrid","title":"Ferrite.nnodes_per_cell","text":"Returns the number of nodes of the i-th cell.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getcellset","page":"Grid & AbstractGrid","title":"Ferrite.getcellset","text":"getcellset(grid::AbstractGrid, setname::String)\n\nReturns all cells as cellid in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getnodeset","page":"Grid & AbstractGrid","title":"Ferrite.getnodeset","text":"getnodeset(grid::AbstractGrid, setname::String)\n\nReturns all nodes as nodeid in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getfacetset","page":"Grid & AbstractGrid","title":"Ferrite.getfacetset","text":"getfacetset(grid::AbstractGrid, setname::String)\n\nReturns all faces as FacetIndex in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getvertexset","page":"Grid & AbstractGrid","title":"Ferrite.getvertexset","text":"getvertexset(grid::AbstractGrid, setname::String)\n\nReturns all vertices as VertexIndex in the set with name setname.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.transform_coordinates!","page":"Grid & AbstractGrid","title":"Ferrite.transform_coordinates!","text":"transform_coordinates!(grid::Abstractgrid, f::Function)\n\nTransform the coordinates of all nodes of the grid based on some transformation function f(x).\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getcoordinates","page":"Grid & AbstractGrid","title":"Ferrite.getcoordinates","text":"getcoordinates(grid::AbstractGrid, idx::Union{Int,CellIndex})\ngetcoordinates(cache::CellCache)\n\nGet a vector with the coordinates of the cell corresponding to idx or cache\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getcoordinates!","page":"Grid & AbstractGrid","title":"Ferrite.getcoordinates!","text":"getcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, idx::Union{Int,CellIndex})\ngetcoordinates!(x::Vector{<:Vec}, grid::AbstractGrid, cell::AbstractCell)\n\nMutate x to the coordinates of the cell corresponding to idx or cell.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.geometric_interpolation-Tuple{Ferrite.AbstractCell}","page":"Grid & AbstractGrid","title":"Ferrite.geometric_interpolation","text":"geometric_interpolation(::AbstractCell)::ScalarInterpolation\ngeometric_interpolation(::Type{<:AbstractCell})::ScalarInterpolation\n\nEach AbstractCell type has a unique geometric interpolation describing its geometry. This function returns that interpolation, which is always a scalar interpolation.\n\n\n\n\n\n","category":"method"},{"location":"reference/grid/#Ferrite.get_node_coordinate","page":"Grid & AbstractGrid","title":"Ferrite.get_node_coordinate","text":"get_node_coordinate(::Node)\n\nGet the value of the node coordinate.\n\n\n\n\n\nget_node_coordinate(grid::AbstractGrid, n::Int)\n\nReturn the coordinate of the nth node in grid\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getspatialdim-Tuple{Ferrite.AbstractGrid}","page":"Grid & AbstractGrid","title":"Ferrite.getspatialdim","text":"Ferrite.getspatialdim(grid::AbstractGrid)\n\nGet the spatial dimension of the grid, corresponding to the vector dimension of the grid's coordinates.\n\n\n\n\n\n","category":"method"},{"location":"reference/grid/#Ferrite.getrefdim-Tuple{Ferrite.AbstractCell}","page":"Grid & AbstractGrid","title":"Ferrite.getrefdim","text":"Ferrite.getrefdim(cell::AbstractCell)\nFerrite.getrefdim(::Type{<:AbstractCell})\n\nGet the reference dimension of the cell, i.e. the dimension of the cell's reference shape.\n\n\n\n\n\n","category":"method"},{"location":"reference/grid/#Topology","page":"Grid & AbstractGrid","title":"Topology","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"ExclusiveTopology\ngetneighborhood\nfacetskeleton\nvertex_star_stencils\ngetstencil","category":"page"},{"location":"reference/grid/#Ferrite.ExclusiveTopology","page":"Grid & AbstractGrid","title":"Ferrite.ExclusiveTopology","text":"ExclusiveTopology(grid::AbstractGrid)\n\nThe experimental feature ExclusiveTopology saves topological (connectivity/neighborhood) data of the grid. Only the highest dimensional neighborhood is saved. I.e., if something is connected by a face and an edge, only the face neighborhood is saved. The lower dimensional neighborhood is recomputed when calling getneighborhood if needed.\n\nFields\n\nvertex_to_cell::AbstractArray{AbstractVector{Int}, 1}: global vertex id to all cells containing the vertex\ncell_neighbor::AbstractArray{AbstractVector{Int}, 1}: cellid to all connected cells\nface_neighbor::AbstractArray{AbstractVector{FaceIndex}, 2}: face_neighbor[cellid, local_face_id] -> neighboring faces\nedge_neighbor::AbstractArray{AbstractVector{EdgeIndex}, 2}: edge_neighbor[cellid, local_edge_id] -> neighboring edges\nvertex_neighbor::AbstractArray{AbstractVector{VertexIndex}, 2}: vertex_neighbor[cellid, local_vertex_id] -> neighboring vertices\nface_skeleton::Union{Vector{FaceIndex}, Nothing}: List of unique faces in the grid given as FaceIndex\nedge_skeleton::Union{Vector{EdgeIndex}, Nothing}: List of unique edges in the grid given as EdgeIndex\nvertex_skeleton::Union{Vector{VertexIndex}, Nothing}: List of unique vertices in the grid given as VertexIndex\n\nwarning: Limitations\nThe implementation only works with conforming grids, i.e. grids without \"hanging nodes\". Non-conforming grids will give unexpected results. Grids with embedded cells (different reference dimension compared to the spatial dimension) are not supported, and will error on construction.\n\n\n\n\n\n","category":"type"},{"location":"reference/grid/#Ferrite.getneighborhood","page":"Grid & AbstractGrid","title":"Ferrite.getneighborhood","text":"getneighborhood(topology, grid::AbstractGrid, cellidx::CellIndex, include_self=false)\ngetneighborhood(topology, grid::AbstractGrid, faceidx::FaceIndex, include_self=false)\ngetneighborhood(topology, grid::AbstractGrid, vertexidx::VertexIndex, include_self=false)\ngetneighborhood(topology, grid::AbstractGrid, edgeidx::EdgeIndex, include_self=false)\n\nReturns all connected entities of the same type as defined by the respective topology. If include_self is true, the given entity is included in the returned list as well.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.facetskeleton","page":"Grid & AbstractGrid","title":"Ferrite.facetskeleton","text":"facetskeleton(top::ExclusiveTopology, grid::AbstractGrid)\n\nMaterializes the skeleton from the neighborhood information by returning an iterable over the unique facets in the grid, described by FacetIndex.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.vertex_star_stencils","page":"Grid & AbstractGrid","title":"Ferrite.vertex_star_stencils","text":"vertex_star_stencils(top::ExclusiveTopology, grid::Grid) -> AbstractVector{AbstractVector{VertexIndex}}\n\nComputes the stencils induced by the edge connectivity of the vertices.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.getstencil","page":"Grid & AbstractGrid","title":"Ferrite.getstencil","text":"getstencil(top::ArrayOfVectorViews{VertexIndex, 1}, grid::AbstractGrid, vertex_idx::VertexIndex) -> AbstractVector{VertexIndex}\n\nGet an iterateable over the stencil members for a given local entity.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Grid-Sets-Utility","page":"Grid & AbstractGrid","title":"Grid Sets Utility","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"addcellset!\naddfacetset!\naddboundaryfacetset!\naddvertexset!\naddboundaryvertexset!\naddnodeset!","category":"page"},{"location":"reference/grid/#Ferrite.addcellset!","page":"Grid & AbstractGrid","title":"Ferrite.addcellset!","text":"addcellset!(grid::AbstractGrid, name::String, cellid::AbstractVecOrSet{Int})\naddcellset!(grid::AbstractGrid, name::String, f::function; all::Bool=true)\n\nAdds a cellset to the grid with key name. Cellsets are typically used to define subdomains of the problem, e.g. two materials in the computational domain. The DofHandler can construct different fields which live not on the whole domain, but rather on a cellset. all=true implies that f(x) must return true for all nodal coordinates x in the cell if the cell should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\naddcellset!(grid, \"left\", Set((1,3))) #add cells with id 1 and 3 to cellset left\naddcellset!(grid, \"right\", x -> norm(x[1]) < 2.0 ) #add cell to cellset right, if x[1] of each cell's node is smaller than 2.0\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addfacetset!","page":"Grid & AbstractGrid","title":"Ferrite.addfacetset!","text":"addfacetset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FacetIndex})\naddfacetset!(grid::AbstractGrid, name::String, f::Function; all::Bool=true)\n\nAdds a facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\naddfacetset!(grid, \"right\", Set((FacetIndex(2,2), FacetIndex(4,2)))) #see grid manual example for reference\naddfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0) #see incompressible elasticity example for reference\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addboundaryfacetset!","page":"Grid & AbstractGrid","title":"Ferrite.addboundaryfacetset!","text":"addboundaryfacetset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)\n\nAdds a boundary facetset to the grid with key name. A facetset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_facet_id). Facetsets are used to initialize Dirichlet structs, that are needed to specify the boundary for the ConstraintHandler. all=true implies that f(x) must return true for all nodal coordinates x on the facet if the facet should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addvertexset!","page":"Grid & AbstractGrid","title":"Ferrite.addvertexset!","text":"addvertexset!(grid::AbstractGrid, name::String, faceid::AbstractVecOrSet{FaceIndex})\naddvertexset!(grid::AbstractGrid, name::String, f::Function)\n\nAdds a vertexset to the grid with key name. A vertexset maps a String key to a OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). Vertexsets can be used to initialize Dirichlet boundary conditions for the ConstraintHandler.\n\naddvertexset!(grid, \"right\", Set((VertexIndex(2,2), VertexIndex(4,2))))\naddvertexset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addboundaryvertexset!","page":"Grid & AbstractGrid","title":"Ferrite.addboundaryvertexset!","text":"addboundaryvertexset!(grid::AbstractGrid, topology::ExclusiveTopology, name::String, f::Function; all::Bool=true)\n\nAdds a boundary vertexset to the grid with key name. A vertexset maps a String key to an OrderedSet of tuples corresponding to (global_cell_id, local_vertex_id). all=true implies that f(x) must return true for all nodal coordinates x on the face if the face should be added to the set, otherwise it suffices that f(x) returns true for one node.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Ferrite.addnodeset!","page":"Grid & AbstractGrid","title":"Ferrite.addnodeset!","text":"addnodeset!(grid::AbstractGrid, name::String, nodeid::AbstractVecOrSet{Int})\naddnodeset!(grid::AbstractGrid, name::String, f::Function)\n\nAdds a nodeset::OrderedSet{Int} to the grid's nodesets with key name. Has the same interface as addcellset. However, instead of mapping a cell id to the String key, a set of node ids is returned.\n\n\n\n\n\n","category":"function"},{"location":"reference/grid/#Multithreaded-Assembly","page":"Grid & AbstractGrid","title":"Multithreaded Assembly","text":"","category":"section"},{"location":"reference/grid/","page":"Grid & AbstractGrid","title":"Grid & AbstractGrid","text":"create_coloring","category":"page"},{"location":"reference/grid/#Ferrite.create_coloring","page":"Grid & AbstractGrid","title":"Ferrite.create_coloring","text":"create_coloring(g::Grid, cellset=1:getncells(g); alg::ColoringAlgorithm)\n\nCreate a coloring of the cells in grid g such that no neighboring cells have the same color. If only a subset of cells should be colored, the cells to color can be specified by cellset.\n\nReturns a vector of vectors with cell indexes, e.g.:\n\nret = [\n [1, 3, 5, 10, ...], # cells for color 1\n [2, 4, 6, 12, ...], # cells for color 2\n]\n\nTwo different algorithms are available, specified with the alg keyword argument:\n\nalg = ColoringAlgorithm.WorkStream (default): Three step algorithm from Turcksin et al. [11], albeit with a greedy coloring in the second step. Generally results in more colors than ColoringAlgorithm.Greedy, however the cells are more equally distributed among the colors.\nalg = ColoringAlgorithm.Greedy: greedy algorithm that works well for structured quadrilateral grids such as e.g. quadrilateral grids from generate_grid.\n\nThe resulting colors can be visualized using Ferrite.write_cell_colors.\n\nnote: Cell to color mapping\nIn a previous version of Ferrite this function returned a dictionary mapping cell ID to color numbers as the first argument. If you need this mapping you can create it using the following construct:colors = create_coloring(...)\ncell_colormap = Dict{Int,Int}(\n cellid => color for (color, cellids) in enumerate(final_colors) for cellid in cellids\n)\n\nReferences\n\n[11] Turcksin et al. ACM Trans. Math. Softw. 43 (2016).\n\n\n\n\n\n","category":"function"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"EditURL = \"../literate-tutorials/porous_media.jl\"","category":"page"},{"location":"tutorials/porous_media/#Porous-media","page":"Porous media","title":"Porous media","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Porous media is a two-phase material, consisting of solid parts and a liquid occupying the pores inbetween. Using the porous media theory, we can model such a material without explicitly resolving the microstructure, but by considering the interactions between the solid and liquid. In this example, we will additionally consider larger linear elastic solid aggregates that are impermeable. Hence, there is no liquids in these particles and the only unknown variable is the displacement field :u. In the porous media, denoted the matrix, we have both the displacement field, :u, as well as the liquid pressure, :p, as unknown. The simulation result is shown below","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"(Image: Pressure evolution.)","category":"page"},{"location":"tutorials/porous_media/#Theory-of-porous-media","page":"Porous media","title":"Theory of porous media","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The strong forms are given as","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nboldsymbolsigma(boldsymbolepsilon p) cdot boldsymbolnabla = boldsymbol0 \ndotPhi(boldsymbolepsilon p) + boldsymbolw(p) cdot boldsymbolnabla = 0\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"where boldsymbolepsilon = leftboldsymboluotimesboldsymbolnablaright^mathrmsym The constitutive relationships are","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nboldsymbolsigma = boldsymbolmathsfCboldsymbolepsilon - alpha p boldsymbolI \nboldsymbolw = - k boldsymbolnabla p \nPhi = phi + alpha mathrmtr(boldsymbolepsilon) + beta p\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"with boldsymbolmathsfC=2G boldsymbolmathsfI^mathrmdev + 3K boldsymbolIotimesboldsymbolI. The material parameters are then the shear modulus, G, bulk modulus, K, permeability, k, Biot's coefficient, alpha, and liquid compressibility, beta. The porosity, phi, doesn't enter into the equations (A different porosity leads to different skeleton stiffness and permeability).","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The variational (weak) form can then be derived for the variations boldsymboldelta u and delta p as","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nint_Omega leftleftboldsymboldelta uotimesboldsymbolnablaright^mathrmsym\nboldsymbolmathsfCboldsymbolepsilon - boldsymboldelta u cdot boldsymbolnabla alpha pright mathrmdOmega\n= int_Gamma boldsymboldelta u cdot boldsymbolt mathrmd Gamma \nint_Omega leftdelta p leftalpha dotboldsymbolu cdot boldsymbolnabla + beta dotpright +\nboldsymbolnabla(delta p) cdot k boldsymbolnablaright mathrmdOmega\n= int_Gamma delta p w_mathrmn mathrmd Gamma\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"where boldsymbolt=boldsymbolncdotboldsymbolsigma is the traction and w_mathrmn = boldsymbolncdotboldsymbolw is the normal flux.","category":"page"},{"location":"tutorials/porous_media/#Finite-element-form","page":"Porous media","title":"Finite element form","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Discretizing in space using finite elements, we obtain the vector equation r_i = f_i^mathrmint - f_i^mathrmext where f^mathrmext are the external \"forces\", and f_i^mathrmint are the internal \"forces\". We split this into the displacement part r_i^mathrmu = f_i^mathrmintu - f_i^mathrmextu and pressure part r_i^mathrmp = f_i^mathrmintp - f_i^mathrmextp to obtain the discretized equation system","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nf_i^mathrmintu = int_Omega boldsymboldelta N^mathrmu_iotimesboldsymbolnabla^mathrmsym boldsymbolmathsfC boldsymboluotimesboldsymbolnabla^mathrmsym \n- boldsymboldelta N^mathrmu_i cdot boldsymbolnabla alpha p mathrmdOmega\n= int_Gamma boldsymboldelta N^mathrmu_i cdot boldsymbolt mathrmd Gamma \nf_i^mathrmintp = int_Omega delta N_i^mathrmp alpha dotboldsymbolucdotboldsymbolnabla + betadotp + boldsymbolnabla(delta N_i^mathrmp) cdot k boldsymbolnabla(p) mathrmdOmega\n= int_Gamma delta N_i^mathrmp w_mathrmn mathrmd Gamma\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Approximating the time-derivatives, dotboldsymboluapprox leftboldsymbolu-^nboldsymbolurightDelta t and dotpapprox leftp-^nprightDelta t, we can implement the finite element equations in the residual form r_i(boldsymbola(t) t) = 0 where the vector boldsymbola contains all unknown displacements u_i and pressures p_i.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The jacobian, K_ij = partial r_ipartial a_j, is then split into four parts,","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"beginaligned\nK_ij^mathrmuu = fracpartial r_i^mathrmupartial u_j = int_Omega boldsymboldelta N^mathrmu_iotimesboldsymbolnabla^mathrmsym boldsymbolmathsfC boldsymbolN_j^mathrmuotimesboldsymbolnabla^mathrmsym mathrmdOmega \nK_ij^mathrmup = fracpartial r_i^mathrmupartial p_j = - int_Omega boldsymboldelta N^mathrmu_i cdot boldsymbolnabla alpha N_j^mathrmp mathrmdOmega \nK_ij^mathrmpu = fracpartial r_i^mathrmppartial u_j = int_Omega delta N_i^mathrmp fracalphaDelta t boldsymbolN_j^mathrmu cdotboldsymbolnabla mathrmdOmega\nK_ij^mathrmpp = fracpartial r_i^mathrmppartial p_j = int_Omega delta N_i^mathrmp fracN_j^mathrmpDelta t + boldsymbolnabla(delta N_i^mathrmp) cdot k boldsymbolnabla(N_j^mathrmp) mathrmdOmega\nendaligned","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"We could assemble one stiffness matrix and one mass matrix, which would be constant, but for simplicity we only consider a single system matrix that depends on the time step, and assemble this for each step. The equations are still linear, so no iterations are required.","category":"page"},{"location":"tutorials/porous_media/#Implementation","page":"Porous media","title":"Implementation","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"We now solve the problem step by step. The full program with fewer comments is found in the final section","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Required packages","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"using Ferrite, FerriteMeshParser, Tensors, WriteVTK","category":"page"},{"location":"tutorials/porous_media/#Elasticity","page":"Porous media","title":"Elasticity","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"We start by defining the elastic material type, containing the elastic stiffness, for the linear elastic impermeable solid aggregates.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"struct Elastic{T}\n C::SymmetricTensor{4, 2, T, 9}\nend\nfunction Elastic(; E = 20.0e3, ν = 0.3)\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n I2 = one(SymmetricTensor{2, 2})\n I4vol = I2 ⊗ I2\n I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3\n return Elastic(2G * I4dev + K * I4vol)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Next, we define the element routine for the solid aggregates, where we dispatch on the Elastic material struct. Note that the unused inputs here are used for the porous matrix below.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function element_routine!(Ke, re, material::Elastic, cv, cell, a, args...)\n reinit!(cv, cell)\n n_basefuncs = getnbasefunctions(cv)\n\n for q_point in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, q_point)\n ϵ = function_symmetric_gradient(cv, q_point, a)\n σ = material.C ⊡ ϵ\n for i in 1:n_basefuncs\n δ∇N = shape_symmetric_gradient(cv, q_point, i)\n re[i] += (δ∇N ⊡ σ) * dΩ\n for j in 1:n_basefuncs\n ∇N = shape_symmetric_gradient(cv, q_point, j)\n Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#PoroElasticity","page":"Porous media","title":"PoroElasticity","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"To define the poroelastic material, we re-use the elastic part from above for the skeleton, and add the additional required material parameters.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"struct PoroElastic{T}\n elastic::Elastic{T} ## Skeleton stiffness\n k::T ## Permeability of liquid [mm^4/(Ns)]\n ϕ::T ## Porosity [-]\n α::T ## Biot's coefficient [-]\n β::T ## Liquid compressibility [1/MPa]\nend\nPoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β);\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"The element routine requires a few more inputs since we have two fields, as well as the dependence on the rates of the displacements and pressure. Again, we dispatch on the material type.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh)\n # Setup cellvalues and give easier names\n reinit!.(cvs, (cell,))\n cv_u, cv_p = cvs\n dr_u = dof_range(sdh, :u)\n dr_p = dof_range(sdh, :p)\n\n C = m.elastic.C ## Elastic stiffness\n\n # Assemble stiffness and force vectors\n for q_point in 1:getnquadpoints(cv_u)\n dΩ = getdetJdV(cv_u, q_point)\n p = function_value(cv_p, q_point, a, dr_p)\n p_old = function_value(cv_p, q_point, a_old, dr_p)\n pdot = (p - p_old) / Δt\n ∇p = function_gradient(cv_p, q_point, a, dr_p)\n ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u)\n tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u)\n tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt\n σ_eff = C ⊡ ϵ\n # Variation of u_i\n for (iᵤ, Iᵤ) in pairs(dr_u)\n ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ)\n div_δNu = shape_divergence(cv_u, q_point, iᵤ)\n re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ)\n Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ\n end\n end\n # Variation of p_i\n for (iₚ, Iₚ) in pairs(dr_p)\n δNp = shape_value(cv_p, q_point, iₚ)\n ∇δNp = shape_gradient(cv_p, q_point, iₚ)\n re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n div_Nu = shape_divergence(cv_u, q_point, jᵤ)\n Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n ∇Np = shape_gradient(cv_p, q_point, jₚ)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Assembly","page":"Porous media","title":"Assembly","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"To organize the different domains, we'll first define a container type","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"struct FEDomain{M, CV, SDH <: SubDofHandler}\n material::M\n cellvalues::CV\n sdh::SDH\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"And then we can loop over a vector of such domains, allowing us to loop over each domain, to assemble the contributions from each cell in that domain (given by the SubDofHandler's cellset)","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt)\n assembler = start_assemble(K, r)\n for domain in domains\n doassemble!(assembler, domain, a, a_old, Δt)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"For one domain (corresponding to a specific SubDofHandler), we can then loop over all cells in its cellset. Doing this in a separate function (instead of a nested loop), ensures that the calls to the element_routine are type stable, which can be important for good performance.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function doassemble!(assembler, domain::FEDomain, a, a_old, Δt)\n material = domain.material\n cv = domain.cellvalues\n sdh = domain.sdh\n n = ndofs_per_cell(sdh)\n Ke = zeros(n, n)\n re = zeros(n)\n ae_old = zeros(n)\n ae = zeros(n)\n for cell in CellIterator(sdh)\n # copy values from a to ae\n map!(i -> a[i], ae, celldofs(cell))\n map!(i -> a_old[i], ae_old, celldofs(cell))\n fill!(Ke, 0)\n fill!(re, 0)\n element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh)\n assemble!(assembler, celldofs(cell), Ke, re)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Mesh-import","page":"Porous media","title":"Mesh import","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"In this example, we import the mesh from the Abaqus input file, porous_media_0p25.inp using FerriteMeshParser's get_ferrite_grid function. We then create one cellset for each phase (solid and porous) for each element type. These 4 sets will later be used in their own SubDofHandler","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function get_grid()\n # Import grid from abaqus mesh\n grid = get_ferrite_grid(joinpath(@__DIR__, \"porous_media_0p25.inp\"))\n\n # Create cellsets for each fieldhandler\n addcellset!(grid, \"solid3\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"solid4\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS4R\")))\n addcellset!(grid, \"porous3\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"porous4\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS4R\")))\n return grid\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Problem-setup","page":"Porous media","title":"Problem setup","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Define the finite element interpolation, integration, and boundary conditions.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function setup_problem(; t_rise = 0.1, u_max = -0.1)\n\n grid = get_grid()\n\n # Define materials\n m_solid = Elastic(; E = 20.0e3, ν = 0.3)\n m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8)\n\n # Define interpolations\n ipu_quad = Lagrange{RefQuadrilateral, 2}()^2\n ipu_tri = Lagrange{RefTriangle, 2}()^2\n ipp_quad = Lagrange{RefQuadrilateral, 1}()\n ipp_tri = Lagrange{RefTriangle, 1}()\n\n # Quadrature rules\n qr_quad = QuadratureRule{RefQuadrilateral}(2)\n qr_tri = QuadratureRule{RefTriangle}(2)\n\n # CellValues\n cvu_quad = CellValues(qr_quad, ipu_quad)\n cvu_tri = CellValues(qr_tri, ipu_tri)\n cvp_quad = CellValues(qr_quad, ipp_quad)\n cvp_tri = CellValues(qr_tri, ipp_tri)\n\n # Setup the DofHandler\n dh = DofHandler(grid)\n # Solid quads\n sdh_solid_quad = SubDofHandler(dh, getcellset(grid, \"solid4\"))\n add!(sdh_solid_quad, :u, ipu_quad)\n # Solid triangles\n sdh_solid_tri = SubDofHandler(dh, getcellset(grid, \"solid3\"))\n add!(sdh_solid_tri, :u, ipu_tri)\n # Porous quads\n sdh_porous_quad = SubDofHandler(dh, getcellset(grid, \"porous4\"))\n add!(sdh_porous_quad, :u, ipu_quad)\n add!(sdh_porous_quad, :p, ipp_quad)\n # Porous triangles\n sdh_porous_tri = SubDofHandler(dh, getcellset(grid, \"porous3\"))\n add!(sdh_porous_tri, :u, ipu_tri)\n add!(sdh_porous_tri, :p, ipp_tri)\n\n close!(dh)\n\n # Setup the domains\n domains = [\n FEDomain(m_solid, cvu_quad, sdh_solid_quad),\n FEDomain(m_solid, cvu_tri, sdh_solid_tri),\n FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad),\n FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri),\n ]\n\n # Boundary conditions\n # Sliding for u, except top which is compressed\n # Sealed for p, except top with prescribed zero pressure\n addfacetset!(dh.grid, \"sides\", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0)\n addfacetset!(dh.grid, \"top\", x -> x[2] ≈ 10.0)\n ch = ConstraintHandler(dh)\n add!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"sides\"), (x, t) -> zero(Vec{1}), [1]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"top\"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2]))\n add!(ch, Dirichlet(:p, getfacetset(grid, \"top_p\"), (x, t) -> 0.0))\n close!(ch)\n\n return dh, ch, domains\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#Solving","page":"Porous media","title":"Solving","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Given the DofHandler, ConstraintHandler, and CellValues, we can solve the problem by stepping through the time history","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0)\n K = allocate_matrix(dh)\n r = zeros(ndofs(dh))\n a = zeros(ndofs(dh))\n a_old = copy(a)\n pvd = paraview_collection(\"porous_media\")\n step = 0\n for t in 0:Δt:t_total\n if t > 0\n update!(ch, t)\n apply!(a, ch)\n doassemble!(K, r, domains, a, a_old, Δt)\n apply_zero!(K, r, ch)\n Δa = -K \\ r\n apply_zero!(Δa, ch)\n a .+= Δa\n copyto!(a_old, a)\n end\n step += 1\n VTKGridFile(\"porous_media_$step\", dh) do vtk\n write_solution(vtk, dh, a)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Finally we call the functions to actually run the code","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"dh, ch, domains = setup_problem()\nsolve(dh, ch, domains);\nnothing #hide","category":"page"},{"location":"tutorials/porous_media/#porous-media-plain-program","page":"Porous media","title":"Plain program","text":"","category":"section"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"Here follows a version of the program without any comments. The file is also available here: porous_media.jl.","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"using Ferrite, FerriteMeshParser, Tensors, WriteVTK\n\nstruct Elastic{T}\n C::SymmetricTensor{4, 2, T, 9}\nend\nfunction Elastic(; E = 20.0e3, ν = 0.3)\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n I2 = one(SymmetricTensor{2, 2})\n I4vol = I2 ⊗ I2\n I4dev = minorsymmetric(otimesu(I2, I2)) - I4vol / 3\n return Elastic(2G * I4dev + K * I4vol)\nend;\n\nfunction element_routine!(Ke, re, material::Elastic, cv, cell, a, args...)\n reinit!(cv, cell)\n n_basefuncs = getnbasefunctions(cv)\n\n for q_point in 1:getnquadpoints(cv)\n dΩ = getdetJdV(cv, q_point)\n ϵ = function_symmetric_gradient(cv, q_point, a)\n σ = material.C ⊡ ϵ\n for i in 1:n_basefuncs\n δ∇N = shape_symmetric_gradient(cv, q_point, i)\n re[i] += (δ∇N ⊡ σ) * dΩ\n for j in 1:n_basefuncs\n ∇N = shape_symmetric_gradient(cv, q_point, j)\n Ke[i, j] += (δ∇N ⊡ material.C ⊡ ∇N) * dΩ\n end\n end\n end\n return\nend;\n\nstruct PoroElastic{T}\n elastic::Elastic{T} ## Skeleton stiffness\n k::T ## Permeability of liquid [mm^4/(Ns)]\n ϕ::T ## Porosity [-]\n α::T ## Biot's coefficient [-]\n β::T ## Liquid compressibility [1/MPa]\nend\nPoroElastic(; elastic, k, ϕ, α, β) = PoroElastic(elastic, k, ϕ, α, β);\n\nfunction element_routine!(Ke, re, m::PoroElastic, cvs::Tuple, cell, a, a_old, Δt, sdh)\n # Setup cellvalues and give easier names\n reinit!.(cvs, (cell,))\n cv_u, cv_p = cvs\n dr_u = dof_range(sdh, :u)\n dr_p = dof_range(sdh, :p)\n\n C = m.elastic.C ## Elastic stiffness\n\n # Assemble stiffness and force vectors\n for q_point in 1:getnquadpoints(cv_u)\n dΩ = getdetJdV(cv_u, q_point)\n p = function_value(cv_p, q_point, a, dr_p)\n p_old = function_value(cv_p, q_point, a_old, dr_p)\n pdot = (p - p_old) / Δt\n ∇p = function_gradient(cv_p, q_point, a, dr_p)\n ϵ = function_symmetric_gradient(cv_u, q_point, a, dr_u)\n tr_ϵ_old = function_divergence(cv_u, q_point, a_old, dr_u)\n tr_ϵ_dot = (tr(ϵ) - tr_ϵ_old) / Δt\n σ_eff = C ⊡ ϵ\n # Variation of u_i\n for (iᵤ, Iᵤ) in pairs(dr_u)\n ∇δNu = shape_symmetric_gradient(cv_u, q_point, iᵤ)\n div_δNu = shape_divergence(cv_u, q_point, iᵤ)\n re[Iᵤ] += (∇δNu ⊡ σ_eff - div_δNu * p * m.α) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n ∇Nu = shape_symmetric_gradient(cv_u, q_point, jᵤ)\n Ke[Iᵤ, Jᵤ] += (∇δNu ⊡ C ⊡ ∇Nu) * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iᵤ, Jₚ] -= (div_δNu * m.α * Np) * dΩ\n end\n end\n # Variation of p_i\n for (iₚ, Iₚ) in pairs(dr_p)\n δNp = shape_value(cv_p, q_point, iₚ)\n ∇δNp = shape_gradient(cv_p, q_point, iₚ)\n re[Iₚ] += (δNp * (m.α * tr_ϵ_dot + m.β * pdot) + m.k * (∇δNp ⋅ ∇p)) * dΩ\n for (jᵤ, Jᵤ) in pairs(dr_u)\n div_Nu = shape_divergence(cv_u, q_point, jᵤ)\n Ke[Iₚ, Jᵤ] += δNp * (m.α / Δt) * div_Nu * dΩ\n end\n for (jₚ, Jₚ) in pairs(dr_p)\n ∇Np = shape_gradient(cv_p, q_point, jₚ)\n Np = shape_value(cv_p, q_point, jₚ)\n Ke[Iₚ, Jₚ] += (δNp * m.β * Np / Δt + m.k * (∇δNp ⋅ ∇Np)) * dΩ\n end\n end\n end\n return\nend;\n\nstruct FEDomain{M, CV, SDH <: SubDofHandler}\n material::M\n cellvalues::CV\n sdh::SDH\nend;\n\nfunction doassemble!(K, r, domains::Vector{<:FEDomain}, a, a_old, Δt)\n assembler = start_assemble(K, r)\n for domain in domains\n doassemble!(assembler, domain, a, a_old, Δt)\n end\n return\nend;\n\nfunction doassemble!(assembler, domain::FEDomain, a, a_old, Δt)\n material = domain.material\n cv = domain.cellvalues\n sdh = domain.sdh\n n = ndofs_per_cell(sdh)\n Ke = zeros(n, n)\n re = zeros(n)\n ae_old = zeros(n)\n ae = zeros(n)\n for cell in CellIterator(sdh)\n # copy values from a to ae\n map!(i -> a[i], ae, celldofs(cell))\n map!(i -> a_old[i], ae_old, celldofs(cell))\n fill!(Ke, 0)\n fill!(re, 0)\n element_routine!(Ke, re, material, cv, cell, ae, ae_old, Δt, sdh)\n assemble!(assembler, celldofs(cell), Ke, re)\n end\n return\nend;\n\nfunction get_grid()\n # Import grid from abaqus mesh\n grid = get_ferrite_grid(joinpath(@__DIR__, \"porous_media_0p25.inp\"))\n\n # Create cellsets for each fieldhandler\n addcellset!(grid, \"solid3\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"solid4\", intersect(getcellset(grid, \"solid\"), getcellset(grid, \"CPS4R\")))\n addcellset!(grid, \"porous3\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS3\")))\n addcellset!(grid, \"porous4\", intersect(getcellset(grid, \"porous\"), getcellset(grid, \"CPS4R\")))\n return grid\nend;\n\nfunction setup_problem(; t_rise = 0.1, u_max = -0.1)\n\n grid = get_grid()\n\n # Define materials\n m_solid = Elastic(; E = 20.0e3, ν = 0.3)\n m_porous = PoroElastic(; elastic = Elastic(; E = 10.0e3, ν = 0.3), β = 1 / 15.0e3, α = 0.9, k = 5.0e-3, ϕ = 0.8)\n\n # Define interpolations\n ipu_quad = Lagrange{RefQuadrilateral, 2}()^2\n ipu_tri = Lagrange{RefTriangle, 2}()^2\n ipp_quad = Lagrange{RefQuadrilateral, 1}()\n ipp_tri = Lagrange{RefTriangle, 1}()\n\n # Quadrature rules\n qr_quad = QuadratureRule{RefQuadrilateral}(2)\n qr_tri = QuadratureRule{RefTriangle}(2)\n\n # CellValues\n cvu_quad = CellValues(qr_quad, ipu_quad)\n cvu_tri = CellValues(qr_tri, ipu_tri)\n cvp_quad = CellValues(qr_quad, ipp_quad)\n cvp_tri = CellValues(qr_tri, ipp_tri)\n\n # Setup the DofHandler\n dh = DofHandler(grid)\n # Solid quads\n sdh_solid_quad = SubDofHandler(dh, getcellset(grid, \"solid4\"))\n add!(sdh_solid_quad, :u, ipu_quad)\n # Solid triangles\n sdh_solid_tri = SubDofHandler(dh, getcellset(grid, \"solid3\"))\n add!(sdh_solid_tri, :u, ipu_tri)\n # Porous quads\n sdh_porous_quad = SubDofHandler(dh, getcellset(grid, \"porous4\"))\n add!(sdh_porous_quad, :u, ipu_quad)\n add!(sdh_porous_quad, :p, ipp_quad)\n # Porous triangles\n sdh_porous_tri = SubDofHandler(dh, getcellset(grid, \"porous3\"))\n add!(sdh_porous_tri, :u, ipu_tri)\n add!(sdh_porous_tri, :p, ipp_tri)\n\n close!(dh)\n\n # Setup the domains\n domains = [\n FEDomain(m_solid, cvu_quad, sdh_solid_quad),\n FEDomain(m_solid, cvu_tri, sdh_solid_tri),\n FEDomain(m_porous, (cvu_quad, cvp_quad), sdh_porous_quad),\n FEDomain(m_porous, (cvu_tri, cvp_tri), sdh_porous_tri),\n ]\n\n # Boundary conditions\n # Sliding for u, except top which is compressed\n # Sealed for p, except top with prescribed zero pressure\n addfacetset!(dh.grid, \"sides\", x -> x[1] < 1.0e-6 || x[1] ≈ 5.0)\n addfacetset!(dh.grid, \"top\", x -> x[2] ≈ 10.0)\n ch = ConstraintHandler(dh)\n add!(ch, Dirichlet(:u, getfacetset(grid, \"bottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"sides\"), (x, t) -> zero(Vec{1}), [1]))\n add!(ch, Dirichlet(:u, getfacetset(grid, \"top\"), (x, t) -> u_max * clamp(t / t_rise, 0, 1), [2]))\n add!(ch, Dirichlet(:p, getfacetset(grid, \"top_p\"), (x, t) -> 0.0))\n close!(ch)\n\n return dh, ch, domains\nend;\n\nfunction solve(dh, ch, domains; Δt = 0.025, t_total = 1.0)\n K = allocate_matrix(dh)\n r = zeros(ndofs(dh))\n a = zeros(ndofs(dh))\n a_old = copy(a)\n pvd = paraview_collection(\"porous_media\")\n step = 0\n for t in 0:Δt:t_total\n if t > 0\n update!(ch, t)\n apply!(a, ch)\n doassemble!(K, r, domains, a, a_old, Δt)\n apply_zero!(K, r, ch)\n Δa = -K \\ r\n apply_zero!(Δa, ch)\n a .+= Δa\n copyto!(a_old, a)\n end\n step += 1\n VTKGridFile(\"porous_media_$step\", dh) do vtk\n write_solution(vtk, dh, a)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n return\nend;\n\ndh, ch, domains = setup_problem()\nsolve(dh, ch, domains);","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"","category":"page"},{"location":"tutorials/porous_media/","page":"Porous media","title":"Porous media","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"EditURL = \"../literate-tutorials/computational_homogenization.jl\"","category":"page"},{"location":"tutorials/computational_homogenization/#tutorial-computational-homogenization","page":"Computational homogenization","title":"Computational homogenization","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"(Image: )","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Figure 1: von Mises stress in an RVE with 5 stiff inclusions embedded in a softer matrix material that is loaded in shear. The problem is solved by using homogeneous Dirichlet boundary conditions (left) and (strong) periodic boundary conditions (right).","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"tip: Tip\nThis example is also available as a Jupyter notebook: computational_homogenization.ipynb.","category":"page"},{"location":"tutorials/computational_homogenization/#Introduction","page":"Computational homogenization","title":"Introduction","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"In this example we will solve the Representative Volume Element (RVE) problem for computational homogenization of linear elasticity and compute the effective/homogenized stiffness of an RVE with 5 stiff circular inclusions embedded in a softer matrix material (see Figure 1).","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"It is possible to obtain upper and lower bounds on the stiffness analytically, see for example Rule of mixtures. An upper bound is obtained from the Voigt model, where the strain is assumed to be the same in the two constituents,","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_mathrmVoigt = v_mathrmm mathsfE_mathrmm +\n(1 - v_mathrmm) mathsfE_mathrmi","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where v_mathrmm is the volume fraction of the matrix material, and where mathsfE_mathrmm and mathsfE_mathrmi are the individual stiffness for the matrix material and the inclusions, respectively. The lower bound is obtained from the Reuss model, where the stress is assumed to be the same in the two constituents,","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_mathrmReuss = left(v_mathrmm mathsfE_mathrmm^-1 +\n(1 - v_mathrmm) mathsfE_mathrmi^-1 right)^-1","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"However, neither of these assumptions are, in general, very close to the \"truth\" which is why it is of interest to computationally find the homogenized properties for a given RVE.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The canonical version of the RVE problem can be formulated as follows: For given homogenized field barboldsymbolu, barboldsymbolvarepsilon = boldsymbolvarepsilonbarboldsymbolu, find boldsymbolu in mathbbU_Box, boldsymbolt in mathbbT_Box such that","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"frac1Omega_Box int_Omega_Boxboldsymbolvarepsilondeltaboldsymbolu\n mathsfE boldsymbolvarepsilonboldsymbolu mathrmdOmega\n- frac1Omega_Box int_Gamma_Boxdelta boldsymbolu cdot\nboldsymbolt mathrmdGamma = 0 quad\nforall delta boldsymbolu in mathbbU_Boxquad (1mathrma)\n- frac1Omega_Box int_Gamma_Boxdelta boldsymbolt cdot\nboldsymbolu mathrmdGamma = - barboldsymbolvarepsilon \nleft frac1Omega_Box int_Gamma_Boxdelta boldsymbolt otimes\nboldsymbolx - barboldsymbolx mathrmdGamma right\nquad forall delta boldsymbolt in mathbbT_Box quad (1mathrmb)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where boldsymbolu = barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx + boldsymbolu^mu, where Omega_Box and Omega_Box are the domain and volume of the RVE, where Gamma_Box is the boundary, and where mathbbU_Box, mathbbT_Box are set of \"sufficiently regular\" functions defined on the RVE.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"This system is not solvable without introducing extra restrictions on mathbbU_Box, mathbbT_Box. In this example we will consider the common cases of Dirichlet boundary conditions and (strong) periodic boundary conditions.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Dirichlet boundary conditions","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can introduce the more restrictive sets of mathbbU_Box:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"beginalign*\nmathbbU_Box^mathrmD = leftboldsymbolu in mathbbU_Box boldsymbolu\n= barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx\n mathrmon Gamma_Boxright\nmathbbU_Box^mathrmD0 = leftboldsymbolu in mathbbU_Box boldsymbolu\n= boldsymbol0 mathrmon Gamma_Boxright\nendalign*","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"and use these as trial and test sets to obtain a solvable RVE problem pertaining to Dirichlet boundary conditions. Eq. (1mathrmb) is trivially fulfilled, the second term of Eq. (1mathrma) vanishes, and we are left with the following problem: Find boldsymbolu in mathbbU_Box^mathrmD that solve","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"frac1Omega_Box int_Omega_Boxboldsymbolvarepsilondeltaboldsymbolu\n mathsfE boldsymbolvarepsilonboldsymbolu mathrmdOmega = 0\nquad forall delta boldsymbolu in mathbbU_Box^mathrmD0","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Note that, since boldsymbolu = barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx + boldsymbolu^mu, this problem is equivalent to solving for boldsymbolu^mu in mathbbU_Box^mathrmD0, which is what we will do in the implementation.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Periodic boundary conditions","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The RVE problem pertaining to periodic boundary conditions is obtained by restricting boldsymbolu^mu to be periodic, and boldsymbolt anti-periodic across the RVE. Similarly as for Dirichlet boundary conditions, Eq. (1mathrmb) is directly fulfilled, and the second term in Eq. (1mathrma) vanishes, with these restrictions, and we are left with the following problem: Find boldsymbolu^mu in mathbbU_Box^mathrmP0 such that","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"frac1Omega_Box int_Omega_Boxboldsymbolvarepsilondeltaboldsymbolu\n mathsfE (barboldsymbolvarepsilon + boldsymbolvarepsilon\nboldsymbolu^mu) mathrmdOmega = 0\nquad forall delta boldsymbolu in mathbbU_Box^mathrmP0","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathbbU_Box^mathrmP0 = leftboldsymbolu in mathbbU_Box\n llbracket boldsymbolu rrbracket_Box = boldsymbol0\n mathrmon Gamma_Box^+right","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where llbracket bullet rrbracket_Box = bullet(boldsymbolx^+) - bullet(boldsymbolx^-) defines the \"jump\" over the RVE, i.e. the difference between the value on the image part Gamma_Box^+ (coordinate boldsymbolx^+) and the mirror part Gamma_Box^- (coordinate boldsymbolx^-) of the boundary. To make sure this restriction holds in a strong sense we need a periodic mesh.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Note that it would be possible to solve for the total boldsymbolu directly by instead enforcing the jump to be equal to the jump in the macroscopic part, boldsymbolu^mathrmM, i.e.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"llbracket boldsymbolu rrbracket_Box =\nllbracket boldsymbolu^mathrmM rrbracket_Box =\nllbracket barboldsymbolvarepsilon cdot boldsymbolx - barboldsymbolx\nrrbracket_Box =\nbarboldsymbolvarepsilon cdot boldsymbolx^+ - boldsymbolx^-","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Homogenization of effective properties","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"In general it is necessary to compute the homogenized stress and the stiffness on the fly, but since we in this example consider linear elasticity it is possible to compute the effective properties once and for all for a given RVE configuration. We do this by computing sensitivity fields for every independent strain component (6 in 3D, 3 in 2D). Thus, for a 2D problem, as in the implementation below, we compute sensitivities hatboldsymbolu_11, hatboldsymbolu_22, and hatboldsymbolu_12 = hatboldsymbolu_21 by using","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"barboldsymbolvarepsilon = beginpmatrix1 0 0 0endpmatrix quad\nbarboldsymbolvarepsilon = beginpmatrix0 0 0 1endpmatrix quad\nbarboldsymbolvarepsilon = beginpmatrix0 05 05 0endpmatrix","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"as the input to the RVE problem. When the sensitivies are solved we can compute the entries of the homogenized stiffness as follows","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_ijkl = fracpartial barsigma_ijpartial barvarepsilon_kl\n= barsigma_ij(hatboldsymbolu_kl)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where the homogenized stress, barboldsymbolsigma(boldsymbolu), is computed as the volume average of the stress in the RVE, i.e.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"barboldsymbolsigma(boldsymbolu) =\nfrac1Omega_Box int_Omega_Box boldsymbolsigma mathrmdOmega =\nfrac1Omega_Box int_Omega_Box\nmathsfE boldsymbolvarepsilonboldsymbolu mathrmdOmega","category":"page"},{"location":"tutorials/computational_homogenization/#Commented-program","page":"Computational homogenization","title":"Commented program","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Now we will see how this can be implemented in Ferrite. What follows is a program with comments in between which describe the different steps. You can also find the same program without comments at the end of the page, see Plain program.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"using Ferrite, SparseArrays, LinearAlgebra","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We first load the mesh file periodic-rve.msh (periodic-rve-coarse.msh for a coarser mesh). The mesh is generated with Gmsh, and we read it in as a Ferrite Grid using the FerriteGmsh.jl package:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"using FerriteGmsh\n\ngrid = togrid(\"periodic-rve.msh\")","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"grid = redirect_stdout(devnull) do #hide\n togrid(\"periodic-rve-coarse.msh\") #hide\nend #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Next we construct the interpolation and quadrature rule, and combining them into cellvalues as usual:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"dim = 2\nip = Lagrange{RefTriangle, 1}()^dim\nqr = QuadratureRule{RefTriangle}(2)\ncellvalues = CellValues(qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We define a dof handler with a displacement field :u:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Now we need to define boundary conditions. As discussed earlier we will solve the problem using (i) homogeneous Dirichlet boundary conditions, and (ii) periodic Dirichlet boundary conditions. We construct two different constraint handlers, one for each case. The Dirichlet boundary condition we have seen in many other examples. Here we simply define the condition that the field, :u, should have both components prescribed to 0 on the full boundary:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ch_dirichlet = ConstraintHandler(dh)\ndirichlet = Dirichlet(\n :u,\n union(getfacetset.(Ref(grid), [\"left\", \"right\", \"top\", \"bottom\"])...),\n (x, t) -> [0, 0],\n [1, 2]\n)\nadd!(ch_dirichlet, dirichlet)\nclose!(ch_dirichlet)\nupdate!(ch_dirichlet, 0.0)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"For periodic boundary conditions we use the PeriodicDirichlet constraint type, which is very similar to the Dirichlet type, but instead of a passing a facetset we pass a vector with \"facet pairs\", i.e. the mapping between mirror and image parts of the boundary. In this example the \"left\" and \"bottom\" boundaries are mirrors, and the \"right\" and \"top\" boundaries are the mirrors.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ch_periodic = ConstraintHandler(dh);\nperiodic = PeriodicDirichlet(\n :u,\n [\"left\" => \"right\", \"bottom\" => \"top\"],\n [1, 2]\n)\nadd!(ch_periodic, periodic)\nclose!(ch_periodic)\nupdate!(ch_periodic, 0.0)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"This will now constrain any degrees of freedom located on the mirror boundaries to the matching degree of freedom on the image boundaries. Internally this will create a number of AffineConstraints of the form u_i = 1 * u_j + 0:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"a = AffineConstraint(u_m, [u_i => 1], 0)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"where u_m is the degree of freedom on the mirror and u_i the matching one on the image part. PeriodicDirichlet is thus simply just a more convenient way of constructing such affine constraints since it computes the degree of freedom mapping automatically.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"To simplify things we group the constraint handlers into a named tuple","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ch = (dirichlet = ch_dirichlet, periodic = ch_periodic);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now construct the sparse matrix. Note that, since we are using affine constraints, which need to modify the matrix sparsity pattern in order to account for the constraint equations, we construct the matrix for the periodic case by passing both the dof handler and the constraint handler.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"K = (\n dirichlet = allocate_matrix(dh),\n periodic = allocate_matrix(dh, ch.periodic),\n);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We define the fourth order elasticity tensor for the matrix material, and define the inclusions to have 10 times higher stiffness","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"λ, μ = 1.0e10, 7.0e9 # Lamé parameters\nδ(i, j) = i == j ? 1.0 : 0.0\nEm = SymmetricTensor{4, 2}(\n (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n)\nEi = 10 * Em;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"As mentioned above, in order to compute the apparent/homogenized stiffness we will solve the problem repeatedly with different macroscale strain tensors to compute the sensitvity of the homogenized stress, barboldsymbolsigma, w.r.t. the macroscopic strain, barboldsymbolvarepsilon. The corresponding unit strains are defined below, and will result in three different right-hand-sides:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"εᴹ = [\n SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading\n SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading\n SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading\n];\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The assembly function is nothing strange, and in particular there is no impact from the choice of boundary conditions, so the same function can be used for both cases. Since we want to solve the system 3 times, once for each macroscopic strain component, we assemble 3 right-hand-sides.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"function doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n ndpc = ndofs_per_cell(dh)\n Ke = zeros(ndpc, ndpc)\n fe = zeros(ndpc, length(εᴹ))\n f = zeros(ndofs(dh), length(εᴹ))\n assembler = start_assemble(K)\n\n for cell in CellIterator(dh)\n\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n fill!(Ke, 0)\n fill!(fe, 0)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ\n end\n for (rhs, ε) in enumerate(εᴹ)\n σᴹ = E ⊡ ε\n fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ\n end\n end\n end\n\n cdofs = celldofs(cell)\n assemble!(assembler, cdofs, Ke)\n f[cdofs, :] .+= fe\n end\n return f\nend;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now assemble the system. The assembly function modifies the matrix in-place, but return the right hand side(s) which we collect in another named tuple.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"rhs = (\n dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),\n periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ),\n);\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The next step is to solve the systems. Since application of boundary conditions, using the apply! function, modifies both the matrix and the right hand sides we can not use it directly in this case since we want to reuse the matrix again for the next right hand sides. We could of course re-assemble the matrix for every right hand side, but that would not be very efficient. Instead we will use the get_rhs_data function, together with apply_rhs! in a later step. This will extract the necessary data from the matrix such that we can apply it for all the different right hand sides. Note that we call apply! with just the matrix and no right hand side.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"rhsdata = (\n dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),\n periodic = get_rhs_data(ch.periodic, K.periodic),\n)\n\napply!(K.dirichlet, ch.dirichlet)\napply!(K.periodic, ch.periodic)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now solve the problem(s). Note that we only use apply_rhs! in the loops below. The boundary conditions are already applied to the matrix above, so we only need to modify the right hand side.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"u = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nfor i in 1:size(rhs.dirichlet, 2)\n rhs_i = @view rhs.dirichlet[:, i] # Extract this RHS\n apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC\n u_i = cholesky(Symmetric(K.dirichlet)) \\ rhs_i # Solve\n apply!(u_i, ch.dirichlet) # Apply BC on the solution\n push!(u.dirichlet, u_i) # Save the solution vector\nend\n\nfor i in 1:size(rhs.periodic, 2)\n rhs_i = @view rhs.periodic[:, i] # Extract this RHS\n apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic) # Apply BC\n u_i = cholesky(Symmetric(K.periodic)) \\ rhs_i # Solve\n apply!(u_i, ch.periodic) # Apply BC on the solution\n push!(u.periodic, u_i) # Save the solution vector\nend","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"When the solution(s) are known we can compute the averaged stress, barboldsymbolsigma in the RVE. We define a function that does this, and also returns the von Mise stress in every quadrature point for visualization.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"function compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ)\n σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))\n σ̄Ω = zero(SymmetricTensor{2, 2})\n Ω = 0.0 # Total volume\n for cell in CellIterator(dh)\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])\n σ = E ⊡ (εᴹ + εμ)\n σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ))\n Ω += dΩ # Update total volume\n σ̄Ω += σ * dΩ # Update integrated stress\n end\n end\n σ̄ = σ̄Ω / Ω\n return σvM_qpdata, σ̄\nend;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We now compute the homogenized stress and von Mise stress for all cases","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"σ̄ = (\n dirichlet = SymmetricTensor{2, 2}[],\n periodic = SymmetricTensor{2, 2}[],\n)\nσ = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nprojector = L2Projector(ip, grid)\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.dirichlet, proj)\n push!(σ̄.dirichlet, σ̄_i)\nend\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.periodic, proj)\n push!(σ̄.periodic, σ̄_i)\nend","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"The remaining thing is to compute the homogenized stiffness. As mentioned in the introduction we can find all the components from the average stress of the sensitivity fields that we have solved for","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"mathsfE_ijkl = barsigma_ij(hatboldsymbolu_kl)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"So we have now already computed all the components, and just need to gather the data in a fourth order tensor:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l\n if k == l == 1\n σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11\n elseif k == l == 2\n σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22\n else\n σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21\n end\nend\n\nE_periodic = SymmetricTensor{4, 2}() do i, j, k, l\n if k == l == 1\n σ̄.periodic[1][i, j]\n elseif k == l == 2\n σ̄.periodic[2][i, j]\n else\n σ̄.periodic[3][i, j]\n end\nend","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can check that the result are what we expect, namely that the stiffness with Dirichlet boundary conditions is higher than when using periodic boundary conditions, and that the Reuss assumption is an lower bound, and the Voigt assumption a upper bound. We first compute the volume fraction of the matrix, and then the Voigt and Reuss bounds:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"function matrix_volume_fraction(grid, cellvalues)\n V = 0.0 # Total volume\n Vm = 0.0 # Volume of the matrix\n for c in CellIterator(grid)\n reinit!(cellvalues, c)\n is_matrix = !(cellid(c) in getcellset(grid, \"inclusions\"))\n for qp in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, qp)\n V += dΩ\n if is_matrix\n Vm += dΩ\n end\n end\n end\n return Vm / V\nend\n\nvm = matrix_volume_fraction(grid, cellvalues)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"E_voigt = vm * Em + (1 - vm) * Ei\nE_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei));\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"We can now compare the different computed stiffness tensors. We expect E_mathrmReuss leq E_mathrmPeriodicBC leq E_mathrmDirichletBC leq E_mathrmVoigt. A simple thing to compare are the eigenvalues of the tensors. Here we look at the first eigenvalue:","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"ev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))\nround.(ev; digits = -8)","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Finally, we export the solution and the stress field to a VTK file. For the export we also compute the macroscopic part of the displacement.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"uM = zeros(ndofs(dh))\n\nVTKGridFile(\"homogenization\", dh) do vtk\n for i in 1:3\n # Compute macroscopic solution\n apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x)\n # Dirichlet\n write_solution(vtk, dh, uM + u.dirichlet[i], \"_dirichlet_$i\")\n write_projection(vtk, projector, σ.dirichlet[i], \"σvM_dirichlet_$i\")\n # Periodic\n write_solution(vtk, dh, uM + u.periodic[i], \"_periodic_$i\")\n write_projection(vtk, projector, σ.periodic[i], \"σvM_periodic_$i\")\n end\nend;\nnothing #hide","category":"page"},{"location":"tutorials/computational_homogenization/#homogenization-plain-program","page":"Computational homogenization","title":"Plain program","text":"","category":"section"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"Here follows a version of the program without any comments. The file is also available here: computational_homogenization.jl.","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"using Ferrite, SparseArrays, LinearAlgebra\n\nusing FerriteGmsh\n\n# grid = togrid(\"periodic-rve-coarse.msh\")\ngrid = togrid(\"periodic-rve.msh\")\n\ndim = 2\nip = Lagrange{RefTriangle, 1}()^dim\nqr = QuadratureRule{RefTriangle}(2)\ncellvalues = CellValues(qr, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nch_dirichlet = ConstraintHandler(dh)\ndirichlet = Dirichlet(\n :u,\n union(getfacetset.(Ref(grid), [\"left\", \"right\", \"top\", \"bottom\"])...),\n (x, t) -> [0, 0],\n [1, 2]\n)\nadd!(ch_dirichlet, dirichlet)\nclose!(ch_dirichlet)\nupdate!(ch_dirichlet, 0.0)\n\nch_periodic = ConstraintHandler(dh);\nperiodic = PeriodicDirichlet(\n :u,\n [\"left\" => \"right\", \"bottom\" => \"top\"],\n [1, 2]\n)\nadd!(ch_periodic, periodic)\nclose!(ch_periodic)\nupdate!(ch_periodic, 0.0)\n\nch = (dirichlet = ch_dirichlet, periodic = ch_periodic);\n\nK = (\n dirichlet = allocate_matrix(dh),\n periodic = allocate_matrix(dh, ch.periodic),\n);\n\nλ, μ = 1.0e10, 7.0e9 # Lamé parameters\nδ(i, j) = i == j ? 1.0 : 0.0\nEm = SymmetricTensor{4, 2}(\n (i, j, k, l) -> λ * δ(i, j) * δ(k, l) + μ * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k))\n)\nEi = 10 * Em;\n\nεᴹ = [\n SymmetricTensor{2, 2}([1.0 0.0; 0.0 0.0]), # ε_11 loading\n SymmetricTensor{2, 2}([0.0 0.0; 0.0 1.0]), # ε_22 loading\n SymmetricTensor{2, 2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading\n];\n\nfunction doassemble!(cellvalues::CellValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n ndpc = ndofs_per_cell(dh)\n Ke = zeros(ndpc, ndpc)\n fe = zeros(ndpc, length(εᴹ))\n f = zeros(ndofs(dh), length(εᴹ))\n assembler = start_assemble(K)\n\n for cell in CellIterator(dh)\n\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n fill!(Ke, 0)\n fill!(fe, 0)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ\n end\n for (rhs, ε) in enumerate(εᴹ)\n σᴹ = E ⊡ ε\n fe[i, rhs] += (- δεi ⊡ σᴹ) * dΩ\n end\n end\n end\n\n cdofs = celldofs(cell)\n assemble!(assembler, cdofs, Ke)\n f[cdofs, :] .+= fe\n end\n return f\nend;\n\nrhs = (\n dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),\n periodic = doassemble!(cellvalues, K.periodic, dh, εᴹ),\n);\n\nrhsdata = (\n dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),\n periodic = get_rhs_data(ch.periodic, K.periodic),\n)\n\napply!(K.dirichlet, ch.dirichlet)\napply!(K.periodic, ch.periodic)\n\nu = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nfor i in 1:size(rhs.dirichlet, 2)\n rhs_i = @view rhs.dirichlet[:, i] # Extract this RHS\n apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC\n u_i = cholesky(Symmetric(K.dirichlet)) \\ rhs_i # Solve\n apply!(u_i, ch.dirichlet) # Apply BC on the solution\n push!(u.dirichlet, u_i) # Save the solution vector\nend\n\nfor i in 1:size(rhs.periodic, 2)\n rhs_i = @view rhs.periodic[:, i] # Extract this RHS\n apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic) # Apply BC\n u_i = cholesky(Symmetric(K.periodic)) \\ rhs_i # Solve\n apply!(u_i, ch.periodic) # Apply BC on the solution\n push!(u.periodic, u_i) # Save the solution vector\nend\n\nfunction compute_stress(cellvalues::CellValues, dh::DofHandler, u, εᴹ)\n σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))\n σ̄Ω = zero(SymmetricTensor{2, 2})\n Ω = 0.0 # Total volume\n for cell in CellIterator(dh)\n E = cellid(cell) in getcellset(dh.grid, \"inclusions\") ? Ei : Em\n reinit!(cellvalues, cell)\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])\n σ = E ⊡ (εᴹ + εμ)\n σvM_qpdata[q_point, cellid(cell)] = sqrt(3 / 2 * dev(σ) ⊡ dev(σ))\n Ω += dΩ # Update total volume\n σ̄Ω += σ * dΩ # Update integrated stress\n end\n end\n σ̄ = σ̄Ω / Ω\n return σvM_qpdata, σ̄\nend;\n\nσ̄ = (\n dirichlet = SymmetricTensor{2, 2}[],\n periodic = SymmetricTensor{2, 2}[],\n)\nσ = (\n dirichlet = Vector{Float64}[],\n periodic = Vector{Float64}[],\n)\n\nprojector = L2Projector(ip, grid)\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.dirichlet, proj)\n push!(σ̄.dirichlet, σ̄_i)\nend\n\nfor i in 1:3\n σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])\n proj = project(projector, σ_qp, qr)\n push!(σ.periodic, proj)\n push!(σ̄.periodic, σ̄_i)\nend\n\nE_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l\n if k == l == 1\n σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11\n elseif k == l == 2\n σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22\n else\n σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21\n end\nend\n\nE_periodic = SymmetricTensor{4, 2}() do i, j, k, l\n if k == l == 1\n σ̄.periodic[1][i, j]\n elseif k == l == 2\n σ̄.periodic[2][i, j]\n else\n σ̄.periodic[3][i, j]\n end\nend\n\nfunction matrix_volume_fraction(grid, cellvalues)\n V = 0.0 # Total volume\n Vm = 0.0 # Volume of the matrix\n for c in CellIterator(grid)\n reinit!(cellvalues, c)\n is_matrix = !(cellid(c) in getcellset(grid, \"inclusions\"))\n for qp in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, qp)\n V += dΩ\n if is_matrix\n Vm += dΩ\n end\n end\n end\n return Vm / V\nend\n\nvm = matrix_volume_fraction(grid, cellvalues)\n\nE_voigt = vm * Em + (1 - vm) * Ei\nE_reuss = inv(vm * inv(Em) + (1 - vm) * inv(Ei));\n\nev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))\nround.(ev; digits = -8)\n\nuM = zeros(ndofs(dh))\n\nVTKGridFile(\"homogenization\", dh) do vtk\n for i in 1:3\n # Compute macroscopic solution\n apply_analytical!(uM, dh, :u, x -> εᴹ[i] ⋅ x)\n # Dirichlet\n write_solution(vtk, dh, uM + u.dirichlet[i], \"_dirichlet_$i\")\n write_projection(vtk, projector, σ.dirichlet[i], \"σvM_dirichlet_$i\")\n # Periodic\n write_solution(vtk, dh, uM + u.periodic[i], \"_periodic_$i\")\n write_projection(vtk, projector, σ.periodic[i], \"σvM_periodic_$i\")\n end\nend;","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"","category":"page"},{"location":"tutorials/computational_homogenization/","page":"Computational homogenization","title":"Computational homogenization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"topics/reference_shapes/#Reference-shapes","page":"Reference shapes","title":"Reference shapes","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"The reference shapes in Ferrite are used to define grid cells, function interpolations (i.e. shape functions), and quadrature rules. Currently, the following reference shapes are defined","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"RefLine\nRefTriangle\nRefQuadrilateral\nRefTetrahedron\nRefHexahedron\nRefPrism\nRefPyramid","category":"page"},{"location":"topics/reference_shapes/#Entity-naming","page":"Reference shapes","title":"Entity naming","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Ferrite denotes the entities of a reference shape as follows","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Entity Description\nVertex 0-dimensional entity in the reference shape.\nEdge 1-dimensional entity connecting two vertices.\nFace 2-dimensional entity enclosed by edges.\nVolume 3-dimensional entity enclosed by faces.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Note that a node in Ferrite is not the same as a vertex. Vertices denote endpoints of edges, while nodes may also be located in the middle of edges (e.g. for a QuadraticLine cell).","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"To write dimensionally independent code, Ferrite also denotes entities by their codimension, defined relative the reference shape dimension. Specifically, Ferrite has the entities","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Entity Description\nCell 0-codimensional entity, i.e. the same as the reference shape.\nFacet 1-codimensional entity defining the boundary of cells.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Standard use cases mostly deal with these codimensional entities, such as CellValues and FacetValues.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"note: Definition of codimension\nIn Ferrite, codimension is defined relative to the reference dimension of the specific entity. Note that other finite element codes may define it differently (e.g. relative the highest reference dimension in the grid).","category":"page"},{"location":"topics/reference_shapes/#Entity-numbering","page":"Reference shapes","title":"Entity numbering","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"Each reference shape defines the numbering of its vertices, edges, and faces entities, where the edge and face entities are defined from their vertex numbers.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"note: Note\nThe numbering and identification of entities is (mostly) for internal use and typically not something users of Ferrite need to interact with.","category":"page"},{"location":"topics/reference_shapes/#Example","page":"Reference shapes","title":"Example","text":"","category":"section"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"The RefQuadrilateral is defined on the domain -1 1 times -1 1 in the local xi_1-xi_2 coordinate system.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"(Image: local element)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"The vertices of a RefQuadrilateral are then","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_vertices(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"and its edges are then defined as","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_edges(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"where the numbers refer to the vertex number. Finally, this reference shape is 2-dimensional, so it only has a single face, corresponding to the cell itself,","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_faces(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"also defined in terms of its vertices.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"As this is a 2-dimensional reference shape, the facets are the edges, i.e.","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"using Ferrite #hide\nFerrite.reference_facets(RefQuadrilateral)","category":"page"},{"location":"topics/reference_shapes/","page":"Reference shapes","title":"Reference shapes","text":"note: Not public API\nThe functions reference_vertices, reference_edges, reference_faces, and reference_facets are not public and only shown here to explain the numbering concept. The specific ordering may also change, and is therefore only documented in the Developer documentation.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"EditURL = \"../literate-tutorials/plasticity.jl\"","category":"page"},{"location":"tutorials/plasticity/#tutorial-plasticity","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"(Image: Shows the von Mises stress distribution in a cantilever beam.)","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Figure 1. A coarse mesh solution of a cantilever beam subjected to a load causing plastic deformations. The initial yield limit is 200 MPa but due to hardening it increases up to approximately 240 MPa.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: plasticity.ipynb.","category":"page"},{"location":"tutorials/plasticity/#Introduction","page":"Von Mises plasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This example illustrates the use of a nonlinear material model in Ferrite. The particular model is von Mises plasticity (also know as J₂-plasticity) with isotropic hardening. The model is fully 3D, meaning that no assumptions like plane stress or plane strain are introduced.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Also note that the theory of the model is not described here, instead one is referred to standard textbooks on material modeling.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"To illustrate the use of the plasticity model, we setup and solve a FE-problem consisting of a cantilever beam loaded at its free end. But first, we shortly describe the parts of the implementation dealing with the material modeling.","category":"page"},{"location":"tutorials/plasticity/#Material-modeling","page":"Von Mises plasticity","title":"Material modeling","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This section describes the structs and methods used to implement the material model","category":"page"},{"location":"tutorials/plasticity/#Material-parameters-and-state-variables","page":"Von Mises plasticity","title":"Material parameters and state variables","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Start by loading some necessary packages","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"We define a J₂-plasticity-material, containing material parameters and the elastic stiffness Dᵉ (since it is constant)","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"struct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}\n G::T # Shear modulus\n K::T # Bulk modulus\n σ₀::T # Initial yield limit\n H::T # Hardening modulus\n Dᵉ::S # Elastic stiffness tensor\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Next, we define a constructor for the material instance.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function J2Plasticity(E, ν, σ₀, H)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l))\n Dᵉ = SymmetricTensor{4, 3}(temp)\n return J2Plasticity(G, K, σ₀, H, Dᵉ)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"note: Note\nAbove, we defined a constructor J2Plasticity(E, ν, σ₀, H) in terms of the more common material parameters E and ν - simply as a convenience for the user.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Define a struct to store the material state for a Gauss point.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"struct MaterialState{T, S <: SecondOrderTensor{3, T}}\n # Store \"converged\" values\n ϵᵖ::S # plastic strain\n σ::S # stress\n k::T # hardening variable\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Constructor for initializing a material state. Every quantity is set to zero.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function MaterialState()\n return MaterialState(\n zero(SymmetricTensor{2, 3}),\n zero(SymmetricTensor{2, 3}),\n 0.0\n )\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"For later use, during the post-processing step, we define a function to compute the von Mises effective stress.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function vonMises(σ)\n s = dev(σ)\n return sqrt(3.0 / 2.0 * s ⊡ s)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/#Constitutive-driver","page":"Von Mises plasticity","title":"Constitutive driver","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This is the actual method which computes the stress and material tangent stiffness in a given integration point. Input is the current strain and the material state from the previous timestep.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState)\n # unpack some material parameters\n G = material.G\n H = material.H\n\n # We use (•)ᵗ to denote *trial*-values\n σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress\n sᵗ = dev(σᵗ) # deviatoric part of trial-stress\n J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ\n σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)\n σʸ = material.σ₀ + H * state.k # Previous yield limit\n\n φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface\n\n if φᵗ < 0.0 # elastic loading\n return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)\n else # plastic loading\n h = H + 3G\n μ = φᵗ / h # plastic multiplier\n\n c1 = 1 - 3G * μ / σᵗₑ\n s = c1 * sᵗ # updated deviatoric stress\n σ = s + vol(σᵗ) # updated stress\n\n # Compute algorithmic tangent stiffness ``D = \\frac{\\Delta \\sigma }{\\Delta \\epsilon}``\n κ = H * (state.k + μ) # drag stress\n σₑ = material.σ₀ + κ # updated yield surface\n\n δ(i, j) = i == j ? 1.0 : 0.0\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l]\n b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ)\n\n Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l]\n D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp)\n\n # Return new state\n Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain\n ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain\n k = state.k + μ # hardening variable\n return σ, D, MaterialState(ϵᵖ, σ, k)\n end\nend","category":"page"},{"location":"tutorials/plasticity/#FE-problem","page":"Von Mises plasticity","title":"FE-problem","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"What follows are methods for assembling and and solving the FE-problem.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function create_values(interpolation)\n # setup quadrature rules\n qr = QuadratureRule{RefTetrahedron}(2)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(3)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation)\n facetvalues_u = FacetValues(facet_qr, interpolation)\n\n return cellvalues_u, facetvalues_u\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/#Add-degrees-of-freedom","page":"Von Mises plasticity","title":"Add degrees of freedom","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function create_dofhandler(grid, interpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation) # add a displacement field with 3 components\n close!(dh)\n return dh\nend","category":"page"},{"location":"tutorials/plasticity/#Boundary-conditions","page":"Von Mises plasticity","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function create_bc(dh, grid)\n dbcs = ConstraintHandler(dh)\n # Clamped on the left side\n dofs = [1, 2, 3]\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> [0.0, 0.0, 0.0], dofs)\n add!(dbcs, dbc)\n close!(dbcs)\n return dbcs\nend;\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/#Assembling-of-element-contributions","page":"Von Mises plasticity","title":"Assembling of element contributions","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Residual vector r\nTangent stiffness K","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function doassemble!(\n K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler,\n material::J2Plasticity, u, states, states_old\n )\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n re = zeros(nu) # element residual vector\n ke = zeros(nu, nu) # element tangent matrix\n\n for (i, cell) in enumerate(CellIterator(dh))\n fill!(ke, 0)\n fill!(re, 0)\n eldofs = celldofs(cell)\n ue = u[eldofs]\n state = @view states[:, i]\n state_old = @view states_old[:, i]\n assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old)\n assemble!(assembler, eldofs, ke, re)\n end\n return K, r\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Compute element contribution to the residual and the tangent.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"note: Note\nDue to symmetry, we only compute the lower half of the tangent and then symmetrize it.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n # For each integration point, compute stress and material stiffness\n ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain\n σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point])\n\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δϵ = shape_symmetric_gradient(cellvalues, q_point, i)\n re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual\n for j in 1:i # loop only over lower half\n Δϵ = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ\n end\n end\n end\n symmetrize_lower!(Ke)\n return\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Helper function to symmetrize the material tangent","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend;\n\nfunction doassemble_neumann!(r, dh, facetset, facetvalues, t)\n n_basefuncs = getnbasefunctions(facetvalues)\n re = zeros(n_basefuncs) # element residual vector\n for fc in FacetIterator(dh, facetset)\n # Add traction as a negative contribution to the element residual `re`:\n reinit!(facetvalues, fc)\n fill!(re, 0)\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] -= (δu ⋅ t) * dΓ\n end\n end\n assemble!(r, celldofs(fc), re)\n end\n return r\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Define a function which solves the FE-problem.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"function solve()\n # Define material parameters\n E = 200.0e9 # [Pa]\n H = E / 20 # [Pa]\n ν = 0.3 # [-]\n σ₀ = 200.0e6 # [Pa]\n material = J2Plasticity(E, ν, σ₀, H)\n\n L = 10.0 # beam length [m]\n w = 1.0 # beam width [m]\n h = 1.0 # beam height[m]\n n_timesteps = 10\n u_max = zeros(n_timesteps)\n traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps)\n\n # Create geometry, dofs and boundary conditions\n n = 2\n nels = (10n, n, 2n) # number of elements in each spatial direction\n P1 = Vec((0.0, 0.0, 0.0)) # start point for geometry\n P2 = Vec((L, w, h)) # end point for geometry\n grid = generate_grid(Tetrahedron, nels, P1, P2)\n interpolation = Lagrange{RefTetrahedron, 1}()^3\n\n dh = create_dofhandler(grid, interpolation) # JuaFEM helper function\n dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions\n\n cellvalues, facetvalues = create_values(interpolation)\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n Δu = zeros(n_dofs) # displacement correction\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # tangent stiffness matrix\n\n # Create material states. One array for each cell, where each element is an array of material-\n # states - one for each integration point\n nqp = getnquadpoints(cellvalues)\n states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n\n # Newton-Raphson loop\n NEWTON_TOL = 1 # 1 N\n print(\"\\n Starting Netwon iterations:\\n\")\n\n for timestep in 1:n_timesteps\n t = timestep # actual time (used for evaluating d-bndc)\n traction = Vec((0.0, 0.0, traction_magnitude[timestep]))\n newton_itr = -1\n print(\"\\n Time step @time = $timestep:\\n\")\n update!(dbcs, t) # evaluates the D-bndc at time t\n apply!(u, dbcs) # set the prescribed values in the solution vector\n\n while true\n newton_itr += 1\n if newton_itr > 8\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n # Tangent and residual contribution from the cells (volume integral)\n doassemble!(K, r, cellvalues, dh, material, u, states, states_old)\n # Residual contribution from the Neumann boundary (surface integral)\n doassemble_neumann!(r, dh, getfacetset(grid, \"right\"), facetvalues, traction)\n norm_r = norm(r[Ferrite.free_dofs(dbcs)])\n\n print(\"Iteration: $newton_itr \\tresidual: $(@sprintf(\"%.8f\", norm_r))\\n\")\n if norm_r < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbcs)\n Δu = Symmetric(K) \\ r\n u -= Δu\n end\n\n # Update the old states with the converged values for next timestep\n states_old .= states\n\n u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep\n end\n\n # ## Postprocessing\n # Only a vtu-file corresponding to the last time-step is exported.\n #\n # The following is a quick (and dirty) way of extracting average cell data for export.\n mises_values = zeros(getncells(grid))\n κ_values = zeros(getncells(grid))\n for (el, cell_states) in enumerate(eachcol(states))\n for state in cell_states\n mises_values[el] += vonMises(state.σ)\n κ_values[el] += state.k * material.H\n end\n mises_values[el] /= length(cell_states) # average von Mises stress\n κ_values[el] /= length(cell_states) # average drag stress\n end\n VTKGridFile(\"plasticity\", dh) do vtk\n write_solution(vtk, dh, u) # displacement field\n write_cell_data(vtk, mises_values, \"von Mises [Pa]\")\n write_cell_data(vtk, κ_values, \"Drag stress [Pa]\")\n end\n\n return u_max, traction_magnitude\nend","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Solve the FE-problem and for each time-step extract maximum displacement and the corresponding traction load. Also compute the limit-traction-load","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"u_max, traction_magnitude = solve();\nnothing #hide","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Finally we plot the load-displacement curve.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"using Plots\nplot(\n vcat(0.0, u_max), # add the origin as a point\n vcat(0.0, traction_magnitude),\n linewidth = 2,\n title = \"Traction-displacement\",\n label = nothing,\n markershape = :auto\n)\nylabel!(\"Traction [Pa]\")\nxlabel!(\"Maximum deflection [m]\")","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Figure 2. Load-displacement-curve for the beam, showing a clear decrease in stiffness as more material starts to yield.","category":"page"},{"location":"tutorials/plasticity/#plasticity-plain-program","page":"Von Mises plasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"Here follows a version of the program without any comments. The file is also available here: plasticity.jl.","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf\n\nstruct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}\n G::T # Shear modulus\n K::T # Bulk modulus\n σ₀::T # Initial yield limit\n H::T # Hardening modulus\n Dᵉ::S # Elastic stiffness tensor\nend;\n\nfunction J2Plasticity(E, ν, σ₀, H)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n G = E / 2(1 + ν)\n K = E / 3(1 - 2ν)\n\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n temp(i, j, k, l) = 2.0G * (0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) + ν / (1.0 - 2.0ν) * δ(i, j) * δ(k, l))\n Dᵉ = SymmetricTensor{4, 3}(temp)\n return J2Plasticity(G, K, σ₀, H, Dᵉ)\nend;\n\nstruct MaterialState{T, S <: SecondOrderTensor{3, T}}\n # Store \"converged\" values\n ϵᵖ::S # plastic strain\n σ::S # stress\n k::T # hardening variable\nend\n\nfunction MaterialState()\n return MaterialState(\n zero(SymmetricTensor{2, 3}),\n zero(SymmetricTensor{2, 3}),\n 0.0\n )\nend\n\nfunction vonMises(σ)\n s = dev(σ)\n return sqrt(3.0 / 2.0 * s ⊡ s)\nend;\n\nfunction compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticity, state::MaterialState)\n # unpack some material parameters\n G = material.G\n H = material.H\n\n # We use (•)ᵗ to denote *trial*-values\n σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress\n sᵗ = dev(σᵗ) # deviatoric part of trial-stress\n J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ\n σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)\n σʸ = material.σ₀ + H * state.k # Previous yield limit\n\n φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface\n\n if φᵗ < 0.0 # elastic loading\n return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)\n else # plastic loading\n h = H + 3G\n μ = φᵗ / h # plastic multiplier\n\n c1 = 1 - 3G * μ / σᵗₑ\n s = c1 * sᵗ # updated deviatoric stress\n σ = s + vol(σᵗ) # updated stress\n\n # Compute algorithmic tangent stiffness ``D = \\frac{\\Delta \\sigma }{\\Delta \\epsilon}``\n κ = H * (state.k + μ) # drag stress\n σₑ = material.σ₀ + κ # updated yield surface\n\n δ(i, j) = i == j ? 1.0 : 0.0\n Isymdev(i, j, k, l) = 0.5 * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)) - 1.0 / 3.0 * δ(i, j) * δ(k, l)\n Q(i, j, k, l) = Isymdev(i, j, k, l) - 3.0 / (2.0 * σₑ^2) * s[i, j] * s[k, l]\n b = (3G * μ / σₑ) / (1.0 + 3G * μ / σₑ)\n\n Dtemp(i, j, k, l) = -2G * b * Q(i, j, k, l) - 9G^2 / (h * σₑ^2) * s[i, j] * s[k, l]\n D = material.Dᵉ + SymmetricTensor{4, 3}(Dtemp)\n\n # Return new state\n Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain\n ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain\n k = state.k + μ # hardening variable\n return σ, D, MaterialState(ϵᵖ, σ, k)\n end\nend\n\nfunction create_values(interpolation)\n # setup quadrature rules\n qr = QuadratureRule{RefTetrahedron}(2)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(3)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation)\n facetvalues_u = FacetValues(facet_qr, interpolation)\n\n return cellvalues_u, facetvalues_u\nend;\n\nfunction create_dofhandler(grid, interpolation)\n dh = DofHandler(grid)\n add!(dh, :u, interpolation) # add a displacement field with 3 components\n close!(dh)\n return dh\nend\n\nfunction create_bc(dh, grid)\n dbcs = ConstraintHandler(dh)\n # Clamped on the left side\n dofs = [1, 2, 3]\n dbc = Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> [0.0, 0.0, 0.0], dofs)\n add!(dbcs, dbc)\n close!(dbcs)\n return dbcs\nend;\n\nfunction doassemble!(\n K::SparseMatrixCSC, r::Vector, cellvalues::CellValues, dh::DofHandler,\n material::J2Plasticity, u, states, states_old\n )\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n re = zeros(nu) # element residual vector\n ke = zeros(nu, nu) # element tangent matrix\n\n for (i, cell) in enumerate(CellIterator(dh))\n fill!(ke, 0)\n fill!(re, 0)\n eldofs = celldofs(cell)\n ue = u[eldofs]\n state = @view states[:, i]\n state_old = @view states_old[:, i]\n assemble_cell!(ke, re, cell, cellvalues, material, ue, state, state_old)\n assemble!(assembler, eldofs, ke, re)\n end\n return K, r\nend\n\nfunction assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n # For each integration point, compute stress and material stiffness\n ϵ = function_symmetric_gradient(cellvalues, q_point, ue) # Total strain\n σ, D, state[q_point] = compute_stress_tangent(ϵ, material, state_old[q_point])\n\n dΩ = getdetJdV(cellvalues, q_point)\n for i in 1:n_basefuncs\n δϵ = shape_symmetric_gradient(cellvalues, q_point, i)\n re[i] += (δϵ ⊡ σ) * dΩ # add internal force to residual\n for j in 1:i # loop only over lower half\n Δϵ = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += δϵ ⊡ D ⊡ Δϵ * dΩ\n end\n end\n end\n symmetrize_lower!(Ke)\n return\nend\n\nfunction symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend;\n\nfunction doassemble_neumann!(r, dh, facetset, facetvalues, t)\n n_basefuncs = getnbasefunctions(facetvalues)\n re = zeros(n_basefuncs) # element residual vector\n for fc in FacetIterator(dh, facetset)\n # Add traction as a negative contribution to the element residual `re`:\n reinit!(facetvalues, fc)\n fill!(re, 0)\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] -= (δu ⋅ t) * dΓ\n end\n end\n assemble!(r, celldofs(fc), re)\n end\n return r\nend\n\nfunction solve()\n # Define material parameters\n E = 200.0e9 # [Pa]\n H = E / 20 # [Pa]\n ν = 0.3 # [-]\n σ₀ = 200.0e6 # [Pa]\n material = J2Plasticity(E, ν, σ₀, H)\n\n L = 10.0 # beam length [m]\n w = 1.0 # beam width [m]\n h = 1.0 # beam height[m]\n n_timesteps = 10\n u_max = zeros(n_timesteps)\n traction_magnitude = 1.0e7 * range(0.5, 1.0, length = n_timesteps)\n\n # Create geometry, dofs and boundary conditions\n n = 2\n nels = (10n, n, 2n) # number of elements in each spatial direction\n P1 = Vec((0.0, 0.0, 0.0)) # start point for geometry\n P2 = Vec((L, w, h)) # end point for geometry\n grid = generate_grid(Tetrahedron, nels, P1, P2)\n interpolation = Lagrange{RefTetrahedron, 1}()^3\n\n dh = create_dofhandler(grid, interpolation) # JuaFEM helper function\n dbcs = create_bc(dh, grid) # create Dirichlet boundary-conditions\n\n cellvalues, facetvalues = create_values(interpolation)\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n Δu = zeros(n_dofs) # displacement correction\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # tangent stiffness matrix\n\n # Create material states. One array for each cell, where each element is an array of material-\n # states - one for each integration point\n nqp = getnquadpoints(cellvalues)\n states = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n states_old = [MaterialState() for _ in 1:nqp, _ in 1:getncells(grid)]\n\n # Newton-Raphson loop\n NEWTON_TOL = 1 # 1 N\n print(\"\\n Starting Netwon iterations:\\n\")\n\n for timestep in 1:n_timesteps\n t = timestep # actual time (used for evaluating d-bndc)\n traction = Vec((0.0, 0.0, traction_magnitude[timestep]))\n newton_itr = -1\n print(\"\\n Time step @time = $timestep:\\n\")\n update!(dbcs, t) # evaluates the D-bndc at time t\n apply!(u, dbcs) # set the prescribed values in the solution vector\n\n while true\n newton_itr += 1\n if newton_itr > 8\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n # Tangent and residual contribution from the cells (volume integral)\n doassemble!(K, r, cellvalues, dh, material, u, states, states_old)\n # Residual contribution from the Neumann boundary (surface integral)\n doassemble_neumann!(r, dh, getfacetset(grid, \"right\"), facetvalues, traction)\n norm_r = norm(r[Ferrite.free_dofs(dbcs)])\n\n print(\"Iteration: $newton_itr \\tresidual: $(@sprintf(\"%.8f\", norm_r))\\n\")\n if norm_r < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbcs)\n Δu = Symmetric(K) \\ r\n u -= Δu\n end\n\n # Update the old states with the converged values for next timestep\n states_old .= states\n\n u_max[timestep] = maximum(abs, u) # maximum displacement in current timestep\n end\n\n # ## Postprocessing\n # Only a vtu-file corresponding to the last time-step is exported.\n #\n # The following is a quick (and dirty) way of extracting average cell data for export.\n mises_values = zeros(getncells(grid))\n κ_values = zeros(getncells(grid))\n for (el, cell_states) in enumerate(eachcol(states))\n for state in cell_states\n mises_values[el] += vonMises(state.σ)\n κ_values[el] += state.k * material.H\n end\n mises_values[el] /= length(cell_states) # average von Mises stress\n κ_values[el] /= length(cell_states) # average drag stress\n end\n VTKGridFile(\"plasticity\", dh) do vtk\n write_solution(vtk, dh, u) # displacement field\n write_cell_data(vtk, mises_values, \"von Mises [Pa]\")\n write_cell_data(vtk, κ_values, \"Drag stress [Pa]\")\n end\n\n return u_max, traction_magnitude\nend\n\nu_max, traction_magnitude = solve();\n\nusing Plots\nplot(\n vcat(0.0, u_max), # add the origin as a point\n vcat(0.0, traction_magnitude),\n linewidth = 2,\n title = \"Traction-displacement\",\n label = nothing,\n markershape = :auto\n)\nylabel!(\"Traction [Pa]\")\nxlabel!(\"Maximum deflection [m]\")","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"","category":"page"},{"location":"tutorials/plasticity/","page":"Von Mises plasticity","title":"Von Mises plasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/boundary_conditions/#Boundary-conditions","page":"Boundary conditions","title":"Boundary conditions","text":"","category":"section"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"Pages = [\"boundary_conditions.md\"]","category":"page"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"ConstraintHandler\nDirichlet\nPeriodicDirichlet\ncollect_periodic_facets\ncollect_periodic_facets!\nadd!\nclose!\nupdate!\napply!\napply_zero!\napply_local!\napply_assemble!\nget_rhs_data\napply_rhs!\nFerrite.RHSData","category":"page"},{"location":"reference/boundary_conditions/#Ferrite.ConstraintHandler","page":"Boundary conditions","title":"Ferrite.ConstraintHandler","text":"ConstraintHandler([T=Float64], dh::AbstractDofHandler)\n\nA collection of constraints associated with the dof handler dh. T is the numeric type for stored values.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Ferrite.Dirichlet","page":"Boundary conditions","title":"Ferrite.Dirichlet","text":"Dirichlet(u::Symbol, ∂Ω::AbstractVecOrSet, f::Function, components=nothing)\n\nCreate a Dirichlet boundary condition on u on the ∂Ω part of the boundary. f is a function of the form f(x) or f(x, t) where x is the spatial coordinate and t is the current time, and returns the prescribed value. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.\n\nThe set, ∂Ω, can be an AbstractSet or AbstractVector with elements of type FacetIndex, FaceIndex, EdgeIndex, VertexIndex, or Int. For most cases, the element type is FacetIndex, as shown below. To constrain a single point, using VertexIndex is recommended, but it is also possible to constrain a specific nodes by giving the node numbers via Int elements. To constrain e.g. an edge in 3d EdgeIndex elements can be given.\n\nFor example, here we create a Dirichlet condition for the :u field, on the facetset called ∂Ω and the value given by the sin function:\n\nExamples\n\n# Obtain the facetset from the grid\n∂Ω = getfacetset(grid, \"boundary-1\")\n\n# Prescribe scalar field :s on ∂Ω to sin(t)\ndbc = Dirichlet(:s, ∂Ω, (x, t) -> sin(t))\n\n# Prescribe all components of vector field :v on ∂Ω to 0\ndbc = Dirichlet(:v, ∂Ω, x -> 0 * x)\n\n# Prescribe component 2 and 3 of vector field :v on ∂Ω to [sin(t), cos(t)]\ndbc = Dirichlet(:v, ∂Ω, (x, t) -> [sin(t), cos(t)], [2, 3])\n\nDirichlet boundary conditions are added to a ConstraintHandler which applies the condition via apply! and/or apply_zero!.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Ferrite.PeriodicDirichlet","page":"Boundary conditions","title":"Ferrite.PeriodicDirichlet","text":"PeriodicDirichlet(u::Symbol, facet_mapping, components=nothing)\nPeriodicDirichlet(u::Symbol, facet_mapping, R::AbstractMatrix, components=nothing)\nPeriodicDirichlet(u::Symbol, facet_mapping, f::Function, components=nothing)\n\nCreate a periodic Dirichlet boundary condition for the field u on the facet-pairs given in facet_mapping. The mapping can be computed with collect_periodic_facets. The constraint ensures that degrees-of-freedom on the mirror facet are constrained to the corresponding degrees-of-freedom on the image facet. components specify the components of u that are prescribed by this condition. By default all components of u are prescribed.\n\nIf the mapping is not aligned with the coordinate axis (e.g. rotated) a rotation matrix R should be passed to the constructor. This matrix rotates dofs on the mirror facet to the image facet. Note that this is only applicable for vector-valued problems.\n\nTo construct an inhomogeneous periodic constraint it is possible to pass a function f. Note that this is currently only supported when the periodicity is aligned with the coordinate axes.\n\nSee the manual section on Periodic boundary conditions for more information.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Ferrite.collect_periodic_facets","page":"Boundary conditions","title":"Ferrite.collect_periodic_facets","text":"collect_periodic_facets(grid::Grid, mset, iset, transform::Union{Function,Nothing}=nothing; tol=1e-12)\n\nMatch all mirror facets in mset with a corresponding image facet in iset. Return a dictionary which maps each mirror facet to a image facet. The result can then be passed to PeriodicDirichlet.\n\nmset and iset can be given as a String (an existing facet set in the grid) or as a AbstractSet{FacetIndex} directly.\n\nBy default this function looks for a matching facet in the directions of the coordinate system. For other types of periodicities the transform function can be used. The transform function is applied on the coordinates of the image facet, and is expected to transform the coordinates to the matching locations in the mirror set.\n\nThe keyword tol specifies the tolerance (i.e. distance and deviation in facet-normals) between a image-facet and mirror-facet, for them to be considered matched.\n\nSee also: collect_periodic_facets!, PeriodicDirichlet.\n\n\n\n\n\ncollect_periodic_facets(grid::Grid, all_facets::Union{AbstractSet{FacetIndex},String,Nothing}=nothing; tol=1e-12)\n\nSplit all facets in all_facets into image and mirror sets. For each matching pair, the facet located further along the vector (1, 1, 1) becomes the image facet.\n\nIf no set is given, all facets on the outer boundary of the grid (i.e. all facets that do not have a neighbor) is used.\n\nSee also: collect_periodic_facets!, PeriodicDirichlet.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.collect_periodic_facets!","page":"Boundary conditions","title":"Ferrite.collect_periodic_facets!","text":"collect_periodic_facets!(facet_map::Vector{PeriodicFacetPair}, grid::Grid, mset, iset, transform::Union{Function,Nothing}; tol=1e-12)\n\nSame as collect_periodic_facets but adds all matches to the existing facet_map.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.add!","page":"Boundary conditions","title":"Ferrite.add!","text":"add!(sdh::SubDofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the SubDofHandler sdh.\n\n\n\n\n\nadd!(dh::DofHandler, name::Symbol, ip::Interpolation)\n\nAdd a field called name approximated by ip to the DofHandler dh.\n\nThe field is added to all cells of the underlying grid, use SubDofHandlers if the grid contains multiple cell types, or to add the field to subset of all the cells.\n\n\n\n\n\nadd!(ch::ConstraintHandler, ac::AffineConstraint)\n\nAdd the AffineConstraint to the ConstraintHandler.\n\n\n\n\n\nadd!(ch::ConstraintHandler, dbc::Dirichlet)\n\nAdd a Dirichlet boundary condition to the ConstraintHandler.\n\n\n\n\n\nadd!(proj::L2Projector, set::AbstractVecOrSet{Int}, ip::Interpolation;\n qr_rhs, [qr_lhs])\n\nAdd an interpolation ip on the cells in set to the L2Projector proj.\n\nqr_rhs sets the quadrature rule used to later integrate the right-hand-side of the projection equation, when calling project. It should match the quadrature points used when creating the quadrature-point variables to project.\nThe optional qr_lhs sets the quadrature rule used to integrate the left-hand-side of the projection equation, and defaults to a quadrature rule that integrates the mass-matrix exactly for the given interpolation ip.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.close!","page":"Boundary conditions","title":"Ferrite.close!","text":"close!(dh::AbstractDofHandler)\n\nCloses dh and creates degrees of freedom for each cell.\n\n\n\n\n\nclose!(ch::ConstraintHandler)\n\nClose and finalize the ConstraintHandler.\n\n\n\n\n\nclose!(proj::L2Projector)\n\nClose proj which assembles and calculates the left-hand-side of the projection equation, before doing a Cholesky factorization of the mass-matrix.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.update!","page":"Boundary conditions","title":"Ferrite.update!","text":"update!(ch::ConstraintHandler, time::Real=0.0)\n\nUpdate time-dependent inhomogeneities for the new time. This calls f(x) or f(x, t) when applicable, where f is the function(s) corresponding to the constraints in the handler, to compute the inhomogeneities.\n\nNote that this is called implicitly in close!(::ConstraintHandler).\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply!","page":"Boundary conditions","title":"Ferrite.apply!","text":"apply!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)\n\nAdjust the matrix K and right hand side rhs to account for the Dirichlet boundary conditions specified in ch such that K \\ rhs gives the expected solution.\n\nnote: Note\napply!(K, rhs, ch) essentially calculatesrhs[free] = rhs[free] - K[constrained, constrained] * a[constrained]where a[constrained] are the inhomogeneities. Consequently, the sign of rhs matters (in contrast with apply_zero!).\n\napply!(v::AbstractVector, ch::ConstraintHandler)\n\nApply Dirichlet boundary conditions and affine constraints, specified in ch, to the solution vector v.\n\nExamples\n\nK, f = assemble_system(...) # Assemble system\napply!(K, f, ch) # Adjust K and f to account for boundary conditions\nu = K \\ f # Solve the system, u should be \"approximately correct\"\napply!(u, ch) # Explicitly make sure bcs are correct\n\nnote: Note\nThe last operation is not strictly necessary since the boundary conditions should already be fulfilled after apply!(K, f, ch). However, solvers of linear systems are not exact, and thus apply!(u, ch) can be used to make sure the boundary conditions are fulfilled exactly.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_zero!","page":"Boundary conditions","title":"Ferrite.apply_zero!","text":"apply_zero!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler)\n\nAdjust the matrix K and the right hand side rhs to account for prescribed Dirichlet boundary conditions and affine constraints such that du = K \\ rhs gives the expected result (e.g. du zero for all prescribed degrees of freedom).\n\napply_zero!(v::AbstractVector, ch::ConstraintHandler)\n\nZero-out values in v corresponding to prescribed degrees of freedom and update values prescribed by affine constraints, such that if a fulfills the constraints, a ± v also will.\n\nThese methods are typically used in e.g. a Newton solver where the increment, du, should be prescribed to zero even for non-homogeneouos boundary conditions.\n\nSee also: apply!.\n\nExamples\n\nu = un + Δu # Current guess\nK, g = assemble_system(...) # Assemble residual and tangent for current guess\napply_zero!(K, g, ch) # Adjust tangent and residual to take prescribed values into account\nΔΔu = K \\ g # Compute the (negative) increment, prescribed values are \"approximately\" zero\napply_zero!(ΔΔu, ch) # Make sure values are exactly zero\nΔu .-= ΔΔu # Update current guess\n\nnote: Note\nThe last call to apply_zero! is only strictly necessary for affine constraints. However, even if the Dirichlet boundary conditions should be fulfilled after apply!(K, g, ch), solvers of linear systems are not exact. apply!(ΔΔu, ch) can be used to make sure the values for the prescribed degrees of freedom are fulfilled exactly.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_local!","page":"Boundary conditions","title":"Ferrite.apply_local!","text":"apply_local!(\n local_matrix::AbstractMatrix, local_vector::AbstractVector,\n global_dofs::AbstractVector, ch::ConstraintHandler;\n apply_zero::Bool = false\n)\n\nSimilar to apply! but perform condensation of constrained degrees-of-freedom locally in local_matrix and local_vector before they are to be assembled into the global system.\n\nWhen the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).\n\nThis method can only be used if all constraints are \"local\", i.e. no constraint couples with dofs outside of the element dofs (global_dofs) since condensation of such constraints requires writing to entries in the global matrix/vector. For such a case, apply_assemble! can be used instead.\n\nNote that this method is destructive since it, by definition, modifies local_matrix and local_vector.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_assemble!","page":"Boundary conditions","title":"Ferrite.apply_assemble!","text":"apply_assemble!(\n assembler::AbstractAssembler, ch::ConstraintHandler,\n global_dofs::AbstractVector{Int},\n local_matrix::AbstractMatrix, local_vector::AbstractVector;\n apply_zero::Bool = false\n)\n\nAssemble local_matrix and local_vector into the global system in assembler by first doing constraint condensation using apply_local!.\n\nThis is similar to using apply_local! followed by assemble! with the advantage that non-local constraints can be handled, since this method can write to entries of the global matrix and vector outside of the indices in global_dofs.\n\nWhen the keyword argument apply_zero is true all inhomogeneities are set to 0 (cf. apply! vs apply_zero!).\n\nNote that this method is destructive since it modifies local_matrix and local_vector.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.get_rhs_data","page":"Boundary conditions","title":"Ferrite.get_rhs_data","text":"get_rhs_data(ch::ConstraintHandler, A::SparseMatrixCSC) -> RHSData\n\nReturns the needed RHSData for apply_rhs!.\n\nThis must be used when the same stiffness matrix is reused for multiple steps, for example when timestepping, with different non-homogeneouos Dirichlet boundary conditions.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.apply_rhs!","page":"Boundary conditions","title":"Ferrite.apply_rhs!","text":"apply_rhs!(data::RHSData, f::AbstractVector, ch::ConstraintHandler, applyzero::Bool=false)\n\nApplies the boundary condition to the right-hand-side vector without modifying the stiffness matrix.\n\nSee also: get_rhs_data.\n\n\n\n\n\n","category":"function"},{"location":"reference/boundary_conditions/#Ferrite.RHSData","page":"Boundary conditions","title":"Ferrite.RHSData","text":"RHSData\n\nStores the constrained columns and mean of the diagonal of stiffness matrix A.\n\n\n\n\n\n","category":"type"},{"location":"reference/boundary_conditions/#Initial-conditions","page":"Boundary conditions","title":"Initial conditions","text":"","category":"section"},{"location":"reference/boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"apply_analytical!","category":"page"},{"location":"reference/boundary_conditions/#Ferrite.apply_analytical!","page":"Boundary conditions","title":"Ferrite.apply_analytical!","text":"apply_analytical!(\n a::AbstractVector, dh::AbstractDofHandler, fieldname::Symbol,\n f::Function, cellset=1:getncells(get_grid(dh)))\n\nApply a solution f(x) by modifying the values in the degree of freedom vector a pertaining to the field fieldname for all cells in cellset. The function f(x) are given the spatial coordinate of the degree of freedom. For scalar fields, f(x)::Number, and for vector fields with dimension dim, f(x)::Vec{dim}.\n\nThis function can be used to apply initial conditions for time dependent problems.\n\nnote: Note\nThis function only works for standard nodal finite element interpolations when the function value at the (algebraic) node is equal to the corresponding degree of freedom value. This holds for e.g. Lagrange and Serendipity interpolations, including sub- and superparametric elements.\n\n\n\n\n\n","category":"function"},{"location":"cited-literature/#Cited-literature","page":"Cited literature","title":"Cited literature","text":"","category":"section"},{"location":"cited-literature/","page":"Cited literature","title":"Cited literature","text":"G. A. Holzapfel. Nonlinear Solid Mechanics: A Continuum Approach for Engineering (Wiley, Chichester ; New York, 2000).\n\n\n\nJ. Simo and C. Miehe. Associative coupled thermoplasticity at finite strains: Formulation, numerical analysis and implementation. Computer Methods in Applied Mechanics and Engineering 98, 41–104 (1992).\n\n\n\nL. Mu, J. Wang, Y. Wang and X. Ye. Interior penalty discontinuous Galerkin method on very general polygonal and polyhedral meshes. Journal of Computational and Applied Mathematics 255, 432–440 (2014).\n\n\n\nD. N. Arnold, F. Brezzi, B. Cockburn and L. D. Marini. Unified Analysis of Discontinuous Galerkin Methods for Elliptic Problems. SIAM Journal on Numerical Analysis 39, 1749–1779 (2002). Accessed on Dec 20, 2023.\n\n\n\nR. C. Kirby. A general approach to transforming finite elements (2017), arXiv:1706.09017 [math.NA].\n\n\n\nD. Dunavant. High degree efficient symmetrical Gaussian quadrature rules for the triangle. International journal for numerical methods in engineering 21, 1129–1148 (1985).\n\n\n\nP. Keast. Moderate-degree tetrahedral quadrature formulas. Computer methods in applied mechanics and engineering 55, 339–348 (1986).\n\n\n\nF. D. Witherden and P. E. Vincent. On the identification of symmetric quadrature rules for finite element methods. Computers & Mathematics with Applications 69, 1232–1241 (2015).\n\n\n\nM. Crouzeix and P.-A. Raviart. Conforming and nonconforming finite element methods for solving the stationary Stokes equations I. Revue française d'automatique informatique recherche opérationnelle. Mathématique 7, 33–75 (1973).\n\n\n\nR. Rannacher and S. Turek. Simple nonconforming quadrilateral Stokes element. Numerical Methods for Partial Differential Equations 8, 97–111 (1992).\n\n\n\nB. Turcksin, M. Kronbichler and W. Bangerth. WorkStream – A Design Pattern for Multicore-Enabled Finite Element Computations. ACM Trans. Math. Softw. 43 (2016).\n\n\n\nM. Cenanovic. Finite element methods for surface problems. Ph.D. Thesis, Jönköping University, School of Engineering (2017).\n\n\n\nM. W. Scroggs, J. S. Dokken, C. N. Richardson and G. N. Wells. Construction of Arbitrary Order Finite Element Degree-of-Freedom Maps on Polygonal and Polyhedral Cell Meshes. ACM Trans. Math. Softw. 48 (2022).\n\n\n\nD. R. Jantos, K. Hackl and P. Junker. An accurate and fast regularization approach to thermodynamic topology optimization. International Journal for Numerical Methods in Engineering 117, 991–1017 (2019).\n\n\n\nM. Blaszczyk, D. R. Jantos and P. Junker. Application of Taylor series combined with the weighted least square method to thermodynamic topology optimization. Computer Methods in Applied Mechanics and Engineering 393, 114698 (2022).\n\n\n\n","category":"page"},{"location":"cited-literature/","page":"Cited literature","title":"Cited literature","text":"","category":"page"},{"location":"devdocs/reference_cells/#Reference-cells","page":"Reference cells","title":"Reference cells","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"The reference cells are used to i) define grid cells, ii) define shape functions, and iii) define quadrature rules. The numbering of vertices, edges, faces are visualized below. See also FerriteViz.elementinfo.","category":"page"},{"location":"devdocs/reference_cells/#AbstractRefShape-subtypes","page":"Reference cells","title":"AbstractRefShape subtypes","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.AbstractRefShape\nFerrite.RefLine\nFerrite.RefTriangle\nFerrite.RefQuadrilateral\nFerrite.RefTetrahedron\nFerrite.RefHexahedron\nFerrite.RefPrism","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.AbstractRefShape","page":"Reference cells","title":"Ferrite.AbstractRefShape","text":"AbstractRefShape{refdim}\n\nSupertype for all reference shapes, with reference dimension refdim. Reference shapes are used to define grid cells, shape functions, and quadrature rules. Currently existing reference shapes are: RefLine, RefTriangle, RefQuadrilateral, RefTetrahedron, RefHexahedron, RefPrism.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefLine","page":"Reference cells","title":"Ferrite.RefLine","text":"RefLine <: AbstractRefShape{1}\n\nReference line/interval, reference dimension 1.\n\n----------------+--------------------\nVertex numbers: | Vertex coordinates:\n 1-------2 | v1: 𝛏 = (-1.0,)\n --> ξ₁ | v2: 𝛏 = ( 1.0,)\n----------------+--------------------\nFace numbers: | Face identifiers:\n 1-------2 | f1: (v1,)\n | f2: (v2,)\n----------------+--------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefTriangle","page":"Reference cells","title":"Ferrite.RefTriangle","text":"RefTriangle <: AbstractRefShape{2}\n\nReference triangle, reference dimension 2.\n\n----------------+--------------------\nVertex numbers: | Vertex coordinates:\n 2 |\n | \\ | v1: 𝛏 = (1.0, 0.0)\n | \\ | v2: 𝛏 = (0.0, 1.0)\nξ₂^ | \\ | v3: 𝛏 = (0.0, 0.0)\n | 3-------1 |\n +--> ξ₁ |\n----------------+--------------------\nFace numbers: | Face identifiers:\n + |\n | \\ | f1: (v1, v2)\n 2 1 | f2: (v2, v3)\n | \\ | f3: (v3, v1)\n +---3---+ |\n----------------+--------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefQuadrilateral","page":"Reference cells","title":"Ferrite.RefQuadrilateral","text":"RefQuadrilateral <: AbstractRefShape{2}\n\nReference quadrilateral, reference dimension 2.\n\n----------------+---------------------\nVertex numbers: | Vertex coordinates:\n 4-------3 |\n | | | v1: 𝛏 = (-1.0, -1.0)\n | | | v2: 𝛏 = ( 1.0, -1.0)\nξ₂^ | | | v3: 𝛏 = ( 1.0, 1.0)\n | 1-------2 | v4: 𝛏 = (-1.0, 1.0)\n +--> ξ₁ |\n----------------+---------------------\nFace numbers: | Face identifiers:\n +---3---+ | f1: (v1, v2)\n | | | f2: (v2, v3)\n 4 2 | f3: (v3, v4)\n | | | f4: (v4, v1)\n +---1---+ |\n----------------+---------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefTetrahedron","page":"Reference cells","title":"Ferrite.RefTetrahedron","text":"RefTetrahedron <: AbstractRefShape{3}\n\nReference tetrahedron, reference dimension 3.\n\n---------------------------------------+-------------------------\nVertex numbers: | Vertex coordinates:\n 4 4 |\n ^ ξ₃ / \\ /| \\ | v1: 𝛏 = (0.0, 0.0, 0.0)\n | / \\ / | \\ | v2: 𝛏 = (1.0, 0.0, 0.0)\n +-> ξ₂ / \\ / 1___ \\ | v3: 𝛏 = (0.0, 1.0, 0.0)\n / / __--3 / / __‾-3 | v4: 𝛏 = (0.0, 0.0, 1.0)\nξ₁ 2 __--‾‾ 2/__--‾‾ |\n---------------------------------------+-------------------------\nEdge numbers: | Edge identifiers:\n + + | e1: (v1, v2)\n / \\ /| \\ | e2: (v2, v3)\n 5 / \\ 6 5 / |4 \\ 6 | e3: (v3, v1)\n / \\ / +__3 \\ | e4: (v1, v4)\n / __--+ / /1 __‾-+ | e5: (v2, v4)\n + __--‾‾2 +/__--‾‾2 | e6: (v3, v4)\n---------------------------------------+-------------------------\nFace numbers: | Face identifiers:\n + + |\n / \\ /| \\ | f1: (v1, v3, v2)\n / \\ / | 4 \\ | f2: (v1, v2, v4)\n / 3 \\ /2 +___ \\ | f3: (v2, v3, v4)\n / __--+ / / 1 __‾-+ | f4: (v1, v4, v3)\n + __--‾‾ +/__--‾‾ |\n---------------------------------------+-------------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefHexahedron","page":"Reference cells","title":"Ferrite.RefHexahedron","text":"RefHexahedron <: AbstractRefShape{3}\n\nReference hexahedron, reference dimension 3.\n\n-----------------------------------------+----------------------------\nVertex numbers: | Vertex coordinates:\n 5--------8 5--------8 | v1: 𝛏 = (-1.0, -1.0, -1.0)\n / /| /| | | v2: 𝛏 = ( 1.0, -1.0, -1.0)\n / / | / | | | v3: 𝛏 = ( 1.0, 1.0, -1.0)\n ^ ξ₃ 6--------7 | 6 | | | v4: 𝛏 = (-1.0, 1.0, -1.0)\n | | | 4 | 1--------4 | v5: 𝛏 = (-1.0, -1.0, 1.0)\n +-> ξ₂ | | / | / / | v6: 𝛏 = ( 1.0, -1.0, 1.0)\n / | |/ |/ / | v7: 𝛏 = ( 1.0, 1.0, 1.0)\nξ₁ 2--------3 2--------3 | v8: 𝛏 = (-1.0, 1.0, 1.0)\n-----------------------------------------+-----------------------------\nEdge numbers: | Edge identifiers:\n +----8---+ +----8---+ |\n 5/ /| 5/| | | e1: (v1, v2), e2: (v2, v3)\n / 7/ |12 / |9 12| | e3: (v3, v4), e4: (v4, v1)\n +----6---+ | + | | | e5: (v5, v6), e6: (v6, v7)\n | | + | +---4----+ | e7: (v7, v8), e8: (v8, v5)\n 10| 11| / 10| /1 / | e9: (v1, v5), e10: (v2, v6)\n | |/3 |/ /3 | e11: (v3, v7), e12: (v4, v8)\n +---2----+ +---2----+ |\n-----------------------------------------+-----------------------------\nFace numbers: | Face identifiers:\n +--------+ +--------+ |\n / 6 /| /| | | f1: (v1, v4, v3, v2)\n / / | / | 5 | | f2: (v1, v2, v6, v5)\n +--------+ 4| + | | | f3: (v2, v3, v7, v6)\n | | + |2 +--------+ | f4: (v3, v4, v8, v7)\n | 3 | / | / / | f5: (v1, v5, v8, v4)\n | |/ |/ 1 / | f6: (v5, v6, v7, v8)\n +--------+ +--------+ |\n-----------------------------------------+-----------------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Ferrite.RefPrism","page":"Reference cells","title":"Ferrite.RefPrism","text":"RefPrism <: AbstractRefShape{3}\n\nReference prism, reference dimension 3.\n\n-----------------------------------------+----------------------------\nVertex numbers: | Vertex coordinates:\n 4-------/6 4--------6 |\n / / | /| | | v1: 𝛏 = (0.0, 0.0, 0.0)\n / / | / | | | v2: 𝛏 = (1.0, 0.0, 0.0)\n ^ ξ₃ 5 / | 5 | | | v3: 𝛏 = (0.0, 1.0, 0.0)\n | | /3 | 1-------/3 | v4: 𝛏 = (0.0, 0.0, 1.0)\n +-> ξ₂ | / | / / | v5: 𝛏 = (1.0, 0.0, 1.0)\n / | / |/ / | v6: 𝛏 = (0.0, 1.0, 1.0)\nξ₁ 2 / 2 / |\n-----------------------------------------+----------------------------\nEdge numbers: | Edge identifiers:\n +---8---/+ +---8----+ |\n 7/ / | 7/| | | e1: (v2, v1), e2: (v1, v3)\n / / 9 |6 / |3 |6 | e3: (v1, v4), e4: (v3, v2)\n + / | + | | | e5: (v2, v5), e6: (v3, v6)\n | /+ | +--2----/+ | e7: (v4, v5), e8: (v4, v6)\n 5| / 5| /1 / | e9: (v6, v5)\n | / 4 |/ / 4 |\n + / + / |\n-----------------------------------------+----------------------------\nFace numbers: | Face identifiers:\n +-------/+ +--------+ |\n / 5 / | /| | | f1: (v1, v3, v2)\n / / | / | 3 | | f2: (v1, v2, v5, v4)\n + / | + | | | f3: (v3, v1, v4, v6)\n | 4 /+ |2 +-------/+ | f4: (v2, v3, v6, v5)\n | / | / 1 / | f5: (v4, v5, v6)\n | / |/ / |\n + / + / |\n-----------------------------------------+----------------------------\n\n\n\n\n\n","category":"type"},{"location":"devdocs/reference_cells/#Required-methods-to-implement-for-all-subtypes-of-AbstractRefShape-to-define-a-new-reference-shape","page":"Reference cells","title":"Required methods to implement for all subtypes of AbstractRefShape to define a new reference shape","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.reference_vertices(::Type{<:Ferrite.AbstractRefShape})\nFerrite.reference_edges(::Type{<:Ferrite.AbstractRefShape})\nFerrite.reference_faces(::Type{<:Ferrite.AbstractRefShape})","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.reference_vertices-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_vertices","text":"reference_vertices(::Type{<:AbstractRefShape})\nreference_vertices(::AbstractCell)\n\nReturns a tuple of integers containing the local node indices corresponding to the vertices (i.e. corners or endpoints) of the cell.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/#Ferrite.reference_edges-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_edges","text":"reference_edges(::Type{<:AbstractRefShape})\nreference_edges(::AbstractCell)\n\nReturns a tuple of 2-tuples containing the ordered local node indices (corresponding to the vertices) that define an edge.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/#Ferrite.reference_faces-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_faces","text":"reference_faces(::Type{<:AbstractRefShape})\nreference_faces(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a face.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"which automatically defines","category":"page"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.reference_facets(::Type{<:Ferrite.AbstractRefShape})","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.reference_facets-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.reference_facets","text":"Ferrite.reference_facets(::Type{<:AbstractRefShape})\nFerrite.reference_facets(::AbstractCell)\n\nReturns a tuple of n-tuples containing the ordered local node indices (corresponding to the vertices) that define a facet.\n\nSee also reference_vertices, reference_edges, and reference_faces.\n\n\n\n\n\n","category":"method"},{"location":"devdocs/reference_cells/#Applicable-methods-to-AbstractRefShapes","page":"Reference cells","title":"Applicable methods to AbstractRefShapes","text":"","category":"section"},{"location":"devdocs/reference_cells/","page":"Reference cells","title":"Reference cells","text":"Ferrite.getrefdim(::Type{<:Ferrite.AbstractRefShape})","category":"page"},{"location":"devdocs/reference_cells/#Ferrite.getrefdim-Tuple{Type{<:Ferrite.AbstractRefShape}}","page":"Reference cells","title":"Ferrite.getrefdim","text":"Ferrite.getrefdim(RefShape::Type{<:AbstractRefShape})\n\nGet the dimension of the reference shape\n\n\n\n\n\n","category":"method"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"EditURL = \"../literate-tutorials/incompressible_elasticity.jl\"","category":"page"},{"location":"tutorials/incompressible_elasticity/#tutorial-incompressible-elasticity","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: incompressible_elasticity.ipynb.","category":"page"},{"location":"tutorials/incompressible_elasticity/#Introduction","page":"Incompressible elasticity","title":"Introduction","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Mixed elements can be used to overcome locking when the material becomes incompressible. However, for an element to be stable, it needs to fulfill the LBB condition. In this example we will consider two different element formulations","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"linear displacement with linear pressure approximation (does not fulfill LBB)\nquadratic displacement with linear pressure approximation (does fulfill LBB)","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"The quadratic/linear element is also known as the Taylor-Hood element. We will consider Cook's Membrane with an applied traction on the right hand side.","category":"page"},{"location":"tutorials/incompressible_elasticity/#Commented-program","page":"Incompressible elasticity","title":"Commented program","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"using Ferrite, Tensors\nusing BlockArrays, SparseArrays, LinearAlgebra","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"First we generate a simple grid, specifying the 4 corners of Cooks membrane.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_cook_grid(nx, ny)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((48.0, 44.0)),\n Vec{2}((48.0, 60.0)),\n Vec{2}((0.0, 44.0)),\n ]\n grid = generate_grid(Triangle, (nx, ny), corners)\n # facesets for boundary conditions\n addfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> norm(x[1]) ≈ 48.0)\n return grid\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Next we define a function to set up our cell- and FacetValues.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTriangle}(3)\n facet_qr = FacetQuadratureRule{RefTriangle}(3)\n\n # cell and FacetValues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"We create a DofHandler, with two fields, :u and :p, with possibly different interpolations","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement\n add!(dh, :p, ipp) # pressure\n close!(dh)\n return dh\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"We also need to add Dirichlet boundary conditions on the \"clamped\" facetset. We specify a homogeneous Dirichlet bc on the displacement field, :u.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"clamped\"), x -> zero(x), [1, 2]))\n close!(dbc)\n return dbc\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"The material is linear elastic, which is here specified by the shear and bulk moduli","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"struct LinearElasticity{T}\n G::T\n K::T\nend","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Now to the assembling of the stiffness matrix. This mixed formulation leads to a blocked element matrix. Since Ferrite does not force us to use any particular matrix type we will use a BlockedArray from BlockArrays.jl.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function doassemble(\n cellvalues_u::CellValues,\n cellvalues_p::CellValues,\n facetvalues_u::FacetValues,\n K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity\n )\n f = zeros(ndofs(dh))\n assembler = start_assemble(K, f)\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n # traction vector\n t = Vec{2}((0.0, 1 / 16))\n # cache ɛdev outside the element routine to avoid some unnecessary allocations\n ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)]\n\n for cell in CellIterator(dh)\n fill!(ke, 0)\n fill!(fe, 0)\n assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n\n return K, f\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"The element routine integrates the local stiffness and force vector for all elements. Since the problem results in a symmetric matrix we choose to only assemble the lower part, and then symmetrize it after the loop over the quadrature points.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n u▄, p▄ = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n for q_point in 1:getnquadpoints(cellvalues_u)\n for i in 1:n_basefuncs_u\n ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))\n end\n dΩ = getdetJdV(cellvalues_u, q_point)\n for i in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, i)\n δu = shape_value(cellvalues_u, q_point, i)\n for j in 1:i\n Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ\n end\n end\n\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, q_point, i)\n for j in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, j)\n Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ\n end\n for j in 1:i\n p = shape_value(cellvalues_p, q_point, j)\n Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ\n end\n\n end\n end\n\n symmetrize_lower!(Ke)\n\n # We integrate the Neumann boundary using the FacetValues.\n # We loop over all the facets in the cell, then check if the facet\n # is in our `\"traction\"` facetset.\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues_u, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues_u)\n dΓ = getdetJdV(facetvalues_u, q_point)\n for i in 1:n_basefuncs_u\n δu = shape_value(facetvalues_u, q_point, i)\n fe[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(Ke)\n for i in 1:size(Ke, 1)\n for j in (i + 1):size(Ke, 1)\n Ke[i, j] = Ke[j, i]\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"To evaluate the stresses after solving the problem we once again loop over the cells in the grid. Stresses are evaluated in the quadrature points, however, for export/visualization you typically want values in the nodes of the mesh, or as single data points per cell. For the former you can project the quadrature point data to a finite element space (see the example with the L2Projector in Post processing and visualization). In this example we choose to compute the mean value of the stress within each cell, and thus end up with one data point per cell. The mean value is computed as","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"barboldsymbolsigma_i = frac1 Omega_i\nint_Omega_i boldsymbolsigma mathrmdOmega quad\nOmega_i = int_Omega_i 1 mathrmdOmega","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"where Omega_i is the domain occupied by cell number i, and Omega_i the volume (area) of the cell. The integrals are evaluated using numerical quadrature with the help of cellvalues for u and p, just like in the assembly procedure.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Note that even though all strain components in the out-of-plane direction are zero (plane strain) the stress components are not. Specifically, sigma_33 will be non-zero in this formulation. Therefore we expand the strain to a 3D tensor, and then compute the (3D) stress tensor.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function compute_stresses(\n cellvalues_u::CellValues, cellvalues_p::CellValues,\n dh::DofHandler, mp::LinearElasticity, a::Vector\n )\n ae = zeros(ndofs_per_cell(dh)) # local solution vector\n u_range = dof_range(dh, :u) # local range of dofs corresponding to u\n p_range = dof_range(dh, :p) # local range of dofs corresponding to p\n # Allocate storage for the stresses\n σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid))\n # Loop over the cells and compute the cell-average stress\n for cc in CellIterator(dh)\n # Update cellvalues\n reinit!(cellvalues_u, cc)\n reinit!(cellvalues_p, cc)\n # Extract the cell local part of the solution\n for (i, I) in pairs(celldofs(cc))\n ae[i] = a[I]\n end\n # Loop over the quadrature points\n σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell\n Ωi = 0.0 # cell volume (area)\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Evaluate the strain and the pressure\n ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range)\n p = function_value(cellvalues_p, qp, ae, p_range)\n # Expand strain to 3D\n ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0)\n # Compute the stress in this quadrature point\n σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p\n σΩi += σqp * dΩ\n Ωi += dΩ\n end\n # Store the value\n σ[cellid(cc)] = σΩi / Ωi\n end\n return σ\nend;\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Now we have constructed all the necessary components, we just need a function to put it all together.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"function solve(ν, interpolation_u, interpolation_p)\n # material\n Emod = 1.0\n Gmod = Emod / 2(1 + ν)\n Kmod = Emod * ν / ((1 + ν) * (1 - 2ν))\n mp = LinearElasticity(Gmod, Kmod)\n\n # Grid, dofhandler, boundary condition\n n = 50\n grid = create_cook_grid(n, n)\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n dbc = create_bc(dh)\n\n # CellValues\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Assembly and solve\n K = allocate_matrix(dh)\n K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp)\n apply!(K, f, dbc)\n u = K \\ f\n\n # Compute the stress\n σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u)\n σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress\n\n # Export the solution and the stress\n filename = \"cook_\" *\n (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? \"linear\" : \"quadratic\") *\n \"_linear\"\n\n VTKGridFile(filename, grid) do vtk\n write_solution(vtk, dh, u)\n for i in 1:3, j in 1:3\n σij = [x[i, j] for x in σ]\n write_cell_data(vtk, σij, \"sigma_$(i)$(j)\")\n end\n write_cell_data(vtk, σvM, \"sigma von Mises\")\n end\n return u\nend\nnothing # hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"We now define the interpolation for displacement and pressure. We use (scalar) Lagrange interpolation as a basis for both, and for the displacement, which is a vector, we vectorize it to 2 dimensions such that we obtain vector shape functions (and 2nd order tensors for the gradients).","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"linear_p = Lagrange{RefTriangle, 1}()\nlinear_u = Lagrange{RefTriangle, 1}()^2\nquadratic_u = Lagrange{RefTriangle, 2}()^2\nnothing # hide","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"All that is left is to solve the problem. We choose a value of Poissons ratio that results in incompressibility (ν = 05) and thus expect the linear/linear approximation to return garbage, and the quadratic/linear approximation to be stable.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"u1 = solve(0.5, linear_u, linear_p);\nu2 = solve(0.5, quadratic_u, linear_p);\nnothing #hide","category":"page"},{"location":"tutorials/incompressible_elasticity/#incompressible_elasticity-plain-program","page":"Incompressible elasticity","title":"Plain program","text":"","category":"section"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"Here follows a version of the program without any comments. The file is also available here: incompressible_elasticity.jl.","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"using Ferrite, Tensors\nusing BlockArrays, SparseArrays, LinearAlgebra\n\nfunction create_cook_grid(nx, ny)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((48.0, 44.0)),\n Vec{2}((48.0, 60.0)),\n Vec{2}((0.0, 44.0)),\n ]\n grid = generate_grid(Triangle, (nx, ny), corners)\n # facesets for boundary conditions\n addfacetset!(grid, \"clamped\", x -> norm(x[1]) ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> norm(x[1]) ≈ 48.0)\n return grid\nend;\n\nfunction create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTriangle}(3)\n facet_qr = FacetQuadratureRule{RefTriangle}(3)\n\n # cell and FacetValues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\n\nfunction create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement\n add!(dh, :p, ipp) # pressure\n close!(dh)\n return dh\nend;\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"clamped\"), x -> zero(x), [1, 2]))\n close!(dbc)\n return dbc\nend;\n\nstruct LinearElasticity{T}\n G::T\n K::T\nend\n\nfunction doassemble(\n cellvalues_u::CellValues,\n cellvalues_p::CellValues,\n facetvalues_u::FacetValues,\n K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::LinearElasticity\n )\n f = zeros(ndofs(dh))\n assembler = start_assemble(K, f)\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n # traction vector\n t = Vec{2}((0.0, 1 / 16))\n # cache ɛdev outside the element routine to avoid some unnecessary allocations\n ɛdev = [zero(SymmetricTensor{2, 2}) for i in 1:getnbasefunctions(cellvalues_u)]\n\n for cell in CellIterator(dh)\n fill!(ke, 0)\n fill!(fe, 0)\n assemble_up!(ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n\n return K, f\nend;\n\nfunction assemble_up!(Ke, fe, cell, cellvalues_u, cellvalues_p, facetvalues_u, grid, mp, ɛdev, t)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n u▄, p▄ = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n for q_point in 1:getnquadpoints(cellvalues_u)\n for i in 1:n_basefuncs_u\n ɛdev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))\n end\n dΩ = getdetJdV(cellvalues_u, q_point)\n for i in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, i)\n δu = shape_value(cellvalues_u, q_point, i)\n for j in 1:i\n Ke[BlockIndex((u▄, u▄), (i, j))] += 2 * mp.G * ɛdev[i] ⊡ ɛdev[j] * dΩ\n end\n end\n\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, q_point, i)\n for j in 1:n_basefuncs_u\n divδu = shape_divergence(cellvalues_u, q_point, j)\n Ke[BlockIndex((p▄, u▄), (i, j))] += -δp * divδu * dΩ\n end\n for j in 1:i\n p = shape_value(cellvalues_p, q_point, j)\n Ke[BlockIndex((p▄, p▄), (i, j))] += - 1 / mp.K * δp * p * dΩ\n end\n\n end\n end\n\n symmetrize_lower!(Ke)\n\n # We integrate the Neumann boundary using the FacetValues.\n # We loop over all the facets in the cell, then check if the facet\n # is in our `\"traction\"` facetset.\n for facet in 1:nfacets(cell)\n if (cellid(cell), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues_u, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues_u)\n dΓ = getdetJdV(facetvalues_u, q_point)\n for i in 1:n_basefuncs_u\n δu = shape_value(facetvalues_u, q_point, i)\n fe[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(Ke)\n for i in 1:size(Ke, 1)\n for j in (i + 1):size(Ke, 1)\n Ke[i, j] = Ke[j, i]\n end\n end\n return\nend;\n\nfunction compute_stresses(\n cellvalues_u::CellValues, cellvalues_p::CellValues,\n dh::DofHandler, mp::LinearElasticity, a::Vector\n )\n ae = zeros(ndofs_per_cell(dh)) # local solution vector\n u_range = dof_range(dh, :u) # local range of dofs corresponding to u\n p_range = dof_range(dh, :p) # local range of dofs corresponding to p\n # Allocate storage for the stresses\n σ = zeros(SymmetricTensor{2, 3}, getncells(dh.grid))\n # Loop over the cells and compute the cell-average stress\n for cc in CellIterator(dh)\n # Update cellvalues\n reinit!(cellvalues_u, cc)\n reinit!(cellvalues_p, cc)\n # Extract the cell local part of the solution\n for (i, I) in pairs(celldofs(cc))\n ae[i] = a[I]\n end\n # Loop over the quadrature points\n σΩi = zero(SymmetricTensor{2, 3}) # stress integrated over the cell\n Ωi = 0.0 # cell volume (area)\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Evaluate the strain and the pressure\n ε = function_symmetric_gradient(cellvalues_u, qp, ae, u_range)\n p = function_value(cellvalues_p, qp, ae, p_range)\n # Expand strain to 3D\n ε3D = SymmetricTensor{2, 3}((i, j) -> i < 3 && j < 3 ? ε[i, j] : 0.0)\n # Compute the stress in this quadrature point\n σqp = 2 * mp.G * dev(ε3D) - one(ε3D) * p\n σΩi += σqp * dΩ\n Ωi += dΩ\n end\n # Store the value\n σ[cellid(cc)] = σΩi / Ωi\n end\n return σ\nend;\n\nfunction solve(ν, interpolation_u, interpolation_p)\n # material\n Emod = 1.0\n Gmod = Emod / 2(1 + ν)\n Kmod = Emod * ν / ((1 + ν) * (1 - 2ν))\n mp = LinearElasticity(Gmod, Kmod)\n\n # Grid, dofhandler, boundary condition\n n = 50\n grid = create_cook_grid(n, n)\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n dbc = create_bc(dh)\n\n # CellValues\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Assembly and solve\n K = allocate_matrix(dh)\n K, f = doassemble(cellvalues_u, cellvalues_p, facetvalues_u, K, grid, dh, mp)\n apply!(K, f, dbc)\n u = K \\ f\n\n # Compute the stress\n σ = compute_stresses(cellvalues_u, cellvalues_p, dh, mp, u)\n σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress\n\n # Export the solution and the stress\n filename = \"cook_\" *\n (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? \"linear\" : \"quadratic\") *\n \"_linear\"\n\n VTKGridFile(filename, grid) do vtk\n write_solution(vtk, dh, u)\n for i in 1:3, j in 1:3\n σij = [x[i, j] for x in σ]\n write_cell_data(vtk, σij, \"sigma_$(i)$(j)\")\n end\n write_cell_data(vtk, σvM, \"sigma von Mises\")\n end\n return u\nend\n\nlinear_p = Lagrange{RefTriangle, 1}()\nlinear_u = Lagrange{RefTriangle, 1}()^2\nquadratic_u = Lagrange{RefTriangle, 2}()^2\n\nu1 = solve(0.5, linear_u, linear_p);\nu2 = solve(0.5, quadratic_u, linear_p);","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"","category":"page"},{"location":"tutorials/incompressible_elasticity/","page":"Incompressible elasticity","title":"Incompressible elasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"CurrentModule = Ferrite\nDocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/fevalues/#FEValues","page":"FEValues","title":"FEValues","text":"","category":"section"},{"location":"reference/fevalues/#Main-types","page":"FEValues","title":"Main types","text":"","category":"section"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"CellValues and FacetValues are the most common subtypes of Ferrite.AbstractValues. For more details about how these work, please see the related topic guide.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"CellValues\nFacetValues","category":"page"},{"location":"reference/fevalues/#Ferrite.CellValues","page":"FEValues","title":"Ferrite.CellValues","text":"CellValues([::Type{T},] quad_rule::QuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])\n\nA CellValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. in the finite element cell.\n\nArguments:\n\nT: an optional argument (default to Float64) to determine the type the internal data is stored as.\nquad_rule: an instance of a QuadratureRule\nfunc_interpol: an instance of an Interpolation used to interpolate the approximated function\ngeom_interpol: an optional instance of a Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used. For embedded elements the geometric interpolations should be vectorized to the spatial dimension.\n\nKeyword arguments: The following keyword arguments are experimental and may change in future minor releases\n\nupdate_gradients: Specifies if the gradients of the shape functions should be updated (default true)\nupdate_hessians: Specifies if the hessians of the shape functions should be updated (default false)\nupdate_detJdV: Specifies if the volume associated with each quadrature point should be updated (default true)\n\nCommon methods:\n\nreinit!\ngetnquadpoints\ngetdetJdV\nshape_value\nshape_gradient\nshape_symmetric_gradient\nshape_divergence\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nspatial_coordinate\n\n\n\n\n\n","category":"type"},{"location":"reference/fevalues/#Ferrite.FacetValues","page":"FEValues","title":"Ferrite.FacetValues","text":"FacetValues([::Type{T}], quad_rule::FacetQuadratureRule, func_interpol::Interpolation, [geom_interpol::Interpolation])\n\nA FacetValues object facilitates the process of evaluating values of shape functions, gradients of shape functions, values of nodal functions, gradients and divergences of nodal functions etc. on the facets of finite elements.\n\nArguments:\n\nT: an optional argument (default to Float64) to determine the type the internal data is stored as.\nquad_rule: an instance of a FacetQuadratureRule\nfunc_interpol: an instance of an Interpolation used to interpolate the approximated function\ngeom_interpol: an optional instance of an Interpolation which is used to interpolate the geometry. By default linear Lagrange interpolation is used.\n\nKeyword arguments: The following keyword arguments are experimental and may change in future minor releases\n\nupdate_gradients: Specifies if the gradients of the shape functions should be updated (default true)\nupdate_hessians: Specifies if the hessians of the shape functions should be updated (default false)\n\nCommon methods:\n\nreinit!\ngetnquadpoints\ngetdetJdV\nshape_value\nshape_gradient\nshape_symmetric_gradient\nshape_divergence\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nspatial_coordinate\n\n\n\n\n\n","category":"type"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"warning: Embedded API\nCurrently, embedded FEValues returns SArrays, which behave differently from the Tensors for normal value. In the future, we expect to return an AbstractTensor, this change may happen in a minor release, and the API for embedded FEValues should therefore be considered experimental.","category":"page"},{"location":"reference/fevalues/#Applicable-functions","page":"FEValues","title":"Applicable functions","text":"","category":"section"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"The following functions are applicable to both CellValues and FacetValues.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"reinit!\ngetnquadpoints\ngetdetJdV\n\nshape_value(::Ferrite.AbstractValues, ::Int, ::Int)\nshape_gradient(::Ferrite.AbstractValues, ::Int, ::Int)\nshape_symmetric_gradient\nshape_divergence\nshape_curl\ngeometric_value\n\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nfunction_curl\nspatial_coordinate","category":"page"},{"location":"reference/fevalues/#Ferrite.reinit!","page":"FEValues","title":"Ferrite.reinit!","text":"reinit!(cv::CellValues, cell::AbstractCell, x::AbstractVector)\nreinit!(cv::CellValues, x::AbstractVector)\nreinit!(fv::FacetValues, cell::AbstractCell, x::AbstractVector, facet::Int)\nreinit!(fv::FacetValues, x::AbstractVector, function_gradient::Int)\n\nUpdate the CellValues/FacetValues object for a cell or facet with cell coordinates x. The derivatives of the shape functions, and the new integration weights are computed. For interpolations with non-identity mappings, the current cell is also required.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.getnquadpoints","page":"FEValues","title":"Ferrite.getnquadpoints","text":"getnquadpoints(fe_v::AbstractValues)\n\nReturn the number of quadrature points. For FacetValues, this is the number for the current facet.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.getdetJdV","page":"FEValues","title":"Ferrite.getdetJdV","text":"getdetJdV(fe_v::AbstractValues, q_point::Int)\n\nReturn the product between the determinant of the Jacobian and the quadrature point weight for the given quadrature point: det(J(mathbfx)) w_q.\n\nThis value is typically used when one integrates a function on a finite element cell or facet as\n\nintlimits_Omega f(mathbfx) d Omega approx sumlimits_q = 1^n_q f(mathbfx_q) det(J(mathbfx)) w_q intlimits_Gamma f(mathbfx) d Gamma approx sumlimits_q = 1^n_q f(mathbfx_q) det(J(mathbfx)) w_q\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_value-Tuple{Ferrite.AbstractValues, Int64, Int64}","page":"FEValues","title":"Ferrite.shape_value","text":"shape_value(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the value of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"method"},{"location":"reference/fevalues/#Ferrite.shape_gradient-Tuple{Ferrite.AbstractValues, Int64, Int64}","page":"FEValues","title":"Ferrite.shape_gradient","text":"shape_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the gradient of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"method"},{"location":"reference/fevalues/#Ferrite.shape_symmetric_gradient","page":"FEValues","title":"Ferrite.shape_symmetric_gradient","text":"shape_symmetric_gradient(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the symmetric gradient of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_divergence","page":"FEValues","title":"Ferrite.shape_divergence","text":"shape_divergence(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the divergence of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_curl","page":"FEValues","title":"Ferrite.shape_curl","text":"shape_curl(fe_v::AbstractValues, q_point::Int, base_function::Int)\n\nReturn the curl of shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.geometric_value","page":"FEValues","title":"Ferrite.geometric_value","text":"geometric_value(fe_v::AbstractValues, q_point, base_function::Int)\n\nReturn the value of the geometric shape function base_function evaluated in quadrature point q_point.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_value","page":"FEValues","title":"Ferrite.function_value","text":"function_value(iv::InterfaceValues, q_point::Int, u; here::Bool)\nfunction_value(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)\n\nCompute the value of the function in quadrature point q_point on the \"here\" (here=true) or \"there\" (here=false) side of the interface. u_here and u_there are the values of the degrees of freedom for the respective element.\n\nu is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.\n\nhere determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.\n\nThe value of a scalar valued function is computed as u(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) u_i where u_i are the value of u in the nodes. For a vector valued function the value is calculated as mathbfu(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) mathbfu_i where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\nfunction_value(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the value of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).\n\nThe value of a scalar valued function is computed as u(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) u_i where u_i are the value of u in the nodes. For a vector valued function the value is calculated as mathbfu(mathbfx) = sumlimits_i = 1^n N_i (mathbfx) mathbfu_i where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_gradient","page":"FEValues","title":"Ferrite.function_gradient","text":"function_gradient(iv::InterfaceValues, q_point::Int, u; here::Bool)\nfunction_gradient(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there; here::Bool)\n\nCompute the gradient of the function in a quadrature point. u is a vector of scalar values for the degrees of freedom. This function can be used with a single u vector containing the dofs of both elements of the interface or two vectors (u_here and u_there) which contain the dofs of each cell of the interface respectively.\n\nhere determines which element to use for calculating function value. true uses the value on the first element's side of the interface, while false uses the value on the second element's side.\n\nThe gradient of a scalar function or a vector valued function with use of VectorValues is computed as mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla N_i (mathbfx) u_i or mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla mathbfN_i (mathbfx) u_i respectively, where u_i are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as mathbfnabla mathbfu(mathbfx) = sumlimits_i = 1^n mathbfu_i otimes mathbfnabla N_i (mathbfx) where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\nfunction_gradient(fe_v::AbstractValues{dim}, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the gradient of the function in a quadrature point. u is a vector with values for the degrees of freedom. For a scalar valued function, u contains scalars. For a vector valued function, u can be a vector of scalars (for use of VectorValues) or u can be a vector of Vecs (for use with ScalarValues).\n\nThe gradient of a scalar function or a vector valued function with use of VectorValues is computed as mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla N_i (mathbfx) u_i or mathbfnabla u(mathbfx) = sumlimits_i = 1^n mathbfnabla mathbfN_i (mathbfx) u_i respectively, where u_i are the nodal values of the function. For a vector valued function with use of ScalarValues the gradient is computed as mathbfnabla mathbfu(mathbfx) = sumlimits_i = 1^n mathbfu_i otimes mathbfnabla N_i (mathbfx) where mathbfu_i are the nodal values of mathbfu.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_symmetric_gradient","page":"FEValues","title":"Ferrite.function_symmetric_gradient","text":"function_symmetric_gradient(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the symmetric gradient of the function, see function_gradient. Return a SymmetricTensor.\n\nThe symmetric gradient of a scalar function is computed as left mathbfnabla mathbfu(mathbfx_q) right^textsym = sumlimits_i = 1^n frac12 left mathbfnabla N_i (mathbfx_q) otimes mathbfu_i + mathbfu_i otimes mathbfnabla N_i (mathbfx_q) right where mathbfu_i are the nodal values of the function.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_divergence","page":"FEValues","title":"Ferrite.function_divergence","text":"function_divergence(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the divergence of the vector valued function in a quadrature point.\n\nThe divergence of a vector valued functions in the quadrature point mathbfx_q) is computed as mathbfnabla cdot mathbfu(mathbfx_q) = sumlimits_i = 1^n mathbfnabla N_i (mathbfx_q) cdot mathbfu_i where mathbfu_i are the nodal values of the function.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_curl","page":"FEValues","title":"Ferrite.function_curl","text":"function_curl(fe_v::AbstractValues, q_point::Int, u::AbstractVector, [dof_range])\n\nCompute the curl of the vector valued function in a quadrature point.\n\nThe curl of a vector valued functions in the quadrature point mathbfx_q) is computed as mathbfnabla times mathbfu(mathbfx_q) = sumlimits_i = 1^n mathbfnabla N_i times (mathbfx_q) cdot mathbfu_i where mathbfu_i are the nodal values of the function.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.spatial_coordinate","page":"FEValues","title":"Ferrite.spatial_coordinate","text":"spatial_coordinate(fe_v::AbstractValues, q_point::Int, x::AbstractVector)\n\nCompute the spatial coordinate in a quadrature point. x contains the nodal coordinates of the cell.\n\nThe coordinate is computed, using the geometric interpolation, as mathbfx = sumlimits_i = 1^n M_i (mathbfxi) mathbfhatx_i.\n\nwhere xiis the coordinate of the given quadrature point q_point of the associated quadrature rule.\n\n\n\n\n\nspatial_coordinate(ip::ScalarInterpolation, ξ::Vec, x::AbstractVector{<:Vec{sdim, T}})\n\nCompute the spatial coordinate in a given quadrature point. x contains the nodal coordinates of the cell.\n\nThe coordinate is computed, using the geometric interpolation, as mathbfx = sumlimits_i = 1^n M_i (mathbfxi) mathbfhatx_i\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"In addition, there are some methods that are unique for FacetValues.","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"Ferrite.getcurrentfacet\ngetnormal","category":"page"},{"location":"reference/fevalues/#Ferrite.getcurrentfacet","page":"FEValues","title":"Ferrite.getcurrentfacet","text":"getcurrentfacet(fv::FacetValues)\n\nReturn the current active facet of the FacetValues object (from last reinit!).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.getnormal","page":"FEValues","title":"Ferrite.getnormal","text":"getnormal(fv::FacetValues, qp::Int)\n\nReturn the normal at the quadrature point qp for the active facet of the FacetValues object(from last reinit!).\n\n\n\n\n\ngetnormal(iv::InterfaceValues, qp::Int; here::Bool=true)\n\nReturn the normal vector in the quadrature point qp on the interface. If here = true (default) the outward normal to the \"here\" element is returned, otherwise the outward normal to the \"there\" element.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#reference-interfacevalues","page":"FEValues","title":"InterfaceValues","text":"","category":"section"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"All of the methods for FacetValues apply for InterfaceValues as well. In addition, there are some methods that are unique for InterfaceValues:","category":"page"},{"location":"reference/fevalues/","page":"FEValues","title":"FEValues","text":"InterfaceValues\nshape_value_average\nshape_value_jump\nshape_gradient_average\nshape_gradient_jump\nfunction_value_average\nfunction_value_jump\nfunction_gradient_average\nfunction_gradient_jump","category":"page"},{"location":"reference/fevalues/#Ferrite.InterfaceValues","page":"FEValues","title":"Ferrite.InterfaceValues","text":"InterfaceValues\n\nAn InterfaceValues object facilitates the process of evaluating values, averages, jumps and gradients of shape functions and function on the interfaces between elements.\n\nThe first element of the interface is denoted \"here\" and the second element \"there\".\n\nConstructors\n\nInterfaceValues(qr::FacetQuadratureRule, ip::Interpolation): same quadrature rule and interpolation on both sides, default linear Lagrange geometric interpolation.\nInterfaceValues(qr::FacetQuadratureRule, ip::Interpolation, ip_geo::Interpolation): same as above but with given geometric interpolation.\nInterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation): different quadrature rule and interpolation on the two sides, default linear Lagrange geometric interpolation.\nInterfaceValues(qr_here::FacetQuadratureRule, ip_here::Interpolation, ip_geo_here::Interpolation, qr_there::FacetQuadratureRule, ip_there::Interpolation, ip_geo_there::Interpolation): same as above but with given geometric interpolation.\nInterfaceValues(fv::FacetValues): quadrature rule and interpolations from face values (same on both sides).\nInterfaceValues(fv_here::FacetValues, fv_there::FacetValues): quadrature rule and interpolations from the face values.\n\nAssociated methods:\n\nshape_value_average\nshape_value_jump\nshape_gradient_average\nshape_gradient_jump\n\nCommon methods:\n\nreinit!\ngetnquadpoints\ngetdetJdV\nshape_value\nshape_gradient\nshape_divergence\nshape_curl\nfunction_value\nfunction_gradient\nfunction_symmetric_gradient\nfunction_divergence\nfunction_curl\nspatial_coordinate\n\n\n\n\n\n","category":"type"},{"location":"reference/fevalues/#Ferrite.shape_value_average","page":"FEValues","title":"Ferrite.shape_value_average","text":"shape_value_average(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the average of the value of shape function i at quadrature point qp across the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_value_jump","page":"FEValues","title":"Ferrite.shape_value_jump","text":"shape_value_jump(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the jump of the value of shape function i at quadrature point qp across the interface in the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere cdot vecn^textthere + vecv^texthere cdot vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_gradient_average","page":"FEValues","title":"Ferrite.shape_gradient_average","text":"shape_gradient_average(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the average of the gradient of shape function i at quadrature point qp across the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.shape_gradient_jump","page":"FEValues","title":"Ferrite.shape_gradient_jump","text":"shape_gradient_jump(iv::InterfaceValues, qp::Int, i::Int)\n\nCompute the jump of the gradient of shape function i at quadrature point qp across the interface in the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere vecn^textthere + vecv^texthere vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_value_average","page":"FEValues","title":"Ferrite.function_value_average","text":"function_value_average(iv::InterfaceValues, q_point::Int, u)\nfunction_value_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the average of the function value at the quadrature point on the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_value_jump","page":"FEValues","title":"Ferrite.function_value_jump","text":"function_value_jump(iv::InterfaceValues, q_point::Int, u)\nfunction_value_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the jump of the function value at the quadrature point over the interface along the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere vecn^textthere + vecv^texthere vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_gradient_average","page":"FEValues","title":"Ferrite.function_gradient_average","text":"function_gradient_average(iv::InterfaceValues, q_point::Int, u)\nfunction_gradient_average(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the average of the function gradient at the quadrature point on the interface.\n\n\n\n\n\n","category":"function"},{"location":"reference/fevalues/#Ferrite.function_gradient_jump","page":"FEValues","title":"Ferrite.function_gradient_jump","text":"function_gradient_jump(iv::InterfaceValues, q_point::Int, u)\nfunction_gradient_jump(iv::InterfaceValues, q_point::Int, u, dof_range_here, dof_range_there)\n\nCompute the jump of the function gradient at the quadrature point over the interface along the default normal direction.\n\nThis function uses the definition llbracket vecv rrbracket=vecv^textthere -vecv^texthere. To obtain the form, llbracket vecv rrbracket=vecv^textthere vecn^textthere + vecv^texthere vecn^texthere, multiply by minus the outward facing normal to the first element's side of the interface (which is the default normal for getnormal with InterfaceValues).\n\n\n\n\n\n","category":"function"},{"location":"reference/assembly/","page":"Assembly","title":"Assembly","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/assembly/#Assembly","page":"Assembly","title":"Assembly","text":"","category":"section"},{"location":"reference/assembly/","page":"Assembly","title":"Assembly","text":"start_assemble\nassemble!\nfinish_assemble","category":"page"},{"location":"reference/assembly/#Ferrite.start_assemble","page":"Assembly","title":"Ferrite.start_assemble","text":"start_assemble(K::AbstractSparseMatrixCSC; fillzero::Bool=true) -> CSCAssembler\nstart_assemble(K::AbstractSparseMatrixCSC, f::Vector; fillzero::Bool=true) -> CSCAssembler\n\nCreate a CSCAssembler from the matrix K and optional vector f.\n\nstart_assemble(K::Symmetric{AbstractSparseMatrixCSC}; fillzero::Bool=true) -> SymmetricCSCAssembler\nstart_assemble(K::Symmetric{AbstractSparseMatrixCSC}, f::Vector=Td[]; fillzero::Bool=true) -> SymmetricCSCAssembler\n\nCreate a SymmetricCSCAssembler from the matrix K and optional vector f.\n\nCSCAssembler and SymmetricCSCAssembler allocate workspace necessary for efficient matrix assembly. To assemble the contribution from an element, use assemble!.\n\nThe keyword argument fillzero can be set to false if K and f should not be zeroed out, but instead keep their current values.\n\n\n\n\n\n","category":"function"},{"location":"reference/assembly/#Ferrite.assemble!","page":"Assembly","title":"Ferrite.assemble!","text":"assemble!(a::COOAssembler, dofs, Ke)\nassemble!(a::COOAssembler, dofs, Ke, fe)\n\nAssembles the element matrix Ke and element vector fe into a.\n\n\n\n\n\nassemble!(a::COOAssembler, rowdofs, coldofs, Ke)\n\nAssembles the matrix Ke into a according to the dofs specified by rowdofs and coldofs.\n\n\n\n\n\nassemble!(g, dofs, ge)\n\nAssembles the element residual ge into the global residual vector g.\n\n\n\n\n\nassemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix)\nassemble!(A::AbstractAssembler, dofs::AbstractVector{Int}, Ke::AbstractMatrix, fe::AbstractVector)\n\nAssemble the element stiffness matrix Ke (and optional force vector fe) into the global stiffness (and force) in A, given the element degrees of freedom dofs.\n\nThis is equivalent to K[dofs, dofs] += Ke and f[dofs] += fe, where K is the global stiffness matrix and f the global force/residual vector, but more efficient.\n\n\n\n\n\n","category":"function"},{"location":"reference/assembly/#Ferrite.finish_assemble","page":"Assembly","title":"Ferrite.finish_assemble","text":"finish_assemble(a::COOAssembler) -> K, f\n\nFinalize the assembly and return the sparse matrix K::SparseMatrixCSC and vector f::Vector. If the assembler have not been used for vector assembly, f is an empty vector.\n\n\n\n\n\n","category":"function"},{"location":"devdocs/performance/#devdocs-performance","page":"Performance analysis","title":"Performance analysis","text":"","category":"section"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"In the benchmark folder we provide basic infrastructure to analyze the performance of Ferrite to help tracking down performance regression issues. Two basic tools can be directly executed via make: A basic benchmark for the current branch and a comparison between two commits. To execute the benchmark on the current branch only open a shell in the benchmark folder and call","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"make benchmark","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"whereas for the comparison of two commits simply call","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"make compare target= baseline=","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"If you have a custom julia executable that is not accessible via the julia command, then you can pass the executable via","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"JULIA_CMD= make compare target= baseline=","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"note: Note\nFor the performance comparison between two commits you must not have any uncommitted or untracked files in your Ferrite.jl folder! Otherwise the PkgBenchmark.jl will fail to setup the comparison.","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"For more fine grained control you can run subsets of the benchmarks via by appending - to compare or benchmark, e.g.","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"make benchmark-mesh","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"to benchmark only the mesh functionality. The following subsets are currently available:","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"assembly\nboundary-conditions\ndofs\nmesh","category":"page"},{"location":"devdocs/performance/","page":"Performance analysis","title":"Performance analysis","text":"note: Note\nIt is recommended to run all benchmarks before running subsets to get the correct tuning parameters for each benchmark.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"EditURL = \"../literate-gallery/topology_optimization.jl\"","category":"page"},{"location":"gallery/topology_optimization/#tutorial-topology-optimization","page":"Topology optimization","title":"Topology optimization","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Keywords: Topology optimization, weak and strong form, non-linear problem, Laplacian, grid topology","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"(Image: )","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Figure 1: Optimization of the bending beam. Evolution of the density for fixed total mass.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"tip: Tip\nThis example is also available as a Jupyter notebook: topology_optimization.ipynb.","category":"page"},{"location":"gallery/topology_optimization/#Introduction","page":"Topology optimization","title":"Introduction","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Topology optimization is the task of finding structures that are mechanically ideal. In this example we cover the bending beam, where we specify a load, boundary conditions and the total mass. Then, our objective is to find the most suitable geometry within the design space minimizing the compliance (i.e. the inverse stiffness) of the structure. We shortly introduce our simplified model for regular meshes. A detailed derivation of the method and advanced techniques can be found in [14] and [15].","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We start by introducing the local, elementwise density chi in chi_textmin 1 of the material, where we choose chi_textmin slightly above zero to prevent numerical instabilities. Here, chi = chi_textmin means void and chi=1 means bulk material. Then, we use a SIMP ansatz (solid isotropic material with penalization) for the stiffness tensor C(chi) = chi^p C_0, where C_0 is the stiffness of the bulk material. The SIMP exponent p1 ensures that the model prefers the density values void and bulk before the intermediate values. The variational formulation then yields the modified Gibbs energy","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"G = int_Omega frac12 chi^p varepsilon C varepsilon textdV - int_Omega boldsymbolf cdot boldsymbolu textdV - int_partialOmega boldsymbolt cdot boldsymbolu textdA","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Furthermore, we receive the evolution equation of the density and the additional Neumann boundary condition in the strong form","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"p_chi + eta dotchi + lambda + gamma - beta nabla^2 chi ni 0 quad forall textbfx in Omega","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"beta nabla chi cdot textbfn = 0 quad forall textbfx in partial Omega","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"with the thermodynamic driving force","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"p_chi = frac12 p chi^p-1 varepsilon C varepsilon","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We obtain the mechanical displacement field by applying the Finite Element Method to the weak form of the Gibbs energy using Ferrite. In contrast, we use the evolution equation (i.e. the strong form) to calculate the value of the density field chi. The advantage of this \"split\" approach is the very high computation speed. The evolution equation consists of the driving force, the damping parameter eta, the regularization parameter beta times the Laplacian, which is necessary to avoid numerical issues like mesh dependence or checkerboarding, and the constraint parameters lambda, to keep the mass constant, and gamma, to avoid leaving the set chi_textmin 1. By including gradient regularization, it becomes necessary to calculate the Laplacian. The Finite Difference Method for square meshes with the edge length Delta h approximates the Laplacian as follows:","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"nabla^2 chi_p = frac1(Delta h)^2 (chi_n + chi_s + chi_w + chi_e - 4 chi_p)","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Here, the indices refer to the different cardinal directions. Boundary element do not have neighbors in each direction. However, we can calculate the central difference to fulfill Neumann boundary condition. For example, if the element is on the left boundary, we have to fulfill","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"nabla chi_p cdot textbfn = frac1Delta h (chi_w - chi_e) = 0","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"from which follows chi_w = chi_e. Thus for boundary elements we can replace the value for the missing neighbor by the value of the opposite neighbor. In order to find the corresponding neighbor elements, we will make use of Ferrites grid topology funcionalities.","category":"page"},{"location":"gallery/topology_optimization/#Commented-Program","page":"Topology optimization","title":"Commented Program","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We now solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"First we load all necessary packages.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Next, we create a simple square grid of the size 2x1. We apply a fixed Dirichlet boundary condition to the left facet set, called clamped. On the right facet, we create a small set traction, where we will later apply a force in negative y-direction.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function create_grid(n)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((2.0, 0.0)),\n Vec{2}((2.0, 1.0)),\n Vec{2}((0.0, 1.0)),\n ]\n grid = generate_grid(Quadrilateral, (2 * n, n), corners)\n\n # node-/facesets for boundary conditions\n addnodeset!(grid, \"clamped\", x -> x[1] ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05)\n return grid\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Next, we create the FE values, the DofHandler and the Dirichlet boundary condition.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function create_values()\n # quadrature rules\n qr = QuadratureRule{RefQuadrilateral}(2)\n facet_qr = FacetQuadratureRule{RefQuadrilateral}(2)\n\n # cell and facetvalues for u\n ip = Lagrange{RefQuadrilateral, 1}()^2\n cellvalues = CellValues(qr, ip)\n facetvalues = FacetValues(facet_qr, ip)\n\n return cellvalues, facetvalues\nend\n\nfunction create_dofhandler(grid)\n dh = DofHandler(grid)\n add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement\n close!(dh)\n return dh\nend\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getnodeset(dh.grid, \"clamped\"), (x, t) -> zero(Vec{2}), [1, 2]))\n close!(dbc)\n t = 0.0\n update!(dbc, t)\n return dbc\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Now, we define a struct to store all necessary material parameters (stiffness tensor of the bulk material and the parameters for topology optimization) and add a constructor to the struct to initialize it by using the common material parameters Young's modulus and Poisson number.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"struct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}}\n C::S\n χ_min::T\n p::T\n β::T\n η::T\nend\nnothing # hide\n\nfunction MaterialParameters(E, ν, χ_min, p, β, η)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n\n G = E / 2(1 + ν) # =μ\n λ = E * ν / (1 - ν^2) # correction for plane stress included\n\n C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)))\n return MaterialParameters(C, χ_min, p, β, η)\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"To store the density and the strain required to calculate the driving forces, we create the struct MaterialState. We add a constructor to initialize the struct. The function update_material_states! updates the density values once we calculated the new values.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"mutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}}\n χ::T # density\n ε::S # strain in each quadrature point\nend\n\nfunction MaterialState(ρ, n_qp)\n return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp))\nend\n\nfunction update_material_states!(χn1, states, dh)\n for (element, state) in zip(CellIterator(dh), states)\n state.χ = χn1[cellid(element)]\n end\n return\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Next, we define a function to calculate the driving forces for all elements. For this purpose, we iterate through all elements and calculate the average strain in each element. Then, we compute the driving force from the formula introduced at the beginning. We create a second function to collect the density in each element.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function compute_driving_forces(states, mp, dh, χn)\n pΨ = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n ε = sum(state.ε) / length(state.ε) # average element strain\n pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε)\n end\n return pΨ\nend\n\nfunction compute_densities(states, dh)\n χn = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n χn[i] = state.χ\n end\n return χn\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"For the Laplacian we need some neighboorhood information which is constant throughout the analysis so we compute it once and cache it. We iterate through each facet of each element, obtaining the neighboring element by using the getneighborhood function. For boundary facets, the function call will return an empty object. In that case we use the dictionary to instead find the opposite facet, as discussed in the introduction.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function cache_neighborhood(dh, topology)\n nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid))\n _nfacets = nfacets(dh.grid.cells[1])\n opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2)\n\n for element in CellIterator(dh)\n nbg = zeros(Int, _nfacets)\n i = cellid(element)\n for j in 1:_nfacets\n nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))\n if !isempty(nbg_cellid)\n nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell\n else # boundary facet\n nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]\n end\n end\n\n nbgs[i] = nbg\n end\n\n return nbgs\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Now we calculate the Laplacian using the previously cached neighboorhood information.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function approximate_laplacian(nbgs, χn, Δh)\n ∇²χ = zeros(length(nbgs))\n for i in 1:length(nbgs)\n nbg = nbgs[i]\n ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2)\n end\n\n return ∇²χ\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"For the iterative computation of the solution, a function is needed to update the densities in each element. To ensure that the mass is kept constant, we have to calculate the constraint parameter lambda, which we do via the bisection method. We repeat the calculation until the difference between the average density (calculated from the element-wise trial densities) and the target density nearly vanishes. By using the extremal values of Delta chi as the starting interval, we guarantee that the method converges eventually.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function compute_χn1(χn, Δχ, ρ, ηs, χ_min)\n n_el = length(χn)\n\n χ_trial = zeros(n_el)\n ρ_trial = 0.0\n\n λ_lower = minimum(Δχ) - ηs\n λ_upper = maximum(Δχ) + ηs\n λ_trial = 0.0\n\n while abs(ρ - ρ_trial) > 1.0e-7\n for i in 1:n_el\n Δχt = 1 / ηs * (Δχ[i] - λ_trial)\n χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))\n end\n\n ρ_trial = 0.0\n for i in 1:n_el\n ρ_trial += χ_trial[i] / n_el\n end\n\n if ρ_trial > ρ\n λ_lower = λ_trial\n elseif ρ_trial < ρ\n λ_upper = λ_trial\n end\n λ_trial = 1 / 2 * (λ_upper + λ_lower)\n end\n\n return χ_trial\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Lastly, we use the following helper function to compute the average driving force, which is later used to normalize the driving forces. This makes the used material parameters and numerical parameters independent of the problem.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function compute_average_driving_force(mp, pΨ, χn)\n n = length(pΨ)\n w = zeros(n)\n\n for i in 1:n\n w[i] = (χn[i] - mp.χ_min) * (1 - χn[i])\n end\n\n p_Ω = sum(w .* pΨ) / sum(w) # average driving force\n\n return p_Ω\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Finally, we put everything together to update the density. The loop ensures the stability of the updated solution.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability\n χn = compute_densities(states, dh) # old density field\n χn1 = zeros(length(χn))\n\n for j in 1:n_j\n ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian\n pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces\n p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force\n\n Δχ = pΨ / p_Ω + mp.β * ∇²χ\n\n χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)\n\n if j < n_j\n χn[:] = χn1[:]\n end\n end\n\n return χn1\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Now, we move on to the Finite Element part of the program. We use the following function to assemble our linear system.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states)\n r = zeros(ndofs(dh))\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n\n re = zeros(nu) # local residual vector\n Ke = zeros(nu, nu) # local stiffness matrix\n\n for (element, state) in zip(CellIterator(dh), states)\n fill!(Ke, 0)\n fill!(re, 0)\n\n eldofs = celldofs(element)\n ue = u[eldofs]\n\n elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n assemble!(assembler, celldofs(element), Ke, re)\n end\n\n return K, r\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"The element routine is used to calculate the elementwise stiffness matrix and the residual. In contrast to a purely elastomechanic problem, for topology optimization we additionally use our material state to receive the density value of the element and to store the strain at each quadrature point.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, element)\n χ = state.χ\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n @inbounds for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue)\n\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:i\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ\n end\n re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ\n end\n end\n\n symmetrize_lower!(Ke)\n\n @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))\n if (cellid(element), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues, element, facet)\n t = Vec((0.0, -1.0)) # force pointing downwards\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We put everything together in the main function. Here the user may choose the radius parameter, which is related to the regularization parameter as beta = ra^2, the starting density, the number of elements in vertical direction and finally the name of the output. Additionally, the user may choose whether only the final design (default) or every iteration step is saved.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"First, we compute the material parameters and create the grid, DofHandler, boundary condition and FE values. Then we prepare the iterative Newton-Raphson method by pre-allocating all important vectors. Furthermore, we create material states for each element and construct the topology of the grid.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"During each iteration step, first we solve our FE problem in the Newton-Raphson loop. With the solution of the elastomechanic problem, we check for convergence of our topology design. The criteria has to be fulfilled twice in a row to avoid oscillations. If no convergence is reached yet, we update our design and prepare the next iteration step. Finally, we output the results in paraview and calculate the relative stiffness of the final design, i.e. how much how the stiffness increased compared to the starting point.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"function topopt(ra, ρ, n, filename; output = :false)\n # material\n mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0)\n\n # grid, dofhandler, boundary condition\n grid = create_grid(n)\n dh = create_dofhandler(grid)\n Δh = 1 / n # element edge length\n dbc = create_bc(dh)\n\n # cellvalues\n cellvalues, facetvalues = create_values()\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n un = zeros(n_dofs) # previous solution vector\n\n Δu = zeros(n_dofs) # previous displacement correction\n ΔΔu = zeros(n_dofs) # new displacement correction\n\n # create material states\n states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)]\n\n χ = zeros(getncells(dh.grid))\n\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # stiffness matrix\n\n i_max = 300 ## maximum number of iteration steps\n tol = 1.0e-4\n compliance = 0.0\n compliance_0 = 0.0\n compliance_n = 0.0\n conv = :false\n\n topology = ExclusiveTopology(grid)\n neighboorhoods = cache_neighborhood(dh, topology)\n\n # Newton-Raphson loop\n NEWTON_TOL = 1.0e-8\n print(\"\\n Starting Newton iterations\\n\")\n\n for it in 1:i_max\n apply_zero!(u, dbc)\n newton_itr = -1\n\n while true\n newton_itr += 1\n\n if newton_itr > 10\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n\n # current guess\n u .= un .+ Δu\n K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states)\n norm_r = norm(r[Ferrite.free_dofs(dbc)])\n\n if (norm_r) < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbc)\n ΔΔu = Symmetric(K) \\ r\n\n apply_zero!(ΔΔu, dbc)\n Δu .+= ΔΔu\n end # of loop while NR-Iteration\n\n # calculate compliance\n compliance = 1 / 2 * u' * K * u\n\n if it == 1\n compliance_0 = compliance\n end\n\n # check convergence criterium (twice!)\n if abs(compliance - compliance_n) / compliance < tol\n if conv\n println(\"Converged at iteration number: \", it)\n break\n else\n conv = :true\n end\n else\n conv = :false\n end\n\n # update density\n χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n\n # update old displacement, density and compliance\n un .= u\n Δu .= 0.0\n update_material_states!(χ, states, dh)\n compliance_n = compliance\n\n # output during calculation\n if output\n i = @sprintf(\"%3.3i\", it)\n filename_it = string(filename, \"_\", i)\n\n VTKGridFile(filename_it, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n end\n\n # export converged results\n if !output\n VTKGridFile(filename, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n @printf \"Rel. stiffness: %.4f \\n\" compliance^(-1) / compliance_0^(-1)\n\n return\nend\nnothing # hide","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Lastly, we call our main function and compare the results. To create the complete output with all iteration steps, it is possible to set the output parameter to true.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"grid, χ =topopt(0.02, 0.5, 60, \"small_radius\"; output=:false);","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"@time topopt(0.03, 0.5, 60, \"large_radius\"; output = :false);\n#topopt(0.02, 0.5, 60, \"topopt_animation\"; output=:true); # can be used to create animations","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"We observe, that the stiffness for the lower value of ra is higher, but also requires more iterations until convergence and finer structures to be manufactured, as can be seen in Figure 2:","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"(Image: )","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Figure 2: Optimization results of the bending beam for smaller (left) and larger (right) value of the regularization parameter beta.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"To prove mesh independence, the user could vary the mesh resolution and compare the results.","category":"page"},{"location":"gallery/topology_optimization/#References","page":"Topology optimization","title":"References","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"D. R. Jantos, K. Hackl and P. Junker. An accurate and fast regularization approach to thermodynamic topology optimization. International Journal for Numerical Methods in Engineering 117, 991–1017 (2019).\n\n\n\nM. Blaszczyk, D. R. Jantos and P. Junker. Application of Taylor series combined with the weighted least square method to thermodynamic topology optimization. Computer Methods in Applied Mechanics and Engineering 393, 114698 (2022).\n\n\n\n","category":"page"},{"location":"gallery/topology_optimization/#topology_optimization-plain-program","page":"Topology optimization","title":"Plain program","text":"","category":"section"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"Here follows a version of the program without any comments. The file is also available here: topology_optimization.jl.","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"using Ferrite, SparseArrays, LinearAlgebra, Tensors, Printf\n\nfunction create_grid(n)\n corners = [\n Vec{2}((0.0, 0.0)),\n Vec{2}((2.0, 0.0)),\n Vec{2}((2.0, 1.0)),\n Vec{2}((0.0, 1.0)),\n ]\n grid = generate_grid(Quadrilateral, (2 * n, n), corners)\n\n # node-/facesets for boundary conditions\n addnodeset!(grid, \"clamped\", x -> x[1] ≈ 0.0)\n addfacetset!(grid, \"traction\", x -> x[1] ≈ 2.0 && norm(x[2] - 0.5) <= 0.05)\n return grid\nend\n\nfunction create_values()\n # quadrature rules\n qr = QuadratureRule{RefQuadrilateral}(2)\n facet_qr = FacetQuadratureRule{RefQuadrilateral}(2)\n\n # cell and facetvalues for u\n ip = Lagrange{RefQuadrilateral, 1}()^2\n cellvalues = CellValues(qr, ip)\n facetvalues = FacetValues(facet_qr, ip)\n\n return cellvalues, facetvalues\nend\n\nfunction create_dofhandler(grid)\n dh = DofHandler(grid)\n add!(dh, :u, Lagrange{RefQuadrilateral, 1}()^2) # displacement\n close!(dh)\n return dh\nend\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getnodeset(dh.grid, \"clamped\"), (x, t) -> zero(Vec{2}), [1, 2]))\n close!(dbc)\n t = 0.0\n update!(dbc, t)\n return dbc\nend\n\nstruct MaterialParameters{T, S <: SymmetricTensor{4, 2, T}}\n C::S\n χ_min::T\n p::T\n β::T\n η::T\nend\n\nfunction MaterialParameters(E, ν, χ_min, p, β, η)\n δ(i, j) = i == j ? 1.0 : 0.0 # helper function\n\n G = E / 2(1 + ν) # =μ\n λ = E * ν / (1 - ν^2) # correction for plane stress included\n\n C = SymmetricTensor{4, 2}((i, j, k, l) -> λ * δ(i, j) * δ(k, l) + G * (δ(i, k) * δ(j, l) + δ(i, l) * δ(j, k)))\n return MaterialParameters(C, χ_min, p, β, η)\nend\n\nmutable struct MaterialState{T, S <: AbstractArray{SymmetricTensor{2, 2, T, 3}, 1}}\n χ::T # density\n ε::S # strain in each quadrature point\nend\n\nfunction MaterialState(ρ, n_qp)\n return MaterialState(ρ, Array{SymmetricTensor{2, 2, Float64, 3}, 1}(undef, n_qp))\nend\n\nfunction update_material_states!(χn1, states, dh)\n for (element, state) in zip(CellIterator(dh), states)\n state.χ = χn1[cellid(element)]\n end\n return\nend\n\nfunction compute_driving_forces(states, mp, dh, χn)\n pΨ = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n ε = sum(state.ε) / length(state.ε) # average element strain\n pΨ[i] = 1 / 2 * mp.p * χn[i]^(mp.p - 1) * (ε ⊡ mp.C ⊡ ε)\n end\n return pΨ\nend\n\nfunction compute_densities(states, dh)\n χn = zeros(length(states))\n for (element, state) in zip(CellIterator(dh), states)\n i = cellid(element)\n χn[i] = state.χ\n end\n return χn\nend\n\nfunction cache_neighborhood(dh, topology)\n nbgs = Vector{Vector{Int}}(undef, getncells(dh.grid))\n _nfacets = nfacets(dh.grid.cells[1])\n opp = Dict(1 => 3, 2 => 4, 3 => 1, 4 => 2)\n\n for element in CellIterator(dh)\n nbg = zeros(Int, _nfacets)\n i = cellid(element)\n for j in 1:_nfacets\n nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i, j))\n if !isempty(nbg_cellid)\n nbg[j] = first(nbg_cellid)[1] # assuming only one facet neighbor per cell\n else # boundary facet\n nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i, opp[j])))[1]\n end\n end\n\n nbgs[i] = nbg\n end\n\n return nbgs\nend\n\nfunction approximate_laplacian(nbgs, χn, Δh)\n ∇²χ = zeros(length(nbgs))\n for i in 1:length(nbgs)\n nbg = nbgs[i]\n ∇²χ[i] = (χn[nbg[1]] + χn[nbg[2]] + χn[nbg[3]] + χn[nbg[4]] - 4 * χn[i]) / (Δh^2)\n end\n\n return ∇²χ\nend\n\nfunction compute_χn1(χn, Δχ, ρ, ηs, χ_min)\n n_el = length(χn)\n\n χ_trial = zeros(n_el)\n ρ_trial = 0.0\n\n λ_lower = minimum(Δχ) - ηs\n λ_upper = maximum(Δχ) + ηs\n λ_trial = 0.0\n\n while abs(ρ - ρ_trial) > 1.0e-7\n for i in 1:n_el\n Δχt = 1 / ηs * (Δχ[i] - λ_trial)\n χ_trial[i] = max(χ_min, min(1.0, χn[i] + Δχt))\n end\n\n ρ_trial = 0.0\n for i in 1:n_el\n ρ_trial += χ_trial[i] / n_el\n end\n\n if ρ_trial > ρ\n λ_lower = λ_trial\n elseif ρ_trial < ρ\n λ_upper = λ_trial\n end\n λ_trial = 1 / 2 * (λ_upper + λ_lower)\n end\n\n return χ_trial\nend\n\nfunction compute_average_driving_force(mp, pΨ, χn)\n n = length(pΨ)\n w = zeros(n)\n\n for i in 1:n\n w[i] = (χn[i] - mp.χ_min) * (1 - χn[i])\n end\n\n p_Ω = sum(w .* pΨ) / sum(w) # average driving force\n\n return p_Ω\nend\n\nfunction update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n n_j = Int(ceil(6 * mp.β / (mp.η * Δh^2))) # iterations needed for stability\n χn = compute_densities(states, dh) # old density field\n χn1 = zeros(length(χn))\n\n for j in 1:n_j\n ∇²χ = approximate_laplacian(neighboorhoods, χn, Δh) # Laplacian\n pΨ = compute_driving_forces(states, mp, dh, χn) # driving forces\n p_Ω = compute_average_driving_force(mp, pΨ, χn) # average driving force\n\n Δχ = pΨ / p_Ω + mp.β * ∇²χ\n\n χn1 = compute_χn1(χn, Δχ, ρ, mp.η, mp.χ_min)\n\n if j < n_j\n χn[:] = χn1[:]\n end\n end\n\n return χn1\nend\n\nfunction doassemble!(cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, grid::Grid, dh::DofHandler, mp::MaterialParameters, u, states)\n r = zeros(ndofs(dh))\n assembler = start_assemble(K, r)\n nu = getnbasefunctions(cellvalues)\n\n re = zeros(nu) # local residual vector\n Ke = zeros(nu, nu) # local stiffness matrix\n\n for (element, state) in zip(CellIterator(dh), states)\n fill!(Ke, 0)\n fill!(re, 0)\n\n eldofs = celldofs(element)\n ue = u[eldofs]\n\n elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n assemble!(assembler, celldofs(element), Ke, re)\n end\n\n return K, r\nend\n\nfunction elmt!(Ke, re, element, cellvalues, facetvalues, grid, mp, ue, state)\n n_basefuncs = getnbasefunctions(cellvalues)\n reinit!(cellvalues, element)\n χ = state.χ\n\n # We only assemble lower half triangle of the stiffness matrix and then symmetrize it.\n @inbounds for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n state.ε[q_point] = function_symmetric_gradient(cellvalues, q_point, ue)\n\n for i in 1:n_basefuncs\n δεi = shape_symmetric_gradient(cellvalues, q_point, i)\n for j in 1:i\n δεj = shape_symmetric_gradient(cellvalues, q_point, j)\n Ke[i, j] += (χ)^(mp.p) * (δεi ⊡ mp.C ⊡ δεj) * dΩ\n end\n re[i] += (-δεi ⊡ ((χ)^(mp.p) * mp.C ⊡ state.ε[q_point])) * dΩ\n end\n end\n\n symmetrize_lower!(Ke)\n\n @inbounds for facet in 1:nfacets(getcells(grid, cellid(element)))\n if (cellid(element), facet) ∈ getfacetset(grid, \"traction\")\n reinit!(facetvalues, element, facet)\n t = Vec((0.0, -1.0)) # force pointing downwards\n for q_point in 1:getnquadpoints(facetvalues)\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n re[i] += (δu ⋅ t) * dΓ\n end\n end\n end\n end\n return\nend\n\nfunction symmetrize_lower!(K)\n for i in 1:size(K, 1)\n for j in (i + 1):size(K, 1)\n K[i, j] = K[j, i]\n end\n end\n return\nend\n\nfunction topopt(ra, ρ, n, filename; output = :false)\n # material\n mp = MaterialParameters(210.0e3, 0.3, 1.0e-3, 3.0, ra^2, 15.0)\n\n # grid, dofhandler, boundary condition\n grid = create_grid(n)\n dh = create_dofhandler(grid)\n Δh = 1 / n # element edge length\n dbc = create_bc(dh)\n\n # cellvalues\n cellvalues, facetvalues = create_values()\n\n # Pre-allocate solution vectors, etc.\n n_dofs = ndofs(dh) # total number of dofs\n u = zeros(n_dofs) # solution vector\n un = zeros(n_dofs) # previous solution vector\n\n Δu = zeros(n_dofs) # previous displacement correction\n ΔΔu = zeros(n_dofs) # new displacement correction\n\n # create material states\n states = [MaterialState(ρ, getnquadpoints(cellvalues)) for _ in 1:getncells(dh.grid)]\n\n χ = zeros(getncells(dh.grid))\n\n r = zeros(n_dofs) # residual\n K = allocate_matrix(dh) # stiffness matrix\n\n i_max = 300 ## maximum number of iteration steps\n tol = 1.0e-4\n compliance = 0.0\n compliance_0 = 0.0\n compliance_n = 0.0\n conv = :false\n\n topology = ExclusiveTopology(grid)\n neighboorhoods = cache_neighborhood(dh, topology)\n\n # Newton-Raphson loop\n NEWTON_TOL = 1.0e-8\n print(\"\\n Starting Newton iterations\\n\")\n\n for it in 1:i_max\n apply_zero!(u, dbc)\n newton_itr = -1\n\n while true\n newton_itr += 1\n\n if newton_itr > 10\n error(\"Reached maximum Newton iterations, aborting\")\n break\n end\n\n # current guess\n u .= un .+ Δu\n K, r = doassemble!(cellvalues, facetvalues, K, grid, dh, mp, u, states)\n norm_r = norm(r[Ferrite.free_dofs(dbc)])\n\n if (norm_r) < NEWTON_TOL\n break\n end\n\n apply_zero!(K, r, dbc)\n ΔΔu = Symmetric(K) \\ r\n\n apply_zero!(ΔΔu, dbc)\n Δu .+= ΔΔu\n end # of loop while NR-Iteration\n\n # calculate compliance\n compliance = 1 / 2 * u' * K * u\n\n if it == 1\n compliance_0 = compliance\n end\n\n # check convergence criterium (twice!)\n if abs(compliance - compliance_n) / compliance < tol\n if conv\n println(\"Converged at iteration number: \", it)\n break\n else\n conv = :true\n end\n else\n conv = :false\n end\n\n # update density\n χ = update_density(dh, states, mp, ρ, neighboorhoods, Δh)\n\n # update old displacement, density and compliance\n un .= u\n Δu .= 0.0\n update_material_states!(χ, states, dh)\n compliance_n = compliance\n\n # output during calculation\n if output\n i = @sprintf(\"%3.3i\", it)\n filename_it = string(filename, \"_\", i)\n\n VTKGridFile(filename_it, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n end\n\n # export converged results\n if !output\n VTKGridFile(filename, grid) do vtk\n write_cell_data(vtk, χ, \"density\")\n end\n end\n @printf \"Rel. stiffness: %.4f \\n\" compliance^(-1) / compliance_0^(-1)\n\n return\nend\n\n@time topopt(0.03, 0.5, 60, \"large_radius\"; output = :false);\n#topopt(0.02, 0.5, 60, \"topopt_animation\"; output=:true); # can be used to create animations","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"","category":"page"},{"location":"gallery/topology_optimization/","page":"Topology optimization","title":"Topology optimization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"EditURL = \"../literate-gallery/helmholtz.jl\"","category":"page"},{"location":"gallery/helmholtz/#tutorial-helmholtz","page":"Helmholtz equation","title":"Helmholtz equation","text":"","category":"section"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"In this example, we want to solve a (variant of) of the Helmholtz equation. The example is inspired by an dealii step_7 on the standard square.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":" - Delta u + u = f","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"With boundary conditions given by","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"u = g_1 quad x in Gamma_1","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"and","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"n cdot nabla u = g_2 quad x in Gamma_2","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"Here Γ₁ is the union of the top and the right boundary of the square, while Γ₂ is the union of the bottom and the left boundary.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"(Image: )","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"We will use the following weak formulation:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"int_Omega nabla δu cdot nabla u dOmega\n+ int_Omega δu cdot u dOmega\n- int_Omega δu cdot f dOmega\n- int_Gamma_2 δu g_2 dGamma = 0 quad forall δu","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"where δu is a suitable test function that satisfies:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"δu = 0 quad x in Gamma_1","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"and u is a suitable function that satisfies:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"u = g_1 quad x in Gamma_1","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"The example highlights the following interesting features:","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"There are two kinds of boundary conditions, \"Dirichlet\" and \"Von Neumann\"\nThe example contains boundary integrals\nThe Dirichlet condition is imposed strongly and the Von Neumann condition is imposed weakly.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"using Ferrite\nusing Tensors\nusing SparseArrays\nusing LinearAlgebra\n\nconst ∇ = Tensors.gradient\nconst Δ = Tensors.hessian;\n\ngrid = generate_grid(Quadrilateral, (150, 150))\n\nip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\nqr_facet = FacetQuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\nfacetvalues = FacetValues(qr_facet, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh)","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"We will set things up, so that a known analytic solution is approximately reproduced. This is a good testing strategy for PDE codes and known as the method of manufactured solutions.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"function u_ana(x::Vec{2, T}) where {T}\n xs = (\n Vec{2}((-0.5, 0.5)),\n Vec{2}((-0.5, -0.5)),\n Vec{2}((0.5, -0.5)),\n )\n σ = 1 / 8\n s = zero(eltype(x))\n for i in 1:3\n s += exp(- norm(x - xs[i])^2 / σ^2)\n end\n return max(1.0e-15 * one(T), s) # Denormals, be gone\nend;\n\ndbcs = ConstraintHandler(dh)","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"The (strong) Dirichlet boundary condition can be handled automatically by the Ferrite library.","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"dbc = Dirichlet(:u, union(getfacetset(grid, \"top\"), getfacetset(grid, \"right\")), (x, t) -> u_ana(x))\nadd!(dbcs, dbc)\nclose!(dbcs)\nupdate!(dbcs, 0.0)\n\nK = allocate_matrix(dh);\n\nfunction doassemble(\n cellvalues::CellValues, facetvalues::FacetValues, K::SparseMatrixCSC, dh::DofHandler\n )\n b = 1.0\n f = zeros(ndofs(dh))\n assembler = start_assemble(K, f)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n\n fe = zeros(n_basefuncs) # Local force vector\n Ke = zeros(n_basefuncs, n_basefuncs) # Local stiffness mastrix\n\n for (cellcount, cell) in enumerate(CellIterator(dh))\n fill!(Ke, 0)\n fill!(fe, 0)\n coords = getcoordinates(cell)\n\n reinit!(cellvalues, cell)","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"First we derive the non boundary part of the variation problem from the destined solution u_ana","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"int_Omega nabla δu cdot nabla u dOmega\n+ int_Omega δu cdot u dOmega\n- int_Omega δu cdot f dOmega","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":" for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n coords_qp = spatial_coordinate(cellvalues, q_point, coords)\n f_true = -LinearAlgebra.tr(hessian(u_ana, coords_qp)) + u_ana(coords_qp)\n for i in 1:n_basefuncs\n δu = shape_value(cellvalues, q_point, i)\n ∇δu = shape_gradient(cellvalues, q_point, i)\n fe[i] += (δu * f_true) * dΩ\n for j in 1:n_basefuncs\n u = shape_value(cellvalues, q_point, j)\n ∇u = shape_gradient(cellvalues, q_point, j)\n Ke[i, j] += (∇δu ⋅ ∇u + δu * u) * dΩ\n end\n end\n end","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"Now we manually add the von Neumann boundary terms","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"int_Gamma_2 δu g_2 dGamma","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":" for facet in 1:nfacets(cell)\n if (cellcount, facet) ∈ getfacetset(grid, \"left\") ||\n (cellcount, facet) ∈ getfacetset(grid, \"bottom\")\n reinit!(facetvalues, cell, facet)\n for q_point in 1:getnquadpoints(facetvalues)\n coords_qp = spatial_coordinate(facetvalues, q_point, coords)\n n = getnormal(facetvalues, q_point)\n g_2 = gradient(u_ana, coords_qp) ⋅ n\n dΓ = getdetJdV(facetvalues, q_point)\n for i in 1:n_basefuncs\n δu = shape_value(facetvalues, q_point, i)\n fe[i] += (δu * g_2) * dΓ\n end\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend;\n\nK, f = doassemble(cellvalues, facetvalues, K, dh);\napply!(K, f, dbcs)\nu = Symmetric(K) \\ f;\n\nvtk = VTKGridFile(\"helmholtz\", dh)\nwrite_solution(vtk, dh, u)\nclose(vtk)\nprintln(\"Helmholtz successful\")","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"","category":"page"},{"location":"gallery/helmholtz/","page":"Helmholtz equation","title":"Helmholtz equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"EditURL = \"../literate-tutorials/stokes-flow.jl\"","category":"page"},{"location":"tutorials/stokes-flow/#tutorial-stokes-flow","page":"Stokes flow","title":"Stokes flow","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Keywords: periodic boundary conditions, multiple fields, mean value constraint","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"tip: Tip\nThis example is also available as a Jupyter notebook: stokes-flow.ipynb.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"(Image: ) Figure 1: Left: Computational domain Omega with boundaries Gamma_1, Gamma_3 (periodic boundary conditions) and Gamma_2, Gamma_4 (homogeneous Dirichlet boundary conditions). Right: Magnitude of the resulting velocity field.","category":"page"},{"location":"tutorials/stokes-flow/#Introduction-and-problem-formulation","page":"Stokes flow","title":"Introduction and problem formulation","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"This example is a translation of the step-45 example from deal.ii which solves Stokes flow on a quarter circle. In particular it shows how to use periodic boundary conditions, how to solve a problem with multiple unknown fields, and how to enforce a specific mean value of the solution. For the mesh generation we use Gmsh.jl and then use FerriteGmsh.jl to import the mesh into Ferrite's format.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The strong form of Stokes flow with velocity boldsymbolu and pressure p can be written as follows:","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\n-Delta boldsymbolu + boldsymbolnabla p = bigl(exp(-100boldsymbolx - (075 01)^2) 0bigr) =\nboldsymbolb quad forall boldsymbolx in Omega\n-boldsymbolnabla cdot boldsymbolu = 0 quad forall boldsymbolx in Omega\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"where the domain is defined as Omega = boldsymbolx in (0 1)^2 boldsymbolx in (05 1), see Figure 1. For the velocity we use periodic boundary conditions on the inlet Gamma_1 and outlet Gamma_3:","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\nu_x(0nu) = -u_y(nu 0) quad nu in 05 1\nu_y(0nu) = u_x(nu 0) quad nu in 05 1\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"and homogeneous Dirichlet boundary conditions for Gamma_2 and Gamma_4:","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"boldsymbolu = boldsymbol0 quad forall boldsymbolx in\nGamma_2 cup Gamma_4 = boldsymbolx boldsymbolx in 05 1","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The corresponding weak form reads as follows: Find (boldsymbolu p) in mathbbU times mathrmL_2 s.t.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\nint_Omega Bigldeltaboldsymboluotimesboldsymbolnablaboldsymboluotimesboldsymbolnabla -\n(boldsymbolnablacdotdeltaboldsymbolu) p Bigr mathrmdOmega =\nint_Omega deltaboldsymbolu cdot boldsymbolb mathrmdOmega quad forall\ndelta boldsymbolu in mathbbU\nint_Omega - (boldsymbolnablacdotboldsymbolu) delta p mathrmdOmega = 0\nquad forall delta p in mathrmL_2\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"where mathbbU is a suitable function space, that, in particular, enforces the Dirichlet boundary conditions, and the periodicity constraints. This formulation is a saddle point problem, and, just like the example with Incompressible Elasticity, we need our formulation to fulfill the LBB condition. We ensure this by using a quadratic approximation for the velocity field, and a linear approximation for the pressure.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"With this formulation and boundary conditions for boldsymbolu the pressure will only be determined up to a constant. We will therefore add an additional constraint which fixes this constant (see deal.ii step-11 for some more discussion around this). In particular, we will enforce the mean value of the pressure on the boundary to be 0, i.e. int_Gamma p mathrmdGamma = 0. One option is to enforce this using a Lagrange multiplier. This would give a contribution lambda int_Gamma delta p mathrmdGamma to the second equation in the weak form above, and a third equation deltalambda int_Gamma p mathrmdGamma = 0 so that we can solve for lambda. However, since we in this case are not interested in computing lambda, and since the constraint is linear, we can directly embed this constraint using an AffineConstraint in Ferrite.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"After FE discretization we obtain a linear system of the form underlineunderlineK underlinea = underlinef, where","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"underlineunderlineK =\nbeginbmatrix\nunderlineunderlineK_uu underlineunderlineK_pu^textrmT \nunderlineunderlineK_pu underlineunderline0\nendbmatrix quad\nunderlinea = beginbmatrix\nunderlinea_u \nunderlinea_p\nendbmatrix quad\nunderlinef = beginbmatrix\nunderlinef_u \nunderline0\nendbmatrix","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"and where","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"beginalign*\n(underlineunderlineK_uu)_ij = int_Omega boldsymbolphi^u_iotimesboldsymbolnablaboldsymbolphi^u_jotimesboldsymbolnabla mathrmdOmega \n(underlineunderlineK_pu)_ij = int_Omega - (boldsymbolnablacdotboldsymbolphi^u_j) phi^p_i mathrmdOmega \n(underlinef_u)_i = int_Omega boldsymbolphi^u_i cdot boldsymbolb mathrmdOmega\nendalign*","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The affine constraint to enforce zero mean pressure on the boundary is obtained from underlineunderlineC_p underlinea_p = underline0, where","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"(underlineunderlineC_p)_1j = int_Gamma phi^p_j mathrmdGamma","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"note: Note\nThe constraint matrix underlineunderlineC_p is the same matrix we would have obtained when assembling the system with the Lagrange multiplier. In that case the full system would beunderlineunderlineK =\nbeginbmatrix\nunderlineunderlineK_uu underlineunderlineK_pu^textrmT \nunderlineunderline0\nunderlineunderlineK_pu underlineunderline0 underlineunderlineC_p^mathrmT \nunderlineunderline0 underlineunderlineC_p 0 \nendbmatrix quad\nunderlinea = beginbmatrix\nunderlinea_u \nunderlinea_p \nunderlinea_lambda\nendbmatrix quad\nunderlinef = beginbmatrix\nunderlinef_u \nunderline0 \nunderline0\nendbmatrix","category":"page"},{"location":"tutorials/stokes-flow/#Commented-program","page":"Stokes flow","title":"Commented program","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays","category":"page"},{"location":"tutorials/stokes-flow/#Geometry-and-mesh-generation-with-Gmsh.jl","page":"Stokes flow","title":"Geometry and mesh generation with Gmsh.jl","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"In the setup_grid function below we use the Gmsh.jl package for setting up the geometry and performing the meshing. We will not discuss this part in much detail but refer to the Gmsh API documentation instead. The most important thing to note is the mesh periodicity constraint that is applied between the \"inlet\" and \"outlet\" parts using gmsh.model.set_periodic. This is necessary to later on apply a periodicity constraint for the approximated velocity field.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_grid(h = 0.05)\n # Initialize gmsh\n Gmsh.initialize()\n gmsh.option.set_number(\"General.Verbosity\", 2)\n\n # Add the points\n o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h)\n p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h)\n p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h)\n p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h)\n p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h)\n\n # Add the lines\n l1 = gmsh.model.geo.add_line(p1, p2)\n l2 = gmsh.model.geo.add_circle_arc(p2, o, p3)\n l3 = gmsh.model.geo.add_line(p3, p4)\n l4 = gmsh.model.geo.add_circle_arc(p4, o, p1)\n\n # Create the closed curve loop and the surface\n loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4])\n surf = gmsh.model.geo.add_plane_surface([loop])\n\n # Synchronize the model\n gmsh.model.geo.synchronize()\n\n # Create the physical domains\n gmsh.model.add_physical_group(1, [l1], -1, \"Γ1\")\n gmsh.model.add_physical_group(1, [l2], -1, \"Γ2\")\n gmsh.model.add_physical_group(1, [l3], -1, \"Γ3\")\n gmsh.model.add_physical_group(1, [l4], -1, \"Γ4\")\n gmsh.model.add_physical_group(2, [surf])\n\n # Add the periodicity constraint using 4x4 affine transformation matrix,\n # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations\n transformation_matrix = zeros(4, 4)\n transformation_matrix[1, 2] = 1 # -sin(-pi/2)\n transformation_matrix[2, 1] = -1 # cos(-pi/2)\n transformation_matrix[3, 3] = 1\n transformation_matrix[4, 4] = 1\n transformation_matrix = vec(transformation_matrix')\n gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix)\n\n # Generate a 2D mesh\n gmsh.model.mesh.generate(2)\n\n # Save the mesh, and read back in as a Ferrite Grid\n grid = mktempdir() do dir\n path = joinpath(dir, \"mesh.msh\")\n gmsh.write(path)\n togrid(path)\n end\n\n # Finalize the Gmsh library\n Gmsh.finalize()\n\n return grid\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Degrees-of-freedom","page":"Stokes flow","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"As mentioned in the introduction we will use a quadratic approximation for the velocity field and a linear approximation for the pressure to ensure that we fulfill the LBB condition. We create the corresponding FE values with interpolations ipu for the velocity and ipp for the pressure. Note that we specify linear geometric mapping (ipg) for both the velocity and pressure because our grid contains linear triangles. However, since linear mapping is default this could have been skipped. We also construct facet-values for the pressure since we need to integrate along the boundary when assembling the constraint matrix underlineunderlineC.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_fevalues(ipu, ipp, ipg)\n qr = QuadratureRule{RefTriangle}(2)\n cvu = CellValues(qr, ipu, ipg)\n cvp = CellValues(qr, ipp, ipg)\n qr_facet = FacetQuadratureRule{RefTriangle}(2)\n fvp = FacetValues(qr_facet, ipp, ipg)\n return cvu, cvp, fvp\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The setup_dofs function creates the DofHandler, and adds the two fields: a vector field :u with interpolation ipu, and a scalar field :p with interpolation ipp.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_dofs(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu)\n add!(dh, :p, ipp)\n close!(dh)\n return dh\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Boundary-conditions-and-constraints","page":"Stokes flow","title":"Boundary conditions and constraints","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Now it is time to setup the ConstraintHandler and add our boundary conditions and the mean value constraint. This is perhaps the most interesting section in this example, and deserves some attention.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Let's first discuss the assembly of the constraint matrix underlineunderlineC and how to create an AffineConstraint from it. This is done in the setup_mean_constraint function below. Assembling this is not so different from standard assembly in Ferrite: we loop over all the facets, loop over the quadrature points, and loop over the shape functions. Note that since there is only one constraint the matrix will only have one row. After assembling C we construct an AffineConstraint from it. We select the constrained dof to be the one with the highest weight (just to avoid selecting one with 0 or a very small weight), then move the remaining to the right hand side. As an example, consider the case where the constraint equation underlineunderlineC_p underlinea_p is","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"w_10 p_10 + w_23 p_23 + w_154 p_154 = 0","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"i.e. dofs 10, 23, and 154, are the ones located on the boundary (all other dofs naturally gives 0 contribution). If w_23 is the largest weight, then we select p_23 to be the constrained one, and thus reorder the constraint to the form","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"p_23 = -fracw_10w_23 p_10 -fracw_154w_23 p_154 + 0","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"which is the form the AffineConstraint constructor expects.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"note: Note\nIf all nodes along the boundary are equidistant all the weights would be the same. In this case we can construct the constraint without having to do any integration by simply finding all degrees of freedom that are located along the boundary (and using 1 as the weight). This is what is done in the deal.ii step-11 example.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_mean_constraint(dh, fvp)\n assembler = Ferrite.COOAssembler()\n # All external boundaries\n set = union(\n getfacetset(dh.grid, \"Γ1\"),\n getfacetset(dh.grid, \"Γ2\"),\n getfacetset(dh.grid, \"Γ3\"),\n getfacetset(dh.grid, \"Γ4\"),\n )\n # Allocate buffers\n range_p = dof_range(dh, :p)\n element_dofs = zeros(Int, ndofs_per_cell(dh))\n element_dofs_p = view(element_dofs, range_p)\n element_coords = zeros(Vec{2}, 3)\n Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row)\n # Loop over all the boundaries\n for (ci, fi) in set\n Ce .= 0\n getcoordinates!(element_coords, dh.grid, ci)\n reinit!(fvp, element_coords, fi)\n celldofs!(element_dofs, dh, ci)\n for qp in 1:getnquadpoints(fvp)\n dΓ = getdetJdV(fvp, qp)\n for i in 1:getnbasefunctions(fvp)\n Ce[1, i] += shape_value(fvp, qp, i) * dΓ\n end\n end\n # Assemble to row 1\n assemble!(assembler, [1], element_dofs_p, Ce)\n end\n C, _ = finish_assemble(assembler)\n # Create an AffineConstraint from the C-matrix\n _, J, V = findnz(C)\n _, constrained_dof_idx = findmax(abs2, V)\n constrained_dof = J[constrained_dof_idx]\n V ./= V[constrained_dof_idx]\n mean_value_constraint = AffineConstraint(\n constrained_dof,\n Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof],\n 0.0,\n )\n return mean_value_constraint\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"We now setup all the boundary conditions in the setup_constraints function below. Since the periodicity constraint for this example is between two boundaries which are not parallel to each other we need to i) compute the mapping between each mirror facet and the corresponding image facet (on the element level) and ii) describe the dof relation between dofs on these two facets. In Ferrite this is done by defining a transformation of entities on the image boundary such that they line up with the matching entities on the mirror boundary. In this example we consider the inlet Gamma_1 to be the image, and the outlet Gamma_3 to be the mirror. The necessary transformation to apply then becomes a rotation of pi2 radians around the out-of-plane axis. We set up the rotation matrix R, and then compute the mapping between mirror and image facets using collect_periodic_facets where the rotation is applied to the coordinates. In the next step we construct the constraint using the PeriodicDirichlet constructor. We pass the constructor the computed mapping, and also the rotation matrix. This matrix is used to rotate the dofs on the mirror surface such that we properly constrain boldsymbolu_x-dofs on the mirror to -boldsymbolu_y-dofs on the image, and boldsymbolu_y-dofs on the mirror to boldsymbolu_x-dofs on the image.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"For the remaining part of the boundary we add a homogeneous Dirichlet boundary condition on both components of the velocity field. This is done using the Dirichlet constructor, which we have discussed in other tutorials.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function setup_constraints(dh, fvp)\n ch = ConstraintHandler(dh)\n # Periodic BC\n R = rotation_tensor(π / 2)\n periodic_faces = collect_periodic_facets(dh.grid, \"Γ3\", \"Γ1\", x -> R ⋅ x)\n periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2])\n add!(ch, periodic)\n # Dirichlet BC\n Γ24 = union(getfacetset(dh.grid, \"Γ2\"), getfacetset(dh.grid, \"Γ4\"))\n dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2])\n add!(ch, dbc)\n # Compute mean value constraint and add it\n mean_value_constraint = setup_mean_constraint(dh, fvp)\n add!(ch, mean_value_constraint)\n # Finalize\n close!(ch)\n update!(ch, 0)\n return ch\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Global-and-local-assembly","page":"Stokes flow","title":"Global and local assembly","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Assembly of the global system is also something that we have seen in many previous tutorials. One interesting thing to note here is that, since we have two unknown fields, we use the dof_range function to make sure we assemble the element contributions to the correct block of the local stiffness matrix ke.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function assemble_system!(K, f, dh, cvu, cvp)\n assembler = start_assemble(K, f)\n ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n fe = zeros(ndofs_per_cell(dh))\n range_u = dof_range(dh, :u)\n ndofs_u = length(range_u)\n range_p = dof_range(dh, :p)\n ndofs_p = length(range_p)\n ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u)\n ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u)\n divϕᵤ = Vector{Float64}(undef, ndofs_u)\n ϕₚ = Vector{Float64}(undef, ndofs_p)\n for cell in CellIterator(dh)\n reinit!(cvu, cell)\n reinit!(cvp, cell)\n ke .= 0\n fe .= 0\n for qp in 1:getnquadpoints(cvu)\n dΩ = getdetJdV(cvu, qp)\n for i in 1:ndofs_u\n ϕᵤ[i] = shape_value(cvu, qp, i)\n ∇ϕᵤ[i] = shape_gradient(cvu, qp, i)\n divϕᵤ[i] = shape_divergence(cvu, qp, i)\n end\n for i in 1:ndofs_p\n ϕₚ[i] = shape_value(cvp, qp, i)\n end\n # u-u\n for (i, I) in pairs(range_u), (j, J) in pairs(range_u)\n ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ\n end\n # u-p\n for (i, I) in pairs(range_u), (j, J) in pairs(range_p)\n ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ\n end\n # p-u\n for (i, I) in pairs(range_p), (j, J) in pairs(range_u)\n ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ\n end\n # rhs\n for (i, I) in pairs(range_u)\n x = spatial_coordinate(cvu, qp, getcoordinates(cell))\n b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2)\n bv = Vec{2}((b, 0.0))\n fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ\n end\n end\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n return K, f\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/#Running-the-simulation","page":"Stokes flow","title":"Running the simulation","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"We now have all the puzzle pieces, and just need to define the main function, which puts them all together.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"function main()\n # Grid\n h = 0.05 # approximate element size\n grid = setup_grid(h)\n # Interpolations\n ipu = Lagrange{RefTriangle, 2}()^2 # quadratic\n ipp = Lagrange{RefTriangle, 1}() # linear\n # Dofs\n dh = setup_dofs(grid, ipu, ipp)\n # FE values\n ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation\n cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg)\n # Boundary conditions\n ch = setup_constraints(dh, fvp)\n # Global tangent matrix and rhs\n coupling = [true true; true false] # no coupling between pressure test/trial functions\n K = allocate_matrix(dh, ch; coupling = coupling)\n f = zeros(ndofs(dh))\n # Assemble system\n assemble_system!(K, f, dh, cvu, cvp)\n # Apply boundary conditions and solve\n apply!(K, f, ch)\n u = K \\ f\n apply!(u, ch)\n # Export the solution\n VTKGridFile(\"stokes-flow\", grid) do vtk\n write_solution(vtk, dh, u)\n end\n\n\n return\nend\nnothing #hide","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Run it!","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"main()","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"The resulting magnitude of the velocity field is visualized in Figure 1.","category":"page"},{"location":"tutorials/stokes-flow/#stokes-flow-plain-program","page":"Stokes flow","title":"Plain program","text":"","category":"section"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"Here follows a version of the program without any comments. The file is also available here: stokes-flow.jl.","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"using Ferrite, FerriteGmsh, Gmsh, Tensors, LinearAlgebra, SparseArrays\n\nfunction setup_grid(h = 0.05)\n # Initialize gmsh\n Gmsh.initialize()\n gmsh.option.set_number(\"General.Verbosity\", 2)\n\n # Add the points\n o = gmsh.model.geo.add_point(0.0, 0.0, 0.0, h)\n p1 = gmsh.model.geo.add_point(0.5, 0.0, 0.0, h)\n p2 = gmsh.model.geo.add_point(1.0, 0.0, 0.0, h)\n p3 = gmsh.model.geo.add_point(0.0, 1.0, 0.0, h)\n p4 = gmsh.model.geo.add_point(0.0, 0.5, 0.0, h)\n\n # Add the lines\n l1 = gmsh.model.geo.add_line(p1, p2)\n l2 = gmsh.model.geo.add_circle_arc(p2, o, p3)\n l3 = gmsh.model.geo.add_line(p3, p4)\n l4 = gmsh.model.geo.add_circle_arc(p4, o, p1)\n\n # Create the closed curve loop and the surface\n loop = gmsh.model.geo.add_curve_loop([l1, l2, l3, l4])\n surf = gmsh.model.geo.add_plane_surface([loop])\n\n # Synchronize the model\n gmsh.model.geo.synchronize()\n\n # Create the physical domains\n gmsh.model.add_physical_group(1, [l1], -1, \"Γ1\")\n gmsh.model.add_physical_group(1, [l2], -1, \"Γ2\")\n gmsh.model.add_physical_group(1, [l3], -1, \"Γ3\")\n gmsh.model.add_physical_group(1, [l4], -1, \"Γ4\")\n gmsh.model.add_physical_group(2, [surf])\n\n # Add the periodicity constraint using 4x4 affine transformation matrix,\n # see https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations\n transformation_matrix = zeros(4, 4)\n transformation_matrix[1, 2] = 1 # -sin(-pi/2)\n transformation_matrix[2, 1] = -1 # cos(-pi/2)\n transformation_matrix[3, 3] = 1\n transformation_matrix[4, 4] = 1\n transformation_matrix = vec(transformation_matrix')\n gmsh.model.mesh.set_periodic(1, [l1], [l3], transformation_matrix)\n\n # Generate a 2D mesh\n gmsh.model.mesh.generate(2)\n\n # Save the mesh, and read back in as a Ferrite Grid\n grid = mktempdir() do dir\n path = joinpath(dir, \"mesh.msh\")\n gmsh.write(path)\n togrid(path)\n end\n\n # Finalize the Gmsh library\n Gmsh.finalize()\n\n return grid\nend\n\nfunction setup_fevalues(ipu, ipp, ipg)\n qr = QuadratureRule{RefTriangle}(2)\n cvu = CellValues(qr, ipu, ipg)\n cvp = CellValues(qr, ipp, ipg)\n qr_facet = FacetQuadratureRule{RefTriangle}(2)\n fvp = FacetValues(qr_facet, ipp, ipg)\n return cvu, cvp, fvp\nend\n\nfunction setup_dofs(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu)\n add!(dh, :p, ipp)\n close!(dh)\n return dh\nend\n\nfunction setup_mean_constraint(dh, fvp)\n assembler = Ferrite.COOAssembler()\n # All external boundaries\n set = union(\n getfacetset(dh.grid, \"Γ1\"),\n getfacetset(dh.grid, \"Γ2\"),\n getfacetset(dh.grid, \"Γ3\"),\n getfacetset(dh.grid, \"Γ4\"),\n )\n # Allocate buffers\n range_p = dof_range(dh, :p)\n element_dofs = zeros(Int, ndofs_per_cell(dh))\n element_dofs_p = view(element_dofs, range_p)\n element_coords = zeros(Vec{2}, 3)\n Ce = zeros(1, length(range_p)) # Local constraint matrix (only 1 row)\n # Loop over all the boundaries\n for (ci, fi) in set\n Ce .= 0\n getcoordinates!(element_coords, dh.grid, ci)\n reinit!(fvp, element_coords, fi)\n celldofs!(element_dofs, dh, ci)\n for qp in 1:getnquadpoints(fvp)\n dΓ = getdetJdV(fvp, qp)\n for i in 1:getnbasefunctions(fvp)\n Ce[1, i] += shape_value(fvp, qp, i) * dΓ\n end\n end\n # Assemble to row 1\n assemble!(assembler, [1], element_dofs_p, Ce)\n end\n C, _ = finish_assemble(assembler)\n # Create an AffineConstraint from the C-matrix\n _, J, V = findnz(C)\n _, constrained_dof_idx = findmax(abs2, V)\n constrained_dof = J[constrained_dof_idx]\n V ./= V[constrained_dof_idx]\n mean_value_constraint = AffineConstraint(\n constrained_dof,\n Pair{Int, Float64}[J[i] => -V[i] for i in 1:length(J) if J[i] != constrained_dof],\n 0.0,\n )\n return mean_value_constraint\nend\n\nfunction setup_constraints(dh, fvp)\n ch = ConstraintHandler(dh)\n # Periodic BC\n R = rotation_tensor(π / 2)\n periodic_faces = collect_periodic_facets(dh.grid, \"Γ3\", \"Γ1\", x -> R ⋅ x)\n periodic = PeriodicDirichlet(:u, periodic_faces, R, [1, 2])\n add!(ch, periodic)\n # Dirichlet BC\n Γ24 = union(getfacetset(dh.grid, \"Γ2\"), getfacetset(dh.grid, \"Γ4\"))\n dbc = Dirichlet(:u, Γ24, (x, t) -> [0, 0], [1, 2])\n add!(ch, dbc)\n # Compute mean value constraint and add it\n mean_value_constraint = setup_mean_constraint(dh, fvp)\n add!(ch, mean_value_constraint)\n # Finalize\n close!(ch)\n update!(ch, 0)\n return ch\nend\n\nfunction assemble_system!(K, f, dh, cvu, cvp)\n assembler = start_assemble(K, f)\n ke = zeros(ndofs_per_cell(dh), ndofs_per_cell(dh))\n fe = zeros(ndofs_per_cell(dh))\n range_u = dof_range(dh, :u)\n ndofs_u = length(range_u)\n range_p = dof_range(dh, :p)\n ndofs_p = length(range_p)\n ϕᵤ = Vector{Vec{2, Float64}}(undef, ndofs_u)\n ∇ϕᵤ = Vector{Tensor{2, 2, Float64, 4}}(undef, ndofs_u)\n divϕᵤ = Vector{Float64}(undef, ndofs_u)\n ϕₚ = Vector{Float64}(undef, ndofs_p)\n for cell in CellIterator(dh)\n reinit!(cvu, cell)\n reinit!(cvp, cell)\n ke .= 0\n fe .= 0\n for qp in 1:getnquadpoints(cvu)\n dΩ = getdetJdV(cvu, qp)\n for i in 1:ndofs_u\n ϕᵤ[i] = shape_value(cvu, qp, i)\n ∇ϕᵤ[i] = shape_gradient(cvu, qp, i)\n divϕᵤ[i] = shape_divergence(cvu, qp, i)\n end\n for i in 1:ndofs_p\n ϕₚ[i] = shape_value(cvp, qp, i)\n end\n # u-u\n for (i, I) in pairs(range_u), (j, J) in pairs(range_u)\n ke[I, J] += (∇ϕᵤ[i] ⊡ ∇ϕᵤ[j]) * dΩ\n end\n # u-p\n for (i, I) in pairs(range_u), (j, J) in pairs(range_p)\n ke[I, J] += (-divϕᵤ[i] * ϕₚ[j]) * dΩ\n end\n # p-u\n for (i, I) in pairs(range_p), (j, J) in pairs(range_u)\n ke[I, J] += (-divϕᵤ[j] * ϕₚ[i]) * dΩ\n end\n # rhs\n for (i, I) in pairs(range_u)\n x = spatial_coordinate(cvu, qp, getcoordinates(cell))\n b = exp(-100 * norm(x - Vec{2}((0.75, 0.1)))^2)\n bv = Vec{2}((b, 0.0))\n fe[I] += (ϕᵤ[i] ⋅ bv) * dΩ\n end\n end\n assemble!(assembler, celldofs(cell), ke, fe)\n end\n return K, f\nend\n\nfunction main()\n # Grid\n h = 0.05 # approximate element size\n grid = setup_grid(h)\n # Interpolations\n ipu = Lagrange{RefTriangle, 2}()^2 # quadratic\n ipp = Lagrange{RefTriangle, 1}() # linear\n # Dofs\n dh = setup_dofs(grid, ipu, ipp)\n # FE values\n ipg = Lagrange{RefTriangle, 1}() # linear geometric interpolation\n cvu, cvp, fvp = setup_fevalues(ipu, ipp, ipg)\n # Boundary conditions\n ch = setup_constraints(dh, fvp)\n # Global tangent matrix and rhs\n coupling = [true true; true false] # no coupling between pressure test/trial functions\n K = allocate_matrix(dh, ch; coupling = coupling)\n f = zeros(ndofs(dh))\n # Assemble system\n assemble_system!(K, f, dh, cvu, cvp)\n # Apply boundary conditions and solve\n apply!(K, f, ch)\n u = K \\ f\n apply!(u, ch)\n # Export the solution\n VTKGridFile(\"stokes-flow\", grid) do vtk\n write_solution(vtk, dh, u)\n end\n\n\n return\nend\n\nmain()","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"","category":"page"},{"location":"tutorials/stokes-flow/","page":"Stokes flow","title":"Stokes flow","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/#Reference","page":"Reference overview","title":"Reference","text":"","category":"section"},{"location":"reference/","page":"Reference overview","title":"Reference overview","text":"Pages = [\n \"quadrature.md\",\n \"interpolations.md\",\n \"fevalues.md\",\n \"dofhandler.md\",\n \"assembly.md\",\n \"boundary_conditions.md\",\n \"grid.md\",\n \"export.md\",\n \"utils.md\",\n]","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"EditURL = \"../literate-tutorials/reactive_surface.jl\"","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"if isdefined(Main, :is_ci) #hide\n IS_CI = Main.is_ci #hide\nelse #hide\n IS_CI = false #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/#tutorial-reactive-surface","page":"Reactive surface","title":"Reactive surface","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"(Image: )","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Figure 1: Reactant concentration field of the Gray-Scott model on the unit sphere.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"tip: Tip\nThis example is also available as a Jupyter notebook: reactive_surface.ipynb.","category":"page"},{"location":"tutorials/reactive_surface/#Introduction","page":"Reactive surface","title":"Introduction","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"This tutorial gives a quick tutorial on how to assemble and solve time-dependent problems on embedded surfaces.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"For this showcase we use the well known Gray-Scott model, which is a well-known reaction-diffusion system to study pattern formation. The strong form is given by","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" beginaligned\n partial_t r_1 = nabla cdot (D_1 nabla r_1) - r_1*r_2^2 + F *(1 - r_1) quad textbfx in Omega \n partial_t r_2 = nabla cdot (D_2 nabla r_2) + r_1*r_2^2 - r_2*(F + k ) quad textbfx in Omega\n endaligned","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"where r_1 and r_2 are the reaction fields, D_1 and D_2 the diffusion tensors, k is the conversion rate, F is the feed rate and Omega the domain. Depending on the choice of parameters a different pattern can be observed. Please also note that the domain does not have a boundary. The corresponding weak form can be derived as usual.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"For simplicity we will solve the problem with the Lie-Troter-Godunov operator splitting technique with the classical reaction-diffusion split. In this method we split our problem in two problems, i.e. a heat problem and a pointwise reaction problem, and solve them alternatingly to advance in time.","category":"page"},{"location":"tutorials/reactive_surface/#Solver-details","page":"Reactive surface","title":"Solver details","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"The main idea for the Lie-Trotter-Godunov scheme is simple. We can write down the reaction diffusion problem in an abstract way as","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" partial_t mathbfr = mathcalDmathbfr + R(mathbfr) quad textbfx in Omega","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"where mathcalD is the diffusion operator and R is the reaction operator. Notice that the right hand side is just the sum of two operators. Now with our operator splitting scheme we can advance a solution mathbfr(t_1) to mathbfr(t_2) by first solving a heat problem","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" partial_t mathbfr^mathrmmathrmA = mathcalDmathbfr^mathrmA quad textbfx in Omega","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"with mathbfr^mathrmA(t_1) = mathbfr(t_1) on the time interval t_1 to t_2 and use the solution as the initial condition to solve the reaction problem","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":" partial_t mathbfr^mathrmB = R(mathbfr^mathrmB) quad textbfx in Omega","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"with mathbfr^mathrmB(t_1) = mathbfr^mathrmA(t_2). This way we obtain a solution approximation mathbfr(t_2) approx mathbfr^mathrmB(t_2).","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"note: Note\nThe operator splitting itself is an approximation, so even if we solve the subproblems analytically we end up with having only a solution approximation. We also do not have a beginner friendly reference for the theory behind operator splitting and can only refer to the original papers for each method.","category":"page"},{"location":"tutorials/reactive_surface/#Commented-Program","page":"Reactive surface","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"First we load Ferrite, and some other packages we need","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"using Ferrite, FerriteGmsh\nusing BlockArrays, SparseArrays, LinearAlgebra, WriteVTK","category":"page"},{"location":"tutorials/reactive_surface/#Assembly-routines","page":"Reactive surface","title":"Assembly routines","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Before we head into the assembly, we define a helper struct to control the dispatches.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"struct GrayScottMaterial{T}\n D₁::T\n D₂::T\n F::T\n k::T\nend;\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"The following assembly routines are written analogue to these found in previous tutorials.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function assemble_element_mass!(Me::Matrix, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n Me₁ = @view Me[r₁range, r₁range]\n Me₂ = @view Me[r₂range, r₂range]\n # Reset to 0\n fill!(Me, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δuᵢ = shape_value(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n δuⱼ = shape_value(cellvalues, q_point, j)\n # Add contribution to Ke\n Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ\n Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n D₁ = material.D₁\n D₂ = material.D₂\n # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n De₁ = @view De[r₁range, r₁range]\n De₂ = @view De[r₂range, r₂range]\n # Reset to 0\n fill!(De, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n ∇δuᵢ = shape_gradient(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇δuⱼ = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n\n # Allocate the element stiffness matrix and element force vector\n Me = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n De = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n\n # Create an assembler\n M_assembler = start_assemble(M)\n D_assembler = start_assemble(D)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element_mass!(Me, cellvalues)\n assemble!(M_assembler, celldofs(cell), Me)\n\n assemble_element_diffusion!(De, cellvalues, material)\n assemble!(D_assembler, celldofs(cell), De)\n end\n return nothing\nend;\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/#Initial-condition-setup","page":"Reactive surface","title":"Initial condition setup","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Time-dependent problems always need an initial condition from which the time evolution starts. In this tutorial we set the concentration of reactant 1 to 1 and the concentration of reactant 2 to 0 for all nodal dof with associated coordinate z leq 09 on the sphere. Since the simulation would be pretty boring with a steady-state initial condition, we introduce some heterogeneity by setting the dofs associated to top part of the sphere (i.e. dofs with z 09 to store the reactant concentrations of 05 and 025 for the reactants 1 and 2 respectively.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler)\n u₀ .= ones(ndofs(dh))\n u₀[2:2:end] .= 0.0\n\n n_basefuncs = getnbasefunctions(cellvalues)\n\n for cell in CellIterator(dh)\n reinit!(cellvalues, cell)\n\n coords = getcoordinates(cell)\n dofs = celldofs(cell)\n uₑ = @view u₀[dofs]\n rv₀ₑ = reshape(uₑ, (2, n_basefuncs))\n\n for i in 1:n_basefuncs\n if coords[i][3] > 0.9\n rv₀ₑ[1, i] = 0.5\n rv₀ₑ[2, i] = 0.25\n end\n end\n end\n\n u₀ .+= 0.01 * rand(ndofs(dh))\n return\nend;\nnothing #hide","category":"page"},{"location":"tutorials/reactive_surface/#Mesh-generation","page":"Reactive surface","title":"Mesh generation","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"In this section we define a routine to create a surface mesh with the help of FerriteGmsh.jl.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function create_embedded_sphere(refinements)\n gmsh.initialize()\n\n # Add a unit sphere in 3D space\n gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0)\n gmsh.model.occ.synchronize()\n\n # Generate nodes and surface elements only, hence we need to pass 2 into generate\n gmsh.model.mesh.generate(2)\n\n # To get good solution quality refine the elements several times\n for _ in 1:refinements\n gmsh.model.mesh.refine()\n end\n\n # Now we create a Ferrite grid out of it. Note that we also call toelements\n # with our surface element dimension to obtain these.\n nodes = tonodes()\n elements, _ = toelements(2)\n gmsh.finalize()\n return Grid(elements, nodes)\nend","category":"page"},{"location":"tutorials/reactive_surface/#Simulation-routines","page":"Reactive surface","title":"Simulation routines","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Now we define a function to setup and solve the problem with given feed and conversion rates F and k, as well as the time step length and for how long we want to solve the model.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)\n # We start by setting up grid, dof handler and the matrices for the heat problem.\n grid = create_embedded_sphere(refinements)\n\n # Next we are creating our element assembly helper for surface elements.\n # The only change which we need to introduce here is to pass in a geometrical\n # interpolation with the same dimension as the physical space into which our\n # elements are embedded into, which is in this example 3.\n ip = Lagrange{RefTriangle, 1}()\n qr = QuadratureRule{RefTriangle}(2)\n cellvalues = CellValues(qr, ip, ip^3)\n\n # We have two options to add the reactants to the dof handler, which will give us slightly\n # different resulting dof distributions:\n # A) We can add a scalar-valued interpolation for each reactant.\n # B) We can add one vectorized interpolation whose dimension is the number of reactants\n # number of reactants.\n # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or\n # to be specific for this tutorial, we use an isoparametric concept such that the nodes\n # of our grid and the nodes of our solution approximation coincide. This way a reaction\n # we can create simply reshape the solution vector u to a matrix where the inner index\n # corresponds to the index of the reactant. Note that we will still use the scalar\n # interpolation for the assembly procedure.\n dh = DofHandler(grid)\n add!(dh, :reactants, ip^2)\n close!(dh)\n\n # We can save some memory by telling the sparsity pattern that the matrices are not coupled.\n M = allocate_matrix(dh; coupling = [true false; false true])\n D = allocate_matrix(dh; coupling = [true false; false true])\n\n # Since the heat problem is linear and has no time dependent parameters, we precompute the\n # decomposition of the system matrix to speed up the linear system solver.\n assemble_matrices!(M, D, cellvalues, dh, material)\n A = M + Δt .* D\n cholA = cholesky(A)\n\n # Now we setup buffers for the time dependent solution and fill the initial condition.\n uₜ = zeros(ndofs(dh))\n uₜ₋₁ = ones(ndofs(dh))\n setup_initial_conditions!(uₜ₋₁, cellvalues, dh)\n\n # And prepare output for visualization.\n pvd = paraview_collection(\"reactive-surface\")\n VTKGridFile(\"reactive-surface-0\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[0.0] = vtk\n end\n\n # This is now the main solve loop.\n F = material.F\n k = material.k\n for (iₜ, t) in enumerate(Δt:Δt:T)\n # First we solve the heat problem\n uₜ .= cholA \\ (M * uₜ₋₁)\n\n # Then we solve the point-wise reaction problem with the solution of\n # the heat problem as initial guess. 2 is the number of reactants.\n num_individual_reaction_dofs = ndofs(dh) ÷ 2\n rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs))\n for i in 1:num_individual_reaction_dofs\n r₁ = rvₜ[1, i]\n r₂ = rvₜ[2, i]\n rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁))\n rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k))\n end\n\n # The solution is then stored every 10th step to vtk files for\n # later visualization purposes.\n if (iₜ % 10) == 0\n VTKGridFile(\"reactive-surface-$(iₜ)\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[t] = vtk\n end\n end\n\n # Finally we totate the solution to initialize the next timestep.\n uₜ₋₁ .= uₜ\n end\n vtk_save(pvd)\n return\nend\n\n# This parametrization gives the spot pattern shown in the gif above.\nmaterial = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)\n gray_scott_on_sphere(material, 10.0, 32000.0, 3)","category":"page"},{"location":"tutorials/reactive_surface/#reactive_surface-plain-program","page":"Reactive surface","title":"Plain program","text":"","category":"section"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"Here follows a version of the program without any comments. The file is also available here: reactive_surface.jl.","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"using Ferrite, FerriteGmsh\nusing BlockArrays, SparseArrays, LinearAlgebra, WriteVTK\n\nstruct GrayScottMaterial{T}\n D₁::T\n D₂::T\n F::T\n k::T\nend;\n\nfunction assemble_element_mass!(Me::Matrix, cellvalues::CellValues)\n n_basefuncs = getnbasefunctions(cellvalues)\n # The mass matrices between the reactions are not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n Me₁ = @view Me[r₁range, r₁range]\n Me₂ = @view Me[r₂range, r₂range]\n # Reset to 0\n fill!(Me, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n δuᵢ = shape_value(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n δuⱼ = shape_value(cellvalues, q_point, j)\n # Add contribution to Ke\n Me₁[i, j] += (δuᵢ * δuⱼ) * dΩ\n Me₂[i, j] += (δuᵢ * δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_element_diffusion!(De::Matrix, cellvalues::CellValues, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n D₁ = material.D₁\n D₂ = material.D₂\n # The diffusion between the reactions is not coupled, so we get a blocked-strided matrix.\n num_reactants = 2\n r₁range = 1:num_reactants:(num_reactants * n_basefuncs)\n r₂range = 2:num_reactants:(num_reactants * n_basefuncs)\n De₁ = @view De[r₁range, r₁range]\n De₂ = @view De[r₂range, r₂range]\n # Reset to 0\n fill!(De, 0)\n # Loop over quadrature points\n for q_point in 1:getnquadpoints(cellvalues)\n # Get the quadrature weight\n dΩ = getdetJdV(cellvalues, q_point)\n # Loop over test shape functions\n for i in 1:n_basefuncs\n ∇δuᵢ = shape_gradient(cellvalues, q_point, i)\n # Loop over trial shape functions\n for j in 1:n_basefuncs\n ∇δuⱼ = shape_gradient(cellvalues, q_point, j)\n # Add contribution to Ke\n De₁[i, j] += D₁ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n De₂[i, j] += D₂ * (∇δuᵢ ⋅ ∇δuⱼ) * dΩ\n end\n end\n end\n return nothing\nend\n\nfunction assemble_matrices!(M::SparseMatrixCSC, D::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler, material::GrayScottMaterial)\n n_basefuncs = getnbasefunctions(cellvalues)\n\n # Allocate the element stiffness matrix and element force vector\n Me = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n De = zeros(2 * n_basefuncs, 2 * n_basefuncs)\n\n # Create an assembler\n M_assembler = start_assemble(M)\n D_assembler = start_assemble(D)\n # Loop over all cels\n for cell in CellIterator(dh)\n # Reinitialize cellvalues for this cell\n reinit!(cellvalues, cell)\n # Compute element contribution\n assemble_element_mass!(Me, cellvalues)\n assemble!(M_assembler, celldofs(cell), Me)\n\n assemble_element_diffusion!(De, cellvalues, material)\n assemble!(D_assembler, celldofs(cell), De)\n end\n return nothing\nend;\n\nfunction setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::DofHandler)\n u₀ .= ones(ndofs(dh))\n u₀[2:2:end] .= 0.0\n\n n_basefuncs = getnbasefunctions(cellvalues)\n\n for cell in CellIterator(dh)\n reinit!(cellvalues, cell)\n\n coords = getcoordinates(cell)\n dofs = celldofs(cell)\n uₑ = @view u₀[dofs]\n rv₀ₑ = reshape(uₑ, (2, n_basefuncs))\n\n for i in 1:n_basefuncs\n if coords[i][3] > 0.9\n rv₀ₑ[1, i] = 0.5\n rv₀ₑ[2, i] = 0.25\n end\n end\n end\n\n u₀ .+= 0.01 * rand(ndofs(dh))\n return\nend;\n\nfunction create_embedded_sphere(refinements)\n gmsh.initialize()\n\n # Add a unit sphere in 3D space\n gmsh.model.occ.addSphere(0.0, 0.0, 0.0, 1.0)\n gmsh.model.occ.synchronize()\n\n # Generate nodes and surface elements only, hence we need to pass 2 into generate\n gmsh.model.mesh.generate(2)\n\n # To get good solution quality refine the elements several times\n for _ in 1:refinements\n gmsh.model.mesh.refine()\n end\n\n # Now we create a Ferrite grid out of it. Note that we also call toelements\n # with our surface element dimension to obtain these.\n nodes = tonodes()\n elements, _ = toelements(2)\n gmsh.finalize()\n return Grid(elements, nodes)\nend\n\nfunction gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)\n # We start by setting up grid, dof handler and the matrices for the heat problem.\n grid = create_embedded_sphere(refinements)\n\n # Next we are creating our element assembly helper for surface elements.\n # The only change which we need to introduce here is to pass in a geometrical\n # interpolation with the same dimension as the physical space into which our\n # elements are embedded into, which is in this example 3.\n ip = Lagrange{RefTriangle, 1}()\n qr = QuadratureRule{RefTriangle}(2)\n cellvalues = CellValues(qr, ip, ip^3)\n\n # We have two options to add the reactants to the dof handler, which will give us slightly\n # different resulting dof distributions:\n # A) We can add a scalar-valued interpolation for each reactant.\n # B) We can add one vectorized interpolation whose dimension is the number of reactants\n # number of reactants.\n # In this tutorial we opt for B, because the dofs are distributed per cell entity -- or\n # to be specific for this tutorial, we use an isoparametric concept such that the nodes\n # of our grid and the nodes of our solution approximation coincide. This way a reaction\n # we can create simply reshape the solution vector u to a matrix where the inner index\n # corresponds to the index of the reactant. Note that we will still use the scalar\n # interpolation for the assembly procedure.\n dh = DofHandler(grid)\n add!(dh, :reactants, ip^2)\n close!(dh)\n\n # We can save some memory by telling the sparsity pattern that the matrices are not coupled.\n M = allocate_matrix(dh; coupling = [true false; false true])\n D = allocate_matrix(dh; coupling = [true false; false true])\n\n # Since the heat problem is linear and has no time dependent parameters, we precompute the\n # decomposition of the system matrix to speed up the linear system solver.\n assemble_matrices!(M, D, cellvalues, dh, material)\n A = M + Δt .* D\n cholA = cholesky(A)\n\n # Now we setup buffers for the time dependent solution and fill the initial condition.\n uₜ = zeros(ndofs(dh))\n uₜ₋₁ = ones(ndofs(dh))\n setup_initial_conditions!(uₜ₋₁, cellvalues, dh)\n\n # And prepare output for visualization.\n pvd = paraview_collection(\"reactive-surface\")\n VTKGridFile(\"reactive-surface-0\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[0.0] = vtk\n end\n\n # This is now the main solve loop.\n F = material.F\n k = material.k\n for (iₜ, t) in enumerate(Δt:Δt:T)\n # First we solve the heat problem\n uₜ .= cholA \\ (M * uₜ₋₁)\n\n # Then we solve the point-wise reaction problem with the solution of\n # the heat problem as initial guess. 2 is the number of reactants.\n num_individual_reaction_dofs = ndofs(dh) ÷ 2\n rvₜ = reshape(uₜ, (2, num_individual_reaction_dofs))\n for i in 1:num_individual_reaction_dofs\n r₁ = rvₜ[1, i]\n r₂ = rvₜ[2, i]\n rvₜ[1, i] += Δt * (-r₁ * r₂^2 + F * (1 - r₁))\n rvₜ[2, i] += Δt * (r₁ * r₂^2 - r₂ * (F + k))\n end\n\n # The solution is then stored every 10th step to vtk files for\n # later visualization purposes.\n if (iₜ % 10) == 0\n VTKGridFile(\"reactive-surface-$(iₜ)\", dh) do vtk\n write_solution(vtk, dh, uₜ₋₁)\n pvd[t] = vtk\n end\n end\n\n # Finally we totate the solution to initialize the next timestep.\n uₜ₋₁ .= uₜ\n end\n vtk_save(pvd)\n return\nend\n\n# This parametrization gives the spot pattern shown in the gif above.\nmaterial = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)\n gray_scott_on_sphere(material, 10.0, 32000.0, 3)","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"","category":"page"},{"location":"tutorials/reactive_surface/","page":"Reactive surface","title":"Reactive surface","text":"This page was generated using Literate.jl.","category":"page"},{"location":"devdocs/special_datastructures/#Special-data-structures","page":"Special data structures","title":"Special data structures","text":"","category":"section"},{"location":"devdocs/special_datastructures/#ArrayOfVectorViews","page":"Special data structures","title":"ArrayOfVectorViews","text":"","category":"section"},{"location":"devdocs/special_datastructures/","page":"Special data structures","title":"Special data structures","text":"ArrayOfVectorViews is a data structure representing an Array of vector views (specifically SubArray{T, 1} where T). By arranging all data (of type T) continuously in memory, this will significantly reduce the garbage collection time compared to using an Array{AbstractVector{T}}. While the data in each view can be mutated, the length of each view is fixed after construction. This data structure provides two features not provided by ArraysOfArrays.jl: Support of matrices and higher order arrays for storing vectors of different dimensions and efficient construction when the number of elements in each view is not known in advance.","category":"page"},{"location":"devdocs/special_datastructures/","page":"Special data structures","title":"Special data structures","text":"Ferrite.ArrayOfVectorViews\nFerrite.ConstructionBuffer\nFerrite.push_at_index!","category":"page"},{"location":"devdocs/special_datastructures/#Ferrite.CollectionsOfViews.ArrayOfVectorViews","page":"Special data structures","title":"Ferrite.CollectionsOfViews.ArrayOfVectorViews","text":"ArrayOfVectorViews(f!::Function, data::Vector{T}, dims::NTuple{N, Int}; sizehint)\n\nCreate an ArrayOfVectorViews to store many vector views of potentially different sizes, emulating an Array{Vector{T}, N} with size dims. However, it avoids allocating each vector individually by storing all data in data, and instead of Vector{T}, the each element is a typeof(view(data, 1:2)).\n\nWhen the length of each vector is unknown, the ArrayOfVectorViews can be created reasonably efficient with the following do-block, which creates an intermediate buffer::ConstructionBuffer supporting the push_at_index! function.\n\nvector_views = ArrayOfVectorViews(data, dims; sizehint) do buffer\n for (ind, val) in some_data\n push_at_index!(buffer, val, ind)\n end\nend\n\nsizehint tells how much space to allocate for the index ind if no val has been added to that index before, or how much more space to allocate in case all previously allocated space for ind has been used up.\n\n\n\n\n\nArrayOfVectorViews(b::CollectionsOfViews.ConstructionBuffer)\n\nCreates the ArrayOfVectorViews directly from the ConstructionBuffer that was manually created and filled.\n\n\n\n\n\nArrayOfVectorViews(indices::Vector{Int}, data::Vector{T}, lin_idx::LinearIndices{N}; checkargs = true)\n\nCreates the ArrayOfVectorViews directly where the user is responsible for having the correct input data. Checking of the argument dimensions can be elided by setting checkargs = false, but incorrect dimensions may lead to illegal out of bounds access later.\n\ndata is indexed by indices[i]:indices[i+1], where i = lin_idx[idx...] and idx... are the user-provided indices to the ArrayOfVectorViews.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/special_datastructures/#Ferrite.CollectionsOfViews.ConstructionBuffer","page":"Special data structures","title":"Ferrite.CollectionsOfViews.ConstructionBuffer","text":"ConstructionBuffer(data::Vector, dims::NTuple{N, Int}, sizehint)\n\nCreate a buffer for creating an ArrayOfVectorViews, representing an array with N axes. sizehint sets the number of elements in data allocated when a new index is added via push_at_index!, or when the current storage for the index is full, how much many additional elements are reserved for that index. Any content in data is overwritten, but performance is improved by pre-allocating it to a reasonable size or by sizehint!ing it.\n\n\n\n\n\n","category":"type"},{"location":"devdocs/special_datastructures/#Ferrite.CollectionsOfViews.push_at_index!","page":"Special data structures","title":"Ferrite.CollectionsOfViews.push_at_index!","text":"push_at_index!(b::ConstructionBuffer, val, indices::Int...)\n\npush! the value val to the Vector view at the index given by indices, typically called inside the ArrayOfVectorViews constructor do-block. But can also be used when manually creating a ConstructionBuffer.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"EditURL = \"../literate-tutorials/ns_vs_diffeq.jl\"","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"if isdefined(Main, :is_ci) #hide\n IS_CI = Main.is_ci #hide\nelse #hide\n IS_CI = false #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#tutorial-ins-ordinarydiffeq","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"(Image: nsdiffeq)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"In this example we focus on a simple but visually appealing problem from fluid dynamics, namely vortex shedding. This problem is also known as von-Karman vortex streets. Within this example, we show how to utilize DifferentialEquations.jl in tandem with Ferrite to solve this space-time problem. To keep things simple we use a naive approach to discretize the system.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Remarks-on-DifferentialEquations.jl","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Remarks on DifferentialEquations.jl","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Required Version\nThis example will only work with OrdinaryDiffEq@v6.80.1. or above","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Many \"time step solvers\" of DifferentialEquations.jl assume that that the problem is provided in mass matrix form. The incompressible Navier-Stokes equations as stated above yield a DAE in this form after applying a spatial discretization technique - in our case FEM. The mass matrix form of ODEs and DAEs is given as:","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" M(t) mathrmd_t u = f(ut)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where M is a possibly time-dependent and not necessarily invertible mass matrix, u the vector of unknowns and f the right-hand-side (RHS). For us f can be interpreted as the spatial discretization of all linear and nonlinear operators depending on u and t, but not on the time derivative of u.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Some-theory-on-the-incompressible-Navier-Stokes-equations","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Some theory on the incompressible Navier-Stokes equations","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/#Problem-description-in-strong-form","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Problem description in strong form","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"The incompressible Navier-Stokes equations can be stated as the system","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" beginaligned\n partial_t v = underbracenu Delta v_textviscosity - underbrace(v cdot nabla) v_textadvection - underbracenabla p_textpressure \n 0 = underbracenabla cdot v_textincompressibility\n endaligned","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where v is the unknown velocity field, p the unknown pressure field, nu the dynamic viscosity and Delta the Laplacian. In the derivation we assumed a constant density of 1 for the fluid and negligible coupling between the velocity components.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Our setup is derived from Turek's DFG benchmark. We model a channel with size 041 times 11 and a hole of radius 005 centered at (02 02). The left side has a parabolic inflow profile, which is ramped up over time, modeled as the time dependent Dirichlet condition","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" v(xyt)\n =\n beginbmatrix\n 4 v_in(t) y (041-y)041^2 \n 0\n endbmatrix","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where v_in(t) = textclamp(t 00 15). With a dynamic viscosity of nu = 0001 this is enough to induce turbulence behind the cylinder which leads to vortex shedding. The top and bottom of our channel have no-slip conditions, i.e. v = 00^textrmT, while the right boundary has the do-nothing boundary condition nu partial_textrmn v - p n = 0 to model outflow. With these boundary conditions we can choose the zero solution as a feasible initial condition.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Derivation-of-Semi-Discrete-Weak-Form","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Derivation of Semi-Discrete Weak Form","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"By multiplying test functions varphi and psi from a suitable test function space on the strong form, followed by integrating over the domain and applying partial integration to the pressure and viscosity terms we can obtain the following weak form","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" beginaligned\n int_Omega partial_t v cdot varphi = - int_Omega nu nabla v nabla varphi - int_Omega (v cdot nabla) v cdot varphi + int_Omega p (nabla cdot varphi) + int_partial Omega_N underbrace(nu partial_n v - p n )_=0 cdot varphi \n 0 = int_Omega (nabla cdot v) psi\n endaligned","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"for all possible test functions from the suitable space.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we can discretize the problem as usual with the finite element method utilizing Taylor-Hood elements (Q2Q1) to yield a stable discretization in mass matrix form:","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" underbracebeginbmatrix\n M_v 0 \n 0 0\n endbmatrix_=M\n beginbmatrix\n mathrmd_thatv \n mathrmd_thatp\n endbmatrix\n =\n underbracebeginbmatrix\n A B^textrmT \n B 0\n endbmatrix_=K\n beginbmatrix\n hatv \n hatp\n endbmatrix\n +\n beginbmatrix\n N(hatv hatv hatvarphi) \n 0\n endbmatrix","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Here M is the singular block mass matrix, K is the discretized Stokes operator and N the nonlinear advection term, which is also called trilinear form. hatv and hatp represent the time-dependent vectors of nodal values of the discretizations of v and p respectively, while hatvarphi is the choice for the test function in the discretization. The hats are dropped in the implementation and only stated for clarity in this section.","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Commented-implementation","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Commented implementation","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we solve the problem with Ferrite and DifferentialEquations.jl. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"First we load Ferrite and some other packages we need","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Since we do not need the complete DifferentialEquations suite, we just load the required ODE infrastructure, which can also handle DAEs in mass matrix form.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using OrdinaryDiffEq","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We start off by defining our only material parameter.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"ν = 1.0 / 1000.0; #dynamic viscosity\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Next a rectangular grid with a cylinder in it has to be generated. We use Gmsh.jl for the creation of the mesh and FerriteGmsh.jl to translate it to a Ferrite.Grid. Note that the mesh is pretty fine, leading to a high memory consumption when feeding the equation system to direct solvers.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using FerriteGmsh\nusing FerriteGmsh: Gmsh\nGmsh.initialize()\ngmsh.option.set_number(\"General.Verbosity\", 2)\ndim = 2;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We specify first the rectangle, the cylinder, the surface spanned by the cylinder and the boolean difference of rectangle and cylinder.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"if !IS_CI #hide\n rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41)\n circle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05)\n circle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag])\n circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])\n gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])\nelse #hide\n rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now, the geometrical entities need to be synchronized in order to be available outside of gmsh.model.occ","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"gmsh.model.occ.synchronize()","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"In the next lines, we add the physical groups needed to define boundary conditions.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"if !IS_CI #hide\n bottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, \"bottom\")\n lefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, \"left\")\n righttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, \"right\")\n toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, \"top\")\n holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, \"hole\")\nelse #hide\n gmsh.model.model.add_physical_group(dim - 1, [4], 7, \"left\") #hide\n gmsh.model.model.add_physical_group(dim - 1, [3], 8, \"top\") #hide\n gmsh.model.model.add_physical_group(dim - 1, [2], 9, \"right\") #hide\n gmsh.model.model.add_physical_group(dim - 1, [1], 10, \"bottom\") #hide\nend #hide\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Since we want a quad mesh, we specify the meshing algorithm to the quasi structured quad one. For a complete list, see the Gmsh docs.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"gmsh.option.setNumber(\"Mesh.Algorithm\", 11)\ngmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20)\ngmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.05)\nif IS_CI #hide\n gmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20) #hide\n gmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.15) #hide\nend #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"In the next step, the mesh is generated and finally translated.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"gmsh.model.mesh.generate(dim)\ngrid = togrid()\nGmsh.finalize();\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Function-Space","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Function Space","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"To ensure stability we utilize the Taylor-Hood element pair Q2-Q1. We have to utilize the same quadrature rule for the pressure as for the velocity, because in the weak form the linear pressure term is tested against a quadratic function.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"ip_v = Lagrange{RefQuadrilateral, 2}()^dim\nqr = QuadratureRule{RefQuadrilateral}(4)\ncellvalues_v = CellValues(qr, ip_v);\n\nip_p = Lagrange{RefQuadrilateral, 1}()\ncellvalues_p = CellValues(qr, ip_p);\n\ndh = DofHandler(grid)\nadd!(dh, :v, ip_v)\nadd!(dh, :p, ip_p)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Boundary-conditions","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"As in the DFG benchmark we apply no-slip conditions to the top, bottom and cylinder boundary. The no-slip condition states that the velocity of the fluid on this portion of the boundary is fixed to be zero.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"ch = ConstraintHandler(dh);\n\nnosplip_facet_names = [\"top\", \"bottom\", \"hole\"];\nif IS_CI #hide\n nosplip_facet_names = [\"top\", \"bottom\"] #hide\nend #hide\n∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);\nnoslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])\nadd!(ch, noslip_bc);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"The left boundary has a parabolic inflow with peak velocity of 1.5. This ensures that for the given geometry the Reynolds number is 100, which is already enough to obtain some simple vortex streets. By increasing the velocity further we can obtain stronger vortices - which may need additional refinement of the grid.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"∂Ω_inflow = getfacetset(grid, \"left\");\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Note\nThe kink in the velocity profile will lead to a discontinuity in the pressure at t=1. This needs to be considered in the DiffEq init by providing the keyword argument d_discontinuities=[1.0].","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"vᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity\n\nparabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0))\ninflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2])\nadd!(ch, inflow_bc);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"The outflow boundary condition has been applied on the right side of the cylinder when the weak form has been derived by setting the boundary integral to zero. It is also called the do-nothing condition. Other outflow conditions are also possible.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"∂Ω_free = getfacetset(grid, \"right\");\n\nclose!(ch)\nupdate!(ch, 0.0);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Linear-System-Assembly","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Linear System Assembly","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Next we describe how the block mass matrix and the Stokes matrix are assembled.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"For the block mass matrix M we remember that only the first equation had a time derivative and that the block mass matrix corresponds to the term arising from discretizing the time derivatives. Hence, only the upper left block has non-zero components.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"function assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler)\n # Allocate a buffer for the local matrix and some helpers, together with the assembler.\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # It follows the assembly loop as explained in the basic tutorials.\n mass_assembler = start_assemble(M)\n for cell in CellIterator(dh)\n fill!(Mₑ, 0)\n Ferrite.reinit!(cellvalues_v, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n # Remember that we assemble a vector mass term, hence the dot product.\n # There is only one time derivative on the left hand side, so only one mass block is non-zero.\n for i in 1:n_basefuncs_v\n φᵢ = shape_value(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n φⱼ = shape_value(cellvalues_v, q_point, j)\n Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ\n end\n end\n end\n assemble!(mass_assembler, celldofs(cell), Mₑ)\n end\n\n return M\nend;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Next we discuss the assembly of the Stokes matrix appearing on the right hand side. Remember that we use the same function spaces for trial and test, hence the matrix has the following block form","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" K = beginbmatrix\n A B^textrmT \n B 0\n endbmatrix","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"which is also called saddle point matrix. These problems are known to have a non-trivial kernel, which is a reflection of the strong form as discussed in the theory portion if this example.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"function assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler)\n # Again, some buffers and helpers\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # Assembly loop\n stiffness_assembler = start_assemble(K)\n for cell in CellIterator(dh)\n # Don't forget to initialize everything\n fill!(Kₑ, 0)\n\n Ferrite.reinit!(cellvalues_v, cell)\n Ferrite.reinit!(cellvalues_p, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Assemble local viscosity block of A","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" for i in 1:n_basefuncs_v\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n ∇φⱼ = shape_gradient(cellvalues_v, q_point, j)\n Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ\n end\n end","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Assemble local pressure and incompressibility blocks of B^textrmT and B.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" for j in 1:n_basefuncs_p\n ψ = shape_value(cellvalues_p, q_point, j)\n for i in 1:n_basefuncs_v\n divφ = shape_divergence(cellvalues_v, q_point, i)\n Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ\n Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ\n end\n end\n end\n\n # Assemble `Kₑ` into the Stokes matrix `K`.\n assemble!(stiffness_assembler, celldofs(cell), Kₑ)\n end\n return K\nend;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#Solution-of-the-semi-discretized-system-via-DifferentialEquations.jl","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Solution of the semi-discretized system via DifferentialEquations.jl","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"First we assemble the linear portions for efficiency. These matrices are assumed to be constant over time.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Note\nTo obtain the vortex street a small time step is important to resolve the small oscillation forming. The mesh size becomes important to \"only\" resolve the smaller vertices forming, but less important for the initial formation.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"T = 6.0\nΔt₀ = 0.001\nif IS_CI #hide\n Δt₀ = 0.1 #hide\nend #hide\nΔt_save = 0.1\n\nM = allocate_matrix(dh);\nM = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh);\n\nK = allocate_matrix(dh);\nK = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"These are our initial conditions. We start from the zero solution, because it is trivially admissible if the Dirichlet conditions are zero everywhere on the Dirichlet boundary for t=0. Note that the time stepper is also doing fine if the Dirichlet condition is non-zero and not too pathological.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"u₀ = zeros(ndofs(dh))\napply!(u₀, ch);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"DifferentialEquations assumes dense matrices by default, which is not feasible for semi-discretization of finite element models. We communicate that a sparse matrix with specified pattern should be utilized through the jac_prototyp argument. It is simple to see that the Jacobian and the stiffness matrix share the same sparsity pattern, since they share the same relation between trial and test functions.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"jac_sparsity = sparse(K);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"To apply the nonlinear portion of the Navier-Stokes problem we simply hand over the dof handler and cell values to the right-hand-side (RHS) as a parameter. Furthermore the pre-assembled linear part, our Stokes opeartor (which is time independent) is passed to save some additional runtime. To apply the time-dependent Dirichlet BCs, we also need to hand over the constraint handler. The basic idea to apply the Dirichlet BCs consistently is that we copy the current solution u, apply the Dirichlet BCs on the copy, evaluate the discretized RHS of the Navier-Stokes equations with this vector. Furthermore we pass down the Jacobian assembly manually. For the Jacobian we eliminate all rows and columns associated with constrained dofs. Also note that we eliminate the mass matrix beforehand in a similar fashion. This decouples the time evolution of the constrained dofs from the true unknowns. The correct solution is enforced by utilizing step and stage limiters. The correct norms are computed by passing down a custom norm which simply ignores all constrained dofs.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Note\nAn alternative strategy is to hook into the nonlinear and linear solvers and enforce the solution therein. However, this is not possible at the time of writing this tutorial.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"apply!(M, ch)\n\nstruct RHSparams\n K::SparseMatrixCSC\n ch::ConstraintHandler\n dh::DofHandler\n cellvalues_v::CellValues\n u::Vector\nend\np = RHSparams(K, ch, dh, cellvalues_v, copy(u₀))\n\nfunction ferrite_limiter!(u, _, p, t)\n update!(p.ch, t)\n return apply!(u, p.ch)\nend\n\nfunction navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Note that in Tensors.jl the definition textrmgrad v = nabla v holds. With this information it can be quickly shown in index notation that","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"(v cdot nabla) v_textrmi = v_textrmj (partial_textrmj v_textrmi) = v (nabla v)^textrmT_textrmi","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where we should pay attentation to the transpose of the gradient.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ\n end\n end\n return\nend\n\nfunction navierstokes!(du, u_uc, p::RHSparams, t)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Unpack the struct to save some allocations.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" @unpack K, ch, dh, cellvalues_v, u = p","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We start by applying the time-dependent Dirichlet BCs. Note that we are not allowed to mutate u_uc! Furthermore not that we also can not pre- allocate a buffer for this variable variable if we want to use AD to derive the Jacobian matrix, which appears in stiff solvers. Therefore, for efficiency reasons, we simply pass down the jacobian analytically.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" u .= u_uc\n update!(ch, t)\n apply!(u, ch)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we apply the rhs of the Navier-Stokes equations","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" # Linear contribution (Stokes operator)\n mul!(du, K, u) # du .= K * u\n\n # nonlinear contribution\n v_range = dof_range(dh, :v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n vₑ = zeros(n_basefuncs)\n duₑ = zeros(n_basefuncs)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n vₑ .= @views u[v_celldofs]\n fill!(duₑ, 0.0)\n navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v)\n assemble!(du, v_celldofs, duₑ)\n end\n return\nend;\n\nfunction navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Note that in Tensors.jl the definition textrmgrad v = nabla v holds. With this information it can be quickly shown in index notation that","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"(v cdot nabla) v_textrmi = v_textrmj (partial_textrmj v_textrmi) = v (nabla v)^textrmT_textrmi","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"where we should pay attentation to the transpose of the gradient.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" for i in 1:n_basefuncs\n φᵢ = shape_value(cellvalues_v, q_point, i)\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ\n end\n end\n end\n return\nend\n\nfunction navierstokes_jac!(J, u_uc, p, t)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Unpack the struct to save some allocations.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" @unpack K, ch, dh, cellvalues_v, u = p","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"We start by applying the time-dependent Dirichlet BCs. Note that we are not allowed to mutate u_uc, so we use our buffer again.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" u .= u_uc\n update!(ch, t)\n apply!(u, ch)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we apply the Jacobian of the Navier-Stokes equations.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" # Linear contribution (Stokes operator)\n # Here we assume that J has exactly the same structure as K by construction\n nonzeros(J) .= nonzeros(K)\n\n assembler = start_assemble(J; fillzero = false)\n\n # Assemble variation of the nonlinear term\n n_basefuncs = getnbasefunctions(cellvalues_v)\n Jₑ = zeros(n_basefuncs, n_basefuncs)\n vₑ = zeros(n_basefuncs)\n v_range = dof_range(dh, :v)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n\n vₑ .= @views u[v_celldofs]\n fill!(Jₑ, 0.0)\n navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n assemble!(assembler, v_celldofs, Jₑ)\n end","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Finally we eliminate the constrained dofs from the Jacobian to decouple them in the nonlinear solver from the remaining system.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":" return apply!(J, ch)\nend;\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Finally, together with our pre-assembled mass matrix, we are now able to define our problem in mass matrix form.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"rhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity)\nproblem = ODEProblem(rhs, u₀, (0.0, T), p);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"All norms must not depend on constrained dofs. A problem with the presented implementation is that we are currently unable to strictly enforce constraint everywhere in the internal time integration process of DifferentialEquations.jl, hence the values might differ, resulting in worse error estimates. We try to resolve this issue in the future. Volunteers are also welcome to take a look into this!","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"struct FreeDofErrorNorm\n ch::ConstraintHandler\nend\n(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t)\n(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t)","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Now we can put everything together by specifying how to solve the problem. We want to use an adaptive variant of the implicit Euler method. Further we enable the progress bar with the progress and progress_steps arguments. Finally we have to communicate the time step length and initialization algorithm. Since we start with a valid initial state we do not use one of DifferentialEquations.jl initialization algorithms.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: DAE initialization\nAt the time of writing this no Hessenberg index 2 initialization is implemented.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"To visualize the result we export the grid and our fields to VTK-files, which can be viewed in ParaView by utilizing the corresponding pvd file.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"timestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"info: Debugging convergence issues\nWe can obtain some debug information from OrdinaryDiffEq by wrapping the following section into a debug logger.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"integrator = init(\n problem, timestepper; initializealg = NoInit(), dt = Δt₀,\n adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5,\n progress = true, progress_steps = 1,\n verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0]\n);\nnothing #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"note: Export of solution\nExporting interpolated solutions of problems containing mass matrices is currently broken. Thus, the intervals iterator is used. Note that solve holds all solutions in the memory.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"pvd = paraview_collection(\"vortex-street\")\nfor (step, (u, t)) in enumerate(intervals(integrator))\n VTKGridFile(\"vortex-street-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\nend\nvtk_save(pvd);\n\n\nusing Test #hide\nif IS_CI #hide\n function compute_divergence(dh, u, cellvalues_v) #hide\n divv = 0.0 #hide\n for cell in CellIterator(dh) #hide\n Ferrite.reinit!(cellvalues_v, cell) #hide\n for q_point in 1:getnquadpoints(cellvalues_v) #hide\n dΩ = getdetJdV(cellvalues_v, q_point) #hide\n #hide\n all_celldofs = celldofs(cell) #hide\n v_celldofs = all_celldofs[dof_range(dh, :v)] #hide\n v_cell = u[v_celldofs] #hide\n #hide\n divv += function_divergence(cellvalues_v, q_point, v_cell) * dΩ #hide\n end #hide\n end #hide\n return divv #hide\n end #hide\n let #hide\n u = copy(integrator.u) #hide\n Δdivv = abs(compute_divergence(dh, u, cellvalues_v)) #hide\n @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide\n #hide\n Δv = 0.0 #hide\n for cell in CellIterator(dh) #hide\n Ferrite.reinit!(cellvalues_v, cell) #hide\n all_celldofs = celldofs(cell) #hide\n v_celldofs = all_celldofs[dof_range(dh, :v)] #hide\n v_cell = u[v_celldofs] #hide\n coords = getcoordinates(cell) #hide\n for q_point in 1:getnquadpoints(cellvalues_v) #hide\n dΩ = getdetJdV(cellvalues_v, q_point) #hide\n coords_qp = spatial_coordinate(cellvalues_v, q_point, coords) #hide\n v = function_value(cellvalues_v, q_point, v_cell) #hide\n Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide\n end #hide\n end #hide\n @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide\n end #hide\n nothing #hide\nend #hide","category":"page"},{"location":"tutorials/ns_vs_diffeq/#ns_vs_diffeq-plain-program","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Plain program","text":"","category":"section"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"Here follows a version of the program without any comments. The file is also available here: ns_vs_diffeq.jl.","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"using Ferrite, SparseArrays, BlockArrays, LinearAlgebra, UnPack, LinearSolve, WriteVTK\n\nusing OrdinaryDiffEq\n\nν = 1.0 / 1000.0; #dynamic viscosity\n\nusing FerriteGmsh\nusing FerriteGmsh: Gmsh\nGmsh.initialize()\ngmsh.option.set_number(\"General.Verbosity\", 2)\ndim = 2;\n\n rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 1.1, 0.41)\n circle_tag = gmsh.model.occ.add_circle(0.2, 0.2, 0, 0.05)\n circle_curve_tag = gmsh.model.occ.add_curve_loop([circle_tag])\n circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])\n gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])\n\ngmsh.model.occ.synchronize()\n\n bottomtag = gmsh.model.model.add_physical_group(dim - 1, [6], -1, \"bottom\")\n lefttag = gmsh.model.model.add_physical_group(dim - 1, [7], -1, \"left\")\n righttag = gmsh.model.model.add_physical_group(dim - 1, [8], -1, \"right\")\n toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, \"top\")\n holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, \"hole\")\n\ngmsh.option.setNumber(\"Mesh.Algorithm\", 11)\ngmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20)\ngmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.05)\n\ngmsh.model.mesh.generate(dim)\ngrid = togrid()\nGmsh.finalize();\n\nip_v = Lagrange{RefQuadrilateral, 2}()^dim\nqr = QuadratureRule{RefQuadrilateral}(4)\ncellvalues_v = CellValues(qr, ip_v);\n\nip_p = Lagrange{RefQuadrilateral, 1}()\ncellvalues_p = CellValues(qr, ip_p);\n\ndh = DofHandler(grid)\nadd!(dh, :v, ip_v)\nadd!(dh, :p, ip_p)\nclose!(dh);\n\nch = ConstraintHandler(dh);\n\nnosplip_facet_names = [\"top\", \"bottom\", \"hole\"];\n∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);\nnoslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])\nadd!(ch, noslip_bc);\n\n∂Ω_inflow = getfacetset(grid, \"left\");\n\nvᵢₙ(t) = min(t * 1.5, 1.5) #inflow velocity\n\nparabolic_inflow_profile(x, t) = Vec((4 * vᵢₙ(t) * x[2] * (0.41 - x[2]) / 0.41^2, 0.0))\ninflow_bc = Dirichlet(:v, ∂Ω_inflow, parabolic_inflow_profile, [1, 2])\nadd!(ch, inflow_bc);\n\n∂Ω_free = getfacetset(grid, \"right\");\n\nclose!(ch)\nupdate!(ch, 0.0);\n\nfunction assemble_mass_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, M::SparseMatrixCSC, dh::DofHandler)\n # Allocate a buffer for the local matrix and some helpers, together with the assembler.\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Mₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # It follows the assembly loop as explained in the basic tutorials.\n mass_assembler = start_assemble(M)\n for cell in CellIterator(dh)\n fill!(Mₑ, 0)\n Ferrite.reinit!(cellvalues_v, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n # Remember that we assemble a vector mass term, hence the dot product.\n # There is only one time derivative on the left hand side, so only one mass block is non-zero.\n for i in 1:n_basefuncs_v\n φᵢ = shape_value(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n φⱼ = shape_value(cellvalues_v, q_point, j)\n Mₑ[BlockIndex((v▄, v▄), (i, j))] += φᵢ ⋅ φⱼ * dΩ\n end\n end\n end\n assemble!(mass_assembler, celldofs(cell), Mₑ)\n end\n\n return M\nend;\n\nfunction assemble_stokes_matrix(cellvalues_v::CellValues, cellvalues_p::CellValues, ν, K::SparseMatrixCSC, dh::DofHandler)\n # Again, some buffers and helpers\n n_basefuncs_v = getnbasefunctions(cellvalues_v)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n n_basefuncs = n_basefuncs_v + n_basefuncs_p\n v▄, p▄ = 1, 2\n Kₑ = BlockedArray(zeros(n_basefuncs, n_basefuncs), [n_basefuncs_v, n_basefuncs_p], [n_basefuncs_v, n_basefuncs_p])\n\n # Assembly loop\n stiffness_assembler = start_assemble(K)\n for cell in CellIterator(dh)\n # Don't forget to initialize everything\n fill!(Kₑ, 0)\n\n Ferrite.reinit!(cellvalues_v, cell)\n Ferrite.reinit!(cellvalues_p, cell)\n\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n\n for i in 1:n_basefuncs_v\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n for j in 1:n_basefuncs_v\n ∇φⱼ = shape_gradient(cellvalues_v, q_point, j)\n Kₑ[BlockIndex((v▄, v▄), (i, j))] -= ν * ∇φᵢ ⊡ ∇φⱼ * dΩ\n end\n end\n\n for j in 1:n_basefuncs_p\n ψ = shape_value(cellvalues_p, q_point, j)\n for i in 1:n_basefuncs_v\n divφ = shape_divergence(cellvalues_v, q_point, i)\n Kₑ[BlockIndex((v▄, p▄), (i, j))] += (divφ * ψ) * dΩ\n Kₑ[BlockIndex((p▄, v▄), (j, i))] += (ψ * divφ) * dΩ\n end\n end\n end\n\n # Assemble `Kₑ` into the Stokes matrix `K`.\n assemble!(stiffness_assembler, celldofs(cell), Kₑ)\n end\n return K\nend;\n\nT = 6.0\nΔt₀ = 0.001\nΔt_save = 0.1\n\nM = allocate_matrix(dh);\nM = assemble_mass_matrix(cellvalues_v, cellvalues_p, M, dh);\n\nK = allocate_matrix(dh);\nK = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);\n\nu₀ = zeros(ndofs(dh))\napply!(u₀, ch);\n\njac_sparsity = sparse(K);\n\napply!(M, ch)\n\nstruct RHSparams\n K::SparseMatrixCSC\n ch::ConstraintHandler\n dh::DofHandler\n cellvalues_v::CellValues\n u::Vector\nend\np = RHSparams(K, ch, dh, cellvalues_v, copy(u₀))\n\nfunction ferrite_limiter!(u, _, p, t)\n update!(p.ch, t)\n return apply!(u, p.ch)\nend\n\nfunction navierstokes_rhs_element!(dvₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)\n\n dvₑ[j] -= v ⋅ ∇v' ⋅ φⱼ * dΩ\n end\n end\n return\nend\n\nfunction navierstokes!(du, u_uc, p::RHSparams, t)\n\n @unpack K, ch, dh, cellvalues_v, u = p\n\n u .= u_uc\n update!(ch, t)\n apply!(u, ch)\n\n # Linear contribution (Stokes operator)\n mul!(du, K, u) # du .= K * u\n\n # nonlinear contribution\n v_range = dof_range(dh, :v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n vₑ = zeros(n_basefuncs)\n duₑ = zeros(n_basefuncs)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n vₑ .= @views u[v_celldofs]\n fill!(duₑ, 0.0)\n navierstokes_rhs_element!(duₑ, vₑ, cellvalues_v)\n assemble!(du, v_celldofs, duₑ)\n end\n return\nend;\n\nfunction navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n n_basefuncs = getnbasefunctions(cellvalues_v)\n for q_point in 1:getnquadpoints(cellvalues_v)\n dΩ = getdetJdV(cellvalues_v, q_point)\n ∇v = function_gradient(cellvalues_v, q_point, vₑ)\n v = function_value(cellvalues_v, q_point, vₑ)\n for j in 1:n_basefuncs\n φⱼ = shape_value(cellvalues_v, q_point, j)\n\n for i in 1:n_basefuncs\n φᵢ = shape_value(cellvalues_v, q_point, i)\n ∇φᵢ = shape_gradient(cellvalues_v, q_point, i)\n Jₑ[j, i] -= (φᵢ ⋅ ∇v' + v ⋅ ∇φᵢ') ⋅ φⱼ * dΩ\n end\n end\n end\n return\nend\n\nfunction navierstokes_jac!(J, u_uc, p, t)\n\n @unpack K, ch, dh, cellvalues_v, u = p\n\n u .= u_uc\n update!(ch, t)\n apply!(u, ch)\n\n # Linear contribution (Stokes operator)\n # Here we assume that J has exactly the same structure as K by construction\n nonzeros(J) .= nonzeros(K)\n\n assembler = start_assemble(J; fillzero = false)\n\n # Assemble variation of the nonlinear term\n n_basefuncs = getnbasefunctions(cellvalues_v)\n Jₑ = zeros(n_basefuncs, n_basefuncs)\n vₑ = zeros(n_basefuncs)\n v_range = dof_range(dh, :v)\n for cell in CellIterator(dh)\n Ferrite.reinit!(cellvalues_v, cell)\n v_celldofs = @view celldofs(cell)[v_range]\n\n vₑ .= @views u[v_celldofs]\n fill!(Jₑ, 0.0)\n navierstokes_jac_element!(Jₑ, vₑ, cellvalues_v)\n assemble!(assembler, v_celldofs, Jₑ)\n end\n\n return apply!(J, ch)\nend;\n\nrhs = ODEFunction(navierstokes!, mass_matrix = M; jac = navierstokes_jac!, jac_prototype = jac_sparsity)\nproblem = ODEProblem(rhs, u₀, (0.0, T), p);\n\nstruct FreeDofErrorNorm\n ch::ConstraintHandler\nend\n(fe_norm::FreeDofErrorNorm)(u::Union{AbstractFloat, Complex}, t) = DiffEqBase.ODE_DEFAULT_NORM(u, t)\n(fe_norm::FreeDofErrorNorm)(u::AbstractArray, t) = DiffEqBase.ODE_DEFAULT_NORM(u[fe_norm.ch.free_dofs], t)\n\ntimestepper = Rodas5P(autodiff = false, step_limiter! = ferrite_limiter!);\n\nintegrator = init(\n problem, timestepper; initializealg = NoInit(), dt = Δt₀,\n adaptive = true, abstol = 1.0e-4, reltol = 1.0e-5,\n progress = true, progress_steps = 1,\n verbose = true, internalnorm = FreeDofErrorNorm(ch), d_discontinuities = [1.0]\n);\n\npvd = paraview_collection(\"vortex-street\")\nfor (step, (u, t)) in enumerate(intervals(integrator))\n VTKGridFile(\"vortex-street-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\nend\nvtk_save(pvd);","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"","category":"page"},{"location":"tutorials/ns_vs_diffeq/","page":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","title":"Incompressible Navier-Stokes equations via DifferentialEquations.jl","text":"This page was generated using Literate.jl.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"EditURL = \"../literate-tutorials/transient_heat_equation.jl\"","category":"page"},{"location":"tutorials/transient_heat_equation/#tutorial-transient-heat-equation","page":"Transient heat equation","title":"Transient heat equation","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"(Image: ) (Image: )","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Figure 1: Visualization of the temperature time evolution on a unit square where the prescribed temperature on the upper and lower parts of the boundary increase with time.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"tip: Tip\nThis example is also available as a Jupyter notebook: transient_heat_equation.ipynb.","category":"page"},{"location":"tutorials/transient_heat_equation/#Introduction","page":"Transient heat equation","title":"Introduction","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In this example we extend the heat equation by a time dependent term, i.e.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":" fracpartial upartial t-nabla cdot (k nabla u) = f quad x in Omega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where u is the unknown temperature field, k the heat conductivity, f the heat source and Omega the domain. For simplicity, we hard code f = 01 and k = 10^-3. We define homogeneous Dirichlet boundary conditions along the left and right edge of the domain.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"u(xt) = 0 quad x in partial Omega_1","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where partial Omega_1 denotes the left and right boundary of Omega.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Further, we define heterogeneous Dirichlet boundary conditions at the top and bottom edge partial Omega_2. We choose a linearly increasing function a(t) that describes the temperature at this boundary","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"u(xt) = a(t) quad x in partial Omega_2","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"The semidiscrete weak form is given by","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"int_Omegav fracpartial upartial t mathrmdOmega + int_Omega k nabla v cdot nabla u mathrmdOmega = int_Omega f v mathrmdOmega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where v is a suitable test function. Now, we still need to discretize the time derivative. An implicit Euler scheme is applied, which yields:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"int_Omega v u_n+1 mathrmdOmega + Delta tint_Omega k nabla v cdot nabla u_n+1 mathrmdOmega = Delta tint_Omega f v mathrmdOmega + int_Omega v u_n mathrmdOmega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"If we assemble the discrete operators, we get the following algebraic system:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"mathbfM mathbfu_n+1 + Δt mathbfK mathbfu_n+1 = Δt mathbff + mathbfM mathbfu_n","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In this example we apply the boundary conditions to the assembled discrete operators (mass matrix mathbfM and stiffnes matrix mathbfK) only once. We utilize the fact that in finite element computations Dirichlet conditions can be applied by zero out rows and columns that correspond to a prescribed dof in the system matrix (mathbfA = Δt mathbfK + mathbfM) and setting the value of the right-hand side vector to the value of the Dirichlet condition. Thus, we only need to apply in every time step the Dirichlet condition to the right-hand side of the problem.","category":"page"},{"location":"tutorials/transient_heat_equation/#Commented-Program","page":"Transient heat equation","title":"Commented Program","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Now we solve the problem in Ferrite. What follows is a program spliced with comments. The full program, without comments, can be found in the next section.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"First we load Ferrite, and some other packages we need.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"using Ferrite, SparseArrays, WriteVTK","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We create the same grid as in the heat equation example.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"grid = generate_grid(Quadrilateral, (100, 100));\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Trial-and-test-functions","page":"Transient heat equation","title":"Trial and test functions","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Again, we define the structs that are responsible for the shape_value and shape_gradient evaluation.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"ip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Degrees-of-freedom","page":"Transient heat equation","title":"Degrees of freedom","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"After this, we can define the DofHandler and distribute the DOFs of the problem.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"dh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"By means of the DofHandler we can allocate the needed SparseMatrixCSC. M refers here to the so called mass matrix, which always occurs in time related terms, i.e.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"M_ij = int_Omega v_i u_j mathrmdOmega","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"where u_i and v_j are trial and test functions, respectively.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"K = allocate_matrix(dh);\nM = allocate_matrix(dh);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We also preallocate the right hand side","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"f = zeros(ndofs(dh));\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Boundary-conditions","page":"Transient heat equation","title":"Boundary conditions","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In order to define the time dependent problem, we need some end time T and something that describes the linearly increasing Dirichlet boundary condition on partial Omega_2.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"max_temp = 100\nΔt = 1\nT = 200\nt_rise = 100\nch = ConstraintHandler(dh);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Here, we define the boundary condition related to partial Omega_1.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"∂Ω₁ = union(getfacetset.((grid,), [\"left\", \"right\"])...)\ndbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0)\nadd!(ch, dbc);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"While the next code block corresponds to the linearly increasing temperature description on partial Omega_2 until t=t_rise, and then keep constant","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"∂Ω₂ = union(getfacetset.((grid,), [\"top\", \"bottom\"])...)\ndbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1))\nadd!(ch, dbc)\nclose!(ch)\nupdate!(ch, 0.0);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Assembling-the-linear-system","page":"Transient heat equation","title":"Assembling the linear system","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"As in the heat equation example we define a doassemble! function that assembles the diffusion parts of the equation:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"function doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n\n assembler = start_assemble(K, f)\n\n for cell in CellIterator(dh)\n\n fill!(Ke, 0)\n fill!(fe, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n ∇v = shape_gradient(cellvalues, q_point, i)\n fe[i] += 0.1 * v * dΩ\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\nnothing # hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In addition to the diffusive part, we also need a function that assembles the mass matrix M.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"function doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Me = zeros(n_basefuncs, n_basefuncs)\n\n assembler = start_assemble(M)\n\n for cell in CellIterator(dh)\n\n fill!(Me, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n u = shape_value(cellvalues, q_point, j)\n Me[i, j] += (v * u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Me)\n end\n return M\nend\nnothing # hide","category":"page"},{"location":"tutorials/transient_heat_equation/#Solution-of-the-system","page":"Transient heat equation","title":"Solution of the system","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We first assemble all parts in the prior allocated SparseMatrixCSC.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"K, f = doassemble_K!(K, f, cellvalues, dh)\nM = doassemble_M!(M, cellvalues, dh)\nA = (Δt .* K) + M;\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Now, we need to save all boundary condition related values of the unaltered system matrix A, which is done by get_rhs_data. The function returns a RHSData struct, which contains all needed information to apply the boundary conditions solely on the right-hand-side vector of the problem.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"rhsdata = get_rhs_data(ch, A);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"We set the values at initial time step, denoted by uₙ, to a bubble-shape described by (x_1^2-1)(x_2^2-1), such that it is zero at the boundaries and the maximum temperature in the center.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"uₙ = zeros(length(f));\napply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Here, we apply once the boundary conditions to the system matrix A.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"apply!(A, ch);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"To store the solution, we initialize a paraview collection (.pvd) file,","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"pvd = paraview_collection(\"transient-heat\")\nVTKGridFile(\"transient-heat-0\", dh) do vtk\n write_solution(vtk, dh, uₙ)\n pvd[0.0] = vtk\nend","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"At this point everything is set up and we can finally approach the time loop.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"for (step, t) in enumerate(Δt:Δt:T)\n #First of all, we need to update the Dirichlet boundary condition values.\n update!(ch, t)\n\n #Secondly, we compute the right-hand-side of the problem.\n b = Δt .* f .+ M * uₙ\n #Then, we can apply the boundary conditions of the current time step.\n apply_rhs!(rhsdata, b, ch)\n\n #Finally, we can solve the time step and save the solution afterwards.\n u = A \\ b\n\n VTKGridFile(\"transient-heat-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\n #At the end of the time loop, we set the previous solution to the current one and go to the next time step.\n uₙ .= u\nend","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"In order to use the .pvd file we need to store it to the disk, which is done by:","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"vtk_save(pvd);\nnothing #hide","category":"page"},{"location":"tutorials/transient_heat_equation/#transient_heat_equation-plain-program","page":"Transient heat equation","title":"Plain program","text":"","category":"section"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"Here follows a version of the program without any comments. The file is also available here: transient_heat_equation.jl.","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"using Ferrite, SparseArrays, WriteVTK\n\ngrid = generate_grid(Quadrilateral, (100, 100));\n\nip = Lagrange{RefQuadrilateral, 1}()\nqr = QuadratureRule{RefQuadrilateral}(2)\ncellvalues = CellValues(qr, ip);\n\ndh = DofHandler(grid)\nadd!(dh, :u, ip)\nclose!(dh);\n\nK = allocate_matrix(dh);\nM = allocate_matrix(dh);\n\nf = zeros(ndofs(dh));\n\nmax_temp = 100\nΔt = 1\nT = 200\nt_rise = 100\nch = ConstraintHandler(dh);\n\n∂Ω₁ = union(getfacetset.((grid,), [\"left\", \"right\"])...)\ndbc = Dirichlet(:u, ∂Ω₁, (x, t) -> 0)\nadd!(ch, dbc);\n\n∂Ω₂ = union(getfacetset.((grid,), [\"top\", \"bottom\"])...)\ndbc = Dirichlet(:u, ∂Ω₂, (x, t) -> max_temp * clamp(t / t_rise, 0, 1))\nadd!(ch, dbc)\nclose!(ch)\nupdate!(ch, 0.0);\n\nfunction doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Ke = zeros(n_basefuncs, n_basefuncs)\n fe = zeros(n_basefuncs)\n\n assembler = start_assemble(K, f)\n\n for cell in CellIterator(dh)\n\n fill!(Ke, 0)\n fill!(fe, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n ∇v = shape_gradient(cellvalues, q_point, i)\n fe[i] += 0.1 * v * dΩ\n for j in 1:n_basefuncs\n ∇u = shape_gradient(cellvalues, q_point, j)\n Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Ke, fe)\n end\n return K, f\nend\n\nfunction doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)\n\n n_basefuncs = getnbasefunctions(cellvalues)\n Me = zeros(n_basefuncs, n_basefuncs)\n\n assembler = start_assemble(M)\n\n for cell in CellIterator(dh)\n\n fill!(Me, 0)\n\n reinit!(cellvalues, cell)\n\n for q_point in 1:getnquadpoints(cellvalues)\n dΩ = getdetJdV(cellvalues, q_point)\n\n for i in 1:n_basefuncs\n v = shape_value(cellvalues, q_point, i)\n for j in 1:n_basefuncs\n u = shape_value(cellvalues, q_point, j)\n Me[i, j] += (v * u) * dΩ\n end\n end\n end\n\n assemble!(assembler, celldofs(cell), Me)\n end\n return M\nend\n\nK, f = doassemble_K!(K, f, cellvalues, dh)\nM = doassemble_M!(M, cellvalues, dh)\nA = (Δt .* K) + M;\n\nrhsdata = get_rhs_data(ch, A);\n\nuₙ = zeros(length(f));\napply_analytical!(uₙ, dh, :u, x -> (x[1]^2 - 1) * (x[2]^2 - 1) * max_temp);\n\napply!(A, ch);\n\npvd = paraview_collection(\"transient-heat\")\nVTKGridFile(\"transient-heat-0\", dh) do vtk\n write_solution(vtk, dh, uₙ)\n pvd[0.0] = vtk\nend\n\nfor (step, t) in enumerate(Δt:Δt:T)\n #First of all, we need to update the Dirichlet boundary condition values.\n update!(ch, t)\n\n #Secondly, we compute the right-hand-side of the problem.\n b = Δt .* f .+ M * uₙ\n #Then, we can apply the boundary conditions of the current time step.\n apply_rhs!(rhsdata, b, ch)\n\n #Finally, we can solve the time step and save the solution afterwards.\n u = A \\ b\n\n VTKGridFile(\"transient-heat-$step\", dh) do vtk\n write_solution(vtk, dh, u)\n pvd[t] = vtk\n end\n #At the end of the time loop, we set the previous solution to the current one and go to the next time step.\n uₙ .= u\nend\n\nvtk_save(pvd);","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"","category":"page"},{"location":"tutorials/transient_heat_equation/","page":"Transient heat equation","title":"Transient heat equation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"gallery/#Code-gallery","page":"Code gallery","title":"Code gallery","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"This page gives an overview of the code gallery. Compared to the tutorials, these programs do not focus on teaching Ferrite, but rather focus on showing how Ferrite can be used \"in the wild\".","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"note: Contribute to the gallery!\nMost of the gallery is user contributed. If you use Ferrite, and have something you want to share, please contribute to the gallery! This could, for example, be your research code for a published paper, some interesting application, or just some nice trick.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Helmholtz-equation](helmholtz.md)","page":"Code gallery","title":"Helmholtz equation","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Solves the Helmholtz equation on the unit square using a combination of Dirichlet and Neumann boundary conditions and the method of manufactured solutions.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Kristoffer Carlsson (@KristofferC).","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Nearly-incompressible-hyperelasticity](quasi_incompressible_hyperelasticity.md)","page":"Code gallery","title":"Nearly incompressible hyperelasticity","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"This program combines the ideas from Tutorial 3: Incompressible elasticity and Tutorial 4: Hyperelasticity to construct a mixed element solving three-dimensional displacement-pressure equations.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Bhavesh Shrimali (@bhaveshshrimali).","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Ginzburg-Landau-model-energy-minimization](landau.md)","page":"Code gallery","title":"Ginzburg-Landau model energy minimization","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"A basic Ginzburg-Landau model is solved. ForwardDiff.jl is used to compute the gradient and hessian of the energy function. Multi-threading is used to parallelize the assembly procedure.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Louis Ponet (@louisponet).","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"","category":"page"},{"location":"gallery/#[Topology-optimization](topology_optimization.md)","page":"Code gallery","title":"Topology optimization","text":"","category":"section"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Topology optimization is shown for the bending problem by using a SIMP material model. To avoid numerical instabilities, a regularization scheme requiring the calculation of the Laplacian is imposed, which is done by using the grid topology functionalities.","category":"page"},{"location":"gallery/","page":"Code gallery","title":"Code gallery","text":"Contributed by: Mischa Blaszczyk (@blaszm).","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"EditURL = \"../literate-gallery/quasi_incompressible_hyperelasticity.jl\"","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#tutorial-nearly-incompressible-hyperelasticity","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"(Image: )","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"tip: Tip\nThis example is also available as a Jupyter notebook: quasi_incompressible_hyperelasticity.ipynb","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#Introduction","page":"Nearly Incompressible Hyperelasticity","title":"Introduction","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"In this example we study quasi- or nearly-incompressible hyperelasticity using the stable Taylor-Hood approximation. In spirit, this example is the nonlinear analogue of incompressible_elasticity and the incompressible analogue of hyperelasticity. Much of the code therefore follows from the above two examples. The problem is formulated in the undeformed or reference configuration with the displacement mathbfu and pressure p being the unknown fields. We now briefly outline the formulation. Consider the standard hyperelasticity problem","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" mathbfu = argmin_mathbfvinmathcalK(Omega)Pi(mathbfv)quad textwherequad Pi(mathbfv) = int_Omega Psi(mathbfv) mathrmdOmega ","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"where mathcalK(Omega) is a suitable function space.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"For clarity of presentation we ignore any non-zero surface tractions and body forces and instead consider only applied displacements (i.e. non-homogeneous dirichlet boundary conditions). Moreover we stick our attention to the standard Neo-Hookean stored energy density","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" Psi(mathbfu) = fracmu2left(I_1 - 3 right) - mu log(J) + fraclambda2left( J - 1right)^2","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"where I_1 = mathrmtr(mathbfC) = mathrmtr(mathbfF^mathrmT mathbfF) = F_ijF_ij and J = det(mathbfF) denote the standard invariants of the deformation gradient tensor mathbfF = mathbfI+nabla_mathbfX mathbfu. The above problem is ill-posed in the limit of incompressibility (or near-incompressibility), namely when","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" lambdamu rightarrow +infty","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"In order to alleviate the problem, we consider the partial legendre transform of the strain energy density Psi with respect to J = det(mathbfF), namely","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" widehatPsi(mathbfu p) = sup_J left p(J - 1) - fracmu2left(I_1 - 3 right) + mu log(J) - fraclambda2left( J - 1right)^2 right","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The supremum, say J^star, can be calculated in closed form by the first order optimailty condition partialwidehatPsipartial J = 0. This gives","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" J^star(p) = fraclambda + p + sqrt(lambda + p)^2 + 4 lambda mu (2 lambda)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Furthermore, taking the partial legendre transform of widehatPsi once again, gives us back the original problem, i.e.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" Psi(mathbfu) = Psi^star(mathbfu p) = sup_p left p(J - 1) - p(J^star - 1) + fracmu2left(I_1 - 3 right) - mu log(J^star) + fraclambda2left( J^star - 1right)^2 right","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Therefore our original hyperelasticity problem can now be reformulated as","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" inf_mathbfuinmathcalK(Omega)sup_p int_OmegaPsi^star (mathbfu p) mathrmdOmega","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The total (modified) energy Pi^star can then be written as","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" Pi^star(mathbfu p) = int_Omega p (J - J^star) mathrmdOmega + int_Omega fracmu2 left( I_1 - 3right) mathrmdOmega - int_Omega mulog(J^star) mathrmdOmega + int_Omega fraclambda2left( J^star - 1 right)^2 mathrmdOmega","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The Euler-Lagrange equations corresponding to the above energy give us our governing PDEs in the weak form, namely","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" int_Omega fracpartialPsi^starpartial mathbfFdelta mathbfF mathrmdOmega = 0","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":" int_Omega fracpartial Psi^starpartial pdelta p mathrmdOmega = 0","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"where delta mathrmF = delta mathrmgrad_0(mathbfu) = mathrmgrad_0(delta mathbfu) and delta mathbfu and delta p denote arbitrary variations with respect to displacement and pressure (or the test functions). See the references below for a more detailed explanation of the above mathematical trick. Now, in order to apply Newton's method to the above problem, we further need to linearize the above equations and calculate the respective hessians (or tangents), namely, partial^2Psi^starpartial mathbfF^2, partial^2Psi^starpartial p^2 and partial^2Psi^starpartial mathbfFpartial p which, using Tensors.jl, can be determined conveniently using automatic differentiation (see the code below). Hence we only need to define the above potential. The remaineder of the example follows similarly.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#References","page":"Nearly Incompressible Hyperelasticity","title":"References","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"A paradigm for higher-order polygonal elements in finite elasticity using a gradient correction scheme, CMAME 2016, 306, 216–251\nApproximation of incompressible large deformation elastic problems: some unresolved issues, Computational Mechanics, 2013","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#Implementation","page":"Nearly Incompressible Hyperelasticity","title":"Implementation","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We now get to the actual code. First, we import the respective packages","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"using Ferrite, Tensors, ProgressMeter, WriteVTK\nusing BlockArrays, SparseArrays, LinearAlgebra","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and the corresponding struct to store our material properties.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"struct NeoHooke\n μ::Float64\n λ::Float64\nend","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We then create a function to generate a simple test mesh on which to compute FE solution. We also mark the boundaries to later assign Dirichlet boundary conditions","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function importTestGrid()\n grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3}))\n addfacetset!(grid, \"myBottom\", x -> norm(x[2]) ≈ 0.0)\n addfacetset!(grid, \"myBack\", x -> norm(x[3]) ≈ 0.0)\n addfacetset!(grid, \"myRight\", x -> norm(x[1]) ≈ 1.0)\n addfacetset!(grid, \"myLeft\", x -> norm(x[1]) ≈ 0.0)\n return grid\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The function to create corresponding cellvalues for the displacement field u and pressure p follows in a similar fashion from the incompressible_elasticity example","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTetrahedron}(4)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(4)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We now create the function for Ψ*","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function Ψ(F, p, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(tdot(F))\n J = det(F)\n Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ)\n return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and it's derivatives (required in computing the jacobian and hessian respectively)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function constitutive_driver(F, p, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all)\n ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all)\n ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p)\n return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The functions to create the DofHandler and ConstraintHandler (to assign corresponding boundary conditions) follow likewise from the incompressible elasticity example, namely","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement dim = 3\n add!(dh, :p, ipp) # pressure dim = 1\n close!(dh)\n return dh\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We are simulating a uniaxial tensile loading of a unit cube. Hence we apply a displacement field (:u) in x direction on the right face. The left, bottom and back facets are fixed in the x, y and z components of the displacement so as to emulate the uniaxial nature of the loading.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myLeft\"), (x, t) -> zero(Vec{1}), [1]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBack\"), (x, t) -> zero(Vec{1}), [3]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myRight\"), (x, t) -> t * ones(Vec{1}), [1]))\n close!(dbc)\n Ferrite.update!(dbc, 0.0)\n return dbc\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Also, since we are considering incompressible hyperelasticity, an interesting quantity that we can compute is the deformed volume of the solid. It is easy to show that this is equal to ∫J*dΩ where J=det(F). This can be done at the level of each element (cell)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function calculate_element_volume(cell, cellvalues_u, ue)\n reinit!(cellvalues_u, cell)\n evol::Float64 = 0.0\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n ∇u = function_gradient(cellvalues_u, qp, ue)\n F = one(∇u) + ∇u\n J = det(F)\n evol += J * dΩ\n end\n return evol\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"and then assembled over all the cells (elements)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u)\n evol::Float64 = 0.0\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n nu = getnbasefunctions(cellvalues_u)\n global_dofs_u = global_dofs[1:nu]\n ue = w[global_dofs_u]\n δevol = calculate_element_volume(cell, cellvalues_u, ue)\n evol += δevol\n end\n return evol\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The function to assemble the element stiffness matrix for each element in the mesh now has a block structure like in incompressible_elasticity.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n # Reinitialize cell values, and reset output arrays\n ublock, pblock = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n fill!(Ke, 0.0)\n fill!(fe, 0.0)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Compute deformation gradient F\n ∇u = function_gradient(cellvalues_u, qp, ue)\n p = function_value(cellvalues_p, qp, pe)\n F = one(∇u) + ∇u\n\n # Compute first Piola-Kirchhoff stress and tangent modulus\n ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp)\n\n # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks\n for i in 1:n_basefuncs_u\n # gradient of the test function\n ∇δui = shape_gradient(cellvalues_u, qp, i)\n # Add contribution to the residual from this test function\n fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ\n\n ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F²\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ\n end\n # Loop over the `p`-test functions\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ\n end\n end\n # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, i)\n fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ\n\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ\n end\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ\n end\n end\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The only thing that changes in the assembly of the global stiffness matrix is slicing the corresponding element dofs for the displacement (see global_dofsu) and pressure (global_dofsp).","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function assemble_global!(\n K::SparseMatrixCSC, f, cellvalues_u::CellValues,\n cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w\n )\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n # start_assemble resets K and f\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n assembler = start_assemble(K, f)\n # Loop over all cells in the grid\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n global_dofsu = global_dofs[1:nu] # first nu dofs are displacement\n global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure\n @assert size(global_dofs, 1) == nu + np # sanity check\n ue = w[global_dofsu] # displacement dofs for the current cell\n pe = w[global_dofsp] # pressure dofs for the current cell\n assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n assemble!(assembler, global_dofs, ke, fe)\n end\n return\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We now define a main function solve. For nonlinear quasistatic problems we often like to parameterize the solution in terms of a pseudo time like parameter, which in this case is used to gradually apply the boundary displacement on the right face. Also for definitenessm we consider λ/μ = 10⁴","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"function solve(interpolation_u, interpolation_p)\n\n # import the mesh\n grid = importTestGrid()\n\n # Material parameters\n μ = 1.0\n λ = 1.0e4 * μ\n mp = NeoHooke(μ, λ)\n\n # Create the DofHandler and CellValues\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Create the DirichletBCs\n dbc = create_bc(dh)\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n w = zeros(_ndofs)\n ΔΔw = zeros(_ndofs)\n apply!(w, dbc)\n\n # Create the sparse matrix and residual vector\n K = allocate_matrix(dh)\n f = zeros(_ndofs)\n\n # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value\n # of this parameter, and Δt denotes its increment in each step\n Tf = 2.0\n Δt = 0.1\n NEWTON_TOL = 1.0e-8\n\n pvd = paraview_collection(\"hyperelasticity_incomp_mixed\")\n for (step, t) in enumerate(0.0:Δt:Tf)\n # Perform Newton iterations\n Ferrite.update!(dbc, t)\n apply!(w, dbc)\n newton_itr = -1\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving @ time $t of $Tf;\")\n fill!(ΔΔw, 0.0)\n while true\n newton_itr += 1\n assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w)\n norm_res = norm(f[Ferrite.free_dofs(dbc)])\n apply_zero!(K, f, dbc)\n # Only display output at specific load steps\n if t % (5 * Δt) == 0\n ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)])\n end\n if norm_res < NEWTON_TOL\n break\n elseif newton_itr > 30\n error(\"Reached maximum Newton iterations, aborting\")\n end\n # Compute the incremental `dof`-vector (both displacement and pressure)\n ΔΔw .= K \\ f\n\n apply_zero!(ΔΔw, dbc)\n w .-= ΔΔw\n end\n\n # Save the solution fields\n VTKGridFile(\"hyperelasticity_incomp_mixed_$step\", grid) do vtk\n write_solution(vtk, dh, w)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u)\n print(\"Deformed volume is $vol_def\")\n return vol_def\nend;\nnothing #hide","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"We can now test the solution using the Taylor-Hood approximation","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"quadratic_u = Lagrange{RefTetrahedron, 2}()^3\nlinear_p = Lagrange{RefTetrahedron, 1}()\nvol_def = solve(quadratic_u, linear_p)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"The deformed volume is indeed close to 1 (as should be for a nearly incompressible material).","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/#Plain-program","page":"Nearly Incompressible Hyperelasticity","title":"Plain program","text":"","category":"section"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"Here follows a version of the program without any comments. The file is also available here: quasi_incompressible_hyperelasticity.jl.","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"using Ferrite, Tensors, ProgressMeter, WriteVTK\nusing BlockArrays, SparseArrays, LinearAlgebra\n\nstruct NeoHooke\n μ::Float64\n λ::Float64\nend\n\nfunction importTestGrid()\n grid = generate_grid(Tetrahedron, (5, 5, 5), zero(Vec{3}), ones(Vec{3}))\n addfacetset!(grid, \"myBottom\", x -> norm(x[2]) ≈ 0.0)\n addfacetset!(grid, \"myBack\", x -> norm(x[3]) ≈ 0.0)\n addfacetset!(grid, \"myRight\", x -> norm(x[1]) ≈ 1.0)\n addfacetset!(grid, \"myLeft\", x -> norm(x[1]) ≈ 0.0)\n return grid\nend;\n\nfunction create_values(interpolation_u, interpolation_p)\n # quadrature rules\n qr = QuadratureRule{RefTetrahedron}(4)\n facet_qr = FacetQuadratureRule{RefTetrahedron}(4)\n\n # cell and facetvalues for u\n cellvalues_u = CellValues(qr, interpolation_u)\n facetvalues_u = FacetValues(facet_qr, interpolation_u)\n\n # cellvalues for p\n cellvalues_p = CellValues(qr, interpolation_p)\n\n return cellvalues_u, cellvalues_p, facetvalues_u\nend;\n\nfunction Ψ(F, p, mp::NeoHooke)\n μ = mp.μ\n λ = mp.λ\n Ic = tr(tdot(F))\n J = det(F)\n Js = (λ + p + sqrt((λ + p)^2.0 + 4.0 * λ * μ)) / (2.0 * λ)\n return p * (Js - J) + μ / 2 * (Ic - 3) - μ * log(Js) + λ / 2 * (Js - 1)^2\nend;\n\nfunction constitutive_driver(F, p, mp::NeoHooke)\n # Compute all derivatives in one function call\n ∂²Ψ∂F², ∂Ψ∂F = Tensors.hessian(y -> Ψ(y, p, mp), F, :all)\n ∂²Ψ∂p², ∂Ψ∂p = Tensors.hessian(y -> Ψ(F, y, mp), p, :all)\n ∂²Ψ∂F∂p = Tensors.gradient(q -> Tensors.gradient(y -> Ψ(y, q, mp), F), p)\n return ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p\nend;\n\nfunction create_dofhandler(grid, ipu, ipp)\n dh = DofHandler(grid)\n add!(dh, :u, ipu) # displacement dim = 3\n add!(dh, :p, ipp) # pressure dim = 1\n close!(dh)\n return dh\nend;\n\nfunction create_bc(dh)\n dbc = ConstraintHandler(dh)\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myLeft\"), (x, t) -> zero(Vec{1}), [1]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBottom\"), (x, t) -> zero(Vec{1}), [2]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myBack\"), (x, t) -> zero(Vec{1}), [3]))\n add!(dbc, Dirichlet(:u, getfacetset(dh.grid, \"myRight\"), (x, t) -> t * ones(Vec{1}), [1]))\n close!(dbc)\n Ferrite.update!(dbc, 0.0)\n return dbc\nend;\n\nfunction calculate_element_volume(cell, cellvalues_u, ue)\n reinit!(cellvalues_u, cell)\n evol::Float64 = 0.0\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n ∇u = function_gradient(cellvalues_u, qp, ue)\n F = one(∇u) + ∇u\n J = det(F)\n evol += J * dΩ\n end\n return evol\nend;\n\nfunction calculate_volume_deformed_mesh(w, dh::DofHandler, cellvalues_u)\n evol::Float64 = 0.0\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n nu = getnbasefunctions(cellvalues_u)\n global_dofs_u = global_dofs[1:nu]\n ue = w[global_dofs_u]\n δevol = calculate_element_volume(cell, cellvalues_u, ue)\n evol += δevol\n end\n return evol\nend;\n\nfunction assemble_element!(Ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n # Reinitialize cell values, and reset output arrays\n ublock, pblock = 1, 2\n reinit!(cellvalues_u, cell)\n reinit!(cellvalues_p, cell)\n fill!(Ke, 0.0)\n fill!(fe, 0.0)\n\n n_basefuncs_u = getnbasefunctions(cellvalues_u)\n n_basefuncs_p = getnbasefunctions(cellvalues_p)\n\n for qp in 1:getnquadpoints(cellvalues_u)\n dΩ = getdetJdV(cellvalues_u, qp)\n # Compute deformation gradient F\n ∇u = function_gradient(cellvalues_u, qp, ue)\n p = function_value(cellvalues_p, qp, pe)\n F = one(∇u) + ∇u\n\n # Compute first Piola-Kirchhoff stress and tangent modulus\n ∂Ψ∂F, ∂²Ψ∂F², ∂Ψ∂p, ∂²Ψ∂p², ∂²Ψ∂F∂p = constitutive_driver(F, p, mp)\n\n # Loop over the `u`-test functions to calculate the `u`-`u` and `u`-`p` blocks\n for i in 1:n_basefuncs_u\n # gradient of the test function\n ∇δui = shape_gradient(cellvalues_u, qp, i)\n # Add contribution to the residual from this test function\n fe[BlockIndex((ublock), (i))] += (∇δui ⊡ ∂Ψ∂F) * dΩ\n\n ∇δui∂S∂F = ∇δui ⊡ ∂²Ψ∂F²\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, ublock), (i, j))] += (∇δui∂S∂F ⊡ ∇δuj) * dΩ\n end\n # Loop over the `p`-test functions\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n # Add contribution to the tangent\n Ke[BlockIndex((ublock, pblock), (i, j))] += (∂²Ψ∂F∂p ⊡ ∇δui) * δp * dΩ\n end\n end\n # Loop over the `p`-test functions to calculate the `p-`u` and `p`-`p` blocks\n for i in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, i)\n fe[BlockIndex((pblock), (i))] += (δp * ∂Ψ∂p) * dΩ\n\n for j in 1:n_basefuncs_u\n ∇δuj = shape_gradient(cellvalues_u, qp, j)\n Ke[BlockIndex((pblock, ublock), (i, j))] += ∇δuj ⊡ ∂²Ψ∂F∂p * δp * dΩ\n end\n for j in 1:n_basefuncs_p\n δp = shape_value(cellvalues_p, qp, j)\n Ke[BlockIndex((pblock, pblock), (i, j))] += δp * ∂²Ψ∂p² * δp * dΩ\n end\n end\n end\n return\nend;\n\nfunction assemble_global!(\n K::SparseMatrixCSC, f, cellvalues_u::CellValues,\n cellvalues_p::CellValues, dh::DofHandler, mp::NeoHooke, w\n )\n nu = getnbasefunctions(cellvalues_u)\n np = getnbasefunctions(cellvalues_p)\n\n # start_assemble resets K and f\n fe = BlockedArray(zeros(nu + np), [nu, np]) # local force vector\n ke = BlockedArray(zeros(nu + np, nu + np), [nu, np], [nu, np]) # local stiffness matrix\n\n assembler = start_assemble(K, f)\n # Loop over all cells in the grid\n for cell in CellIterator(dh)\n global_dofs = celldofs(cell)\n global_dofsu = global_dofs[1:nu] # first nu dofs are displacement\n global_dofsp = global_dofs[(nu + 1):end] # last np dofs are pressure\n @assert size(global_dofs, 1) == nu + np # sanity check\n ue = w[global_dofsu] # displacement dofs for the current cell\n pe = w[global_dofsp] # pressure dofs for the current cell\n assemble_element!(ke, fe, cell, cellvalues_u, cellvalues_p, mp, ue, pe)\n assemble!(assembler, global_dofs, ke, fe)\n end\n return\nend;\n\nfunction solve(interpolation_u, interpolation_p)\n\n # import the mesh\n grid = importTestGrid()\n\n # Material parameters\n μ = 1.0\n λ = 1.0e4 * μ\n mp = NeoHooke(μ, λ)\n\n # Create the DofHandler and CellValues\n dh = create_dofhandler(grid, interpolation_u, interpolation_p)\n cellvalues_u, cellvalues_p, facetvalues_u = create_values(interpolation_u, interpolation_p)\n\n # Create the DirichletBCs\n dbc = create_bc(dh)\n\n # Pre-allocation of vectors for the solution and Newton increments\n _ndofs = ndofs(dh)\n w = zeros(_ndofs)\n ΔΔw = zeros(_ndofs)\n apply!(w, dbc)\n\n # Create the sparse matrix and residual vector\n K = allocate_matrix(dh)\n f = zeros(_ndofs)\n\n # We run the simulation parameterized by a time like parameter. `Tf` denotes the final value\n # of this parameter, and Δt denotes its increment in each step\n Tf = 2.0\n Δt = 0.1\n NEWTON_TOL = 1.0e-8\n\n pvd = paraview_collection(\"hyperelasticity_incomp_mixed\")\n for (step, t) in enumerate(0.0:Δt:Tf)\n # Perform Newton iterations\n Ferrite.update!(dbc, t)\n apply!(w, dbc)\n newton_itr = -1\n prog = ProgressMeter.ProgressThresh(NEWTON_TOL; desc = \"Solving @ time $t of $Tf;\")\n fill!(ΔΔw, 0.0)\n while true\n newton_itr += 1\n assemble_global!(K, f, cellvalues_u, cellvalues_p, dh, mp, w)\n norm_res = norm(f[Ferrite.free_dofs(dbc)])\n apply_zero!(K, f, dbc)\n # Only display output at specific load steps\n if t % (5 * Δt) == 0\n ProgressMeter.update!(prog, norm_res; showvalues = [(:iter, newton_itr)])\n end\n if norm_res < NEWTON_TOL\n break\n elseif newton_itr > 30\n error(\"Reached maximum Newton iterations, aborting\")\n end\n # Compute the incremental `dof`-vector (both displacement and pressure)\n ΔΔw .= K \\ f\n\n apply_zero!(ΔΔw, dbc)\n w .-= ΔΔw\n end\n\n # Save the solution fields\n VTKGridFile(\"hyperelasticity_incomp_mixed_$step\", grid) do vtk\n write_solution(vtk, dh, w)\n pvd[t] = vtk\n end\n end\n vtk_save(pvd)\n vol_def = calculate_volume_deformed_mesh(w, dh, cellvalues_u)\n print(\"Deformed volume is $vol_def\")\n return vol_def\nend;\n\nquadratic_u = Lagrange{RefTetrahedron, 2}()^3\nlinear_p = Lagrange{RefTetrahedron, 1}()\nvol_def = solve(quadratic_u, linear_p)","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"","category":"page"},{"location":"gallery/quasi_incompressible_hyperelasticity/","page":"Nearly Incompressible Hyperelasticity","title":"Nearly Incompressible Hyperelasticity","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/utils/","page":"Development utility functions","title":"Development utility functions","text":"DocTestSetup = :(using Ferrite)","category":"page"},{"location":"reference/utils/#Development-utility-functions","page":"Development utility functions","title":"Development utility functions","text":"","category":"section"},{"location":"reference/utils/","page":"Development utility functions","title":"Development utility functions","text":"Ferrite.debug_mode","category":"page"},{"location":"reference/utils/#Ferrite.debug_mode","page":"Development utility functions","title":"Ferrite.debug_mode","text":"Ferrite.debug_mode(; enable=true)\n\nHelper to turn on (enable=true) or off (enable=false) debug expressions in Ferrite.\n\nDebug mode influences Ferrite.@debug expr: when debug mode is enabled, expr is evaluated, and when debug mode is disabled expr is ignored.\n\n\n\n\n\n","category":"function"}] } diff --git a/previews/PR1096/topics/FEValues/index.html b/previews/PR1096/topics/FEValues/index.html index fc6179597b..1fe5311e45 100644 --- a/previews/PR1096/topics/FEValues/index.html +++ b/previews/PR1096/topics/FEValues/index.html @@ -49,17 +49,24 @@ - J^{-T}_{mn} \mathcal{H}_{mnl} J^{-1}_{lj} \frac{J_{ik} \hat{N}_k}{\det(\boldsymbol{J})} + \frac{J_{ik}}{\det(\boldsymbol{J})} \frac{\mathrm{d} \hat{N}_k}{\mathrm{d} \xi_l} J_{lj}^{-1} \end{align*}\]

Walkthrough: Creating SimpleCellValues

In the following, we walk through how to create a SimpleCellValues type which works similar to Ferrite's CellValues, but is not performance optimized and not as general. The main purpose is to explain how the CellValues works for the standard case of IdentityMapping described above. Please note that several internal functions are used, and these may change without a major version increment. Please see the Developer documentation for their documentation.

We start by including Ferrite and Test (to check our implementation).

using Ferrite, Test

Then, we define a simple version of the cell values object, which only supports

  • Scalar interpolations
  • Identity mapping from reference to physical cell.
  • The cell shape has the same dimension as the physical space (excludes so-called embedded cells).
struct SimpleCellValues{T, dim} <: Ferrite.AbstractCellValues
-    N::Matrix{T}             # Precalculated shape values, N[i, q_point] where i is the
+    # Precalculated shape values, N[i, q_point] where i is the
     # shape function number and q_point the integration point
-    dNdξ::Matrix{Vec{dim, T}} # Precalculated shape gradients in the reference domain, dNdξ[i, q_point]
-    dNdx::Matrix{Vec{dim, T}} # Cache for shape gradients in the physical domain, dNdx[i, q_point]
-    M::Matrix{T}             # Precalculated geometric shape values, M[j, q_point] where j is the
+    N::Matrix{T}
+    # Precalculated shape gradients in the reference domain, dNdξ[i, q_point]
+    dNdξ::Matrix{Vec{dim, T}}
+    # Cache for shape gradients in the physical domain, dNdx[i, q_point]
+    dNdx::Matrix{Vec{dim, T}}
+    # Precalculated geometric shape values, M[j, q_point] where j is the
     # geometric shape function number
-    dMdξ::Matrix{Vec{dim, T}} # Precalculated geometric shape gradients, dMdξ[j, q_point]
-    weights::Vector{T}       # Given quadrature weights in the reference domain, weights[q_point]
-    detJdV::Vector{T}        # Cache for quadrature weights in the physical domain, detJdV[q_point], i.e.
+    M::Matrix{T}
+    # Precalculated geometric shape gradients, dMdξ[j, q_point]
+    dMdξ::Matrix{Vec{dim, T}}
+    # Given quadrature weights in the reference domain, weights[q_point]
+    weights::Vector{T}
+    # Cache for quadrature weights in the physical domain, detJdV[q_point], i.e.
     # det(J)*weight[q_point], where J is the jacobian of the geometric mapping
     # at the quadrature point, q_point.
+    detJdV::Vector{T}
 end;

Next, we create a constructor with the same input as CellValues

function SimpleCellValues(qr::QuadratureRule, ip_fun::Interpolation, ip_geo::Interpolation)
     dim = Ferrite.getrefdim(ip_fun)
     # Quadrature weights and coordinates (in reference cell)
@@ -118,4 +125,4 @@
 reinit!(cv, x);

If we now pretend we are inside an element routine and have a vector of element degree of freedom values, ue. Then, we can check that our function values and gradients match Ferrite's builtin CellValues:

ue = rand(getnbasefunctions(simple_cv))
 q_point = 2
 @test function_value(cv, q_point, ue) ≈ function_value(simple_cv, q_point, ue)
-@test function_gradient(cv, q_point, ue) ≈ function_gradient(simple_cv, q_point, ue)
Test Passed

Further reading

+@test function_gradient(cv, q_point, ue) ≈ function_gradient(simple_cv, q_point, ue)
Test Passed

Further reading

diff --git a/previews/PR1096/topics/assembly/index.html b/previews/PR1096/topics/assembly/index.html index e85f382357..7f4461df08 100644 --- a/previews/PR1096/topics/assembly/index.html +++ b/previews/PR1096/topics/assembly/index.html @@ -90,4 +90,4 @@ @time assemble_system!(assemble_v4, K, dh, cellvalues)

We then obtain the following results (running on the same machine as above):

12.175625 seconds (719.99 k allocations: 149.809 GiB, 11.59% gc time)
  0.009313 seconds (8 allocations: 928 bytes)
  0.006055 seconds (8 allocations: 928 bytes)
- 0.004530 seconds (10 allocations: 1.062 KiB)

This follows the same trend as for the benchmarks for individual cell assembly and shows that the efficiency of the assembly strategy is crucial for the overall performance of the program. In particular this benchmark shows that allocations in such a tight loop from the first strategy is very costly and puts a strain on the garbage collector: 11% of the time is spent in GC instead of crunching numbers.

It should of course be noted that the more expensive the element routine is, the less the performance of the assembly strategy matters for the total runtime. However, there are no reason not to use the fastest method given that it is readily available in Ferrite.

+ 0.004530 seconds (10 allocations: 1.062 KiB)

This follows the same trend as for the benchmarks for individual cell assembly and shows that the efficiency of the assembly strategy is crucial for the overall performance of the program. In particular this benchmark shows that allocations in such a tight loop from the first strategy is very costly and puts a strain on the garbage collector: 11% of the time is spent in GC instead of crunching numbers.

It should of course be noted that the more expensive the element routine is, the less the performance of the assembly strategy matters for the total runtime. However, there are no reason not to use the fastest method given that it is readily available in Ferrite.

diff --git a/previews/PR1096/topics/boundary_conditions/index.html b/previews/PR1096/topics/boundary_conditions/index.html index 95753d9d52..018b5977db 100644 --- a/previews/PR1096/topics/boundary_conditions/index.html +++ b/previews/PR1096/topics/boundary_conditions/index.html @@ -78,4 +78,4 @@ grid = generate_grid(Quadrilateral, (10,10)) dh = DofHandler(grid); add!(dh, :u, 2); add!(dh, :p, 1); close!(dh) u = zeros(ndofs(dh)) -apply_analytical!(u, dh, :p, x -> ρ * g * x[2])

See also Transient heat equation for one example.

Consistency

apply_analytical! does not enforce consistency of the applied solution with the system of equations. Some problems, like for example differential-algebraic systems of equations (DAEs) need extra care during initialization. We refer to the paper "Consistent Initial Condition Calculation for Differential-Algebraic Systems" by Brown et al. for more details on this matter.

+apply_analytical!(u, dh, :p, x -> ρ * g * x[2])

See also Transient heat equation for one example.

Consistency

apply_analytical! does not enforce consistency of the applied solution with the system of equations. Some problems, like for example differential-algebraic systems of equations (DAEs) need extra care during initialization. We refer to the paper "Consistent Initial Condition Calculation for Differential-Algebraic Systems" by Brown et al. for more details on this matter.

diff --git a/previews/PR1096/topics/constraints/index.html b/previews/PR1096/topics/constraints/index.html index 542a2909da..ecf2c1b703 100644 --- a/previews/PR1096/topics/constraints/index.html +++ b/previews/PR1096/topics/constraints/index.html @@ -21,4 +21,4 @@ apply_zero!(Δa, ch) # Change the constrained values in `Δa` such that `a-Δa` # fulfills constraints if `a` did. a .-= Δa -end +end diff --git a/previews/PR1096/topics/degrees_of_freedom/index.html b/previews/PR1096/topics/degrees_of_freedom/index.html index 16f7af37d1..6b4f0582c5 100644 --- a/previews/PR1096/topics/degrees_of_freedom/index.html +++ b/previews/PR1096/topics/degrees_of_freedom/index.html @@ -12,4 +12,4 @@ :p, Lagrange{RefTriangle, 1}() :u, Lagrange{RefTriangle, 1}()^2 Dofs per cell: 9 - Total dofs: 1323

Ordering of Dofs

Todo

Describe dof ordering within elements (vertices -> edges -> faces -> volumes) and dof_range. Describe (global) dof renumbering

+ Total dofs: 1323

Ordering of Dofs

Todo

Describe dof ordering within elements (vertices -> edges -> faces -> volumes) and dof_range. Describe (global) dof renumbering

diff --git a/previews/PR1096/topics/export/index.html b/previews/PR1096/topics/export/index.html index ef431986ba..3a0a8a5fcf 100644 --- a/previews/PR1096/topics/export/index.html +++ b/previews/PR1096/topics/export/index.html @@ -19,4 +19,4 @@ "my_results_2.vtu" "my_results_3.vtu" "my_results_4.vtu" - "my_results_5.vtu"

See Transient heat equation for an example

+ "my_results_5.vtu"

See Transient heat equation for an example

diff --git a/previews/PR1096/topics/fe_intro/index.html b/previews/PR1096/topics/fe_intro/index.html index cf7e1a7d6d..6f81c0c598 100644 --- a/previews/PR1096/topics/fe_intro/index.html +++ b/previews/PR1096/topics/fe_intro/index.html @@ -12,4 +12,4 @@ \int_{\Gamma_\mathrm{N}} \phi_i \, q^\mathrm{p} \, \mathrm{d}\Gamma + \int_{\Omega_\mathrm{h}} \phi_i \, f \, \mathrm{d}\Omega \, .\]

Finally we also need to take care of the Dirichlet boundary conditions. These are enforced by setting the corresponding $\hat{u}_i$ to the prescribed values and eliminating the associated equations from the system. Now, solving this equation system yields the nodal values and thus an approximation to the true solution.

Notes on the implementation

In practice, the shape functions $\phi_i$ are only non-zero on parts of the domain $\Omega_\mathrm{h}$. Thus, the integrals are evaluated on sub-domains, called elements or cells.

Each cell gives a contribution to the global stiffness matrix and force vector. The process of constructing the system of equations is also called assembly. For clarification, let us rewrite the formula for the stiffness matrix entries as follows:

\[(\underline{\underline{K}})_{ij} = \int_{\Omega_\mathrm{h}} \nabla \phi_i \cdot (k \nabla \phi_j) \mathrm{d}\Omega = \sum_{E \in \Omega_\mathrm{h}} \int_E \nabla \phi_i \cdot (k \nabla \phi_j) \mathrm{d}\Omega \, .\]

This formulation underlines the element-centric perspective of finite element methods and reflects how it is usually implemented in software.

Computing the element integrals by hand can become a tedious task. To avoid this issue we approximate the element integrals with a technique called numerical integration. Skipping any of the mathematical details, the basic idea is to evaluate the function under the integral at specific points and weighting the evaluations accordingly, such that their sum approximates the volume properly. A very nice feature of these techniques is, that under quite general circumstances the formula is not just an approximation, but the exact evaluation of the integral. To avoid the recomputation of the just mentioned evaluation positions of the integral for each individual element, we perform a coordinate transformation onto a so-called reference element. Formally we write

\[ \int_E \nabla \phi_i \cdot (k \nabla \phi_j) \mathrm{d}\Omega - \approx \sum_q \nabla \phi_i(\textbf{x}_q) \cdot (k(\textbf{x}_q) \nabla \phi_j(\textbf{x}_q)) \, w_q \, \textrm{det}(J(\textbf{x}_q)) \, ,\]

where $J$ is the Jacobian of the coordinate transformation function. The computation of the transformation, weights, positions and of the Jacobi determinant is handled by Ferrite. On an intuitive level, and to explain the notation used in the implementation, we think of

\[ \mathrm{d}\Omega \approx \, w \, \textrm{det}(J)\]

being the chosen approximation when changing from the integral to the finite summation.

For an example of the implementation to solve a heat problem with Ferrite check out this thoroughly commented example.

More details

We finally want to note that this quick introduction barely scratches the surface of the finite element method. Also, we presented some things in a simplified way for the sake of keeping this article short and concise. There is a large corpus of literature and online tutorials containing more details about the finite element method. To give a few recommendations there is:

  • Hans Petter Langtangen's Script
  • Wolfgang Bangerth's Lecture Series
  • Introduction to the Finite Element Method by Niels Ottosen and Hans Petersson
  • The Finite Element Method for Elliptic Problems by Philippe Ciarlet
  • Finite Elements: Theory, Fast Solvers, and Applications in Elasticity Theory by Dietrich Braess
  • An Analysis of the Finite Element Method by Gilbert Strang and George Fix
  • Finite Element Procedures by Klaus-Jürgen Bathe
  • The Finite Element Method: Its Basis and Fundamentals by Olgierd Cecil Zienkiewicz, Robert Taylor and J.Z. Zhu
  • Higher-Order Finite Element Methods by Pavel Šolín, Karel Segeth and Ivo Doležel

This list is neither meant to be exhaustive, nor does the absence of a work mean that it is in any way bad or not recommendable. The ordering of the articles also has no particular meaning.

+ \approx \sum_q \nabla \phi_i(\textbf{x}_q) \cdot (k(\textbf{x}_q) \nabla \phi_j(\textbf{x}_q)) \, w_q \, \textrm{det}(J(\textbf{x}_q)) \, ,\]

where $J$ is the Jacobian of the coordinate transformation function. The computation of the transformation, weights, positions and of the Jacobi determinant is handled by Ferrite. On an intuitive level, and to explain the notation used in the implementation, we think of

\[ \mathrm{d}\Omega \approx \, w \, \textrm{det}(J)\]

being the chosen approximation when changing from the integral to the finite summation.

For an example of the implementation to solve a heat problem with Ferrite check out this thoroughly commented example.

More details

We finally want to note that this quick introduction barely scratches the surface of the finite element method. Also, we presented some things in a simplified way for the sake of keeping this article short and concise. There is a large corpus of literature and online tutorials containing more details about the finite element method. To give a few recommendations there is:

  • Hans Petter Langtangen's Script
  • Wolfgang Bangerth's Lecture Series
  • Introduction to the Finite Element Method by Niels Ottosen and Hans Petersson
  • The Finite Element Method for Elliptic Problems by Philippe Ciarlet
  • Finite Elements: Theory, Fast Solvers, and Applications in Elasticity Theory by Dietrich Braess
  • An Analysis of the Finite Element Method by Gilbert Strang and George Fix
  • Finite Element Procedures by Klaus-Jürgen Bathe
  • The Finite Element Method: Its Basis and Fundamentals by Olgierd Cecil Zienkiewicz, Robert Taylor and J.Z. Zhu
  • Higher-Order Finite Element Methods by Pavel Šolín, Karel Segeth and Ivo Doležel

This list is neither meant to be exhaustive, nor does the absence of a work mean that it is in any way bad or not recommendable. The ordering of the articles also has no particular meaning.

diff --git a/previews/PR1096/topics/grid/index.html b/previews/PR1096/topics/grid/index.html index f820ad4a68..8c66da6a37 100644 --- a/previews/PR1096/topics/grid/index.html +++ b/previews/PR1096/topics/grid/index.html @@ -19,4 +19,4 @@ Ferrite.getnnodes(grid::SmallGrid) = length(grid.nodes_test) Ferrite.get_coordinate_eltype(::SmallGrid) = Float64 Ferrite.get_coordinate_type(::SmallGrid{dim}) where dim = Vec{dim,Float64} -Ferrite.nnodes_per_cell(grid::SmallGrid, i::Int=1) = Ferrite.nnodes(grid.cells_test[i])

These definitions make many of Ferrite functions work out of the box, e.g. you can now call getcoordinates(grid, cellid) on the SmallGrid.

Now, you would be able to assemble the heat equation example over the new custom SmallGrid type. Note that this particular subtype isn't able to handle boundary entity sets and so, you can't describe boundaries with it. In order to use boundaries, e.g. for Dirichlet constraints in the ConstraintHandler, you would need to dispatch the AbstractGrid sets utility functions on SmallGrid.

Topology

Ferrite.jl's Grid type offers experimental features w.r.t. topology information. The functions getneighborhood and facetskeleton are the interface to obtain topological information. The getneighborhood can construct lists of directly connected entities based on a given entity (CellIndex, FacetIndex, FaceIndex, EdgeIndex, or VertexIndex). The facetskeleton function can be used to evaluate integrals over material interfaces or computing element interface values such as jumps.

+Ferrite.nnodes_per_cell(grid::SmallGrid, i::Int=1) = Ferrite.nnodes(grid.cells_test[i])

These definitions make many of Ferrite functions work out of the box, e.g. you can now call getcoordinates(grid, cellid) on the SmallGrid.

Now, you would be able to assemble the heat equation example over the new custom SmallGrid type. Note that this particular subtype isn't able to handle boundary entity sets and so, you can't describe boundaries with it. In order to use boundaries, e.g. for Dirichlet constraints in the ConstraintHandler, you would need to dispatch the AbstractGrid sets utility functions on SmallGrid.

Topology

Ferrite.jl's Grid type offers experimental features w.r.t. topology information. The functions getneighborhood and facetskeleton are the interface to obtain topological information. The getneighborhood can construct lists of directly connected entities based on a given entity (CellIndex, FacetIndex, FaceIndex, EdgeIndex, or VertexIndex). The facetskeleton function can be used to evaluate integrals over material interfaces or computing element interface values such as jumps.

diff --git a/previews/PR1096/topics/index.html b/previews/PR1096/topics/index.html index ea1a63bdb7..f3f277fafd 100644 --- a/previews/PR1096/topics/index.html +++ b/previews/PR1096/topics/index.html @@ -1,2 +1,2 @@ -Topic guide overview · Ferrite.jl
+Topic guide overview · Ferrite.jl
diff --git a/previews/PR1096/topics/my_results_1.vtu b/previews/PR1096/topics/my_results_1.vtu index 2047fc4bc6..180eff64c3 100644 Binary files a/previews/PR1096/topics/my_results_1.vtu and b/previews/PR1096/topics/my_results_1.vtu differ diff --git a/previews/PR1096/topics/my_results_2.vtu b/previews/PR1096/topics/my_results_2.vtu index 2047fc4bc6..180eff64c3 100644 Binary files a/previews/PR1096/topics/my_results_2.vtu and b/previews/PR1096/topics/my_results_2.vtu differ diff --git a/previews/PR1096/topics/my_results_3.vtu b/previews/PR1096/topics/my_results_3.vtu index 2047fc4bc6..180eff64c3 100644 Binary files a/previews/PR1096/topics/my_results_3.vtu and b/previews/PR1096/topics/my_results_3.vtu differ diff --git a/previews/PR1096/topics/my_results_4.vtu b/previews/PR1096/topics/my_results_4.vtu index 2047fc4bc6..180eff64c3 100644 Binary files a/previews/PR1096/topics/my_results_4.vtu and b/previews/PR1096/topics/my_results_4.vtu differ diff --git a/previews/PR1096/topics/my_results_5.vtu b/previews/PR1096/topics/my_results_5.vtu index 2047fc4bc6..180eff64c3 100644 Binary files a/previews/PR1096/topics/my_results_5.vtu and b/previews/PR1096/topics/my_results_5.vtu differ diff --git a/previews/PR1096/topics/my_solution.vtu b/previews/PR1096/topics/my_solution.vtu index 2047fc4bc6..180eff64c3 100644 Binary files a/previews/PR1096/topics/my_solution.vtu and b/previews/PR1096/topics/my_solution.vtu differ diff --git a/previews/PR1096/topics/reference_shapes/index.html b/previews/PR1096/topics/reference_shapes/index.html index d7f4567713..be8d03bec6 100644 --- a/previews/PR1096/topics/reference_shapes/index.html +++ b/previews/PR1096/topics/reference_shapes/index.html @@ -1,2 +1,2 @@ -Reference shapes · Ferrite.jl

Reference shapes

The reference shapes in Ferrite are used to define grid cells, function interpolations (i.e. shape functions), and quadrature rules. Currently, the following reference shapes are defined

  • RefLine
  • RefTriangle
  • RefQuadrilateral
  • RefTetrahedron
  • RefHexahedron
  • RefPrism
  • RefPyramid

Entity naming

Ferrite denotes the entities of a reference shape as follows

EntityDescription
Vertex0-dimensional entity in the reference shape.
Edge1-dimensional entity connecting two vertices.
Face2-dimensional entity enclosed by edges.
Volume3-dimensional entity enclosed by faces.

Note that a node in Ferrite is not the same as a vertex. Vertices denote endpoints of edges, while nodes may also be located in the middle of edges (e.g. for a QuadraticLine cell).

To write dimensionally independent code, Ferrite also denotes entities by their codimension, defined relative the reference shape dimension. Specifically, Ferrite has the entities

EntityDescription
Cell0-codimensional entity, i.e. the same as the reference shape.
Facet1-codimensional entity defining the boundary of cells.

Standard use cases mostly deal with these codimensional entities, such as CellValues and FacetValues.

Definition of codimension

In Ferrite, codimension is defined relative to the reference dimension of the specific entity. Note that other finite element codes may define it differently (e.g. relative the highest reference dimension in the grid).

Entity numbering

Each reference shape defines the numbering of its vertices, edges, and faces entities, where the edge and face entities are defined from their vertex numbers.

Note

The numbering and identification of entities is (mostly) for internal use and typically not something users of Ferrite need to interact with.

Example

The RefQuadrilateral is defined on the domain $[-1, 1] \times [-1, 1]$ in the local $\xi_1-\xi_2$ coordinate system.

local element

The vertices of a RefQuadrilateral are then

Ferrite.reference_vertices(RefQuadrilateral)
(1, 2, 3, 4)

and its edges are then defined as

Ferrite.reference_edges(RefQuadrilateral)
((1, 2), (2, 3), (3, 4), (4, 1))

where the numbers refer to the vertex number. Finally, this reference shape is 2-dimensional, so it only has a single face, corresponding to the cell itself,

Ferrite.reference_faces(RefQuadrilateral)
((1, 2, 3, 4),)

also defined in terms of its vertices.

As this is a 2-dimensional reference shape, the facets are the edges, i.e.

Ferrite.reference_facets(RefQuadrilateral)
((1, 2), (2, 3), (3, 4), (4, 1))
Not public API

The functions reference_vertices, reference_edges, reference_faces, and reference_facets are not public and only shown here to explain the numbering concept. The specific ordering may also change, and is therefore only documented in the Developer documentation.

+Reference shapes · Ferrite.jl

Reference shapes

The reference shapes in Ferrite are used to define grid cells, function interpolations (i.e. shape functions), and quadrature rules. Currently, the following reference shapes are defined

  • RefLine
  • RefTriangle
  • RefQuadrilateral
  • RefTetrahedron
  • RefHexahedron
  • RefPrism
  • RefPyramid

Entity naming

Ferrite denotes the entities of a reference shape as follows

EntityDescription
Vertex0-dimensional entity in the reference shape.
Edge1-dimensional entity connecting two vertices.
Face2-dimensional entity enclosed by edges.
Volume3-dimensional entity enclosed by faces.

Note that a node in Ferrite is not the same as a vertex. Vertices denote endpoints of edges, while nodes may also be located in the middle of edges (e.g. for a QuadraticLine cell).

To write dimensionally independent code, Ferrite also denotes entities by their codimension, defined relative the reference shape dimension. Specifically, Ferrite has the entities

EntityDescription
Cell0-codimensional entity, i.e. the same as the reference shape.
Facet1-codimensional entity defining the boundary of cells.

Standard use cases mostly deal with these codimensional entities, such as CellValues and FacetValues.

Definition of codimension

In Ferrite, codimension is defined relative to the reference dimension of the specific entity. Note that other finite element codes may define it differently (e.g. relative the highest reference dimension in the grid).

Entity numbering

Each reference shape defines the numbering of its vertices, edges, and faces entities, where the edge and face entities are defined from their vertex numbers.

Note

The numbering and identification of entities is (mostly) for internal use and typically not something users of Ferrite need to interact with.

Example

The RefQuadrilateral is defined on the domain $[-1, 1] \times [-1, 1]$ in the local $\xi_1-\xi_2$ coordinate system.

local element

The vertices of a RefQuadrilateral are then

Ferrite.reference_vertices(RefQuadrilateral)
(1, 2, 3, 4)

and its edges are then defined as

Ferrite.reference_edges(RefQuadrilateral)
((1, 2), (2, 3), (3, 4), (4, 1))

where the numbers refer to the vertex number. Finally, this reference shape is 2-dimensional, so it only has a single face, corresponding to the cell itself,

Ferrite.reference_faces(RefQuadrilateral)
((1, 2, 3, 4),)

also defined in terms of its vertices.

As this is a 2-dimensional reference shape, the facets are the edges, i.e.

Ferrite.reference_facets(RefQuadrilateral)
((1, 2), (2, 3), (3, 4), (4, 1))
Not public API

The functions reference_vertices, reference_edges, reference_faces, and reference_facets are not public and only shown here to explain the numbering concept. The specific ordering may also change, and is therefore only documented in the Developer documentation.

diff --git a/previews/PR1096/topics/sparse_matrix/index.html b/previews/PR1096/topics/sparse_matrix/index.html index 6bd4dfe5eb..529f1980a8 100644 --- a/previews/PR1096/topics/sparse_matrix/index.html +++ b/previews/PR1096/topics/sparse_matrix/index.html @@ -3,4 +3,4 @@ 0.0 0.0 ⋅ ⋅ 0.0 0.0 0.0 ⋅ ⋅ 0.0 0.0 0.0 - ⋅ ⋅ 0.0 0.0

Moreover, if the problem is solved with periodic boundary conditions, for example by constraining the value on the right side to the value on the left side, there will be additional couplings. In the example above, this means that DoF 4 should be equal to DoF

  1. Since DoF 4 is constrained it has to be eliminated from the system. Existing entries

that include DoF 4 are (3, 4), (4, 3), and (4, 4). Given the simple constraint in this case we can simply replace DoF 4 with DoF 1 in these entries and we end up with entries (3, 1), (1, 3), and (1, 1). This results in two new entries: (3, 1) and (1, 3) (entry (1, 1) is already included).

Creating sparsity patterns

Creating a sparsity pattern can be quite expensive if not done properly and therefore Ferrite provides efficient methods and data structures for this. In general the sparsity pattern is not known in advance and has to be created incrementally. To make this incremental construction efficient it is necessary to use a dynamic data structure which allow for fast insertions.

The sparsity pattern also serves as a "matrix builder". When all entries are inserted into the sparsity pattern the dynamic data structure is typically converted, or "compressed", into a sparse matrix format such as e.g. the compressed sparse row (CSR) format or the compressed sparse column (CSC) format, where the latter is the default sparse matrix type implemented in the SparseArrays standard library. These matrix formats allow for fast linear algebra operations, such as factorizations and matrix-vector multiplications, that are needed when the linear system is solved. See Instantiating the sparse matrix for more details.

In summary, a dynamic structure is more efficient when incrementally building the pattern by inserting new entries, and a static or compressed structure is more efficient for linear algebra operations.

Basic sparsity patterns construction

Working with the sparsity pattern explicitly is in many cases not necessary. For basic usage (e.g. when only one matrix needed, when no customization of the pattern is required, etc) there exist convenience methods of allocate_matrix that return the matrix directly. Most examples in this documentation don't deal with the sparsity pattern explicitly because the basic method suffice. See also Instantiating the sparse matrix for more details.

Custom sparsity pattern construction

In more advanced cases there might be a need for more fine grained control of the sparsity pattern. The following steps are typically taken when constructing a sparsity pattern in Ferrite:

  1. Initialize an empty pattern: This can be done by either using the init_sparsity_pattern(dh) function or by using a constructor directly. init_sparsity_pattern will return a default pattern type that is compatible with the DofHandler. In some cases you might require another type of pattern (for example a blocked pattern, see Blocked sparsity pattern) and in that case you can use the constructor directly.

  2. Add entries to the pattern: There are a number of functions that add entries to the pattern:

    • add_sparsity_entries! is a convenience method for performing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries! after each other (see below).
    • add_cell_entries! adds entries for all couplings between the DoFs within each element. These entries correspond to assembling the standard element matrix and is thus almost always required.
    • add_interface_entries! adds entries for couplings between the DoFs in neighboring elements. These entries are required when integrating along internal interfaces between elements (e.g. for discontinuous Galerkin methods).
    • add_constraint_entries! adds entries required from constraints and boundary conditions in the ConstraintHandler. Note that this operation depends on existing entries in the pattern and must be called as the last operation on the pattern.
    • Ferrite.add_entry! adds a single entry to the pattern. This can be used if you need to add custom entries that are not covered by the other functions.
  3. Instantiate the matrix: A sparse matrix can be created from the sparsity pattern using allocate_matrix, see Instantiating the sparse matrix below for more details.

Increasing the sparsity

By default, when creating a sparsity pattern, it is assumed that each DoF within an element couple with with all other DoFs in the element.

Todo
  • Discuss the coupling keyword argument.
  • Discuss the keep_constrained keyword argument.

Blocked sparsity pattern

Todo

Discuss BlockSparsityPattern and BlockArrays extension.

Instantiating the sparse matrix

As mentioned above, for many simple cases there is no need to work with the sparsity pattern directly and using methods of allocate_matrix that take the DofHandler as input is enough, for example:

K = allocate_matrix(dh, ch)

allocate_matrix is also used to instantiate a matrix from a sparsity pattern, for example:

K = allocate_matrix(sp)
Multiple matrices with the same pattern

For some problems there is a need for multiple matrices with the same sparsity pattern, for example a mass matrix and a stiffness matrix. In this case it is more efficient to create the sparsity pattern once and then instantiate both matrices from it.

  • 1Structurally nonzero means that there is a possibility of a nonzero value even though the computed value might become zero in the end for various reasons.
  • 2At least for most practical problems using low order interpolations.
+ ⋅ ⋅ 0.0 0.0

Moreover, if the problem is solved with periodic boundary conditions, for example by constraining the value on the right side to the value on the left side, there will be additional couplings. In the example above, this means that DoF 4 should be equal to DoF

  1. Since DoF 4 is constrained it has to be eliminated from the system. Existing entries

that include DoF 4 are (3, 4), (4, 3), and (4, 4). Given the simple constraint in this case we can simply replace DoF 4 with DoF 1 in these entries and we end up with entries (3, 1), (1, 3), and (1, 1). This results in two new entries: (3, 1) and (1, 3) (entry (1, 1) is already included).

Creating sparsity patterns

Creating a sparsity pattern can be quite expensive if not done properly and therefore Ferrite provides efficient methods and data structures for this. In general the sparsity pattern is not known in advance and has to be created incrementally. To make this incremental construction efficient it is necessary to use a dynamic data structure which allow for fast insertions.

The sparsity pattern also serves as a "matrix builder". When all entries are inserted into the sparsity pattern the dynamic data structure is typically converted, or "compressed", into a sparse matrix format such as e.g. the compressed sparse row (CSR) format or the compressed sparse column (CSC) format, where the latter is the default sparse matrix type implemented in the SparseArrays standard library. These matrix formats allow for fast linear algebra operations, such as factorizations and matrix-vector multiplications, that are needed when the linear system is solved. See Instantiating the sparse matrix for more details.

In summary, a dynamic structure is more efficient when incrementally building the pattern by inserting new entries, and a static or compressed structure is more efficient for linear algebra operations.

Basic sparsity patterns construction

Working with the sparsity pattern explicitly is in many cases not necessary. For basic usage (e.g. when only one matrix needed, when no customization of the pattern is required, etc) there exist convenience methods of allocate_matrix that return the matrix directly. Most examples in this documentation don't deal with the sparsity pattern explicitly because the basic method suffice. See also Instantiating the sparse matrix for more details.

Custom sparsity pattern construction

In more advanced cases there might be a need for more fine grained control of the sparsity pattern. The following steps are typically taken when constructing a sparsity pattern in Ferrite:

  1. Initialize an empty pattern: This can be done by either using the init_sparsity_pattern(dh) function or by using a constructor directly. init_sparsity_pattern will return a default pattern type that is compatible with the DofHandler. In some cases you might require another type of pattern (for example a blocked pattern, see Blocked sparsity pattern) and in that case you can use the constructor directly.

  2. Add entries to the pattern: There are a number of functions that add entries to the pattern:

    • add_sparsity_entries! is a convenience method for performing the common task of calling add_cell_entries!, add_interface_entries!, and add_constraint_entries! after each other (see below).
    • add_cell_entries! adds entries for all couplings between the DoFs within each element. These entries correspond to assembling the standard element matrix and is thus almost always required.
    • add_interface_entries! adds entries for couplings between the DoFs in neighboring elements. These entries are required when integrating along internal interfaces between elements (e.g. for discontinuous Galerkin methods).
    • add_constraint_entries! adds entries required from constraints and boundary conditions in the ConstraintHandler. Note that this operation depends on existing entries in the pattern and must be called as the last operation on the pattern.
    • Ferrite.add_entry! adds a single entry to the pattern. This can be used if you need to add custom entries that are not covered by the other functions.
  3. Instantiate the matrix: A sparse matrix can be created from the sparsity pattern using allocate_matrix, see Instantiating the sparse matrix below for more details.

Increasing the sparsity

By default, when creating a sparsity pattern, it is assumed that each DoF within an element couple with with all other DoFs in the element.

Todo
  • Discuss the coupling keyword argument.
  • Discuss the keep_constrained keyword argument.

Blocked sparsity pattern

Todo

Discuss BlockSparsityPattern and BlockArrays extension.

Instantiating the sparse matrix

As mentioned above, for many simple cases there is no need to work with the sparsity pattern directly and using methods of allocate_matrix that take the DofHandler as input is enough, for example:

K = allocate_matrix(dh, ch)

allocate_matrix is also used to instantiate a matrix from a sparsity pattern, for example:

K = allocate_matrix(sp)
Multiple matrices with the same pattern

For some problems there is a need for multiple matrices with the same sparsity pattern, for example a mass matrix and a stiffness matrix. In this case it is more efficient to create the sparsity pattern once and then instantiate both matrices from it.

  • 1Structurally nonzero means that there is a possibility of a nonzero value even though the computed value might become zero in the end for various reasons.
  • 2At least for most practical problems using low order interpolations.
diff --git a/previews/PR1096/tutorials/computational_homogenization.ipynb b/previews/PR1096/tutorials/computational_homogenization.ipynb index df1ec19a52..4985d74707 100644 --- a/previews/PR1096/tutorials/computational_homogenization.ipynb +++ b/previews/PR1096/tutorials/computational_homogenization.ipynb @@ -667,32 +667,37 @@ "metadata": {} }, { - "outputs": [], + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "2×2×2×2 SymmetricTensor{4, 2, Float64, 9}:\n[:, :, 1, 1] =\n 4.30443e10 -809961.0\n -809961.0 1.43401e10\n\n[:, :, 2, 1] =\n -809961.0 1.16827e10\n 1.16827e10 -2.18543e6\n\n[:, :, 1, 2] =\n -809961.0 1.16827e10\n 1.16827e10 -2.18543e6\n\n[:, :, 2, 2] =\n 1.43401e10 -2.18543e6\n -2.18543e6 4.30725e10" + }, + "metadata": {}, + "execution_count": 17 + } + ], "cell_type": "code", "source": [ - "E_dirichlet = SymmetricTensor{4, 2}(\n", - " (i, j, k, l) -> begin\n", - " if k == l == 1\n", - " σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11\n", - " elseif k == l == 2\n", - " σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22\n", - " else\n", - " σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21\n", - " end\n", + "E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l\n", + " if k == l == 1\n", + " σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11\n", + " elseif k == l == 2\n", + " σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22\n", + " else\n", + " σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21\n", " end\n", - ")\n", + "end\n", "\n", - "E_periodic = SymmetricTensor{4, 2}(\n", - " (i, j, k, l) -> begin\n", - " if k == l == 1\n", - " σ̄.periodic[1][i, j]\n", - " elseif k == l == 2\n", - " σ̄.periodic[2][i, j]\n", - " else\n", - " σ̄.periodic[3][i, j]\n", - " end\n", + "E_periodic = SymmetricTensor{4, 2}() do i, j, k, l\n", + " if k == l == 1\n", + " σ̄.periodic[1][i, j]\n", + " elseif k == l == 2\n", + " σ̄.periodic[2][i, j]\n", + " else\n", + " σ̄.periodic[3][i, j]\n", " end\n", - ");" + "end" ], "metadata": {}, "execution_count": 17 diff --git a/previews/PR1096/tutorials/computational_homogenization.jl b/previews/PR1096/tutorials/computational_homogenization.jl index 1853fbe612..732dc1ad15 100644 --- a/previews/PR1096/tutorials/computational_homogenization.jl +++ b/previews/PR1096/tutorials/computational_homogenization.jl @@ -172,29 +172,25 @@ for i in 1:3 push!(σ̄.periodic, σ̄_i) end -E_dirichlet = SymmetricTensor{4, 2}( - (i, j, k, l) -> begin - if k == l == 1 - σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11 - elseif k == l == 2 - σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22 - else - σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21 - end +E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l + if k == l == 1 + σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11 + elseif k == l == 2 + σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22 + else + σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21 end -) +end -E_periodic = SymmetricTensor{4, 2}( - (i, j, k, l) -> begin - if k == l == 1 - σ̄.periodic[1][i, j] - elseif k == l == 2 - σ̄.periodic[2][i, j] - else - σ̄.periodic[3][i, j] - end +E_periodic = SymmetricTensor{4, 2}() do i, j, k, l + if k == l == 1 + σ̄.periodic[1][i, j] + elseif k == l == 2 + σ̄.periodic[2][i, j] + else + σ̄.periodic[3][i, j] end -); +end function matrix_volume_fraction(grid, cellvalues) V = 0.0 # Total volume diff --git a/previews/PR1096/tutorials/computational_homogenization/index.html b/previews/PR1096/tutorials/computational_homogenization/index.html index 1a103f324f..4462ccc6bc 100644 --- a/previews/PR1096/tutorials/computational_homogenization/index.html +++ b/previews/PR1096/tutorials/computational_homogenization/index.html @@ -174,29 +174,40 @@ proj = project(projector, σ_qp, qr) push!(σ.periodic, proj) push!(σ̄.periodic, σ̄_i) -end

The remaining thing is to compute the homogenized stiffness. As mentioned in the introduction we can find all the components from the average stress of the sensitivity fields that we have solved for

\[\mathsf{E}_{ijkl} = \bar{\sigma}_{ij}(\hat{\boldsymbol{u}}_{kl}).\]

So we have now already computed all the components, and just need to gather the data in a fourth order tensor:

E_dirichlet = SymmetricTensor{4, 2}(
-    (i, j, k, l) -> begin
-        if k == l == 1
-            σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11
-        elseif k == l == 2
-            σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22
-        else
-            σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21
-        end
+end

The remaining thing is to compute the homogenized stiffness. As mentioned in the introduction we can find all the components from the average stress of the sensitivity fields that we have solved for

\[\mathsf{E}_{ijkl} = \bar{\sigma}_{ij}(\hat{\boldsymbol{u}}_{kl}).\]

So we have now already computed all the components, and just need to gather the data in a fourth order tensor:

E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l
+    if k == l == 1
+        σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11
+    elseif k == l == 2
+        σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22
+    else
+        σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21
     end
-)
+end
 
-E_periodic = SymmetricTensor{4, 2}(
-    (i, j, k, l) -> begin
-        if k == l == 1
-            σ̄.periodic[1][i, j]
-        elseif k == l == 2
-            σ̄.periodic[2][i, j]
-        else
-            σ̄.periodic[3][i, j]
-        end
+E_periodic = SymmetricTensor{4, 2}() do i, j, k, l
+    if k == l == 1
+        σ̄.periodic[1][i, j]
+    elseif k == l == 2
+        σ̄.periodic[2][i, j]
+    else
+        σ̄.periodic[3][i, j]
     end
-);

We can check that the result are what we expect, namely that the stiffness with Dirichlet boundary conditions is higher than when using periodic boundary conditions, and that the Reuss assumption is an lower bound, and the Voigt assumption a upper bound. We first compute the volume fraction of the matrix, and then the Voigt and Reuss bounds:

function matrix_volume_fraction(grid, cellvalues)
+end
2×2×2×2 SymmetricTensor{4, 2, Float64, 9}:
+[:, :, 1, 1] =
+       4.30443e10  -809961.0
+ -809961.0               1.43401e10
+
+[:, :, 2, 1] =
+ -809961.0          1.16827e10
+       1.16827e10  -2.18543e6
+
+[:, :, 1, 2] =
+ -809961.0          1.16827e10
+       1.16827e10  -2.18543e6
+
+[:, :, 2, 2] =
+  1.43401e10  -2.18543e6
+ -2.18543e6    4.30725e10

We can check that the result are what we expect, namely that the stiffness with Dirichlet boundary conditions is higher than when using periodic boundary conditions, and that the Reuss assumption is an lower bound, and the Voigt assumption a upper bound. We first compute the volume fraction of the matrix, and then the Voigt and Reuss bounds:

function matrix_volume_fraction(grid, cellvalues)
     V = 0.0 # Total volume
     Vm = 0.0 # Volume of the matrix
     for c in CellIterator(grid)
@@ -402,29 +413,25 @@
     push!(σ̄.periodic, σ̄_i)
 end
 
-E_dirichlet = SymmetricTensor{4, 2}(
-    (i, j, k, l) -> begin
-        if k == l == 1
-            σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11
-        elseif k == l == 2
-            σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22
-        else
-            σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21
-        end
+E_dirichlet = SymmetricTensor{4, 2}() do i, j, k, l
+    if k == l == 1
+        σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11
+    elseif k == l == 2
+        σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22
+    else
+        σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21
     end
-)
+end
 
-E_periodic = SymmetricTensor{4, 2}(
-    (i, j, k, l) -> begin
-        if k == l == 1
-            σ̄.periodic[1][i, j]
-        elseif k == l == 2
-            σ̄.periodic[2][i, j]
-        else
-            σ̄.periodic[3][i, j]
-        end
+E_periodic = SymmetricTensor{4, 2}() do i, j, k, l
+    if k == l == 1
+        σ̄.periodic[1][i, j]
+    elseif k == l == 2
+        σ̄.periodic[2][i, j]
+    else
+        σ̄.periodic[3][i, j]
     end
-);
+end
 
 function matrix_volume_fraction(grid, cellvalues)
     V = 0.0 # Total volume
@@ -464,4 +471,4 @@
         write_solution(vtk, dh, uM + u.periodic[i], "_periodic_$i")
         write_projection(vtk, projector, σ.periodic[i], "σvM_periodic_$i")
     end
-end;

This page was generated using Literate.jl.

+end;

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/dg_heat_equation.ipynb b/previews/PR1096/tutorials/dg_heat_equation.ipynb index 2bef490bf9..201085239a 100644 --- a/previews/PR1096/tutorials/dg_heat_equation.ipynb +++ b/previews/PR1096/tutorials/dg_heat_equation.ipynb @@ -53,69 +53,70 @@ "where $u^+$ and $u^-$ are the temperature on the two sides of the interface.\n", "\n", "\n", - "!!! details \"Derivation of the weak form for homogeneous Dirichlet boundary condition\"\n", - " Defining $\\boldsymbol{\\sigma}$ as the gradient of the temperature field the equation can be expressed as\n", - " $$\n", - " \\boldsymbol{\\sigma} = \\boldsymbol{\\nabla} (u),\\\\\n", - " -\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\sigma} = 1,\n", - " $$\n", - " Multiplying by test functions $ \\boldsymbol{\\tau} $ and $ \\delta u $ respectively and integrating\n", - " over the domain,\n", - " $$\n", - " \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega,\\\\\n", - " -\\int_\\Omega \\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\sigma} \\delta u \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\n", - " $$\n", - " Integrating by parts and applying divergence theorem,\n", - " $$\n", - " \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = -\\int_\\Omega u (\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\tau}) \\,\\mathrm{d}\\Omega + \\int_\\Gamma \\hat{u} \\boldsymbol{\\tau} \\cdot \\boldsymbol{n} \\,\\mathrm{d}\\Gamma,\\\\\n", - " \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma \\delta u \\boldsymbol{\\hat{\\sigma}} \\cdot \\boldsymbol{n} \\,\\mathrm{d}\\Gamma,\n", - " $$\n", - " Where $\\boldsymbol{n}$ is the outwards pointing normal, $\\Gamma$ is the union of the elements' boundaries, and $\\hat{u}, \\, \\hat{\\sigma}$ are the numerical fluxes.\n", - " Substituting the integrals of form\n", - " $$\n", - " \\int_\\Gamma q \\boldsymbol{\\phi} \\cdot \\boldsymbol{n} \\,\\mathrm{d}\\Gamma = \\int_\\Gamma [\\![ q]\\!] \\cdot \\{\\boldsymbol{\\phi}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{q\\} [\\![ \\boldsymbol{\\phi}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", - " $$\n", - " where $\\Gamma^0 : \\Gamma \\setminus \\partial \\Omega$, and the jump of the vector-valued field $\\boldsymbol{\\phi}$ is defined as\n", - " $$\n", - " [\\![ \\boldsymbol{\\phi}]\\!] = \\boldsymbol{\\phi}^+ \\cdot \\boldsymbol{n}^+ + \\boldsymbol{\\phi}^- \\cdot \\boldsymbol{n}^-\\\\\n", - " $$\n", - " with the jumps and averages results in\n", - " $$\n", - " \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = -\\int_\\Omega u (\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\tau}) \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u}]\\!] \\cdot \\{\\boldsymbol{\\tau}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u}\\} [\\![ \\boldsymbol{\\tau}]\\!] \\,\\mathrm{d}\\Gamma^0,\\\\\n", - " \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", - " $$\n", - " Integrating $ \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega $ by parts and applying divergence theorem\n", - " without using numerical flux, then substitute in the equation to obtain a weak form.\n", - " $$\n", - " \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u} - u]\\!] \\cdot \\{\\boldsymbol{\\tau}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u} - u\\} [\\![ \\boldsymbol{\\tau}]\\!] \\,\\mathrm{d}\\Gamma^0,\\\\\n", - " \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", - " $$\n", - " Substituting\n", - " $$\n", - " \\boldsymbol{\\tau} = \\boldsymbol{\\nabla} (\\delta u),\\\\\n", - " $$\n", - " results in\n", - " $$\n", - " \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u} - u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u} - u\\} [\\![ \\boldsymbol{\\nabla} (\\delta u)]\\!] \\,\\mathrm{d}\\Gamma^0,\\\\\n", - " \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", - " $$\n", - " Combining the two equations,\n", - " $$\n", - " \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u} - u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u} - u\\} [\\![ \\boldsymbol{\\nabla} (\\delta u)]\\!] \\,\\mathrm{d}\\Gamma^0 - \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma - \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0 = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\\\\\n", - " $$\n", - " The numerical fluxes chosen for the interior penalty method are $\\boldsymbol{\\hat{\\sigma}} = \\{\\boldsymbol{\\nabla} (u)\\} - \\alpha([\\![ u]\\!])$ on $\\Gamma$, $\\hat{u} = \\{u\\}$ on the interfaces between elements $\\Gamma^0 : \\Gamma \\setminus \\partial \\Omega$,\n", - " and $\\hat{u} = 0$ on $\\partial \\Omega$. Such choice results in $\\{\\hat{\\boldsymbol{\\sigma}}\\} = \\{\\boldsymbol{\\nabla} (u)\\} - \\alpha([\\![ u]\\!])$, $[\\![ \\hat{u}]\\!] = 0$, $\\{\\hat{u}\\} = \\{u\\}$, $[\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] = 0$ and the equation becomes\n", - " $$\n", - " \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega - \\int_\\Gamma [\\![ u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} \\,\\mathrm{d}\\Gamma - \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (u)\\} - [\\![ \\delta u]\\!] \\cdot \\alpha([\\![ u]\\!]) \\,\\mathrm{d}\\Gamma = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\\\\\n", - " $$\n", - " Where\n", - " $$\n", - " \\alpha([\\![ u]\\!]) = \\mu [\\![ u]\\!]\n", - " $$\n", - " Where $\\mu = \\eta h_e^{-1}$, the weak form becomes\n", - " $$\n", - " \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla}] (\\delta u) \\,\\mathrm{d}\\Omega - \\int_\\Gamma [\\![ u ]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} + [\\![ \\delta u ]\\!] \\cdot \\{\\boldsymbol{\\nabla} (u)\\} \\,\\mathrm{d}\\Gamma + \\int_\\Gamma \\frac{\\eta}{h_e} [\\![ u]\\!] \\cdot [\\![ \\delta u]\\!] \\,\\mathrm{d}\\Gamma = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\\\\\n", - " $$\n", + "> **Derivation of the weak form for homogeneous Dirichlet boundary condition**\n", + ">\n", + "> Defining $\\boldsymbol{\\sigma}$ as the gradient of the temperature field the equation can be expressed as\n", + "> $$\n", + "> \\boldsymbol{\\sigma} = \\boldsymbol{\\nabla} (u),\\\\\n", + "> -\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\sigma} = 1,\n", + "> $$\n", + "> Multiplying by test functions $ \\boldsymbol{\\tau} $ and $ \\delta u $ respectively and integrating\n", + "> over the domain,\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega,\\\\\n", + "> -\\int_\\Omega \\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\sigma} \\delta u \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\n", + "> $$\n", + "> Integrating by parts and applying divergence theorem,\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = -\\int_\\Omega u (\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\tau}) \\,\\mathrm{d}\\Omega + \\int_\\Gamma \\hat{u} \\boldsymbol{\\tau} \\cdot \\boldsymbol{n} \\,\\mathrm{d}\\Gamma,\\\\\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma \\delta u \\boldsymbol{\\hat{\\sigma}} \\cdot \\boldsymbol{n} \\,\\mathrm{d}\\Gamma,\n", + "> $$\n", + "> Where $\\boldsymbol{n}$ is the outwards pointing normal, $\\Gamma$ is the union of the elements' boundaries, and $\\hat{u}, \\, \\hat{\\sigma}$ are the numerical fluxes.\n", + "> Substituting the integrals of form\n", + "> $$\n", + "> \\int_\\Gamma q \\boldsymbol{\\phi} \\cdot \\boldsymbol{n} \\,\\mathrm{d}\\Gamma = \\int_\\Gamma [\\![ q]\\!] \\cdot \\{\\boldsymbol{\\phi}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{q\\} [\\![ \\boldsymbol{\\phi}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", + "> $$\n", + "> where $\\Gamma^0 : \\Gamma \\setminus \\partial \\Omega$, and the jump of the vector-valued field $\\boldsymbol{\\phi}$ is defined as\n", + "> $$\n", + "> [\\![ \\boldsymbol{\\phi}]\\!] = \\boldsymbol{\\phi}^+ \\cdot \\boldsymbol{n}^+ + \\boldsymbol{\\phi}^- \\cdot \\boldsymbol{n}^-\\\\\n", + "> $$\n", + "> with the jumps and averages results in\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = -\\int_\\Omega u (\\boldsymbol{\\nabla} \\cdot \\boldsymbol{\\tau}) \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u}]\\!] \\cdot \\{\\boldsymbol{\\tau}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u}\\} [\\![ \\boldsymbol{\\tau}]\\!] \\,\\mathrm{d}\\Gamma^0,\\\\\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", + "> $$\n", + "> Integrating $ \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega $ by parts and applying divergence theorem\n", + "> without using numerical flux, then substitute in the equation to obtain a weak form.\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega = \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{\\tau} \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u} - u]\\!] \\cdot \\{\\boldsymbol{\\tau}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u} - u\\} [\\![ \\boldsymbol{\\tau}]\\!] \\,\\mathrm{d}\\Gamma^0,\\\\\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", + "> $$\n", + "> Substituting\n", + "> $$\n", + "> \\boldsymbol{\\tau} = \\boldsymbol{\\nabla} (\\delta u),\\\\\n", + "> $$\n", + "> results in\n", + "> $$\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u} - u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u} - u\\} [\\![ \\boldsymbol{\\nabla} (\\delta u)]\\!] \\,\\mathrm{d}\\Gamma^0,\\\\\n", + "> \\int_\\Omega \\boldsymbol{\\sigma} \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0,\n", + "> $$\n", + "> Combining the two equations,\n", + "> $$\n", + "> \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega + \\int_\\Gamma [\\![ \\hat{u} - u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} \\,\\mathrm{d}\\Gamma + \\int_{\\Gamma^0} \\{\\hat{u} - u\\} [\\![ \\boldsymbol{\\nabla} (\\delta u)]\\!] \\,\\mathrm{d}\\Gamma^0 - \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\hat{\\boldsymbol{\\sigma}}\\} \\,\\mathrm{d}\\Gamma - \\int_{\\Gamma^0} \\{\\delta u\\} [\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] \\,\\mathrm{d}\\Gamma^0 = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\\\\\n", + "> $$\n", + "> The numerical fluxes chosen for the interior penalty method are $\\boldsymbol{\\hat{\\sigma}} = \\{\\boldsymbol{\\nabla} (u)\\} - \\alpha([\\![ u]\\!])$ on $\\Gamma$, $\\hat{u} = \\{u\\}$ on the interfaces between elements $\\Gamma^0 : \\Gamma \\setminus \\partial \\Omega$,\n", + "> and $\\hat{u} = 0$ on $\\partial \\Omega$. Such choice results in $\\{\\hat{\\boldsymbol{\\sigma}}\\} = \\{\\boldsymbol{\\nabla} (u)\\} - \\alpha([\\![ u]\\!])$, $[\\![ \\hat{u}]\\!] = 0$, $\\{\\hat{u}\\} = \\{u\\}$, $[\\![ \\hat{\\boldsymbol{\\sigma}}]\\!] = 0$ and the equation becomes\n", + "> $$\n", + "> \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla} (\\delta u)] \\,\\mathrm{d}\\Omega - \\int_\\Gamma [\\![ u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} \\,\\mathrm{d}\\Gamma - \\int_\\Gamma [\\![ \\delta u]\\!] \\cdot \\{\\boldsymbol{\\nabla} (u)\\} - [\\![ \\delta u]\\!] \\cdot \\alpha([\\![ u]\\!]) \\,\\mathrm{d}\\Gamma = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\\\\\n", + "> $$\n", + "> Where\n", + "> $$\n", + "> \\alpha([\\![ u]\\!]) = \\mu [\\![ u]\\!]\n", + "> $$\n", + "> Where $\\mu = \\eta h_e^{-1}$, the weak form becomes\n", + "> $$\n", + "> \\int_\\Omega [\\boldsymbol{\\nabla} (u)] \\cdot [\\boldsymbol{\\nabla}] (\\delta u) \\,\\mathrm{d}\\Omega - \\int_\\Gamma [\\![ u ]\\!] \\cdot \\{\\boldsymbol{\\nabla} (\\delta u)\\} + [\\![ \\delta u ]\\!] \\cdot \\{\\boldsymbol{\\nabla} (u)\\} \\,\\mathrm{d}\\Gamma + \\int_\\Gamma \\frac{\\eta}{h_e} [\\![ u]\\!] \\cdot [\\![ \\delta u]\\!] \\,\\mathrm{d}\\Gamma = \\int_\\Omega \\delta u \\,\\mathrm{d}\\Omega,\\\\\n", + "> $$\n", "Since $\\partial \\Omega$ is constrained with both Dirichlet and Neumann boundary conditions the term $\\int_{\\partial \\Omega} [\\boldsymbol{\\nabla} (u)] \\cdot \\boldsymbol{n} \\delta u \\,\\mathrm{d} \\Omega$ can be expressed as an integral over $\\partial \\Omega_N$, where $\\partial \\Omega_N$ is the boundaries with only prescribed Neumann boundary condition,\n", "The resulting weak form is given given as follows: Find $u \\in \\mathbb{U}$ such that\n", "$$\n", diff --git a/previews/PR1096/tutorials/dg_heat_equation/index.html b/previews/PR1096/tutorials/dg_heat_equation/index.html index 3404292360..e5846e0431 100644 --- a/previews/PR1096/tutorials/dg_heat_equation/index.html +++ b/previews/PR1096/tutorials/dg_heat_equation/index.html @@ -297,4 +297,4 @@ u = K \ f; VTKGridFile("dg_heat_equation", dh) do vtk write_solution(vtk, dh, u) -end;

This page was generated using Literate.jl.

+end;

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/heat_equation.ipynb b/previews/PR1096/tutorials/heat_equation.ipynb index 9e9d28dec0..730a221a1a 100644 --- a/previews/PR1096/tutorials/heat_equation.ipynb +++ b/previews/PR1096/tutorials/heat_equation.ipynb @@ -274,13 +274,14 @@ "value and gradient of the test function, `δu` and also the gradient of the trial function\n", "`u`. We get all of these from `cellvalues`.\n", "\n", - "!!! note \"Notation\"\n", - " Comparing with the brief finite element introduction in Introduction to FEM,\n", - " the variables `δu`, `∇δu` and `∇u` are actually $\\phi_i(\\textbf{x}_q)$, $\\nabla\n", - " \\phi_i(\\textbf{x}_q)$ and $\\nabla \\phi_j(\\textbf{x}_q)$, i.e. the evaluation of the\n", - " trial and test functions in the quadrature point $\\textbf{x}_q$. However, to\n", - " underline the strong parallel between the weak form and the implementation, this\n", - " example uses the symbols appearing in the weak form." + "> **Notation**\n", + ">\n", + "> Comparing with the brief finite element introduction in Introduction to FEM,\n", + "> the variables `δu`, `∇δu` and `∇u` are actually $\\phi_i(\\textbf{x}_q)$, $\\nabla\n", + "> \\phi_i(\\textbf{x}_q)$ and $\\nabla \\phi_j(\\textbf{x}_q)$, i.e. the evaluation of the\n", + "> trial and test functions in the quadrature point $\\textbf{x}_q$. However, to\n", + "> underline the strong parallel between the weak form and the implementation, this\n", + "> example uses the symbols appearing in the weak form." ], "metadata": {} }, @@ -340,11 +341,12 @@ "compute the element contribution with `assemble_element!`, and then assemble into the\n", "global `K` and `f` with `assemble!`.\n", "\n", - "!!! note \"Notation\"\n", - " Comparing again with Introduction to FEM, `f` and `u` correspond to\n", - " $\\underline{\\hat{f}}$ and $\\underline{\\hat{u}}$, since they represent the discretized\n", - " versions. However, through the code we use `f` and `u` instead to reflect the strong\n", - " connection between the weak form and the Ferrite implementation." + "> **Notation**\n", + ">\n", + "> Comparing again with Introduction to FEM, `f` and `u` correspond to\n", + "> $\\underline{\\hat{f}}$ and $\\underline{\\hat{u}}$, since they represent the discretized\n", + "> versions. However, through the code we use `f` and `u` instead to reflect the strong\n", + "> connection between the weak form and the Ferrite implementation." ], "metadata": {} }, diff --git a/previews/PR1096/tutorials/heat_equation/index.html b/previews/PR1096/tutorials/heat_equation/index.html index 69329c2970..13eff162bc 100644 --- a/previews/PR1096/tutorials/heat_equation/index.html +++ b/previews/PR1096/tutorials/heat_equation/index.html @@ -159,4 +159,4 @@ VTKGridFile("heat_equation", dh) do vtk write_solution(vtk, dh, u) -end

This page was generated using Literate.jl.

+end

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/hyperelasticity.ipynb b/previews/PR1096/tutorials/hyperelasticity.ipynb index 03b57240f2..83a8c50417 100644 --- a/previews/PR1096/tutorials/hyperelasticity.ipynb +++ b/previews/PR1096/tutorials/hyperelasticity.ipynb @@ -85,18 +85,31 @@ "$$\n", "\n", "where $I_1 = \\mathrm{tr}(\\mathbf{C})$ is the first invariant, $J = \\sqrt{\\det(\\mathbf{C})}$\n", - "and $\\mu$ and $\\lambda$ material parameters.\n", - "!!! details \"Extra details on compressible neo-Hookean formulations\"\n", - " The Neo-Hooke model is only a well defined terminology in the incompressible case.\n", - " Thus, only $W(\\mathbf{C})$ specifies the neo-Hookean behavior, the volume penalty $U(J)$ can vary in different formulations.\n", - " In order to obtain a well-posed problem, it is crucial to choose a convex formulation of $U(J)$.\n", - " Other examples for $U(J)$ can be found, e.g. in [Hol:2000:nsm; Eq. (6.138)](@cite)\n", - " $$\n", - " \\beta^{-2} (\\beta \\ln J + J^{-\\beta} -1)\n", - " $$\n", - " where [SimMie:1992:act; Eq. (2.37)](@cite) published a non-generalized version with $\\beta=-2$.\n", - " This shows the possible variety of $U(J)$ while all of them refer to compressible neo-Hookean models.\n", - " Sometimes the modified first invariant $\\overline{I}_1=\\frac{I_1}{I_3^{1/3}}$ is used in $W(\\mathbf{C})$ instead of $I_1$.\n", + "and $\\mu$ and $\\lambda$ material parameters." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Extra details on compressible neo-Hookean formulations**\n", + ">\n", + "> The Neo-Hooke model is only a well defined terminology in the incompressible case.\n", + "> Thus, only $W(\\mathbf{C})$ specifies the neo-Hookean behavior, the volume penalty $U(J)$ can vary in different formulations.\n", + "> In order to obtain a well-posed problem, it is crucial to choose a convex formulation of $U(J)$.\n", + "> Other examples for $U(J)$ can be found, e.g. in [Hol:2000:nsm; Eq. (6.138)](@cite)\n", + "> $$\n", + "> \\beta^{-2} (\\beta \\ln J + J^{-\\beta} -1)\n", + "> $$\n", + "> where [SimMie:1992:act; Eq. (2.37)](@cite) published a non-generalized version with $\\beta=-2$.\n", + "> This shows the possible variety of $U(J)$ while all of them refer to compressible neo-Hookean models.\n", + "> Sometimes the modified first invariant $\\overline{I}_1=\\frac{I_1}{I_3^{1/3}}$ is used in $W(\\mathbf{C})$ instead of $I_1$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ "From the potential we obtain the second Piola-Kirchoff stress $\\mathbf{S}$ as\n", "\n", "$$\n", @@ -116,7 +129,7 @@ "$$\n", "\\begin{align*}\n", "\\mathbf{P} &= \\mathbf{F} \\cdot \\mathbf{S},\\\\\n", - "\\frac{\\partial \\mathbf{P}}{\\partial \\mathbf{F}} &= \\mathbf{I} \\bar{\\otimes} \\mathbf{S} + 2\\, \\mathbf{F} \\bar{\\otimes} \\mathbf{I} :\n", + "\\frac{\\partial \\mathbf{P}}{\\partial \\mathbf{F}} &= \\mathbf{I} \\bar{\\otimes} \\mathbf{S} + 2\\, \\mathbf{F} \\cdot\n", "\\frac{\\partial \\mathbf{S}}{\\partial \\mathbf{C}} : \\mathbf{F}^\\mathrm{T} \\bar{\\otimes} \\mathbf{I}.\n", "\\end{align*}\n", "$$" @@ -126,45 +139,48 @@ { "cell_type": "markdown", "source": [ - "### Derivation of $\\partial \\mathbf{P} / \\partial \\mathbf{F}$\n", - "Using the product rule, the chain rule, and the relations $\\mathbf{P} = \\mathbf{F} \\cdot\n", - "\\mathbf{S}$ and $\\mathbf{C} = \\mathbf{F}^\\mathrm{T} \\cdot \\mathbf{F}$, we obtain the\n", - "following:\n", - "$$\n", - "\\begin{aligned}\n", - "\\frac{\\partial \\mathbf{P}}{\\partial \\mathbf{F}} &=\n", - "\\frac{\\partial P_{ij}}{\\partial F_{kl}} \\\\ &=\n", - "\\frac{\\partial (F_{im}S_{mj})}{\\partial F_{kl}} \\\\ &=\n", - "\\frac{\\partial F_{im}}{\\partial F_{kl}}S_{mj} +\n", - "F_{im}\\frac{\\partial S_{mj}}{\\partial F_{kl}} \\\\ &=\n", - "\\delta_{ik}\\delta_{ml} S_{mj} +\n", - "F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\\frac{\\partial C_{no}}{\\partial F_{kl}} \\\\ &=\n", - "\\delta_{ik}S_{lj} +\n", - "F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\n", - "\\frac{\\partial (F^\\mathrm{T}_{np}F_{po})}{\\partial F_{kl}} \\\\ &=\n", - "\\delta_{ik}S^\\mathrm{T}_{jl} +\n", - "F_{im}\\delta_{jq}\\frac{\\partial S_{mq}}{\\partial C_{no}}\n", - "\\left(\n", - "\\frac{\\partial F^\\mathrm{T}_{np}}{\\partial F_{kl}}F_{po} +\n", - "F^\\mathrm{T}_{np}\\frac{\\partial F_{po}}{\\partial F_{kl}}\n", - "\\right) \\\\ &=\n", - "\\delta_{ik}S_{jl} +\n", - "F_{im}\\delta_{jq}\\frac{\\partial S_{mq}}{\\partial C_{no}}\n", - "(\\delta_{nl} \\delta_{pk} F_{po} + F^\\mathrm{T}_{np}\\delta_{pk} \\delta_{ol}) \\\\ &=\n", - "\\delta_{ik}S_{lj} +\n", - "F_{im}\\delta_{jq}\\frac{\\partial S_{mq}}{\\partial C_{no}}\n", - "(F^\\mathrm{T}_{ok} \\delta_{nl} + F^\\mathrm{T}_{nk} \\delta_{ol}) \\\\ &=\n", - "\\delta_{ik}S_{jl} +\n", - "2\\, F_{im}\\delta_{jq} \\frac{\\partial S_{mq}}{\\partial C_{no}}\n", - "F^\\mathrm{T}_{nk} \\delta_{ol} \\\\ &=\n", - "\\mathbf{I}\\bar{\\otimes}\\mathbf{S} +\n", - "2\\, \\mathbf{F}\\bar{\\otimes}\\mathbf{I} : \\frac{\\partial \\mathbf{S}}{\\partial \\mathbf{C}}\n", - ": \\mathbf{F}^\\mathrm{T} \\bar{\\otimes} \\mathbf{I},\n", - "\\end{aligned}\n", - "$$\n", - "where we used the fact that $\\mathbf{S}$ is symmetric ($S_{lj} = S_{jl}$) and that\n", - "$\\frac{\\partial \\mathbf{S}}{\\partial \\mathbf{C}}$ is *minor* symmetric ($\\frac{\\partial\n", - "S_{mq}}{\\partial C_{no}} = \\frac{\\partial S_{mq}}{\\partial C_{on}}$)." + "> **Derivation of $\\partial \\mathbf{P} / \\partial \\mathbf{F}$**\n", + ">\n", + "> *Tip:* See [knutam.github.io/tensors](https://knutam.github.io/tensors/Theory/IndexNotation/) for\n", + "> an explanation of the index notation used in this derivation.\n", + "> Using the product rule, the chain rule, and the relations $\\mathbf{P} = \\mathbf{F} \\cdot\n", + "> \\mathbf{S}$ and $\\mathbf{C} = \\mathbf{F}^\\mathrm{T} \\cdot \\mathbf{F}$, we obtain the\n", + "> following:\n", + "> $$\n", + "> \\begin{aligned}\n", + "> \\frac{\\partial P_{ij}}{\\partial F_{kl}} &=\n", + "> \\frac{\\partial (F_{im}S_{mj})}{\\partial F_{kl}} \\\\ &=\n", + "> \\frac{\\partial F_{im}}{\\partial F_{kl}}S_{mj} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial F_{kl}} \\\\ &=\n", + "> \\delta_{ik}\\delta_{ml} S_{mj} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\\frac{\\partial C_{no}}{\\partial F_{kl}} \\\\ &=\n", + "> \\delta_{ik}S_{lj} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> \\frac{\\partial (F^\\mathrm{T}_{np}F_{po})}{\\partial F_{kl}} \\\\ &=\n", + "> \\delta_{ik}S^\\mathrm{T}_{jl} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> \\left(\n", + "> \\frac{\\partial F^\\mathrm{T}_{np}}{\\partial F_{kl}}F_{po} +\n", + "> F^\\mathrm{T}_{np}\\frac{\\partial F_{po}}{\\partial F_{kl}}\n", + "> \\right) \\\\ &=\n", + "> \\delta_{ik}S_{jl} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> (\\delta_{nl} \\delta_{pk} F_{po} + F^\\mathrm{T}_{np}\\delta_{pk} \\delta_{ol}) \\\\ &=\n", + "> \\delta_{ik}S_{lj} +\n", + "> F_{im}\\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> (F^\\mathrm{T}_{ok} \\delta_{nl} + F^\\mathrm{T}_{nk} \\delta_{ol}) \\\\ &=\n", + "> \\delta_{ik}S_{jl} +\n", + "> 2\\, F_{im} \\frac{\\partial S_{mj}}{\\partial C_{no}}\n", + "> F^\\mathrm{T}_{nk} \\delta_{ol} \\\\\n", + "> \\frac{\\partial \\mathbf{P}}{\\partial \\mathbf{F}} &=\n", + "> \\mathbf{I}\\bar{\\otimes}\\mathbf{S} +\n", + "> 2\\, \\mathbf{F} \\cdot \\frac{\\partial \\mathbf{S}}{\\partial \\mathbf{C}}\n", + "> : \\mathbf{F}^\\mathrm{T} \\bar{\\otimes} \\mathbf{I},\n", + "> \\end{aligned}\n", + "> $$\n", + "> where we used the fact that $\\mathbf{S}$ is symmetric ($S_{lj} = S_{jl}$) and that\n", + "> $\\frac{\\partial \\mathbf{S}}{\\partial \\mathbf{C}}$ is *minor* symmetric ($\\frac{\\partial\n", + "> S_{mj}}{\\partial C_{no}} = \\frac{\\partial S_{mj}}{\\partial C_{on}}$)." ], "metadata": {} }, @@ -284,7 +300,7 @@ " S, ∂S∂C = constitutive_driver(C, mp)\n", " P = F ⋅ S\n", " I = one(S)\n", - " ∂P∂F = otimesu(I, S) + 2 * otimesu(F, I) ⊡ ∂S∂C ⊡ otimesu(F', I)\n", + " ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)\n", "\n", " # Loop over test functions\n", " for i in 1:ndofs\n", @@ -345,12 +361,13 @@ " assembler = start_assemble(K, g)\n", "\n", " # Loop over all cells in the grid\n", - " return @timeit \"assemble\" for cell in CellIterator(dh)\n", + " @timeit \"assemble\" for cell in CellIterator(dh)\n", " global_dofs = celldofs(cell)\n", " ue = u[global_dofs] # element dofs\n", " @timeit \"element assemble\" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)\n", " assemble!(assembler, global_dofs, ke, ge)\n", " end\n", + " return\n", "end;" ], "metadata": {}, @@ -512,14 +529,14 @@ "-------------------------------------------------------------------------------\n", " Analysis with 6000 elements Time Allocations \n", " ----------------------- ------------------------\n", - " Tot / % measured: 1.90s / 46.1% 119MiB / 31.1% \n", + " Tot / % measured: 1.87s / 46.3% 119MiB / 31.1% \n", "\n", "Section ncalls time %tot avg alloc %tot avg\n", "-------------------------------------------------------------------------------\n", - "export 1 688ms 78.6% 688ms 31.1MiB 83.9% 31.1MiB\n", - "assemble 6 113ms 12.9% 18.8ms 5.50MiB 14.9% 938KiB\n", - " element assemble 36.0k 65.7ms 7.5% 1.83μs 0.00B 0.0% 0.00B\n", - "linear solve 5 74.7ms 8.5% 14.9ms 473KiB 1.2% 94.6KiB\n", + "export 1 682ms 78.9% 682ms 31.1MiB 83.9% 31.1MiB\n", + "assemble 6 108ms 12.5% 18.0ms 5.50MiB 14.9% 938KiB\n", + " element assemble 36.0k 62.3ms 7.2% 1.73μs 0.00B 0.0% 0.00B\n", + "linear solve 5 74.6ms 8.6% 14.9ms 473KiB 1.2% 94.6KiB\n", "-------------------------------------------------------------------------------\n" ] } diff --git a/previews/PR1096/tutorials/hyperelasticity.jl b/previews/PR1096/tutorials/hyperelasticity.jl index e46efa2f5a..aaf68928d5 100644 --- a/previews/PR1096/tutorials/hyperelasticity.jl +++ b/previews/PR1096/tutorials/hyperelasticity.jl @@ -41,7 +41,7 @@ function assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN) S, ∂S∂C = constitutive_driver(C, mp) P = F ⋅ S I = one(S) - ∂P∂F = otimesu(I, S) + 2 * otimesu(F, I) ⊡ ∂S∂C ⊡ otimesu(F', I) + ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I) # Loop over test functions for i in 1:ndofs @@ -86,12 +86,13 @@ function assemble_global!(K, g, dh, cv, fv, mp, u, ΓN) assembler = start_assemble(K, g) # Loop over all cells in the grid - return @timeit "assemble" for cell in CellIterator(dh) + @timeit "assemble" for cell in CellIterator(dh) global_dofs = celldofs(cell) ue = u[global_dofs] # element dofs @timeit "element assemble" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN) assemble!(assembler, global_dofs, ke, ge) end + return end; function solve() diff --git a/previews/PR1096/tutorials/hyperelasticity/index.html b/previews/PR1096/tutorials/hyperelasticity/index.html index 88d76e93a3..b951bbc03e 100644 --- a/previews/PR1096/tutorials/hyperelasticity/index.html +++ b/previews/PR1096/tutorials/hyperelasticity/index.html @@ -4,15 +4,10 @@ \delta \mathbf{u} \cdot \mathbf{t}\ \mathrm{d}\Gamma \quad \forall \delta \mathbf{u} \in \mathbb{U}^0,\]

where $\mathbf{u}$ is the unknown displacement field, $\mathbf{b}$ is the body force acting on the reference domain, $\mathbf{t}$ is the traction acting on the Neumann part of the reference domain's boundary, and where $\mathbb{U}$ and $\mathbb{U}^0$ are suitable trial and test sets. $\Omega$ denotes the reference (sometimes also called initial or material) domain. Gradients are defined with respect to the reference domain, here denoted with an $\mathbf{X}$. Formally this is expressed as $(\nabla_{\mathbf{X}} \bullet)_{ij} := \frac{\partial(\bullet)_i}{\partial X_j}$. Note that for large deformation problems it is also possible that gradients and integrals are defined on the deformed (sometimes also called current or spatial) domain, depending on the specific formulation.

The specific problem we will solve in this example is the cube from Figure 1: On one side we apply a rotation using Dirichlet boundary conditions, on the opposite side we fix the displacement with a homogeneous Dirichlet boundary condition, and on the remaining four sides we apply a traction in the normal direction of the surface. In addition, a body force is applied in one direction.

In addition to Ferrite.jl and Tensors.jl, this examples uses TimerOutputs.jl for timing the program and print a summary at the end, ProgressMeter.jl for showing a simple progress bar, and IterativeSolvers.jl for solving the linear system using conjugate gradients.

using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers

Hyperelastic material model

The stress can be derived from an energy potential, defined in terms of the right Cauchy-Green tensor $\mathbf{C} = \mathbf{F}^{\mathrm{T}} \cdot \mathbf{F}$, where $\mathbf{F} = \mathbf{I} + \nabla_{\mathbf{X}} \mathbf{u}$ is the deformation gradient. We shall use the compressible neo-Hookean model from Wikipedia with the potential

\[\Psi(\mathbf{C}) = \underbrace{\frac{\mu}{2} (I_1 - 3)}_{W(\mathbf{C})} \underbrace{- {\mu} \ln(J) + \frac{\lambda}{2} (J - 1)^2}_{U(J)},\]

where $I_1 = \mathrm{tr}(\mathbf{C})$ is the first invariant, $J = \sqrt{\det(\mathbf{C})}$ and $\mu$ and $\lambda$ material parameters.

Extra details on compressible neo-Hookean formulations

The Neo-Hooke model is only a well defined terminology in the incompressible case. Thus, only $W(\mathbf{C})$ specifies the neo-Hookean behavior, the volume penalty $U(J)$ can vary in different formulations. In order to obtain a well-posed problem, it is crucial to choose a convex formulation of $U(J)$. Other examples for $U(J)$ can be found, e.g. in [1, Eq. (6.138)]

\[ \beta^{-2} (\beta \ln J + J^{-\beta} -1)\]

where [2, Eq. (2.37)] published a non-generalized version with $\beta=-2$. This shows the possible variety of $U(J)$ while all of them refer to compressible neo-Hookean models. Sometimes the modified first invariant $\overline{I}_1=\frac{I_1}{I_3^{1/3}}$ is used in $W(\mathbf{C})$ instead of $I_1$.

From the potential we obtain the second Piola-Kirchoff stress $\mathbf{S}$ as

\[\mathbf{S} = 2 \frac{\partial \Psi}{\partial \mathbf{C}},\]

and the tangent of $\mathbf{S}$ as

\[\frac{\partial \mathbf{S}}{\partial \mathbf{C}} = 2 \frac{\partial^2 \Psi}{\partial \mathbf{C}^2}.\]

Finally, for the finite element problem we need $\mathbf{P}$ and $\frac{\partial \mathbf{P}}{\partial \mathbf{F}}$, which can be obtained by using the following relations:

\[\begin{align*} \mathbf{P} &= \mathbf{F} \cdot \mathbf{S},\\ -\frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= \mathbf{I} \bar{\otimes} \mathbf{S} + 2\, \mathbf{F} \bar{\otimes} \mathbf{I} : +\frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= \mathbf{I} \bar{\otimes} \mathbf{S} + 2\, \mathbf{F} \cdot \frac{\partial \mathbf{S}}{\partial \mathbf{C}} : \mathbf{F}^\mathrm{T} \bar{\otimes} \mathbf{I}. -\end{align*}\]

- -Derivation of $\partial \mathbf{P} / \partial \mathbf{F}$ - -

Using the product rule, the chain rule, and the relations $\mathbf{P} = \mathbf{F} \cdot \mathbf{S}$ and $\mathbf{C} = \mathbf{F}^\mathrm{T} \cdot \mathbf{F}$, we obtain the following:

\[\begin{aligned} -\frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= -\frac{\partial P_{ij}}{\partial F_{kl}} \\ &= +\end{align*}\]

Derivation of $\partial \mathbf{P} / \partial \mathbf{F}$

Tip: See knutam.github.io/tensors for an explanation of the index notation used in this derivation.

Using the product rule, the chain rule, and the relations $\mathbf{P} = \mathbf{F} \cdot \mathbf{S}$ and $\mathbf{C} = \mathbf{F}^\mathrm{T} \cdot \mathbf{F}$, we obtain the following:

\[\begin{aligned} +\frac{\partial P_{ij}}{\partial F_{kl}} &= \frac{\partial (F_{im}S_{mj})}{\partial F_{kl}} \\ &= \frac{\partial F_{im}}{\partial F_{kl}}S_{mj} + F_{im}\frac{\partial S_{mj}}{\partial F_{kl}} \\ &= @@ -22,24 +17,25 @@ F_{im}\frac{\partial S_{mj}}{\partial C_{no}} \frac{\partial (F^\mathrm{T}_{np}F_{po})}{\partial F_{kl}} \\ &= \delta_{ik}S^\mathrm{T}_{jl} + -F_{im}\delta_{jq}\frac{\partial S_{mq}}{\partial C_{no}} +F_{im}\frac{\partial S_{mj}}{\partial C_{no}} \left( \frac{\partial F^\mathrm{T}_{np}}{\partial F_{kl}}F_{po} + F^\mathrm{T}_{np}\frac{\partial F_{po}}{\partial F_{kl}} \right) \\ &= \delta_{ik}S_{jl} + -F_{im}\delta_{jq}\frac{\partial S_{mq}}{\partial C_{no}} +F_{im}\frac{\partial S_{mj}}{\partial C_{no}} (\delta_{nl} \delta_{pk} F_{po} + F^\mathrm{T}_{np}\delta_{pk} \delta_{ol}) \\ &= \delta_{ik}S_{lj} + -F_{im}\delta_{jq}\frac{\partial S_{mq}}{\partial C_{no}} +F_{im}\frac{\partial S_{mj}}{\partial C_{no}} (F^\mathrm{T}_{ok} \delta_{nl} + F^\mathrm{T}_{nk} \delta_{ol}) \\ &= \delta_{ik}S_{jl} + -2\, F_{im}\delta_{jq} \frac{\partial S_{mq}}{\partial C_{no}} -F^\mathrm{T}_{nk} \delta_{ol} \\ &= +2\, F_{im} \frac{\partial S_{mj}}{\partial C_{no}} +F^\mathrm{T}_{nk} \delta_{ol} \\ +\frac{\partial \mathbf{P}}{\partial \mathbf{F}} &= \mathbf{I}\bar{\otimes}\mathbf{S} + -2\, \mathbf{F}\bar{\otimes}\mathbf{I} : \frac{\partial \mathbf{S}}{\partial \mathbf{C}} +2\, \mathbf{F} \cdot \frac{\partial \mathbf{S}}{\partial \mathbf{C}} : \mathbf{F}^\mathrm{T} \bar{\otimes} \mathbf{I}, -\end{aligned}\]

where we used the fact that $\mathbf{S}$ is symmetric ($S_{lj} = S_{jl}$) and that $\frac{\partial \mathbf{S}}{\partial \mathbf{C}}$ is minor symmetric ($\frac{\partial S_{mq}}{\partial C_{no}} = \frac{\partial S_{mq}}{\partial C_{on}}$).

Implementation of material model using automatic differentiation

We can implement the material model as follows, where we utilize automatic differentiation for the stress and the tangent, and thus only define the potential:

struct NeoHooke
+\end{aligned}\]

where we used the fact that $\mathbf{S}$ is symmetric ($S_{lj} = S_{jl}$) and that $\frac{\partial \mathbf{S}}{\partial \mathbf{C}}$ is minor symmetric ($\frac{\partial S_{mj}}{\partial C_{no}} = \frac{\partial S_{mj}}{\partial C_{on}}$).

Implementation of material model using automatic differentiation

We can implement the material model as follows, where we utilize automatic differentiation for the stress and the tangent, and thus only define the potential:

struct NeoHooke
     μ::Float64
     λ::Float64
 end
@@ -83,7 +79,7 @@
         S, ∂S∂C = constitutive_driver(C, mp)
         P = F ⋅ S
         I = one(S)
-        ∂P∂F = otimesu(I, S) + 2 * otimesu(F, I) ⊡ ∂S∂C ⊡ otimesu(F', I)
+        ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)
 
         # Loop over test functions
         for i in 1:ndofs
@@ -126,12 +122,13 @@
     assembler = start_assemble(K, g)
 
     # Loop over all cells in the grid
-    return @timeit "assemble" for cell in CellIterator(dh)
+    @timeit "assemble" for cell in CellIterator(dh)
         global_dofs = celldofs(cell)
         ue = u[global_dofs] # element dofs
         @timeit "element assemble" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)
         assemble!(assembler, global_dofs, ke, ge)
     end
+    return
 end;

Finally, we define a main function which sets up everything and then performs Newton iterations until convergence.

function solve()
     reset_timer!()
 
@@ -249,14 +246,14 @@
 -------------------------------------------------------------------------------
  Analysis with 6000 elements          Time                    Allocations
                              -----------------------   ------------------------
-      Tot / % measured:           295ms /  67.3%           29.6MiB /  33.4%
+      Tot / % measured:           343ms /  62.9%           29.6MiB /  33.4%
 
 Section              ncalls     time    %tot     avg     alloc    %tot      avg
 -------------------------------------------------------------------------------
-assemble                  6    111ms   55.9%  18.5ms   5.50MiB   55.5%   938KiB
-  element assemble    36.0k   65.6ms   33.1%  1.82μs     0.00B    0.0%    0.00B
-linear solve              5   73.8ms   37.2%  14.8ms    473KiB    4.7%  94.6KiB
-export                    1   13.8ms    7.0%  13.8ms   3.94MiB   39.8%  3.94MiB
+assemble                  6    118ms   54.5%  19.6ms   5.50MiB   55.5%   938KiB
+  element assemble    36.0k   67.5ms   31.3%  1.87μs     0.00B    0.0%    0.00B
+linear solve              5   74.3ms   34.4%  14.9ms    473KiB    4.7%  94.6KiB
+export                    1   23.9ms   11.1%  23.9ms   3.94MiB   39.8%  3.94MiB
 -------------------------------------------------------------------------------

Plain program

Here follows a version of the program without any comments. The file is also available here: hyperelasticity.jl.

using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers
 
 struct NeoHooke
@@ -300,7 +297,7 @@
         S, ∂S∂C = constitutive_driver(C, mp)
         P = F ⋅ S
         I = one(S)
-        ∂P∂F = otimesu(I, S) + 2 * otimesu(F, I) ⊡ ∂S∂C ⊡ otimesu(F', I)
+        ∂P∂F = otimesu(I, S) + 2 * F ⋅ ∂S∂C ⊡ otimesu(F', I)
 
         # Loop over test functions
         for i in 1:ndofs
@@ -345,12 +342,13 @@
     assembler = start_assemble(K, g)
 
     # Loop over all cells in the grid
-    return @timeit "assemble" for cell in CellIterator(dh)
+    @timeit "assemble" for cell in CellIterator(dh)
         global_dofs = celldofs(cell)
         ue = u[global_dofs] # element dofs
         @timeit "element assemble" assemble_element!(ke, ge, cell, cv, fv, mp, ue, ΓN)
         assemble!(assembler, global_dofs, ke, ge)
     end
+    return
 end;
 
 function solve()
@@ -465,4 +463,4 @@
     return u
 end
 
-u = solve();

This page was generated using Literate.jl.

+u = solve();

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/incompressible_elasticity.ipynb b/previews/PR1096/tutorials/incompressible_elasticity.ipynb index 5189721d8d..7a7e10927c 100644 --- a/previews/PR1096/tutorials/incompressible_elasticity.ipynb +++ b/previews/PR1096/tutorials/incompressible_elasticity.ipynb @@ -410,7 +410,8 @@ " σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress\n", "\n", " # Export the solution and the stress\n", - " filename = \"cook_\" * (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? \"linear\" : \"quadratic\") *\n", + " filename = \"cook_\" *\n", + " (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? \"linear\" : \"quadratic\") *\n", " \"_linear\"\n", "\n", " VTKGridFile(filename, grid) do vtk\n", diff --git a/previews/PR1096/tutorials/incompressible_elasticity.jl b/previews/PR1096/tutorials/incompressible_elasticity.jl index 05667ee2ab..a424890d52 100644 --- a/previews/PR1096/tutorials/incompressible_elasticity.jl +++ b/previews/PR1096/tutorials/incompressible_elasticity.jl @@ -210,7 +210,8 @@ function solve(ν, interpolation_u, interpolation_p) σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress # Export the solution and the stress - filename = "cook_" * (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * + filename = "cook_" * + (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * "_linear" VTKGridFile(filename, grid) do vtk diff --git a/previews/PR1096/tutorials/incompressible_elasticity/index.html b/previews/PR1096/tutorials/incompressible_elasticity/index.html index 56c328dbcf..a02527add7 100644 --- a/previews/PR1096/tutorials/incompressible_elasticity/index.html +++ b/previews/PR1096/tutorials/incompressible_elasticity/index.html @@ -195,7 +195,8 @@ σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress # Export the solution and the stress - filename = "cook_" * (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * + filename = "cook_" * + (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * "_linear" VTKGridFile(filename, grid) do vtk @@ -422,7 +423,8 @@ σvM = map(x -> √(3 / 2 * dev(x) ⊡ dev(x)), σ) # von Mise effective stress # Export the solution and the stress - filename = "cook_" * (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * + filename = "cook_" * + (interpolation_u == Lagrange{RefTriangle, 1}()^2 ? "linear" : "quadratic") * "_linear" VTKGridFile(filename, grid) do vtk @@ -441,4 +443,4 @@ quadratic_u = Lagrange{RefTriangle, 2}()^2 u1 = solve(0.5, linear_u, linear_p); -u2 = solve(0.5, quadratic_u, linear_p);

This page was generated using Literate.jl.

+u2 = solve(0.5, quadratic_u, linear_p);

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/index.html b/previews/PR1096/tutorials/index.html index 2be537f9f4..a6c58a0d65 100644 --- a/previews/PR1096/tutorials/index.html +++ b/previews/PR1096/tutorials/index.html @@ -1,2 +1,2 @@ -Tutorials overview · Ferrite.jl

Tutorials

On this page you find an overview of Ferrite tutorials. The tutorials explain and show how Ferrite can be used to solve a wide range of problems. See also the Code gallery for more examples.

The tutorials all follow roughly the same structure:

  • Introduction introduces the problem to be solved and discusses the learning outcomes of the tutorial.
  • Commented program is the code for solving the problem with explanations and comments.
  • Plain program is the raw source code of the program.

When studying the tutorials it is a good idea to obtain a local copy of the code and run it on your own machine as you read along. Some of the tutorials also include suggestions for tweaks to the program that you can try out on your own.

Tutorial index

The tutorials are listed in roughly increasing order of complexity. However, since they focus on different aspects, and solve different problems, it is suggested to have a look at the brief descriptions below to get an idea about what you will learn from each tutorial.

If you are new to Ferrite then Tutorial 1 - Tutorial 6 is the best place to start. These tutorials introduces and teaches most of the basic finite element techniques (e.g. linear and non-linear problems, scalar- and vector-valued problems, Dirichlet and Neumann boundary conditions, mixed finite elements, time integration, direct and iterative linear solvers, etc). In particular the very first tutorial is essential in order to be able to follow any of the other tutorials. The remaining tutorials discuss more advanced topics.


Tutorial 1: Heat equation

This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with homogeneous Dirichlet boundary conditions. This tutorial introduces and teaches many important parts of Ferrite: problem setup, degree of freedom management, assembly procedure, boundary conditions, solving the linear system, visualization of the result). Understanding this tutorial is essential to follow more complex tutorials.

Keywords: scalar-valued solution, Dirichlet boundary conditions.


Tutorial 2: Linear elasticity

TBW.

Keywords: vector-valued solution, Dirichlet and Neumann boundary conditions.


Tutorial 3: Incompressible elasticity

This tutorial focuses on a mixed formulation of linear elasticity, with (vector) displacement and (scalar) pressure as the two unknowns, suitable for incompressibility. Thus, this tutorial guides you through the process of solving a problem with two unknowns from two coupled weak forms. The problem that is studied is Cook's membrane in the incompressible limit.

Keywords: mixed finite elements, Dirichlet and Neumann boundary conditions.


Tutorial 4: Hyperelasticity

In this tutorial you will learn how to solve a non-linear finite element problem. In particular, a hyperelastic material model, in a finite strain setting, is used to solve the rotation of a cube. Automatic differentiatio (AD) is used for the consitutive relations. Newton's method is used for the non-linear iteration, and a conjugate gradient (CG) solver is used for the linear solution of the increment.

Keywords: non-linear finite element, finite strain, automatic differentiation (AD), Newton's method, conjugate gradient (CG).


Tutorial 5: von Mises Plasticity

This tutorial revisits the cantilever beam problem from Tutorial 2: Linear elasticity, but instead of linear elasticity a plasticity model is used for the constitutive relation. You will learn how to solve a problem which require the solution of a local material problem, and the storage of material state, in each quadrature point. Newton's method is used both locally in the material routine, and globally on the finite element level.

Keywords: non-linear finite element, plasticity, material modeling, state variables, Newton’s method.


Tutorial 6: Transient heat equation

In this tutorial the transient heat equation is solved on the unit square. The problem to be solved is thus similar to the one solved in the first tutorial, Heat equation, but with time-varying boundary conditions. In particular you will learn how to solve a time dependent problem with an implicit Euler scheme for the time integration.

Keywords: time dependent finite elements, implicit Euler time integration.


Tutorial 7: Computational homogenization

This tutorial guides you through computational homogenization of an representative volume element (RVE) consisting of a soft matrix material with stiff inclusions. The computational mesh is read from an external mesh file generated with Gmsh. Dirichlet and periodic boundary conditions are used.

Keywords: Gmsh mesh reading, Dirichlet and periodic boundary conditions


Tutorial 8: Stokes flow

In this tutorial Stokes flow with (vector) velocity and (scalar) pressure is solved on on a quarter circle. Rotationally periodic boundary conditions is used for the inlet/outlet coupling. To obtain a unique solution, a mean value constraint is applied on the pressure using an affine constraint. The computational mesh is generated directly using the Gmsh API.

Keywords: periodic boundary conditions, mean value constraint, mesh generation with Gmsh.


Tutorial 9: Porous media (SubDofHandler)

This tutorial introduces how to solve a complex linear problem, where there are different fields on different subdomains, and different cell types in the grid. This requires using the SubDofHandler interface.

Keywords: Mixed grids, multiple fields, porous media, SubDofHandler


Tutorial 10: Incompressible Navier-Stokes equations

In this tutorial the incompressible Navier-Stokes equations are solved. The domain is discretized in space with Ferrite as usual, and then forumalated in a way to be compatible with the OrdinaryDiffEq.jl package, which is used for the time-integration.

Keywords: non-linear time dependent problem


Tutorial 10: Reactive surface

In this tutorial a reaction diffusion system on a sphere surface embedded in 3D is solved. Ferrite is used to assemble the diffusion operators and the mass matrices. The problem is solved by using the usual first order reaction diffusion operator splitting.

Keywords: embedded elements, operator splitting, gmsh


Tutorial 11: Linear shell

In this tutorial a linear shell element formulation is set up as a two-dimensional domain embedded in three-dimensional space. This will teach, and perhaps inspire, you on how Ferrite can be used for non-standard things and how to add "hacks" that build on top of Ferrite.

Keywords: shell elements, automatic differentiation


Tutorial 12: Discontinuous Galerkin heat equation

This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with inhomogeneous Dirichlet and Neumann boundary conditions using the interior penalty discontinuous Galerkin method. This tutorial follows the heat equation tutorial, introducing face and interface iterators, jump and average operators, and cross-element coupling in sparsity patterns. This example was developed as part of the Google Summer of Code funded project "Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl".

Keywords: scalar-valued solution, Dirichlet boundary conditions, Discontinuous Galerkin, Interior penalty

+Tutorials overview · Ferrite.jl

Tutorials

On this page you find an overview of Ferrite tutorials. The tutorials explain and show how Ferrite can be used to solve a wide range of problems. See also the Code gallery for more examples.

The tutorials all follow roughly the same structure:

  • Introduction introduces the problem to be solved and discusses the learning outcomes of the tutorial.
  • Commented program is the code for solving the problem with explanations and comments.
  • Plain program is the raw source code of the program.

When studying the tutorials it is a good idea to obtain a local copy of the code and run it on your own machine as you read along. Some of the tutorials also include suggestions for tweaks to the program that you can try out on your own.

Tutorial index

The tutorials are listed in roughly increasing order of complexity. However, since they focus on different aspects, and solve different problems, it is suggested to have a look at the brief descriptions below to get an idea about what you will learn from each tutorial.

If you are new to Ferrite then Tutorial 1 - Tutorial 6 is the best place to start. These tutorials introduces and teaches most of the basic finite element techniques (e.g. linear and non-linear problems, scalar- and vector-valued problems, Dirichlet and Neumann boundary conditions, mixed finite elements, time integration, direct and iterative linear solvers, etc). In particular the very first tutorial is essential in order to be able to follow any of the other tutorials. The remaining tutorials discuss more advanced topics.


Tutorial 1: Heat equation

This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with homogeneous Dirichlet boundary conditions. This tutorial introduces and teaches many important parts of Ferrite: problem setup, degree of freedom management, assembly procedure, boundary conditions, solving the linear system, visualization of the result). Understanding this tutorial is essential to follow more complex tutorials.

Keywords: scalar-valued solution, Dirichlet boundary conditions.


Tutorial 2: Linear elasticity

TBW.

Keywords: vector-valued solution, Dirichlet and Neumann boundary conditions.


Tutorial 3: Incompressible elasticity

This tutorial focuses on a mixed formulation of linear elasticity, with (vector) displacement and (scalar) pressure as the two unknowns, suitable for incompressibility. Thus, this tutorial guides you through the process of solving a problem with two unknowns from two coupled weak forms. The problem that is studied is Cook's membrane in the incompressible limit.

Keywords: mixed finite elements, Dirichlet and Neumann boundary conditions.


Tutorial 4: Hyperelasticity

In this tutorial you will learn how to solve a non-linear finite element problem. In particular, a hyperelastic material model, in a finite strain setting, is used to solve the rotation of a cube. Automatic differentiatio (AD) is used for the consitutive relations. Newton's method is used for the non-linear iteration, and a conjugate gradient (CG) solver is used for the linear solution of the increment.

Keywords: non-linear finite element, finite strain, automatic differentiation (AD), Newton's method, conjugate gradient (CG).


Tutorial 5: von Mises Plasticity

This tutorial revisits the cantilever beam problem from Tutorial 2: Linear elasticity, but instead of linear elasticity a plasticity model is used for the constitutive relation. You will learn how to solve a problem which require the solution of a local material problem, and the storage of material state, in each quadrature point. Newton's method is used both locally in the material routine, and globally on the finite element level.

Keywords: non-linear finite element, plasticity, material modeling, state variables, Newton’s method.


Tutorial 6: Transient heat equation

In this tutorial the transient heat equation is solved on the unit square. The problem to be solved is thus similar to the one solved in the first tutorial, Heat equation, but with time-varying boundary conditions. In particular you will learn how to solve a time dependent problem with an implicit Euler scheme for the time integration.

Keywords: time dependent finite elements, implicit Euler time integration.


Tutorial 7: Computational homogenization

This tutorial guides you through computational homogenization of an representative volume element (RVE) consisting of a soft matrix material with stiff inclusions. The computational mesh is read from an external mesh file generated with Gmsh. Dirichlet and periodic boundary conditions are used.

Keywords: Gmsh mesh reading, Dirichlet and periodic boundary conditions


Tutorial 8: Stokes flow

In this tutorial Stokes flow with (vector) velocity and (scalar) pressure is solved on on a quarter circle. Rotationally periodic boundary conditions is used for the inlet/outlet coupling. To obtain a unique solution, a mean value constraint is applied on the pressure using an affine constraint. The computational mesh is generated directly using the Gmsh API.

Keywords: periodic boundary conditions, mean value constraint, mesh generation with Gmsh.


Tutorial 9: Porous media (SubDofHandler)

This tutorial introduces how to solve a complex linear problem, where there are different fields on different subdomains, and different cell types in the grid. This requires using the SubDofHandler interface.

Keywords: Mixed grids, multiple fields, porous media, SubDofHandler


Tutorial 10: Incompressible Navier-Stokes equations

In this tutorial the incompressible Navier-Stokes equations are solved. The domain is discretized in space with Ferrite as usual, and then forumalated in a way to be compatible with the OrdinaryDiffEq.jl package, which is used for the time-integration.

Keywords: non-linear time dependent problem


Tutorial 10: Reactive surface

In this tutorial a reaction diffusion system on a sphere surface embedded in 3D is solved. Ferrite is used to assemble the diffusion operators and the mass matrices. The problem is solved by using the usual first order reaction diffusion operator splitting.

Keywords: embedded elements, operator splitting, gmsh


Tutorial 11: Linear shell

In this tutorial a linear shell element formulation is set up as a two-dimensional domain embedded in three-dimensional space. This will teach, and perhaps inspire, you on how Ferrite can be used for non-standard things and how to add "hacks" that build on top of Ferrite.

Keywords: shell elements, automatic differentiation


Tutorial 12: Discontinuous Galerkin heat equation

This tutorial guides you through the process of solving the linear stationary heat equation (i.e. Poisson's equation) on a unit square with inhomogeneous Dirichlet and Neumann boundary conditions using the interior penalty discontinuous Galerkin method. This tutorial follows the heat equation tutorial, introducing face and interface iterators, jump and average operators, and cross-element coupling in sparsity patterns. This example was developed as part of the Google Summer of Code funded project "Discontinuous Galerkin Infrastructure For the finite element toolbox Ferrite.jl".

Keywords: scalar-valued solution, Dirichlet boundary conditions, Discontinuous Galerkin, Interior penalty

diff --git a/previews/PR1096/tutorials/linear_elasticity.ipynb b/previews/PR1096/tutorials/linear_elasticity.ipynb index b4d5f0b7af..d9cab061ac 100644 --- a/previews/PR1096/tutorials/linear_elasticity.ipynb +++ b/previews/PR1096/tutorials/linear_elasticity.ipynb @@ -382,7 +382,7 @@ "cell_type": "code", "source": [ "Emod = 200.0e3 # Young's modulus [MPa]\n", - "ν = 0.3 # Poisson's ratio [-]\n", + "ν = 0.3 # Poisson's ratio [-]\n", "\n", "Gmod = Emod / (2(1 + ν)) # Shear modulus\n", "Kmod = Emod / (3(1 - 2ν)) # Bulk modulus" @@ -619,8 +619,8 @@ " \"1\" => 1, \"5\" => 1, # purple #hide\n", " \"2\" => 2, \"3\" => 2, # red #hide\n", " \"4\" => 3, # blue #hide\n", - " \"6\" => 4, # green #hide\n", - "] #hide\n", + " \"6\" => 4, # green #hide\n", + "] #hide\n", "for (key, color) in colors #hide\n", " for i in getcellset(grid, key) #hide\n", " color_data[i] = color #hide\n", diff --git a/previews/PR1096/tutorials/linear_elasticity.jl b/previews/PR1096/tutorials/linear_elasticity.jl index ea45a755f8..0eeb1839ce 100644 --- a/previews/PR1096/tutorials/linear_elasticity.jl +++ b/previews/PR1096/tutorials/linear_elasticity.jl @@ -63,7 +63,7 @@ function assemble_external_forces!(f_ext, dh, facetset, facetvalues, prescribed_ end Emod = 200.0e3 # Young's modulus [MPa] -ν = 0.3 # Poisson's ratio [-] +ν = 0.3 # Poisson's ratio [-] Gmod = Emod / (2(1 + ν)) # Shear modulus Kmod = Emod / (3(1 - 2ν)) # Bulk modulus @@ -147,8 +147,8 @@ colors = [ #hide "1" => 1, "5" => 1, # purple #hide "2" => 2, "3" => 2, # red #hide "4" => 3, # blue #hide - "6" => 4, # green #hide -] #hide + "6" => 4, # green #hide +] #hide for (key, color) in colors #hide for i in getcellset(grid, key) #hide color_data[i] = color #hide diff --git a/previews/PR1096/tutorials/linear_elasticity/index.html b/previews/PR1096/tutorials/linear_elasticity/index.html index 8176fd520d..c63ff9db77 100644 --- a/previews/PR1096/tutorials/linear_elasticity/index.html +++ b/previews/PR1096/tutorials/linear_elasticity/index.html @@ -68,7 +68,7 @@ \boldsymbol{\varepsilon}^\mathrm{vol} &= \frac{\mathrm{tr}(\boldsymbol{\varepsilon})}{3}\boldsymbol{I}, \quad \boldsymbol{\varepsilon}^\mathrm{dev} &= \boldsymbol{\varepsilon} - \boldsymbol{\varepsilon}^\mathrm{vol} \end{align*}\]

Starting from Young's modulus, $E$, and Poisson's ratio, $\nu$, the shear and bulk modulus are

\[G = \frac{E}{2(1 + \nu)}, \quad K = \frac{E}{3(1 - 2\nu)}\]

Emod = 200.0e3 # Young's modulus [MPa]
-ν = 0.3      # Poisson's ratio [-]
+ν = 0.3        # Poisson's ratio [-]
 
 Gmod = Emod / (2(1 + ν))  # Shear modulus
 Kmod = Emod / (3(1 - 2ν)) # Bulk modulus
166666.66666666663

Finally, we demonstrate Tensors.jl's automatic differentiation capabilities when calculating the elastic stiffness tensor

C = gradient(ϵ -> 2 * Gmod * dev(ϵ) + 3 * Kmod * vol(ϵ), zero(SymmetricTensor{2, 2}));
Plane stress instead of plane strain?

In order to change this tutorial to consider plane stress instead of plane strain, the elastic stiffness tensor should be changed to reflect this. The plane stress elasticity stiffness matrix in Voigt notation for engineering shear strains, is given as

\[\underline{\underline{\boldsymbol{E}}} = \frac{E}{1 - \nu^2}\begin{bmatrix} @@ -203,7 +203,7 @@ end Emod = 200.0e3 # Young's modulus [MPa] -ν = 0.3 # Poisson's ratio [-] +ν = 0.3 # Poisson's ratio [-] Gmod = Emod / (2(1 + ν)) # Shear modulus Kmod = Emod / (3(1 - 2ν)) # Bulk modulus @@ -290,4 +290,4 @@ end write_projection(vtk, proj, stress_field, "stress field") Ferrite.write_cellset(vtk, grid) -end


This page was generated using Literate.jl.

+end

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/linear_shell.ipynb b/previews/PR1096/tutorials/linear_shell.ipynb index 238b8a1994..f915788cbf 100644 --- a/previews/PR1096/tutorials/linear_shell.ipynb +++ b/previews/PR1096/tutorials/linear_shell.ipynb @@ -3,6 +3,7 @@ { "cell_type": "markdown", "source": [ + "runic: off\n", "# Linear shell\n", "\n", "![](linear_shell.png)" @@ -35,89 +36,87 @@ "using ForwardDiff\n", "\n", "function main() #wrap everything in a function...\n", - " # First we generate a flat rectangular mesh. There is currently no built-in function for generating\n", - " # shell meshes in Ferrite, so we have to create our own simple mesh generator (see the\n", - " # function `generate_shell_grid` further down in this file).\n", - " nels = (10, 10)\n", - " size = (10.0, 10.0)\n", - " grid = generate_shell_grid(nels, size)\n", - " # Here we define the bi-linear interpolation used for the geometrical description of the shell.\n", - " # We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use\n", - " # under integration for the inplane integration, to avoid shear locking.\n", - " ip = Lagrange{RefQuadrilateral, 1}()\n", - " qr_inplane = QuadratureRule{RefQuadrilateral}(1)\n", - " qr_ooplane = QuadratureRule{RefLine}(2)\n", - " cv = CellValues(qr_inplane, ip, ip^3)\n", - " # Next we distribute displacement dofs,`:u = (x,y,z)` and rotational dofs, `:θ = (θ₁, θ₂)`.\n", - " dh = DofHandler(grid)\n", - " add!(dh, :u, ip^3)\n", - " add!(dh, :θ, ip^2)\n", - " close!(dh)\n", - " # In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This\n", - " # is done with `addfacetset!` and `addvertexset!`\n", - " addfacetset!(grid, \"left\", (x) -> x[1] ≈ 0.0)\n", - " addfacetset!(grid, \"right\", (x) -> x[1] ≈ size[1])\n", - " addvertexset!(grid, \"corner\", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0)\n", - " # Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations.\n", - " ch = ConstraintHandler(dh)\n", - " add!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1, 3]))\n", - " add!(ch, Dirichlet(:θ, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1, 2]))\n", - " # On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation.\n", - " add!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> (0.0, 0.0), [1, 3]))\n", - " add!(ch, Dirichlet(:θ, getfacetset(grid, \"right\"), (x, t) -> (0.0, pi / 10), [1, 2]))\n", - " # In order to not get rigid body motion, we lock the y-displacement in one of the corners.\n", - " add!(ch, Dirichlet(:θ, getvertexset(grid, \"corner\"), (x, t) -> (0.0), [2]))\n", - "\n", - " close!(ch)\n", - " update!(ch, 0.0)\n", - " # Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material.\n", - " # In this linear shell, plane stress is assumed, ie $\\\\sigma_{zz} = 0$. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6).\n", - " κ = 5 / 6 # Shear correction factor\n", - " E = 210.0\n", - " ν = 0.3\n", - " a = (1 - ν) / 2\n", - " C = E / (1 - ν^2) * [\n", - " 1 ν 0 0 0;\n", - " ν 1 0 0 0;\n", - " 0 0 a * κ 0 0;\n", - " 0 0 0 a * κ 0;\n", - " 0 0 0 0 a * κ\n", - " ]\n", - "\n", - "\n", - " data = (thickness = 1.0, C = C) #Named tuple\n", - " # We now assemble the problem in standard finite element fashion\n", - " nnodes = getnbasefunctions(ip)\n", - " ndofs_shell = ndofs_per_cell(dh)\n", - "\n", - " K = allocate_matrix(dh)\n", - " f = zeros(Float64, ndofs(dh))\n", - "\n", - " ke = zeros(ndofs_shell, ndofs_shell)\n", - " fe = zeros(ndofs_shell)\n", - "\n", - " celldofs = zeros(Int, ndofs_shell)\n", - " cellcoords = zeros(Vec{3, Float64}, nnodes)\n", - "\n", - " assembler = start_assemble(K, f)\n", - " for cell in CellIterator(grid)\n", - " fill!(ke, 0.0)\n", - " reinit!(cv, cell)\n", - " celldofs!(celldofs, dh, cellid(cell))\n", - " getcoordinates!(cellcoords, grid, cellid(cell))\n", - "\n", - " #Call the element routine\n", - " integrate_shell!(ke, cv, qr_ooplane, cellcoords, data)\n", - "\n", - " assemble!(assembler, celldofs, ke, fe)\n", - " end\n", - " # Apply BC and solve.\n", - " apply!(K, f, ch)\n", - " a = K \\ f\n", - " # Output results.\n", - " return VTKGridFile(\"linear_shell\", dh) do vtk\n", - " write_solution(vtk, dh, a)\n", - " end\n", + "# First we generate a flat rectangular mesh. There is currently no built-in function for generating\n", + "# shell meshes in Ferrite, so we have to create our own simple mesh generator (see the\n", + "# function `generate_shell_grid` further down in this file).\n", + "nels = (10,10)\n", + "size = (10.0, 10.0)\n", + "grid = generate_shell_grid(nels, size)\n", + "# Here we define the bi-linear interpolation used for the geometrical description of the shell.\n", + "# We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use\n", + "# under integration for the inplane integration, to avoid shear locking.\n", + "ip = Lagrange{RefQuadrilateral,1}()\n", + "qr_inplane = QuadratureRule{RefQuadrilateral}(1)\n", + "qr_ooplane = QuadratureRule{RefLine}(2)\n", + "cv = CellValues(qr_inplane, ip, ip^3)\n", + "# Next we distribute displacement dofs,`:u = (x,y,z)` and rotational dofs, `:θ = (θ₁, θ₂)`.\n", + "dh = DofHandler(grid)\n", + "add!(dh, :u, ip^3)\n", + "add!(dh, :θ, ip^2)\n", + "close!(dh)\n", + "# In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This\n", + "# is done with `addfacetset!` and `addvertexset!`\n", + "addfacetset!(grid, \"left\", (x) -> x[1] ≈ 0.0)\n", + "addfacetset!(grid, \"right\", (x) -> x[1] ≈ size[1])\n", + "addvertexset!(grid, \"corner\", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0)\n", + "# Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations.\n", + "ch = ConstraintHandler(dh)\n", + "add!(ch, Dirichlet(:u, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1,3]) )\n", + "add!(ch, Dirichlet(:θ, getfacetset(grid, \"left\"), (x, t) -> (0.0, 0.0), [1,2]) )\n", + "# On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation.\n", + "add!(ch, Dirichlet(:u, getfacetset(grid, \"right\"), (x, t) -> (0.0, 0.0), [1,3]) )\n", + "add!(ch, Dirichlet(:θ, getfacetset(grid, \"right\"), (x, t) -> (0.0, pi/10), [1,2]) )\n", + "# In order to not get rigid body motion, we lock the y-displacement in one of the corners.\n", + "add!(ch, Dirichlet(:θ, getvertexset(grid, \"corner\"), (x, t) -> (0.0), [2]) )\n", + "\n", + "close!(ch)\n", + "update!(ch, 0.0)\n", + "# Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material.\n", + "# In this linear shell, plane stress is assumed, ie $\\\\sigma_{zz} = 0$. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6).\n", + "κ = 5/6 # Shear correction factor\n", + "E = 210.0\n", + "ν = 0.3\n", + "a = (1-ν)/2\n", + "C = E/(1-ν^2) * [1 ν 0 0 0;\n", + " ν 1 0 0 0;\n", + " 0 0 a*κ 0 0;\n", + " 0 0 0 a*κ 0;\n", + " 0 0 0 0 a*κ]\n", + "\n", + "\n", + "data = (thickness = 1.0, C = C); #Named tuple\n", + "# We now assemble the problem in standard finite element fashion\n", + "nnodes = getnbasefunctions(ip)\n", + "ndofs_shell = ndofs_per_cell(dh)\n", + "\n", + "K = allocate_matrix(dh)\n", + "f = zeros(Float64, ndofs(dh))\n", + "\n", + "ke = zeros(ndofs_shell, ndofs_shell)\n", + "fe = zeros(ndofs_shell)\n", + "\n", + "celldofs = zeros(Int, ndofs_shell)\n", + "cellcoords = zeros(Vec{3,Float64}, nnodes)\n", + "\n", + "assembler = start_assemble(K, f)\n", + "for cell in CellIterator(grid)\n", + " fill!(ke, 0.0)\n", + " reinit!(cv, cell)\n", + " celldofs!(celldofs, dh, cellid(cell))\n", + " getcoordinates!(cellcoords, grid, cellid(cell))\n", + "\n", + " #Call the element routine\n", + " integrate_shell!(ke, cv, qr_ooplane, cellcoords, data)\n", + "\n", + " assemble!(assembler, celldofs, ke, fe)\n", + "end\n", + "# Apply BC and solve.\n", + "apply!(K, f, ch)\n", + "a = K\\f\n", + "# Output results.\n", + "VTKGridFile(\"linear_shell\", dh) do vtk\n", + " write_solution(vtk, dh, a)\n", + "end\n", "\n", "end; #end main functions" ], @@ -137,7 +136,7 @@ "cell_type": "code", "source": [ "function generate_shell_grid(nels, size)\n", - " _grid = generate_grid(Quadrilateral, nels, Vec((0.0, 0.0)), Vec(size))\n", + " _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size))\n", " nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node for n in _grid.nodes]\n", "\n", " grid = Grid(_grid.cells, nodes)\n", @@ -173,20 +172,16 @@ "outputs": [], "cell_type": "code", "source": [ - "function fiber_coordsys(Ps::Vector{Vec{3, Float64}})\n", + "function fiber_coordsys(Ps::Vector{Vec{3,Float64}})\n", "\n", - " ef1 = Vec{3, Float64}[]\n", - " ef2 = Vec{3, Float64}[]\n", - " ef3 = Vec{3, Float64}[]\n", + " ef1 = Vec{3,Float64}[]\n", + " ef2 = Vec{3,Float64}[]\n", + " ef3 = Vec{3,Float64}[]\n", " for P in Ps\n", " a = abs.(P)\n", " j = 1\n", - " if a[1] > a[3]\n", - " a[3] = a[1]; j = 2\n", - " end\n", - " if a[2] > a[3]\n", - " j = 3\n", - " end\n", + " if a[1] > a[3]; a[3] = a[1]; j = 2; end\n", + " if a[2] > a[3]; j = 3; end\n", "\n", " e3 = P\n", " e2 = Tensors.cross(P, basevec(Vec{3}, j))\n", @@ -225,26 +220,26 @@ " e2 = zero(Vec{3})\n", "\n", " for i in 1:length(dNdξ)\n", - " e1 += dNdξ[i][1] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i]\n", - " e2 += dNdξ[i][2] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i]\n", + " e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]\n", + " e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]\n", " end\n", "\n", " e1 /= norm(e1)\n", " e2 /= norm(e2)\n", "\n", - " ez = Tensors.cross(e1, e2)\n", + " ez = Tensors.cross(e1,e2)\n", " ez /= norm(ez)\n", "\n", - " a = 0.5 * (e1 + e2)\n", + " a = 0.5*(e1 + e2)\n", " a /= norm(a)\n", "\n", - " b = Tensors.cross(ez, a)\n", + " b = Tensors.cross(ez,a)\n", " b /= norm(b)\n", "\n", - " ex = sqrt(2) / 2 * (a - b)\n", - " ey = sqrt(2) / 2 * (a + b)\n", + " ex = sqrt(2)/2 * (a - b)\n", + " ey = sqrt(2)/2 * (a + b)\n", "\n", - " return Tensor{2, 3}(hcat(ex, ey, ez))\n", + " return Tensor{2,3}(hcat(ex,ey,ez))\n", "end;" ], "metadata": {}, @@ -273,18 +268,18 @@ "cell_type": "code", "source": [ "function getjacobian(q, N, dNdξ, ζ, X, p, h)\n", - " J = zeros(3, 3)\n", + " J = zeros(3,3)\n", " for a in 1:length(N)\n", " for i in 1:3, j in 1:3\n", - " _dNdξ = (j == 3) ? 0.0 : dNdξ[a][j]\n", - " _dζdξ = (j == 3) ? 1.0 : 0.0\n", + " _dNdξ = (j==3) ? 0.0 : dNdξ[a][j]\n", + " _dζdξ = (j==3) ? 1.0 : 0.0\n", " _N = N[a]\n", "\n", - " J[i, j] += _dNdξ * X[a][i] + (_dNdξ * ζ + _N * _dζdξ) * h / 2 * p[a][i]\n", + " J[i,j] += _dNdξ * X[a][i] + (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i]\n", " end\n", " end\n", "\n", - " return (q' * J) |> Tensor{2, 3, Float64}\n", + " return (q' * J) |> Tensor{2,3,Float64}\n", "end;" ], "metadata": {}, @@ -316,20 +311,20 @@ "outputs": [], "cell_type": "code", "source": [ - "function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where {T}\n", + "function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T\n", "\n", - " u = reinterpret(Vec{3, T}, dofvec[1:12])\n", - " θ = reinterpret(Vec{2, T}, dofvec[13:20])\n", + " u = reinterpret(Vec{3,T}, dofvec[1:12])\n", + " θ = reinterpret(Vec{2,T}, dofvec[13:20])\n", "\n", " dudx = zeros(T, 3, 3)\n", " for m in 1:3, j in 1:3\n", " for a in 1:length(N)\n", - " dudx[m, j] += dNdx[a][j] * u[a][m] + h / 2 * (dNdx[a][j] * ζ + N[a] * dζdx[j]) * (θ[a][2] * ef1[a][m] - θ[a][1] * ef2[a][m])\n", + " dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m])\n", " end\n", " end\n", "\n", - " dudx = q * dudx\n", - " ε = [dudx[1, 1], dudx[2, 2], dudx[1, 2] + dudx[2, 1], dudx[2, 3] + dudx[3, 2], dudx[1, 3] + dudx[3, 1]]\n", + " dudx = q*dudx\n", + " ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]]\n", " return ε\n", "end;" ], @@ -353,7 +348,7 @@ "\n", "function integrate_shell!(ke, cv, qr_ooplane, X, data)\n", " nnodes = getnbasefunctions(cv)\n", - " ndofs = nnodes * 5\n", + " ndofs = nnodes*5\n", " h = data.thickness\n", "\n", " #Create the directors in each node.\n", @@ -362,7 +357,7 @@ " p = zeros(Vec{3}, nnodes)\n", " for i in 1:nnodes\n", " a = Vec{3}((0.0, 0.0, 1.0))\n", - " p[i] = a / norm(a)\n", + " p[i] = a/norm(a)\n", " end\n", "\n", " ef1, ef2, ef3 = fiber_coordsys(p)\n", @@ -382,14 +377,12 @@ "\n", " #For simplicity, use automatic differentiation to construct the B-matrix from the strain.\n", " B = ForwardDiff.jacobian(\n", - " (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs)\n", - " )\n", + " (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) )\n", "\n", " dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp)\n", - " ke .+= B' * data.C * B * dV\n", + " ke .+= B'*data.C*B * dV\n", " end\n", " end\n", - " return\n", "end;" ], "metadata": {}, diff --git a/previews/PR1096/tutorials/linear_shell.jl b/previews/PR1096/tutorials/linear_shell.jl index 993b4291ad..14070a205c 100644 --- a/previews/PR1096/tutorials/linear_shell.jl +++ b/previews/PR1096/tutorials/linear_shell.jl @@ -3,87 +3,85 @@ using ForwardDiff function main() #wrap everything in a function... - nels = (10, 10) - size = (10.0, 10.0) - grid = generate_shell_grid(nels, size) +nels = (10,10) +size = (10.0, 10.0) +grid = generate_shell_grid(nels, size) - ip = Lagrange{RefQuadrilateral, 1}() - qr_inplane = QuadratureRule{RefQuadrilateral}(1) - qr_ooplane = QuadratureRule{RefLine}(2) - cv = CellValues(qr_inplane, ip, ip^3) +ip = Lagrange{RefQuadrilateral,1}() +qr_inplane = QuadratureRule{RefQuadrilateral}(1) +qr_ooplane = QuadratureRule{RefLine}(2) +cv = CellValues(qr_inplane, ip, ip^3) - dh = DofHandler(grid) - add!(dh, :u, ip^3) - add!(dh, :θ, ip^2) - close!(dh) +dh = DofHandler(grid) +add!(dh, :u, ip^3) +add!(dh, :θ, ip^2) +close!(dh) - addfacetset!(grid, "left", (x) -> x[1] ≈ 0.0) - addfacetset!(grid, "right", (x) -> x[1] ≈ size[1]) - addvertexset!(grid, "corner", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0) +addfacetset!(grid, "left", (x) -> x[1] ≈ 0.0) +addfacetset!(grid, "right", (x) -> x[1] ≈ size[1]) +addvertexset!(grid, "corner", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0) - ch = ConstraintHandler(dh) - add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1, 3])) - add!(ch, Dirichlet(:θ, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1, 2])) +ch = ConstraintHandler(dh) +add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,3]) ) +add!(ch, Dirichlet(:θ, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,2]) ) - add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> (0.0, 0.0), [1, 3])) - add!(ch, Dirichlet(:θ, getfacetset(grid, "right"), (x, t) -> (0.0, pi / 10), [1, 2])) +add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> (0.0, 0.0), [1,3]) ) +add!(ch, Dirichlet(:θ, getfacetset(grid, "right"), (x, t) -> (0.0, pi/10), [1,2]) ) - add!(ch, Dirichlet(:θ, getvertexset(grid, "corner"), (x, t) -> (0.0), [2])) +add!(ch, Dirichlet(:θ, getvertexset(grid, "corner"), (x, t) -> (0.0), [2]) ) - close!(ch) - update!(ch, 0.0) +close!(ch) +update!(ch, 0.0) - κ = 5 / 6 # Shear correction factor - E = 210.0 - ν = 0.3 - a = (1 - ν) / 2 - C = E / (1 - ν^2) * [ - 1 ν 0 0 0; - ν 1 0 0 0; - 0 0 a * κ 0 0; - 0 0 0 a * κ 0; - 0 0 0 0 a * κ - ] +κ = 5/6 # Shear correction factor +E = 210.0 +ν = 0.3 +a = (1-ν)/2 +C = E/(1-ν^2) * [1 ν 0 0 0; + ν 1 0 0 0; + 0 0 a*κ 0 0; + 0 0 0 a*κ 0; + 0 0 0 0 a*κ] - data = (thickness = 1.0, C = C) #Named tuple +data = (thickness = 1.0, C = C); #Named tuple - nnodes = getnbasefunctions(ip) - ndofs_shell = ndofs_per_cell(dh) +nnodes = getnbasefunctions(ip) +ndofs_shell = ndofs_per_cell(dh) - K = allocate_matrix(dh) - f = zeros(Float64, ndofs(dh)) +K = allocate_matrix(dh) +f = zeros(Float64, ndofs(dh)) - ke = zeros(ndofs_shell, ndofs_shell) - fe = zeros(ndofs_shell) +ke = zeros(ndofs_shell, ndofs_shell) +fe = zeros(ndofs_shell) - celldofs = zeros(Int, ndofs_shell) - cellcoords = zeros(Vec{3, Float64}, nnodes) +celldofs = zeros(Int, ndofs_shell) +cellcoords = zeros(Vec{3,Float64}, nnodes) - assembler = start_assemble(K, f) - for cell in CellIterator(grid) - fill!(ke, 0.0) - reinit!(cv, cell) - celldofs!(celldofs, dh, cellid(cell)) - getcoordinates!(cellcoords, grid, cellid(cell)) +assembler = start_assemble(K, f) +for cell in CellIterator(grid) + fill!(ke, 0.0) + reinit!(cv, cell) + celldofs!(celldofs, dh, cellid(cell)) + getcoordinates!(cellcoords, grid, cellid(cell)) - #Call the element routine - integrate_shell!(ke, cv, qr_ooplane, cellcoords, data) + #Call the element routine + integrate_shell!(ke, cv, qr_ooplane, cellcoords, data) - assemble!(assembler, celldofs, ke, fe) - end + assemble!(assembler, celldofs, ke, fe) +end - apply!(K, f, ch) - a = K \ f +apply!(K, f, ch) +a = K\f - return VTKGridFile("linear_shell", dh) do vtk - write_solution(vtk, dh, a) - end +VTKGridFile("linear_shell", dh) do vtk + write_solution(vtk, dh, a) +end end; #end main functions function generate_shell_grid(nels, size) - _grid = generate_grid(Quadrilateral, nels, Vec((0.0, 0.0)), Vec(size)) + _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size)) nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node for n in _grid.nodes] grid = Grid(_grid.cells, nodes) @@ -91,20 +89,16 @@ function generate_shell_grid(nels, size) return grid end; -function fiber_coordsys(Ps::Vector{Vec{3, Float64}}) +function fiber_coordsys(Ps::Vector{Vec{3,Float64}}) - ef1 = Vec{3, Float64}[] - ef2 = Vec{3, Float64}[] - ef3 = Vec{3, Float64}[] + ef1 = Vec{3,Float64}[] + ef2 = Vec{3,Float64}[] + ef3 = Vec{3,Float64}[] for P in Ps a = abs.(P) j = 1 - if a[1] > a[3] - a[3] = a[1]; j = 2 - end - if a[2] > a[3] - j = 3 - end + if a[1] > a[3]; a[3] = a[1]; j = 2; end + if a[2] > a[3]; j = 3; end e3 = P e2 = Tensors.cross(P, basevec(Vec{3}, j)) @@ -125,57 +119,57 @@ function lamina_coordsys(dNdξ, ζ, x, p, h) e2 = zero(Vec{3}) for i in 1:length(dNdξ) - e1 += dNdξ[i][1] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i] - e2 += dNdξ[i][2] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i] + e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i] + e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i] end e1 /= norm(e1) e2 /= norm(e2) - ez = Tensors.cross(e1, e2) + ez = Tensors.cross(e1,e2) ez /= norm(ez) - a = 0.5 * (e1 + e2) + a = 0.5*(e1 + e2) a /= norm(a) - b = Tensors.cross(ez, a) + b = Tensors.cross(ez,a) b /= norm(b) - ex = sqrt(2) / 2 * (a - b) - ey = sqrt(2) / 2 * (a + b) + ex = sqrt(2)/2 * (a - b) + ey = sqrt(2)/2 * (a + b) - return Tensor{2, 3}(hcat(ex, ey, ez)) + return Tensor{2,3}(hcat(ex,ey,ez)) end; function getjacobian(q, N, dNdξ, ζ, X, p, h) - J = zeros(3, 3) + J = zeros(3,3) for a in 1:length(N) for i in 1:3, j in 1:3 - _dNdξ = (j == 3) ? 0.0 : dNdξ[a][j] - _dζdξ = (j == 3) ? 1.0 : 0.0 + _dNdξ = (j==3) ? 0.0 : dNdξ[a][j] + _dζdξ = (j==3) ? 1.0 : 0.0 _N = N[a] - J[i, j] += _dNdξ * X[a][i] + (_dNdξ * ζ + _N * _dζdξ) * h / 2 * p[a][i] + J[i,j] += _dNdξ * X[a][i] + (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i] end end - return (q' * J) |> Tensor{2, 3, Float64} + return (q' * J) |> Tensor{2,3,Float64} end; -function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where {T} +function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T - u = reinterpret(Vec{3, T}, dofvec[1:12]) - θ = reinterpret(Vec{2, T}, dofvec[13:20]) + u = reinterpret(Vec{3,T}, dofvec[1:12]) + θ = reinterpret(Vec{2,T}, dofvec[13:20]) dudx = zeros(T, 3, 3) for m in 1:3, j in 1:3 for a in 1:length(N) - dudx[m, j] += dNdx[a][j] * u[a][m] + h / 2 * (dNdx[a][j] * ζ + N[a] * dζdx[j]) * (θ[a][2] * ef1[a][m] - θ[a][1] * ef2[a][m]) + dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m]) end end - dudx = q * dudx - ε = [dudx[1, 1], dudx[2, 2], dudx[1, 2] + dudx[2, 1], dudx[2, 3] + dudx[3, 2], dudx[1, 3] + dudx[3, 1]] + dudx = q*dudx + ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]] return ε end; @@ -183,7 +177,7 @@ shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_ function integrate_shell!(ke, cv, qr_ooplane, X, data) nnodes = getnbasefunctions(cv) - ndofs = nnodes * 5 + ndofs = nnodes*5 h = data.thickness #Create the directors in each node. @@ -192,7 +186,7 @@ function integrate_shell!(ke, cv, qr_ooplane, X, data) p = zeros(Vec{3}, nnodes) for i in 1:nnodes a = Vec{3}((0.0, 0.0, 1.0)) - p[i] = a / norm(a) + p[i] = a/norm(a) end ef1, ef2, ef3 = fiber_coordsys(p) @@ -212,14 +206,12 @@ function integrate_shell!(ke, cv, qr_ooplane, X, data) #For simplicity, use automatic differentiation to construct the B-matrix from the strain. B = ForwardDiff.jacobian( - (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) - ) + (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) ) dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp) - ke .+= B' * data.C * B * dV + ke .+= B'*data.C*B * dV end end - return end; main() diff --git a/previews/PR1096/tutorials/linear_shell/index.html b/previews/PR1096/tutorials/linear_shell/index.html index 9ea7bf6c09..a83ed18e9d 100644 --- a/previews/PR1096/tutorials/linear_shell/index.html +++ b/previews/PR1096/tutorials/linear_shell/index.html @@ -1,85 +1,79 @@ -Linear shell · Ferrite.jl

Linear shell

Introduction

In this example we show how shell elements can be analyzed with Ferrite. The shell implemented here comes from the book "The finite element method - Linear static and dynamic finite element analysis" by Hughes (1987), and a brief description of it is given at the end of this tutorial. The first part of the tutorial explains how to set up the problem.

Setting up the problem

using Ferrite
+Linear shell · Ferrite.jl

runic: off

Linear shell

Introduction

In this example we show how shell elements can be analyzed with Ferrite. The shell implemented here comes from the book "The finite element method - Linear static and dynamic finite element analysis" by Hughes (1987), and a brief description of it is given at the end of this tutorial. The first part of the tutorial explains how to set up the problem.

Setting up the problem

using Ferrite
 using ForwardDiff
 
-function main() #wrap everything in a function...

First we generate a flat rectangular mesh. There is currently no built-in function for generating shell meshes in Ferrite, so we have to create our own simple mesh generator (see the function generate_shell_grid further down in this file).

    nels = (10, 10)
-    size = (10.0, 10.0)
-    grid = generate_shell_grid(nels, size)

Here we define the bi-linear interpolation used for the geometrical description of the shell. We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use under integration for the inplane integration, to avoid shear locking.

    ip = Lagrange{RefQuadrilateral, 1}()
-    qr_inplane = QuadratureRule{RefQuadrilateral}(1)
-    qr_ooplane = QuadratureRule{RefLine}(2)
-    cv = CellValues(qr_inplane, ip, ip^3)

Next we distribute displacement dofs,:u = (x,y,z) and rotational dofs, :θ = (θ₁, θ₂).

    dh = DofHandler(grid)
-    add!(dh, :u, ip^3)
-    add!(dh, :θ, ip^2)
-    close!(dh)

In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This is done with addfacetset! and addvertexset!

    addfacetset!(grid, "left", (x) -> x[1] ≈ 0.0)
-    addfacetset!(grid, "right", (x) -> x[1] ≈ size[1])
-    addvertexset!(grid, "corner", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0)

Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations.

    ch = ConstraintHandler(dh)
-    add!(ch, Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1, 3]))
-    add!(ch, Dirichlet(:θ, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1, 2]))

On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation.

    add!(ch, Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> (0.0, 0.0), [1, 3]))
-    add!(ch, Dirichlet(:θ, getfacetset(grid, "right"), (x, t) -> (0.0, pi / 10), [1, 2]))

In order to not get rigid body motion, we lock the y-displacement in one of the corners.

    add!(ch, Dirichlet(:θ, getvertexset(grid, "corner"), (x, t) -> (0.0), [2]))
-
-    close!(ch)
-    update!(ch, 0.0)

Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material. In this linear shell, plane stress is assumed, ie $\\sigma_{zz} = 0$. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6).

    κ = 5 / 6 # Shear correction factor
-    E = 210.0
-    ν = 0.3
-    a = (1 - ν) / 2
-    C = E / (1 - ν^2) * [
-        1 ν 0   0   0;
-        ν 1 0   0   0;
-        0 0 a * κ 0   0;
-        0 0 0   a * κ 0;
-        0 0 0   0   a * κ
-    ]
-
-
-    data = (thickness = 1.0, C = C)  #Named tuple

We now assemble the problem in standard finite element fashion

    nnodes = getnbasefunctions(ip)
-    ndofs_shell = ndofs_per_cell(dh)
-
-    K = allocate_matrix(dh)
-    f = zeros(Float64, ndofs(dh))
-
-    ke = zeros(ndofs_shell, ndofs_shell)
-    fe = zeros(ndofs_shell)
-
-    celldofs = zeros(Int, ndofs_shell)
-    cellcoords = zeros(Vec{3, Float64}, nnodes)
-
-    assembler = start_assemble(K, f)
-    for cell in CellIterator(grid)
-        fill!(ke, 0.0)
-        reinit!(cv, cell)
-        celldofs!(celldofs, dh, cellid(cell))
-        getcoordinates!(cellcoords, grid, cellid(cell))
-
-        #Call the element routine
-        integrate_shell!(ke, cv, qr_ooplane, cellcoords, data)
-
-        assemble!(assembler, celldofs, ke, fe)
-    end

Apply BC and solve.

    apply!(K, f, ch)
-    a = K \ f

Output results.

    return VTKGridFile("linear_shell", dh) do vtk
-        write_solution(vtk, dh, a)
-    end
+function main() #wrap everything in a function...

First we generate a flat rectangular mesh. There is currently no built-in function for generating shell meshes in Ferrite, so we have to create our own simple mesh generator (see the function generate_shell_grid further down in this file).

nels = (10,10)
+size = (10.0, 10.0)
+grid = generate_shell_grid(nels, size)

Here we define the bi-linear interpolation used for the geometrical description of the shell. We also create two quadrature rules for the in-plane and out-of-plane directions. Note that we use under integration for the inplane integration, to avoid shear locking.

ip = Lagrange{RefQuadrilateral,1}()
+qr_inplane = QuadratureRule{RefQuadrilateral}(1)
+qr_ooplane = QuadratureRule{RefLine}(2)
+cv = CellValues(qr_inplane, ip, ip^3)

Next we distribute displacement dofs,:u = (x,y,z) and rotational dofs, :θ = (θ₁, θ₂).

dh = DofHandler(grid)
+add!(dh, :u, ip^3)
+add!(dh, :θ, ip^2)
+close!(dh)

In order to apply our boundary conditions, we first need to create some facet- and vertex-sets. This is done with addfacetset! and addvertexset!

addfacetset!(grid, "left",  (x) -> x[1] ≈ 0.0)
+addfacetset!(grid, "right", (x) -> x[1] ≈ size[1])
+addvertexset!(grid, "corner", (x) -> x[1] ≈ 0.0 && x[2] ≈ 0.0 && x[3] ≈ 0.0)

Here we define the boundary conditions. On the left edge, we lock the displacements in the x- and z- directions, and all the rotations.

ch = ConstraintHandler(dh)
+add!(ch,  Dirichlet(:u, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,3])  )
+add!(ch,  Dirichlet(:θ, getfacetset(grid, "left"), (x, t) -> (0.0, 0.0), [1,2])  )

On the right edge, we also lock the displacements in the x- and z- directions, but apply a precribed rotation.

add!(ch,  Dirichlet(:u, getfacetset(grid, "right"), (x, t) -> (0.0, 0.0), [1,3])  )
+add!(ch,  Dirichlet(:θ, getfacetset(grid, "right"), (x, t) -> (0.0, pi/10), [1,2])  )

In order to not get rigid body motion, we lock the y-displacement in one of the corners.

add!(ch,  Dirichlet(:θ, getvertexset(grid, "corner"), (x, t) -> (0.0), [2])  )
+
+close!(ch)
+update!(ch, 0.0)

Next we define relevant data for the shell, such as shear correction factor and stiffness matrix for the material. In this linear shell, plane stress is assumed, ie $\\sigma_{zz} = 0$. Therefor, the stiffness matrix is 5x5 (opposed to the normal 6x6).

κ = 5/6 # Shear correction factor
+E = 210.0
+ν = 0.3
+a = (1-ν)/2
+C = E/(1-ν^2) * [1 ν 0   0   0;
+                ν 1 0   0   0;
+                0 0 a*κ 0   0;
+                0 0 0   a*κ 0;
+                0 0 0   0   a*κ]
+
+
+data = (thickness = 1.0, C = C); #Named tuple

We now assemble the problem in standard finite element fashion

nnodes = getnbasefunctions(ip)
+ndofs_shell = ndofs_per_cell(dh)
+
+K = allocate_matrix(dh)
+f = zeros(Float64, ndofs(dh))
+
+ke = zeros(ndofs_shell, ndofs_shell)
+fe = zeros(ndofs_shell)
+
+celldofs = zeros(Int, ndofs_shell)
+cellcoords = zeros(Vec{3,Float64}, nnodes)
+
+assembler = start_assemble(K, f)
+for cell in CellIterator(grid)
+    fill!(ke, 0.0)
+    reinit!(cv, cell)
+    celldofs!(celldofs, dh, cellid(cell))
+    getcoordinates!(cellcoords, grid, cellid(cell))
+
+    #Call the element routine
+    integrate_shell!(ke, cv, qr_ooplane, cellcoords, data)
+
+    assemble!(assembler, celldofs, ke, fe)
+end

Apply BC and solve.

apply!(K, f, ch)
+a = K\f

Output results.

VTKGridFile("linear_shell", dh) do vtk
+    write_solution(vtk, dh, a)
+end
 
 end; #end main functions

Below is the function that creates the shell mesh. It simply generates a 2d-quadrature mesh, and appends a third coordinate (z-direction) to the node-positions.

function generate_shell_grid(nels, size)
-    _grid = generate_grid(Quadrilateral, nels, Vec((0.0, 0.0)), Vec(size))
+    _grid = generate_grid(Quadrilateral, nels, Vec((0.0,0.0)), Vec(size))
     nodes = [(n.x[1], n.x[2], 0.0) |> Vec{3} |> Node  for n in _grid.nodes]
 
     grid = Grid(_grid.cells, nodes)
 
     return grid
-end;

The shell element

The shell presented here comes from the book "The finite element method - Linear static and dynamic finite element analysis" by Hughes (1987). The shell is a so called degenerate shell element, meaning it is based on a continuum element. A brief description of the shell is given here.

Note

This element might experience various locking phenomenas, and should only be seen as a proof of concept.

Fiber coordinate system

The element uses two coordinate systems. The first coordianate system, called the fiber system, is created for each element node, and is used as a reference frame for the rotations. The function below implements an algorithm that return the fiber directions, $\boldsymbol{e}^{f}_{a1}$, $\boldsymbol{e}^{f}_{a2}$ and $\boldsymbol{e}^{f}_{a3}$, at each node $a$.

function fiber_coordsys(Ps::Vector{Vec{3, Float64}})
+end;

The shell element

The shell presented here comes from the book "The finite element method - Linear static and dynamic finite element analysis" by Hughes (1987). The shell is a so called degenerate shell element, meaning it is based on a continuum element. A brief description of the shell is given here.

Note

This element might experience various locking phenomenas, and should only be seen as a proof of concept.

Fiber coordinate system

The element uses two coordinate systems. The first coordianate system, called the fiber system, is created for each element node, and is used as a reference frame for the rotations. The function below implements an algorithm that return the fiber directions, $\boldsymbol{e}^{f}_{a1}$, $\boldsymbol{e}^{f}_{a2}$ and $\boldsymbol{e}^{f}_{a3}$, at each node $a$.

function fiber_coordsys(Ps::Vector{Vec{3,Float64}})
 
-    ef1 = Vec{3, Float64}[]
-    ef2 = Vec{3, Float64}[]
-    ef3 = Vec{3, Float64}[]
+    ef1 = Vec{3,Float64}[]
+    ef2 = Vec{3,Float64}[]
+    ef3 = Vec{3,Float64}[]
     for P in Ps
         a = abs.(P)
         j = 1
-        if a[1] > a[3]
-            a[3] = a[1]; j = 2
-        end
-        if a[2] > a[3]
-            j = 3
-        end
+        if a[1] > a[3]; a[3] = a[1]; j = 2; end
+        if a[2] > a[3]; j = 3; end
 
         e3 = P
         e2 = Tensors.cross(P, basevec(Vec{3}, j))
@@ -98,62 +92,62 @@
     e2 = zero(Vec{3})
 
     for i in 1:length(dNdξ)
-        e1 += dNdξ[i][1] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i]
-        e2 += dNdξ[i][2] * x[i] + 0.5 * h * ζ * dNdξ[i][1] * p[i]
+        e1 += dNdξ[i][1] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]
+        e2 += dNdξ[i][2] * x[i] + 0.5*h*ζ * dNdξ[i][1] * p[i]
     end
 
     e1 /= norm(e1)
     e2 /= norm(e2)
 
-    ez = Tensors.cross(e1, e2)
+    ez = Tensors.cross(e1,e2)
     ez /= norm(ez)
 
-    a = 0.5 * (e1 + e2)
+    a = 0.5*(e1 + e2)
     a /= norm(a)
 
-    b = Tensors.cross(ez, a)
+    b = Tensors.cross(ez,a)
     b /= norm(b)
 
-    ex = sqrt(2) / 2 * (a - b)
-    ey = sqrt(2) / 2 * (a + b)
+    ex = sqrt(2)/2 * (a - b)
+    ey = sqrt(2)/2 * (a + b)
 
-    return Tensor{2, 3}(hcat(ex, ey, ez))
+    return Tensor{2,3}(hcat(ex,ey,ez))
 end;
Geometrical description

A material point in the shell is defined as

\[\boldsymbol x(\xi, \eta, \zeta) = \sum_{a=1}^{N_{\text{nodes}}} N_a(\xi, \eta) \boldsymbol{\bar{x}}_{a} + ζ \frac{h}{2} \boldsymbol{\bar{p}_a}\]

where $\boldsymbol{\bar{x}}_{a}$ are nodal positions on the mid-surface, and $\boldsymbol{\bar{p}_a}$ is an vector that defines the fiber direction on the reference surface. $N_a$ arethe shape functions.

Based on the definition of the position vector, we create an function for obtaining the Jacobian-matrix,

\[J_{ij} = \frac{\partial x_i}{\partial \xi_j},\]

function getjacobian(q, N, dNdξ, ζ, X, p, h)
-    J = zeros(3, 3)
+    J = zeros(3,3)
     for a in 1:length(N)
         for i in 1:3, j in 1:3
-            _dNdξ = (j == 3) ? 0.0 : dNdξ[a][j]
-            _dζdξ = (j == 3) ? 1.0 : 0.0
+            _dNdξ = (j==3) ? 0.0 : dNdξ[a][j]
+            _dζdξ = (j==3) ? 1.0 : 0.0
             _N = N[a]
 
-            J[i, j] += _dNdξ * X[a][i] + (_dNdξ * ζ + _N * _dζdξ) * h / 2 * p[a][i]
+            J[i,j] += _dNdξ * X[a][i]  +  (_dNdξ*ζ + _N*_dζdξ) * h/2 * p[a][i]
         end
     end
 
-    return (q' * J) |> Tensor{2, 3, Float64}
+    return (q' * J) |> Tensor{2,3,Float64}
 end;
Strains

Small deformation is assumed,

\[\varepsilon_{ij}= \frac{1}{2}(\frac{\partial u_{i}}{\partial x_j} + \frac{\partial u_{j}}{\partial x_i})\]

The displacement field is calculated as:

\[\boldsymbol u = \sum_{a=1}^{N_{\text{nodes}}} N_a \bar{\boldsymbol u}_{a} + N_a ζ\frac{h}{2}(\theta_{a2} \boldsymbol e^{f}_{a1} - \theta_{a1} \boldsymbol e^{f}_{a2}) \]

The gradient of the displacement (in the lamina coordinate system), then becomes:

\[\frac{\partial u_{i}}{\partial x_j} = \sum_{m=1}^3 q_{im} \sum_{a=1}^{N_{\text{nodes}}} \frac{\partial N_a}{\partial x_j} \bar{u}_{am} + - \frac{\partial(N_a ζ)}{\partial x_j} \frac{h}{2} (\theta_{a2} e^{f}_{am1} - \theta_{a1} e^{f}_{am2})\]

function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where {T}
+ \frac{\partial(N_a ζ)}{\partial x_j} \frac{h}{2} (\theta_{a2} e^{f}_{am1} - \theta_{a1} e^{f}_{am2})\]

function strain(dofvec::Vector{T}, N, dNdx, ζ, dζdx, q, ef1, ef2, h) where T
 
-    u = reinterpret(Vec{3, T}, dofvec[1:12])
-    θ = reinterpret(Vec{2, T}, dofvec[13:20])
+    u = reinterpret(Vec{3,T}, dofvec[1:12])
+    θ = reinterpret(Vec{2,T}, dofvec[13:20])
 
     dudx = zeros(T, 3, 3)
     for m in 1:3, j in 1:3
         for a in 1:length(N)
-            dudx[m, j] += dNdx[a][j] * u[a][m] + h / 2 * (dNdx[a][j] * ζ + N[a] * dζdx[j]) * (θ[a][2] * ef1[a][m] - θ[a][1] * ef2[a][m])
+            dudx[m,j] += dNdx[a][j] * u[a][m] + h/2 * (dNdx[a][j]*ζ + N[a]*dζdx[j]) * (θ[a][2]*ef1[a][m] - θ[a][1]*ef2[a][m])
         end
     end
 
-    dudx = q * dudx
-    ε = [dudx[1, 1], dudx[2, 2], dudx[1, 2] + dudx[2, 1], dudx[2, 3] + dudx[3, 2], dudx[1, 3] + dudx[3, 1]]
+    dudx = q*dudx
+    ε = [dudx[1,1], dudx[2,2], dudx[1,2]+dudx[2,1], dudx[2,3]+dudx[3,2], dudx[1,3]+dudx[3,1]]
     return ε
 end;
Main element routine

Below is the main routine that calculates the stiffness matrix of the shell element. Since it is a so called degenerate shell element, the code is similar to that for an standard continuum element.

shape_reference_gradient(cv::CellValues, q_point, i) = cv.fun_values.dNdξ[i, q_point]
 
 function integrate_shell!(ke, cv, qr_ooplane, X, data)
     nnodes = getnbasefunctions(cv)
-    ndofs = nnodes * 5
+    ndofs = nnodes*5
     h = data.thickness
 
     #Create the directors in each node.
@@ -162,7 +156,7 @@
     p = zeros(Vec{3}, nnodes)
     for i in 1:nnodes
         a = Vec{3}((0.0, 0.0, 1.0))
-        p[i] = a / norm(a)
+        p[i] = a/norm(a)
     end
 
     ef1, ef2, ef3 = fiber_coordsys(p)
@@ -182,12 +176,10 @@
 
             #For simplicity, use automatic differentiation to construct the B-matrix from the strain.
             B = ForwardDiff.jacobian(
-                (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs)
-            )
+                (a) -> strain(a, N, dNdx, ζ, dζdx, q, ef1, ef2, h), zeros(Float64, ndofs) )
 
             dV = qr_ooplane.weights[oqp] * getdetJdV(cv, iqp)
-            ke .+= B' * data.C * B * dV
+            ke .+= B'*data.C*B * dV
         end
     end
-    return
-end;

Run everything:

main()
VTKGridFile for the closed file "linear_shell.vtu".

This page was generated using Literate.jl.

+end;

Run everything:

main()
VTKGridFile for the closed file "linear_shell.vtu".

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/ns_vs_diffeq.ipynb b/previews/PR1096/tutorials/ns_vs_diffeq.ipynb index 8477bc986d..15f27ea2b9 100644 --- a/previews/PR1096/tutorials/ns_vs_diffeq.ipynb +++ b/previews/PR1096/tutorials/ns_vs_diffeq.ipynb @@ -30,8 +30,9 @@ "\n", "## Remarks on DifferentialEquations.jl\n", "\n", - "!!! note \"Required Version\"\n", - " This example will only work with OrdinaryDiffEq@v6.80.1. or above\n", + "> **Required Version**\n", + ">\n", + "> This example will only work with OrdinaryDiffEq@v6.80.1. or above\n", "\n", "Many \"time step solvers\" of [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl) assume that that the\n", "problem is provided in mass matrix form. The incompressible Navier-Stokes\n", @@ -218,7 +219,7 @@ " circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag])\n", " gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)])\n", "else #hide\n", - " rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide\n", + " rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide\n", "end #hide\n", "nothing #hide" ], @@ -260,11 +261,11 @@ " toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, \"top\")\n", " holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, \"hole\")\n", "else #hide\n", - " gmsh.model.model.add_physical_group(dim - 1, [4], 7, \"left\") #hide\n", - " gmsh.model.model.add_physical_group(dim - 1, [3], 8, \"top\") #hide\n", - " gmsh.model.model.add_physical_group(dim - 1, [2], 9, \"right\") #hide\n", - " gmsh.model.model.add_physical_group(dim - 1, [1], 10, \"bottom\") #hide\n", - "end #hide\n", + " gmsh.model.model.add_physical_group(dim - 1, [4], 7, \"left\") #hide\n", + " gmsh.model.model.add_physical_group(dim - 1, [3], 8, \"top\") #hide\n", + " gmsh.model.model.add_physical_group(dim - 1, [2], 9, \"right\") #hide\n", + " gmsh.model.model.add_physical_group(dim - 1, [1], 10, \"bottom\") #hide\n", + "end #hide\n", "nothing #hide" ], "metadata": {}, @@ -286,8 +287,8 @@ "gmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20)\n", "gmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.05)\n", "if IS_CI #hide\n", - " gmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20) #hide\n", - " gmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.15) #hide\n", + " gmsh.option.setNumber(\"Mesh.MeshSizeFromCurvature\", 20) #hide\n", + " gmsh.option.setNumber(\"Mesh.MeshSizeMax\", 0.15) #hide\n", "end #hide" ], "metadata": {}, @@ -358,7 +359,7 @@ "\n", "nosplip_facet_names = [\"top\", \"bottom\", \"hole\"];\n", "if IS_CI #hide\n", - " nosplip_facet_names = [\"top\", \"bottom\"] #hide\n", + " nosplip_facet_names = [\"top\", \"bottom\"] #hide\n", "end #hide\n", "∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...);\n", "noslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2])\n", @@ -390,9 +391,10 @@ { "cell_type": "markdown", "source": [ - "!!! note\n", - " The kink in the velocity profile will lead to a discontinuity in the pressure at $t=1$.\n", - " This needs to be considered in the DiffEq `init` by providing the keyword argument `d_discontinuities=[1.0]`." + "> **Note**\n", + ">\n", + "> The kink in the velocity profile will lead to a discontinuity in the pressure at $t=1$.\n", + "> This needs to be considered in the DiffEq `init` by providing the keyword argument `d_discontinuities=[1.0]`." ], "metadata": {} }, @@ -557,11 +559,12 @@ "### Solution of the semi-discretized system via DifferentialEquations.jl\n", "First we assemble the linear portions for efficiency. These matrices are\n", "assumed to be constant over time.\n", - "!!! note\n", - " To obtain the vortex street a small time step is important to resolve\n", - " the small oscillation forming. The mesh size becomes important to\n", - " \"only\" resolve the smaller vertices forming, but less important for\n", - " the initial formation." + "> **Note**\n", + ">\n", + "> To obtain the vortex street a small time step is important to resolve\n", + "> the small oscillation forming. The mesh size becomes important to\n", + "> \"only\" resolve the smaller vertices forming, but less important for\n", + "> the initial formation." ], "metadata": {} }, @@ -644,9 +647,10 @@ "stage limiters. The correct norms are computed by passing down a custom norm which simply\n", "ignores all constrained dofs.\n", "\n", - "!!! note\n", - " An alternative strategy is to hook into the nonlinear and linear solvers and enforce\n", - " the solution therein. However, this is not possible at the time of writing this tutorial." + "> **Note**\n", + ">\n", + "> An alternative strategy is to hook into the nonlinear and linear solvers and enforce\n", + "> the solution therein. However, this is not possible at the time of writing this tutorial." ], "metadata": {} }, @@ -833,8 +837,9 @@ "Finally we have to communicate the time step length and initialization\n", "algorithm. Since we start with a valid initial state we do not use one of\n", "DifferentialEquations.jl initialization algorithms.\n", - "!!! note \"DAE initialization\"\n", - " At the time of writing this [no Hessenberg index 2 initialization is implemented](https://github.com/SciML/OrdinaryDiffEq.jl/issues/1019).\n", + "> **DAE initialization**\n", + ">\n", + "> At the time of writing this [no Hessenberg index 2 initialization is implemented](https://github.com/SciML/OrdinaryDiffEq.jl/issues/1019).\n", "\n", "To visualize the result we export the grid and our fields\n", "to VTK-files, which can be viewed in [ParaView](https://www.paraview.org/)\n", @@ -854,8 +859,9 @@ { "cell_type": "markdown", "source": [ - "!!! info \"Debugging convergence issues\"\n", - " We can obtain some debug information from OrdinaryDiffEq by wrapping the following section into a [debug logger](https://docs.julialang.org/en/v1/stdlib/Logging/#Example:-Enable-debug-level-messages)." + "> **Debugging convergence issues**\n", + ">\n", + "> We can obtain some debug information from OrdinaryDiffEq by wrapping the following section into a [debug logger](https://docs.julialang.org/en/v1/stdlib/Logging/#Example:-Enable-debug-level-messages)." ], "metadata": {} }, @@ -876,9 +882,10 @@ { "cell_type": "markdown", "source": [ - "!!! note \"Export of solution\"\n", - " Exporting interpolated solutions of problems containing mass matrices is currently broken.\n", - " Thus, the `intervals` iterator is used. Note that `solve` holds all solutions in the memory." + "> **Export of solution**\n", + ">\n", + "> Exporting interpolated solutions of problems containing mass matrices is currently broken.\n", + "> Thus, the `intervals` iterator is used. Note that `solve` holds all solutions in the memory." ], "metadata": {} }, @@ -917,7 +924,7 @@ " let #hide\n", " u = copy(integrator.u) #hide\n", " Δdivv = abs(compute_divergence(dh, u, cellvalues_v)) #hide\n", - " @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide\n", + " @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide\n", " #hide\n", " Δv = 0.0 #hide\n", " for cell in CellIterator(dh) #hide\n", @@ -930,10 +937,10 @@ " dΩ = getdetJdV(cellvalues_v, q_point) #hide\n", " coords_qp = spatial_coordinate(cellvalues_v, q_point, coords) #hide\n", " v = function_value(cellvalues_v, q_point, v_cell) #hide\n", - " Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide\n", + " Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide\n", " end #hide\n", " end #hide\n", - " @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide\n", + " @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide\n", " end #hide\n", " nothing #hide\n", "end #hide" diff --git a/previews/PR1096/tutorials/ns_vs_diffeq.jl b/previews/PR1096/tutorials/ns_vs_diffeq.jl index 8072084d1f..60253ef954 100644 --- a/previews/PR1096/tutorials/ns_vs_diffeq.jl +++ b/previews/PR1096/tutorials/ns_vs_diffeq.jl @@ -24,7 +24,7 @@ if !IS_CI circle_surf_tag = gmsh.model.occ.add_plane_surface([circle_curve_tag]) gmsh.model.occ.cut([(dim, rect_tag)], [(dim, circle_surf_tag)]) else #hide - rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide + rect_tag = gmsh.model.occ.add_rectangle(0, 0, 0, 0.55, 0.41) #hide end #hide nothing #hide @@ -37,19 +37,19 @@ if !IS_CI toptag = gmsh.model.model.add_physical_group(dim - 1, [9], -1, "top") holetag = gmsh.model.model.add_physical_group(dim - 1, [5], -1, "hole") else #hide - gmsh.model.model.add_physical_group(dim - 1, [4], 7, "left") #hide - gmsh.model.model.add_physical_group(dim - 1, [3], 8, "top") #hide - gmsh.model.model.add_physical_group(dim - 1, [2], 9, "right") #hide - gmsh.model.model.add_physical_group(dim - 1, [1], 10, "bottom") #hide -end #hide + gmsh.model.model.add_physical_group(dim - 1, [4], 7, "left") #hide + gmsh.model.model.add_physical_group(dim - 1, [3], 8, "top") #hide + gmsh.model.model.add_physical_group(dim - 1, [2], 9, "right") #hide + gmsh.model.model.add_physical_group(dim - 1, [1], 10, "bottom") #hide +end #hide nothing #hide gmsh.option.setNumber("Mesh.Algorithm", 11) gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) gmsh.option.setNumber("Mesh.MeshSizeMax", 0.05) if IS_CI #hide - gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) #hide - gmsh.option.setNumber("Mesh.MeshSizeMax", 0.15) #hide + gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 20) #hide + gmsh.option.setNumber("Mesh.MeshSizeMax", 0.15) #hide end #hide gmsh.model.mesh.generate(dim) @@ -72,7 +72,7 @@ ch = ConstraintHandler(dh); nosplip_facet_names = ["top", "bottom", "hole"]; if IS_CI #hide - nosplip_facet_names = ["top", "bottom"] #hide + nosplip_facet_names = ["top", "bottom"] #hide end #hide ∂Ω_noslip = union(getfacetset.((grid,), nosplip_facet_names)...); noslip_bc = Dirichlet(:v, ∂Ω_noslip, (x, t) -> Vec((0.0, 0.0)), [1, 2]) @@ -343,7 +343,7 @@ if IS_CI let #hide u = copy(integrator.u) #hide Δdivv = abs(compute_divergence(dh, u, cellvalues_v)) #hide - @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide + @test isapprox(Δdivv, 0.0, atol = 1.0e-12) #hide #hide Δv = 0.0 #hide for cell in CellIterator(dh) #hide @@ -356,10 +356,10 @@ if IS_CI dΩ = getdetJdV(cellvalues_v, q_point) #hide coords_qp = spatial_coordinate(cellvalues_v, q_point, coords) #hide v = function_value(cellvalues_v, q_point, v_cell) #hide - Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide + Δv += norm(v - parabolic_inflow_profile(coords_qp, T))^2 * dΩ #hide end #hide end #hide - @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide + @test isapprox(sqrt(Δv), 0.0, atol = 1.0e-3) #hide end #hide nothing #hide end #hide diff --git a/previews/PR1096/tutorials/ns_vs_diffeq/index.html b/previews/PR1096/tutorials/ns_vs_diffeq/index.html index 632a0a5e43..e1cc69c4c1 100644 --- a/previews/PR1096/tutorials/ns_vs_diffeq/index.html +++ b/previews/PR1096/tutorials/ns_vs_diffeq/index.html @@ -546,4 +546,4 @@ pvd[t] = vtk end end -vtk_save(pvd);

This page was generated using Literate.jl.

+vtk_save(pvd);

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/plasticity.ipynb b/previews/PR1096/tutorials/plasticity.ipynb index 3d1729b40e..32510d826e 100644 --- a/previews/PR1096/tutorials/plasticity.ipynb +++ b/previews/PR1096/tutorials/plasticity.ipynb @@ -212,7 +212,7 @@ " σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress\n", " sᵗ = dev(σᵗ) # deviatoric part of trial-stress\n", " J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ\n", - " σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)\n", + " σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)\n", " σʸ = material.σ₀ + H * state.k # Previous yield limit\n", "\n", " φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface\n", @@ -221,7 +221,7 @@ " return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)\n", " else # plastic loading\n", " h = H + 3G\n", - " μ = φᵗ / h # plastic multiplier\n", + " μ = φᵗ / h # plastic multiplier\n", "\n", " c1 = 1 - 3G * μ / σᵗₑ\n", " s = c1 * sᵗ # updated deviatoric stress\n", @@ -241,8 +241,8 @@ "\n", " # Return new state\n", " Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain\n", - " ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain\n", - " k = state.k + μ # hardening variable\n", + " ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain\n", + " k = state.k + μ # hardening variable\n", " return σ, D, MaterialState(ϵᵖ, σ, k)\n", " end\n", "end" @@ -399,10 +399,7 @@ ], "cell_type": "code", "source": [ - "function assemble_cell!(\n", - " Ke, re, cell, cellvalues, material,\n", - " ue, state, state_old\n", - " )\n", + "function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)\n", " n_basefuncs = getnbasefunctions(cellvalues)\n", " reinit!(cellvalues, cell)\n", "\n", @@ -421,7 +418,8 @@ " end\n", " end\n", " end\n", - " return symmetrize_lower!(Ke)\n", + " symmetrize_lower!(Ke)\n", + " return\n", "end" ], "metadata": {}, @@ -500,10 +498,10 @@ "source": [ "function solve()\n", " # Define material parameters\n", - " E = 200.0e9 # [Pa]\n", + " E = 200.0e9 # [Pa]\n", " H = E / 20 # [Pa]\n", - " ν = 0.3 # [-]\n", - " σ₀ = 200.0e6 # [Pa]\n", + " ν = 0.3 # [-]\n", + " σ₀ = 200.0e6 # [Pa]\n", " material = J2Plasticity(E, ν, σ₀, H)\n", "\n", " L = 10.0 # beam length [m]\n", @@ -528,10 +526,10 @@ "\n", " # Pre-allocate solution vectors, etc.\n", " n_dofs = ndofs(dh) # total number of dofs\n", - " u = zeros(n_dofs) # solution vector\n", + " u = zeros(n_dofs) # solution vector\n", " Δu = zeros(n_dofs) # displacement correction\n", " r = zeros(n_dofs) # residual\n", - " K = allocate_matrix(dh) # tangent stiffness matrix\n", + " K = allocate_matrix(dh) # tangent stiffness matrix\n", "\n", " # Create material states. One array for each cell, where each element is an array of material-\n", " # states - one for each integration point\n", @@ -553,7 +551,6 @@ "\n", " while true\n", " newton_itr += 1\n", - "\n", " if newton_itr > 8\n", " error(\"Reached maximum Newton iterations, aborting\")\n", " break\n", @@ -723,182 +720,182 @@ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n" ], "image/svg+xml": [ "\n", "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", + "\n", "\n", - " \n", + " \n", " \n", " \n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n" ] }, diff --git a/previews/PR1096/tutorials/plasticity.jl b/previews/PR1096/tutorials/plasticity.jl index 3269390a51..baad41f525 100644 --- a/previews/PR1096/tutorials/plasticity.jl +++ b/previews/PR1096/tutorials/plasticity.jl @@ -48,7 +48,7 @@ function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticit σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress sᵗ = dev(σᵗ) # deviatoric part of trial-stress J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ - σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress) + σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress) σʸ = material.σ₀ + H * state.k # Previous yield limit φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface @@ -57,7 +57,7 @@ function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticit return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k) else # plastic loading h = H + 3G - μ = φᵗ / h # plastic multiplier + μ = φᵗ / h # plastic multiplier c1 = 1 - 3G * μ / σᵗₑ s = c1 * sᵗ # updated deviatoric stress @@ -77,8 +77,8 @@ function compute_stress_tangent(ϵ::SymmetricTensor{2, 3}, material::J2Plasticit # Return new state Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain - ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain - k = state.k + μ # hardening variable + ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain + k = state.k + μ # hardening variable return σ, D, MaterialState(ϵᵖ, σ, k) end end @@ -134,10 +134,7 @@ function doassemble!( return K, r end -function assemble_cell!( - Ke, re, cell, cellvalues, material, - ue, state, state_old - ) +function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old) n_basefuncs = getnbasefunctions(cellvalues) reinit!(cellvalues, cell) @@ -156,7 +153,8 @@ function assemble_cell!( end end end - return symmetrize_lower!(Ke) + symmetrize_lower!(Ke) + return end function symmetrize_lower!(K) @@ -189,10 +187,10 @@ end function solve() # Define material parameters - E = 200.0e9 # [Pa] + E = 200.0e9 # [Pa] H = E / 20 # [Pa] - ν = 0.3 # [-] - σ₀ = 200.0e6 # [Pa] + ν = 0.3 # [-] + σ₀ = 200.0e6 # [Pa] material = J2Plasticity(E, ν, σ₀, H) L = 10.0 # beam length [m] @@ -217,10 +215,10 @@ function solve() # Pre-allocate solution vectors, etc. n_dofs = ndofs(dh) # total number of dofs - u = zeros(n_dofs) # solution vector + u = zeros(n_dofs) # solution vector Δu = zeros(n_dofs) # displacement correction r = zeros(n_dofs) # residual - K = allocate_matrix(dh) # tangent stiffness matrix + K = allocate_matrix(dh) # tangent stiffness matrix # Create material states. One array for each cell, where each element is an array of material- # states - one for each integration point @@ -242,7 +240,6 @@ function solve() while true newton_itr += 1 - if newton_itr > 8 error("Reached maximum Newton iterations, aborting") break diff --git a/previews/PR1096/tutorials/plasticity/5f1a4585.svg b/previews/PR1096/tutorials/plasticity/d92a5fb5.svg similarity index 83% rename from previews/PR1096/tutorials/plasticity/5f1a4585.svg rename to previews/PR1096/tutorials/plasticity/d92a5fb5.svg index a3b201577b..752af24210 100644 --- a/previews/PR1096/tutorials/plasticity/5f1a4585.svg +++ b/previews/PR1096/tutorials/plasticity/d92a5fb5.svg @@ -1,89 +1,89 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR1096/tutorials/plasticity/index.html b/previews/PR1096/tutorials/plasticity/index.html index 18f0b18216..fddbcbe275 100644 --- a/previews/PR1096/tutorials/plasticity/index.html +++ b/previews/PR1096/tutorials/plasticity/index.html @@ -37,7 +37,7 @@ σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress sᵗ = dev(σᵗ) # deviatoric part of trial-stress J₂ = 0.5 * sᵗ ⊡ sᵗ # second invariant of sᵗ - σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress) + σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress) σʸ = material.σ₀ + H * state.k # Previous yield limit φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface @@ -46,7 +46,7 @@ return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k) else # plastic loading h = H + 3G - μ = φᵗ / h # plastic multiplier + μ = φᵗ / h # plastic multiplier c1 = 1 - 3G * μ / σᵗₑ s = c1 * sᵗ # updated deviatoric stress @@ -66,8 +66,8 @@ # Return new state Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain - ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain - k = state.k + μ # hardening variable + ϵᵖ = state.ϵᵖ + Δϵᵖ # plastic strain + k = state.k + μ # hardening variable return σ, D, MaterialState(ϵᵖ, σ, k) end end
compute_stress_tangent (generic function with 1 method)

FE-problem

What follows are methods for assembling and and solving the FE-problem.

function create_values(interpolation)
@@ -113,10 +113,7 @@
         assemble!(assembler, eldofs, ke, re)
     end
     return K, r
-end
doassemble! (generic function with 1 method)

Compute element contribution to the residual and the tangent.

Note

Due to symmetry, we only compute the lower half of the tangent and then symmetrize it.

function assemble_cell!(
-        Ke, re, cell, cellvalues, material,
-        ue, state, state_old
-    )
+end
doassemble! (generic function with 1 method)

Compute element contribution to the residual and the tangent.

Note

Due to symmetry, we only compute the lower half of the tangent and then symmetrize it.

function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)
     n_basefuncs = getnbasefunctions(cellvalues)
     reinit!(cellvalues, cell)
 
@@ -135,7 +132,8 @@
             end
         end
     end
-    return symmetrize_lower!(Ke)
+    symmetrize_lower!(Ke)
+    return
 end
assemble_cell! (generic function with 1 method)

Helper function to symmetrize the material tangent

function symmetrize_lower!(K)
     for i in 1:size(K, 1)
         for j in (i + 1):size(K, 1)
@@ -164,10 +162,10 @@
     return r
 end
doassemble_neumann! (generic function with 1 method)

Define a function which solves the FE-problem.

function solve()
     # Define material parameters
-    E = 200.0e9 # [Pa]
+    E = 200.0e9  # [Pa]
     H = E / 20   # [Pa]
-    ν = 0.3     # [-]
-    σ₀ = 200.0e6  # [Pa]
+    ν = 0.3      # [-]
+    σ₀ = 200.0e6 # [Pa]
     material = J2Plasticity(E, ν, σ₀, H)
 
     L = 10.0 # beam length [m]
@@ -192,10 +190,10 @@
 
     # Pre-allocate solution vectors, etc.
     n_dofs = ndofs(dh)  # total number of dofs
-    u = zeros(n_dofs)  # solution vector
+    u = zeros(n_dofs)   # solution vector
     Δu = zeros(n_dofs)  # displacement correction
     r = zeros(n_dofs)   # residual
-    K = allocate_matrix(dh)  # tangent stiffness matrix
+    K = allocate_matrix(dh) # tangent stiffness matrix
 
     # Create material states. One array for each cell, where each element is an array of material-
     # states - one for each integration point
@@ -217,7 +215,6 @@
 
         while true
             newton_itr += 1
-
             if newton_itr > 8
                 error("Reached maximum Newton iterations, aborting")
                 break
@@ -349,7 +346,7 @@
     markershape = :auto
 )
 ylabel!("Traction [Pa]")
-xlabel!("Maximum deflection [m]")
Example block output

Figure 2. Load-displacement-curve for the beam, showing a clear decrease in stiffness as more material starts to yield.

Plain program

Here follows a version of the program without any comments. The file is also available here: plasticity.jl.

using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf
+xlabel!("Maximum deflection [m]")
Example block output

Figure 2. Load-displacement-curve for the beam, showing a clear decrease in stiffness as more material starts to yield.

Plain program

Here follows a version of the program without any comments. The file is also available here: plasticity.jl.

using Ferrite, Tensors, SparseArrays, LinearAlgebra, Printf
 
 struct J2Plasticity{T, S <: SymmetricTensor{4, 3, T}}
     G::T  # Shear modulus
@@ -399,7 +396,7 @@
     σᵗ = material.Dᵉ ⊡ (ϵ - state.ϵᵖ) # trial-stress
     sᵗ = dev(σᵗ)         # deviatoric part of trial-stress
     J₂ = 0.5 * sᵗ ⊡ sᵗ   # second invariant of sᵗ
-    σᵗₑ = sqrt(3.0 * J₂)   # effective trial-stress (von Mises stress)
+    σᵗₑ = sqrt(3.0 * J₂) # effective trial-stress (von Mises stress)
     σʸ = material.σ₀ + H * state.k # Previous yield limit
 
     φᵗ = σᵗₑ - σʸ # Trial-value of the yield surface
@@ -408,7 +405,7 @@
         return σᵗ, material.Dᵉ, MaterialState(state.ϵᵖ, σᵗ, state.k)
     else # plastic loading
         h = H + 3G
-        μ = φᵗ / h   # plastic multiplier
+        μ = φᵗ / h # plastic multiplier
 
         c1 = 1 - 3G * μ / σᵗₑ
         s = c1 * sᵗ           # updated deviatoric stress
@@ -428,8 +425,8 @@
 
         # Return new state
         Δϵᵖ = 3 / 2 * μ / σₑ * s # plastic strain
-        ϵᵖ = state.ϵᵖ + Δϵᵖ    # plastic strain
-        k = state.k + μ        # hardening variable
+        ϵᵖ = state.ϵᵖ + Δϵᵖ      # plastic strain
+        k = state.k + μ          # hardening variable
         return σ, D, MaterialState(ϵᵖ, σ, k)
     end
 end
@@ -485,10 +482,7 @@
     return K, r
 end
 
-function assemble_cell!(
-        Ke, re, cell, cellvalues, material,
-        ue, state, state_old
-    )
+function assemble_cell!(Ke, re, cell, cellvalues, material, ue, state, state_old)
     n_basefuncs = getnbasefunctions(cellvalues)
     reinit!(cellvalues, cell)
 
@@ -507,7 +501,8 @@
             end
         end
     end
-    return symmetrize_lower!(Ke)
+    symmetrize_lower!(Ke)
+    return
 end
 
 function symmetrize_lower!(K)
@@ -540,10 +535,10 @@
 
 function solve()
     # Define material parameters
-    E = 200.0e9 # [Pa]
+    E = 200.0e9  # [Pa]
     H = E / 20   # [Pa]
-    ν = 0.3     # [-]
-    σ₀ = 200.0e6  # [Pa]
+    ν = 0.3      # [-]
+    σ₀ = 200.0e6 # [Pa]
     material = J2Plasticity(E, ν, σ₀, H)
 
     L = 10.0 # beam length [m]
@@ -568,10 +563,10 @@
 
     # Pre-allocate solution vectors, etc.
     n_dofs = ndofs(dh)  # total number of dofs
-    u = zeros(n_dofs)  # solution vector
+    u = zeros(n_dofs)   # solution vector
     Δu = zeros(n_dofs)  # displacement correction
     r = zeros(n_dofs)   # residual
-    K = allocate_matrix(dh)  # tangent stiffness matrix
+    K = allocate_matrix(dh) # tangent stiffness matrix
 
     # Create material states. One array for each cell, where each element is an array of material-
     # states - one for each integration point
@@ -593,7 +588,6 @@
 
         while true
             newton_itr += 1
-
             if newton_itr > 8
                 error("Reached maximum Newton iterations, aborting")
                 break
@@ -655,4 +649,4 @@
     markershape = :auto
 )
 ylabel!("Traction [Pa]")
-xlabel!("Maximum deflection [m]")

This page was generated using Literate.jl.

+xlabel!("Maximum deflection [m]")

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/porous_media.ipynb b/previews/PR1096/tutorials/porous_media.ipynb index ac30ff7a1a..50c19e29f6 100644 --- a/previews/PR1096/tutorials/porous_media.ipynb +++ b/previews/PR1096/tutorials/porous_media.ipynb @@ -505,7 +505,8 @@ " pvd[t] = vtk\n", " end\n", " end\n", - " return vtk_save(pvd)\n", + " vtk_save(pvd)\n", + " return\n", "end;" ], "metadata": {}, diff --git a/previews/PR1096/tutorials/porous_media.jl b/previews/PR1096/tutorials/porous_media.jl index 067f470b66..d2c4f0f4e3 100644 --- a/previews/PR1096/tutorials/porous_media.jl +++ b/previews/PR1096/tutorials/porous_media.jl @@ -231,7 +231,8 @@ function solve(dh, ch, domains; Δt = 0.025, t_total = 1.0) pvd[t] = vtk end end - return vtk_save(pvd) + vtk_save(pvd) + return end; dh, ch, domains = setup_problem() diff --git a/previews/PR1096/tutorials/porous_media/index.html b/previews/PR1096/tutorials/porous_media/index.html index 251e1df213..b53b80b0b8 100644 --- a/previews/PR1096/tutorials/porous_media/index.html +++ b/previews/PR1096/tutorials/porous_media/index.html @@ -237,7 +237,8 @@ pvd[t] = vtk end end - return vtk_save(pvd) + vtk_save(pvd) + return end;

Finally we call the functions to actually run the code

dh, ch, domains = setup_problem()
 solve(dh, ch, domains);

Plain program

Here follows a version of the program without any comments. The file is also available here: porous_media.jl.

using Ferrite, FerriteMeshParser, Tensors, WriteVTK
 
@@ -472,8 +473,9 @@
             pvd[t] = vtk
         end
     end
-    return vtk_save(pvd)
+    vtk_save(pvd)
+    return
 end;
 
 dh, ch, domains = setup_problem()
-solve(dh, ch, domains);

This page was generated using Literate.jl.

+solve(dh, ch, domains);

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/reactive_surface.ipynb b/previews/PR1096/tutorials/reactive_surface.ipynb index 1d5a9f037a..c9bab89128 100644 --- a/previews/PR1096/tutorials/reactive_surface.ipynb +++ b/previews/PR1096/tutorials/reactive_surface.ipynb @@ -73,10 +73,11 @@ "with $\\mathbf{r}^{\\mathrm{B}}(t_1) = \\mathbf{r}^{\\mathrm{A}}(t_2)$.\n", "This way we obtain a solution approximation $\\mathbf{r}(t_2) \\approx \\mathbf{r}^{\\mathrm{B}}(t_2)$.\n", "\n", - "!!! note\n", - " The operator splitting itself is an approximation, so even if we solve the subproblems analytically\n", - " we end up with having only a solution approximation. We also do not have a beginner friendly reference\n", - " for the theory behind operator splitting and can only refer to the original papers for each method." + "> **Note**\n", + ">\n", + "> The operator splitting itself is an approximation, so even if we solve the subproblems analytically\n", + "> we end up with having only a solution approximation. We also do not have a beginner friendly reference\n", + "> for the theory behind operator splitting and can only refer to the original papers for each method." ], "metadata": {} }, @@ -260,7 +261,8 @@ " end\n", " end\n", "\n", - " return u₀ .+= 0.01 * rand(ndofs(dh))\n", + " u₀ .+= 0.01 * rand(ndofs(dh))\n", + " return\n", "end;" ], "metadata": {}, @@ -307,7 +309,7 @@ " nodes = tonodes()\n", " elements, _ = toelements(2)\n", " gmsh.finalize()\n", - " return grid = Grid(elements, nodes)\n", + " return Grid(elements, nodes)\n", "end" ], "metadata": {}, @@ -330,10 +332,10 @@ "text": [ "Info : Meshing 1D...\n", "Info : [ 40%] Meshing curve 2 (Circle)\n", - "Info : Done meshing 1D (Wall 0.000102022s, CPU 0.000103s)\n", + "Info : Done meshing 1D (Wall 9.8975e-05s, CPU 0.0001s)\n", "Info : Meshing 2D...\n", "Info : Meshing surface 1 (Sphere, Frontal-Delaunay)\n", - "Info : Done meshing 2D (Wall 0.00920544s, CPU 0.009191s)\n", + "Info : Done meshing 2D (Wall 0.00943915s, CPU 0.009424s)\n", "Info : 160 nodes 328 elements\n", "Info : Refining mesh...\n", "Info : Meshing order 2 (curvilinear on)...\n", @@ -343,8 +345,8 @@ "Info : [ 70%] Meshing surface 1 order 2\n", "Info : [ 90%] Meshing volume 1 order 2\n", "Info : Surface mesh: worst distortion = 0.970866 (0 elements in ]0, 0.2]); worst gamma = 0.433887\n", - "Info : Done meshing order 2 (Wall 0.00112217s, CPU 0.001122s)\n", - "Info : Done refining mesh (Wall 0.00144469s, CPU 0.001444s)\n", + "Info : Done meshing order 2 (Wall 0.00115371s, CPU 0.001153s)\n", + "Info : Done refining mesh (Wall 0.00149271s, CPU 0.001493s)\n", "Info : Refining mesh...\n", "Info : Meshing order 2 (curvilinear on)...\n", "Info : [ 0%] Meshing curve 1 order 2\n", @@ -353,8 +355,8 @@ "Info : [ 70%] Meshing surface 1 order 2\n", "Info : [ 90%] Meshing volume 1 order 2\n", "Info : Surface mesh: worst distortion = 0.992408 (0 elements in ]0, 0.2]); worst gamma = 0.421451\n", - "Info : Done meshing order 2 (Wall 0.00424475s, CPU 0.004244s)\n", - "Info : Done refining mesh (Wall 0.00551566s, CPU 0.005515s)\n", + "Info : Done meshing order 2 (Wall 0.00458057s, CPU 0.004579s)\n", + "Info : Done refining mesh (Wall 0.00601544s, CPU 0.006014s)\n", "Info : Refining mesh...\n", "Info : Meshing order 2 (curvilinear on)...\n", "Info : [ 0%] Meshing curve 1 order 2\n", @@ -363,17 +365,9 @@ "Info : [ 70%] Meshing surface 1 order 2\n", "Info : [ 90%] Meshing volume 1 order 2\n", "Info : Surface mesh: worst distortion = 0.998082 (0 elements in ]0, 0.2]); worst gamma = 0.418317\n", - "Info : Done meshing order 2 (Wall 0.0168046s, CPU 0.016803s)\n", - "Info : Done refining mesh (Wall 0.022342s, CPU 0.022341s)\n" + "Info : Done meshing order 2 (Wall 0.0175366s, CPU 0.017534s)\n", + "Info : Done refining mesh (Wall 0.0231302s, CPU 0.02313s)\n" ] - }, - { - "output_type": "execute_result", - "data": { - "text/plain": "322-element Vector{String}:\n \"reactive-surface.pvd\"\n \"reactive-surface-0.vtu\"\n \"reactive-surface-10.vtu\"\n \"reactive-surface-20.vtu\"\n \"reactive-surface-30.vtu\"\n \"reactive-surface-40.vtu\"\n \"reactive-surface-50.vtu\"\n \"reactive-surface-60.vtu\"\n \"reactive-surface-70.vtu\"\n \"reactive-surface-80.vtu\"\n ⋮\n \"reactive-surface-3120.vtu\"\n \"reactive-surface-3130.vtu\"\n \"reactive-surface-3140.vtu\"\n \"reactive-surface-3150.vtu\"\n \"reactive-surface-3160.vtu\"\n \"reactive-surface-3170.vtu\"\n \"reactive-surface-3180.vtu\"\n \"reactive-surface-3190.vtu\"\n \"reactive-surface-3200.vtu\"" - }, - "metadata": {}, - "execution_count": 7 } ], "cell_type": "code", @@ -457,8 +451,8 @@ " # Finally we totate the solution to initialize the next timestep.\n", " uₜ₋₁ .= uₜ\n", " end\n", - "\n", - " return vtk_save(pvd)\n", + " vtk_save(pvd)\n", + " return\n", "end\n", "\n", "# This parametrization gives the spot pattern shown in the gif above.\n", diff --git a/previews/PR1096/tutorials/reactive_surface.jl b/previews/PR1096/tutorials/reactive_surface.jl index 80d4397bcc..7c7beb2afb 100644 --- a/previews/PR1096/tutorials/reactive_surface.jl +++ b/previews/PR1096/tutorials/reactive_surface.jl @@ -121,7 +121,8 @@ function setup_initial_conditions!(u₀::Vector, cellvalues::CellValues, dh::Dof end end - return u₀ .+= 0.01 * rand(ndofs(dh)) + u₀ .+= 0.01 * rand(ndofs(dh)) + return end; function create_embedded_sphere(refinements) @@ -144,7 +145,7 @@ function create_embedded_sphere(refinements) nodes = tonodes() elements, _ = toelements(2) gmsh.finalize() - return grid = Grid(elements, nodes) + return Grid(elements, nodes) end function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer) @@ -226,8 +227,8 @@ function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, r # Finally we totate the solution to initialize the next timestep. uₜ₋₁ .= uₜ end - - return vtk_save(pvd) + vtk_save(pvd) + return end # This parametrization gives the spot pattern shown in the gif above. diff --git a/previews/PR1096/tutorials/reactive_surface/index.html b/previews/PR1096/tutorials/reactive_surface/index.html index 798bff193e..c546365dcf 100644 --- a/previews/PR1096/tutorials/reactive_surface/index.html +++ b/previews/PR1096/tutorials/reactive_surface/index.html @@ -112,7 +112,8 @@ end end - return u₀ .+= 0.01 * rand(ndofs(dh)) + u₀ .+= 0.01 * rand(ndofs(dh)) + return end;

Mesh generation

In this section we define a routine to create a surface mesh with the help of FerriteGmsh.jl.

function create_embedded_sphere(refinements)
     gmsh.initialize()
 
@@ -133,7 +134,7 @@
     nodes = tonodes()
     elements, _ = toelements(2)
     gmsh.finalize()
-    return grid = Grid(elements, nodes)
+    return Grid(elements, nodes)
 end
create_embedded_sphere (generic function with 1 method)

Simulation routines

Now we define a function to setup and solve the problem with given feed and conversion rates $F$ and $k$, as well as the time step length and for how long we want to solve the model.

function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)
     # We start by setting up grid, dof handler and the matrices for the heat problem.
     grid = create_embedded_sphere(refinements)
@@ -213,33 +214,49 @@
         # Finally we totate the solution to initialize the next timestep.
         uₜ₋₁ .= uₜ
     end
-
-    return vtk_save(pvd)
+    vtk_save(pvd)
+    return
 end
 
 # This parametrization gives the spot pattern shown in the gif above.
 material = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)
-    gray_scott_on_sphere(material, 10.0, 32000.0, 3)
322-element Vector{String}:
- "reactive-surface.pvd"
- "reactive-surface-0.vtu"
- "reactive-surface-10.vtu"
- "reactive-surface-20.vtu"
- "reactive-surface-30.vtu"
- "reactive-surface-40.vtu"
- "reactive-surface-50.vtu"
- "reactive-surface-60.vtu"
- "reactive-surface-70.vtu"
- "reactive-surface-80.vtu"
- ⋮
- "reactive-surface-3120.vtu"
- "reactive-surface-3130.vtu"
- "reactive-surface-3140.vtu"
- "reactive-surface-3150.vtu"
- "reactive-surface-3160.vtu"
- "reactive-surface-3170.vtu"
- "reactive-surface-3180.vtu"
- "reactive-surface-3190.vtu"
- "reactive-surface-3200.vtu"

Plain program

Here follows a version of the program without any comments. The file is also available here: reactive_surface.jl.

using Ferrite, FerriteGmsh
+    gray_scott_on_sphere(material, 10.0, 32000.0, 3)
Info    : Meshing 1D...
+Info    : [ 40%] Meshing curve 2 (Circle)
+Info    : Done meshing 1D (Wall 8.9508e-05s, CPU 9.1e-05s)
+Info    : Meshing 2D...
+Info    : Meshing surface 1 (Sphere, Frontal-Delaunay)
+Info    : Done meshing 2D (Wall 0.00921163s, CPU 0.009198s)
+Info    : 160 nodes 328 elements
+Info    : Refining mesh...
+Info    : Meshing order 2 (curvilinear on)...
+Info    : [  0%] Meshing curve 1 order 2
+Info    : [ 30%] Meshing curve 2 order 2
+Info    : [ 50%] Meshing curve 3 order 2
+Info    : [ 70%] Meshing surface 1 order 2
+Info    : [ 90%] Meshing volume 1 order 2
+Info    : Surface mesh: worst distortion = 0.970866 (0 elements in ]0, 0.2]); worst gamma = 0.433887
+Info    : Done meshing order 2 (Wall 0.00115008s, CPU 0.001138s)
+Info    : Done refining mesh (Wall 0.00152758s, CPU 0.001515s)
+Info    : Refining mesh...
+Info    : Meshing order 2 (curvilinear on)...
+Info    : [  0%] Meshing curve 1 order 2
+Info    : [ 30%] Meshing curve 2 order 2
+Info    : [ 50%] Meshing curve 3 order 2
+Info    : [ 70%] Meshing surface 1 order 2
+Info    : [ 90%] Meshing volume 1 order 2
+Info    : Surface mesh: worst distortion = 0.992408 (0 elements in ]0, 0.2]); worst gamma = 0.421451
+Info    : Done meshing order 2 (Wall 0.0044962s, CPU 0.004496s)
+Info    : Done refining mesh (Wall 0.00586156s, CPU 0.005861s)
+Info    : Refining mesh...
+Info    : Meshing order 2 (curvilinear on)...
+Info    : [  0%] Meshing curve 1 order 2
+Info    : [ 30%] Meshing curve 2 order 2
+Info    : [ 50%] Meshing curve 3 order 2
+Info    : [ 70%] Meshing surface 1 order 2
+Info    : [ 90%] Meshing volume 1 order 2
+Info    : Surface mesh: worst distortion = 0.998082 (0 elements in ]0, 0.2]); worst gamma = 0.418317
+Info    : Done meshing order 2 (Wall 0.0173649s, CPU 0.017363s)
+Info    : Done refining mesh (Wall 0.0230621s, CPU 0.023062s)

Plain program

Here follows a version of the program without any comments. The file is also available here: reactive_surface.jl.

using Ferrite, FerriteGmsh
 using BlockArrays, SparseArrays, LinearAlgebra, WriteVTK
 
 struct GrayScottMaterial{T}
@@ -355,7 +372,8 @@
         end
     end
 
-    return u₀ .+= 0.01 * rand(ndofs(dh))
+    u₀ .+= 0.01 * rand(ndofs(dh))
+    return
 end;
 
 function create_embedded_sphere(refinements)
@@ -378,7 +396,7 @@
     nodes = tonodes()
     elements, _ = toelements(2)
     gmsh.finalize()
-    return grid = Grid(elements, nodes)
+    return Grid(elements, nodes)
 end
 
 function gray_scott_on_sphere(material::GrayScottMaterial, Δt::Real, T::Real, refinements::Integer)
@@ -460,10 +478,10 @@
         # Finally we totate the solution to initialize the next timestep.
         uₜ₋₁ .= uₜ
     end
-
-    return vtk_save(pvd)
+    vtk_save(pvd)
+    return
 end
 
 # This parametrization gives the spot pattern shown in the gif above.
 material = GrayScottMaterial(0.00016, 0.00008, 0.06, 0.062)
-    gray_scott_on_sphere(material, 10.0, 32000.0, 3)

This page was generated using Literate.jl.

+ gray_scott_on_sphere(material, 10.0, 32000.0, 3)

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/stokes-flow.ipynb b/previews/PR1096/tutorials/stokes-flow.ipynb index e0e3302bfe..41da655822 100644 --- a/previews/PR1096/tutorials/stokes-flow.ipynb +++ b/previews/PR1096/tutorials/stokes-flow.ipynb @@ -123,29 +123,30 @@ "(\\underline{\\underline{C}}_p)_{1j} = \\int_{\\Gamma} \\phi^p_j\\ \\mathrm{d}\\Gamma.\n", "$$\n", "\n", - "!!! note\n", - " The constraint matrix $\\underline{\\underline{C}}_p$ is the same matrix we would have\n", - " obtained when assembling the system with the Lagrange multiplier. In that case the\n", - " full system would be\n", - " $$\n", - " \\underline{\\underline{K}} =\n", - " \\begin{bmatrix}\n", - " \\underline{\\underline{K}}_{uu} & \\underline{\\underline{K}}_{pu}^\\textrm{T} &\n", - " \\underline{\\underline{0}}\\\\\n", - " \\underline{\\underline{K}}_{pu} & \\underline{\\underline{0}} & \\underline{\\underline{C}}_p^\\mathrm{T} \\\\\n", - " \\underline{\\underline{0}} & \\underline{\\underline{C}}_p & 0 \\\\\n", - " \\end{bmatrix}, \\quad\n", - " \\underline{a} = \\begin{bmatrix}\n", - " \\underline{a}_{u} \\\\\n", - " \\underline{a}_{p} \\\\\n", - " \\underline{a}_{\\lambda}\n", - " \\end{bmatrix}, \\quad\n", - " \\underline{f} = \\begin{bmatrix}\n", - " \\underline{f}_{u} \\\\\n", - " \\underline{0} \\\\\n", - " \\underline{0}\n", - " \\end{bmatrix}.\n", - " $$" + "> **Note**\n", + ">\n", + "> The constraint matrix $\\underline{\\underline{C}}_p$ is the same matrix we would have\n", + "> obtained when assembling the system with the Lagrange multiplier. In that case the\n", + "> full system would be\n", + "> $$\n", + "> \\underline{\\underline{K}} =\n", + "> \\begin{bmatrix}\n", + "> \\underline{\\underline{K}}_{uu} & \\underline{\\underline{K}}_{pu}^\\textrm{T} &\n", + "> \\underline{\\underline{0}}\\\\\n", + "> \\underline{\\underline{K}}_{pu} & \\underline{\\underline{0}} & \\underline{\\underline{C}}_p^\\mathrm{T} \\\\\n", + "> \\underline{\\underline{0}} & \\underline{\\underline{C}}_p & 0 \\\\\n", + "> \\end{bmatrix}, \\quad\n", + "> \\underline{a} = \\begin{bmatrix}\n", + "> \\underline{a}_{u} \\\\\n", + "> \\underline{a}_{p} \\\\\n", + "> \\underline{a}_{\\lambda}\n", + "> \\end{bmatrix}, \\quad\n", + "> \\underline{f} = \\begin{bmatrix}\n", + "> \\underline{f}_{u} \\\\\n", + "> \\underline{0} \\\\\n", + "> \\underline{0}\n", + "> \\end{bmatrix}.\n", + "> $$" ], "metadata": {} }, @@ -360,12 +361,13 @@ "$$\n", "which is the form the `AffineConstraint` constructor expects.\n", "\n", - "!!! note\n", - " If all nodes along the boundary are equidistant all the weights would be the same. In\n", - " this case we can construct the constraint without having to do any integration by\n", - " simply finding all degrees of freedom that are located along the boundary (and using 1\n", - " as the weight). This is what is done in the [deal.ii step-11\n", - " example](https://www.dealii.org/current/doxygen/deal.II/step_11.html)." + "> **Note**\n", + ">\n", + "> If all nodes along the boundary are equidistant all the weights would be the same. In\n", + "> this case we can construct the constraint without having to do any integration by\n", + "> simply finding all degrees of freedom that are located along the boundary (and using 1\n", + "> as the weight). This is what is done in the [deal.ii step-11\n", + "> example](https://www.dealii.org/current/doxygen/deal.II/step_11.html)." ], "metadata": {} }, @@ -600,7 +602,7 @@ " grid = setup_grid(h)\n", " # Interpolations\n", " ipu = Lagrange{RefTriangle, 2}()^2 # quadratic\n", - " ipp = Lagrange{RefTriangle, 1}() # linear\n", + " ipp = Lagrange{RefTriangle, 1}() # linear\n", " # Dofs\n", " dh = setup_dofs(grid, ipu, ipp)\n", " # FE values\n", diff --git a/previews/PR1096/tutorials/stokes-flow.jl b/previews/PR1096/tutorials/stokes-flow.jl index 0b4ea88420..79cc2b65be 100644 --- a/previews/PR1096/tutorials/stokes-flow.jl +++ b/previews/PR1096/tutorials/stokes-flow.jl @@ -197,7 +197,7 @@ function main() grid = setup_grid(h) # Interpolations ipu = Lagrange{RefTriangle, 2}()^2 # quadratic - ipp = Lagrange{RefTriangle, 1}() # linear + ipp = Lagrange{RefTriangle, 1}() # linear # Dofs dh = setup_dofs(grid, ipu, ipp) # FE values diff --git a/previews/PR1096/tutorials/stokes-flow/index.html b/previews/PR1096/tutorials/stokes-flow/index.html index bcf8d26b84..fea68de6cc 100644 --- a/previews/PR1096/tutorials/stokes-flow/index.html +++ b/previews/PR1096/tutorials/stokes-flow/index.html @@ -231,7 +231,7 @@ grid = setup_grid(h) # Interpolations ipu = Lagrange{RefTriangle, 2}()^2 # quadratic - ipp = Lagrange{RefTriangle, 1}() # linear + ipp = Lagrange{RefTriangle, 1}() # linear # Dofs dh = setup_dofs(grid, ipu, ipp) # FE values @@ -455,7 +455,7 @@ grid = setup_grid(h) # Interpolations ipu = Lagrange{RefTriangle, 2}()^2 # quadratic - ipp = Lagrange{RefTriangle, 1}() # linear + ipp = Lagrange{RefTriangle, 1}() # linear # Dofs dh = setup_dofs(grid, ipu, ipp) # FE values @@ -482,4 +482,4 @@ return end -main()

This page was generated using Literate.jl.

+main()

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/transient_heat_equation/index.html b/previews/PR1096/tutorials/transient_heat_equation/index.html index 540998fef7..3a0a9e0aff 100644 --- a/previews/PR1096/tutorials/transient_heat_equation/index.html +++ b/previews/PR1096/tutorials/transient_heat_equation/index.html @@ -233,4 +233,4 @@ uₙ .= u end -vtk_save(pvd);

This page was generated using Literate.jl.

+vtk_save(pvd);

This page was generated using Literate.jl.

diff --git a/previews/PR1096/tutorials/vortex-street.pvd b/previews/PR1096/tutorials/vortex-street.pvd index e4db267000..fc3f67bd8b 100755 --- a/previews/PR1096/tutorials/vortex-street.pvd +++ b/previews/PR1096/tutorials/vortex-street.pvd @@ -3,37 +3,37 @@ - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + +