Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions docs/src/literate-tutorials/heat_equation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
# where $\partial \Omega$ denotes the boundary of $\Omega$.
# The resulting weak form is given as follows: Find ``u \in \mathbb{U}`` such that
# ```math
# \int_{\Omega} \nabla \delta u \cdot \nabla u \ d\Omega = \int_{\Omega} \delta u \ d\Omega \quad \forall \delta u \in \mathbb{T},
# \int_{\Omega} \nabla \varphi \cdot \nabla u \ d\Omega = \int_{\Omega} \varphi \ d\Omega \quad \forall \varphi \in \mathbb{T},
# ```
# where $\delta u$ is a test function, and where $\mathbb{U}$ and $\mathbb{T}$ are suitable
# where $\varphi$ is a test function, and where $\mathbb{U}$ and $\mathbb{T}$ are suitable
# trial and test function sets, respectively.
#-
# ## Commented program
Expand Down Expand Up @@ -127,12 +127,12 @@ close!(ch)
# 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
# value and gradient of the test function, `ϕ` and also the gradient of the trial function
# `u`. We get all of these from `cellvalues`.
#
# !!! note "Notation"
# Comparing with the brief finite element introduction in [Introduction to FEM](@ref),
# the variables `δu`, `∇δu` and `∇u` are actually $\phi_i(\textbf{x}_q)$, $\nabla
# the variables `ϕ`, `∇ϕ` and `∇u` are actually $\phi_i(\textbf{x}_q)$, $\nabla
# \phi_i(\textbf{x}_q)$ and $\nabla \phi_j(\textbf{x}_q)$, i.e. the evaluation of the
# trial and test functions in the quadrature point ``\textbf{x}_q``. However, to
# underline the strong parallel between the weak form and the implementation, this
Expand All @@ -149,15 +149,15 @@ function assemble_element!(Ke::Matrix, fe::Vector, cellvalues::CellValues)
dΩ = getdetJdV(cellvalues, q_point)
## Loop over test shape functions
for i in 1:n_basefuncs
δu = shape_value(cellvalues, q_point, i)
δu = shape_gradient(cellvalues, q_point, i)
ϕ = shape_value(cellvalues, q_point, i)
ϕ = shape_gradient(cellvalues, q_point, i)
## Add contribution to fe
fe[i] += δu * dΩ
fe[i] += ϕ * dΩ
## Loop over trial shape functions
for j in 1:n_basefuncs
∇u = shape_gradient(cellvalues, q_point, j)
## Add contribution to Ke
Ke[i, j] += (∇δu ⋅ ∇u) * dΩ
Ke[i, j] += (∇ϕ ⋅ ∇u) * dΩ
end
end
end
Expand Down
88 changes: 31 additions & 57 deletions docs/src/literate-tutorials/transient_heat_equation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,29 @@
# ```
# The semidiscrete weak form is given by
# ```math
# \int_{\Omega}v \frac{\partial u}{\partial t} \ \mathrm{d}\Omega + \int_{\Omega} k \nabla v \cdot \nabla u \ \mathrm{d}\Omega = \int_{\Omega} f v \ \mathrm{d}\Omega,
# \int_{\Omega}\delta u \frac{\partial u}{\partial t} \ \mathrm{d}\Omega + \int_{\Omega} k \nabla \delta u \cdot \nabla u \ \mathrm{d}\Omega = \int_{\Omega} f \delta u \ \mathrm{d}\Omega,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I misunderstood you, but didn't you wanted to change these to be consistent with the heat example? Before changing these, let us wait for a quick feedback on https://github.com/Ferrite-FEM/Ferrite.jl/pull/1305/changes#r2934003577 .

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅right. I must have missed that part. Once we get feedback on which symbol would be better, I'll update the lot and fix the two broken reference links.

