Skip to content
Draft
Show file tree
Hide file tree
Changes from 12 commits
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
150 changes: 133 additions & 17 deletions docs/src/literate-tutorials/ns_vs_diffeq.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ nothing #hide
# discretization technique - in our case FEM. The mass matrix form of ODEs and DAEs
# is given as:
# ```math
# M(t) \mathrm{d}_t u = f(u,t)
# M(t) \mathrm{d}_t u = f(u,t),
# ```
# 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
Expand All @@ -41,8 +41,8 @@ nothing #hide
# The incompressible Navier-Stokes equations can be stated as the system
# ```math
# \begin{aligned}
# \partial_t v &= \underbrace{\nu \Delta v}_{\text{viscosity}} - \underbrace{(v \cdot \nabla) v}_{\text{advection}} - \underbrace{\nabla p}_{\text{pressure}} \\
# 0 &= \underbrace{\nabla \cdot v}_{\text{incompressibility}}
# \partial_t v &= \underbrace{\nu \Delta v}_{\text{viscosity}} - \underbrace{(v \cdot \nabla) v}_{\text{advection}} - \underbrace{\nabla p,}_{\text{pressure}} \\
# 0 &= \underbrace{\nabla \cdot v,}_{\text{incompressibility}}
# \end{aligned}
# ```
# where $v$ is the unknown velocity field, $p$ the unknown pressure field,
Expand All @@ -59,23 +59,23 @@ nothing #hide
# \begin{bmatrix}
# 4 v_{in}(t) y (0.41-y)/0.41^2 \\
# 0
# \end{bmatrix}
# \end{bmatrix},
# ```
# where $v_{in}(t) = \text{clamp}(t, 0.0, 1.5)$. With a dynamic viscosity of $\nu = 0.001$
# 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 = [0,0]^{\textrm{T}}$, while the right boundary has the do-nothing boundary condition
# $\nu \partial_{\textrm{n}} v - p n = 0$ to model outflow. With these boundary conditions we can choose the zero solution as a
# feasible initial condition.
#
# ### Derivation of semi-discrete weak form
# ### [Derivation of semi-discrete weak form](@id weak-form-derivation)
#
# 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
# ```math
# \begin{aligned}
# \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 \\
# 0 &= \int_\Omega (\nabla \cdot v) \psi
# \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, \\
# 0 &= \int_\Omega (\nabla \cdot v) \psi,
# \end{aligned}
# ```
# for all possible test functions from the suitable space.
Expand Down Expand Up @@ -105,13 +105,128 @@ nothing #hide
# \begin{bmatrix}
# N(\hat{v}, \hat{v}, \hat{\varphi}) \\
# 0
# \end{bmatrix}
# \end{bmatrix}.
# ```
# 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. $\hat{v}$ and $\hat{p}$ represent the time-dependent vectors of nodal values of the discretizations
# of $v$ and $p$ respectively, while $\hat{\varphi}$ 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.
#
# ### [Derivation of the Jacobian](@id jacobian-derivation)
# To enable the use of a wide range of solvers, it is more efficient to provide
# information to the solver about the [sparsity pattern and values of the Jacobian](https://docs.sciml.ai/DiffEqDocs/stable/tutorials/advanced_ode_example/#stiff)
# $J$ of $f(u, t) \equiv f(v, p, t)$ where $f(u, t)$ is just the RHS of the
# mass matrix form given by $f(u, t) = Ku + N(u)$. By [definition](https://nl.mathworks.com/help/matlab/ref/odeset.html#d126e1203285),
# ```math
# J = \frac{\partial f}{\partial u} = \frac{\partial}{\partial u}(K u + N(u)) = K + \frac{\partial N}{\partial u}.
# ```
# It is simple to see that the Jacobian and the discretized Stokes operator $K$
# share the same sparsity pattern since they share the same relation between
# trial and test functions. This implies that once $K$ is assembled, its
Comment thread
jfdev001 marked this conversation as resolved.
Outdated
# sparsity values and pattern can be used to initialize the Jacobian.
#
# To define $\frac{\partial N}{\partial u}$, we take the directional derivative
Comment thread
jfdev001 marked this conversation as resolved.
Outdated
# of $f$ along the vector $(\delta v, \delta p)$ and
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 think we are mixing the derivative at the continuous weak form with the derivative of the discrete problem.

