|
| 1 | +# ## Introduction |
| 2 | + |
| 3 | +# In this tutorial we will learn how to use [`GridapODEs.jl`](https://github.com/gridap/GridapODEs.jl) to solve the transient PDEs by solving the *heat equation*, equivalent to the transient Poisson equation. |
| 4 | + |
| 5 | +# We will focus on the time discretization on the equations, assuming that the reader is familiar with the spatial Finite Element discretization given in [tutorial 1](https://gridap.github.io/Tutorials/stable/pages/t001_poisson/). |
| 6 | + |
| 7 | +# ## Problem statement |
| 8 | + |
| 9 | +# We solve the heat equation in a 2-dimensional domain defined by a square with Homogenous Dirichlet boundaries, $\Gamma_D$. We consider a time-dependent conductivity $\kappa(t)=1.0 + 0.95\sin(2\pi t)$, a time-dependent volumetric forcing term $f(t) = \sin(\pi t)$ and a constant Homogenous boundary condition $g = 0.0$. The initial solution is $u(x,0) = u_0 = 0$. With these definitions, the strong form of the problem reads: |
| 10 | + |
| 11 | +# ```math |
| 12 | +# \left\lbrace |
| 13 | +# \begin{aligned} |
| 14 | +# \frac{\partial u(t)}{\partial t} -\kappa(t)\Delta u(t) = f(t) \ &\text{in} \ \Omega,\\ |
| 15 | +# u(t) = 0 \ &\text{on}\ \Gamma_{\rm D},\\ |
| 16 | +# u(0) = 0 \ &\text{in}\ \Omega\\ |
| 17 | +# \end{aligned} |
| 18 | +# \right. |
| 19 | +# ``` |
| 20 | + |
| 21 | +# The weak form of the problem will read: find $u(t)\in U_g(t)$ such that |
| 22 | + |
| 23 | +# ```math |
| 24 | +# m(t,u,v) + a(t,u,v) = b(t,v)\quad \forall v\in \ V |
| 25 | +# ``` |
| 26 | + |
| 27 | +# Note that $U_g(t)$ is a transient FE space, in the sense that Dirichlet boundary value of functions in $U_g$ can change in time. The definition of $m(u,v)$, $a(u,v)$ and $b(v)$ is as follows: |
| 28 | + |
| 29 | +# ```math |
| 30 | +# \begin{aligned} |
| 31 | +# m(t,u,v) = \int_\Omega v\frac{\partial u}{\partial t} d\Omega, \\ |
| 32 | +# a(t,u,v) = \int_\Omega \kappa(t) \nalba v\cdot \nabla u d\Omega, \\ |
| 33 | +# b(t,v) = \int_\Omega v\ f(t) d\Omega |
| 34 | +# \end{aligned} |
| 35 | +# ``` |
| 36 | + |
| 37 | +# ## Discrete model and Triangulation |
| 38 | + |
| 39 | +# Let us load the two packages that we will use in this tutorial `Gridap` and `GridapODEs`. |
| 40 | +using Gridap |
| 41 | +using GridapODEs |
| 42 | +using GridapODEs.ODETools |
| 43 | +using GridapODEs.TransientFETools |
| 44 | + |
| 45 | +# Without going into the details we define the `DiscreteModel` and the `Triangulation`, as it is detailed in [tutorial 2](https://gridap.github.io/Tutorials/stable/pages/t002_validation/). |
| 46 | + |
| 47 | + |
| 48 | +𝒯 = CartesianDiscreteModel((0,1,0,1),(20,20)) |
| 49 | +Ω = Interior(𝒯) |
| 50 | +dΩ = Measure(Ω,2) |
| 51 | + |
| 52 | +# ## FE space |
| 53 | + |
| 54 | +# In this tutorial we will use linear Lagrangian Finite Elements. |
| 55 | +refFE = ReferenceFE(lagrangian,Float64,1) |
| 56 | + |
| 57 | +# The space of test functions is constant in time and is defined in steady problems: |
| 58 | +V = TestFESpace(𝒯,refFE,dirichlet_tags="boundary") |
| 59 | + |
| 60 | +# The trial space is now a `TransientTrialFESpace`, wich is constructed from a `TestFESpace` and a function (or vector of functions) for the Dirichlet boundary condition/s. In that case, the boundary condition function is a time-independent constant, but it could also be a time-dependent field depending on the coordinates $x$ and time $t$. |
| 61 | +g(x,t::Real) = 0.0 |
| 62 | +g(t::Real) = x -> g(x,t) |
| 63 | +U = TransientTrialFESpace(V,g) |
| 64 | + |
| 65 | +# ## Weak form |
| 66 | + |
| 67 | +# The weak form of the problem follows the same structure as other `Gridap` tutorials, where we define the bilinear and linear forms to define the `FEOperator`. In this case we need to deal with time-dependent quantities and with the presence of time derivatives. The former is handled by passing the time, $t$, as an additional argument to the form, i.e. $a(t,u,v)$. The later is defined using the time derivative operator `∂t`. |
| 68 | + |
| 69 | +# The most general way of constructing a transient FE operator is by using the `TransientFEOperator` function, which receives a residual, a jacobian with respect to the unknown and a jacobian with respect to the time derivative. |
| 70 | +κ(t) = 1.0 + 0.95*sin(2π*t) |
| 71 | +f(t) = sin(π*t) |
| 72 | +res(t,u,v) = ∫( ∂t(u)*v + κ(t)*(∇(u)⋅∇(v)) - f(t)*v )dΩ |
| 73 | +jac(t,u,du,v) = ∫( κ(t)*(∇(du)⋅∇(v)) )dΩ |
| 74 | +jac_t(t,u,duₜ,v) = ∫( duₜ*v )dΩ |
| 75 | +op = TransientFEOperator(res,jac,jac_t,U,V) |
| 76 | + |
| 77 | +# We can also take advantage of automatic differentitation techniques and use the `TransientFEOperator` function sending only the residual. |
| 78 | +op_AD = TransientFEOperator(res,U,V) |
| 79 | + |
| 80 | +# Alternatively, we can exploit the fact that the problem is linear and use the transient Affine FE operator signature `TransientAffineFEOperator`. In that case, we send a form for the mass contribution, $m$, a form for the stiffness contribution, $a$, and the forcing term, $b$. |
| 81 | +m(t,u,v) = ∫( u*v )dΩ |
| 82 | +a(t,u,v) = ∫( κ(t)*(∇(u)⋅∇(v)) )dΩ |
| 83 | +b(t,v) = ∫( f(t)*v )dΩ |
| 84 | +op_Af = TransientAffineFEOperator(m,a,b,U,V) |
| 85 | + |
| 86 | +# ### Alternative FE operator definitions |
| 87 | + |
| 88 | +# For time-dependent problems with constant coefficients, which is not the case of this tutorial, one could use the optimized operator `TransientConstantMatrixFEOperator`, which assumes that the matrix contributions ($m$ and $a$) are time-independent. That is: |
| 89 | +m₀(u,v) = ∫( u*v )dΩ |
| 90 | +a₀(u,v) = ∫( κ(0.0)*(∇(u)⋅∇(v)) )dΩ |
| 91 | +op_CM = TransientConstantMatrixFEOperator(m,a,b,U,V) |
| 92 | + |
| 93 | +# Going further, if we had a problem with constant forcing term, i.e. constant force and constant boundary conditions, we could have used the `TransientConstantFEOperator`. In that case the linear form is also time-independent. |
| 94 | +b₀(v) = ∫( f(0.0)*v )dΩ |
| 95 | +op_C = TransientConstantFEOperator(m,a,b,U,V) |
| 96 | + |
| 97 | +# ## Transient solver |
| 98 | + |
| 99 | +# Once we have the FE operator defined, we proceed with the definition of the transient solver. First, we define a linear solver to be used at each time step. Here we use the `LUSolver`, but other choices could be made. |
| 100 | +linear_solver = LUSolver() |
| 101 | + |
| 102 | +# Then, we define the ODE solver. That is, the scheme that will be used for the time integration. In this tutorial we use the `ThetaMethod` with $\theta = 0.5$, resulting in a 2nd order scheme. The `ThetaMethod` function receives the linear solver, the time step size $\Delta t$ (constant) and the value of $\theta $. |
| 103 | +Δt = 0.05 |
| 104 | +θ = 0.5 |
| 105 | +ode_solver = ThetaMethod(linear_solver,Δt,θ) |
| 106 | + |
| 107 | +# Finally, we define the solution using the `solve` function, giving the ODE solver, the FE operator, an initial solution, an initial time and a final time. To construct the initial condition we interpolate the initial value (in that case a constant value of 0.0) into the FE space $U(t)$ at $t=0.0$. |
| 108 | +u₀ = interpolate_everywhere(0.0,U(0.0)) |
| 109 | +t₀ = 0.0 |
| 110 | +T = 10.0 |
| 111 | +uₕₜ = solve(ode_solver,op,u₀,t₀,T) |
| 112 | + |
| 113 | +# ## Postprocessing |
| 114 | + |
| 115 | +# We should highlight that `uₕₜ` is just an iterable function and the results at each time steps are only computed when iterating over it. We can post-process the results and generate the corresponding `vtk` files using the `createpvd` and `createvtk` functions. The former will create a `.pvd` file with the collection of `.vtu` files saved at each time step by `createvtk`. This can be done as follows: |
| 116 | +createpvd("poisson_transient_solution") do pvd |
| 117 | + for (uₕ,t) in uₕₜ |
| 118 | + pvd[t] = createvtk(Ω,"poisson_transient_solution_$t"*".vtu",cellfields=["u"=>uₕ]) |
| 119 | + end |
| 120 | +end |
| 121 | + |
| 122 | +#  |
0 commit comments