Heat equation with two layers

module HeatEquationTwoLayers #hide
println("Running heat equation two layers example...") #hide
# # Heat equation
# # Theory
# This example shows how to solve the heat equation with eventually variable physical properties in steady and unsteady formulations:
# ```math
#   \rho C_p \partial_t u - \nabla . ( \lambda \nabla u) = f
# ```
# We shall assume that $$f, \, \rho, \, C_p, \, \lambda \, \in L^2(\Omega)$$. The weak form of the problem is given by: find $$ u \in \tilde{H}^1_0(\Omega)$$
# (there will be at least one Dirichlet boundary condition) such that:
# ```math
#   \forall v \in  \tilde{H}^1_0(\Omega), \, \, \, \underbrace{\int_\Omega \partial_t u . v dx}_{m(\partial_t u,v)} + \underbrace{\int_\Omega \nabla u . \nabla v dx}_{a(u,v)} = \underbrace{\int_\Omega f v dx}_{l(v)}
# ```
# To numerically solve this problem we seek an approximate solution using Lagrange $$P^1$$ or $$P^2$$ elements.
# Here we assume that the domain can be split into two domains having different material properties.

const dir = joinpath(@__DIR__, "..", "..", "..") # BcubeTutorials dir
using Bcube
using LinearAlgebra
using WriteVTK
using Test #src

function T_analytical_two_layers(x, λ1, λ2, T0, T1, L)
    if x < 0.5 * L
        T = 2.0 * (λ2 / (λ1 + λ2)) * ((T1 - T0) / L) * x + T0
    else
        T = 2.0 * (λ1 / (λ1 + λ2)) * ((T1 - T0) / L) * (x - L) + T1
    end
    return T
end

const outputpath = joinpath(dir, "myout", "heat_equation_two_layers")
mkpath(outputpath)

"""
Two layers case using MeshCellData that depends on the subdomain. A unique assembly is then performed over the whole mesh
(by opposition to method2)
"""
function run_steady_two_layers_method1(; degree)
    println("Running steady two layers case - method 1")
    T0 = 260.0
    T1 = 300.0
    λ1 = 150.0
    λ2 = 10.0

    # Read mesh
    mesh, el_names, el_names_inv, el_cells, glo2loc_cell_indices =
        read_msh_with_cell_names(joinpath(dir, "input", "mesh", "domainTwoLayer_tri.msh"))

    fs = FunctionSpace(:Lagrange, degree)
    U = TrialFESpace(fs, mesh, Dict("West" => T0, "East" => T1))
    V = TestFESpace(U)

    # Define measures for cell integration
    dΩ = Measure(CellDomain(mesh), 2 * degree + 1)

    material_1 = el_cells[el_names_inv["Domain_1"]]
    material_2 = el_cells[el_names_inv["Domain_2"]]

    λ = zeros(ncells(mesh))

    for i in material_1
        λ[glo2loc_cell_indices[i]] = λ1
    end
    for i in material_2
        λ[glo2loc_cell_indices[i]] = λ2
    end

    qtmp = zeros(ncells(mesh))

    q = MeshCellData(qtmp)
    η = MeshCellData(λ)

    # Compute matrices associated to bilinear and linear forms
    a(u, v) = ∫(η * ∇(u) ⋅ ∇(v))dΩ
    l(v) = ∫(q * v)dΩ

    system = AffineFESystem(a, l, U, V)
    ϕ = Bcube.solve(system)

    T_analytical = PhysicalFunction(x -> T_analytical_two_layers(x[1], λ1, λ2, T0, T1, 0.2))

    Tcn = var_on_centers(ϕ, mesh)
    Tca = var_on_centers(T_analytical, mesh)
    dict_vars =
        Dict("Temperature" => (Tcn, VTKCellData()), "Temperature_a" => (Tca, VTKCellData()))
    write_vtk(
        joinpath(outputpath, "result_steady_heat_equation_two_layers_method1"),
        0,
        0.0,
        mesh,
        dict_vars,
    )

    err = norm(Tca .- Tcn, Inf) / norm(Tca, Inf)
    @show err

    if get(ENV, "TestMode", "false") == "true" #src
        @test err < 2.1e-5                     #src
    end                                        #src
end

"""
Two layers case using subdomain integration.
"""
function run_steady_two_layers_method2(; degree)
    println("Running steady two layers case - method 2")
    T0 = 260.0
    T1 = 300.0
    λ1 = 150.0
    λ2 = 10.0

    # Read mesh
    mesh, el_names, el_names_inv, el_cells, glo2loc_cell_indices =
        read_msh_with_cell_names(joinpath(dir, "input", "mesh", "domainTwoLayer_tri.msh"))

    fs = FunctionSpace(:Lagrange, degree)
    U = TrialFESpace(fs, mesh, Dict("West" => T0, "East" => T1))
    V = TestFESpace(U)

    material_1 = el_cells[el_names_inv["Domain_1"]]
    material_2 = el_cells[el_names_inv["Domain_2"]]

    # Define measures for cell and interior face integrations
    ind_Ω1 = [glo2loc_cell_indices[i] for i in material_1]
    ind_Ω2 = [glo2loc_cell_indices[i] for i in material_2]

    dΩ = Measure(CellDomain(mesh), 2 * degree + 1)
    dΩ1 = Measure(CellDomain(mesh, ind_Ω1), 2 * degree + 1)
    dΩ2 = Measure(CellDomain(mesh, ind_Ω2), 2 * degree + 1)

    qtmp = zeros(ncells(mesh))

    q = MeshCellData(qtmp)

    # compute matrices associated to bilinear and linear forms
    a(u, v) = ∫(λ1 * ∇(u) ⋅ ∇(v))dΩ1 + ∫(λ2 * ∇(u) ⋅ ∇(v))dΩ2
    l(v) = ∫(q * v)dΩ

    system = AffineFESystem(a, l, U, V)
    ϕ = Bcube.solve(system)

    T_analytical = PhysicalFunction(x -> T_analytical_two_layers(x[1], λ1, λ2, T0, T1, 0.2))

    Tcn = var_on_centers(ϕ, mesh)
    Tca = var_on_centers(T_analytical, mesh)
    dict_vars =
        Dict("Temperature" => (Tcn, VTKCellData()), "Temperature_a" => (Tca, VTKCellData()))
    write_vtk(
        joinpath(outputpath, "result_steady_heat_equation_two_layers_method2"),
        0,
        0.0,
        mesh,
        dict_vars,
    )

    err = norm(Tca .- Tcn, Inf) / norm(Tca, Inf)
    @show err

    if get(ENV, "TestMode", "false") == "true" #src
        @test err < 2.1e-5                     #src
    end                                        #src
end

run_steady_two_layers_method1(; degree = 2)
run_steady_two_layers_method2(; degree = 2)

end #hide