# ```
# where $v$ is a suitable test function. Now, we still need to discretize the time derivative. An implicit Euler scheme is applied,
# where $\delta u$ is a suitable test function. Now, we still need to discretize the time derivative. An implicit Euler scheme is applied,
# which yields:
# ```math
# \int_{\Omega} v\, u_{n+1}\ \mathrm{d}\Omega + \Delta t\int_{\Omega} k \nabla v \cdot \nabla u_{n+1} \ \mathrm{d}\Omega = \Delta t\int_{\Omega} f v \ \mathrm{d}\Omega + \int_{\Omega} v \, u_{n} \ \mathrm{d}\Omega.
# \int_{\Omega} \delta u\, u_{n+1}\ \mathrm{d}\Omega + \Delta t\int_{\Omega} k \nabla \delta u \cdot \nabla u_{n+1} \ \mathrm{d}\Omega = \Delta t\int_{\Omega} f \delta u \ \mathrm{d}\Omega + \int_{\Omega} \delta u \, u_{n} \ \mathrm{d}\Omega.
# ```
# If we assemble the discrete operators, we get the following algebraic system:
# ```math
# \mathbf{M} \mathbf{u}_{n+1} + Δt \mathbf{K} \mathbf{u}_{n+1} = Δt \mathbf{f} + \mathbf{M} \mathbf{u}_{n}
# ```
# Here, `M` refers to the so called mass matrix, which always occurs in time related terms, i.e.
# ```math
# M_{ij} = \int_{\Omega} \varphi_i \, u_j \ \mathrm{d}\Omega,
# ```
# where $u_j$ and $\varphi_i$ are the shape functions of the trial and test functions, respectively.
# In this example we apply the boundary conditions to the assembled discrete operators (mass matrix $\mathbf{M}$ and stiffnes matrix $\mathbf{K}$)
# 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 ($\mathbf{A} = Δt \mathbf{K} + \mathbf{M}$) 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.
# to a prescribed dof in the system matrix ($\mathbf{A} = Δt \mathbf{K} + \mathbf{M}$) 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. For more details
# on the derivation and discretisation, see [Introduction to FEM](@ref fe-intro).
#-
# ## Commented program
#
Expand All @@ -75,11 +82,6 @@ add!(dh, :u, ip)
close!(dh);

# 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.
# ```math
# M_{ij} = \int_{\Omega} v_i \, u_j \ \mathrm{d}\Omega,
# ```
# where $u_i$ and $v_j$ are trial and test functions, respectively.
K = allocate_matrix(dh);
M = allocate_matrix(dh);
# We also preallocate the right hand side
Expand Down Expand Up @@ -107,76 +109,48 @@ close!(ch)
update!(ch, 0.0);

# ### Assembling the linear system
# As in the heat equation example we define a `doassemble!` function that assembles the diffusion parts of the equation:
function doassemble_K!(K::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)

# As in the [heat equation example](@ref heat_equation.jl) we define a `doassemble!` function that assembles the
# diffusion and diffusive parts of the equation:
function doassemble!(K::SparseMatrixCSC, M::SparseMatrixCSC, f::Vector, cellvalues::CellValues, dh::DofHandler)
n_basefuncs = getnbasefunctions(cellvalues)
Ke = zeros(n_basefuncs, n_basefuncs)
Me = zeros(n_basefuncs, n_basefuncs)
fe = zeros(n_basefuncs)

#Initiate assembler for matrices and vector
assembler = start_assemble(K, f)

assembler_M = start_assemble(M)
#Iterate over cells
for cell in CellIterator(dh)

fill!(Ke, 0)
fill!(Me, 0)
fill!(fe, 0)

reinit!(cellvalues, cell)

#Assemble local contributions
for q_point in 1:getnquadpoints(cellvalues)
dΩ = getdetJdV(cellvalues, q_point)

for i in 1:n_basefuncs
v = shape_value(cellvalues, q_point, i)
v = shape_gradient(cellvalues, q_point, i)
fe[i] += 0.1 * v * dΩ
ϕ = shape_value(cellvalues, q_point, i)
ϕ = shape_gradient(cellvalues, q_point, i)
fe[i] += 0.1 * ϕ * dΩ
for j in 1:n_basefuncs
u = shape_value(cellvalues, q_point, j)
∇u = shape_gradient(cellvalues, q_point, j)
Ke[i, j] += 1.0e-3 * (∇v ⋅ ∇u) * dΩ
Ke[i, j] += 1.0e-3 * (∇ϕ ⋅ ∇u) * dΩ
Me[i, j] += (ϕ * u) * dΩ
end
end
end