# define finite element approximations of $\delta v$ and $\delta p$ with trial
# functions $\varphi$ and $\psi$, respectively. The discrete form of
# $\frac{\delta N}{\delta u}$ is therefore given by
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.

Suggested change
# $\frac{\delta N}{\delta u}$ is therefore given by
# $\frac{\partial N}{\partial u}$ is therefore given by

I also think I see where the confusion comes from. We usually use $u$ for the continuous problem and also for the discrete $u$ to show that they are structurally the same thing (and to not do a mess with the notation). However, in this tutorial we actually use $\hat{u}$ to make sure we distinguish these unambiguously. Can you fix this and make sure that this section is just using the discrete stuff?

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.

Working on this now. Need to make more adjustments to 54fbfbd since it's still off, please ignore it since I need to rethink it.

Copy link
Copy Markdown
Contributor Author

@jfdev001 jfdev001 Apr 16, 2025

Choose a reason for hiding this comment

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

So to be clear with the notation, I am assuming the following:

  1. The test functions $\varphi$ and $\psi$ for $v$ and $p$, respectively, are $\varphi \in H^{1}_{0}$ and $\psi \in L^2(\Omega)$ where
$$H^1_0 = \{ w \in [H^1 (\Omega)]^d | w = 0\ \text{on}\ \partial \Omega_D \},$$

where $d=2$ since we have a 2D domain.
2. The finite element approximation of $v$ is $v_h = \sum_i^n \hat{\varphi}_i \hat{v}_i$ and the finite element approximation of $p$ is $p_h = \sum_i^n \hat{\psi}_i \hat{p}_i$. Then $\hat{v} = [ \hat{v}_1, \hat{v}_2, ..., \hat{v}_i, ..., \hat{v}_n]^{\text{T}}$ and $\hat{p} = [ \hat{p}_1, \hat{p}_2, ..., \hat{p}_i, ..., \hat{p}_n]^{\text{T}}$. Using notation from Ferrite.jl: Introduction to FEM -- Finite Element Approximation. Similarly, the finite element approximations for $\delta v$ and $\delta p$ would be $\delta v_h = \sum_i^n \hat{\varphi}_i \delta \hat{v}_i$ and $\delta p_h = \sum_i^n \hat{\psi}_i \delta \hat{p}_i$ used in the directional derivative.
3. We restrict the test functions to the finite set of basis functions $\hat{\varphi}_j \in V^h$ and $\hat{\psi}_j \in Q^h$ where $V^h \subset H^1_0$ and $Q^h \subset L^2(\Omega)$. Standard notation, see also ch. 3.3.4 and ch. 2.7 in Whiteley2017.

This implies to me that the only place I need to use the hat notation (e.g., $\hat{v}$, $\hat{u}$, etc.) would be when I show the finite element approximation. So, from 2. and 3., the right hand side of

$$\frac{\partial N}{\partial \hat{u}} = - \sum_{i}^{n} \left(\int_{\Omega} ((\hat{\varphi}_i \cdot \nabla) \hat{v} + (\hat{v} \cdot \nabla) \hat{\varphi}_i) \cdot \hat{\varphi}_j \cdot \delta \hat{v}_i\right),$$

would be correct, yes? I know I still need to resolve the dimensionality comment #1138 (comment)!

The use of $v$, $p$, $\psi$, and $\varphi$ (i.e., the continuous forms) in the directional derivative derivation (in the long begin{aligned} end{aligned} section) should therefore be okay, right? I base my use of the continuous forms in the derivation on the deal.ii step 57 tutorial.

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.

the right hand side of ... would be correct, yes?