#Update global matrices
assemble!(assembler, celldofs(cell), Ke, fe)
assemble!(assembler_M, celldofs(cell), Me)
end
return K, f
return K, M, f
end
#md nothing # hide
# In addition to the diffusive part, we also need a function that assembles the mass matrix `M`.
function doassemble_M!(M::SparseMatrixCSC, cellvalues::CellValues, dh::DofHandler)

n_basefuncs = getnbasefunctions(cellvalues)
Me = zeros(n_basefuncs, n_basefuncs)

assembler = start_assemble(M)

for cell in CellIterator(dh)

fill!(Me, 0)

reinit!(cellvalues, cell)

for q_point in 1:getnquadpoints(cellvalues)
dΩ = getdetJdV(cellvalues, q_point)

for i in 1:n_basefuncs
v = shape_value(cellvalues, q_point, i)
for j in 1:n_basefuncs
u = shape_value(cellvalues, q_point, j)
Me[i, j] += (v * u) * dΩ
end
end
end

assemble!(assembler, celldofs(cell), Me)
end
return M
end
#md nothing # hide
# ### Solution of the system
# We first assemble all parts in the prior allocated `SparseMatrixCSC`.
K, f = doassemble_K!(K, f, cellvalues, dh)
M = doassemble_M!(M, cellvalues, dh)
K, M, f = doassemble!(K, M, f, cellvalues, dh)
A = (Δt .* K) + M;
# 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
Expand Down
10 changes: 5 additions & 5 deletions docs/src/topics/boundary_conditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ for fc in FacetIterator(dh, getfacetset(grid, "right"))
for q_point in 1:getnquadpoints(fv)
dΓ = getdetJdV(fv, q_point)
for i in 1:getnbasefunctions(fv)
δu = shape_value(fv, q_point, i)
fe[i] += δu * qn * dΓ
ϕ = shape_value(fv, q_point, i)
fe[i] += ϕ * qn * dΓ
end
end
assemble!(f, celldofs(fc), fe)
Expand All @@ -149,7 +149,7 @@ through the local `fe` vector and then using `assemble!`):
# ...
dofs = celldofs(fc)
for i in 1:getnbasefunctions(fv)
f[dofs[i]] += δu * qn * dΓ
f[dofs[i]] += ϕ * qn * dΓ
end
```

Expand All @@ -164,8 +164,8 @@ for facet in 1:nfacets(cell)
for q_point in 1:getnquadpoints(facetvalues)
dΓ = getdetJdV(facetvalues, q_point)
for i in 1:getnbasefunctions(facetvalues)
δu = shape_value(facetvalues, q_point, i)
fe[i] += δu * qn * dΓ
ϕ = shape_value(facetvalues, q_point, i)
fe[i] += ϕ * qn * dΓ
end
end
end
Expand Down
12 changes: 6 additions & 6 deletions docs/src/topics/fe_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,16 @@ simplicity we will consider only constant conductivity $k$.
## Weak form

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
form. By multiplying the equation with an arbitrary *test function* $\varphi$, integrating
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely see the point made by the contributor here @KnutAM . Since we explain it here from the Bubnov-Galerkin perspective I see that the notation $\delta u$ can indeed be a bit confusing. It is definitely not wrong to use $\delta u$ in this context. Would you agree with changing the notation here?

I am just not sure about the choice of $\varphi$, because we mix different alphabets for closely related symbols. Despite $v$ being visually close to $u$, should we rather opt for $v$ to still highlight the closeness of these objects?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

δu can indeed be a bit confusing

Yes, but I also think that for most users it will make things easier if we are always consistent that test function for x is δx, even if mathematically the variation and test functions are different. Alternatively, we should have another consistent scheme (but that would require something like φᵤ or uₜ to have a good scheme for coupled problems). Using the subscripts quickly becomes confusing though, since we typically want to reserve these for indices (e.g. old value in time-dependent problems)

Specifically using φ is not good, since we used that for the shape functions in the intro to FEM (we use N in other places).

In the code, we are currently mixing using the notation for the test function and function itself with the test shape function and shape function. (I'm no fan of that but others are :D)

My preferred notation

For a field x,

  • Function x (obviously) ($x \approx N_i a_i$)
  • Test function δx (but highlight in the intro to FEM that we use this even though we argue from the "Bubnov-Galerkin perspective") ($\delta x \approx \delta N_i c_i$)
  • Use N in code for shape_value for the function interpolation
  • Use δN in code for shape_value for the test function interpolation
  • In code, add letter Nx / δNx in coupled problems (and $N^x$ / $\delta N^x$ in math notation)
  • For assembly, we can subscript these, e.g. ∇δNᵢ⋅D⋅∇Nⱼ or ∇δNuᵢ⋅D⋅∇Npⱼ

Of course, we can exchange N -> φ, but easier to type N and consistent with the internal implementation in FunctionValues.
\varphi and \phi also renders a bit differently depending, sometimes \varphi looks like \phi, so we shouldn't use both for different things.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And noting @fredrikekre's comment here: #1304 (comment) - I guess that would be ok if we note that we (ab)use the notation δx for the test function even though it is not necessarily a variation.

And regarding the transient heat problem specifically, I think this can be derived from a potential so it can actually be considered a variation, but this would fail for other cases such as partially saturated porous media.

I guess we could introduce a different notation instead of δ, e.g. 𝒯 (\scrT), but I'm guessing this would be more confusing for most users even if we are consistent?

over the domain and using partial integration we obtain the *weak form*. Now our problem
can be stated as:

Find $u \in \mathbb{U}$ s.t.

```math
\int_\Omega \nabla \delta u \cdot (k \nabla u) \, \mathrm{d}\Omega =
\int_{\Gamma_\mathrm{N}} \delta u \, q^\mathrm{p} \, \mathrm{d}\Gamma +
\int_\Omega \delta u \, f \, \mathrm{d}\Omega \quad \forall \, \delta u \in \mathbb{T}
\int_\Omega \nabla \varphi \cdot (k \nabla u) \, \mathrm{d}\Omega =
\int_{\Gamma_\mathrm{N}} \varphi \, q^\mathrm{p} \, \mathrm{d}\Gamma +
\int_\Omega \varphi \, f \, \mathrm{d}\Omega \quad \forall \, \varphi \in \mathbb{T}
```

where $\mathbb{U}, \mathbb{T}$ are suitable function spaces with sufficiently regular
Expand Down Expand Up @@ -87,7 +87,7 @@ in our domain $\Omega$ as:

```math
u_\mathrm{h}(\mathbf{x}) = \sum_{i=1}^{\mathrm{N}} \phi_i(\mathbf{x}) \, \hat{u}_i,\qquad
\delta u_\mathrm{h}(\mathbf{x}) = \sum_{i=1}^{\mathrm{N}} \phi_i(\mathbf{x}) \, \delta \hat{u}_i \, .
\varphi_\mathrm{h}(\mathbf{x}) = \sum_{i=1}^{\mathrm{N}} \phi_i(\mathbf{x}) \, \delta \hat{u}_i \, .
```

Since test and trial functions are usually chosen in such a way, that they build the basis of
Expand All @@ -101,7 +101,7 @@ We may now insert these approximations in the weak form, which results in
\int_{\Omega_\mathrm{h}} \phi_i \, f \, \mathrm{d}\Omega \right) \, .
```

Since this equation must hold for arbitrary $\delta u_\mathrm{h}$, the equation must especially
Since this equation must hold for arbitrary $\varphi_\mathrm{h}$, the equation must especially
hold for the specific choice that only one of the nodal values $\delta \hat{u}_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
Expand Down
Loading