No. That expression does not even make sense. You have matrix with (N+Mx(N+M) entries = vector with N entries. If you really want to learn how to do this stuff, then I can just highly recommend to take some time, a pen and some paper and do the derivation step by step yourself without peeking into the tutorial.

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.

You're completely right. I am doing some more reading and re-writing. Thank you for the feedback, I am on vacation now and will send some corrections soon as a comment (not as a commit) :))

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.

Sorry, took me really forever to come back to this one.

Indeed. We typically use the continuous form in the analysis of the equations and you can find often that also the weak form is linearized directly (in contrast to linearizing the discretized form). IIRC both approaches should give you the same result for consistency reasons. I usually prefer the discretized form to be used directly, as it is easier to digest for people outside formal training in math, as we only have to deal with the "simple" finite dimensional case. I am fine also with the explanation you give tho. It comes down to a matter of "taste" anyway.

Under these conditions, the Gateaux derivative is the continuous linear operator represented by the Jacobian matrix---this definition is on pages 57-58 from The Differentiation and Integration of Nonlinear Operators in Nonlinear Functional Analysis and Applications (Rall 1971).

Not quite. In your reference Tapia states this for the finite dimensional (i.e. discrete) case, not for the infinite-dimensional (i.e. continuous) case.

[...] common to simply use autodiff [...]

We could probably just use autodiff here, this is correct. I do not know if the performance will be on par tho and if the resulting system will be correctly constrained (Dirichlet boundary conditions). Have you by any chance tried if it works right now?

Copy link
Copy Markdown
Contributor Author

@jfdev001 jfdev001 Dec 5, 2025

Choose a reason for hiding this comment

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

No problem! Thanks for the feedback! I did not try with autodiff. I'll come back to this PR during the holidays (after 2025-12-23), had to step away for a while, thanks for your patience.

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.

If you have written something somewhere here, I have gotten some notificaiton on Github, but I think the comment got gobbled up by some Github outage.

Copy link
Copy Markdown
Contributor Author

@jfdev001 jfdev001 Jan 19, 2026

Choose a reason for hiding this comment

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

Hi @termi-official, I've been quite caught up with work/application season right now, so will have to step away from Ferrite longer than I anticipated. I am not sure when I would be able to return to this PR. I can either leave it as a draft, or close it. Lmk what you think.

Unfortunately, same thing applies to the #1139. I would recommend unassigning me from that one so that other contributors might be able to tackle it if they want.

I appreciate all your assistance with this PR up until this point! I will try to return to it at some point, but don't know a timeline yet.

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 can fully understand. And no worries, I won't run away in the meantime. :)

#
# ```math
# - \sum_{i}^{N} (\int_{\Omega} ((\varphi_i \cdot \nabla) v + (v \cdot \nabla) \varphi_i) \cdot \varphi_j) \delta \hat{v}_i,
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 think we should specify this further, as this expression is not correct in its dimension. The left side is dim(u) and the right side is dim(v). Also the continuous and discrete variables are mixed.

Suggested change
# - \sum_{i}^{N} (\int_{\Omega} ((\varphi_i \cdot \nabla) v + (v \cdot \nabla) \varphi_i) \cdot \varphi_j) \delta \hat{v}_i,
# \frac{\partial N}{\partial u} = - \sum_{i}^{N} (\int_{\Omega} ((\varphi_i \cdot \nabla) v + (v \cdot \nabla) \varphi_i) \cdot \varphi_j) \cdot \delta \hat{v}_i,

# ```
#
# on a mesh with characteristic element size $h$ and $N$ trial functions.
Comment thread
jfdev001 marked this conversation as resolved.
Outdated
#
# With the Jacobian accounting for both the linear and nonlinear contributions
# of the semi-discrete weak form, we can easily use
# [ODE solvers from DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/).
#
# !!! details "Extra details on the Jacobian"
# Since the [directional derivative](https://en.wikipedia.org/wiki/Directional_derivative)
# is [equivalent to the Jacobian](https://math.stackexchange.com/questions/3191003/directional-derivative-and-jacobian-matrix),
# we take the directional derivative of $f(u, t) \equiv f(v, p, t)$ along
# $\delta u \equiv (\delta v, \delta p)$ (similar to [deal.ii: step 57](https://www.dealii.org/current/doxygen/deal.II/step_57.html))
# to explicitly determine the sparsity pattern
# and values for the Jacobian. Note that $f$ is a vector function with
# components $f_1$ and $f_2$ corresponding to
# the RHS of the first and second equations, respectively, of the
# weak form defined in the previous [section](@ref weak-form-derivation).
# By definition, and omitting $t$, the directional derivative is given by
#
# ```math
# \begin{aligned}
# J &= \nabla f(v, p) \cdot (\delta v, \delta p) \\
# &= \lim_{\epsilon \to 0 } \frac{1}{\epsilon}(f(v + \epsilon \delta v, p + \epsilon \delta p ) - f(v, p)) \\
# &= \lim_{\epsilon \to 0} \frac{1}{\epsilon} \left( \begin{bmatrix}
# - \int_\Omega \nu \nabla (v + \epsilon \delta v) : \nabla \varphi - \int_\Omega ((v + \epsilon \delta v) \cdot \nabla) (v + \epsilon \delta v) \cdot \varphi + \int_\Omega (p + \epsilon \delta p)(\nabla \cdot \varphi) \\
# \int_{\Omega} (\nabla \cdot (v + \epsilon \delta v)) \psi
# \end{bmatrix} - \begin{bmatrix}
# f_1(v,p) \\
# f_2(v,p)
# \end{bmatrix} \right) \\
# &= \lim_{\epsilon \to 0} \frac{1}{\epsilon} \left( \begin{bmatrix}
# - \int_{\Omega} \cancel{\nu \nabla v : \nabla \varphi} + \nu \epsilon \nabla \delta v : \nabla \varphi - \int_{\Omega} (\cancel{(v \cdot \nabla) v} + (\epsilon \delta v \cdot \nabla) v + (v \cdot \nabla) \epsilon \delta v + (\epsilon \delta v \cdot \nabla) \epsilon \delta v) \cdot \varphi + \int_{\Omega} \cancel{p (\nabla \cdot \varphi)} + \epsilon \delta p (\nabla \cdot \varphi) \\
# \int_{\Omega} \cancel{(\nabla \cdot v)\psi} + (\nabla \cdot \epsilon \delta v)\psi
# \end{bmatrix} - \begin{bmatrix}
# \cancel{f_1(v,p)} \\
# \cancel{f_2(v,p)}
# \end{bmatrix} \right)\\
# &= \lim_{\epsilon \to 0} \frac{1}{\cancel{\epsilon}} \begin{bmatrix}
# - \int_{\Omega} \nu \cancel{\epsilon} \nabla \delta v : \nabla \varphi - \int_{\Omega} ((\cancel{\epsilon} \delta v \cdot \nabla) v + (v \cdot \nabla) \cancel{\epsilon} \delta v + \cancel{\epsilon^2 (\delta v \cdot \nabla) \delta v}) \cdot \varphi + \int_{\Omega} \cancel{\epsilon} \delta p (\nabla \cdot \varphi) \\
# \int_{\Omega} (\nabla \cdot \cancel{\epsilon} \delta v) \psi
# \end{bmatrix} \\
# &= \begin{bmatrix}
# - \int_{\Omega} \nu \nabla \delta v : \nabla \varphi - \int_{\Omega} ((\delta v \cdot \nabla) v + (v \cdot \nabla) \delta v) \cdot \varphi + \int_{\Omega} \delta p (\nabla \cdot \varphi) \\
# \int_{\Omega} (\nabla \cdot \delta v) \psi
# \end{bmatrix}.
# \end{aligned}
# ```
#
# The term in the Jacobian
#
# ```math
# - \int_{\Omega} ((\delta v \cdot \nabla) v + (v \cdot \nabla) \delta v) \cdot \varphi,
# ```
#
# corresponding to the nonlinear advection term is of particular interest since
# this integrand contains terms that evolve over time and thus these terms
# cannot be formulated as constant coefficients.
# To make this explicit, we construct a finite element approximation of
# $\delta v$ and $\delta p$ on a mesh with characteristic element size $h$
# and $N$ trial functions $\varphi$. An important note here is
# that the trial functions are the same as in the previous [section](@ref weak-form-derivation). The finite element approximation for $\delta v$ is thus given by
#
# ```math
# \delta v_h(\mathbf{x}) = \sum_{i}^{N} \varphi_i(\mathbf{x}) \delta \hat{v}_i,
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 think we never used $v_h$ in this tutorial?

Copy link
Copy Markdown
Contributor Author

@jfdev001 jfdev001 Apr 14, 2025

Choose a reason for hiding this comment

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

Right! $v_h$ is never defined; however, $\delta v_h$ is the finite element approximation for the direction in which the derivative of $f$ is computed. If we do not explicitly define $\delta v_h$, then its substitution in ns_vs_diffeq.jl:207 to get

$$- \sum_{i}^{n} \left(\int_{\Omega} ((\hat{\varphi}_i \cdot \nabla) \hat{v} + (\hat{v} \cdot \nabla) \hat{\varphi}_i) \cdot \hat{\varphi}_j \cdot \delta \hat{v}_i\right),$$

may be less clear. It is therefore not necessary to define $v_h$, right?

# ```
# for a specific point $\mathbf{x}$ and nodal trial functions $\varphi_i$
# with unknown nodal values $\delta \hat{v}_i$. Substituting $\delta v_h$
# into the corresponding nonlinear advection term above, we have
#
# ```math
# - \sum_{i}^{N} (\int_{\Omega} ((\varphi_i \cdot \nabla) v + (v \cdot \nabla) \varphi_i) \cdot \varphi_j) \delta \hat{v}_i,
# ```
# and we implement a function for the terms in this integrand.
# With this function for the nonlinear term and $K$, we can fully describe the
# Jacobian in the manner required
# by the [ODEFunction](https://docs.sciml.ai/DiffEqDocs/stable/types/ode_types/#SciMLBase.ODEFunction) used by the solver.
#
# Note that the operation
#
# ```math
# g(\xi, \theta) := (\xi \cdot \nabla) \theta
# ```
#
# for vectors $\xi$ and $\theta$ can be implemented as either
#
# ```math
# g(\xi, \theta) := \begin{cases}
# \xi \cdot (\nabla \theta)^{\text{T}},\ \text{or} \\
# \nabla \theta \cdot \xi.
# \end{cases}
# ```
#
# We use the first of these two definitions in the implementation.
#
# ## Commented implementation
#
Expand Down Expand Up @@ -363,23 +478,24 @@ K = assemble_stokes_matrix(cellvalues_v, cellvalues_p, ν, K, dh);
u₀ = zeros(ndofs(dh))
apply!(u₀, ch);

# 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.
# When using ODE solvers from DifferentialEquations for stiff problems, the
# solvers assume the Jacobian is a dense matrix by default. This is not
# feasible for the semi-discretization of finite element models, and it is much
# more efficient to provide the sparsity pattern and construction of the Jacobian.
# From the derivation of the jacobian [section](@ref jacobian-derivation),
# we communicate that a sparse matrix with a specified pattern should be utilized
# through the `jac_prototype` argument.
jac_sparsity = sparse(K);

# 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.
# over the dof handler and cell values to the RHS as a parameter.
# Furthermore the pre-assembled linear part, our Stokes operator (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
# current solution `u`, apply the Dirichlet BCs on the copy, and 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
# 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
Expand Down
6 changes: 3 additions & 3 deletions docs/src/tutorials/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ for the time-integration.

---

#### [Tutorial 10: Reactive surface](@ref tutorial-reactive-surface)
#### [Tutorial 11: Reactive surface](@ref tutorial-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
Expand All @@ -155,7 +155,7 @@ solved by using the usual first order reaction diffusion operator splitting.

---

#### [Tutorial 11: Linear shell](@ref tutorial-linear-shell)
#### [Tutorial 12: Linear shell](@ref tutorial-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
Expand All @@ -166,7 +166,7 @@ Ferrite.

---

#### [Tutorial 12: Discontinuous Galerkin heat equation](@ref tutorial-dg-heat-equation)
#### [Tutorial 13: Discontinuous Galerkin heat equation](@ref tutorial-dg-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
Expand Down