diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..864494b --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,57 @@ +# Configuration file for code formatting +# see here for default options : https://github.com/domluna/JuliaFormatter.jl#formatting-options + +always_for_in = true +whitespace_typedefs = true +whitespace_ops_in_indices = true +remove_extra_newlines = true +short_to_long_function_def = true +long_to_short_function_def = false +# always_use_return = true +whitespace_in_kwargs = true +format_docstrings = false +conditional_to_if = true +trailing_comma = true +normalize_line_endings = "unix" +join_lines_based_on_source = false +separate_kwargs_with_semicolon = true +align_assignment = true +align_struct_field = true +align_conditional = true +# align_pair_arrow = true +# align_matrix = true +# format_markdown = true + + +# possible style : sciml, blue, yas + +# ### SciML options +# whitespace_ops_in_indices = true +# remove_extra_newlines = true +# always_for_in = true +# whitespace_typedefs = true +# normalize_line_endings = "unix" + +# ### YAS options +# always_for_in = true +# whitespace_ops_in_indices = true +# remove_extra_newlines = true +# import_to_using = true +# pipe_to_function_call = true +# short_to_long_function_def = true +# always_use_return = true +# whitespace_in_kwargs = false +# join_lines_based_on_source = true +# separate_kwargs_with_semicolon = true + +# ### Blue option +# always_use_return = true +# short_to_long_function_def = true +# whitespace_ops_in_indices = true +# remove_extra_newlines = true +# always_for_in = true +# import_to_using = true +# pipe_to_function_call = true +# whitespace_in_kwargs = false +# annotate_untyped_fields_with_any = false +# separate_kwargs_with_semicolon = true \ No newline at end of file diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml new file mode 100644 index 0000000..c0faaf1 --- /dev/null +++ b/.github/workflows/Documentation.yml @@ -0,0 +1,27 @@ +name: Documentation + +on: + push: + branches: + - main + tags: "*" + pull_request: + types: [unlabeled, opened, synchronize, reopened] + +# Only trigger the job when `draft` label is not assigned to the PR +jobs: + build: + runs-on: ubuntu-latest + if: contains(github.event.pull_request.labels.*.name, 'draft') == false + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@latest + with: + version: "1.9" + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key + run: julia --project=docs/ docs/make.jl diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 0000000..e72d645 --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,13 @@ +name: TagBot +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +jobs: + TagBot: + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/format_check.yml b/.github/workflows/format_check.yml new file mode 100644 index 0000000..0eeb29b --- /dev/null +++ b/.github/workflows/format_check.yml @@ -0,0 +1,48 @@ +## from : +## https://github.com/julia-actions/julia-format/blob/a8502d9a6b40ef5da6e88721e253e79981aa26d1/workflows/format_check.yml + +name: format-check + +on: + push: + branches: + - "main" + - "release-" + tags: "*" + pull_request: + types: [unlabeled, opened, synchronize, reopened] + +# Only trigger the job when `draft` label is not assigned to the PR +jobs: + format: + runs-on: ${{ matrix.os }} + if: contains(github.event.pull_request.labels.*.name, 'draft') == false + strategy: + matrix: + julia-version: [1.9] + julia-arch: [x86] + os: [ubuntu-latest] + steps: + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.julia-version }} + + - uses: actions/checkout@v1 + - name: Install JuliaFormatter and format + # This will use the latest version by default but you can set the version like so: + # + # julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))' + run: | + julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter"))' + julia -e 'using JuliaFormatter; format(".", verbose=true)' + - name: Format check + run: | + julia -e ' + out = Cmd(`git diff --name-only`) |> read |> String + if out == "" + exit(0) + else + @error "Some files have not been formatted !!!" + write(stdout, out) + exit(1) + end' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59ec348 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/docs/build/ +/docs/site/ +/docs/Manifest.toml +/docs/src/example/ +/docs/src/tutorial/ +.vscode \ No newline at end of file diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..9e85be9 --- /dev/null +++ b/Project.toml @@ -0,0 +1,4 @@ +name = "BcubeTutorials" +uuid = "96b9ee7b-4a19-4ce3-a39f-9fbf54b5b072" +authors = ["Ghislain Blanchard, Lokman Bennani and Maxime Bouyges"] +version = "0.1.0" diff --git a/README.md b/README.md index fff1db5..b3ff8c5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # BcubeTutorials.jl -Tutorials and examples for the Bcube project + +[![](https://img.shields.io/badge/docs-release-blue.svg)](https://bcube-project.github.io/BcubeTutorials.jl) + +Documented tutorials and various examples for the [Bcube.jl](https://github.com/bcube-project/Bcube.jl) project. Browse the [online documentation](https://bcube-project.github.io/BcubeTutorials.jl). + +## Authors + +Ghislain Blanchard, Lokman Bennani and Maxime Bouyges. diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..5aaff0b --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,4 @@ +[deps] +BcubeTutorials = "96b9ee7b-4a19-4ce3-a39f-9fbf54b5b072" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000..e503bb8 --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,87 @@ +push!(LOAD_PATH, "../src/") + +using BcubeTutorials +using Documenter +using Literate + +# Alias for `Literate.markdown` +function gen_markdown(src, name, dir) + Literate.markdown(joinpath(src, name), dir; documenter = false, execute = false) +end + +""" +Build a markdown file with just the content of the julia file in it. +""" +function julia_to_markdown(src_dir, target_dir, filename, title) + open(joinpath(target_dir, split(filename, ".")[1] * ".md"), "w") do io + println(io, "# " * title) + println(io, "```julia") + f = open(joinpath(src_dir, filename), "r") + lines = readlines(f) + close(f) + map(line -> println(io, line), lines) + println(io, "```") + end +end + +# Generate tutorials +# `documenter = false` to avoid Documenter to execute cells +tutorial_names = + ["helmholtz", "heat_equation", "linear_transport", "phase_field_supercooled"] +tutorial_src = joinpath(@__DIR__, "..", "src", "tutorial") +tutorial_dir = joinpath(@__DIR__, "src", "tutorial") +Sys.rm(tutorial_dir; recursive = true, force = true) +map(filename -> gen_markdown(tutorial_src, "$(filename).jl", tutorial_dir), tutorial_names) + +# Generate "commented" examples +# `documenter = false` to avoid Documenter to execute cells +example_src = joinpath(@__DIR__, "..", "src", "example") +example_dir = joinpath(@__DIR__, "src", "example") +Sys.rm(example_dir; recursive = true, force = true) +mkdir(example_dir) +# gen_markdown(example_src, "euler_naca_steady.jl", example_dir) +# gen_markdown(example_src, "covo.jl", example_dir) +# gen_markdown(example_src, "linear_elasticity.jl", example_dir) + +# Generate "uncommented" examples +julia_to_markdown( + example_src, + example_dir, + "euler_naca_steady.jl", + "Euler equations on a NACA0012", +) +julia_to_markdown(example_src, example_dir, "covo.jl", "Euler equations - covo") +julia_to_markdown(example_src, example_dir, "linear_elasticity.jl", "Linear elasticity") +julia_to_markdown( + example_src, + example_dir, + "linear_thermoelasticity.jl", + "Linear thermo-elasticity", +) + +makedocs(; + modules = [BcubeTutorials], + authors = "Ghislain Blanchard, Lokman Bennani and Maxime Bouyges", + sitename = "BcubeTutorials", + clean = true, + doctest = false, + format = Documenter.HTML(; + prettyurls = get(ENV, "CI", "false") == "true", + canonical = "https://bcube-project.github.io/BcubeTutorials.jl", + assets = String[], + ), + checkdocs = :none, + pages = [ + "Home" => "index.md", + "Tutorials" => ["tutorial/$(filename).md" for filename in tutorial_names], + "Advanced examples" => Any[ + "example/covo.md", + "example/euler_naca_steady.md", + "example/linear_elasticity.md", + "example/linear_thermoelasticity.md", + ], + "How to..." => "howto/howto.md", + ], +) + +deploydocs(; repo = "github.com/bcube-project/BcubeTutorials.jl.git", push_preview = true) diff --git a/docs/src/assets/helmholtz_hybrid_mesh.png b/docs/src/assets/helmholtz_hybrid_mesh.png new file mode 100644 index 0000000..3daa356 Binary files /dev/null and b/docs/src/assets/helmholtz_hybrid_mesh.png differ diff --git a/docs/src/assets/helmholtz_hybrid_vp5.png b/docs/src/assets/helmholtz_hybrid_vp5.png new file mode 100644 index 0000000..214e71e Binary files /dev/null and b/docs/src/assets/helmholtz_hybrid_vp5.png differ diff --git a/docs/src/assets/helmholtz_x21_y21_vp6.png b/docs/src/assets/helmholtz_x21_y21_vp6.png new file mode 100644 index 0000000..8fd614b Binary files /dev/null and b/docs/src/assets/helmholtz_x21_y21_vp6.png differ diff --git a/docs/src/assets/linear_transport.gif b/docs/src/assets/linear_transport.gif new file mode 100644 index 0000000..ec7e79e Binary files /dev/null and b/docs/src/assets/linear_transport.gif differ diff --git a/docs/src/assets/logo.jpg b/docs/src/assets/logo.jpg new file mode 100644 index 0000000..31f4dd7 Binary files /dev/null and b/docs/src/assets/logo.jpg differ diff --git a/docs/src/assets/phase-field-supercooled-rectangle.gif b/docs/src/assets/phase-field-supercooled-rectangle.gif new file mode 100644 index 0000000..dce538f Binary files /dev/null and b/docs/src/assets/phase-field-supercooled-rectangle.gif differ diff --git a/docs/src/assets/thermo_elasticity.gif b/docs/src/assets/thermo_elasticity.gif new file mode 100644 index 0000000..7372efd Binary files /dev/null and b/docs/src/assets/thermo_elasticity.gif differ diff --git a/docs/src/howto/howto.md b/docs/src/howto/howto.md new file mode 100644 index 0000000..f628d85 --- /dev/null +++ b/docs/src/howto/howto.md @@ -0,0 +1,84 @@ +# How to + +To be completed to answer common user questions. + +## Comparing manually the benchmarks with `main` + +Let's say you want to compare the performance of your current branch (named "target" hereafter) with the `main` branch (named "baseline" hereafter). + +Open from `Bcube.jl/` a REPL and type: + +```julia +pkg> activate --temp +pkg> add PkgBenchmark BenchmarkTools +pkg> dev . +using PkgBenchmark +import Bcube +benchmarkpkg(Bcube, BenchmarkConfig(; env = Dict("JULIA_NUM_THREADS" => "1")); resultfile = joinpath(@__DIR__, "result-target.json")) +``` + +This will create a `result-target.json` in the current directory. + +Then checkout the `main` branch. Start a fresh REPL and type (almost the same): + +```julia +pkg> activate --temp +pkg> add PkgBenchmark BenchmarkTools +pkg> dev . +using PkgBenchmark +import Bcube +benchmarkpkg(Bcube, BenchmarkConfig(; env = Dict("JULIA_NUM_THREADS" => "1")); resultfile = joinpath(@__DIR__, "result-baseline.json")) +``` + +This will create a `result-baseline.json` in the current directory. + +You can now "compare" the two files by running (watch-out for the order): + +```julia +target = PkgBenchmark.readresults("result-target.json") +baseline = PkgBenchmark.readresults("result-baseline.json") +judgement = judge(target, baseline) +export_markdown("judgement.md", judgement) +``` + +This will create the markdown file `judgement.md` with the results. + +For more details, once you've built the `judgement` object, you can also type the following code from `https://github.com/tkf/BenchmarkCI.jl`: + +```julia +open("detailed-judgement.md", "w") do io + println(io, "# Judge result") + export_markdown(io, judgement) + println(io) + println(io) + println(io, "---") + println(io, "# Target result") + export_markdown(io, PkgBenchmark.target_result(judgement)) + println(io) + println(io) + println(io, "---") + println(io, "# Baseline result") + export_markdown(io, PkgBenchmark.baseline_result(judgement)) + println(io) + println(io) + println(io, "---") +end +``` + +## Run the benchmark manually + +Let's say you want to run the benchmarks locally (without comparing with `main`) + +Open from `Bcube.jl/` a REPL and type: + +```julia +pkg> activate --temp +pkg> add PkgBenchmark +pkg> dev . +using PkgBenchmark +import Bcube +results = benchmarkpkg(Bcube, BenchmarkConfig(; env = Dict("JULIA_NUM_THREADS" => "1")); resultfile = joinpath(@__DIR__, "result.json")) +export_markdown("results.md", results) +``` + +This will create the markdown file `results.md` with the results. diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 0000000..629afba --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,26 @@ +```@meta +CurrentModule = BcubeTutorials +``` + +# Bcube + +## Purpose of Bcube + +Bcube is a Julia library providing tools for the spatial discretization of partial differential equation(s) (PDE). The main objectives are: + +- to provide a set of tools to quickly assemble an algorithm solving partial differential equation(s) (so the main objective is to help building prototypes without thinking about the numerical core) +- to be completed : efficient/performant PDE resolution? + +This documentation is organised as follow. Checkout the tutorials to see what Bcube is capable of and/or quickly learn how to use it. Then, some more elaborated examples are provided to demonstrate the library capabilities. The "Manual" part explains how the core is organized. Finally, the "API" section is the low level code documentation. + +## Writing documentation + +To write documentation for Bcube, Julia's guidelines should be followed : [https://docs.julialang.org/en/v1/manual/documentation/](https://docs.julialang.org/en/v1/manual/documentation/). Moreover, this project tries to apply the [SciML Style Guide](https://github.com/SciML/SciMLStyle). + +## Conventions + +This documentation follows the following notation or naming conventions: + +- coordinates inside a reference frame are noted $$\hat{x}, \hat{y}$$ or $$\xi, \eta$$ while coordinates in the physical frame are noted $$x,y$$ +- when talking about a mapping, $$F$$ or sometimes $$F_{rp}$$ designates the mapping from the reference element to the physical element. On the other side, $$F^{-1}$$ or sometimes $$F_{pr}$$ designates the physical element to the reference element mapping. +- "dof" means "degree of freedom" diff --git a/src/BcubeTutorials.jl b/src/BcubeTutorials.jl new file mode 100644 index 0000000..7d8c114 --- /dev/null +++ b/src/BcubeTutorials.jl @@ -0,0 +1,5 @@ +module BcubeTutorials + +# Package is just for auto-docs generation + +end diff --git a/src/example/Project.toml b/src/example/Project.toml new file mode 100644 index 0000000..e955925 --- /dev/null +++ b/src/example/Project.toml @@ -0,0 +1,9 @@ +[deps] +Bcube = "cf06320b-b7f3-4748-8003-81a6b6979792" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" +Gmsh = "705231aa-382f-11e9-3f0c-b7cb4346fdeb" +Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" +WriteVTK = "64499a7a-5c06-52f2-abe2-ccb03c286192" diff --git a/src/example/constrained_poisson.jl b/src/example/constrained_poisson.jl new file mode 100644 index 0000000..3b2255d --- /dev/null +++ b/src/example/constrained_poisson.jl @@ -0,0 +1,128 @@ +module constrained_poisson_API #hide +println("Running constrained poisson API example...") #hide + +# # Constrained Poisson equation (FE) +# In this example, a Poisson equation with Neumann boundary conditions is solved using a boundary integral constraint. +# +# # Theory +# Consider the following Poisson equation on the unit disk (noted $$\Omega$$ in this example, its boundary is noted $$\Gamma$$): +# ```math +# - \Delta u = f \, \, \forall x \in \Omega +# ``` +# ```math +# \frac{\partial u}{\partial n} = 0 \, \, \forall x \in \Gamma +# ``` +# Poisson's equation can be written in the form of a minimisation problem: +# ```math +# \min_{u} J(u) = \frac{1}{2} \int_{\Omega} \nabla u . \nabla u \, dV + \int_{\Omega} f u \, dV +# ``` +# The discrete version of this problem is: +# ```math +# \min_{X} J_d(X) = < \frac{1}{2} A X , X > - < L , X > +# ``` +# where $$A$$ is the stiffness matrix corresponding to the bilinear form $$a(u,v) = \int_{\Omega} \nabla u . \nabla v \, dV$$ +# and $$L$$ is the right hand side corresponding to the linear form $$l(v) = \int_{\Omega} f v \, dV$$ +# There is no unique solution to this problem (adding a constant to any solution will also be a solution). +# Uniqueness can be recovered by adding a constraint to the problem. In this example the following constraint is added: +# ```math +# \int_{\Gamma} u \, d \gamma = 2 \pi +# ``` +# The discrete version of the constraint is: $$ = 2 \pi$$ +# where $$Lc$$ is the vector corresponding to the linear form $$l_c(v) = \int_{\Gamma} v \, dV$$. +# To solve this constrained minimisation problem, the following lagragian is introduced: +# ```math +# L(X, \lambda) = < \frac{1}{2} A X , X > - < L , X > + \lambda ( < Lc , X > - 2 \pi) +# ``` +# where $$\lambda$$ is a Lagrange multiplier. +# The solution of this problem is given by the first order optimality conditions: +# ```math +# AX + \lambda Lc = L +# ``` +# ```math +# Lc^T X = 2 \pi +# ``` +# In this example, the manufactured solution $$u(x,y)=cos(4\pi(x^2 + y^2))$$ is used to test the method. + +# import necessary packages +using Bcube +using LinearAlgebra +using SparseArrays +using WriteVTK + +const outputpath = joinpath(@__DIR__, "../myout/constrained_poisson/") +isdir(outputpath) || mkpath(outputpath) + +# Read 2D mesh +mesh_path = joinpath(outputpath, "mesh.msh") +gen_disk_mesh(mesh_path; lc = 3.2e-2) +mesh = read_msh(mesh_path) + +# Choose degree and define function space, trial space and test space +const degree = 2 +fs = FunctionSpace(:Lagrange, degree) +U = TrialFESpace(fs, mesh) +V = TestFESpace(U) + +# Define volume and boundary measures +dΩ = Measure(CellDomain(mesh), 2 * degree + 1) +Γ = BoundaryFaceDomain(mesh, ("BORDER",)) +dΓ = Measure(Γ, 2 * degree + 1) + +# Define solution FE Function +ϕ = FEFunction(U) + +# Define source term function (deduced from manufactured solution) +f = PhysicalFunction( + x -> + 64.0 * π^2 * (x[1]^2 + x[2]^2) * cos(4.0 * π * (x[1]^2 + x[2]^2)) + + 16.0 * π * sin(4.0 * π * (x[1]^2 + x[2]^2)), +) + +# Define bilinear and linear forms +a(u, v) = ∫(∇(u) ⋅ ∇(v))dΩ +l(v) = ∫(f * v)dΩ +lc(v) = ∫(side⁻(v))dΓ + +# Assemble to get matrices and vectors +A = assemble_bilinear(a, U, V) +L = assemble_linear(l, V) +Lc = assemble_linear(lc, V) + +# Build augmented problem +n = size(L)[1] + +M = spzeros(n + 1, n + 1) +B = zeros(n + 1) + +M[1:n, 1:n] .= A[1:n, 1:n] +M[n + 1, 1:n] .= Lc[:] +M[1:n, n + 1] .= Lc[:] +B[1:n] .= L[1:n] +B[n + 1] = 2.0 * π + +# Solve problem +sol = M \ B + +# Write solution and compare to analytical solution +set_dof_values!(ϕ, sol[1:n]) +λ = sol[n + 1] + +println(" Value of Lagrange multiplier : ", λ) + +ϕₑ = FEFunction(U) + +projection_l2!(ϕₑ, PhysicalFunction(x -> cos(4.0 * π * (x[1]^2 + x[2]^2))), mesh) + +Un = var_on_vertices(ϕ, mesh) +Ue = var_on_vertices(ϕₑ, mesh) +mkpath(outputpath) +dict_vars = Dict( + "Numerical Solution" => (Un, VTKPointData()), + "Analytical solution" => (Ue, VTKPointData()), +) +write_vtk(outputpath * "result_constrained_poisson_equation", 0, 0.0, mesh, dict_vars) + +error = norm(Un .- Ue, Inf) / norm(Ue, Inf) +println(" Error : ", error) + +end #hide diff --git a/src/example/covo.jl b/src/example/covo.jl new file mode 100644 index 0000000..209dae8 --- /dev/null +++ b/src/example/covo.jl @@ -0,0 +1,425 @@ +module Covo #hide +println("Running covo example...") #hide + +const dir = string(@__DIR__, "/") +using Bcube +using LinearAlgebra +using StaticArrays +using WriteVTK # only for 'VTKCellData' +using Profile +using StaticArrays +using InteractiveUtils +using BenchmarkTools +using UnPack + +function compute_residual(_u, V, params, cache) + u = get_fe_functions(_u) + + # alias on measures + @unpack dΩ, dΓ, dΓ_perio_x, dΓ_perio_y = params + + # face normals for each face domain (lazy, no computation at this step) + nΓ = get_face_normals(dΓ) + nΓ_perio_x = get_face_normals(dΓ_perio_x) + nΓ_perio_y = get_face_normals(dΓ_perio_y) + + # flux residuals from faces for all variables + function l(v) + ∫(flux_Ω(u, v))dΩ + + -∫(flux_Γ(u, v, nΓ))dΓ + + -∫(flux_Γ(u, v, nΓ_perio_x))dΓ_perio_x + + -∫(flux_Γ(u, v, nΓ_perio_y))dΓ_perio_y + end + + rhs = assemble_linear(l, V) + + return cache.mass \ rhs +end + +""" + flux_Ω(u, v) + +Compute volume residual using the lazy-operators approach +""" +flux_Ω(u, v) = _flux_Ω ∘ cellvar(u, v) +cellvar(u, v) = (u, map(∇, v)) +function _flux_Ω(u, ∇v) + ρ, ρu, ρE, ϕ = u + ∇λ_ρ, ∇λ_ρu, ∇λ_ρE, ∇λ_ϕ = ∇v + + vel = ρu ./ ρ + ρuu = ρu * transpose(vel) + p = pressure(ρ, ρu, ρE, γ) + + flux_ρ = ρu + flux_ρu = ρuu + p * I + flux_ρE = (ρE + p) .* vel + flux_ϕ = ϕ .* vel + + return ∇λ_ρ ⋅ flux_ρ + ∇λ_ρu ⊡ flux_ρu + ∇λ_ρE ⋅ flux_ρE + ∇λ_ϕ ⋅ flux_ϕ +end + +""" + flux_Γ(u,v,n) + +Flux at the interface is defined by a composition of two functions: +* facevar(u,v,n) defines the input states which are needed for + the riemann flux using operator notations +* flux_roe(w) defines the Riemann flux (as usual) +""" +flux_Γ(u, v, n) = flux_roe ∘ (side⁻(u), side⁺(u), jump(v), side⁻(n)) + +""" + flux_roe(w) +""" +function flux_roe(ui, uj, δv, nij) + # destructuring inputs given by `facevar` function + + nx, ny = nij + ρ1, ρu1, ρE1, ϕ1 = ui + ρ2, ρu2, ρE2, ϕ2 = uj + δλ_ρ1, δλ_ρu1, δλ_ρE1, δλ_ϕ1 = δv + ρux1, ρuy1 = ρu1 + ρux2, ρuy2 = ρu2 + + # Closure + u1 = ρux1 / ρ1 + v1 = ρuy1 / ρ1 + u2 = ρux2 / ρ2 + v2 = ρuy2 / ρ2 + p1 = pressure(ρ1, ρu1, ρE1, γ) + p2 = pressure(ρ2, ρu2, ρE2, γ) + + H2 = (γ / (γ - 1)) * p2 / ρ2 + (u2 * u2 + v2 * v2) / 2.0 + H1 = (γ / (γ - 1)) * p1 / ρ1 + (u1 * u1 + v1 * v1) / 2.0 + + R = √(ρ1 / ρ2) + invR1 = 1.0 / (R + 1) + uAv = (R * u1 + u2) * invR1 + vAv = (R * v1 + v2) * invR1 + Hav = (R * H1 + H2) * invR1 + cAv = √(abs((γ - 1) * (Hav - (uAv * uAv + vAv * vAv) / 2.0))) + ecAv = (uAv * uAv + vAv * vAv) / 2.0 + + λ1 = nx * uAv + ny * vAv + λ3 = λ1 + cAv + λ4 = λ1 - cAv + + d1 = ρ1 - ρ2 + d2 = ρ1 * u1 - ρ2 * u2 + d3 = ρ1 * v1 - ρ2 * v2 + d4 = ρE1 - ρE2 + + # computation of the centered part of the flux + flu11 = nx * ρ2 * u2 + ny * ρ2 * v2 + flu21 = nx * p2 + flu11 * u2 + flu31 = ny * p2 + flu11 * v2 + flu41 = H2 * flu11 + + # Temp variables + rc1 = (γ - 1) / cAv + rc2 = (γ - 1) / cAv / cAv + uq41 = ecAv / cAv + cAv / (γ - 1) + uq42 = nx * uAv + ny * vAv + + fdc1 = max(λ1, 0.0) * (d1 + rc2 * (-ecAv * d1 + uAv * d2 + vAv * d3 - d4)) + fdc2 = max(λ1, 0.0) * ((nx * vAv - ny * uAv) * d1 + ny * d2 - nx * d3) + fdc3 = + max(λ3, 0.0) * ( + (-uq42 * d1 + nx * d2 + ny * d3) / 2.0 + + rc1 * (ecAv * d1 - uAv * d2 - vAv * d3 + d4) / 2.0 + ) + fdc4 = + max(λ4, 0.0) * ( + (uq42 * d1 - nx * d2 - ny * d3) / 2.0 + + rc1 * (ecAv * d1 - uAv * d2 - vAv * d3 + d4) / 2.0 + ) + + duv1 = fdc1 + (fdc3 + fdc4) / cAv + duv2 = uAv * fdc1 + ny * fdc2 + (uAv / cAv + nx) * fdc3 + (uAv / cAv - nx) * fdc4 + duv3 = vAv * fdc1 - nx * fdc2 + (vAv / cAv + ny) * fdc3 + (vAv / cAv - ny) * fdc4 + duv4 = + ecAv * fdc1 + + (ny * uAv - nx * vAv) * fdc2 + + (uq41 + uq42) * fdc3 + + (uq41 - uq42) * fdc4 + + v₁₂ = 0.5 * ((u1 + u2) * nx + (v1 + v2) * ny) + fluxϕ = max(0.0, v₁₂) * ϕ1 + min(0.0, v₁₂) * ϕ2 + + return ( + δλ_ρ1 * (flu11 + duv1) + + δλ_ρu1 ⋅ (SA[flu21 + duv2, flu31 + duv3]) + + δλ_ρE1 * (flu41 + duv4) + + δλ_ϕ1 * (fluxϕ) + ) +end + +""" +Time integration of `f(q, t)` over a timestep `Δt`. +""" +forward_euler(q, f::Function, t, Δt) = get_dof_values(q) .+ Δt .* f(q, t) + +""" + rk3_ssp(q, f::Function, t, Δt) + +`f(q, t)` is the function to integrate. +""" +function rk3_ssp(q, f::Function, t, Δt) + stepper(q, t) = forward_euler(q, f, t, Δt) + _q0 = get_dof_values(q) + + _q1 = stepper(q, Δt) + + set_dof_values!(q, _q1) + _q2 = (3 / 4) .* _q0 .+ (1 / 4) .* stepper(q, t + Δt) + + set_dof_values!(q, _q2) + _q1 .= (1 / 3) * _q0 .+ (2 / 3) .* stepper(q, t + Δt / 2) + + return _q1 +end + +""" + pressure(ρ, ρu, ρE, γ) + +Computes pressure from perfect gaz law +""" +function pressure(ρ::Number, ρu::AbstractVector, ρE::Number, γ) + vel = ρu ./ ρ + ρuu = ρu * transpose(vel) + p = (γ - 1) * (ρE - tr(ρuu) / 2) + return p +end + +""" + Init field with a vortex (for the COVO test case) +""" +function covo!(q, dΩ) + + # Intermediate vars + Cₚ = γ * r / (γ - 1) + + r²(x) = ((x[1] .- xvc) .^ 2 + (x[2] .- yvc) .^ 2) ./ Rc^2 + # Temperature + T(x) = T₀ .- β^2 * U₀^2 / (2 * Cₚ) .* exp.(-r²(x)) + # Velocity + ux(x) = U₀ .- β * U₀ / Rc .* (x[2] .- yvc) .* exp.(-r²(x) ./ 2) + uy(x) = V₀ .+ β * U₀ / Rc .* (x[1] .- xvc) .* exp.(-r²(x) ./ 2) + # Density + ρ(x) = ρ₀ .* (T(x) ./ T₀) .^ (1.0 / (γ - 1)) + # momentum + ρu(x) = SA[ρ(x) * ux(x), ρ(x) * uy(x)] + # Energy + ρE(x) = ρ(x) * ((Cₚ / γ) .* T(x) + (ux(x) .^ 2 + uy(x) .^ 2) ./ 2) + # Passive scalar + ϕ(x) = Rc^2 * r²(x) < 0.01 ? exp(-r²(x) ./ 2) : 0.0 + + f = map(PhysicalFunction, (ρ, ρu, ρE, ϕ)) + projection_l2!(q, f, dΩ) + return nothing +end + +""" + Tiny struct to ease the VTK output +""" +mutable struct VtkHandler + basename::Any + ite::Any + VtkHandler(basename) = new(basename, 0) +end + +""" + Write solution to vtk + Wrapper for `write_vtk` +""" +function append_vtk(vtk, mesh, vars, t, params) + ρ, ρu, ρE, ϕ = vars + + mesh_degree = 1 + vtk_degree = maximum(x -> get_degree(Bcube.get_function_space(get_fespace(x))), vars) + vtk_degree = max(1, mesh_degree, vtk_degree) + + _ρ = var_on_nodes_discontinuous(ρ, mesh, vtk_degree) + _ρu = var_on_nodes_discontinuous(ρu, mesh, vtk_degree) + _ρE = var_on_nodes_discontinuous(ρE, mesh, vtk_degree) + _ϕ = var_on_nodes_discontinuous(ϕ, mesh, vtk_degree) + + _p = pressure.(_ρ, _ρu, _ρE, γ) + dict_vars_dg = Dict( + "rho" => (_ρ, VTKPointData()), + "rhou" => (_ρu, VTKPointData()), + "rhoE" => (_ρE, VTKPointData()), + "phi" => (_ϕ, VTKPointData()), + "p" => (_p, VTKPointData()), + ) + Bcube.write_vtk_discontinuous( + vtk.basename * "_DG", + vtk.ite, + t, + mesh, + dict_vars_dg, + vtk_degree; + append = vtk.ite > 0, + ) + + # Update counter + vtk.ite += 1 +end + +# Settings +if get(ENV, "BenchmarkMode", "false") == "false" #hide + const cellfactor = 1 + const nx = 32 * cellfactor + 1 + const ny = 32 * cellfactor + 1 + const fspace = :Lagrange + const timeScheme = :ForwardEuler +else #hide + const nx = 128 + 1 #hide + const ny = 128 + 1 #hide + const fspace = :Lagrange + const timeScheme = :ForwardEuler +end #hide +const nperiod = 1 # number of turn +const CFL = 0.1 +const degree = 1 # FunctionSpace degree +const degquad = 2 * degree + 1 +const γ = 1.4 +const β = 0.2 # vortex intensity +const r = 287.15 # Perfect gaz constant +const T₀ = 300 # mean-flow temperature +const P₀ = 1e5 # mean-flow pressure +const M₀ = 0.5 # mean-flow mach number +const ρ₀ = 1.0 # mean-flow density +const xvc = 0.0 # x-center of vortex +const yvc = 0.0 # y-center of vortex +const Rc = 0.005 # Charasteristic vortex radius +const c₀ = √(γ * r * T₀) # Sound velocity +const U₀ = M₀ * c₀ # mean-flow velocity +const V₀ = 0.0 # mean-flow velocity +const ϕ₀ = 1.0 +const l = 0.05 # half-width of the domain +const Δt = CFL * 2 * l / (nx - 1) / ((1 + β) * U₀ + c₀) / (2 * degree + 1) +#const Δt = 5.e-7 +const nout = 100 # Number of time steps to save +const outputpath = "../myout/covo/" +const output = joinpath(@__DIR__, outputpath, "covo_deg$degree") +const nite = Int(floor(nperiod * 2 * l / (U₀ * Δt))) + 1 + +function run_covo() + println("Starting run_covo...") + + # Build mesh + meshParam = (nx = nx, ny = ny, lx = 2l, ly = 2l, xc = 0.0, yc = 0.0) + tmp_path = "tmp.msh" + if get(ENV, "BenchmarkMode", "false") == "false" #hide + gen_rectangle_mesh(tmp_path, :quad; meshParam...) + else #hide + if get(ENV, "MeshConfig", "quad") == "triquad" #hide + gen_rectangle_mesh_with_tri_and_quad(tmp_path; meshParam...) #hide + else #hide + gen_rectangle_mesh(tmp_path, :quad; meshParam...) #hide + end #hide + end #hide + mesh = read_msh(tmp_path) + rm(tmp_path) + + # Define variables and test functions + fs = FunctionSpace(fspace, degree) + U_sca = TrialFESpace(fs, mesh, :discontinuous; size = 1) # DG, scalar + U_vec = TrialFESpace(fs, mesh, :discontinuous; size = 2) # DG, vectoriel + V_sca = TestFESpace(U_sca) + V_vec = TestFESpace(U_vec) + U = MultiFESpace(U_sca, U_vec, U_sca, U_sca) + V = MultiFESpace(V_sca, V_vec, V_sca, V_sca) + u = FEFunction(U) + + @show Bcube.get_ndofs(U) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), degquad) + dΓ = Measure(InteriorFaceDomain(mesh), degquad) + + # Declare periodic boundary conditions and + # create associated domains and measures + periodicBCType_x = PeriodicBCType(Translation(SA[-2l, 0.0]), ("East",), ("West",)) + periodicBCType_y = PeriodicBCType(Translation(SA[0.0, 2l]), ("South",), ("North",)) + Γ_perio_x = BoundaryFaceDomain(mesh, periodicBCType_x) + Γ_perio_y = BoundaryFaceDomain(mesh, periodicBCType_y) + dΓ_perio_x = Measure(Γ_perio_x, degquad) + dΓ_perio_y = Measure(Γ_perio_y, degquad) + + params = (dΩ = dΩ, dΓ = dΓ, dΓ_perio_x = dΓ_perio_x, dΓ_perio_y = dΓ_perio_y) + + # Init vtk + isdir(joinpath(@__DIR__, outputpath)) || mkpath(joinpath(@__DIR__, outputpath)) + vtk = VtkHandler(output) + + # Init solution + t = 0.0 + + covo!(u, dΩ) + + # cache mass matrices + cache = (mass = factorize(Bcube.build_mass_matrix(U, V, dΩ)),) + + if get(ENV, "BenchmarkMode", "false") == "true" #hide + return u, U, V, params, cache + end + + # Write initial solution + append_vtk(vtk, mesh, u, t, params) + + # Time loop + for i in 1:nite + println("") + println("") + println("Iteration ", i, " / ", nite) + + ## Step forward in time + rhs(u, t) = compute_residual(u, V, params, cache) + if timeScheme == :ForwardEuler + unew = forward_euler(u, rhs, time, Δt) + elseif timeScheme == :RK3 + unew = rk3_ssp(u, rhs, time, Δt) + else + error("Unknown time scheme: $timeScheme") + end + + set_dof_values!(u, unew) + + t += Δt + + # Write solution to file + if (i % Int(max(floor(nite / nout), 1)) == 0) + println("--> VTK export") + append_vtk(vtk, mesh, u, t, params) + end + end + + # Summary and benchmark # ndofs total = 20480 + _rhs(u, t) = compute_residual(u, V, params, cache) + @btime forward_euler($u, $_rhs, $time, $Δt) # 5.639 ms (1574 allocations: 2.08 MiB) + # stepper = w -> explicit_step(w, params, cache, Δt) + # RK3_SSP(stepper, (u, v), cache) + # @btime RK3_SSP($stepper, ($u, $v), $cache) + println("ndofs total = ", Bcube.get_ndofs(U)) + Profile.init(; n = 10^7) # returns the current settings + Profile.clear() + Profile.clear_malloc_data() + @profile begin + for i in 1:100 + forward_euler(u, _rhs, time, Δt) + end + end + @show Δt, U₀, U₀ * t + @show boundary_names(mesh) + return nothing +end + +if get(ENV, "BenchmarkMode", "false") == "false" + mkpath(outputpath) + run_covo() +end + +end #hide diff --git a/src/example/euler_naca_steady.jl b/src/example/euler_naca_steady.jl new file mode 100644 index 0000000..d044bf5 --- /dev/null +++ b/src/example/euler_naca_steady.jl @@ -0,0 +1,726 @@ +module EulerNacaSteady #hide +println("Running euler_naca_steady example...") #hide +# # Solve Euler equation around a NACA0012 airfoil + +using Bcube +using LinearAlgebra +using WriteVTK +using StaticArrays +using BenchmarkTools +using Roots +using SparseArrays +using Profile +using InteractiveUtils +using WriteVTK +using DifferentialEquations +using Symbolics +using SparseDiffTools + +const dir = string(@__DIR__, "/") + +function compute_residual(qdof, Q, V, params) + q = (FEFunction(Q, qdof)...,) + + # alias on measures + dΓ = params.dΓ + dΩ = params.dΩ + dΓ_wall = params.dΓ_wall + dΓ_farfield = params.dΓ_farfield + + # Allocate rhs vectors + b_vol = zero(qdof) + b_fac = zero(qdof) + + # compute volume residuals + l_vol(v) = ∫(flux_Ω(q, v))dΩ + assemble_linear!(b_vol, l_vol, V) + + # face normals for each face domain (lazy, no computation at this step) + nΓ = get_face_normals(dΓ) + nΓ_wall = get_face_normals(dΓ_wall) + nΓ_farfield = get_face_normals(dΓ_farfield) + + # flux residuals from interior faces for all variables + l_Γ(v) = ∫(flux_Γ(q, v, nΓ))dΓ + assemble_linear!(b_fac, l_Γ, V) + + # flux residuals from bc faces for all variables + l_Γ_wall(v) = ∫(flux_Γ_wall(q, v, nΓ_wall))dΓ_wall + l_Γ_farfield(v) = ∫(flux_Γ_farfield(q, v, nΓ_farfield))dΓ_farfield + assemble_linear!(b_fac, l_Γ_wall, V) + assemble_linear!(b_fac, l_Γ_farfield, V) + dQ = b_vol .- b_fac + + return dQ +end + +""" + flux_Ω(q, v) + +Compute volume residual using the lazy-operators approach +""" +flux_Ω(q, v) = _flux_Ω ∘ (q, map(∇, v)) + +function _flux_Ω(q, ∇v) + ρ, ρu, ρE = q + ∇λ_ρ, ∇λ_ρu, ∇λ_ρE = ∇v + γ = stateInit.γ + + vel = ρu ./ ρ + ρuu = ρu * transpose(vel) + p = pressure(ρ, ρu, ρE, γ) + + flux_ρ = ρu + flux_ρu = ρuu + p * I + flux_ρE = (ρE + p) .* vel + + return return ∇λ_ρ ⋅ flux_ρ + ∇λ_ρu ⊡ flux_ρu + ∇λ_ρE ⋅ flux_ρE +end + +""" + flux_Γ(q, v, n) + +Flux at the interface is defined by a composition of two functions: +* the input states at face sides which are needed for the riemann flux +* `flux_roe` defines the Riemann flux (as usual) +""" +flux_Γ(q, v, n) = flux_roe ∘ (side⁻(q), side⁺(q), jump(v), side⁻(n)) + +""" + flux_roe(q⁻, q⁺, δv, n) +""" +function flux_roe(q⁻, q⁺, δv, n) + γ = stateInit.γ + nx, ny = n + ρ1, (ρu1, ρv1), ρE1 = q⁻ + ρ2, (ρu2, ρv2), ρE2 = q⁺ + δλ_ρ1, δλ_ρu1, δλ_ρE1 = δv + + ρ1 = max(eps(ρ1), ρ1) + ρ2 = max(eps(ρ2), ρ2) + + # Closure + u1 = ρu1 / ρ1 + v1 = ρv1 / ρ1 + u2 = ρu2 / ρ2 + v2 = ρv2 / ρ2 + p1 = pressure(ρ1, SA[ρu1, ρv1], ρE1, γ) + p2 = pressure(ρ2, SA[ρu2, ρv2], ρE2, γ) + + H2 = (γ / (γ - 1)) * p2 / ρ2 + (u2 * u2 + v2 * v2) / 2.0 + H1 = (γ / (γ - 1)) * p1 / ρ1 + (u1 * u1 + v1 * v1) / 2.0 + + R = √(ρ1 / ρ2) + invR1 = 1.0 / (R + 1) + uAv = (R * u1 + u2) * invR1 + vAv = (R * v1 + v2) * invR1 + Hav = (R * H1 + H2) * invR1 + cAv = √(abs((γ - 1) * (Hav - (uAv * uAv + vAv * vAv) / 2.0))) + ecAv = (uAv * uAv + vAv * vAv) / 2.0 + + λ1 = nx * uAv + ny * vAv + λ3 = λ1 + cAv + λ4 = λ1 - cAv + + d1 = ρ1 - ρ2 + d2 = ρ1 * u1 - ρ2 * u2 + d3 = ρ1 * v1 - ρ2 * v2 + d4 = ρE1 - ρE2 + + # computation of the centered part of the flux + flux_ρ = nx * ρ2 * u2 + ny * ρ2 * v2 + flux_ρu = nx * p2 + flux_ρ * u2 + flux_ρv = ny * p2 + flux_ρ * v2 + flux_ρE = H2 * flux_ρ + + # Temp variables + rc1 = (γ - 1) / cAv + rc2 = (γ - 1) / cAv / cAv + uq41 = ecAv / cAv + cAv / (γ - 1) + uq42 = nx * uAv + ny * vAv + + fdc1 = max(λ1, 0.0) * (d1 + rc2 * (-ecAv * d1 + uAv * d2 + vAv * d3 - d4)) + fdc2 = max(λ1, 0.0) * ((nx * vAv - ny * uAv) * d1 + ny * d2 - nx * d3) + fdc3 = + max(λ3, 0.0) * ( + (-uq42 * d1 + nx * d2 + ny * d3) / 2.0 + + rc1 * (ecAv * d1 - uAv * d2 - vAv * d3 + d4) / 2.0 + ) + fdc4 = + max(λ4, 0.0) * ( + (uq42 * d1 - nx * d2 - ny * d3) / 2.0 + + rc1 * (ecAv * d1 - uAv * d2 - vAv * d3 + d4) / 2.0 + ) + + duv1 = fdc1 + (fdc3 + fdc4) / cAv + duv2 = uAv * fdc1 + ny * fdc2 + (uAv / cAv + nx) * fdc3 + (uAv / cAv - nx) * fdc4 + duv3 = vAv * fdc1 - nx * fdc2 + (vAv / cAv + ny) * fdc3 + (vAv / cAv - ny) * fdc4 + duv4 = + ecAv * fdc1 + + (ny * uAv - nx * vAv) * fdc2 + + (uq41 + uq42) * fdc3 + + (uq41 - uq42) * fdc4 + + flux_ρ += duv1 + flux_ρu += duv2 + flux_ρv += duv3 + flux_ρE += duv4 + + return (δλ_ρ1 ⋅ flux_ρ + δλ_ρu1 ⋅ SA[flux_ρu, flux_ρv] + δλ_ρE1 ⋅ flux_ρE) +end + +""" + flux_Γ_farfield(q, v, n) + +Compute `Roe` flux on boundary face by imposing +`stateBcFarfield.u_in` on `side_p` +""" +flux_Γ_farfield(q, v, n) = flux_roe ∘ (side⁻(q), stateBcFarfield.u_inf, side⁻(v), side⁻(n)) + +""" + flux_Γ_wall(q, v, n) +""" +flux_Γ_wall(q, v, n) = _flux_Γ_wall ∘ (side⁻(q), side⁻(v), side⁻(n)) + +function _flux_Γ_wall(q⁻, v⁻, n) + γ = stateInit.γ + ρ1, ρu1, ρE1 = q⁻ + λ_ρ1, λ_ρu1, λ_ρE1 = v⁻ + + p1 = pressure(ρ1, ρu1, ρE1, γ) + + flux_ρ = zero(ρ1) + flux_ρu = p1 * n + flux_ρE = zero(ρE1) + + return (λ_ρ1 ⋅ flux_ρ + λ_ρu1 ⋅ flux_ρu + λ_ρE1 ⋅ flux_ρE) +end + +function sparse2vtk( + a::AbstractSparseMatrix, + name::String = string(@__DIR__, "/../myout/sparse"), +) + vtk_write_array(name, Array(a), "my_property_name") +end + +mutable struct VtkHandler + basename::String + basename_residual::String + ite::Int + VtkHandler(basename) = new(basename, basename * "_residual", 0) +end + +""" + Write solution (at cell centers) to vtk + Wrapper for `write_vtk` +""" +function append_vtk(vtk, mesh, vars, t, params; res = nothing) + ρ, ρu, ρE = vars + + # Mean cell values + # name2val_mean = (;zip(get_name.(vars), mean_values.(vars, degquad))...) + # p_mean = pressure.(name2val_mean[:ρ], name2val_mean[:ρu], name2val_mean[:ρE], params.stateInit.γ) + + vtk_degree = maximum(x -> get_degree(Bcube.get_function_space(get_fespace(x))), vars) + vtk_degree = max(1, mesh_degree, vtk_degree) + _ρ = var_on_nodes_discontinuous(ρ, mesh, vtk_degree) + _ρu = var_on_nodes_discontinuous(ρu, mesh, vtk_degree) + _ρE = var_on_nodes_discontinuous(ρE, mesh, vtk_degree) + + Cp = pressure_coefficient.(_ρ, _ρu, _ρE) + Ma = mach.(_ρ, _ρu, _ρE) + dict_vars_dg = Dict( + "rho" => (_ρ, VTKPointData()), + "rhou" => (_ρu, VTKPointData()), + "rhoE" => (_ρE, VTKPointData()), + "Cp" => (Cp, VTKPointData()), + "Mach" => (Ma, VTKPointData()), + "rho_mean" => (get_values(Bcube.cell_mean(ρ, params.dΩ)), VTKCellData()), + "rhou_mean" => (get_values(Bcube.cell_mean(ρu, params.dΩ)), VTKCellData()), + "rhoE_mean" => (get_values(Bcube.cell_mean(ρE, params.dΩ)), VTKCellData()), + "lim_rho" => (get_values(params.limρ), VTKCellData()), + "lim_all" => (get_values(params.limAll), VTKCellData()), + ) + Bcube.write_vtk_discontinuous( + vtk.basename * "_DG", + vtk.ite, + t, + mesh, + dict_vars_dg, + vtk_degree; + append = vtk.ite > 0, + ) + + _ρ_wall = var_on_bnd_nodes_discontinuous(ρ, params.Γ_wall, vtk_degree) + _ρu_wall = var_on_bnd_nodes_discontinuous(ρu, params.Γ_wall, vtk_degree) + _ρE_wall = var_on_bnd_nodes_discontinuous(ρE, params.Γ_wall, vtk_degree) + + Cp_wall = pressure_coefficient.(_ρ_wall, _ρu_wall, _ρE_wall) + Ma_wall = pressure_coefficient.(_ρ_wall, _ρu_wall, _ρE_wall) + + dict_vars_wall = Dict( + "rho" => (_ρ_wall, VTKPointData()), + "rhou" => (_ρu_wall, VTKPointData()), + "rhoE" => (_ρE_wall, VTKPointData()), + "Cp" => (Cp_wall, VTKPointData()), + "Mach" => (Ma_wall, VTKPointData()), + ) + Bcube.write_vtk_bnd_discontinuous( + vtk.basename * "_bnd_DG", + 1, + 0.0, + params.Γ_wall, + dict_vars_wall, + vtk_degree; + append = false, + ) + + #residual: + if !isa(res, Nothing) + vtkfile = vtk_grid(vtk.basename_residual, Float64.(res.iter), [0.0, 1.0]) + for (k, valₖ) in enumerate(res.val) + vtkfile["res_" * string(k), VTKPointData()] = [valₖ valₖ] + end + vtk_save(vtkfile) + end + + # Update counter + vtk.ite += 1 + + return nothing +end + +function init!(q, dΩ, initstate) + AoA = initstate.AoA + Minf = initstate.M_inf + Pinf = initstate.P_inf + Tinf = initstate.T_inf + r = initstate.r_gas + γ = initstate.γ + + ρinf = Pinf / r / Tinf + ainf = √(γ * r * Tinf) + Vinf = Minf * ainf + ρVxinf = ρinf * Vinf * cos(AoA) + ρVyinf = ρinf * Vinf * sin(AoA) + ρEinf = Pinf / (γ - 1) + 0.5 * ρinf * Vinf^2 + + ρ0 = PhysicalFunction(x -> ρinf) + ρu0 = PhysicalFunction(x -> SA[ρVxinf, ρVyinf]) + ρE0 = PhysicalFunction(x -> ρEinf) + projection_l2!(q, (ρ0, ρu0, ρE0), dΩ) + return nothing +end + +function main(stateInit, stateBcFarfield, degree) + @show degree, degquad + + mesh = read_msh(dir * "../input/mesh/naca0012_o" * string(mesh_degree) * ".msh", 2) + scale!(mesh, 1.0 / 0.5334) + + dimcar = compute_dimcar(mesh) + + DMPrelax = DMPcurv₀ .* dimcar .^ 2 + + # Then we create a `NamedTuple` to hold the simulation parameters. + params = ( + degquad = degquad, + stateInit = stateInit, + stateBcFarfield = stateBcFarfield, + DMPrelax = DMPrelax, + ) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), degquad) + dΓ = Measure(InteriorFaceDomain(mesh), degquad) + + # Declare boundary conditions and + # create associated domains and measures + Γ_wall = BoundaryFaceDomain(mesh, ("NACA",)) + Γ_farfield = BoundaryFaceDomain(mesh, ("FARFIELD",)) + dΓ_wall = Measure(Γ_wall, degquad) + dΓ_farfield = Measure(Γ_farfield, degquad) + + params = ( + params..., + Γ_wall = Γ_wall, + dΓ = dΓ, + dΩ = dΩ, + dΓ_wall = dΓ_wall, + dΓ_farfield = dΓ_farfield, + ) + + qLowOrder = nothing + + for deg in 0:degree + params = (params..., degree = deg) + + fs = FunctionSpace(fspace, deg) + Q_sca = TrialFESpace(fs, mesh, :discontinuous; size = 1) # DG, scalar + Q_vec = TrialFESpace(fs, mesh, :discontinuous; size = 2) # DG, vectoriel + V_sca = TestFESpace(Q_sca) + V_vec = TestFESpace(Q_vec) + Q = MultiFESpace(Q_sca, Q_vec, Q_sca) + V = MultiFESpace(V_sca, V_vec, V_sca) + + q = FEFunction(Q) + + # select an initial configurations: + if deg == 0 + init!(q, mesh, stateInit) + else + println("Start projection") + projection_l2!(q, qLowOrder, dΩ) + println("End projection") + end + + # create CellData to store limiter values + limρ = Bcube.MeshCellData(ones(ncells(mesh))) + limAll = Bcube.MeshCellData(ones(ncells(mesh))) + params = (params..., limρ = limρ, limAll = limAll) + + # Init vtk handler + mkpath(outputpath) + vtk = VtkHandler( + outputpath * "euler_naca_mdeg" * string(mesh_degree) * "_deg" * string(deg), + ) + + # Init time + time = 0.0 + + # Save initial solution + append_vtk(vtk, mesh, q, time, params) + + # Build the cache and store everything you want to compute only once (such as the mass matrice inverse...) + + cache = () + # Allocate buffer for compute_residual + b_vol = zeros(Bcube.get_ndofs(Q)) + b_fac = zeros(Bcube.get_ndofs(Q)) + cache = (cache..., b_vol = b_vol, b_fac = b_fac) + + cache = ( + cache..., + cacheCellMean = Bcube.build_cell_mean_cache(q, dΩ), + mass = factorize(Bcube.build_mass_matrix(Q, V, dΩ)), + mass_sca = factorize(Bcube.build_mass_matrix(Q_sca, V_sca, dΩ)), + mass_vec = factorize(Bcube.build_mass_matrix(Q_vec, V_vec, dΩ)), + ) + + time, q = steady_solve!(Q, V, q, mesh, params, cache, vtk, deg) + append_vtk(vtk, mesh, q, time, params) + println("end steady_solve for deg=", deg, " !") + + deg < degree && (qLowOrder = deepcopy(q)) + end + return nothing +end + +function steady_solve!(Q, V, q, mesh, params, cache, vtk, deg) + counter = [0] + q0 = deepcopy(get_dof_values(q)) + ode_params = + (Q = Q, V = V, params = params, cache = cache, counter = counter, vtk = vtk) + + rhs!(dq, q, p, t) = dq .= compute_residual(q, p.Q, p.V, p.params) + + # compute sparsity pattern and coloring + println("computing jacobian cache...") + if withbench + _rhs!(dq, q) = rhs!(dq, q, ode_params, 0.0) + @btime $_rhs!(similar($q0), $q0) + q_bench = FEFunction(Q, q0) + @btime $apply_limitation!($q_bench, $ode_params) + @show length(q0) + end + + #sparsity_pattern = Symbolics.jacobian_sparsity(_rhs!, similar(Q0), Q0) + #tjac = @elapsed Symbolics.jacobian_sparsity(_rhs!, similar(Q0), Q0) + #@show tjac + sparsity_pattern = Bcube.build_jacobian_sparsity_pattern(Q, mesh) + println("sparsity pattern computed !") + display(sparsity_pattern) + colors = matrix_colors(sparsity_pattern) + println("coloring done!") + @show maximum(colors) + + ode = ODEFunction( + rhs!; + mass_matrix = Bcube.build_mass_matrix(Q, V, params.dΩ), + jac_prototype = sparsity_pattern, + colorvec = colors, + ) + + Tfinal = Inf + problem = ODEProblem(ode, q0, (0.0, Tfinal), ode_params) + timestepper = ImplicitEuler(; nlsolve = NLNewton(; max_iter = 20)) + + cb_cache = DiscreteCallback(always_true, update_cache!; save_positions = (false, false)) + cb_vtk = DiscreteCallback(always_true, output_vtk; save_positions = (false, false)) + cb_steady = TerminateSteadyState(1e-6, 1e-6, condition_steadystate) + + error = 1e-1 + + sol = solve( + problem, + timestepper; + initializealg = NoInit(), + adaptive = true, + abstol = error, + reltol = error, + progress = false, + progress_steps = 1000, + save_everystep = false, + save_start = false, + save_end = false, + isoutofdomain = isoutofdomain, + callback = CallbackSet(cb_cache, cb_vtk, cb_steady), + ) + + set_dof_values!(q, sol.u[end]) + return sol.t[end], q +end + +always_true(args...) = true + +function isoutofdomain(dof, p, t) + any(isnan, dof) && return true + + q = FEFunction(p.Q, dof) + q_mean = map(get_values, Bcube.cell_mean(q, p.cache.cacheCellMean)) + p_mean = pressure.(q_mean..., stateInit.γ) + + negative_ρ = any(x -> x < 0, q_mean[1]) + negative_p = any(x -> x < 0, p_mean) + isout = negative_ρ || negative_p + isout && @show negative_ρ, negative_p + return isout +end + +function update_cache!(integrator) + Q = integrator.p.Q + Q1, = Q + deg = get_degree(Bcube.get_function_space(Q1)) + println( + "deg=", + deg, + " update_cache! : iter=", + integrator.p.counter[1], + " dt=", + integrator.dt, + ) + + q = FEFunction(integrator.p.Q, integrator.u) + limiter_projection && apply_limitation!(q, integrator.p) + return nothing +end + +function output_vtk(integrator) + u_modified!(integrator, false) + mesh = get_mesh(get_domain(integrator.p.params.dΩ)) + q = FEFunction(integrator.p.Q, integrator.u) + counter = integrator.p.counter + counter .+= 1 + if (counter[1] % nout == 0) + println("output_vtk ", counter[1]) + append_vtk(integrator.p.vtk, mesh, q, integrator.t, integrator.p.params) + end + return nothing +end + +function condition_steadystate(integrator, abstol, reltol, min_t) + u_modified!(integrator, false) + if DiffEqBase.isinplace(integrator.sol.prob) + testval = first(get_tmp_cache(integrator)) + @. testval = (integrator.u - integrator.uprev) / (integrator.t - integrator.tprev) + else + testval = (integrator.u - integrator.uprev) / (integrator.t - integrator.tprev) + end + + if typeof(integrator.u) <: Array + any( + abs(d) > abstol && abs(d) > reltol * abs(u) for (d, abstol, reltol, u) in + zip(testval, Iterators.cycle(abstol), Iterators.cycle(reltol), integrator.u) + ) && (return false) + else + any((abs.(testval) .> abstol) .& (abs.(testval) .> reltol .* abs.(integrator.u))) && + (return false) + end + + if min_t === nothing + return true + else + return integrator.t >= min_t + end +end + +""" +Compute the characteristic dimension of each cell of `mesh`: +dimcar = (cell volume) / (cell surface) + +# TODO : +to be moved to Bcube +""" +function compute_dimcar(mesh) + fs = FunctionSpace(:Lagrange, 0) + V = TestFESpace(fs, mesh; size = 1, isContinuous = false) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), degquad) + dΓ = Measure(InteriorFaceDomain(mesh), degquad) + dΓ_bc = Measure(BoundaryFaceDomain(mesh), degquad) + + f1 = PhysicalFunction(x -> 1.0) + l(v) = ∫(f1 ⋅ v)dΩ + l_face(v, dω) = ∫(side⁻(f1) ⋅ side⁻(v) + side⁺(f1) ⋅ side⁺(v))dω + + vol = assemble_linear(l, V) + surf = assemble_linear(Base.Fix2(l_face, dΓ), V) + surf += assemble_linear(Base.Fix2(l_face, dΓ_bc), V) + return vol ./ surf +end + +""" +References: +* Xiangxiong Zhang, Chi-Wang Shu, On positivity-preserving high order discontinuous + Galerkin schemes for compressible Euler equations on rectangular meshes, + Journal of Computational Physics, Volume 229, Issue 23, 2010. + https://doi.org/10.1016/j.jcp.2010.08.016 +* Zhang, X., Xia, Y. & Shu, CW. Maximum-Principle-Satisfying and Positivity-Preserving + High Order Discontinuous Galerkin Schemes for Conservation Laws on Triangular Meshes. + J Sci Comput 50, 29–62 (2012). https://doi.org/10.1007/s10915-011-9472-8 +""" +function apply_limitation!(q::Bcube.AbstractFEFunction, ode_params) + params = ode_params.params + cache = ode_params.cache + mesh = get_mesh(get_domain(params.dΩ)) + ρ, ρu, ρE = q + + ρ_mean, ρu_mean, ρE_mean = Bcube.cell_mean(q, cache.cacheCellMean) + + _limρ, ρ_proj = linear_scaling_limiter( + ρ, + params.dΩ; + bounds = (ρmin₀, ρmax₀), + DMPrelax = params.DMPrelax, + mass = cache.mass_sca, + ) + + op_t = limiter_param_p ∘ (ρ_proj, ρu, ρE, ρ_mean, ρu_mean, ρE_mean) + t = Bcube._minmax_cells(op_t, mesh, Val(params.degquad)) + tmin = Bcube.MeshCellData(getindex.(t, 1)) + + if eltype(_limρ) == eltype(params.limρ) # skip Dual number case + set_values!(params.limρ, get_values(_limρ)) + set_values!(params.limAll, get_values(tmin)) + end + + limited_var(u, ū, lim_u) = ū + lim_u * (u - ū) + projection_l2!(ρ, limited_var(ρ_proj, ρ_mean, tmin), params.dΩ; mass = cache.mass_sca) + projection_l2!(ρu, limited_var(ρu, ρu_mean, tmin), params.dΩ; mass = cache.mass_vec) + projection_l2!(ρE, limited_var(ρE, ρE_mean, tmin), params.dΩ; mass = cache.mass_sca) + return nothing +end + +function limiter_param_p(ρ̂, ρu, ρE, ρ_mean, ρu_mean, ρE_mean) + γ = stateInit.γ + p = pressure(ρ̂, ρu, ρE, γ) + + if p ≥ pmin₀ + t = 1.0 + else + @show p, ρ̂, ρu, ρE + @show ρ_mean, ρu_mean, ρE_mean + @show pressure(ρ_mean, ρu_mean, ρE_mean, γ) + if pressure(ρ_mean, ρu_mean, ρE_mean, γ) > pmin₀ + fₜ = + t -> + pressure( + t * ρ̂ + (1 - t) * ρ_mean, + t * ρu + (1 - t) * ρu_mean, + t * ρE + (1 - t) * ρE_mean, + γ, + ) - pmin₀ + bounds = (0.0, 1.0) + t = find_zero(fₜ, bounds, Bisection()) + else + t = NaN + println("t = NaN") + end + end + + return t +end + +function pressure(ρ::Number, ρu::AbstractVector, ρE::Number, γ) + vel = ρu ./ ρ + ρuu = ρu * transpose(vel) + p = (γ - 1) * (ρE - tr(ρuu) / 2) + return p +end + +compute_Pᵢ(P, γ, M) = P * (1 + 0.5 * (γ - 1) * M^2)^(γ / (γ - 1)) +compute_Tᵢ(T, γ, M) = T * (1 + 0.5 * (γ - 1) * M^2) + +function bc_state_farfield(AoA, M, P, T, r, γ) + a = √(γ * r * T) + vn = M * a + ρ = P / r / T + ρu = SA[ρ * vn * cos(AoA), ρ * vn * sin(AoA)] + ρE = P / (γ - 1) + 0.5 * ρ * vn^2 + return (ρ, ρu, ρE) +end + +function pressure_coefficient(ρ, ρu, ρE) + (pressure(ρ, ρu, ρE, stateInit.γ) - stateInit.P_inf) / + (stateBcFarfield.Pᵢ_inf - stateInit.P_inf) +end +function mach(ρ, ρu, ρE) + norm(ρu ./ ρ) / √(stateInit.γ * max(0.0, pressure(ρ, ρu, ρE, stateInit.γ) / ρ)) +end + +const degreemax = 2 # Function-space degree +const mesh_degree = 2 +const fspace = :Lagrange +const limiter_projection = true +const ρmin₀ = 1.0e-8 +const ρmax₀ = 1.0e+10 +const pmin₀ = 1.0e-8 +const pmax₀ = 1.0e+10 +const DMPcurv₀ = 10.0e3 +const withbench = false + +const stateInit = ( + AoA = deg2rad(1.25), + M_inf = 0.8, + P_inf = 101325.0, + T_inf = 275.0, + r_gas = 287.0, + γ = 1.4, +) +const nite_max = 300 #300000 # Number of time iteration(s) +const nout = 1 # number of step between two vtk outputs +const mass_matrix_in_solve = true +const degquad = 6 +const outputpath = string(@__DIR__, "/../myout/euler_naca_steady/") + +const stateBcFarfield = ( + AoA = stateInit.AoA, + M_inf = stateInit.M_inf, + Pᵢ_inf = compute_Pᵢ(stateInit.P_inf, stateInit.γ, stateInit.M_inf), + Tᵢ_inf = compute_Tᵢ(stateInit.T_inf, stateInit.γ, stateInit.M_inf), + u_inf = bc_state_farfield( + stateInit.AoA, + stateInit.M_inf, + stateInit.P_inf, + stateInit.T_inf, + stateInit.r_gas, + stateInit.γ, + ), + r_gas = stateInit.r_gas, + γ = stateInit.γ, +) + +main(stateInit, stateBcFarfield, degreemax) + +end #hide diff --git a/src/example/linear_elasticity.jl b/src/example/linear_elasticity.jl new file mode 100644 index 0000000..0d88835 --- /dev/null +++ b/src/example/linear_elasticity.jl @@ -0,0 +1,188 @@ +module linear_elasticity #hide +println("Running linear elasticity API example...") #hide + +# # Linear elasticity + +const dir = string(@__DIR__, "/") # bcube/example dir +using Bcube +using LinearAlgebra +using WriteVTK +using StaticArrays + +# function space (here we shall use Lagrange P1 elements) and quadrature degree. +const fspace = :Lagrange +const degree = 1 # FunctionSpace degree +const degquad = 2 * degree + 1 + +# Input and output paths +const outputpath = dir * "../myout/elasticity/" +const meshpath = dir * "../input/mesh/domainElast_tri.msh" + +# Time stepping scheme params +const α = 0.05 +const γ = 0.5 + α +const β = 0.25 * (1.0 + α)^2 + +# Material parameters (Young's modulus, Poisson coefficient and deduced Lamé coefficients) +const ρ = 2500.0 +const E = 200.0e9 +const ν = 0.3 +const λ = E * ν / ((1.0 + ν) * (1.0 - 2.0 * ν)) +const μ = E / (2.0 * (1.0 + ν)) + +# Strain tensor and stress tensor (Hooke's law) +ϵ(u) = 0.5 * (∇(u) + transpose(∇(u))) +σ(u) = λ * tr(ϵ(u)) * I + 2 * μ * ϵ(u) + +π(u, v) = σ(u) ⊡ ϵ(v) # with the chosen contraction convention ϵ should be transposed, but as it is symmetric the expression remains correct + +# materialize for identity operator +Bcube.materialize(A::LinearAlgebra.UniformScaling, B) = A + +# Function that runs the steady case: +function run_steady() + # read mesh, the second argument specifies the spatial dimension + mesh = read_msh(meshpath, 2) + + fs = FunctionSpace(fspace, degree) + U_vec = TrialFESpace( + fs, + mesh, + Dict("West" => SA[0.0, 0.0], "East" => SA[1.0, 0.0]); + size = 2, + ) + V_vec = TestFESpace(U_vec) + + # Define measures for cell + dΩ = Measure(CellDomain(mesh), degquad) + + # no volume force term + f = PhysicalFunction(x -> SA[0.0, 0.0]) + + # definition of bilinear and linear forms + a(u, v) = ∫(π(u, v))dΩ + l(v) = ∫(f ⋅ v)dΩ + + # solve using AffineFESystem + sys = Bcube.AffineFESystem(a, l, U_vec, V_vec) + ϕ = Bcube.solve(sys) + + Un = var_on_vertices(ϕ, mesh) + # Write the obtained FE solution + dict_vars = Dict("Displacement" => (transpose(Un), VTKPointData())) + mkpath(outputpath) + write_vtk(outputpath * "result_elasticity", itime, t, mesh, dict_vars; append = false) +end + +# Function that performs a time step using a Newmark α-HHT scheme +# The scheme updates the acceleration G, the velocity V and the displacement U using the following formulas: +# +# M G +(1-α)A U + αA U0 = (1-α) L + α L0 = L (because here L is time independent) +# V = V0 + (1-γ) Δt G0 + γ Δt G +# U = U0 + Δt V0 + (0.5-β)*Δt^2 G0 + β Δt^2 G +# +# G is then computed by solving the linear system obtained by inserting the expressions for U and V in the equation for G. +function Newmark_α_HHT(dt, L, A, Mat, U0, V0, G0) + L1 = L - α * A * U0 + L2 = -(1.0 - α) * (A * U0 + dt * A * V0 + (0.5 - β) * dt * dt * A * G0) + RHS = L1 .+ L2 + + G = Mat \ RHS + V = V0 + (1.0 - γ) * dt * G0 + γ * dt * G + U = U0 + dt * V0 + (0.5 - β) * dt * dt * G0 + β * dt * dt * G + + return U, V, G +end + +# Function that runs the unsteady case: +function run_unsteady() + # read mesh, the second argument specifies the spatial dimension + mesh = read_msh(meshpath, 2) + + fs = FunctionSpace(fspace, degree) + U_vec = TrialFESpace(fs, mesh, Dict("West" => SA[0.0, 0.0]); size = 2) + V_vec = TestFESpace(U_vec) + + # Define measures for cell + dΩ = Measure(CellDomain(mesh), degquad) + Γ = BoundaryFaceDomain(mesh, ("East",)) + dΓ = Measure(Γ, degquad) + + # surface force to be applied on East boundary + f = PhysicalFunction(x -> SA[100000.0, 1000.0]) + + # Definition of bilinear and linear forms + a(u, v) = ∫(π(u, v))dΩ + m(u, v) = ∫(ρ * u ⋅ v)dΩ + l(v) = ∫(side⁻(f) ⋅ side⁻(v))dΓ + + # Assemble matrices and vector + M = assemble_bilinear(m, U_vec, V_vec) + A = assemble_bilinear(a, U_vec, V_vec) + L = assemble_linear(l, V_vec) + + # Apply homogeneous dirichlet on A and b + Bcube.apply_homogeneous_dirichlet_to_vector!(L, U_vec, V_vec, mesh) + Bcube.apply_dirichlet_to_matrix!((A, M), U_vec, V_vec, mesh) + + # Initialize solution + ϕ = FEFunction(U_vec, 0.0) + U0 = zeros(Bcube.get_ndofs(U_vec)) + V0 = zeros(Bcube.get_ndofs(U_vec)) + G0 = zeros(Bcube.get_ndofs(U_vec)) + + # Write initial solution + Un = var_on_vertices(ϕ, mesh) + # Write the obtained FE solution + dict_vars = Dict("Displacement" => (transpose(Un), VTKPointData())) + mkpath(outputpath) + write_vtk(outputpath * "result_elasticity", 0, 0.0, mesh, dict_vars; append = false) + + # Time loop + totalTime = 1.0e-3 + Δt = 1.0e-6 + itime = 0 + t = 0.0 + + # Matrix for time stepping + Mat = factorize(M + (1.0 - α) * (β * Δt * Δt * A)) + + while t <= totalTime + t += Δt + itime = itime + 1 + @show t, itime + + # solve time step + U, V, G = Newmark_α_HHT(Δt, L, A, Mat, U0, V0, G0) + + # Update solution + U0 .= U + V0 .= V + G0 .= G + + set_dof_values!(ϕ, U) + + # Write solution + if itime % 10 == 0 + Un = var_on_vertices(ϕ, mesh) + # Write the obtained FE solution + dict_vars = Dict("Displacement" => (transpose(Un), VTKPointData())) + write_vtk( + outputpath * "result_elasticity", + itime, + t, + mesh, + dict_vars; + append = true, + ) + # In order to use the warp function in paraview (solid is deformed using the displacement field) + # the calculator filter has to be used with the following formula to reconstruct a 3D displacement field + # with 0 z-component: Displacement_X*iHat+Displacement_Y*jHat+0.0*kHat + end + end +end + +#run_steady() +run_unsteady() + +end #hide diff --git a/src/example/linear_thermoelasticity.jl b/src/example/linear_thermoelasticity.jl new file mode 100644 index 0000000..45f65a7 --- /dev/null +++ b/src/example/linear_thermoelasticity.jl @@ -0,0 +1,224 @@ +module linear_thermo_elasticity #hide +println("Running linear thermo-elasticity API example...") #hide + +# # Thermo-elasticity + +const dir = string(@__DIR__, "/") # Bcube dir +using Bcube +using LinearAlgebra +using WriteVTK +using StaticArrays + +# function space (here we shall use Lagrange P1 elements) and quadrature degree. +const fspace = :Lagrange +const degree = 1 # FunctionSpace degree +const degquad = 2 * degree + 1 + +# Input and output paths +const outputpath = joinpath(dir, "../myout/elasticity/") +const meshpath = joinpath(dir, "../input/mesh/domainThermoElast_tri.msh") + +# Time stepping scheme params +const α = 0.05 +const γ = 0.5 + α +const β = 0.25 * (1.0 + α)^2 + +const totalTime = 10.0 +const Δt = 1.0e-2 + +# Material parameters (Young's modulus, Poisson coefficient and deduced Lamé coefficients) +const E = 200.0e9 +const ν = 0.3 +const λ = E * ν / ((1.0 + ν) * (1.0 - 2.0 * ν)) +const μ = E / (2.0 * (1.0 + ν)) +const Kₜ = 1.0e-6 +const ρ = 2500.0 +const cₚ = 1000.0 +const k = 250.0 +const T₀ = 280.0 + +# Strain tensor and stress tensor (Hooke's law) +ϵ(u) = 0.5 * (∇(u) + transpose(∇(u))) +σ(u) = λ * tr(ϵ(u)) * I + 2 * μ * (ϵ(u)) # Elastic stress +σₜ(T) = (3 * λ + 2 * μ) * Kₜ * (T - T₀) * I # Thermal stress + +π(u, v) = σ(u) ⊡ ϵ(v) # with the chosen contraction convention ϵ should be transposed, but as it is symmetric the expression remains correct +πₜ(T, v) = σₜ(T) ⊡ ϵ(v) + +# materialize for identity operator +Bcube.materialize(A::LinearAlgebra.UniformScaling, B) = A + +# Function that performs a time step using a Newmark α-HHT scheme +# The scheme updates the acceleration G, the velocity V and the displacement U using the following formulas: +# ```math +# \begin{cases} +# M G^{n+1} +(1-\alpha)A U^{n+1} + \alpha A U^{n} = (1-\alpha) L^{n+1} + \alpha L^n = L \textrm{(because here $L$ is time independent)} \\ +# V^{n+1} = V^{n} + (1-\gamma) \Delta t G^n + \gamma \Delta t G^{n+1} \\ +# U^{n+1} = U^{n} + \Delta t V^{n} + (\frac{1}{2} - \beta)*\Delta t^2 G^{n} + \beta \Delta t^2 G^{n+1} +# \end{cases} +# ``` +# where $$M$$ is the mass matrix, $$A$$ is the stiffness matrix and $$L$$ is the RHS +# G is then computed by solving the linear system obtained by inserting the expressions for U and V in the equation for G. +function Newmark_α_HHT(dt, L, A, Mat, U0, V0, G0) + L1 = L - α * A * U0 + L2 = -(1.0 - α) * (A * U0 + dt * A * V0 + (0.5 - β) * dt * dt * A * G0) + RHS = L1 .+ L2 + + G = Mat \ RHS + U = U0 + dt * V0 + (0.5 - β) * dt * dt * G0 + β * dt * dt * G + V = V0 + (1.0 - γ) * dt * G0 + γ * dt * G + + return U, V, G +end + +# Function that runs the unsteady case: +function run_unsteady() + mesh = read_msh(meshpath, 2) + + fs = FunctionSpace(fspace, degree) + U_scal = TrialFESpace(fs, mesh, Dict("West1" => 280.0, "East1" => 280.0); size = 1) + V_scal = TestFESpace(U_scal) + U_vec = TrialFESpace( + fs, + mesh, + Dict("West1" => SA[0.0, 0.0], "East1" => SA[0.0, 0.0]); + size = 2, + ) + V_vec = TestFESpace(U_vec) + + # Initialize solution + U = FEFunction(U_vec, 0.0) + U0 = zeros(Bcube.get_ndofs(U_vec)) + V0 = zeros(Bcube.get_ndofs(U_vec)) + G0 = zeros(Bcube.get_ndofs(U_vec)) + + T = FEFunction(U_scal, T₀) + + # Define measures for cell + dΩ = Measure(CellDomain(mesh), degquad) + + # no volume force term + f = PhysicalFunction(x -> SA[0.0, 0.0]) + + q = PhysicalFunction( + x -> x[1] .* (1.0 .- x[1]) .* x[2] .* (0.2 .- x[2]) .* 1500000000.0, + ) + + # Definition of bilinear and linear forms for the elasticity problem + a(u, v) = ∫(π(u, v))dΩ + m(u, v) = ∫(ρ * u ⋅ v)dΩ + l(v) = ∫(πₜ(T, v))dΩ + + # An alternative way to define this linear form is to use operator composition: + # l(v) = ∫( πₜ ∘ (T, v, ∇(v)) )dΩ + # where πₜ(T, v, ∇v) = σₜ(T) ⊡ ϵ(v, ∇v) and ϵ(v, ∇v) = 0.5*( ∇v + transpose(∇v) ) + + # Definition of bilinear and linear forms for the heat conduction problem + aₜ(u, v) = ∫(k * ∇(u) ⋅ ∇(v))dΩ + mₜ(u, v) = ∫(ρ * cₚ * u ⋅ v)dΩ + lₜ(v) = ∫(q * v)dΩ + + # Assemble matrices and vector + M = assemble_bilinear(m, U_vec, V_vec) + A = assemble_bilinear(a, U_vec, V_vec) + L = assemble_linear(l, V_vec) + AT = assemble_bilinear(aₜ, U_scal, V_scal) + MT = assemble_bilinear(mₜ, U_scal, V_scal) + LT = assemble_linear(lₜ, V_scal) + + # Apply homogeneous dirichlet on A and b + Bcube.apply_homogeneous_dirichlet_to_vector!(L, U_vec, V_vec, mesh) + Bcube.apply_dirichlet_to_matrix!((A, M), U_vec, V_vec, mesh) + + # Compute a vector of dofs whose values are zeros everywhere + # except on dofs lying on a Dirichlet boundary, where they + # take the Dirichlet value + Td = Bcube.assemble_dirichlet_vector(U_scal, V_scal, mesh) + + # Apply lift + LT = LT - AT * Td + + # Apply homogeneous dirichlet condition + Bcube.apply_homogeneous_dirichlet_to_vector!(LT, U_scal, V_scal, mesh) + Bcube.apply_dirichlet_to_matrix!((AT, MT), U_scal, V_scal, mesh) + + # Write initial solution + Un = var_on_vertices(U, mesh) + Un = transpose(Un) + Tn = var_on_vertices(T, mesh) + mkpath(outputpath) + dict_vars = + Dict("Displacement" => (Un, VTKPointData()), "Temperature" => (Tn, VTKPointData())) + # Write the obtained FE solution + write_vtk( + outputpath * "result_thermoelasticity", + 0, + 0.0, + mesh, + dict_vars; + append = false, + ) + + # Time loop + itime = 0 + t = 0.0 + + # Matrix for time stepping + Mat = factorize(M + (1.0 - α) * (β * Δt * Δt * A)) + Miter = factorize(MT + Δt * AT) + + while t <= totalTime + t += Δt + itime = itime + 1 + @show t, itime + + # solve time step heat equation + rhs = Δt * LT + MT * (get_dof_values(T) .- Td) + set_dof_values!(T, Miter \ rhs .+ Td) + + # solve time step elasticity + U1, V1, G1 = Newmark_α_HHT(Δt, L, A, Mat, U0, V0, G0) + + # Update solution + U0 .= U1 + V0 .= V1 + G0 .= G1 + + set_dof_values!(U, U1) + L = assemble_linear(l, V_vec) + Bcube.apply_homogeneous_dirichlet_to_vector!(L, U_vec, V_vec, mesh) + + # Write solution + if itime % 10 == 0 + Un = var_on_vertices(U, mesh) + Un = transpose(Un) + Tn = var_on_vertices(T, mesh) + mkpath(outputpath) + dict_vars = Dict( + "Displacement" => (Un, VTKPointData()), + "Temperature" => (Tn, VTKPointData()), + ) + # Write the obtained FE solution + write_vtk( + outputpath * "result_thermoelasticity", + itime, + t, + mesh, + dict_vars; + append = true, + ) + # In order to use the warp function in paraview (solid is deformed using the displacement field) + # the calculator filter has to be used with the following formula to reconstruct a 3D displacement field + # with 0 z-component: Displacement_X*iHat+Displacement_Y*jHat+0.0*kHat + end + end +end + +run_unsteady() + +# Here is an animation of the obtained result: +# ```@raw html +# drawing +# ``` + +end #hide diff --git a/src/example/non_API/heat_equation.jl b/src/example/non_API/heat_equation.jl new file mode 100644 index 0000000..b7c52d1 --- /dev/null +++ b/src/example/non_API/heat_equation.jl @@ -0,0 +1,194 @@ +module HeatEquation #hide +println("Running heat equation example...") #hide + +# # Poisson +# # Theory +# Consider a square domain $$\Omega$$ on which we wish to solve the heat equation: +# ```math +# \partial_t u - \Delta u = f +# ``` +# This equation represents for instance transient heat conduction with a given source term $$f$$ and a thermal conductivity of $$1 \, W.K^{-1}.m^{-1}$$. +# For the problem to be well-posed, boundary conditions also have to be specified. +# The "West" boundary will be noted $$\Gamma_w$$ and the "East" boundary will be noted $$\Gamma_e$$. +# We shall consider here two cases: +# +# (1) Homogeneous Dirichlet condition ($$u=0$$) on $$\Gamma_w$$ and homogeneous Neumann ($$\nabla u.n = 0$$) on the rest of $$\partial \Omega$$. +# +# (2) Homogeneous Dirichlet condition ($$u=0$$) on $$\Gamma_w$$ and non-homogeneous Dirichlet on $$\Gamma_e$$ +# +# +# We shall assume that $$f \in L^2(\Omega)$$. The weak form of the problem is given by: find $$ u \in \tilde{H}^1_0(\Omega)$$ +# 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. + +const dir = string(@__DIR__, "/../../") # Bcube directory +include(dir * "src/Bcube.jl") +using .Bcube +using LinearAlgebra +using WriteVTK +using Printf + +# Read mesh +mesh = read_msh(dir * "input/mesh/domainSquare_tri.msh", 2) + +# Function space +fs = FunctionSpace(:Lagrange, 2) + +# Create a `Variable` +u = CellVariable(:u, mesh, FESpace(fs, :continuous)) + +# Allocate the problem matrices and RHS +nd = ndofs(u) +M = zeros(Float64, (nd, nd)) +A = zeros(Float64, (nd, nd)) +L = zeros(Float64, (nd)) + +function assemble!(mesh, u, M, A, L) + ## Get connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Retrieve function space + fs = function_space(u) + + ## Compute needed quadrature orders + orderM = Val(2 * get_order(fs) + 1) + orderA = Val(2 * (get_order(fs) - 1) + 1) + orderL = Val(get_order(fs) + 1) + + ## Loop on cells + for icell in 1:ncells(mesh) + ## Alias for cell type + ct = cellTypes[icell] + + ## Alias for nodes + n = get_nodes(mesh, c2n[icell]) + + ## Corresponding shape + s = shape(ct) + + ## Get shape functions in reference element + λ = shape_functions(fs, s) + + ## Get gradient (in the local element) of shape functions + ∇λ = grad_shape_functions(fs, ct, n) + + ## Loop over cell dofs to fill "stiffness" matrix A and RHS L + for i in 1:ndofs(fs, s) + for j in 1:ndofs(fs, s) + A[dof(u, icell, 1, i), dof(u, icell, 1, j)] += + integrate_ref(ξ -> ∇λ(ξ)[i, :] ⋅ ∇λ(ξ)[j, :], n, ct, orderA) + M[dof(u, icell, 1, i), dof(u, icell, 1, j)] += + integrate_ref(ξ -> λ(ξ)[i] * λ(ξ)[j], n, ct, orderM) + end + L[dof(u, icell, 1, i)] += integrate_ref(ξ -> λ(ξ)[i], n, ct, orderL) + end + end +end + +# Function to get the indices of the dofs located on the boundary (this has meaning for Lagrange elements) +function generateBoundaryDofs!(mesh, u) + + ## Get cell -> node, face -> node and face -> cell connectivities + c2n = connectivities_indices(mesh, :c2n) + f2n = connectivities_indices(mesh, :f2n) + f2c = connectivities_indices(mesh, :f2c) + + ## Cell and face types + cellTypes = cells(mesh) + + ## Dictionary which will contain the list of dofs on a given boundary + bnd_dofs = Dict{String, Vector{Int}}() + + ## Loop on all the boundary of type 'faces' + for tag in keys(mesh.bc_faces) + dof_glob = Vector{Int}() + ## Loop over this boundarie's faces + for kface in boundary_faces(mesh, tag) + ## Neighbor cell + icell = f2c[kface][1] + ctype = cellTypes[icell] + s = shape(ctype) + side = cell_side(ctype, c2n[icell], f2n[kface]) + + ## "Outer" dofs on face `side` (i.e faces vertices) + ivertices = faces2nodes(s)[side] # get local index of vertices on face 'iside' + for ivertex in ivertices + idofs_loc = idof_by_vertex(fs, s)[ivertex] + for idof_loc in idofs_loc + push!(dof_glob, dof(u, icell, 1, idof_loc)) + end + end + + ## Add dofs located on the boundary egde + for idof_loc in idof_by_edge(fs, s)[side] + push!(dof_glob, dof(u, icell, 1, idof_loc)) + end + end + bnd_dofs[boundary_names(mesh, tag)] = unique(dof_glob) + end + return bnd_dofs +end + +# Use this function to fill the matrices: +assemble!(mesh, u, M, A, L) + +# generate boundary dofs: +bnd_dofs = generateBoundaryDofs!(mesh, u) +@show bnd_dofs["West"] +# here a homogeneous Dirichlet boundary condition is applied on "West". The value of u is imposed to 0. +for idof in bnd_dofs["West"] + A[idof, :] .= 0.0 + A[:, idof] .= 0.0 + A[idof, idof] = 1.0 + + M[idof, :] .= 0.0 + M[:, idof] .= 0.0 + M[idof, idof] = 1.0 + + L[idof] = 0.0 +end + +# solve the unsteady problem (implicit Euler scheme) +totalTime = 2.0 +time = 0.0 +dt = 0.01 + +U0 = zeros(Float64, nd) +U1 = zeros(Float64, nd) + +# write initial condition in vtk format +set_values!(u, U1) +values = var_on_nodes(u) + +dict_vars = Dict(@sprintf("Temperature") => (values, VTKPointData())) +write_vtk(dir * "myout/result_heatequation_homogeneousDirichlet", 0, 0.0, mesh, dict_vars) + +itime = 0 +while time <= totalTime + global time = time + dt + global itime = itime + 1 + @show time, itime + U1 = (M + dt * A) \ (dt * L + M * U0) + U0 .= U1 + + if itime % 10 == 0 + # write solution in vtk format + set_values!(u, U1) + values = var_on_nodes(u) + + dict_vars = Dict(@sprintf("Temperature") => (values[:], VTKPointData())) + write_vtk( + dir * "myout/result_heatequation_homogeneousDirichlet", + itime, + time, + mesh, + dict_vars, + ) + end +end + +end #hide diff --git a/src/example/non_API/helmholtz.jl b/src/example/non_API/helmholtz.jl new file mode 100644 index 0000000..4dd6435 --- /dev/null +++ b/src/example/non_API/helmholtz.jl @@ -0,0 +1,233 @@ +module Helmholtz #hide +println("Running Helmholtz example...") #hide +# # Helmholtz +# # Theory +# We consider the following Helmholtz equation, representing for instance the acoustic wave propagation with Neuman boundary condition(s): +# ```math +# \begin{cases} +# \Delta u + \omega^2 u = 0 \\ +# \dfrac{\partial u}{\partial n} = 0 \textrm{ on } \Gamma +# \end{cases} +# ``` +# +# An analytic solution of this equation can be obtained: for a rectangular domain $$\Omega = [0,L_x] \times [0,L_y]$$, +# ```math +# u(x,y) = \cos \left( \frac{k_x \pi}{L_x} x \right) \cos \left( \frac{k_y \pi}{L_y} y \right) \mathrm{~~with~~} k_x,~k_y \in \mathbb{N} +# ``` +# with $$\omega^2 = \pi^2 \left( \dfrac{k_x^2}{L_x^2} + \dfrac{k_y^2}{L_y^2} \right)$$ +# +# Now, both the finite-element method and the discontinuous Galerkin method requires to write the weak form of the problem: +# ```math +# - \int_\Omega \nabla u \cdot \nabla v \mathrm{\,d}\Omega +# + \underbrace{\left[ (\nabla u \cdot n) v \right]_\Gamma}_{=0} + \omega^2 \int_\Omega u v \mathrm{\,d} \Omega = 0 +# ``` +# ```math +# \int_\Omega \nabla u \cdot \nabla v \mathrm{\,d} \Omega = \omega^2 \int_\Omega u v \mathrm{\,d} \Omega +# ``` +# This equation is actually a generalized eigenvalue problem which can be writtin in matrix / linear operator form: +# ```math +# A u = \alpha B u +# ``` +# where +# ```math +# A u = \int_\Omega \nabla u \cdot \nabla v \mathrm{\,d} \Omega,~~ B u = \int_\Omega u v \mathrm{\,d} \Omega,~~ \alpha = \omega^2 +# ``` + +# # Easiest solution +# Load the necessary packages (Bcube is loaded only if not already loaded) +const dir = string(@__DIR__, "/../../") # Bcube directory +include(dir * "src/Bcube.jl") +using .Bcube +using LinearAlgebra +using WriteVTK +using Printf + +# Mesh a rectangular domain with quads. +mesh = rectangle_mesh(21, 21) + +# Next, create a scalar Finite Element Space. The Lagrange polynomial space is used here. The order is set to `1`. +fes = FESpace(FunctionSpace(:Lagrange, 1), :continuous) + +# Create a dof handler that will handle the numbering. +u = CellVariable(:u, mesh, fes) + +# Allocate the problem matrices +nd = ndofs(u) +A = zeros(Float64, (nd, nd)) +B = zeros(Float64, (nd, nd)) + +# Now let's assemble the problem, filling the matrices `A` and `B`. Since the assembly is the +# same for this first example and for the other examples below, we define a function that we +# will be able to reuse. `Bcube` performs all integration in the reference element. However +# working in the reference element is harder than working in the local element. For simple non-curved +# element, such as quads, the local shape functions and their gradient can be obtained using the `shape_functions` +# and `grad_shape_functions`. Then, just use the `∫` to integrate any function in the local element. +function assemble!(mesh, u, A, B) + ## Get connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Retrieve function space + fs = function_space(u) + + ## Compute needed quadrature orders + orderA = Val(2 * (get_order(fs) - 1) + 1) + orderB = Val(2 * get_order(fs) + 1) + + ## Loop on cells + for icell in 1:ncells(mesh) + ## Alias for cell type + ct = cellTypes[icell] + + ## Alias for nodes + n = get_nodes(mesh, c2n[icell]) + + ## Corresponding shape + s = shape(ct) + + ## Get shape functions in reference element + λ = shape_functions(fs, s) + + ## Get gradient (in the local element) of shape functions + ∇λ = grad_shape_functions(fs, ct, n) + + ## Loop over cell dofs + for i in 1:ndofs(fs, s) + for j in 1:ndofs(fs, s) + A[dof(u, icell, 1, i), dof(u, icell, 1, j)] += + integrate_ref(ξ -> ∇λ(ξ)[i, :] ⋅ ∇λ(ξ)[j, :], n, ct, orderA) + B[dof(u, icell, 1, i), dof(u, icell, 1, j)] += + integrate_ref(ξ -> λ(ξ)[i] * λ(ξ)[j], n, ct, orderB) + end + end + end +end + +# Use this function to fill the matrices: +assemble!(mesh, u, A, B) + +# Compute eigen-values and vectors +vp, vecp = eigen(A, B) + +# Display the "first" five eigenvalues: +@show sqrt.(abs.(vp[3:8])) + +# Now we can export the solution. Since we used continuous Lagrange elements, the computed information +# is on nodes but not necessarily organized with the same numbering as the mesh nodes. Hence we will +# use the `vars_on_nodes` interpolation function : given variable names and a vector of values +# for these variables, this function builds a values on nodes> dictionnary. Here our +# variable `Val(:u)` has as many values as the number of eigenvalues so we need to call this function multiple +# time. We will restrict to the first 20 eigenvectors. +nvecs = min(20, nd) +values = zeros(nnodes(mesh), nvecs) +for i in 1:nvecs + set_values!(u, vecp[:, i]) + values[:, i] = var_on_vertices(u) +end + +# To write a VTK file, we need to build a dictionnary linking the variable name with its +# values and type +dict_vars = Dict(@sprintf("u_%02d", i) => (values[:, i], VTKPointData()) for i in 1:nvecs) +write_vtk(dir * "myout/helmholtz_bcube_mesh", 0, 0.0, mesh, dict_vars) + +# And here is the eigenvector corresponding to the 6th eigenvalue: +# ```@raw html +# drawing +# ``` + +# ## Hybrid mesh with Gmsh +# For this example we still solve the Helmholtz equation but importing a mesh +# generated by Gmsh. +# First, import the mesh from the `.msh` file. +mesh = read_msh(dir * "input/mesh/domainSquare_hybrid_2.msh") + +# The mesh looks like this : +# ```@raw html +# drawing +# ``` + +# We can keep the same variable as before, no need to define a new one. We could +# treat this problem with discontinuous Lagrange elements, but this is treated in +# an other example (see linear transport example). +# As previously done, build the dof handler and allocate the matrices +u = CellVariable(:u, mesh, fes) +nd = ndofs(u) +A = zeros(Float64, (nd, nd)) +B = zeros(Float64, (nd, nd)) + +# Now we can call the exact same method to assemble the problem +assemble!(mesh, u, A, B) + +# Compute eigen-values and vectors and show the first five eigenvalues: +vp, vecp = eigen(A, B) +@show sqrt.(abs.(vp[3:8])) + +# Now we can export the solution. Like before, we first need to "interpolate" the eigenvectors +# on the mesh. +nvecs = min(20, nd) +values = zeros(nnodes(mesh), nvecs) +for i in 1:nvecs + set_values!(u, vecp[:, i]) + values[:, i] = var_on_vertices(u) +end +dict_vars = Dict(@sprintf("u_%02d", i) => (values[:, i], VTKPointData()) for i in 1:nvecs) +write_vtk(dir * "myout/helmholtz_gmsh_mesh", 0, 0.0, mesh, dict_vars) + +# You can check that the result is identical to the previous one (except for the mesh): +# ```@raw html +# drawing +# ``` + +# ## Mesh with boundary conditions +# In this part we want to solve the Helmholtz equation on a disk domain, using homogeneous Dirichlet boundary +# condition on the border. + +# We build the mesh of a disk. The circle domain boundary is named "BORDER" +tmp_path = "tmp.msh" +gen_disk_mesh(tmp_path) +mesh = read_msh(tmp_path) +rm(tmp_path) + +# We will need the face to cell, face to nodes and cell to nodes connectivities, we can retrieve them very easily +c2n = connectivities_indices(mesh, :c2n) +f2n = connectivities_indices(mesh, :f2n) +f2c = connectivities_indices(mesh, :f2c) + +# As previously done, build the dof handler and allocate the matrices +u = CellVariable(:u, mesh, fes) +nd = ndofs(u) +A = zeros(Float64, (nd, nd)) +B = zeros(Float64, (nd, nd)) + +# Call the exact same method to assemble the problem +assemble!(mesh, u, A, B) + +# Now we can update the matrices to impose our Dirichlet boundary condition +## Get dofs lying on the boundary condition "BORDER": +bnd_dofs = boundary_dofs(u, "BORDER") + +## Loop over these dofs to impose Dirichlet condition: +for iglob in bnd_dofs + ## Clear matrice entries + A[iglob, :] .= 0.0 + B[iglob, :] .= 0.0 + + ## Set the condition `u = 0` explicitely + A[iglob, iglob] = 1.0 +end + +# Compute eigen-values and vectors and show the first five eigenvalues: +vp, vecp = eigen(A, B) +@show sqrt.(abs.(vp[3:8])) + +# Export the solution +nvecs = min(20, nd) +values = zeros(nnodes(mesh), nvecs) +for i in 1:nvecs + set_values!(u, vecp[:, i]) + values[:, i] = var_on_vertices(u) +end +dict_vars = Dict(@sprintf("u_%02d", i) => (values[:, i], VTKPointData()) for i in 1:nvecs) +write_vtk(dir * "myout/helmholtz_disk_mesh", 0, 0.0, mesh, dict_vars) + +end #hide diff --git a/src/example/non_API/linear_transport.jl b/src/example/non_API/linear_transport.jl new file mode 100644 index 0000000..e19ced7 --- /dev/null +++ b/src/example/non_API/linear_transport.jl @@ -0,0 +1,403 @@ +module LinearTransport #hide +println("Running linear transport example...") #hide +# # Linear transport +# ## Theory +# In this example, we solve the following linear transport equation using discontinuous elements: +# ```math +# \frac{\partial \phi}{\partial t} + \nabla \cdot (c \phi) = 0 +# ``` +# where ``c`` is a constant velocity. Using an explicit time scheme, one obtains: +# ```math +# \phi^{n+1} = \phi^n - \Delta t \nabla \cdot (c \phi^n) +# ``` +# The corresponding weak form of this equation is: +# ```math +# \int_\Omega \phi^{n+1} v \mathrm{\,d}\Omega = \int_\Omega \phi^n v \mathrm{\,d}\Omega + \Delta t \left[ +# \int_\Omega c \phi^n \cdot \nabla v \mathrm{\,d}\Omega - \oint_\Gamma \left( c \phi \cdot n \right) v \mathrm{\,d}\Gamma +# \right] +# ``` +# where ``\Gamma = \delta \Omega``. Adopting the discontinuous Galerkin framework, this equation is written in every mesh cell +# ``\Omega_i``. The cell boundary term involves discontinuous quantities and is replaced by a "numerical flux", +# leading to the expression: +# ```math +# \int_{\Omega_i} \phi^{n+1} v \mathrm{\,d}\Omega_i = \int_{\Omega_i} \phi^n v \mathrm{\,d}\Omega_i + \Delta t \left[ +# \int_{\Omega_i} c \phi^n \cdot \nabla v \mathrm{\,d}\Omega_i - \oint_{\Gamma_i} F^*(\phi) v \mathrm{\,d} \Gamma_i +# \right] +# ``` +# For this example, an upwind flux will be used for ``F^*``. Using a matrix formulation, the above equation can be written as: +# ```math +# \phi^{n+1} = \phi^n + M^{-1}(f_\Omega - f_\Gamma) +# ``` +# where ``M^{-1}`` is the inverse of the mass matrix, ``f_\Omega`` the volumic flux term and ``f_\Gamma`` the surfacic flux term. +# +# ## Solution with Bcube +# Start by importing the necessary packages: +# Load the necessary packages (Bcube is loaded only if not already loaded) +const dir = string(@__DIR__, "/../../") # Bcube dir +include(dir * "src/Bcube.jl") +using .Bcube +using LinearAlgebra +using WriteVTK +using StaticArrays +using BenchmarkTools + +# Basically, we need three methods to solve the problem : one to compute the flux terms (looping on the mesh faces), one to assemble +# the problem (volumic + surfacic terms) and one to step forward in time. Before defining these three methods, let's define auxiliary +# ones that help improving the code readability. First we define our numerical flux function, the updwind flux: +""" + Upwind flux. Face normal oriented from cell i to cell j. + Here `c` is the constant convection velocity. +""" +function upwind(ϕᵢ, ϕⱼ, c, nᵢⱼ) + vij = c ⋅ nᵢⱼ + if vij > zero(vij) + vij * ϕᵢ + else + vij * ϕⱼ + end +end + +# Then we define two methods to build the inverse of the mass matrix in each mesh cell. For this problem, the mass matrix +# are time-independant, so we will compute them only once. + +""" + Inverse of the mass matrix in a given cell. +""" +function inv_mass_matrix(λ, cnodes, ct, order) + M = integrate_ref(ξ -> ⊗(λ(ξ)), cnodes, ct, order) + return inv(M) +end + +""" + Build the inverse of mass matrix for all mesh cells + @TODO : use projection.L2_projector +""" +function build_mass_matrix_inv(mesh, fs, params) + ## Get cell -> node connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Alias for some params (optionnal) + order = params.order + + ## Alias for quadrature orders + orderM = Val(2 * order + 1) + + ## Allocate + inv_matrix = [ + zeros(ntuple(i -> ndofs(fs, shape(cellTypes[icell])), 2)) for + icell in 1:ncells(mesh) + ] + + ## Assemble + for icell in 1:ncells(mesh) + + ## Alias for cell type + ct = cellTypes[icell] + + ## Alias for nodes + n = get_nodes(mesh, c2n[icell]) + + ## Get shape functions + λ = shape_functions(fs, shape(ct)) + + ## Compute inverse of mass matrix + inv_matrix[icell] .= inv_mass_matrix(λ, n, ct, orderM) + end + return inv_matrix +end + +# Now that we have these two methods in hand, we can write the main methods. Let's start with the one computing the flux on +# each mesh face. For inner faces, the flux is computed and used for both neighbor cells. For outter (=boundary) faces, +# a boundary-condition-dependant treatment is applied. We will see later how to define boundary conditions. Note the use +# of the `interpolate` function that builds the interpolating function ``\tilde{\phi} = \sum \phi_i \lambda_i`` using the values +# of ``\phi`` on the degree of freedom. + +function compute_flux(mesh, ϕ, params, q, t) + ## Allocate + ## This is just the the demo -> for performance, this vector should be allocated + ## outside of this function and given as an input + f = zeros(ndofs(ϕ)) + + ## Alias + c = params.c + + ## Get cell -> node, face -> node and face -> cell connectivities + c2n = connectivities_indices(mesh, :c2n) + f2n = connectivities_indices(mesh, :f2n) + f2c = connectivities_indices(mesh, :f2c) + + ## Cell and face types + cellTypes = cells(mesh) + faceTypes = faces(mesh) + + ## Function space + fs = function_space(ϕ) + + ## Integration order + order = Val(2 * params.order + 1) # it's a lucky guess since we don't really know the "flux order" + + ## Loop on all the inner faces + for kface in inner_faces(mesh) + ## Face nodes, type and shape + ftype = faceTypes[kface] + fnodes = get_nodes(mesh, f2n[kface]) + + ## Neighbor cell i + i = f2c[kface][1] + xᵢ = get_nodes(mesh, c2n[i]) + ctᵢ = cellTypes[i] + shapeᵢ = shape(ctᵢ) + λᵢ = shape_functions(fs, shape(ctᵢ)) + ϕᵢ = interpolate(λᵢ, q[dof(ϕ, i)]) + sideᵢ = cell_side(ctᵢ, c2n[i], f2n[kface]) + fpᵢ = mapping_face(shapeᵢ, sideᵢ) # for adjacent cell 1, we assume that glob2loc = 1:nnodes(face) + + ## Neighbor cell j + j = f2c[kface][2] + xⱼ = get_nodes(mesh, c2n[j]) + ctⱼ = cellTypes[j] + λⱼ = shape_functions(fs, shape(ctⱼ)) + ϕⱼ = interpolate(λⱼ, q[dof(ϕ, j)]) + shapeⱼ = shape(ctⱼ) + sideⱼ = cell_side(ctⱼ, c2n[j], f2n[kface]) + # This part is a bit tricky : we want the face parametrization (face-ref -> cell-ref) on + # side `j`. For this, we need to know the permutation between the vertices of `kface` and the + # vertices of the `sideⱼ`-th face of cell `j`. However all the information we have for entities, + # namely `fnodes` and `faces2nodes(ctⱼ, sideⱼ)` refer to the nodes, not the vertices. So we need + # to retrieve the number of vertices of the face and then restrict the arrays to these vertices. + # (by the way, we use that the vertices appears necessarily in first) + # We could simplify the expressions below by introducing the notion of "vertex" in Entity, for + # instance with `nvertices` and `faces2vertices`. + nv = length(faces2nodes(shapeⱼ, sideⱼ)) # number of vertices of the face + iglob_vertices_of_face_of_cell_j = + [c2n[j][faces2nodes(ctⱼ, sideⱼ)[l]] for l in 1:nv] + g2l = indexin(f2n[kface][1:nv], iglob_vertices_of_face_of_cell_j) + fpⱼ = mapping_face(shapeⱼ, sideⱼ, g2l) + + ## Flux definition : upwind + fluxn = (nᵢⱼ, ξ) -> upwind(ϕᵢ(fpᵢ(ξ)), ϕⱼ(fpⱼ(ξ)), c, nᵢⱼ) # Warning : 'flux' must contained the scalar product with n + + ## Append flux contribution of face `kface` to cell `i`, performing a surfacic integration + g_ref = ξ -> fluxn(normal(xᵢ, ctᵢ, sideᵢ, ξ), ξ) * λᵢ(fpᵢ(ξ)) + f[dof(ϕ, i)] += integrate_ref(g_ref, fnodes, ftype, order) + + ## Append flux contribution of face `kface` to cell `j`, performing a surfacic integration + g_ref = ξ -> fluxn(-normal(xⱼ, ctⱼ, sideⱼ, ξ), ξ) * λⱼ(fpⱼ(ξ)) + f[dof(ϕ, j)] -= integrate_ref(g_ref, fnodes, ftype, order) + end + + ## Loop on all the boundary of type 'faces' + for tag in keys(mesh.bc_faces) + ## Loop over this boundary faces + for kface in boundary_faces(mesh, tag) + + # Face nodes, type and shape + ftype = faceTypes[kface] + fnodes = get_nodes(mesh, f2n[kface]) + F = mapping(fnodes, ftype) # mapping face-ref coords to local coords + + ## Neighbor cell i + i = f2c[kface][1] + cnodes = get_nodes(mesh, c2n[i]) + ctype = cellTypes[i] + s = shape(ctype) + λ = shape_functions(fs, shape(ctype)) + ϕᵢ = interpolate(λ, q[dof(ϕ, i)]) + side = cell_side(ctype, c2n[i], f2n[kface]) + fp = mapping_face(s, side) # mapping face-ref -> cell-ref + + ## For a multi-variable problem, we should loop over the variables. Here we have only one variable + ## Skip if no boundary condition on this boundary + !haskey(params.cdts, tag) && continue + + ## Get associated boundary condition + cdt = params.cdts[tag] + + ## Flux boundary condition + if type(cdt) == :flux + ## Append flux contribution of face `kface` to cell `i` + g_ref = ξ -> normal(cnodes, ctype, side, ξ) ⋅ apply(cdt, F(ξ), t) * λ(fp(ξ)) + f[dof(ϕ, i)] += integrate_ref(g_ref, fnodes, ftype, order) + + ## Dirichlet boundary condition : we apply classic flux with imposed condition in ghost. We split in + ## three steps to improve clarity, but this can be done in one line. + elseif type(cdt) == :diri + ϕ_bnd = ξ -> apply(cdt, F(ξ), t) # here ξ is in the face-ref-element, so F(ξ) is in the local element + g_ref = + ξ -> + upwind(ϕᵢ(fp(ξ)), ϕ_bnd(ξ), c, normal(cnodes, ctype, side, ξ)) * + λ(fp(ξ)) # fp(ξ) is in the cell-ref-element + f[dof(ϕ, i)] += integrate_ref(g_ref, fnodes, ftype, order) + end + end + end + + ## Result : a vector of size `ndofs` + return f +end + +# Now let's write the assembling method which computes the volumic terms and assembles them with the surfacic flux terms. +# We call this method `explicit_step!` because it returns the ``\Delta \phi`` associated with the selected explicit time +# scheme. + +function explicit_step!(mesh, ϕ, params, dq, q, t, cache) + + ## Get inverse of mass matrix from cache + inv_mass_matrix, = cache + + ## Get cell -> node connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Alias for the function space + fs = function_space(ϕ) + + ## Alias for some parameters + c = params.c + order = params.order + + ## Alias for quadrature orders + orderF = Val(order + order - 1 + 1) + + ## Compute surfacic flux + f = compute_flux(mesh, ϕ, params, q, t) + + ## Assemble + for icell in 1:ncells(mesh) + ## Allocate + F = zeros(ndofs(ϕ, icell)) # Volumic flux + + ## Indices of all the variable dofs in this cell + i_ϕ = dof(ϕ, icell) + + ## Alias for cell type + ctype = cellTypes[icell] + + ## Alias for nodes + cnodes = get_nodes(mesh, c2n[icell]) + + ## Corresponding shape + s = shape(ctype) + + ## Get shape functions in reference element + λ = shape_functions(fs, s) + + ## Get gradient, in the local element, of shape functions + ∇λ = grad_shape_functions(fs, ctype, cnodes) + + ## Create interpolation function + ϕᵢ = interpolate(λ, q[i_ϕ]) + + ## Loop over cell dofs for volumic term + # bmxam : need an additionnal explanation here it should be ∇λ ⋅ (c ϕ) but + # since ∇λ is a matrix, we compute every "dofs" at once + F .= integrate_ref(ξ -> ∇λ(ξ) * (c .* ϕᵢ(ξ)), cnodes, ctype, orderF) + + ## Assemble + dq[i_ϕ] .= inv_mass_matrix[icell] * (F .- f[i_ϕ]) + end +end + +# We are all set to solve a linear transport equation. However, we will add two more things to ease the solution VTK output : a +# structure to store the vtk filename and the number of iteration: +mutable struct VtkHandler + basename::Any + ite::Any + VtkHandler(basename) = new(basename, 0) +end + +# ... and a method to interpolate the discontinuous solution on cell centers and to append it to the VTK file: +function append_vtk(vtk, ϕ, t) + ## Write + dict_vars = Dict("ϕ" => (var_on_centers(ϕ), VTKCellData())) + write_vtk(vtk.basename, vtk.ite, t, mesh, dict_vars; append = vtk.ite > 0) + + ## Update counter + vtk.ite += 1 +end + +# Now let's manipulate everything to solve a linear transport equation. First we define some constants, relative to +# the problem or the mesh : +const order = 0 # Function-space order (Taylor(0) = first order Finite Volume) +const nite = 100 # Number of time iteration(s) +const c = SA[1.0, 0.0] # Convection velocity (must be a vector) +const CFL = 1 # 0.1 for order 1 +const nout = 100 # Number of time steps to save +const nx = 41 # Number of nodes in the x-direction +const ny = 41 # Number of nodes in the y-direction +const lx = 2.0 # Domain width +const ly = 2.0 # Domain height +Δt = CFL * min(lx / nx, ly / ny) / norm(c) # Time step + +# Then generate the mesh of a rectangle using Gmsh and read it +tmp_path = "tmp.msh" +gen_rectangle_mesh(tmp_path, :quad; nx = nx, ny = ny, lx = lx, ly = ly, xc = 0.0, yc = 0.0) +mesh = read_msh(tmp_path) +rm(tmp_path) + +# Create a `Variable` : we choose to use the `Taylor` function space and hence a discontinuous Galerkin framework. +fs = FunctionSpace(:Taylor, order) +fes = FESpace(fs, :discontinuous) + +# Create the degree of freedom handler +ϕ = CellVariable(:ϕ, mesh, fes) + +# Assign boundary conditions. On the west side of the rectangle, we impose an incomming wave : ``c \cos(\lambda_y y) \sin(\omega t)``. +# The wave oscillates in time and along the ``y`` direction. To impose this condition, we choose to directly set the numerical flux +# rather than a ghost value of ``\phi``. This choice is arbitrary and only serves this example. On all the other boundaries, we set +# ``\phi = 0`` using a Dirichlet boundary condition. Note that for a multivariable problem, the keys should be (tag, varname) +g(x, t) = c .* cos(3 * x[2]) * sin(4 * t) * 1.0 # flux +cdt_in = BoundaryCondition(:flux, g) +cdt_out = BoundaryCondition(:diri, (x, t) -> 0.0) +cdts = Dict( + boundary_tag(mesh, "West") => cdt_in, + boundary_tag(mesh, "East") => cdt_out, + boundary_tag(mesh, "North") => cdt_out, + boundary_tag(mesh, "South") => cdt_out, +) + +# Then we create a `NamedTuple` to hold the simulation parameters. +params = (c = c, order = order, cdts = cdts) + +# Let's allocate the unknown vector and set it to zero. Along with this vector, we also allocate the "increment" vector. +q = zeros(ndofs(ϕ)) +dq = zeros(size(q)) +set_values!(ϕ, q) + +# Init vtk handler +vtk = VtkHandler(dir * "myout/linear_transport") + +# Init time +t = 0.0 + +# Save initial solution +append_vtk(vtk, ϕ, t) + +# Build the cache and store everything you want to compute only once (such as the mass matrice inverse...) +cache = (build_mass_matrix_inv(mesh, fs, params),) + +# Let's loop to solve the equation. +for i in 1:nite + ## Infos + println("Iteration ", i) + + ## Step forward in time + explicit_step!(mesh, ϕ, params, dq, q, t, cache) + q .+= Δt * dq + set_values!(ϕ, q) + global t += Δt + + ## Write solution to file (respecting max. number of output) + if (i % Int(max(floor(nite / nout), 1)) == 0) + append_vtk(vtk, ϕ, t) + end +end + +@btime explicit_step!($mesh, $ϕ, $params, $dq, $q, $t, $cache) + +# And here is an animation of the result: +# ```@raw html +# drawing +# ``` +end #hide diff --git a/src/example/non_API/phase_field_supercooled.jl b/src/example/non_API/phase_field_supercooled.jl new file mode 100644 index 0000000..28ae04e --- /dev/null +++ b/src/example/non_API/phase_field_supercooled.jl @@ -0,0 +1,286 @@ +module PhaseFieldSupercooled #hide +println("Running phase field supercooled equation example...") #hide + +# # Phase field model - solidification of a liquid in supercooled state +# # Theory +# This case is taken from: Kobayashi, R. (1993). Modeling and numerical simulations of dendritic crystal growth. Physica D: Nonlinear Phenomena, 63(3-4), 410-423. +# In particular, the variables of the problem are denoted in the same way ($p$ for the phase indicator and $T$ for temperature). +# Consider a rectangular domain $$\Omega = [0, L_x] \times [0, L_y]$$ on which we wish to solve the following equations: +# ```math +# \partial_t p = \epsilon^2 \Delta p + p (1-p)(p - \frac{1}{2} + m(T)) +# ``` +# ```math +# \partial_t T = \Delta T + K \partial_t p +# ``` +# where $m(T) = \frac{\alpha}{\pi} atan \left[ \gamma (T_e - T) \right]$. +# This set of equations represents the solidification of a liquid in a supercooled state. Here $T$ is a dimensionless temperature and $p$ is the solid volume fraction. +# Lagrange finite elements are used to discretize both equations. Time marching is performed with a forward Euler scheme for the first equation and a backward Euler scheme for the second one. +# +# To initiate the solidification process, a Dirichlet boundary condition ($p=1$,$T=1$) is applied at $x=0$ ("West" boundary). + +const dir = string(@__DIR__, "/../../") # Bcube dir +include(dir * "src/Bcube.jl") +using .Bcube +using LinearAlgebra +using WriteVTK +using Printf +using SparseArrays + +const l = 3.0 +const nx = 100 +const ny = 100 +const eps = 0.01 +const tau = 0.0003 +const alp = 0.9 +const gam = 10.0 +const a = 0.01 +const K = 1.6 +const Te = 1.0 + +m = T -> (alp / pi) * atan(gam * (Te - T)) + +# Read mesh +#mesh = rectangle_mesh(nx, ny, xmin = 0, xmax = l, ymin = 0, ymax = l) +mesh = read_msh(dir * "input/mesh/domainPhaseField_tri.msh", 2) +# Function space +fs = FunctionSpace(:Lagrange, 1) + +# Create a `Variable` +fes = FESpace(fs, :continuous) +u = CellVariable(:u, mesh, fes) + +# Allocate the problem matrices and RHS +nd = ndofs(u) + +function assembleRHS!(mesh, u, p, T) + nd = ndofs(u) + LP = zeros(Float64, (nd)) + + ## Get connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Retrieve function space + fs = function_space(u) + + ## Compute needed quadrature orders + orderL = Val(get_order(fs) + 1) + + q = p .* (1.0 .- p) .* (p .- 0.5 .+ m.(T)) + + ## Loop on cells + for icell in 1:ncells(mesh) + ## Alias for cell type + ct = cellTypes[icell] + + i_ϕ = dof(u, icell) + + ## Alias for nodes + n = get_nodes(mesh, c2n[icell]) + ## Corresponding shape + s = shape(ct) + + ## Get shape functions in reference element + λ = shape_functions(fs, s) + + ## Create interpolation function + qsrc = interpolate(λ, q[i_ϕ]) + + ## Loop over cell dofs to fill RHS L + for i in 1:ndofs(fs, s) + LP[dof(u, icell, 1, i)] += integrate_ref(ξ -> λ(ξ)[i] * qsrc(ξ), n, ct, orderL) + end + end + + return LP +end + +function assemble!(mesh, u) + ## Get connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Retrieve function space + fs = function_space(u) + + ## Compute needed quadrature orders + orderM = Val(2 * get_order(fs) + 1) + orderA = Val(2 * (get_order(fs) - 1) + 1) + + ndm = max_ndofs(u) + nhint = ndm * ndm * ncells(mesh) + Mval = Float64[] + rowM = Int[] + colM = Int[] + sizehint!(Mval, nhint) + sizehint!(rowM, nhint) + sizehint!(colM, nhint) + + Aval = Float64[] + rowA = Int[] + colA = Int[] + sizehint!(Aval, nhint) + sizehint!(rowA, nhint) + sizehint!(colA, nhint) + + ## Loop on cells + for icell in 1:ncells(mesh) + ## Alias for cell type + ct = cellTypes[icell] + + ## Alias for nodes + n = get_nodes(mesh, c2n[icell]) + + ## Corresponding shape + s = shape(ct) + + ## Get shape functions in reference element + λ = shape_functions(fs, s) + + ## Get gradient (in the local element) of shape functions + ∇λ = grad_shape_functions(fs, ct, n) + + ## Loop over cell dofs to fill "stiffness" matrix A and RHS L + for i in 1:ndofs(fs, s) + for j in 1:ndofs(fs, s) + push!(Aval, integrate_ref(ξ -> ∇λ(ξ)[i, :] ⋅ ∇λ(ξ)[j, :], n, ct, orderA)) + push!(rowA, dof(u, icell, 1, i)) + push!(colA, dof(u, icell, 1, j)) + + push!(Mval, integrate_ref(ξ -> λ(ξ)[i] * λ(ξ)[j], n, ct, orderM)) + push!(rowM, dof(u, icell, 1, i)) + push!(colM, dof(u, icell, 1, j)) + end + end + end + A = sparse(rowA, colA, Aval) + M = sparse(rowM, colM, Mval) + return M, A +end + +# Function to get the indices of the dofs located on the boundary (this has meaning for Lagrange elements) +function generateBoundaryDofs!(mesh, u) + + ## Get cell -> node, face -> node and face -> cell connectivities + c2n = connectivities_indices(mesh, :c2n) + f2n = connectivities_indices(mesh, :f2n) + f2c = connectivities_indices(mesh, :f2c) + + ## Cell and face types + cellTypes = cells(mesh) + + ## Dictionary which will contain the list of dofs on a given boundary + bnd_dofs = Dict{String, Vector{Int}}() + + ## Loop on all the boundary of type 'faces' + for tag in keys(mesh.bc_faces) + dof_glob = Vector{Int}() + ## Loop over this boundarie's faces + for kface in boundary_faces(mesh, tag) + ## Neighbor cell + icell = f2c[kface][1] + ctype = cellTypes[icell] + s = shape(ctype) + side = cell_side(ctype, c2n[icell], f2n[kface]) + + ## "Outer" dofs on face `side` (i.e faces vertices) + ivertices = faces2nodes(s)[side] # get local index of vertices on face 'iside' + for ivertex in ivertices + idofs_loc = idof_by_vertex(fs, s)[ivertex] + for idof_loc in idofs_loc + push!(dof_glob, dof(u, icell, 1, idof_loc)) + end + end + + ## Add dofs located on the boundary egde + for idof_loc in idof_by_edge(fs, s)[side] + push!(dof_glob, dof(u, icell, 1, idof_loc)) + end + end + bnd_dofs[boundary_names(mesh, tag)] = unique(dof_glob) + end + return bnd_dofs +end + +# Use this function to fill the matrices: +M, A = assemble!(mesh, u) + +time = 0.0 +dt = 0.0001 +totalTime = 1.0 + +T0 = zeros(Float64, nd) +T1 = zeros(Float64, nd) + +p0 = zeros(Float64, nd) +p1 = zeros(Float64, nd) + +qp = zeros(Float64, nd) + +Matp = (M + (dt / tau) * eps * eps * A) +MatT = (M + dt * A) + +# generate boundary dofs: +bnd_dofs = generateBoundaryDofs!(mesh, u) + +# here a Dirichlet boundary condition is applied on "West". p and T are imposed to 1 (solid). +for idof in bnd_dofs["West"] + Matp[idof, :] .= 0.0 + Matp[idof, idof] = 1.0 + MatT[idof, :] .= 0.0 + MatT[idof, idof] = 1.0 + p0[idof] = 1.0 + T0[idof] = 1.0 +end + +# write initial condition in vtk format +set_values!(u, T0) +valuesT = var_on_nodes(u) +set_values!(u, p0) +valuesp = var_on_nodes(u) + +dict_vars = + Dict("Temperature" => (valuesT, VTKPointData()), "phi" => (valuesp, VTKPointData())) +write_vtk(dir * "myout/result_phaseField", 0, 0.0, mesh, dict_vars) + +itime = 0 +while time <= totalTime + global time = time + dt + global itime = itime + 1 + @show time, itime + + #qp[:] .= p0[:]*(1.0 .- p0[:])*(p0[:] .- 0.5 .+ m(T0)[:]) + + LP = assembleRHS!(mesh, u, p0, T0) + RHSP = (M * p0 + (dt / tau) * LP) + for idof in bnd_dofs["West"] + RHSP[idof] = 1.0 + end + + p1 = Matp \ RHSP + + RHST = (M * T0 + K * M * (p1 - p0)) + for idof in bnd_dofs["West"] + RHST[idof] = 1.0 + end + T1 = MatT \ RHST + + p0 .= p1 + T0 .= T1 + + if itime % 100 == 0 + # write solution in vtk format + set_values!(u, T1) + valuesT = var_on_nodes(u) # the result is a dictionnary, but we are only interested in the values + set_values!(u, p1) + valuesp = var_on_nodes(u) + + dict_vars = Dict( + "Temperature" => (valuesT, VTKPointData()), + "phi" => (valuesp, VTKPointData()), + ) + write_vtk(dir * "myout/result_phaseField", itime, time, mesh, dict_vars) + end +end + +end #hide diff --git a/src/example/non_API/poisson.jl b/src/example/non_API/poisson.jl new file mode 100644 index 0000000..d94b1f1 --- /dev/null +++ b/src/example/non_API/poisson.jl @@ -0,0 +1,177 @@ +module Poisson #hide +println("Running Poisson example...") #hide + +# # Poisson +# # Theory +# Consider a square domain $$\Omega$$ on which we wish to solve Poisson's equation: +# ```math +# -\Delta u = f +# ``` +# This equation represents for instance steady state heat conduction with a given source term $$f$$ and a thermal conductivity of $$1 \, W.K^{-1}.m^{-1}$$. +# For the problem to be well-posed, boundary conditions also have to be specified. +# The "West" boundary will be noted $$\Gamma_w$$ and the "East" boundary will be noted $$\Gamma_e$$. +# We shall consider here two cases: +# +# (1) Homogeneous Dirichlet condition ($$u=0$$) on $$\Gamma_w$$ and homogeneous Neumann ($$\nabla u.n = 0$$) on the rest of $$\partial \Omega$$. +# +# (2) Homogeneous Dirichlet condition ($$u=0$$) on $$\Gamma_w$$ and non-homogeneous Dirichlet on $$\Gamma_e$$ +# +# +# We shall assume that $$f \in L^2(\Omega)$$. The weak form of the problem is given by: find $$ u \in \tilde{H}^1_0(\Omega)$$ +# such that: +# ```math +# \forall v \in \tilde{H}^1_0(\Omega), \, \, \, \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. + +const dir = string(@__DIR__, "/../../") # Bcube dir +include(dir * "src/Bcube.jl") +using .Bcube +using LinearAlgebra +using WriteVTK +using Printf + +# Read mesh +mesh = read_msh(dir * "input/mesh/domainSquare_tri.msh", 2) + +# Function space +fs = FunctionSpace(:Lagrange, 2) + +# Create a `Variable` +fes = FESpace(fs, :continuous) +u = CellVariable(:u, mesh, fes) + +# Allocate the problem matrices and RHS +nd = ndofs(u) +A = zeros(Float64, (nd, nd)) +L = zeros(Float64, nd) + +function assemble!(mesh, u, A, L) + ## Get connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Retrieve function space + fs = function_space(u) + + ## Compute needed quadrature orders + orderA = Val(2 * (get_order(fs) - 1) + 1) + orderL = Val(get_order(fs) + 1) + + ## Loop on cells + for icell in 1:ncells(mesh) + ## Alias for cell type + ct = cellTypes[icell] + + ## Alias for nodes + n = get_nodes(mesh, c2n[icell]) + + ## Corresponding shape + s = shape(ct) + + ## Get shape functions in reference element + λ = shape_functions(fs, s) + + ## Get gradient (in the local element) of shape functions + ∇λ = grad_shape_functions(fs, ct, n) + + ## Loop over cell dofs to fill "stiffness" matrix A and RHS L + for i in 1:ndofs(fs, s) + for j in 1:ndofs(fs, s) + A[dof(u, icell, 1, i), dof(u, icell, 1, j)] += + integrate_ref(ξ -> ∇λ(ξ)[i, :] ⋅ ∇λ(ξ)[j, :], n, ct, orderA) + end + L[dof(u, icell, 1, i)] += integrate_ref(ξ -> λ(ξ)[i], n, ct, orderL) + end + end +end + +# Function to get the indices of the dofs located on the boundary (this has meaning for Lagrange elements) +function generateBoundaryDofs!(mesh, u) + + ## Get cell -> node, face -> node and face -> cell connectivities + c2n = connectivities_indices(mesh, :c2n) + f2n = connectivities_indices(mesh, :f2n) + f2c = connectivities_indices(mesh, :f2c) + + ## Cell and face types + cellTypes = cells(mesh) + faceTypes = faces(mesh) + + ## Dictionary which will contain the list of dofs on a given boundary + bnd_dofs = Dict{String, Vector{Int}}() + + ## Loop on all the boundary of type 'faces' + for tag in keys(mesh.bc_faces) + dof_glob = Vector{Int}() + ## Loop over this boundarie's faces + for kface in boundary_faces(mesh, tag) + ## Neighbor cell + icell = f2c[kface][1] + ctype = cellTypes[icell] + s = shape(ctype) + side = cell_side(ctype, c2n[icell], f2n[kface]) + + ## "Outer" dofs on face `side` (i.e faces vertices) + ivertices = faces2nodes(s)[side] # get local index of vertices on face 'iside' + for ivertex in ivertices + idofs_loc = idof_by_vertex(fs, s)[ivertex] + for idof_loc in idofs_loc + push!(dof_glob, dof(u, icell, 1, idof_loc)) + end + end + + ## Add dofs located on the boundary egde + for idof_loc in idof_by_edge(fs, s)[side] + push!(dof_glob, dof(u, icell, 1, idof_loc)) + end + end + bnd_dofs[boundary_names(mesh, tag)] = unique(dof_glob) + end + return bnd_dofs +end + +# Use this function to fill the matrices: +assemble!(mesh, u, A, L) + +# generate boundary dofs: +bnd_dofs = generateBoundaryDofs!(mesh, u) +@show bnd_dofs["West"] +# here a homogeneous Dirichlet boundary condition is applied on "West". The value of u is imposed to 0. +for idof in bnd_dofs["West"] + A[idof, :] .= 0.0 + A[:, idof] .= 0.0 + A[idof, idof] = 1.0 + L[idof] = 0.0 +end + +# solve the linear system +sol = A \ L + +# write solution in vtk format +set_values!(u, sol) +values = var_on_nodes(u) + +dict_vars = Dict(@sprintf("sol") => (values, VTKPointData())) +write_vtk(dir * "myout/result_poisson_homogeneousDirichlet", 0, 0.0, mesh, dict_vars) + +# here we add a non-homogeneous Dirichlet boundary condition on "East" and solve the problem again. The value of u is imposed to 1. +ud = zeros(Float64, nd) +ud[bnd_dofs["East"]] .= 1.0 +L = L - A * ud +for idof in bnd_dofs["East"] + A[idof, :] .= 0.0 + A[:, idof] .= 0.0 + A[idof, idof] = 1.0 + L[idof] = 1.0 +end + +sol = A \ L + +set_values!(u, sol) +values .= var_on_nodes(u) # the result is a dictionnary, but we are only interested in the values + +dict_vars = Dict(@sprintf("sol") => (values, VTKPointData())) +write_vtk(dir * "myout/result_poisson_nonHomogeneousDirichlet", 0, 0.0, mesh, dict_vars) + +end #hide diff --git a/src/example/non_API/shallow_water.jl b/src/example/non_API/shallow_water.jl new file mode 100644 index 0000000..cb9fd9d --- /dev/null +++ b/src/example/non_API/shallow_water.jl @@ -0,0 +1,683 @@ +module ShallowWater #hide +const dir = string(@__DIR__, "/../../") # Bcube dir +include(dir * "src/Bcube.jl") +using .Bcube +using WriteVTK +using LinearAlgebra +using StaticArrays +using ForwardDiff + +# # Shallow water equations +# Following "A conservative Saint-Venant type model to describe the dynamics of thien partially wetting films with +# regularized forces at the contact line". +# The gravity is noted ``g = g_n \vec{e_n} + \vec{g_t}`` (note that ``g_n`` is a scalar while ``g_t`` is a vector). The goal is to solve: +# ```math +# \begin{cases} +# \partial_t \rho h + \nabla \cdot \rho h u = 0 \\ +# \partial_t \rho h u + \nabla \cdot \mathcal{F}_{\rho h u} = h \left( \rho g_t - \nabla P_{gaz} \right) - \tilde{\tau}_{wall} + \tilde{\tau}_{air} +# \end{cases} +# ``` +# +# To simplify a little bit, we assume a constant density. The systems becomes: +# ```math +# \begin{cases} +# \partial_t h + \nabla \cdot h u = 0 \\ +# \partial_t h u + \nabla \cdot \mathcal{F}_{hu} = h \left( g_t - \nabla P_{gaz} \right) - \tau_{wall} + \tau_{air} +# \end{cases} +# ``` +# where +# ```math +# \tau_{wall} = \frac{3 \nu u}{h + b} - \frac{\tau_{air}h}{2(h+b)} +# ``` +# ``b`` being a slip length and +# ```math +# \mathcal{F}_{h u} = h u \otimes u + g_n \frac{h^2}{2} \mathcal{I} + \frac{1}{\rho}\left[h \partial_h e_d(h) - e_d(h) \right] +# - \frac{\gamma_{lg}}{\rho \sqrt{1 + ||\nabla h||^2}} + \frac{1}{\rho} \gamma_{lg} h \kappa +# ``` +# +# ## Explicit time integration +# ```math +# \begin{cases} +# h^{n+1} = h^n - \Delta t \nabla \cdot h u^n \\ +# h u^{n+1} = hu^n - \Delta t \left[ +# \nabla \cdot \mathcal{F}_{hu}(h^n,hu^n) +# - h^n \left( g_t - \nabla P_{gaz} \right) + \tau_{wall}(h^n, hu^n) - \tau_{air} +# \right] +# \end{cases} +# ``` +# +# ## Implicit time integration +# ```math +# \begin{cases} +# h^{n+1} = h^n - \Delta t \nabla \cdot h u^{n+1} \\ +# h u^{n+1} = hu^n - \Delta t \left[ +# \nabla \cdot \mathcal{F}_{hu}(h^{n+1},hu^{n+1}) +# - h^{n+1} \left( g_t - \nabla P_{gaz} \right) + \tau_{wall}(h^{n+1}, hu^{n+1}) - \tau_{air} +# \right] +# \end{cases} +# ``` +# +# ## IMEX time integration (not finished / not relevant) +# The wall friction term, ``\tau_w`` is singular when ``h \rightarrow 0``. To overcome this difficulty, an implicit-explicit (IMEX) +# scheme is used : all terms are integrated explicitely except the wall friction. More precisely, the system is first written: +# ```math +# \begin{cases} +# h^{n+1} = h^n - \Delta t \nabla \cdot h u^n \\ +# h u^{n+1} = hu^n - \Delta t \left[ +# \nabla \cdot \mathcal{F}_{hu}(h^n,hu^n) +# - h^n \left( g_t - \nabla P_{gaz} \right) + \tau_{wall}(h^{n+1}, hu^{n+1}) - \tau_{air} +# \right] +# \end{cases} +# ``` +# At each time step, the mass equation can be solved explicitely independantly from the momentum equation. Besides, the wall +# friction can be expressed as: +# ```math +# \tau_{wall} = \frac{3 \nu hu^{n+1}}{{h^{n+1}}^2} - \frac{\tau_{air}}{2} +# ``` +# where the slipping length, ``b``, is neglected (which is fine when working with an implicit formulation). The momentum +# equation can then be rearranged to obtain: +# ```math +# h u^{n+1}\left( 1 + \frac{3 \nu \Delta t }{{h^{n+1}}^2} \right) = hu^n - \Delta t \left[ +# \nabla \cdot \mathcal{F}_{hu}(h^n,hu^n) +# - h^n \left( g_t - \nabla P_{gaz} \right) - \frac{3}{2}\tau_{air} +# \right] +# ``` +# Moving the multiplying factor to the right-hand-side, we finally obtain: +# ```math +# h u^{n+1} = \frac{{h^{n+1}}^2}{{h^{n+1}}^2 + 3 \nu \Delta t} \left[ +# hu^n - \Delta t \left[ +# \nabla \cdot \mathcal{F}_{hu}(h^n,hu^n) +# - h^n \left( g_t - \nabla P_{gaz} \right) - \frac{3}{2}\tau_{air} +# \right] +# \right] +# ``` +# +# +# ## Weak form +# First we write the different equation with a full explicit scheme to improve clarity. +# +# ### Mass conservation equation +# We multiply the equation by a test function ``\phi_{h}`` and integrate on a control volume ``\Omega``. After an integration by parts, +# we obtain (for an explicit integration time scheme): +# ```math +# \int_{\Omega} h^{n+1} \phi_{h} \mathrm{\,d}\Omega = \int_{\Omega} h^n \phi_{h} \mathrm{\,d}\Omega +# + \Delta t \left[ \int_{\Omega} h u^n \cdot \nabla \phi_{h} \mathrm{\,d}\Omega +# - \oint_{\Gamma} F_{h}^*(h^n, h u^n) \phi_{h} \mathrm{\,d} \Gamma \right] +# ``` +# where ``F^*_{h}`` is the numerical flux corresponding to ``hu``. +# +# ### Momentum conservation equation +# We first consider the case without contact line forces nor curvature. Multiplying by a test function ``\phi_{h u}`` and integrating +# by parts leads to: +# ```math +# \int_{\Omega} h u^{n+1} \phi_{h u} \mathrm{\,d}\Omega = \int_{\Omega} h u^n \phi_{h u} \mathrm{\,d}\Omega +# + \Delta t \left[ +# \int_{\Omega} \left[ +# \mathcal{F}^n \cdot \nabla \phi_{h u} +# + \left( h^n(g_t - \nabla P_g) - \tau_w + \tau_a \right) \phi_{h u} +# \right] \mathrm{\,d}\Omega +# - \oint_{\Gamma} F_{h u}^*(h^n, h u^n) \phi_{h u} \mathrm{\,d} \Gamma +# \right] +# ``` +# where ``F^*_{h u}`` is the numerical flux corresponding to ``h u \otimes u + g_n h^2 /2 \mathcal{I}``. +# + +const ε = eps() + +""" + Inverse of the mass matrix in a given cell. +""" +function inv_mass_matrix(λ, cnodes, ctype, order) + M = integrate_ref(ξ -> ⊗(λ(ξ)), cnodes, ctype, order) + return inv(M) +end + +""" + Build the inverse of mass matrix for all mesh cells + @TODO : use projection.L2_projector +""" +function build_mass_matrix_inv(mesh, h::CellVariable, hu::CellVariable, order_h, order_hu) + ## Get cell -> node connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Function spaces + fs_h = function_space(h) + fs_hu = function_space(hu) + + ## Integration order + qorder_h = Val(order_h^2) + qorder_hu = Val(order_hu^2) + + ## Allocate + iM_h = [zeros(ntuple(i -> ndofs(fs_h, shape(cellTypes[icell])), 2)) for icell in 1:ncells(mesh)] + iM_hu = [zeros(ntuple(i -> ndofs(fs_hu, shape(cellTypes[icell])), 2)) for icell in 1:ncells(mesh)] + + ## Loop over cells + for icell in 1:ncells(mesh) + + ## Cell type and shape + ctype = cellTypes[icell] + cshape = shape(ctype) + + ## Alias for nodes + cnodes = get_nodes(mesh, c2n[icell]) + + ## Shape functions and gradients + λ_h = shape_functions(fs_h, cshape) + λ_hu = shape_functions(fs_hu, cshape) + + ## Compute inverse of mass matrix + iM_h[icell] .= inv_mass_matrix(λ_h, cnodes, ctype, qorder_h) + iM_hu[icell] .= inv_mass_matrix(λ_hu, cnodes, ctype, qorder_hu) + end + + return iM_h, iM_hu +end + +""" + ξᵢ and ξⱼ are in the cell-ref-element +""" +function upwind(valᵢ, valⱼ, hᵢ, hⱼ, huᵢ, huⱼ, nᵢⱼ, ξᵢ, ξⱼ) + upwind(valᵢ(ξᵢ), valⱼ(ξⱼ), hᵢ(ξᵢ), hⱼ(ξⱼ), huᵢ(ξᵢ), huⱼ(ξⱼ), nᵢⱼ) +end + +function upwind(valᵢ, valⱼ, hᵢ, hⱼ, huᵢ, huⱼ, nᵢⱼ) + ## Centered velocity + #vij = 0.5 * (vᵢ + vⱼ) ⋅ nᵢⱼ + + ## Face velocity + vij = (huᵢ + huⱼ) / (hᵢ + hⱼ + ε) ⋅ nᵢⱼ + + ## min/max + vij⁺ = max(0.0, vij) + vij⁻ = min(0.0, vij) + + return valᵢ * vij⁺ + valⱼ * vij⁻ +end + +centered(valᵢ, valⱼ, nᵢⱼ) = 0.5 * (valᵢ + valⱼ) * nᵢⱼ +centered(valᵢ, valⱼ, nᵢⱼ, ξᵢ, ξⱼ) = centered(valᵢ(ξᵢ), valⱼ(ξⱼ), nᵢⱼ) + +function momentum_flux(hᵢ, hⱼ, huᵢ, huⱼ, gnᵢ, gnⱼ, nᵢⱼ) + + ## Centered height + hij = 0.5 * (hᵢ + hⱼ) + + ## Centered gravity + gnij = 0.5 * (gnᵢ + gnⱼ) + + ## Upwind for convection + center for gravity + return upwind(huᵢ, huⱼ, hᵢ, hⱼ, huᵢ, huⱼ, nᵢⱼ) + gnij * hij^2 / 2 .* nᵢⱼ # erreur? +end + +function momentum_flux(hᵢ, hⱼ, huᵢ, huⱼ, gnᵢ, gnⱼ, nᵢⱼ, ξᵢ, ξⱼ) + return momentum_flux(hᵢ(ξᵢ), hⱼ(ξⱼ), huᵢ(ξᵢ), huⱼ(ξⱼ), gnᵢ(ξᵢ), gnⱼ(ξⱼ), nᵢⱼ) +end + +""" + Compute flux on each face of the mesh. + + Same shape functions assumed for all variables for now +""" +function compute_flux!( + mesh::AbstractMesh, + sys::System, + h::CellVariable, + hu::CellVariable, + params, + q::Vector, + t::Real, + f::Vector, +) + ## Unpack params + g = params.g + + ## Get connectivities + c2n = connectivities_indices(mesh, :c2n) + f2n = connectivities_indices(mesh, :f2n) + f2c = connectivities_indices(mesh, :f2c) + + ## Cell and face types + cellTypes = cells(mesh) + faceTypes = faces(mesh) + + ## Alias : number of components of hu + n_hu = ncomponents(hu) + + ## Integration order + order_h = Val(3 * params.order_h) # lucky guess (for the term '∇λ * h * u') + order_hu = Val(3 * params.order_hu) # lucky guess (for the term '∇λ * hu * u') + + ## Function spaces + fs_h = function_space(h) + fs_hu = function_space(hu) + + ## Reset flux vector + f .= 0.0 + + # Loop on all the inner faces + for kface in inner_faces(mesh) + ## Face nodes, type and shape + ftype = faceTypes[kface] + fnodes = get_nodes(mesh, f2n[kface]) + + ## Neighbor cell i + i = f2c[kface][1] + xᵢ = get_nodes(mesh, c2n[i]) + ctᵢ = cellTypes[i] + shapeᵢ = shape(ctᵢ) + sideᵢ = cell_side(ctᵢ, c2n[i], f2n[kface]) + fpᵢ = mapping_face(shapeᵢ, sideᵢ) + λᵢ_h = shape_functions(fs_h, shapeᵢ) + λᵢ_hu = shape_functions(fs_hu, shapeᵢ) + hᵢ = interpolate(λᵢ_h, q[dof(h, i)]) + huᵢ = interpolate(λᵢ_hu, q[dof(hu, i)], n_hu) + gnᵢ(ξ) = g ⋅ cell_normal(xᵢ, ctᵢ, ξ) + + ## Neighbor cell j + j = f2c[kface][2] + xⱼ = get_nodes(mesh, c2n[j]) + ctⱼ = cellTypes[j] + shapeⱼ = shape(ctⱼ) + sideⱼ = cell_side(ctⱼ, c2n[j], f2n[kface]) + nv = length(faces2nodes(shapeⱼ, sideⱼ)) # number of vertices of the face + iglob_vertices_of_face_of_cell_j = + [c2n[j][faces2nodes(ctⱼ, sideⱼ)[l]] for l in 1:nv] + g2l = indexin(f2n[kface][1:nv], iglob_vertices_of_face_of_cell_j) + fpⱼ = mapping_face(shapeⱼ, sideⱼ, g2l) + λⱼ_h = shape_functions(fs_h, shape(ctⱼ)) + λⱼ_hu = shape_functions(fs_hu, shape(ctⱼ)) + hⱼ = interpolate(λⱼ_h, q[dof(h, j)]) + huⱼ = interpolate(λⱼ_hu, q[dof(hu, j)], n_hu) + gnⱼ(ξ) = g ⋅ cell_normal(xⱼ, ctⱼ, ξ) + + ##------- Mass conservation + ## Flux definition in face-ref-element + fluxn = (nᵢⱼ, ξ) -> upwind(hᵢ, hⱼ, hᵢ, hⱼ, huᵢ, huⱼ, nᵢⱼ, fpᵢ(ξ), fpⱼ(ξ)) + + ## Compute flux contribution of face `kface` to cell `i`, performing a surfacic integration + g_ref = ξ -> λᵢ_h(fpᵢ(ξ)) .* fluxn(normal(xᵢ, ctᵢ, sideᵢ, ξ), ξ) + f[dof(sys, h, i)] .+= integrate_ref(g_ref, fnodes, ftype, order_h) + + ## Compute flux contribution of face `kface` to cell `j`, performing a surfacic integration + g_ref = ξ -> λⱼ_h(fpⱼ(ξ)) * fluxn(-normal(xⱼ, ctⱼ, sideⱼ, ξ), ξ) + f[dof(sys, h, j)] .-= integrate_ref(g_ref, fnodes, ftype, order_h) + + ##------- Momentum conservation + ## Flux definition in face-ref-element + fluxn = (nᵢⱼ, ξ) -> momentum_flux(hᵢ, hⱼ, huᵢ, huⱼ, gnᵢ, gnⱼ, nᵢⱼ, fpᵢ(ξ), fpⱼ(ξ)) + + ## Compute flux contribution of face `kface` to cell `i`, performing a surfacic integration + g_ref = ξ -> λᵢ_hu(fpᵢ(ξ)) * transpose(fluxn(normal(xᵢ, ctᵢ, sideᵢ, ξ), ξ)) + flux⁺ = integrate_ref(g_ref, fnodes, ftype, order_hu) + + ## Compute flux contribution of face `kface` to cell `j`, performing a surfacic integration + g_ref = ξ -> λⱼ_hu(fpⱼ(ξ)) * transpose(fluxn(-normal(xⱼ, ctⱼ, sideⱼ, ξ), ξ)) + flux⁻ = integrate_ref(g_ref, fnodes, ftype, order_hu) + + ## Add contribution to the residual of each adjacent cells + i_dofs = dof(sys, hu, i) + j_dofs = dof(sys, hu, j) + f[i_dofs] .+= vec(flux⁺) + f[j_dofs] .-= vec(flux⁻) + end + + ## Loop on all the boundary of type 'faces' + for tag in keys(mesh.bc_faces) + + ## Loop over this boundary faces + for kface in boundary_faces(mesh, tag) + + ## Face nodes and type + ftype = faceTypes[kface] + fnodes = get_nodes(mesh, f2n[kface]) + + ## Neighbor cell i + i = f2c[kface][1] + cnodes = get_nodes(mesh, c2n[i]) + ctype = cellTypes[i] + s = shape(ctype) + side = cell_side(ctype, c2n[i], f2n[kface]) + F = mapping(cnodes, ctype) + fp = mapping_face(s, side) + λ_h = shape_functions(fs_h, s) + λ_hu = shape_functions(fs_hu, s) + hᵢ = interpolate(λ_h, q[dof(sys, h, i)]) + huᵢ = interpolate(λ_hu, q[dof(sys, hu, i)], n_hu) + gn(ξ) = g ⋅ cell_normal(cnodes, ctype, ξ) + + ##----- Mass conservation + if haskey(params.cdts, (tag, :h)) + + ## Get associated boundary condition + cdt = params.cdts[(tag, :h)] + + ## Dofs + i_dofs = dof(sys, h, i) + + ## Flux boundary condition + if type(cdt) == :flux + ## Append flux contribution of face `kface` to cell `i` + g_ref = + ξ -> + normal(cnodes, ctype, side, ξ) ⋅ apply(cdt, F(fp(ξ)), t) * + λ_h(fp(ξ)) + f[i_dofs] .+= integrate_ref(g_ref, fnodes, ftype, order_h) + + elseif type(cdt) == :outlet + fluxn = (nᵢⱼ, ξ) -> upwind(hᵢ, hᵢ, hᵢ, hᵢ, huᵢ, huᵢ, nᵢⱼ, fp(ξ), fp(ξ)) + g_ref = ξ -> λ_h(fp(ξ)) .* fluxn(normal(cnodes, ctype, side, ξ), ξ) + f[i_dofs] .+= integrate_ref(g_ref, fnodes, ftype, order_h) + end + end + + ##----- Momentum conservation + if haskey(params.cdts, (tag, :hu)) + + ## Get associated boundary condition + cdt = params.cdts[(tag, :hu)] + + ## Dofs + i_dofs = dof(sys, hu, i) + + ## Flux boundary condition + if (type(cdt) == :flux) + ## Append flux contribution of face `kface` to cell `i` + g_ref = + ξ -> + λ_hu(fp(ξ)) * transpose( + apply(cdt, F(fp(ξ)), t) .* normal(cnodes, ctype, side, ξ), + ) + f[i_dofs] .+= vec(integrate_ref(g_ref, fnodes, ftype, order_hu)) + + elseif (type(cdt) == :outlet) + fluxn = + (nᵢⱼ, ξ) -> + momentum_flux(hᵢ, hᵢ, huᵢ, huᵢ, gn, gn, nᵢⱼ, fp(ξ), fp(ξ)) + g_ref = + ξ -> + λ_hu(fp(ξ)) * + transpose(fluxn(normal(cnodes, ctype, side, ξ), ξ)) + f[i_dofs] .+= vec(integrate_ref(g_ref, fnodes, ftype, order_hu)) + end + end + end # end loop on boundary faces + end # end loop on bnd tags +end + +function rhs!( + mesh::AbstractMesh, + sys::System, + h::CellVariable, + hu::CellVariable, + params, + t::Real, + rhs::Vector, + q::Vector, +) + ## Unpack params + g = params.g + ν = params.ν + b = params.b + + ## Flux + f = similar(q) # needed for ForwardDiff + compute_flux!(mesh, sys, h, hu, params, q, t, f) + + ## Cell -> node connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Identity matrix + Id = SMatrix{spacedim(mesh), spacedim(mesh)}(1.0I) + + ## Alias : number of components of hu + n_hu = ncomponents(hu) + + ## Integration order + order_h = Val(3 * params.order_h) # lucky guess (for the term '∇λ * h * u') + order_hu = Val(3 * params.order_hu) # lucky guess (for the term '∇λ * hu * u') + + ## Function spaces + fs_h = function_space(h) + fs_hu = function_space(hu) + + ## Loop over cells + for icell in 1:ncells(mesh) + ## Unpack cache + iM_h = params.iM_h[icell] + iM_hu = params.iM_hu[icell] + + ## Cell type and shape + ctype = cellTypes[icell] + cshape = shape(ctype) + + ## Alias for nodes + cnodes = get_nodes(mesh, c2n[icell]) + + ## Cell mapping : ref -> loc + F = mapping(cnodes, ctype) + + ## Cell normal + n(ξ) = cell_normal(cnodes, ctype, ξ) + + ## Shape functions and gradients + λ_h = shape_functions(fs_h, cshape) + λ_hu = shape_functions(fs_hu, cshape) + ∇λ_h = grad_shape_functions(fs_h, ctype, cnodes) + ∇λ_hu = grad_shape_functions(fs_hu, ctype, cnodes) + + ## Alias + i_h = dof(sys, h, icell) + i_hu = dof(sys, hu, icell) + + ## Interpolations + hᵢ = interpolate(λ_h, q[i_h]) + huᵢ = interpolate(λ_hu, q[i_hu], Val(n_hu)) + u(ξ) = huᵢ(ξ) / (hᵢ(ξ) + ε) # `+ ε` to avoid division by zero + + ## Gravity + gn(ξ) = g ⋅ n(ξ) # scalar + gt(ξ) = g - gn(ξ) .* n(ξ) # vector + + ## External sources (ρ is already included) + ∇Pg(ξ) = params.∇Pg(F(ξ)) + τₐ(ξ) = params.τₐ(F(ξ)) + + ## Wall friction + τw(ξ) = wall_friction(hᵢ(ξ), u(ξ), ν, τₐ(ξ), b) + + ## Mass conservation + FV = integrate_ref(ξ -> ∇λ_h(ξ) ⋅ huᵢ(ξ), cnodes, ctype, order_h) + @views rhs[i_h] .= iM_h * (FV .- f[i_h]) + + # Momentum conservation + FV = integrate_ref( + ξ -> + ∇λ_hu(ξ) * transpose(huᵢ(ξ) * transpose(u(ξ)) + gn(ξ) * hᵢ(ξ)^2 / 2 * Id) + + λ_hu(ξ) .* transpose(hᵢ(ξ) * (gt(ξ) - ∇Pg(ξ)) - τw(ξ) + τₐ(ξ)), + cnodes, + ctype, + order_hu, + ) + @views rhs[i_hu] .= vec(iM_hu * (FV - reshape(f[i_hu], size(FV)))) + end +end + +""" + Explicit step in time +""" +function explicit_step!( + mesh::AbstractMesh, + sys, + h, + hu, + params, + rhs::Vector, + dq::Vector, + q::Vector, + t::Real, +) + rhs!(mesh, sys, h, hu, params, t, rhs, q) + dq .= params.Δt .* rhs +end + +""" + Implicit step in time +""" +function implicit_step!( + mesh::AbstractMesh, + sys, + h, + hu, + params, + rhs::Vector, + dq::Vector, + q::Vector, + t::Real, +) + # Unpack + Δt = params.Δt + + # Compute RHS + rhs!(mesh, sys, h, hu, params, t, rhs, q) + #@show rhs + + # RHS Jacobian + J = ForwardDiff.jacobian((rhs, q) -> rhs!(mesh, sys, h, hu, params, t, rhs, q), rhs, q) + #display(J) + + # Invert + dq .= (I - Δt .* J) \ (Δt .* rhs) +end + +""" + +Return the wall friction effort divided by ρ + +Note : input τₐ is already divided by ρ +""" +function wall_friction(h, u, ν, τₐ, b) + return (6 * ν * u - τₐ * h) / (2 * (h + b)) +end + +# We are all set to solve a linear transport equation. However, we will add two more things to ease the solution VTK output : a +# structure to store the vtk filename and the number of iteration: +mutable struct VtkHandler + basename::Any + ite::Any + VtkHandler(basename) = new(basename, 0) +end + +# ... and a method to interpolate the discontinuous solution on cell centers and to append it to the VTK file: +function append_vtk(vtk, mesh, sys::System, h::CellVariable, hu::CellVariable, q, t) + ## Values on center + set_values!(h, q[dof(sys, h)]) + set_values!(hu, q[dof(sys, hu)]) + cv2val = var_on_centers((h, hu)) + + ## Write + dict_vars = Dict( + "h" => (cv2val[h], VTKCellData()), + "hu" => (transpose(cv2val[hu]), VTKCellData()), + ) + write_vtk(vtk.basename, vtk.ite, t, mesh, dict_vars; append = vtk.ite > 0) + + ## Update counter + vtk.ite += 1 +end + +# Physical settings +const g = [0.0, 0.0] # gravity vector +const ρ = 1000 # water density +const ν = 1e-6 # water cinematic viscosity (1e-6 at 20C) +const b = 1e-9 # slipping length +const τₐ(x) = [0.0, 0.0] # Tangent gas friction (divided by rho) +const ∇Pg(x) = [0.0, 0.0] # Tangent gas pressure gradient (divided by rho) +const h_in = 1e-3 # (m) +const u_in = [1e-2, 0.0] # (m/s) - column vector + +# Numerical settings +const lx = 1.0 # Domain length +const nx = 51 # Number of mesh points in `x` direction +const ny = 0 # Number of mesh points in `y` direction +const nite = 2000 # Number of time iterations +const Δt = lx / (norm(u_in) * (nx - 1)) # time step +const order_h = 0 # discretization order for `h` +const order_hu = 0 # discretization order for `hu` +const nout = 100 # Maximum number of VTK outputs + +# Build mesh +mesh = line_mesh(nx; names = ("West", "East")) # 1D mesh in a 1D space +mesh = transform(mesh, x -> [x[1], 0.0]) # 1D mesh in a 2D space + +# Create variables +fes_h = FESpace(FunctionSpace(:Taylor, order_h), :discontinuous) +fes_hu = FESpace(FunctionSpace(:Taylor, order_hu), :discontinuous; size = spacedim(mesh)) +h = CellVariable(:h, mesh, fes_h) +hu = CellVariable(:hu, mesh, fes_hu) +sys = System((h, hu)) + +# Boundary condition +cdt_h_in = BoundaryCondition(:flux, (x, t) -> h_in .* u_in) +cdt_hu_in = BoundaryCondition(:flux, (x, t) -> h_in .* u_in .^ 2) +cdts = Dict((boundary_tag(mesh, "West"), :h) => cdt_h_in, (boundary_tag(mesh, "West"), :hu) => cdt_hu_in, (boundary_tag(mesh, "East"), :h) => BoundaryCondition(:outlet, nothing), (boundary_tag(mesh, "East"), :hu) => BoundaryCondition(:outlet, nothing)) + +# Inverse mass matrix +iM_h, iM_hu = build_mass_matrix_inv(mesh, h, hu, order_h, order_hu) + +# Then we create a `NamedTuple` to hold the simulation parameters. +params = ( + order_h = order_h, + order_hu = order_hu, + iM_h = iM_h, + iM_hu = iM_hu, + cdts = cdts, + g = g, + ρ = ρ, + ν = ν, + b = b, + τₐ = τₐ, + ∇Pg = ∇Pg, + Δt = Δt, +) + +# Let's allocate the unknown vector and set it to zero. Along with this vector, we also allocate the "increment" vector. +nd = get_ndofs(sys) +q = zeros(nd) +q[1:3:end] .= h_in +q[2:3:end] .= h_in .* u_in[1] +dq = zeros(size(q)) +rhs = zeros(size(q)) + +# Init vtk handler +vtk = VtkHandler(dir * "myout/shallow_water") + +# Init time +t = 0.0 + +# Save initial solution +append_vtk(vtk, mesh, sys, h, hu, q, t) + +# Let's loop to solve the equation. +for i in 1:nite + ## Infos + println("\nIteration ", i) + + ## Step forward in time + #explicit_step!(mesh, dhl, params, rhs, dq, q, t) + implicit_step!(mesh, sys, h, hu, params, rhs, dq, q, t) + q .+= dq + global t += Δt + #@show q + + ## Write solution to file (respecting max. number of output) + if (i % Int(max(floor(nite / nout), 1)) == 0) + append_vtk(vtk, mesh, sys, h, hu, q, t) + end +end + +@show Δt + +end #hide diff --git a/src/example/non_API/transport_hypersurface.jl b/src/example/non_API/transport_hypersurface.jl new file mode 100644 index 0000000..35c51c1 --- /dev/null +++ b/src/example/non_API/transport_hypersurface.jl @@ -0,0 +1,446 @@ +module TransportHypersurface #hide +println("Running transport on hypersurface example...") #hide + +# Load the necessary packages +const dir = string(@__DIR__, "/../../") # Bcube dir +include(dir * "src/Bcube.jl") +using .Bcube +using LinearAlgebra +using WriteVTK + +# Matrix-vector multiplication when matrix is dimension 1 and vector also... +Base.:*(a::Array{Float64, 1}, b::Array{Float64, 1}) = a .* b + +# Upwind flux, nij is the local element normal +# This function is wrong : we should apply a "rotation" of the velocity in cell-j to +# bring it back in cell-i plane. But we assume here that the normal vector are coplanar. +function upwind(ϕᵢ, ϕⱼ, c, nᵢⱼ) + vij = c ⋅ nᵢⱼ + if vij > zero(vij) + vij * ϕᵢ + else + vij * ϕⱼ + end +end + +function inv_mass_matrix(λ, cnodes, ct, order) + M = integrate_ref(ξ -> ⊗(λ(ξ)), cnodes, ct, order) + return inv(M) +end + +function build_mass_matrix_inv(mesh, fs, params) + ## Get cell -> node connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Alias for some params (optionnal) + order = params.order + + ## Alias for quadrature orders + orderM = Val(2 * order + 1) + + ## Allocate + inv_matrix = [ + zeros(ntuple(i -> ndofs(fs, shape(cellTypes[icell])), 2)) for + icell in 1:ncells(mesh) + ] + + ## Assemble + for icell in 1:ncells(mesh) + + ## Alias for cell type + ctype = cellTypes[icell] + + ## Alias for nodes + cnodes = get_nodes(mesh, c2n[icell]) + + ## Get shape functions in the reference element + λ = shape_functions(fs, shape(ctype)) + + ## Compute inverse of mass matrix + inv_matrix[icell] .= inv_mass_matrix(λ, cnodes, ctype, orderM) + end + return inv_matrix +end + +function compute_flux(mesh, ϕ, params, q, t) + ## Allocate + ## This is just the the demo -> for performance, this vector should be allocated + ## outside of this function and given as an input + f = zeros(ndofs(ϕ)) + + ## Alias + c = params.c + + ## Get cell -> node, face -> node and face -> cell connectivities + c2n = connectivities_indices(mesh, :c2n) + f2n = connectivities_indices(mesh, :f2n) + f2c = connectivities_indices(mesh, :f2c) + + ## Cell and face types + cellTypes = cells(mesh) + faceTypes = faces(mesh) + + ## Function space + fs = function_space(ϕ) + + ## Integration order + order = Val(2 * params.order + 1) # it's a lucky guess since we don't really know the "flux order" + + ## Loop on all the inner faces + for kface in inner_faces(mesh) + + ## Face nodes, type and shape + ftype = faceTypes[kface] + fnodes = get_nodes(mesh, f2n[kface]) + F = mapping(fnodes, ftype) # Face mapping : face-ref coords -> local coords + + ## Neighbor cell i + i = f2c[kface][1] + xᵢ = get_nodes(mesh, c2n[i]) + ctᵢ = cellTypes[i] + shapeᵢ = shape(ctᵢ) + λᵢ = shape_functions(fs, shape(ctᵢ)) + ϕᵢ = interpolate(λᵢ, q[dof(ϕ, i)]) + sideᵢ = cell_side(ctᵢ, c2n[i], f2n[kface]) + fpᵢ = mapping_face(shapeᵢ, sideᵢ) # for adjacent cell 1, we assume that glob2loc = 1:nnodes(face) + + ## Neighbor cell j + j = f2c[kface][2] + xⱼ = get_nodes(mesh, c2n[j]) + ctⱼ = cellTypes[j] + shapeⱼ = shape(ctⱼ) + λⱼ = shape_functions(fs, shape(ctⱼ)) + ϕⱼ = interpolate(λⱼ, q[dof(ϕ, j)]) + sideⱼ = cell_side(ctⱼ, c2n[j], f2n[kface]) + # This part is a bit tricky : we want the face parametrization (face-ref -> cell-ref) on + # side `j`. For this, we need to know the permutation between the vertices of `kface` and the + # vertices of the `sideⱼ`-th face of cell `j`. However all the information we have for entities, + # namely `fnodes` and `faces2nodes(ctⱼ, sideⱼ)` refer to the nodes, not the vertices. So we need + # to retrieve the number of vertices of the face and then restrict the arrays to these vertices. + # (by the way, we use that the vertices appears necessarily in first) + # We could simplify the expressions below by introducing the notion of "vertex" in Entity, for + # instance with `nvertices` and `faces2vertices`. + nv = length(faces2nodes(shapeⱼ, sideⱼ)) # number of vertices of the face + iglob_vertices_of_face_of_cell_j = + [c2n[j][faces2nodes(ctⱼ, sideⱼ)[l]] for l in 1:nv] + g2l = indexin(f2n[kface][1:nv], iglob_vertices_of_face_of_cell_j) + fpⱼ = mapping_face(shapeⱼ, sideⱼ, g2l) + + ## Flux definition from `i` to `j` : using upwind + fluxn = (nᵢⱼ, ξ) -> upwind(ϕᵢ(fpᵢ(ξ)), ϕⱼ(fpⱼ(ξ)), c(F(ξ)), nᵢⱼ) # Note the use of F + + ## Append flux contribution of face `kface` to cell `i`, performing a surfacic integration + # ξ is the face-ref-element + g_ref = ξ -> fluxn(normal(xᵢ, ctᵢ, sideᵢ, ξ), ξ) * λᵢ(fpᵢ(ξ)) + f[dof(ϕ, i)] += integrate_ref(g_ref, fnodes, ftype, order) + + ## Append flux contribution of face `kface` to cell `j` + g_ref = ξ -> fluxn(-normal(xⱼ, ctⱼ, sideⱼ, ξ), ξ) * λⱼ(fpⱼ(ξ)) + f[dof(ϕ, j)] -= integrate_ref(g_ref, fnodes, ftype, order) + end + + ## Loop on all the boundary of type 'faces' + for tag in keys(mesh.bc_faces) + ## Loop over this boundary faces + for kface in boundary_faces(mesh, tag) + + ## Face nodes, type and shape + ftype = faceTypes[kface] + fnodes = get_nodes(mesh, f2n[kface]) + F = mapping(fnodes, ftype) # mapping face-ref coords -> local coords + + ## Neighbor cell i + i = f2c[kface][1] + cnodes = get_nodes(mesh, c2n[i]) + ct = cellTypes[i] + s = shape(ct) + λ = shape_functions(fs, shape(ct)) + ϕᵢ = interpolate(λ, q[dof(ϕ, i)]) + side = cell_side(ct, c2n[i], f2n[kface]) + fp = mapping_face(s, side) + + ## For a multi-variable problem, we should loop over the variables. Here we have only one variable + ## Skip if no boundary condition on this boundary + !haskey(params.cdts, tag) && continue + + ## Get associated boundary condition + cdt = params.cdts[tag] + + ## Flux boundary condition + if type(cdt) == :flux + ## Append flux contribution of face `kface` to cell `i` + g_ref = ξ -> normal(cnodes, ct, side, ξ) ⋅ apply(cdt, F(ξ), t) * λ(fp(ξ)) + f[dof(ϕ, i)] += integrate_ref(g_ref, fnodes, ftype, order) + + ## Dirichlet boundary condition : we apply classic flux with imposed condition in ghost. We split in + ## three steps to improve clarity, but this can be done in one line. + elseif type(cdt) == :diri + ϕ_bnd = ξ -> apply(cdt, F(ξ), t) + fluxn = (n, ξ) -> upwind(ϕᵢ(fp(ξ)), ϕ_bnd(ξ), c(F(ξ)), n) + g_ref = ξ -> fluxn(normal(cnodes, ct, side, ξ), ξ) * λ(fp(ξ)) + f[dof(ϕ, i)] += integrate_ref(g_ref, fnodes, ftype, order) + end + end + end + + ## Result : a vector of size `ndofs` + return f +end + +function explicit_step!(mesh, ϕ, params, dq, q, t) + + ## Get inverse of mass matrix from cache + inv_mass_matrix = build_mass_matrix_inv(mesh, function_space(ϕ), params) + + ## Get cell -> node connectivity and cell types + c2n = connectivities_indices(mesh, :c2n) + cellTypes = cells(mesh) + + ## Alias for the function space + fs = function_space(ϕ) + + ## Alias for some parameters + c = params.c + order = params.order + + ## Alias for quadrature orders + orderF = Val(order + order - 1 + 1) + + ## Compute surfacic flux + f = compute_flux(mesh, ϕ, params, q, t) + + ## Assemble + for icell in 1:ncells(mesh) + + ## Allocate + FV = zeros(ndofs(ϕ, icell)) # Volumic flux + + ## Indices of all the variable dofs in this cell + i_ϕ = dof(ϕ, icell) + + ## Alias for cell type + ctype = cellTypes[icell] + + ## Alias for nodes + cnodes = get_nodes(mesh, c2n[icell]) + + ## Get reference shape functions + λ = shape_functions(fs, shape(ctype)) + + ## Compute shape functions gradient + ∇λ = grad_shape_functions(fs, ctype, cnodes) + + ## Create interpolation function + ϕᵢ = interpolate(λ, q[i_ϕ]) + + ## Loop over cell dofs for volumic term + FV .= integrate_ref( + ξ -> ∇λ(ξ) * (c(mapping(cnodes, ctype, ξ)) .* ϕᵢ(ξ)), + cnodes, + ctype, + orderF, + ) + + ## Assemble + dq[i_ϕ] .= inv_mass_matrix[icell] * (FV .- f[i_ϕ]) + end +end + +# We are all set to solve a linear transport equation. However, we will add two more things to ease the solution VTK output : a +# structure to store the vtk filename and the number of iteration: +mutable struct VtkHandler + basename::Any + ite::Any + VtkHandler(basename) = new(basename, 0) +end + +# ... and a method to interpolate the discontinuous solution on cell centers and to append it to the VTK file: +function append_vtk(vtk, mesh, ϕ, q, t) + ## Values on center + set_values!(ϕ, q) + values = var_on_centers(ϕ) + + ## Write + dict_vars = Dict("ϕ" => (values, VTKCellData())) + write_vtk(vtk.basename, vtk.ite, t, mesh, dict_vars; append = vtk.ite > 0) + + ## Update counter + vtk.ite += 1 +end + +function solve!(mesh, ϕ, params, dq, q) + # Alias + nite = params.nite + nout = params.nout + Δt = params.Δt + + # Init time + t = 0.0 + + # Solution at t = 0 + vtk = VtkHandler(params.name) + append_vtk(vtk, mesh, ϕ, q, t) + + # Let's loop to solve the equation. + for i in 1:nite + ## Infos + println("Iteration ", i) + + ## Step forward in time + explicit_step!(mesh, ϕ, params, dq, q, t) + q .+= Δt * dq + t += Δt + + ## Write solution to file (respecting max. number of output) + if (i % Int(max(floor(nite / nout), 1)) == 0) + append_vtk(vtk, mesh, ϕ, q, t) + end + end +end + +# Define horizontal line mesh +nx = 10 +xmin = 1.0 +xmax = 4.0 +mesh = line_mesh(nx; xmin, xmax) + +# Augment space dimension, rotate and translate +A(x) = [x[1], 0] +R(θ) = [ + cos(θ) -sin(θ) + sin(θ) cos(θ) +] +T(u, t) = u + t + +# Rotage and translate mesh +θ = π / 4 +t⃗ = [1.0, 2.0] +f(x) = T(R(θ) * A(x), t⃗) +mesh = transform(mesh, f) + +# Function space and var +fes = FESpace(FunctionSpace(:Taylor, 0), :discontinuous) +ϕ = CellVariable(:phi, mesh, fes) + +# Analytic velocity +c(x) = 1.0 * [cos(θ), sin(θ)] + +# Boundary condition +#cdt_in = BoundaryCondition(:flux, (x, t) -> 0. * c(x)) +cdt_in = BoundaryCondition(:diri, (x, t) -> 0.0) +cdt_out = BoundaryCondition(:diri, (x, t) -> 0.0) +cdts = Dict(boundary_tag(mesh, "LEFT") => cdt_in, boundary_tag(mesh, "RIGHT") => cdt_out) + +# Time step +Δt = (xmax - xmin) / (nx - 1) + +# Then we create a `NamedTuple` to hold the simulation parameters +params = ( + name = dir * "myout/transport_on_inclined_line", + nout = nx, + nite = nx, + Δt = Δt, + order = 2, + c = c, + cdts = cdts, +) + +# Let's allocate the unknown vector and set it to zero. Along with this vector, we also allocate the "increment" vector. +q = zeros(ndofs(ϕ)) +dq = zeros(size(q)) + +# Initial solution +q[1] = 1.0 + +# Solve +solve!(mesh, ϕ, params, dq, q) +@show q + +# Define circle mesh +nx = 10 +radius = 1.0 +mesh = circle_mesh(nx; radius = radius, order = 2) + +# Function space and var +fes = FESpace(FunctionSpace(:Taylor, 0), :discontinuous) +ϕ = CellVariable(:ϕ, mesh, fes) + +# Analytic velocity +c(x) = 1.0 * [-x[2], x[1]] / radius + +# Time step +Δt = 2 * π * radius / nx + +# Then we create a `NamedTuple` to hold the simulation parameters +params = ( + name = dir * "myout/transport_on_circle", + nout = nx, + nite = nx, + Δt = Δt, + order = 2, + c = c, + cdts = Dict(), +) + +# Let's allocate the unknown vector and set it to zero. Along with this vector, we also allocate the "increment" vector. +q = zeros(ndofs(ϕ)) +dq = zeros(size(q)) + +# Initial solution +q[1] = 1.0 + +# Solve +solve!(mesh, ϕ, params, dq, q) +@show q + +# Settings +r = 1.0 +nite = 75 +nout = 100 + +# Read mesh of a sphere +mesh = read_msh(dir * "input/mesh/sphere_2.msh") + +# Function space and var +fes = FESpace(FunctionSpace(:Taylor, 0), :discontinuous) +ϕ = CellVariable(:ϕ, mesh, fes) + +# Analytic velocity +# x = r sin(\theta)cos(\phi) +# y = r sin(\theta)sin(\phi) +# z = r cos(\theta) +# u_r = sin(\theta)cos(\phi) ex + sin(\theta)sin(\phi) ey + cos(\theta) ez +# u_\theta = cos(\theta)cos(\phi) ex + cos(\theta)sin(\phi) ey - sin(\theta) ez +# u_\phi = -sin(\phi) ex + cos(\phi) ey +# We want the velocity to be u_\theta +# sin(theta) = x / (r cos(phi)) +# cos(theta) = z / r +φ = 0.0 +c(x) = 1.0 * [x[3] / r * cos(φ), x[3] / r * sin(φ), -x[1] / (r * cos(φ))] + +# Time step +Δt = 0.04 + +# Then we create a `NamedTuple` to hold the simulation parameters +params = ( + name = dir * "myout/transport_on_sphere", + nout = nout, + nite = nite, + Δt = Δt, + order = 2, + c = c, + cdts = Dict(), +) + +# Let's allocate the unknown vector and set it to zero. Along with this vector, we also allocate the "increment" vector. +q = zeros(ndofs(ϕ)) +dq = zeros(size(q)) + +# Initial solution +q[1] = 1.0 + +# Solve +solve!(mesh, ϕ, params, dq, q) + +end #hide diff --git a/src/example/old_API/euler_naca.jl b/src/example/old_API/euler_naca.jl new file mode 100644 index 0000000..9f514ae --- /dev/null +++ b/src/example/old_API/euler_naca.jl @@ -0,0 +1,743 @@ +module EulerNaca #hide +println("Running euler_naca example...") #hide +# # Solve Euler equation around a NACA0012 airfoil + +include(string(@__DIR__, "/../src/Bcube.jl")) +using .Bcube +using LinearAlgebra +using WriteVTK +using StaticArrays +using BenchmarkTools +using Roots +using SparseArrays +using SparseDiffTools +using Profile +#using Symbolics +using InteractiveUtils +using WriteVTK + +const dir = string(@__DIR__, "/") + +function compute_residual(w, params, cache) + # destructuring + u, v = w + + limiter_projection && apply_limitation!(w, params, cache) + + # alias on measures + dΓ = params.dΓ + dΩ = params.dΩ + dΓ_wall = params.dΓ_wall + dΓ_farfield = params.dΓ_farfield + + # init a tuple of new CellVariables from `u` to store all residual contributions + du = zeros.(u) + + # compute volume residuals + du_Ω = ∫(flux_Ω(u, v))dΩ + + # Store volume residuals in `du` + du += du_Ω + + # face normals for each face domain (lazy, no computation at this step) + n_Γ = FaceNormals(dΓ) + n_Γ_wall = FaceNormals(dΓ_wall) + n_Γ_farfield = FaceNormals(dΓ_farfield) + + # flux residuals from interior faces for all variables + du_Γ = ∫(flux_Γ(u, v, n_Γ))dΓ + + # flux residuals from bc faces for all variables + du_Γ_wall = ∫(flux_Γ_wall(u, v, n_Γ_wall))dΓ_wall + du_Γ_farfield = ∫(flux_Γ_farfield(u, v, n_Γ_farfield))dΓ_farfield + + # accumulate face-flux residuals to cell residuals for all variables + # (this step will be improved when a better API will be available) + du = ((du - du_Γ) - du_Γ_wall) - du_Γ_farfield + + return du +end + +""" + flux_Ω(u,v) + +Compute volume residual using the lazy-operators approach +""" +flux_Ω(u, v) = _flux_Ω ∘ cellvar(u, v) +cellvar(u, v) = (u, ∇.(v)) +function _flux_Ω(args) + u, ∇v = args + ρ, ρu, ρE = u + ∇λ_ρ, ∇λ_ρu, ∇λ_ρE = ∇v + γ = stateInit.γ + + ncomp = length(ρu) + Id = SMatrix{ncomp, ncomp}(1.0I) + + flux_ρ = ∇λ_ρ * ρu + + vel = ρu ./ ρ + ρuu = ρu * transpose(vel) + p = (γ - 1) * (ρE - tr(ρuu) / 2) + flux_ρu = ∇λ_ρu ⊡ (ρuu .+ p .* Id) + + flux_ρE = ∇λ_ρE * ((ρE + p) .* vel) + + return (flux_ρ, flux_ρu, flux_ρE) +end + +""" + flux_Γ(u,v,n,params) + +Flux at the interface is defined by a composition of two functions: +* facevar(u,v,n) defines the input states which are needed for + the riemann flux using operator notations +* flux_roe(w) defines the Riemann flux (as usual) +""" +flux_Γ(u, v, n) = flux_roe ∘ facevar(u, v, n) +facevar(u, v, n) = (side⁻(u), side⁺(u), side⁻(v), n) + +""" + flux_roe((w1, w2, λ1, n12)) +""" +function flux_roe(args) + w1, w2, λ1, n12 = args + γ = stateInit.γ + nx = n12[1] + ny = n12[2] + ρ1, (ρu1, ρv1), ρE1 = w1 + ρ2, (ρu2, ρv2), ρE2 = w2 + λ_ρ1, λ_ρu1, λ_ρE1 = λ1 + + # Closure + u1 = ρu1 / ρ1 + v1 = ρv1 / ρ1 + u2 = ρu2 / ρ2 + v2 = ρv2 / ρ2 + p1 = pressure(ρ1, SA[ρu1, ρv1], ρE1, γ) + p2 = pressure(ρ2, SA[ρu2, ρv2], ρE2, γ) + + H2 = (γ / (γ - 1)) * p2 / ρ2 + (u2 * u2 + v2 * v2) / 2.0 + H1 = (γ / (γ - 1)) * p1 / ρ1 + (u1 * u1 + v1 * v1) / 2.0 + + R = √(ρ1 / ρ2) + invR1 = 1.0 / (R + 1) + uAv = (R * u1 + u2) * invR1 + vAv = (R * v1 + v2) * invR1 + Hav = (R * H1 + H2) * invR1 + cAv = √(abs((γ - 1) * (Hav - (uAv * uAv + vAv * vAv) / 2.0))) + ecAv = (uAv * uAv + vAv * vAv) / 2.0 + + λ1 = nx * uAv + ny * vAv + λ3 = λ1 + cAv + λ4 = λ1 - cAv + + d1 = ρ1 - ρ2 + d2 = ρ1 * u1 - ρ2 * u2 + d3 = ρ1 * v1 - ρ2 * v2 + d4 = ρE1 - ρE2 + + # computation of the centered part of the flux + flu11 = nx * ρ2 * u2 + ny * ρ2 * v2 + flu21 = nx * p2 + flu11 * u2 + flu31 = ny * p2 + flu11 * v2 + flu41 = H2 * flu11 + + # Temp variables + rc1 = (γ - 1) / cAv + rc2 = (γ - 1) / cAv / cAv + uq41 = ecAv / cAv + cAv / (γ - 1) + uq42 = nx * uAv + ny * vAv + + fdc1 = max(λ1, 0.0) * (d1 + rc2 * (-ecAv * d1 + uAv * d2 + vAv * d3 - d4)) + fdc2 = max(λ1, 0.0) * ((nx * vAv - ny * uAv) * d1 + ny * d2 - nx * d3) + fdc3 = + max(λ3, 0.0) * ( + (-uq42 * d1 + nx * d2 + ny * d3) / 2.0 + + rc1 * (ecAv * d1 - uAv * d2 - vAv * d3 + d4) / 2.0 + ) + fdc4 = + max(λ4, 0.0) * ( + (uq42 * d1 - nx * d2 - ny * d3) / 2.0 + + rc1 * (ecAv * d1 - uAv * d2 - vAv * d3 + d4) / 2.0 + ) + + duv1 = fdc1 + (fdc3 + fdc4) / cAv + duv2 = uAv * fdc1 + ny * fdc2 + (uAv / cAv + nx) * fdc3 + (uAv / cAv - nx) * fdc4 + duv3 = vAv * fdc1 - nx * fdc2 + (vAv / cAv + ny) * fdc3 + (vAv / cAv - ny) * fdc4 + duv4 = + ecAv * fdc1 + + (ny * uAv - nx * vAv) * fdc2 + + (uq41 + uq42) * fdc3 + + (uq41 - uq42) * fdc4 + + return ( + λ_ρ1 * (flu11 + duv1), + λ_ρu1 * (SA[flu21 + duv2, flu31 + duv3]), + λ_ρE1 * (flu41 + duv4), + ) +end + +""" + flux_Γ_farfield(u, v, n) +""" +facevar_farfield(u, v, n) = (side⁻(u), side⁻(v), n) +# Note: +# Currently operator composition works on tuples which contain 'CellVariable's only. +# Then, the farfield state 'u_inf', which is used as the rigth state of the Roe flux at the farfield boundary condition, +# is directly given the function 'flux_roe'. Ultimately, we would like to be able to write something like: +# flux_Γ_farfield(u,v,n) = flux_roe ∘ facevar_farfield(u,stateBcFarfield.u_inf, v,n) +function flux_Γ_farfield(u, v, n) + (w -> flux_roe((w[1], stateBcFarfield.u_inf, w[2], w[3]))) ∘ facevar_farfield(u, v, n) +end + +""" + flux_Γ_wall(u, v, n) +""" +facevar_wall(u, v, n) = (side⁻(u), side⁻(v), n) +flux_Γ_wall(u, v, n) = _flux_Γ_wall ∘ facevar_wall(u, v, n) + +function _flux_Γ_wall((w1, λ1, n12)) + γ = stateInit.γ + ρ1, (ρu1, ρv1), ρE1 = w1 + λ_ρ1, λ_ρu1, λ_ρE1 = λ1 + + p1 = pressure(ρ1, SA[ρu1, ρv1], ρE1, γ) + + flu11 = 0.0 + flu21 = p1 * n12 + flu41 = 0.0 + + return (λ_ρ1 * (flu11), λ_ρu1 * (flu21), λ_ρE1 * (flu41)) +end + +# Now let's write the assembling method which computes the volumic terms and assembles them with the surfacic flux terms. +# We call this method `explicit_step!` because it returns the ``\Delta \phi`` associated with the selected explicit time +# scheme. +function explicit_step(w, params, cache, Δt, t) + (ρ, ρu, ρE), λ = w + + _dρ, _dρu, _dρE, = compute_residual(w, params, cache) + + for x in (_dρ, _dρu, _dρE) + lmul!(Δt, x) + end + + ρ_new = get_values(ρ) .+ cache.invMass_ρ * get_values(_dρ) + ρu_new = get_values(ρu) .+ cache.invMass_ρu * get_values(_dρu) + ρE_new = get_values(ρE) .+ cache.invMass_ρE * get_values(_dρE) + + return ρ_new, ρu_new, ρE_new +end + +function mass_matrix(u, v) + u_ρ, u_ρu, u_ρE = get_trial_function.(u) + v_ρ, v_ρu, v_ρE = v + (u_ρ * transpose(v_ρ), u_ρu * transpose(v_ρu), u_ρE * transpose(v_ρE)) +end + +function compute_mass_matrix(w, params) + u, v = w + dΩ = params.dΩ + ∫(mass_matrix(u, v))dΩ +end + +function implicit_step(w, params, cache, Δt, t, jac_cache) + u, v = w + dofs = cache.dofs + system = cache.system + dofs2 = similar(dofs) + pack!(dofs, u, system) + + f! = (out, in) -> rhs!(out, in, w, params, cache) + if isa(jac_cache, Nothing) + println("Computing jac_cache...") + jac_cache = ForwardColorJacCache( + f!, + dofs, + nothing; + dx = similar(dofs), + colorvec = get_jac_colors(system), + sparsity = get_jac_sparsity(system), + ) + @show maximum(get_jac_colors(system)) + debug_implicit && write_sparsity(system, "sparsity") + end + + J = get_jac_sparsity(system) + forwarddiff_color_jacobian!(J, f!, dofs, jac_cache) + + f!(dofs2, dofs) + _M = compute_mass_matrix(w, params) + M = sparse(_M, u, get_mapping(system)) + + Δtmin = minimum(get_values(Δt)) + A = M ./ Δtmin - J + dofs .= A \ dofs2 + Δu = zeros.(u) + unpack!(Δu, dofs, system) + + return coef_relax_implicit .* get_values.(Δu) .+ get_values.(u), jac_cache +end + +function rhs!(out, in, w, params, cache) + u, v = w + u2 = zeros.(u, eltype(in)) + unpack!(u2, in, cache.system) + u3 = compute_residual((u2, v), params, cache) + pack!(out, u3, cache.system) + return nothing +end + +function sparse2vtk( + a::AbstractSparseMatrix, + name::String = string(@__DIR__, "/../myout/sparse"), +) + vtk_write_array(name, Array(a), "my_property_name") +end + +function RK3_SSP(f::Function, w) + u, v = w + + _u₁ = f((u, v)) + u₁ = zeros.(u) + map(set_values!, u₁, _u₁) + + _u₂ = f((u₁, v)) + u₂ = zeros.(u) + map( + (a, b, c) -> set_values!(a, (3.0 / 4) .* get_values(b) .+ (1.0 / 4) .* (c)), + u₂, + u, + _u₂, + ) + + _u₃ = f((u₂, v)) + u₃ = map((a, b) -> (1.0 / 3) .* get_values(a) .+ (2.0 / 3) .* (b), u, _u₃) + + return u₃ +end + +mutable struct VtkHandler + basename::String + basename_residual::String + ite::Int + VtkHandler(basename) = new(basename, basename * "_residual", 0) +end + +""" + Write solution (at cell centers) to vtk + Wrapper for `write_vtk` +""" +function append_vtk(vtk, mesh, vars, t, params; res = nothing) + # Values on center + # Mean cell values + name2val_mean = (; zip(get_name.(vars), mean_values.(vars, degquad))...) + p_mean = + pressure.( + name2val_mean[:ρ], + name2val_mean[:ρu], + name2val_mean[:ρE], + params.stateInit.γ, + ) + + vtk_degree = maximum(x -> get_order(function_space(x)), vars) + vtk_degree = max(1, mesh_degree, vtk_degree) + name2val_dg = (; zip(get_name.(vars), var_on_nodes_discontinuous.(vars, vtk_degree))...) + + dict_vars_dg = Dict( + "rho" => (name2val_dg[:ρ], VTKPointData()), + "rhou" => (name2val_dg[:ρu], VTKPointData()), + "rhoE" => (name2val_dg[:ρE], VTKPointData()), + "rho_mean" => (name2val_mean[:ρ], VTKCellData()), + "rhou_mean" => (name2val_mean[:ρu], VTKCellData()), + "rhoE_mean" => (name2val_mean[:ρE], VTKCellData()), + ) + Bcube.write_vtk_discontinuous( + vtk.basename * "_DG", + vtk.ite, + t, + mesh, + dict_vars_dg, + vtk_degree; + append = vtk.ite > 0, + ) + + #residual: + if !isa(res, Nothing) + vtkfile = vtk_grid(vtk.basename_residual, Float64.(res.iter), [0.0, 1.0]) + for (k, valₖ) in enumerate(res.val) + vtkfile["res_" * string(k), VTKPointData()] = [valₖ valₖ] + end + vtk_save(vtkfile) + end + + # Update counter + vtk.ite += 1 + + return nothing +end + +function init!(u, initstate) + AoA = initstate.AoA + Minf = initstate.M_inf + Pinf = initstate.P_inf + Tinf = initstate.T_inf + r = initstate.r_gas + γ = initstate.γ + + ρinf = Pinf / r / Tinf + ainf = √(γ * r * Tinf) + Vinf = Minf * ainf + ρVxinf = ρinf * Vinf * cos(AoA) + ρVyinf = ρinf * Vinf * sin(AoA) + ρEinf = Pinf / (γ - 1) + 0.5 * ρinf * Vinf^2 + + ρ, ρu, ρE = u + set_values!(ρ, x -> ρinf; degquad = degquad) + set_values!(ρu, (x -> ρVxinf, x -> ρVyinf); degquad = degquad) + set_values!(ρE, x -> ρEinf; degquad = degquad) +end + +function run(stateInit, stateBcFarfield, degree) + @show degree, degquad + + if debug_implicit + tmp_path = "tmp.msh" + gen_rectangle_mesh( + tmp_path, + :quad; + nx = 5, + ny = 5, + lx = 1.0, + ly = 1.0, + xc = 0.0, + yc = 0.0, + ) + mesh = read_msh(tmp_path) + else + mesh = read_msh(dir * "../input/mesh/naca0012_o" * string(mesh_degree) * ".msh") + end + + dimcar = compute_dimcar(mesh) + + # Create a `CellVariable` + fs = FunctionSpace(fspace, degree) + fes1 = FESpace(fs, :discontinuous; size = 1) # size=1 for scalar variable + fes2 = FESpace(fs, :discontinuous; size = 2) # DG, vectoriel + ρ = CellVariable(:ρ, mesh, fes1) + ρu = CellVariable(:ρu, mesh, fes2) + ρE = CellVariable(:ρE, mesh, fes1) + λ₁ = TestFunction(mesh, fes1) + λ₂ = TestFunction(mesh, fes2) + u, v = ((ρ, ρu, ρE), (λ₁, λ₂, λ₁)) + + # select an initial configurations: + init!(u, stateInit) + + DMPrelax = DMPcurv₀ .* dimcar .^ 2 + + # Then we create a `NamedTuple` to hold the simulation parameters. + params = ( + degree = degree, + degquad = degquad, + stateInit = stateInit, + stateBcFarfield = stateBcFarfield, + DMPrelax = DMPrelax, + ) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), degquad) + dΓ = Measure(InteriorFaceDomain(mesh), degquad) + + # Declare periodic boundary conditions and + # create associated domains and measures + if debug_implicit + Γ_wall = BoundaryFaceDomain(mesh, ("South", "North")) + Γ_farfield = BoundaryFaceDomain(mesh, ("East", "West")) + else + Γ_wall = BoundaryFaceDomain(mesh, ("NACA",)) + Γ_farfield = BoundaryFaceDomain(mesh, ("FARFIELD",)) + end + dΓ_wall = Measure(Γ_wall, degquad) + dΓ_farfield = Measure(Γ_farfield, degquad) + + params = (params..., dΓ = dΓ, dΩ = dΩ, dΓ_wall = dΓ_wall, dΓ_farfield = dΓ_farfield) + + # create CellData to store limiter values + limρ = CellData(ones(ncells(mesh))) + limAll = CellData(ones(ncells(mesh))) + params = (params..., limρ = limρ, limAll = limAll) + + # Init vtk handler + mkpath(outputpath) + vtk = VtkHandler( + outputpath * "euler_naca_mdeg" * string(mesh_degree) * "_deg" * string(degree), + ) + + # Init time + time = 0.0 + + # Save initial solution + append_vtk(vtk, mesh, u, time, params) + + # Build the cache and store everything you want to compute only once (such as the mass matrice inverse...) + invMass_ρ = InvMassMatrix(ρ, Val(degquad)) + invMass_ρu = InvMassMatrix(ρu, Val(degquad)) + invMass_ρE = InvMassMatrix(ρE, Val(degquad)) + cache = (invMass_ρ = invMass_ρ, invMass_ρu = invMass_ρu, invMass_ρE = invMass_ρE) + + system = System(u; cacheJacobian = true) + dofs = zeros(eltype(get_values(ρ)), get_ndofs(system)) + cache = (cache..., dofs = dofs, system = system) + jac_cache = nothing + + Δt = CellData(Δt₀ .* ones(typeof(Δt₀), ncells(mesh))) + + res = (iter = Int[], val = map(x -> typeof(norm(get_values(x)))[], u)) + + # Let's loop to solve the equation. + for i in 1:nite_max + _CFL = fun_CFL(i) + + compute_timestep!(Δt, u, mesh, dimcar, degquad, _CFL, params) + !(localTimeStep) && set_values!(Δt, minimum(Δt)) + + ## Infos + if (i % Int(max(floor(nite_max / (nout * 10)), 1)) == 0) + println("---") + println("Iteration ", i) + @show minimum(Δt), _CFL + end + + ## Step forward in time + if timeScheme == :ForwardEuler + u_new = explicit_step((u, v), params, cache, Δt, time) + elseif timeScheme == :BackwardEuler + u_new, jac_cache = implicit_step((u, v), params, cache, Δt, time, jac_cache) + elseif timeScheme == :RK3 + stepper = w -> explicit_step(w, params, cache, Δt, time) + u_new = RK3_SSP(stepper, (u, v)) + else + println("Unknown time scheme: ", timeScheme) + end + + res_ = norm.(get_values.(u) .- u_new) + push!(res.iter, i) + for (k, valₖ) in enumerate(res.val) + length(valₖ) > 0 ? valₖ₀ = valₖ[1] : valₖ₀ = 1.0 + push!(valₖ, res_[k] / valₖ₀) + end + println("Residual = ", [res.val[k][end] for k in 1:length(res.val)]) + map(set_values!, u, u_new) + + time += minimum(get_values(Δt)) + + ## Write solution to file (respecting max. number of output) + if (i % Int(max(floor(nite_max / nout), 1)) == 0) + println("writing vtk...") + append_vtk(vtk, mesh, u, time, params; res = res) + end + end + + append_vtk(vtk, mesh, u, time, params; res = res) + + @show degmass, degquad + println("Benchmarking 'explicit_step':") + @btime explicit_step(($u, $v), $params, $cache, $Δt, $time) + if timeScheme == :BackwardEuler + println("Benchmarking 'implicit_step':") + @btime implicit_step(($u, $v), $params, $cache, $Δt, $time, $jac_cache) + end + println("ndofs total = ", sum(length.(get_values.(u)))) + + if timeScheme == :BackwardEuler + Profile.clear() + Profile.clear_malloc_data() + @profile begin + for i in 1:5 + implicit_step((u, v), params, cache, Δt, time, jac_cache) + end + end + end + # And here is an animation of the result: + # ```@raw html + # drawing + # ``` +end + +function compute_timestep!(Δt, u, mesh, dimcar, degquad, CFL, params) + λ = Bcube._minmax_cells(euler_maxeigval(u, params.stateInit.γ), mesh, Val(degquad)) + λmax = getindex.(λ, 2) + set_values!(Δt, CFL .* dimcar ./ λmax) +end + +euler_maxeigval(u, γ) = (x -> _euler_maxeigval(x, γ)) ∘ (u,) +function _euler_maxeigval(args, γ) + u, = args + ρ, ρu, ρE = u + vel = ρu ./ ρ + ρuu = ρu * transpose(vel) + p = (γ - 1) * (ρE - tr(ρuu) / 2) + sqrt(tr(ρuu) / ρ) + sqrt(γ * p / ρ) +end + +compute_Pᵢ(P, γ, M) = P * (1 + 0.5 * (γ - 1) * M^2)^(γ / (γ - 1)) +compute_Tᵢ(T, γ, M) = T * (1 + 0.5 * (γ - 1) * M^2) + +function compute_dimcar(mesh) + fs = FunctionSpace(:Taylor, 0) + fes = FESpace(fs, :discontinuous; size = 1) + a = CellVariable(:a, mesh, fes) + set_values!(a, x -> 1.0) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), degquad) + dΓ = Measure(InteriorFaceDomain(mesh), degquad) + dΓ_bc = Measure(BoundaryFaceDomain(mesh, Tuple(values(boundary_names(mesh)))), degquad) + + int_Ω = ∫(a * SA[1.0])dΩ + int_Γ = ∫(a * SA[1.0])dΓ + int_Γ_bc = ∫(a * SA[1.0])dΓ_bc + vol = zeros(a) + int_Ω + surf = (zeros(a) + int_Γ) + int_Γ_bc + dimcar1 = get_values(vol) ./ get_values(surf) + return dimcar1 +end + +function apply_limitation!(w, params, cache) + u, v = w + ρ, ρu, ρE = u + mesh = ρ.mesh + + ρ_mean, ρu_mean, ρE_mean = CellData.(mean_values.((ρ, ρu, ρE), Val(params.degquad))) + + _limρ, _ρ_proj = linear_scaling_limiter( + ρ, + params.degquad; + bounds = (ρmin₀, ρmax₀), + DMPrelax = params.DMPrelax, + invMass = cache.invMass_ρ, + ) + ρ_proj = zeros(ρ) + set_values!(ρ_proj, _ρ_proj) + op_t = limiter_param_p ∘ (ρ_proj, ρu, ρE, ρ_mean, ρu_mean, ρE_mean) + t = Bcube._minmax_cells(op_t, mesh, Val(params.degquad)) + tmin = CellData(getindex.(t, 1)) + + #@show extrema(get_values(_limρ)), extrema(get_values(tmin)) + if eltype(_limρ) == eltype(params.limρ) + set_values!(params.limρ, get_values(_limρ)) + set_values!(params.limAll, get_values(tmin)) + end + + set_values!(ρ, Bcube._projection(ρ_proj, tmin, ρ_mean, cache.invMass_ρ, degquad)) + set_values!(ρu, Bcube._projection(ρu, tmin, ρu_mean, cache.invMass_ρu, degquad)) + set_values!(ρE, Bcube._projection(ρE, tmin, ρE_mean, cache.invMass_ρE, degquad)) + nothing +end + +function pressure(ρ, ρu, ρE, γ) + vel = ρu ./ ρ + ρuu = ρu * transpose(vel) + p = (γ - 1) * (ρE - tr(ρuu) / 2) + return p +end + +function limiter_param_p((ρ̂, ρu, ρE, ρ_mean, ρu_mean, ρE_mean)) + γ = stateInit.γ + + p = pressure(ρ̂, ρu, ρE, γ) + if p ≥ pmin₀ + t = 1.0 + else + @show p, ρ̂, ρu, ρE + @show ρ_mean, ρu_mean, ρE_mean + @show pressure(ρ_mean, ρu_mean, ρE_mean, γ) + #@show p, ρ̂, ρu, ρE, ρ_mean, ρu_mean, ρE_mean + fₜ = + t -> + pressure( + t * ρ̂ + (1 - t) * ρ_mean, + t * ρu + (1 - t) * ρu_mean, + t * ρE + (1 - t) * ρE_mean, + γ, + ) - pmin₀ + bounds = (0.0, 1.0) + t = find_zero(fₜ, bounds, Bisection()) + end + return t +end + +function bc_state_farfield(AoA, M, P, T, r, γ) + a = √(γ * r * T) + vn = M * a + ρ = P / r / T + ρu = SA[ρ * vn * cos(AoA), ρ * vn * sin(AoA)] + ρE = P / (γ - 1) + 0.5 * ρ * vn^2 + return (ρ, ρu, ρE) +end + +const debug_implicit = false +const degreemax = 2# Function-space order (Taylor(0) = first order Finite Volume) +const mesh_degree = 2 +const fspace = :Lagrange +const limiter_projection = false +const ρmin₀ = 1.0e-10 +const ρmax₀ = 1.0e+10 +const pmin₀ = 1.0e-10 +const pmax₀ = 1.0e+10 +const DMPcurv₀ = 10.0 +const localTimeStep = true + +const stateInit = ( + AoA = deg2rad(4.0), + M_inf = 0.3, + P_inf = 88888.0, + T_inf = 263.0, + r_gas = 287.0, + γ = 1.4, +) +const nite_max = 300 #300000 # Number of time iteration(s) +const CFL = 100 * 0.1 * 1.0 / (2 * degreemax + 1) +const timeScheme = :BackwardEuler # :BackwardEuler, :ForwardEuler, :RK3 +const CFLratio = 1.5 +const CFLmin = 1 +const CFLmax = 3000 +const iter_ramp_min_CFL = 1 +const iter_ramp_max_CFL = 200 +const coef_relax_implicit = 0.5 +const nout = 100 # Number of time steps to save +const mass_matrix_in_solve = false +const Δt₀ = 1.e-7 +const outputpath = string(@__DIR__, "/../myout/euler_naca/") + +function fun_CFL(i) + k = (i - iter_ramp_min_CFL) / (iter_ramp_max_CFL - iter_ramp_min_CFL) + k = max(0.0, min(1.0, k)) + CFL = (1 - k) * CFLmin + k * CFLmax + return CFL +end + +const degquad = 4 # Int(ceil((degmass+3)/2)) + +const stateBcFarfield = ( + AoA = stateInit.AoA, + M_inf = stateInit.M_inf, + Pᵢ_inf = compute_Pᵢ(stateInit.P_inf, stateInit.γ, stateInit.M_inf), + Tᵢ_inf = compute_Tᵢ(stateInit.T_inf, stateInit.γ, stateInit.M_inf), + u_inf = bc_state_farfield( + stateInit.AoA, + stateInit.M_inf, + stateInit.P_inf, + stateInit.T_inf, + stateInit.r_gas, + stateInit.γ, + ), + r_gas = stateInit.r_gas, + γ = stateInit.γ, +) + +run(stateInit, stateBcFarfield, degreemax) + +end #hide diff --git a/src/example/old_API/flat_heater.jl b/src/example/old_API/flat_heater.jl new file mode 100644 index 0000000..406429f --- /dev/null +++ b/src/example/old_API/flat_heater.jl @@ -0,0 +1,256 @@ +module flat_heater #hide +println("Running flat heater API example...") #hide + +const dir = string(@__DIR__, "/../") # Bcube dir +include(dir * "src/Bcube.jl") +using .Bcube +using LinearAlgebra +using WriteVTK +using Printf +using SparseArrays + +function f1(u, v, params) + λ, = v + return params.η * ∇(λ) * transpose(∇(λ)) +end + +function f2(u, v, params) + λ, = v + return params.ρCp * λ * transpose(λ) +end + +function f3(u, v, params) + λ, = v + return params.q * λ +end + +function f4(u, v, params) + λ, = v + (params.htc * λ * transpose(λ),) +end + +function f5(u, v, params) + λ, = v + (params.htc * params.Tr * λ,) +end + +# convective boundary condition +const htc = 10000.0 +const Tr = 260.0 +const phi = 0.0 + +# heat source +const l1_h = 60.0e-3 +const l2_h = 300.0e-3 +const e_h = 0.2e-3 + +const qtot = 50.0 +const qheat = qtot / (l1_h * l2_h * e_h) +@show qheat + +const degree = 1 + +function run() + + # Read mesh + mesh, el_names, el_names_inv, el_cells, glo2loc_cell_indices = + read_msh_with_cell_names(dir * "input/mesh/flat_heater.msh", 2) + + fs = FunctionSpace(:Lagrange, degree) + fes = FESpace(fs, :continuous; size = 1) # size=1 for scalar variable + ϕ = CellVariable(:ϕ, mesh, fes) + # Create a `TestFunction` + λ = TestFunction(mesh, fes) + + u, v = ((ϕ,), (λ,)) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + + nd = ndofs(ϕ) + + #Adense = zeros(Float64, (nd,nd)) + #Mdense = zeros(Float64, (nd,nd)) + L = zeros(Float64, (nd)) + + qtmp = zeros(Float64, (ncells(mesh))) + heater = vcat(el_cells[el_names_inv["HEATER"]]) + for i in heater + qtmp[glo2loc_cell_indices[i]] = qheat + end + volTag = zeros(Int64, (ncells(mesh))) + for k in el_names + elements = el_cells[el_names_inv[k[2]]] + for i in elements + volTag[glo2loc_cell_indices[i]] = k[1] + end + end + + mat_1 = el_cells[el_names_inv["MAT_1"]] + mat_2 = el_cells[el_names_inv["MAT_2"]] + + rho = zeros(Float64, (ncells(mesh))) + cp = zeros(Float64, (ncells(mesh))) + lamda = zeros(Float64, (ncells(mesh))) + rhoCp = zeros(Float64, (ncells(mesh))) + for i in heater + rho[glo2loc_cell_indices[i]] = 1500.0 + cp[glo2loc_cell_indices[i]] = 900.0 + lamda[glo2loc_cell_indices[i]] = 120.0 + rhoCp[glo2loc_cell_indices[i]] = + rho[glo2loc_cell_indices[i]] * cp[glo2loc_cell_indices[i]] + end + for i in mat_1 + rho[glo2loc_cell_indices[i]] = 2000.0 + cp[glo2loc_cell_indices[i]] = 1000.0 + lamda[glo2loc_cell_indices[i]] = 120.0 + rhoCp[glo2loc_cell_indices[i]] = + rho[glo2loc_cell_indices[i]] * cp[glo2loc_cell_indices[i]] + end + for i in mat_2 + rho[glo2loc_cell_indices[i]] = 2500.0 + cp[glo2loc_cell_indices[i]] = 900.0 + lamda[glo2loc_cell_indices[i]] = 10.0 + rhoCp[glo2loc_cell_indices[i]] = + rho[glo2loc_cell_indices[i]] * cp[glo2loc_cell_indices[i]] + end + + #set_values!(q, qtmp) + #set_values!(ρ, rho) + #set_values!(Cp, cp) + #set_values!(η, lamda) + + q = CellData(qtmp) + ρCp = CellData(rhoCp) + η = CellData(lamda) + + params = (q = q, ρCp = ρCp, η = η, htc = htc, Tr = Tr) + #params = (q = 0.0, ρCp= 1.0e6, η=160.0, htc=300.0, Tr=280.0) + + ndm = max_ndofs(ϕ) + nhint = ndm * ndm * ncells(mesh) + Aval = Float64[] + rowA = Int[] + colA = Int[] + sizehint!(Aval, nhint) + sizehint!(rowA, nhint) + sizehint!(colA, nhint) + + dict_vars = Dict( + "rhoCp" => (get_values(ρCp), VTKCellData()), + "lam" => (get_values(η), VTKCellData()), + "qheat" => (get_values(q), VTKCellData()), + ) + write_vtk(dir * "myout/params_flat_heater", 0, 0.0, mesh, dict_vars) + + Γ_front = BoundaryFaceDomain(mesh, ("FRONT",)) + dΓ_front = Measure(Γ_front, 2 * degree + 1) + + _AFR = ∫(f4(u, v, params))dΓ_front + _LFR = ∫(f5(u, v, params))dΓ_front + + # compute matrices associated to bilinear and linear forms + _A = ∫(f1(u, v, params))dΩ + _M = ∫(f2(u, v, params))dΩ + _L = ∫(f3(u, v, params))dΩ + + for (ic, val) in result(_L) + idof = get_dof(ϕ, ic) + ndof = length(idof) + + for i in 1:ndof + L[idof[i]] += val[i] + end + end + + for FR_res in _AFR.result + ic = FR_res[1][1] + + idof = get_dof(ϕ, ic) + ndof = length(idof) + + #@show ic, idof + for i in 1:length(idof) + for j in 1:length(idof) + push!(Aval, FR_res[3][1][i, j]) + push!(rowA, idof[i]) + push!(colA, idof[j]) + end + end + end + + for LFR_res in _LFR.result + ic = LFR_res[1][1] + + idof = get_dof(ϕ, ic) + ndof = length(idof) + + for i in 1:length(idof) + L[idof[i]] += LFR_res[3][1][i] + end + end + + AFR = sparse(rowA, colA, Aval, nd, nd) + #M = sparse(rowM,colM,Mval, nd, nd) + #A = Adense + #M = Mdense + + A = sparse(_A, ϕ) + AFR + M = sparse(_M, ϕ) + + time = 0.0 + dt = 0.1 + totalTime = 10.0 + + Miter = (M + dt * A) + + U0 = 260.0 * ones(Float64, nd) + U1 = 260.0 * ones(Float64, nd) + + # set_values!(ϕ, x -> 260.0) Not implemented for continuous elements + set_values!(ϕ, U1) + # here a Dirichlet boundary condition is applied on "West". p and T are imposed to 1 (solid). + #for idof in bnd_dofs["REAR"] + # Miter[idof,:] .= 0.0 + # Miter[idof,idof] = 1.0 + # U0[idof] = 300.0 + #end + + #dict_vars = Dict(@sprintf("Temperature") => (get_values(ϕ), VTKPointData())) + dict_vars = Dict("Temperature" => (var_on_centers(ϕ), VTKCellData())) + write_vtk(dir * "myout/result_flat_heater", 0, 0.0, mesh, dict_vars) + + itime = 0 + while time <= totalTime + time = time + dt + itime = itime + 1 + @show time, itime + RHS = dt * L + M * U0 + #for idof in bnd_dofs["REAR"] + # RHS[idof] = 300.0 + #end + U1 = Miter \ RHS + U0[:] .= U1[:] + + @show extrema(U1) + + set_values!(ϕ, U1) + + if itime % 10 == 0 + #dict_vars = Dict(@sprintf("Temperature") => (get_values(ϕ), VTKPointData())) + dict_vars = Dict("Temperature" => (var_on_centers(ϕ), VTKCellData())) + write_vtk(dir * "myout/result_flat_heater", itime, time, mesh, dict_vars) + end + end + + Usteady = A \ L + set_values!(ϕ, Usteady) + dict_vars = Dict("Temperature" => (var_on_centers(ϕ), VTKCellData())) + write_vtk(dir * "myout/result_flat_heater", itime + 1, time + 10.0, mesh, dict_vars) + + @show extrema(Usteady) +end + +run() + +end #hide diff --git a/src/example/old_API/heat_equation_run.jl b/src/example/old_API/heat_equation_run.jl new file mode 100644 index 0000000..2c2fb17 --- /dev/null +++ b/src/example/old_API/heat_equation_run.jl @@ -0,0 +1,559 @@ +module heat_equation_API #hide +println("Running heat equation API 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 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 = string(@__DIR__, "/../") # Bcube dir +using Bcube +using LinearAlgebra +using WriteVTK +using Printf +using SparseArrays +using DelimitedFiles +using BenchmarkTools +using Profile +using StaticArrays +using Cthulhu + +# Function that defines the bilinear form a: +function f1(u, v, η) + λ, = v + return η .* ∇(λ) * transpose(∇(λ)) +end + +# Function that defines the bilinear form m: +function f2(u, v, params) + λ, = v + return params.ρCp * λ * transpose(λ) +end + +# Function that defines the linear form l: +function f3(u, v, q) + λ, = v + return q * λ +end + +# Function that defines the bilinear par of a Fourier-Robin condition: +function f4(u, v, params) + λ, = v + return params.htc * λ * transpose(λ) +end + +# Function that defines the linear par of a Fourier-Robin condition: +function f5(u, v, params) + λ, = v + return (params.htc * params.Tr + params.phi) * λ +end + +function f_Dirichlet(ud, v, params) + λ, = v + φ, = ud + return params.η * ∇(φ) * transpose(∇(λ)) +end + +function T_analytical_twoLayer(x, lam1, lam2, T0, T1, L) + if x < 0.5 * L + T = 2.0 * (lam2 / (lam1 + lam2)) * ((T1 - T0) / L) * x + T0 + else + T = 2.0 * (lam1 / (lam1 + lam2)) * ((T1 - T0) / L) * (x - L) + T1 + end + return T +end + +# convective boundary condition +const htc = 100.0 +const Tr = 268.0 +const phi = 100.0 + +const degree = 2 +const outputpath = string(@__DIR__, "/../myout/heat_equation/") + +# Function that runs the steady single layer case: +function run_steady() + println("Running steady single layer case") + + # Read mesh + mesh = read_msh(dir * "input/mesh/domainSquare_tri.msh", 2) + + # Build function space and associated Trial and Test FE spaces. + # We impose a Dirichlet condition with a temperature of 260K + # on boundary "West" + fs = FunctionSpace(:Lagrange, degree) + U = TrialFESpace(fs, mesh, Dict("West" => 260.0)) + V = TestFESpace(U) + + # Define measures for cell integration + dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + + # Physical parameters + q = 1500.0 + λ = 100.0 + η = 100.0 + + # Define bilinear and linear forms + a(u, v) = ∫(η * ∇(u) ⋅ ∇(v))dΩ + l(v) = ∫(q * v)dΩ + + # Create an affine FE system and solve it. The result is a FEFunction. + # We can interpolate it on mesh centers + sys = Bcube.AffineFESystem(a, l, U, V) + ϕ = Bcube.solve(sys) + Tcn = var_on_centers(ϕ, mesh) + + # Compute analytical solution for comparison. Apply the analytical solution + # on mesh centers + T_analytical = x -> 260.0 + (q / λ) * x[1] * (1.0 - 0.5 * x[1]) + Tca = map(T_analytical, Bcube.get_cell_centers(mesh)) + + # Write both the obtained FE solution and the analytical solution. + mkpath(outputpath) + dict_vars = + Dict("Temperature" => (Tcn, VTKCellData()), "Temperature_a" => (Tca, VTKCellData())) + write_vtk(outputpath * "result_steady_heat_equation", 0, 0.0, mesh, dict_vars) + + # Compute and display the error + @show norm(Tcn .- Tca, Inf) / norm(Tca, Inf) +end + +# Function that runs the unsteady single layer case: +function run_unsteady() + println("Running unsteady single layer case") + + # Read mesh + mesh = read_msh(dir * "input/mesh/domainSquare_tri_2.msh", 2) + + # Build function space and associated Trial and Test FE spaces. + # We impose a Dirichlet condition with a temperature of 260K + # on boundary "West" + fs = FunctionSpace(:Lagrange, degree) + U = TrialFESpace(fs, mesh, Dict("West" => 260.0)) + V = TestFESpace(U) + + # Define measures for cell integration + dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + + # Physical parameters + q = 1500.0 + λ = 150.0 + ρCp = 100.0 * 200.0 + η = λ + totalTime = 100.0 + + # Numerical parameters + Δt = 0.1 + + # Compute matrices associated to bilinear and linear forms + a(u, v) = ∫(η * ∇(u) ⋅ ∇(v))dΩ + m(u, v) = ∫(ρCp * u ⋅ v)dΩ + l(v) = ∫(q * v)dΩ + + # Assemble + A = assemble_bilinear(a, U, V) + M = assemble_bilinear(m, U, V) + L = assemble_linear(l, V) + + # Compute a vector of dofs whose values are zeros everywhere + # except on dofs lying on a Dirichlet boundary, where they + # take the Dirichlet value + Ud = Bcube.assemble_dirichlet_vector(U, V, mesh) + + # Apply lift + L = L - A * Ud + + # Apply homogeneous dirichlet condition + Bcube.apply_homogeneous_dirichlet_to_vector!(L, U, V, mesh) + Bcube.apply_dirichlet_to_matrix!((A, M), U, V, mesh) + + # Form time iteration matrix + # (note that this is bad for performance since up to now, + # M and A are sparse matrices) + Miter = factorize(M + Δt * A) + + # Init solution + ϕ = FEFunction(U, 260.0) + + # Write initial solution + mkpath(outputpath) + dict_vars = Dict("Temperature" => (var_on_centers(ϕ, mesh), VTKCellData())) + write_vtk(outputpath * "result_unsteady_heat_equation", 0, 0.0, mesh, dict_vars) + + # Time loop + itime = 0 + t = 0.0 + while t <= totalTime + t += Δt + itime = itime + 1 + @show t, itime + + # Compute rhs + rhs = Δt * L + M * (get_dof_values(ϕ) .- Ud) + + # Invert system and apply inverse shift + set_dof_values!(ϕ, Miter \ rhs .+ Ud) + + # Write solution + if itime % 10 == 0 + dict_vars = Dict("Temperature" => (var_on_centers(ϕ, mesh), VTKCellData())) + write_vtk( + outputpath * "result_unsteady_heat_equation", + itime, + t, + mesh, + dict_vars; + append = true, + ) + end + end +end + +# Function that runs the steady two layer case: +function run_steady_twoLayer() + println("Running steady two layer case") + # Read mesh + with_cell_names = true + mesh, el_names, el_names_inv, el_cells, glo2loc_cell_indices = + read_msh_with_cell_names(dir * "input/mesh/domainTwoLayer_tri.msh", 2) + + fs = FunctionSpace(:Lagrange, degree) + U = TrialFESpace(fs, mesh) + V = TestFESpace(U) + + ϕ = CellVariable(:ϕ, mesh, fes) + # φ = CellVariable(:φ, mesh, fes) + + # Create a `TestFunction` + # λ = TestFunction(mesh, fes) + + # u, v = ((ϕ,), (λ,)) + # ud = (φ,) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + + nd = get_ndofs(U) + + T0 = 260.0 + bnd_dofs0 = boundary_dofs(ϕ, "West") + # here a Dirichlet boundary condition is applied on "West". p and T are imposed to 1 (solid). + Ud = zeros(Float64, (nd)) + for idof in bnd_dofs0 + Ud[idof] = T0 + end + + T1 = 300.0 + bnd_dofs1 = boundary_dofs(ϕ, "East") + # here a Dirichlet boundary condition is applied on "West". p and T are imposed to 1 (solid). + for idof in bnd_dofs1 + Ud[idof] = T1 + end + + # set_values!(φ, Ud) # mbouyges -> I believe this is a mistake in the original tutorial + + #Adense = zeros(Float64, (nd,nd)) + #Mdense = zeros(Float64, (nd,nd)) + L = zeros(Float64, (nd)) + + lamda1 = 150.0 + lamda2 = 10.0 + + mat_1 = el_cells[el_names_inv["Domain_1"]] + mat_2 = el_cells[el_names_inv["Domain_2"]] + + rho = zeros(Float64, (ncells(mesh))) + cp = zeros(Float64, (ncells(mesh))) + lamda = zeros(Float64, (ncells(mesh))) + rhoCp = zeros(Float64, (ncells(mesh))) + + for i in mat_1 + rho[glo2loc_cell_indices[i]] = 2000.0 + cp[glo2loc_cell_indices[i]] = 1000.0 + lamda[glo2loc_cell_indices[i]] = lamda1 + rhoCp[glo2loc_cell_indices[i]] = + rho[glo2loc_cell_indices[i]] * cp[glo2loc_cell_indices[i]] + end + for i in mat_2 + rho[glo2loc_cell_indices[i]] = 2500.0 + cp[glo2loc_cell_indices[i]] = 900.0 + lamda[glo2loc_cell_indices[i]] = lamda2 + rhoCp[glo2loc_cell_indices[i]] = + rho[glo2loc_cell_indices[i]] * cp[glo2loc_cell_indices[i]] + end + + qtmp = zeros(Float64, (ncells(mesh))) + + q = CellData(qtmp) + ρCp = CellData(rhoCp) + η = CellData(lamda) + + params = (q = q, ρCp = ρCp, η = η) + #params = (q = qtmp, ρCp= rhoCp, η=lamda, htc=htc, Tr=Tr, phi=phi) + + # compute matrices associated to bilinear and linear forms + a(u, v) = ∫(params.η * ∇(u) ⋅ ∇(v))dΩ + A = assemble_bilinear(a, U, V) + # _A = ∫(f1(u, v, params),)dΩ + #_Ad = ∫( f_Dirichlet(ud, v, params) )dΩ + l(v) = ∫(params.q * v)dΩ + L = assemble_linear(l, V) + # _L = ∫(f3(u, v, params),)dΩ + + # A = sparse(_A, ϕ) + + # for (ic, val) in result(_L) + # idof = get_dof(ϕ, ic) + # ndof = length(idof) + + # for i in 1:ndof + # L[idof[i]] += val[i] + # end + # end + + nodes = get_nodes(mesh) + L = L - A * Ud + + # here a Dirichlet boundary condition is applied on "West". p and T are imposed to 1 (solid). + for idof in bnd_dofs0 + A[idof, :] .= 0.0 + A[:, idof] .= 0.0 + A[idof, idof] = 1.0 + L[idof] = Ud[idof] + end + + for idof in bnd_dofs1 + A[idof, :] .= 0.0 + A[:, idof] .= 0.0 + A[idof, idof] = 1.0 + L[idof] = Ud[idof] + end + + b = A \ L + set_values!(ϕ, b) + + Ta = + CellVariable(:Ta, mesh, FESpace(FunctionSpace(:Lagrange, 1), :continuous; size = 1)) + T_analytical = X -> T_analytical_twoLayer(X[1], lamda1, lamda2, T0, T1, 0.2) + set_values!(Ta, T_analytical; degquad = 5) + + Tcn = var_on_centers(ϕ) + Tca = var_on_centers(Ta) + #dict_vars = Dict(@sprintf("Temperature") => (get_values(ϕ), VTKPointData())) + dict_vars = + Dict("Temperature" => (Tcn, VTKCellData()), "Temperature_a" => (Tca, VTKCellData())) + write_vtk(dir * "myout/result_steady_heat_equation_two_layer", 0, 0.0, mesh, dict_vars) + + if degree == 1 + @show norm(get_values(ϕ) .- get_values(Ta), Inf) / norm(get_values(Ta), Inf) + else + @show norm(Tca .- Tcn, Inf) / norm(Tca, Inf) + end +end + +# Function that runs the unsteady two layer case: +function run_unsteady_twoLayer() + println("Running unsteady two layer case") + # Read mesh + with_cell_names = true + mesh, el_names, el_names_inv, el_cells, glo2loc_cell_indices = + read_msh_with_cell_names(dir * "input/mesh/domainTwoLayer_quad.msh", 2) + + fs = FunctionSpace(:Lagrange, degree) + fes = FESpace(fs, :continuous; size = 1) # size=1 for scalar variable + ϕ = CellVariable(:ϕ, mesh, fes) + φ = CellVariable(:φ, mesh, fes) + + # Create a `TestFunction` + λ = TestFunction(mesh, fes) + + u, v = ((ϕ,), (λ,)) + ud = (φ,) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + + nd = length(get_values(ϕ)) + + T0 = 0.0 + bnd_dofs0 = boundary_dofs(ϕ, "West") + # here a Dirichlet boundary condition is applied on "West". p and T are imposed to 1 (solid). + Ud = zeros(Float64, (nd)) + for idof in bnd_dofs0 + Ud[idof] = T0 + end + + T1 = 1.0 + bnd_dofs1 = boundary_dofs(ϕ, "East") + # here a Dirichlet boundary condition is applied on "West". p and T are imposed to 1 (solid). + for idof in bnd_dofs1 + Ud[idof] = T1 + end + + set_values!(φ, Ud) + + #Adense = zeros(Float64, (nd,nd)) + #Mdense = zeros(Float64, (nd,nd)) + L = zeros(Float64, (nd)) + + lamda1 = 1.0 + lamda2 = 1.0 + + mat_1 = el_cells[el_names_inv["Domain_1"]] + mat_2 = el_cells[el_names_inv["Domain_2"]] + + qtmp = zeros(Float64, (ncells(mesh))) + rho = zeros(Float64, (ncells(mesh))) + cp = zeros(Float64, (ncells(mesh))) + lamda = zeros(Float64, (ncells(mesh))) + rhoCp = zeros(Float64, (ncells(mesh))) + + for i in mat_1 + rho[glo2loc_cell_indices[i]] = 100000.0 + cp[glo2loc_cell_indices[i]] = 1.0 + lamda[glo2loc_cell_indices[i]] = lamda1 + rhoCp[glo2loc_cell_indices[i]] = + rho[glo2loc_cell_indices[i]] * cp[glo2loc_cell_indices[i]] + end + for i in mat_2 + rho[glo2loc_cell_indices[i]] = 100000.0 + cp[glo2loc_cell_indices[i]] = 1.0 + lamda[glo2loc_cell_indices[i]] = lamda2 + rhoCp[glo2loc_cell_indices[i]] = + rho[glo2loc_cell_indices[i]] * cp[glo2loc_cell_indices[i]] + end + + q = CellData(qtmp) + ρCp = CellData(rhoCp) + η = CellData(lamda) + + params = (q = q, ρCp = ρCp, η = η) + + # compute matrices associated to bilinear and linear forms + _A = ∫(f1(u, v, params))dΩ + _M = ∫(f2(u, v, params))dΩ + #_Ad = ∫( f_Dirichlet(ud, v, params) )dΩ + _L = ∫(f3(u, v, params))dΩ + + A = sparse(_A, ϕ) + M = sparse(_M, ϕ) + + for (ic, val) in result(_L) + idof = get_dof(ϕ, ic) + ndof = length(idof) + + for i in 1:ndof + L[idof[i]] += val[i] + end + end + + L = L - A * Ud + + # here a Dirichlet boundary condition is applied on "West". p and T are imposed to 1 (solid). + for idof in bnd_dofs0 + A[idof, :] .= 0.0 + A[:, idof] .= 0.0 + A[idof, idof] = 1.0 + M[idof, :] .= 0.0 + M[:, idof] .= 0.0 + M[idof, idof] = 1.0 + L[idof] = Ud[idof] + end + for idof in bnd_dofs1 + A[idof, :] .= 0.0 + A[:, idof] .= 0.0 + A[idof, idof] = 1.0 + M[idof, :] .= 0.0 + M[:, idof] .= 0.0 + M[idof, idof] = 1.0 + L[idof] = Ud[idof] + end + + #vp, vecp = eigen(Array(A), Array(M)) + #display(vp) + + time = 0.0 + dt = 0.1 + totalTime = 100.0 + + Miter = (M + 0.5 * dt * A) + + U0 = 260.0 * ones(Float64, (nd)) + U1 = 260.0 * ones(Float64, (nd)) + + U0 = 0.0 * ones(Float64, (nd)) + U1 = 0.0 * ones(Float64, (nd)) + + for idof in bnd_dofs0 + U0[idof] = Ud[idof] + U1[idof] = Ud[idof] + end + for idof in bnd_dofs1 + U0[idof] = Ud[idof] + U1[idof] = Ud[idof] + end + + # set_values!(ϕ, x -> 260.0) Not implemented for continuous elements + set_values!(ϕ, U1) + # here a Dirichlet boundary condition is applied on "West". p and T are imposed to 1 (solid). + #for idof in bnd_dofs["REAR"] + # Miter[idof,:] .= 0.0 + # Miter[idof,idof] = 1.0 + # U0[idof] = 300.0 + #end + + #dict_vars = Dict(@sprintf("Temperature") => (get_values(ϕ), VTKPointData())) + mkpath(outputpath) + dict_vars = Dict("Temperature" => (var_on_centers(ϕ), VTKCellData())) + write_vtk(outputpath * "result_unsteady_heat_equation", 0, 0.0, mesh, dict_vars) + + itime = 0 + while time <= totalTime + time = time + dt + itime = itime + 1 + @show time, itime + RHS = dt * L + (M - 0.5 * dt * A) * U0 + #@show extrema(U0) + U1 = Miter \ RHS + #@show extrema(U1) + U0[:] .= U1[:] + + #writedlm("Miter.txt", Miter) + #writedlm("RHS.txt", RHS) + + #@show extrema(U1) + set_values!(ϕ, U1) + + if itime % 100 == 0 + #dict_vars = Dict(@sprintf("Temperature") => (get_values(ϕ), VTKPointData())) + dict_vars = Dict("Temperature" => (var_on_centers(ϕ), VTKCellData())) + write_vtk( + outputpath * "result_unsteady_heat_equation", + itime, + time, + mesh, + dict_vars, + ) + end + end + + #Usteady = A\L + #@show extrema(Usteady) + +end + +run_steady() +run_unsteady() + +# run_steady_twoLayer() +# run_unsteady_twoLayer() + +end #hide diff --git a/src/example/old_API/helmholtz_slepc.jl b/src/example/old_API/helmholtz_slepc.jl new file mode 100644 index 0000000..9bf21c7 --- /dev/null +++ b/src/example/old_API/helmholtz_slepc.jl @@ -0,0 +1,212 @@ +module Helmholtz #hide +println("Running Helmholtz example...") #hide +# Remark : this example should be removed from this repository and moved to BcubeParallel +# +# # Helmholtz with SLEPc +# # Theory +# We consider the following Helmholtz equation, representing for instance the acoustic wave propagation with Neuman boundary condition(s): +# ```math +# \begin{cases} +# \Delta u + \omega^2 u = 0 \\ +# \dfrac{\partial u}{\partial n} = 0 \textrm{ on } \Gamma +# \end{cases} +# ``` + +# # Easiest solution +# Load the necessary packages (Bcube is loaded only if not already loaded) +const dir = string(@__DIR__, "/") +include(dir * "../src/Bcube.jl") +using .Bcube +using LinearAlgebra +using WriteVTK +using Printf +using SparseArrays +using MPI +using PetscWrap +using SlepcWrap +include(dir * "private/stability/util.jl") + +# Settings +const with_nozzle = false +const write_eigenvectors = true +const write_mat = false +const out_dir = dir * "../myout/" + +# Init Slepc to init MPI comm (to be improved, should be able to start Slepc from existing comm...) +SlepcInitialize("-eps_nev 50 -st_pc_factor_shift_type NONZERO -st_type sinvert -eps_view") +#SlepcInitialize("-eps_type lapack -eps_view") + +# Get MPI infos +#MPI.Init() +comm = MPI.COMM_WORLD +rank = MPI.Comm_rank(comm) + 1 # '+1' to start at 1 +nprocs = MPI.Comm_size(comm) +isRoot = (rank == 1) + +# Mesh +const mesh_path = out_dir * "mesh.msh" + +#- Generate a 1D mesh : (un)comment +#spaceDim = 1; topoDim = 1 +#isRoot && gen_line_mesh(mesh_path; nx = 4, npartitions = nprocs) + +#- Generate a 2D mesh : (un)comment +#spaceDim = 2; topoDim = 2 +#isRoot && gen_rectangle_mesh(mesh_path, :tri; nx = 2, ny = 2, lx = 1., ly = 1., xc = 0.5, yc = 0.5, npartitions = nprocs) + +#- Generate a 3D mesh : (un)comment +spaceDim = 3 +topoDim = 3 +isRoot && gen_cylinder_mesh(mesh_path, 10.0, 30; npartitions = nprocs) + +# Read mesh and partitions +MPI.Barrier(comm) # all procs must wait for the mesh to be built (otherwise they start reading a wrong mesh) +mesh = read_msh(mesh_path) +cell2part = read_partitions(mesh_path, topoDim) + +# Next, create a scalar variable named `:u`. The Lagrange polynomial space is used here. By default, +# a "continuous" function space is created (by opposition to a "discontinuous" one). The order is set to `1`. +const degree = 1 +fs = FunctionSpace(:Lagrange, degree) +fes = FESpace(fs, :continuous; size = 1) # size=1 for scalar variable +ϕ = CellVariable(:ϕ, mesh, fes, ComplexF64) +system = System(ϕ) +nd = get_ndofs(system) +@show ndofs(ϕ) + +# Partition +#part2dof, part2cell, dof2loc = partition(system, ncells(mesh), cell2part) +part2dof, part2cell, part2minmax, dof2loc, loc2dof, dof2glob, glob2dof = + partition(system, ncells(mesh), cell2part) +my_dofs = part2dof[rank] +my_cells = part2cell[rank] +i_min, i_max = part2minmax[rank, :] +nd_loc = length(my_dofs) + +# Create a `TestFunction` +λ = get_trial_function(ϕ) + +# Define measures for cell and interior face integrations +dΩ = Measure(CellDomain(mesh, my_cells), 2) # no quadrature higher than 2 for Penta6... + +# compute integrals +println("Computing integrals...") +_A = ∫(∇(λ) * transpose(∇(λ)))dΩ +_B = ∫(λ * transpose(λ))dΩ + +# build julia sparse matrices from integration result +println("Assembling...") +# NOTE : we could directly fill Slepc matrices, but manipulating SparseArrays +# is more convenient for boundary conditions (if any) +function fun(IJV, i, j, v) + (i_min <= dof2glob[i] <= i_max) && push!.(IJV, (dof2glob[i], dof2glob[j], v)) +end # sparse with global numbering + +IJV = (Int[], Int[], Float64[]) +sizehint!.(IJV, nd_loc) +assemble((i, j, v) -> fun(IJV, i, j, v), _A, ((ϕ, ϕ),), system) +As = sparse(IJV...) + +IJV = (Int[], Int[], Float64[]) +sizehint!.(IJV, nd_loc) +assemble((i, j, v) -> fun(IJV, i, j, v), _B, ((ϕ, ϕ),), system) +Bs = sparse(IJV...) + +# Boundary conditions +if with_nozzle + println("Nozzle not implemented yet") + MPI.Barrier(comm) + SlepcFinalize() +end + +# Convert to Slepc matrices +println("Converting to Petsc matrices...") +A = julia_sparse_to_petsc(As, nd, nd_loc, i_min) +B = julia_sparse_to_petsc(Bs, nd, nd_loc, i_min) + +# Assemble PETSc mat +PetscWrap.assemble!(A, MAT_FINAL_ASSEMBLY) +PetscWrap.assemble!(B, MAT_FINAL_ASSEMBLY) + +# Print mat to file for debug +if write_mat + println("Writing matrices to file for debug...") + mat2file(A, out_dir * "A_$nprocs.txt") + mat2file(B, out_dir * "B_$nprocs.txt") +end + +# Now we set up the eigenvalue solver +println("Creating EPS...") +eps = create_eps(A, B; auto_setup = true) + +# Then we solve +println("Solving...") +solve!(eps) + +# Retrieve eigenvalues +println("Number of converged eigenvalues : " * string(neigs(eps))) +i_eigs = 1:min(50, neigs(eps)) +vp = get_eig(eps, i_eigs) + +# Display the "first" eigenvalues: +@show sqrt.(abs.(vp[i_eigs])) + +# Write result to ascii +const casename = "helmholtz_slepc_$(nprocs)" +println("Writing results to files") +println("Writing eigenvalues to '$(casename)'...") +eigenvalues2file( + eps, + out_dir * casename * "_vp.csv"; + two_cols = true, + write_index = true, + write_header = true, + comment = "", +) +if write_eigenvectors + println("Writing eigenvectors...") + eigenvectors2file(eps, out_dir * casename * "_vecp") + A_start, _ = get_range(A) # needed for eigenvectors2vtk +end + +# Free memory +println("Destroying Petsc/Slepc objects") +destroy!.((A, B, eps)) + +# Convert to VTK +if write_eigenvectors + println("Converting to VTK...") + proc2start = MPI.Gather(A_start, 0, comm) # gather starting row of all procs + + # Only root proc converts everything to VTK + if (isRoot) + # Read real and imag parts of eigenvectors + #TODO: (could use PetscViewerAsciiOpen...) + vecs_r = readPetscAscii(out_dir * casename * "_vecp_r.dat") + vecs_i = readPetscAscii(out_dir * casename * "_vecp_i.dat") + println("Vectors successfully read!") + + # Now we need to reorder this vectors. We mainly "reverse" what is done in `julia_sparse_to_petsc` + #vecs .= vecs[dof2glob,:] # reorder elements -> I don't understand why this is working : just luck? + vecs = zeros(ComplexF64, size(vecs_r)) + for irank in 1:nprocs + i_min = part2minmax[irank, 1] + p2s = proc2start[irank] # first row handled by proc `irank` + nd_rank = length(part2dof[irank]) # number of dofs handled by proc `irank` + ind_rank = p2s:(p2s + nd_rank - 1) # row indices of `vecs_*` handled by proc `irank` + ind_glob = ind_rank .- p2s .+ i_min + vecs[glob2dof[ind_glob], :] = vecs_r[ind_rank, :] .+ vecs_i[ind_rank, :] .* im + end + + # Finally convert to VTK + eigenvectors2VTK(system, vecs, out_dir * casename * "_vecp", i_eigs) + println("done writing " * out_dir * casename * "_vecp") + end +end + +# The end (the Barrier helps debugging) +println("processor $(rank)/$(nprocs) reached end of script") +MPI.Barrier(comm) +SlepcFinalize() + +end #hide diff --git a/src/example/old_API/linear_transport_heaviside.jl b/src/example/old_API/linear_transport_heaviside.jl new file mode 100644 index 0000000..06068ec --- /dev/null +++ b/src/example/old_API/linear_transport_heaviside.jl @@ -0,0 +1,432 @@ +module LinearTransport #hide +println("Running linear transport heaviside example...") #hide +# # Linear transport +# ## Theory +# In this example, we solve the following linear transport equation using discontinuous elements: +# ```math +# \frac{\partial \phi}{\partial t} + \nabla \cdot (c \phi) = 0 +# ``` +# where ``c`` is a constant velocity. Using an explicit time scheme, one obtains: +# ```math +# \phi^{n+1} = \phi^n - \Delta t \nabla \cdot (c \phi^n) +# ``` +# The corresponding weak form of this equation is: +# ```math +# \int_\Omega \phi^{n+1} v \mathrm{\,d}\Omega = \int_\Omega \phi^n v \mathrm{\,d}\Omega + \Delta t \left[ +# \int_\Omega c \phi^n \cdot \nabla v \mathrm{\,d}\Omega - \oint_\Gamma \left( c \phi \cdot n \right) v \mathrm{\,d}\Gamma +# \right] +# ``` +# where ``\Gamma = \delta \Omega``. Adopting the discontinuous Galerkin framework, this equation is written in every mesh cell +# ``\Omega_i``. The cell boundary term involves discontinuous quantities and is replaced by a "numerical flux", +# leading to the expression: +# ```math +# \int_{\Omega_i} \phi^{n+1} v \mathrm{\,d}\Omega_i = \int_{\Omega_i} \phi^n v \mathrm{\,d}\Omega_i + \Delta t \left[ +# \int_{\Omega_i} c \phi^n \cdot \nabla v \mathrm{\,d}\Omega_i - \oint_{\Gamma_i} F^*(\phi) v \mathrm{\,d} \Gamma_i +# \right] +# ``` +# For this example, an upwind flux will be used for ``F^*``. Using a matrix formulation, the above equation can be written as: +# ```math +# \phi^{n+1} = \phi^n + M^{-1}(f_\Omega - f_\Gamma) +# ``` +# where ``M^{-1}`` is the inverse of the mass matrix, ``f_\Omega`` the volumic flux term and ``f_\Gamma`` the surfacic flux term. +# +# ## Solution with Bcube +# Start by importing the necessary packages: +# Load the necessary packages (Bcube is loaded only if not already loaded) +include(string(@__DIR__, "/../src/Bcube.jl")) +using .Bcube +using LinearAlgebra +using WriteVTK +using StaticArrays +using BenchmarkTools + +function compute_residual(w, params, t) + + # destructuring : `ϕ` for cellvariable, `λ`for test function + u, v = w + ϕ, = u + λ, = v + + # alias on measures + dΓ = params.dΓ + dΩ = params.dΩ + dΓ_perio_x = params.dΓ_perio_x + dΓ_perio_y = params.dΓ_perio_y + + c = params.c + + # @show limϕ[917],ϕ̅[917] + # init a tuple of new CellVariables from `u` to store all residual contributions + du = zeros.(u) + + # compute volume residuals + du_Ω = ∫(flux_Ω((ϕ,), v, c))dΩ + + # Store volume residuals in `du` + du += du_Ω + + # face normals for each face domain (lazy, no computation at this step) + n_Γ = FaceNormals(dΓ) + n_Γ_perio_x = FaceNormals(dΓ_perio_x) + n_Γ_perio_y = FaceNormals(dΓ_perio_y) + + # flux residuals from interior faces for all variables + du_Γ = ∫(flux_Γ((ϕ,), v, n_Γ))dΓ + + # flux residuals from bc faces for all variables + du_Γ_perio_x = ∫(flux_Γ((ϕ,), v, n_Γ_perio_x))dΓ_perio_x + du_Γ_perio_y = ∫(flux_Γ((ϕ,), v, n_Γ_perio_y))dΓ_perio_y + + # accumulate face-flux residuals to cell residuals for all variables + # (this step will be improved when a better API will be available) + du = ((du - du_Γ) - du_Γ_perio_x) - du_Γ_perio_y + + return du +end + +""" + flux_Ω(u,v) + +Compute volume residual using the lazy-operators approach +""" +function flux_Ω(u, v, c) + ϕ, = u + λ, = v + flux_ϕ = ∇(λ) * (ϕ .* c) + (flux_ϕ,) +end + +# Basically, we need three methods to solve the problem : one to compute the flux terms (looping on the mesh faces), one to assemble +# the problem (volumic + surfacic terms) and one to step forward in time. Before defining these three methods, let's define auxiliary +# ones that help improving the code readability. First we define our numerical flux function, the updwind flux: +""" + Upwind flux. Face normal oriented from cell i to cell j. + Here `c` is the constant convection velocity. +""" +function upwind(w) + (ϕᵢ,), (ϕⱼ,), (λᵢ,), nᵢⱼ = w + c = SA[1.0, 0.0] #TODO : put it as an input argument + vij = c ⋅ nᵢⱼ + # if ϕᵢ<0.0 || ϕᵢ>1.0 || ϕⱼ <0.0 || ϕᵢ > 1.0 + # @show ϕᵢ, ϕⱼ + # error("ldksldksjqjslkd") + # end + if vij > zero(vij) + flux = vij * ϕᵢ + else + flux = vij * ϕⱼ + end + return (λᵢ * flux,) +end + +""" + flux_Γ(u,v,n) + +Flux at the interface is defined by a composition of two functions: +* `facevar(u,v,n)` defines the input states which are needed for + the upwind flux using operator notations +* `upwind(w)` defines the upwind face flux (as usual) +""" +flux_Γ(u, v, n) = upwind ∘ facevar(u, v, n) +facevar(u, v, n) = (side⁻(u), side⁺(u), side⁻(v), n) + +""" + flux_Γ_out(u,v,n,c,dirichlet_out) +""" +function flux_Γ_out(u, v, n, c, dirichlet_out) + ϕ, = u + λ, = v + cn = c ⋅ n + flux = max(0, cn) * side⁻(ϕ) + min(0, cn) * dirichlet_out + (side⁻(λ) * flux,) +end + +""" + flux_Γ_in(u,v,n,bc_in) +""" +function flux_Γ_in(u, v, n, bc_in) + ϕ, = u + λ, = v + flux = bc_in ⋅ n + (side⁻(λ) * flux,) +end + +# Now let's write the assembling method which computes the volumic terms and assembles them with the surfacic flux terms. +# We call this method `explicit_step!` because it returns the ``\Delta \phi`` associated with the selected explicit time +# scheme. +function explicit_step(w, params, cache, Δt, t) + (ϕ,), (λ,) = w + + if limiter_projection + bounds = (params.ϕmin₀, params.ϕmax₀) + perioBCs = (get_domain(params.dΓ_perio_x), get_domain(params.dΓ_perio_y)) + _limϕ, ϕproj = linear_scaling_limiter( + ϕ, + params.degquad; + bounds = bounds, + DMPrelax = params.DMPrelax, + periodicBCs = perioBCs, + invMass = cache.invMass_ϕ, + ) + set_values!(ϕ, ϕproj) + params.limϕ .= _limϕ + end + + _dϕ, = compute_residual(w, params, t) + ϕnew = get_values(ϕ) .+ cache.invMass_ϕ * (Δt .* get_values(_dϕ)) + + return ϕnew +end + +# We are all set to solve a linear transport equation. However, we will add two more things to ease the solution VTK output : a +# structure to store the vtk filename and the number of iteration: +mutable struct VtkHandler + basename::Any + ite::Any + VtkHandler(basename) = new(basename, 0) +end + +# ... and a method to interpolate the discontinuous solution on cell centers and to append it to the VTK file: +function append_vtk(vtk, mesh, vars, t, params) + ## Values on center + # Values on center + name2val = (; zip(get_name.(vars), var_on_centers.(vars))...) + name2val_avg = (; + zip( + Symbol.(string.(get_name.(vars)) .* "_avg"), + map(v -> mean_values(v, Val(degquad)), vars), + )... + ) + + name2val_lim = (limϕ = params.limϕ,) + name2val = merge(name2val, name2val_avg) + name2val = merge(name2val, name2val_lim) + + # Write + dict_vars = Dict( + "phi_avg" => (name2val[:ϕ_avg], VTKCellData()), + "phi_center" => (name2val[:ϕ], VTKCellData()), + "lim_phi" => (name2val[:limϕ], VTKCellData()), + ) + + write_vtk(vtk.basename, vtk.ite, t, mesh, dict_vars; append = vtk.ite > 0) + + # Update counter + vtk.ite += 1 + + return nothing +end + +function RK3_SSP(w, params, cache, Δt, t) + (ϕ,), v = w + + _ϕ₁ = explicit_step(((ϕ,), v), params, cache, Δt, t) + ϕ₁ = zeros(ϕ) + set_values!(ϕ₁, _ϕ₁) + + _ϕ₂ = explicit_step(((ϕ₁,), v), params, cache, Δt, t) + ϕ₂ = zeros(ϕ) + set_values!(ϕ₂, (3.0 / 4) .* get_values(ϕ) .+ (1.0 / 4) .* (_ϕ₂)) + + _ϕ₃ = explicit_step(((ϕ₂,), v), params, cache, Δt, t) + ϕ₃ = (1.0 / 3) .* get_values(ϕ) .+ (2.0 / 3) .* (_ϕ₃) + + return ϕ₃ +end + +function check_bounds(ϕ, mesh, params) + ϕ̅ = mean_values(ϕ, Val(degquad)) + if !(minimum(ϕ̅) > -1.e-12 && maximum(ϕ̅) < 1.0 + 1.e-12) + @show extrema(ϕ̅) + imin = argmin(ϕ̅) + imax = argmax(ϕ̅) + c2n = connectivities_indices(mesh, :c2n) + @show center(get_nodes(mesh, c2n[imin]), cells(mesh)[imin]) + @show center(get_nodes(mesh, c2n[imax]), cells(mesh)[imax]) + @show params.limϕ[imin], ϕ̅[imin] + @show get_values(ϕ, imin) + @show get_values(ϕ, imax) + @show params.limϕ[imax], ϕ̅[imax] + error("jkojojo") + end +end + +heaviside!(x, x₀, l₀) = all(abs.(x .- x₀) .< l₀ / 2) ? 1.0 : 0.0 +sinus!(x, x₀, l₀) = 0.5 * (1 + sin(2π * 2 * (x[1] - x₀) / l₀ + π / 2)) +function step3!(x, x₀, l₀) + if x[1] < x₀ - l₀ / 2 + 0.0 + elseif x[1] > x₀ + l₀ / 2 + 1.0 + else + ((x[1] - (x₀ - l₀ / 2)) / l₀)^2 * (3 - 2(x[1] - (x₀ - l₀ / 2)) / l₀) + end +end + +function run() + @show degmass, degquad + + # Then generate the mesh of a rectangle using Gmsh and read it + tmp_path = "tmp.msh" + gen_rectangle_mesh( + tmp_path, + :quad; + nx = nx, + ny = ny, + lx = lx, + ly = ly, + xc = 0.0, + yc = 0.0, + ) + mesh = read_msh(tmp_path) + rm(tmp_path) + + # Create a `CellVariable` + fs = FunctionSpace(fspace, degree) + fes = FESpace(fs, :discontinuous; size = 1) # size=1 for scalar variable + ϕ = CellVariable(:ϕ, mesh, fes) + + # select an initial configurations: + set_values!(ϕ, x -> heaviside!(x, x₀, l₀ * 0.999); degquad = degquad) + #set_values!(ϕ, x-> x[1]<0.0 ? step3!(-x,lx/4,8*lx/(nx+1)) : step3!(x,lx/4,8*lx/(nx+1)) ) + #set_values!(ϕ, x-> sinus!(-x,lx/2,2lx)) + + # user-defined limiter parameters + DMPrelax = zeros(ncells(mesh)) + DMPrelax .= DMPcurv₀ * Δx₀^2 + params = (ϕmin₀ = ϕmin₀, ϕmax₀ = ϕmax₀, DMPrelax = DMPrelax) + + # Create a `TestFunction` + λ = TestFunction(mesh, fes) + + u, v = ((ϕ,), (λ,)) + + # Then we create a `NamedTuple` to hold the simulation parameters. + params = (params..., c = c, degree = degree, degquad = degquad) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), degquad) + dΓ = Measure(InteriorFaceDomain(mesh), degquad) + + # Declare periodic boundary conditions and + # create associated domains and measures + periodicBCType_x = PeriodicBCType(Translation(SA[-lx, 0.0]), ("East",), ("West",)) + periodicBCType_y = PeriodicBCType(Translation(SA[0.0, ly]), ("South",), ("North",)) + Γ_perio_x = BoundaryFaceDomain(mesh, periodicBCType_x) + Γ_perio_y = BoundaryFaceDomain(mesh, periodicBCType_y) + dΓ_perio_x = Measure(Γ_perio_x, degquad) + dΓ_perio_y = Measure(Γ_perio_y, degquad) + + params = (params..., dΓ = dΓ, dΩ = dΩ, dΓ_perio_x = dΓ_perio_x, dΓ_perio_y = dΓ_perio_y) + + # create a vector `limϕ` to store limiter values + # and write it in the vtk outputs + limϕ = zeros(ncells(mesh)) + params = (params..., limϕ = limϕ) + + # Init vtk handler + vtk = VtkHandler( + string(@__DIR__, "/../myout/linear_transport_heaviside_deg" * string(degree)), + ) + + # Init time + time = 0.0 + + # Save initial solution + append_vtk(vtk, mesh, (ϕ,), time, params) + + # Build the cache and store everything you want to compute only once (such as the mass matrice inverse...) + invMass_ϕ = InvMassMatrix(ϕ, Val(degquad)) + cache = (invMass_ϕ = invMass_ϕ,) + + mass0 = sum(mean_values(ϕ, Val(degquad))) + + if limiter_projection + bounds = (params.ϕmin₀, params.ϕmax₀) + perioBCs = (get_domain(params.dΓ_perio_x), get_domain(params.dΓ_perio_y)) + _limϕ, ϕproj = linear_scaling_limiter( + ϕ, + degquad; + bounds = bounds, + DMPrelax = DMPrelax, + periodicBCs = perioBCs, + invMass = invMass_ϕ, + ) + set_values!(ϕ, ϕproj) + params.limϕ .= _limϕ + end + + check_bounds(ϕ, mesh, params) + + ϕold = zeros(ϕ) + + # Let's loop to solve the equation. + for i in 1:nite + ## Infos + println("Iteration ", i) + + set_values!(ϕold, get_values(ϕ)) + + ## Step forward in time + dϕ = RK3_SSP((u, v), params, cache, Δt, time) + set_values!(ϕ, dϕ) + + any(isnan(x) for x in get_values(ϕ)) && error("NaN") + + time += Δt + + ## Write solution to file (respecting max. number of output) + if (i % Int(max(floor(nite / nout), 1)) == 0) + append_vtk(vtk, mesh, (ϕ,), time, params) + end + + check_bounds(ϕ, mesh, params) + + ratio_mass = sum(mean_values(ϕ, Val(degquad))) / mass0 + println("Volume conservation = ", ratio_mass) + end + + append_vtk(vtk, mesh, (ϕ,), time, params) + + @show degmass, degquad + @btime explicit_step(($u, $v), $params, $cache, $Δt, $time) + @show length(get_values(ϕ)) + + # And here is an animation of the result: + # ```@raw html + # drawing + # ``` +end + +# Now let's manipulate everything to solve a linear transport equation. First we define some constants, relative to +# the problem or the mesh : +const degree = 2# Function-space order (Taylor(0) = first order Finite Volume) +const fspace = :Lagrange +const limiter_projection = true + +const ϕmin₀ = 0.0 # lower physical bound on cell average value (strictly enforced by limitation) +const ϕmax₀ = 1.0 # upper physical bound on cell average value (strictly enforced by limitation) +const DMPcurv₀ = 10.0 # coef. (≥0) used to relax bounds of the local discrete maximum principle when limitation is applied. +# It allows small oscillations at local extrema while physical bounds are satisfied. +# There is no relaxation if the coef. is set to zero. + +const nite = 5 * 1600 # Number of time iteration(s) +const c = SA[1.0, 0.0] # Convection velocity (must be a vector) +const CFL = 1.0 / 20 / 2 # 0.1 for order 1 +const nout = 100 # Number of time steps to save +const nx = 40 # Number of nodes in the x-direction +const ny = 40 # Number of nodes in the y-direction +const lx = 2.0 # Domain width +const ly = 2.0 # Domain height +const l₀ = SA[11 * lx / (nx - 1), 11 * ly / (ny - 1)] # size of heaviside step +const x₀ = SA[0.0, 0.0] # center of heaviside step + +const Δt = CFL * min(lx / nx, ly / ny) / norm(c) # Time step +const Δx₀ = lx / nx + +const degmass = degree * 2 * 2 # *2 because ndim=2, and *2 because λᵢ*λⱼ +const degquad = 5# Int(ceil((degmass+3)/2)) + +run() + +end #hide diff --git a/src/example/phase_field_time_integration.jl b/src/example/phase_field_time_integration.jl new file mode 100644 index 0000000..ad15107 --- /dev/null +++ b/src/example/phase_field_time_integration.jl @@ -0,0 +1,438 @@ +module PhaseFieldSupercooled #hide +println("Running phase field supercooled equation example...") #hide + +# # Phase field model - solidification of a liquid in supercooled state +# # Theory +# This case is taken from: Kobayashi, R. (1993). Modeling and numerical simulations of dendritic crystal growth. Physica D: Nonlinear Phenomena, 63(3-4), 410-423. +# In particular, the variables of the problem are denoted in the same way ($p$ for the phase indicator and $T$ for temperature). +# Consider a rectangular domain $$\Omega = [0, L_x] \times [0, L_y]$$ on which we wish to solve the following equations: +# ```math +# \tau \partial_t p = \epsilon^2 \Delta p + p (1-p)(p - \frac{1}{2} + m(T)) +# ``` +# ```math +# \partial_t T = \Delta T + K \partial_t p +# ``` +# where $m(T) = \frac{\alpha}{\pi} atan \left[ \gamma (T_e - T) \right]$. +# This set of equations represents the solidification of a liquid in a supercooled state. Here $T$ is a dimensionless temperature and $p$ is the solid volume fraction. +# Lagrange finite elements are used to discretize both equations. Time marching is performed with a forward Euler scheme for the first equation and a backward Euler scheme for the second one. +# +# To initiate the solidification process, a Dirichlet boundary condition ($p=1$,$T=1$) is applied at $x=0$ ("West" boundary). +using Bcube +using LinearAlgebra +using Random +using WriteVTK +# using NLsolve +#using Profile +using Symbolics +using DifferentialEquations +using SparseDiffTools +#using InteractiveUtils +#using Cthulhu + +Random.seed!(1234) # to obtain reproductible results + +const dir = string(@__DIR__, "/../") # Bcube dir +const lx = 5.0 +const ly = 3.0 +const nx = 30 +const ny = 30 +const ε = 0.01 # original value 0.01 +const τ = 0.0003 +const α = 0.9 +const γ = 10.0 +const K = 1.6 +const Te = 1.0 +const β = 0.0 # noise amplitude, original value : 0.01 +const totalTime = 1.0 # original value : 1 +const nout = 50 # Number of iterations to skip before writing file + +g(T) = (α / π) * atan(γ * (Te - T)) + +# Read mesh +# mesh = rectangle_mesh(nx, ny, xmin=0, xmax=lx, ymin=0, ymax=ly; bnd_names=("North", "South", "East", "West")) +# const mesh_path = dir * "myout/tmp.msh" +const mesh_path = dir * "input/mesh/domainPhaseField_tri.msh" +# gen_rectangle_mesh(mesh_path, :tri; transfinite=true, nx=nx, ny=ny, lx=lx, ly=ly) +const mesh = read_msh(mesh_path, 2) +# const mesh = line_mesh(10; names=("West", "East")) + +# Noise function : random between [-1/2,1/2] +const χ = Bcube.MeshCellData(rand(ncells(mesh)) .- 0.5) + +""" +Version with explicit - implicit time integration +We don't use a MultiFESpace here, only one FESpace -> the two variables can't be of different degrees +""" +function run_imex_1space() + degree = 1 + + # Function spaces and FE Spaces + fs = FunctionSpace(:Lagrange, degree) + U = TrialFESpace(fs, mesh, Dict("West" => (x, t) -> 1.0)) + V = TestFESpace(U) + + # FE functions + ϕ = FEFunction(U) + T = FEFunction(U) + + # Define measures for cell integration + dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + + # Bilinear and linear forms + a(u, v) = ∫(∇(u) ⋅ ∇(v))dΩ + m(u, v) = ∫(u ⋅ v)dΩ + l(v) = ∫(v * ϕ * (1.0 - ϕ) * (ϕ - 0.5 + g(T) + β * χ))dΩ + + # Assemble matrices + A = assemble_bilinear(a, U, V) + M = assemble_bilinear(m, U, V) + + # Numerical parameters + Δt = 0.0001 + + # Create iterative matrices + C_ϕ = M + Δt / τ * ε^2 * A + C_T = M + Δt * A + + # Set Dirichlet conditions + # For this example, we don't use a lifting method to impose the Dirichlet. + d = Bcube.assemble_dirichlet_vector(U, V, mesh) + Bcube.apply_dirichlet_to_matrix!((C_ϕ, C_T), U, V, mesh) + + # Factorize for performance + C_ϕ = factorize(C_ϕ) + C_T = factorize(C_T) + + # Init solution + set_dof_values!(ϕ, d) + set_dof_values!(T, d) + + dict_vars = Dict( + "Temperature" => (var_on_vertices(T, mesh), VTKPointData()), + "Phi" => (var_on_vertices(ϕ, mesh), VTKPointData()), + ) + write_vtk(dir * "myout/result_phaseField_imex_1space", 0, 0.0, mesh, dict_vars) + + # Preallocate + L = zero(d) + rhs = zero(d) + ϕ_new = zero(d) + + # Time loop + t = 0.0 + itime = 0 + while t <= totalTime + t += Δt + itime += 1 + @show t, itime + + # Integrate equation on ϕ + L .= 0.0 # reset L + assemble_linear!(L, l, V) + rhs .= M * get_dof_values(ϕ) .+ Δt / τ .* L + Bcube.apply_dirichlet_to_vector!(rhs, U, V, mesh) + ϕ_new .= C_ϕ \ rhs + + # Integrate equation on T + rhs .= M * (get_dof_values(T) .+ K .* (ϕ_new .- get_dof_values(ϕ))) + Bcube.apply_dirichlet_to_vector!(rhs, U, V, mesh) + + # Update solution + set_dof_values!(ϕ, ϕ_new) + set_dof_values!(T, C_T \ rhs) + + # write solution in vtk format + if itime % nout == 0 + dict_vars = Dict( + "Temperature" => (var_on_vertices(T, mesh), VTKPointData()), + "Phi" => (var_on_vertices(ϕ, mesh), VTKPointData()), + ) + write_vtk( + dir * "myout/result_phaseField_imex_1space", + itime, + t, + mesh, + dict_vars; + append = true, + ) + end + end + + println("End of imex 1 space") +end + +""" +Version with explicit - implicit time integration +We don't use a MultiFESpace here, but two distincts FESpaces +""" +function run_imex_2spaces() + degree_ϕ = 1 + degree_T = 1 + + # Function spaces and FE Spaces + fs_ϕ = FunctionSpace(:Lagrange, degree_ϕ) + fs_T = FunctionSpace(:Lagrange, degree_T) + U_ϕ = TrialFESpace(fs_ϕ, mesh, Dict("West" => (x, t) -> 1.0)) + U_T = TrialFESpace(fs_T, mesh, Dict("West" => (x, t) -> 1.0)) + V_ϕ = TestFESpace(U_ϕ) + V_T = TestFESpace(U_T) + + # FE functions + ϕ = FEFunction(U_ϕ) + T = FEFunction(U_T) + + # Define measures for cell integration + dΩ = Measure(CellDomain(mesh), 2 * max(degree_ϕ, degree_T) + 1) + + # Bilinear and linear forms + a(u, v) = ∫(∇(u) ⋅ ∇(v))dΩ + m(u, v) = ∫(u ⋅ v)dΩ + l(v) = ∫(v * ϕ * (1 - ϕ) * (ϕ - 0.5 + g(T) + β * χ))dΩ + + # Assemble matrices + A_ϕ = assemble_bilinear(a, U_ϕ, V_ϕ) + A_T = assemble_bilinear(a, U_T, V_T) + M_ϕ = assemble_bilinear(m, U_ϕ, V_ϕ) + M_T = assemble_bilinear(m, U_T, V_T) + + # Numerical parameters + Δt = 0.0001 + + # Create iterative matrices + C_ϕ = M_ϕ + Δt / τ * ε^2 * A_ϕ + C_T = M_T + Δt * A_T + + # Set Dirichlet conditions + ϕ_Γ = Bcube.assemble_dirichlet_vector(U_ϕ, V_ϕ, mesh) + T_Γ = Bcube.assemble_dirichlet_vector(U_T, V_T, mesh) + Bcube.apply_dirichlet_to_matrix!(C_ϕ, U_ϕ, V_ϕ, mesh) + Bcube.apply_dirichlet_to_matrix!(C_T, U_T, V_T, mesh) + + # Factorize for performance + C_ϕ = factorize(C_ϕ) + C_T = factorize(C_T) + + # Init solution + set_dof_values!(ϕ, ϕ_Γ) + set_dof_values!(T, T_Γ) + + dict_vars = Dict( + "Temperature" => (var_on_vertices(T, mesh), VTKPointData()), + "Phi" => (var_on_vertices(ϕ, mesh), VTKPointData()), + ) + write_vtk(dir * "myout/result_phaseField_imex_2spaces", 0, 0.0, mesh, dict_vars) + + # Preallocate + L_ϕ = zero(ϕ_Γ) + ϕ_new = zero(ϕ_Γ) + rhs_ϕ = zero(ϕ_Γ) + rhs_T = zero(T_Γ) + + # Time loop + t = 0.0 + itime = 0 + while t <= totalTime + t += Δt + itime += 1 + @show t, itime + + # Integrate equation on ϕ + L_ϕ .= 0.0 # reset + assemble_linear!(L_ϕ, l, V_ϕ) + rhs_ϕ .= M_ϕ * get_dof_values(ϕ) + Δt / τ * L_ϕ + Bcube.apply_dirichlet_to_vector!(rhs_ϕ, U_ϕ, V_ϕ, mesh) + ϕ_new .= C_ϕ \ rhs_ϕ + + # Integrate equation on T + rhs_T .= M_T * (get_dof_values(T) .+ K .* (ϕ_new .- get_dof_values(ϕ))) + apply_dirichlet_to_vector!(rhs_T, U_T, V_T, mesh) + + # Update solution + set_dof_values!(ϕ, ϕ_new) + set_dof_values!(T, C_T \ rhs_T) + + # write solution in vtk format + if itime % nout == 0 + dict_vars = Dict( + "Temperature" => (var_on_vertices(T, mesh), VTKPointData()), + "Phi" => (var_on_vertices(ϕ, mesh), VTKPointData()), + ) + write_vtk( + dir * "myout/result_phaseField_imex_2spaces", + itime, + t, + mesh, + dict_vars; + append = true, + ) + end + end + + println("End of imex 2 spaces") +end + +""" +Full implicit resolution. This is just an experiment to test DifferentialEquations. The example fails after +a few iterations. +""" +function run_full_implicit() + filepath = dir * "myout/result_phaseField_implicit" + + # Numerical parameters + Δt = 0.0001 + degree_ϕ = 1 + degree_T = 1 + + # Function spaces and FE Spaces + fs_ϕ = FunctionSpace(:Lagrange, degree_ϕ) + fs_T = FunctionSpace(:Lagrange, degree_T) + U_ϕ = TrialFESpace(fs_ϕ, mesh, Dict("West" => (x, t) -> 1.0)) + U_T = TrialFESpace(fs_T, mesh, Dict("West" => (x, t) -> 1.0)) + V_ϕ = TestFESpace(U_ϕ) + V_T = TestFESpace(U_T) + + # Multi-FE spaces + U = MultiFESpace(U_ϕ, U_T) + V = MultiFESpace(V_ϕ, V_T) + + # FE functions + q = FEFunction(U) + ϕ, T = get_fe_functions(q) + + # Define measures for cell integration + dΩ = Measure(CellDomain(mesh), 2 * max(degree_ϕ, degree_T) + 1) + + # Bilinear and linear forms + a((u_ϕ, u_T), (v_ϕ, v_T)) = ∫(ε^2 * ∇(u_ϕ) ⋅ ∇(v_ϕ) + ∇(u_T) ⋅ ∇(v_T))dΩ + m((u_ϕ, u_T), (v_ϕ, v_T)) = ∫(τ * u_ϕ ⋅ v_ϕ + u_T ⋅ v_T - K * u_ϕ ⋅ v_T)dΩ + l((ϕ, T), (v_ϕ, v_T)) = ∫(v_ϕ * ϕ * (1 - ϕ) * (ϕ - 0.5 + g(T) + β * χ))dΩ # need (ϕ, T) for DiffEqns + + # Assemble matrices + A = assemble_bilinear(a, U, V) + M = assemble_bilinear(m, U, V) + + # Set Dirichlet conditions on vectors and matrix + Ud = Bcube.assemble_dirichlet_vector(U, V, mesh) + Bcube.apply_homogeneous_dirichlet_to_matrix!(M, U, V, mesh) + Bcube.apply_dirichlet_to_matrix!(A, U, V, mesh) # no "homogeneous" because no lifting + + # Init solution, and write to file + set_dof_values!(q, Ud) + dict_vars = Dict( + "Temperature" => (var_on_vertices(T, mesh), VTKPointData()), + "Phi" => (var_on_vertices(ϕ, mesh), VTKPointData()), + ) + write_vtk(filepath, 0, 0.0, mesh, dict_vars) + + # Init solution + Q0 = zeros(Bcube.get_ndofs(U)) + + # Function to cancel via iterative solver + p = (U = U, counter = [1]) + function f!(dQ, Q, p, t) + # Unpack + U = p.U + + # Update q (necessary to compute l(v)) + ϕ, T = FEFunction(U, Q) + + # Compute rhs + L = zero(Q) + _l = ((v_ϕ, v_T),) -> l((ϕ, T), (v_ϕ, v_T)) + assemble_linear!(L, _l, V) + Bcube.apply_dirichlet_to_vector!(L, U, V, mesh) + dQ .= L .- A * Q + end + + # (Optional) compute jacobian function + println("computing jacobian cache...") + _f! = (y, x) -> f!(y, x, p, 0.0) + output = zeros(Bcube.get_ndofs(U)) + input = zeros(Bcube.get_ndofs(U)) + sparsity_pattern = Symbolics.jacobian_sparsity(_f!, output, input) + println("sparsity pattern computed !") + jac = Float64.(sparsity_pattern) + display(jac) + colors = matrix_colors(jac) + println("coloring done!") + @show maximum(colors) + # jac_cache = ForwardColorJacCache(_f!, input, nothing, dx=similar(input), colorvec=colors, sparsity=sparsity_pattern) + # j! = (J, u, p, t) -> forwarddiff_color_jacobian!(J, (y, x) -> f!(y, x, p, t), u, jac_cache=jac_cache) + + # Use ModelingToolkit and/or DifferentialEquations to solve the problem + println("Setting DiffEq...") + tspan = (0.0, totalTime) + # odeFunction = ODEFunction(f!; mass_matrix=M) + odeFunction = ODEFunction( + f!; + mass_matrix = M, + jac_prototype = sparsity_pattern, + colorvec = colors, + ) + prob = ODEProblem(odeFunction, Q0, tspan, p) + always_true(args...) = true + + isoutofdomain(Q, p, t) = begin + U = p.U + ϕ, T = FEFunction(U, Q) + cdt1 = any(v -> v < -0.0001, get_dof_values(ϕ)) + cdt2 = any(v -> v > 1.0001, get_dof_values(ϕ)) + cdt3 = any(Q .< -0.0001) + + # return cdt1 && cdt2 + return cdt3 + end + + cb_vtk = DiscreteCallback( + always_true, + integrator -> begin + # Alias + t = integrator.t + counter = integrator.p.counter + + Δt = t - integrator.tprev + println("i = $(counter[1]), t = $t, Δt = $(Δt)") + + if (counter[1] % nout == 0) + set_dof_values!(q, integrator.u) + dict_vars = Dict( + "Temperature" => (var_on_vertices(T, mesh), VTKPointData()), + "Phi" => (var_on_vertices(ϕ, mesh), VTKPointData()), + ) + println("saving to vtk...") + write_vtk(filepath, counter[1], t, mesh, dict_vars; append = true) + end + + counter .+= 1 + end; + save_positions = (false, false), + ) + + timestepper = ImplicitEuler(; nlsolve = NLNewton(; max_iter = 20)) + println("solving...") + sol = solve( + prob, + timestepper; + progress = false, + callback = CallbackSet(cb_vtk), + save_everystep = false, + save_start = false, + save_end = true, + isoutofdomain = isoutofdomain, + ) + # Write final result + set_dof_values!(q, sol.u[end]) + dict_vars = Dict( + "Temperature" => (var_on_vertices(T, mesh), VTKPointData()), + "Phi" => (var_on_vertices(ϕ, mesh), VTKPointData()), + ) + write_vtk(filepath, 1, totalTime, mesh, dict_vars; append = true) + + println("End of full implicit") +end + +# run_imex_1space() +# run_imex_2spaces() +run_full_implicit() + +end diff --git a/src/example/post/helmholtz.pvsm b/src/example/post/helmholtz.pvsm new file mode 100644 index 0000000..c9d4a04 --- /dev/null +++ b/src/example/post/helmholtz.pvsm @@ -0,0 +1,12921 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/example/post/linear_transport.pvsm b/src/example/post/linear_transport.pvsm new file mode 100644 index 0000000..cdb907b --- /dev/null +++ b/src/example/post/linear_transport.pvsm @@ -0,0 +1,10721 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/example/post/transport_on_surface.pvsm b/src/example/post/transport_on_surface.pvsm new file mode 100644 index 0000000..0d319b2 --- /dev/null +++ b/src/example/post/transport_on_surface.pvsm @@ -0,0 +1,14748 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/example/runexamples.jl b/src/example/runexamples.jl new file mode 100644 index 0000000..942cfff --- /dev/null +++ b/src/example/runexamples.jl @@ -0,0 +1,7 @@ +# Alias to script directory : helps running this file from anywhere +const dir = string(@__DIR__, "/../") # Bcube dir + +# Run examples +include(dir * "example/covo.jl") +include(dir * "example/euler_naca_steady.jl") +include(dir * "example/linear_elasticity.jl") diff --git a/src/example/shallow_water.jl b/src/example/shallow_water.jl new file mode 100644 index 0000000..9083c58 --- /dev/null +++ b/src/example/shallow_water.jl @@ -0,0 +1,474 @@ +module ShallowWater #hide +println("Running shallow_water example...") #hide +# # Solve Shallow Water equation + +const dir = string(@__DIR__, "/") +using Bcube +using LinearAlgebra +using WriteVTK +using StaticArrays +using BenchmarkTools +using Roots +using SparseArrays +using SparseDiffTools +using Profile +#using Symbolics +using InteractiveUtils +using WriteVTK + +const eps_h = 1.0e-10 + +function compute_residual(_q, V, params, cache) + # alias on measures + dΓ = params.dΓ + dΩ = params.dΩ + dΓ_perio_x = params.dΓ_perio_x + dΓ_perio_y = params.dΓ_perio_y + + q = get_fe_functions(_q) + + # face normals for each face domain (lazy, no computation at this step) + n_Γ = get_face_normals(dΓ) + nΓ_perio_x = get_face_normals(dΓ_perio_x) + nΓ_perio_y = get_face_normals(dΓ_perio_y) + + function l(v) + ∫(flux_Ω(q, v))dΩ + + -∫(flux_Γ(q, v, n_Γ))dΓ + + -∫(flux_Γ(q, v, nΓ_perio_x))dΓ_perio_x + + -∫(flux_Γ(q, v, nΓ_perio_y))dΓ_perio_y + end + + rhs = assemble_linear(l, V) + return rhs +end + +#velocity(h,hu) = (hu/max(h,eps_h))*(h>eps_h) +velocity(h, hu) = (hu * 2 * h) / (h * h + max(h * h, 1.0e-6)) #desingularization + +""" + flux_Ω(q, v) + +Compute volume residual using the lazy-operators approach +""" +flux_Ω(q, v) = _flux_Ω ∘ (q, map(∇, v)) + +function _flux_Ω(q, ∇v) + ∇λ_h, ∇λ_hu = ∇v + f_h, f_hu = flux_sw(q) + return ∇λ_h ⋅ f_h + ∇λ_hu ⊡ f_hu +end + +function flux_sw(q) + h, hu = q + u = velocity(h, hu) + huu = hu * transpose(u) + g = stateInit.gravity + p_grav = 0.5 * g * h * h + return h .* u, huu + p_grav * I +end + +""" + flux_Γ(q, v, n) + +Flux at the interface is defined by a composition of two functions: +* the input states which are needed for the flux using operator notations +* flux_rusanov(q, v, n) defines the face flux for values returned by facevar (as usual) +""" +flux_Γ(q, v, n) = flux_HLL ∘ (side⁻(q), side⁺(q), jump(v), side⁻(n)) + +function flux_HLL(q1, q2, δv, n12) + g = stateInit.gravity + δv_h, δv_hu = δv + + f_λ = x -> shallow_water_eigval(x, n12, g) + flux = _flux_HLL(q1, q2, n12, flux_sw, f_λ) + + flux_h, flux_hu = flux + return flux_h ⋅ δv_h + flux_hu ⋅ δv_hu +end + +function _flux_rusanov(a, b, n, flux, f_λ) + λ = max(f_λ(a), f_λ(b)) + f_rusanov(a, b, fa, fb) = 0.5 * (dotn(fa + fb, n) - λ * (b - a)) + map(f_rusanov, a, b, flux(a), flux(b)) +end + +function _flux_HLL(qL, qR, n, flux, f_λ) + λL, λR = f_λ(qL), f_λ(qR) + λ⁻ = min(minimum(λL), minimum(λR), zero(λL[1])) + λ⁺ = max(maximum(λL), maximum(λR), zero(λL[1])) + function f_HLL(qL, qR, fL, fR) + if abs(λ⁺ - λ⁻) > 1.0e-12 + fLn, fRn = dotn(fL, n), dotn(fR, n) + f = (λ⁺ * fLn - λ⁻ * fRn + λ⁻ * λ⁺ * (qR - qL)) / (λ⁺ - λ⁻) + else + f = 0.5 * (fL(qL) + fR(qR)) + end + return f + end + map(f_HLL, qL, qR, flux(qL), flux(qR)) +end + +dotn(f::AbstractVector, n::AbstractVector) = f ⋅ n +dotn(f::AbstractMatrix, n::AbstractVector) = f * n + +""" + flux_Γ_wall(q, v, n) +""" +flux_Γ_wall(q, v, n) = flux_HLL ∘ (side⁻(q), side⁻(q), side⁻(v), side⁻(n)) + +# function _flux_Γ_wall(q1, v1, n12) +# g = stateInit.gravity +# h1, hu1 = q1 +# λ_h1, λ_hu1 = v1 + +# flux_h = zero(h1) +# flux_hu = 0.5 * g * h1^2 * n12 + +# return λ_h1 * flux_h + λ_hu1 * flux_hu +# end + +function rhs(u, U, V, params, cache) + rhs = compute_residual(u, V, params, cache) + return cache.mass \ rhs +end + +""" Inversion of mass matrix (expensive version!!) """ +function compute_mass_matrix(U, V, dΩ) + m(u, v) = ∫(u ⋅ v)dΩ + M = assemble_bilinear(m, U, V) + return factorize(M) +end + +""" + rk3_ssp(q, f::Function, t, Δt) + +`f(q, t)` is the function to integrate. +""" +function rk3_ssp(q, f::Function, t, Δt) + stepper(q, t) = forward_euler(q, f, t, Δt) + _q0 = get_dof_values(q) + + _q1 = stepper(q, Δt) + + set_dof_values!(q, _q1) + _q2 = (3 / 4) .* _q0 .+ (1 / 4) .* stepper(q, t + Δt) + + set_dof_values!(q, _q2) + _q1 .= (1 / 3) * _q0 .+ (2 / 3) .* stepper(q, t + Δt / 2) + + return _q1 +end + +""" +Time integration of `f(q, t)` over a timestep `Δt`. +""" +forward_euler(q, f::Function, t, Δt) = get_dof_values(q) .+ Δt .* f(q, t) + +mutable struct VtkHandler + basename::String + basename_residual::String + ite::Int + VtkHandler(basename) = new(basename, basename * "_residual", 0) +end + +""" + Write solution (at cell centers) to vtk + Wrapper for `write_vtk` +""" +function append_vtk(vtk, mesh, vars, t, params) + h, hu = vars + + vtk_degree = maximum(x -> get_degree(Bcube.get_function_space(get_fespace(x))), vars) + vtk_degree = max(1, mesh_degree, vtk_degree) + + _h = var_on_nodes_discontinuous(h, mesh, vtk_degree) + _hu = var_on_nodes_discontinuous(hu, mesh, vtk_degree) + _u = velocity.(_h, _hu) + + # Write + dict_vars_dg = Dict( + "h" => (_h, VTKPointData()), + "hu" => (_hu, VTKPointData()), + "u" => (_u, VTKPointData()), + "h_mean" => (get_values(Bcube.cell_mean(h, params.dΩ)), VTKCellData()), + "hu_mean" => (get_values(Bcube.cell_mean(hu, params.dΩ)), VTKCellData()), + "lim_h" => (get_values(params.limh), VTKCellData()), + "centers" => (params.xc, VTKCellData()), + ) + + Bcube.write_vtk_discontinuous( + vtk.basename * "_DG", + vtk.ite, + t, + mesh, + dict_vars_dg, + vtk_degree; + append = vtk.ite > 0, + ) + + # Update counter + vtk.ite += 1 + return nothing +end + +function init!(q, dΩ, initstate) + x0 = SA[initstate.x0, initstate.y0] + Lstep = initstate.Lstep + hstep = initstate.hstep + + f_h(x) = norm(x - x0) < Lstep / 2 ? hstep : 0.0 + # f_h(x) = hstep * exp.(-(abs(x[1] - x0) ./ Lstep)^2 ./ 2) + f_hu(x) = SA[0.0, 0.0] + f = map(PhysicalFunction, (f_h, f_hu)) + projection_l2!(q, f, dΩ) + return nothing +end + +function run_simulation(stateInit) + # Then generate the mesh of a rectangle using Gmsh and read it + tmp_path = "tmp.msh" + nx, ny, lx, ly = stateInit.nx, stateInit.ny, stateInit.lx, stateInit.ly + gen_rectangle_mesh( + tmp_path, + :quad; + nx = nx, + ny = ny, + lx = lx, + ly = ly, + xc = 0.0, + yc = 0.0, + ) + mesh = read_msh(tmp_path, 2) # '2' indicates the space dimension (3 by default) + rm(tmp_path) + + dimcar = compute_dimcar(mesh) + + # Create a `CellVariable` + fs = FunctionSpace(fspace, degree) + Q_sca = TrialFESpace(fs, mesh, :discontinuous; size = 1) # DG, scalar + Q_vec = TrialFESpace(fs, mesh, :discontinuous; size = 2) # DG, vectoriel + V_sca = TestFESpace(Q_sca) + V_vec = TestFESpace(Q_vec) + + Q = MultiFESpace(Q_sca, Q_vec) + V = MultiFESpace(V_sca, V_vec) + q = FEFunction(Q) + + # select an initial configurations: + init!(q, mesh, stateInit) + + DMPrelax = DMPcurv₀ .* dimcar .^ 2 + + # Then we create a `NamedTuple` to hold the simulation parameters. + params = (degree = degree, stateInit = stateInit, DMPrelax = DMPrelax) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), QUAD) + dΓ = Measure(InteriorFaceDomain(mesh), QUAD) + + # Declare boundary conditions and + # create associated domains and measures + periodicBCType_x = PeriodicBCType(Translation(SA[-lx, 0.0]), ("East",), ("West",)) + periodicBCType_y = PeriodicBCType(Translation(SA[0.0, ly]), ("South",), ("North",)) + Γ_perio_x = BoundaryFaceDomain(mesh, periodicBCType_x) + Γ_perio_y = BoundaryFaceDomain(mesh, periodicBCType_y) + dΓ_perio_x = Measure(Γ_perio_x, QUAD) + dΓ_perio_y = Measure(Γ_perio_y, QUAD) + + xc = get_cell_centers(mesh) # used for VTK outputs + + params = ( + params..., + dΓ = dΓ, + dΩ = dΩ, + dΓ_perio_x = dΓ_perio_x, + dΓ_perio_y = dΓ_perio_y, + xc = xc, + ) + + # create CellData to store limiter values + limh = Bcube.MeshCellData(ones(ncells(mesh))) + limAll = Bcube.MeshCellData(ones(ncells(mesh))) + params = (params..., limh = limh, limAll = limAll) + + # Init vtk handler + mkpath(outputpath) + vtk = VtkHandler(output) + + # Init time + time = 0.0 + + # cache mass matrices + cache = ( + mass = compute_mass_matrix(Q, V, dΩ), + mass_sca = compute_mass_matrix(Q_sca, V_sca, dΩ), + mass_vec = compute_mass_matrix(Q_vec, V_vec, dΩ), + cacheCellMean = Bcube.build_cell_mean_cache(q, dΩ), + ) + Δt = Δt₀ + + limiter_projection && apply_limitation!(q, params, cache) + + # Save initial solution + append_vtk(vtk, mesh, q, time, params) + + # Let's loop to solve the equation. + for i in 1:nite + Δt = compute_timestep!(q, dΩ, dimcar, CFL) + + ## Infos + if (i % Int(max(floor(nite / (nout * 10)), 1)) == 0) + println("---") + println("Iteration ", i) + @show Δt, CFL + end + + ## Step forward in time + _rhs(q, t) = rhs(q, Q, V, params, cache) + if timeScheme == :ForwardEuler + qnew = forward_euler(q, _rhs, time, Δt) + elseif timeScheme == :RK3_SPP + qnew = rk3_ssp(q, _rhs, time, Δt) + else + error("Unknown time scheme") + end + set_dof_values!(q, qnew) + + limiter_projection && apply_limitation!(q, params, cache) + + time += Δt + + ## Write solution to file (respecting max. number of output) + if (i % Int(max(floor(nite / nout), 1)) == 0) + append_vtk(vtk, mesh, q, time, params) + end + end + + append_vtk(vtk, mesh, q, time, params) + + println("Benchmarking 'forward_euler':") + _rhs1(q, t) = rhs(q, Q, V, params, cache) + @btime forward_euler($q, $_rhs1, $time, $Δt) + @btime apply_limitation!($q, $params, $cache) + println("ndofs total = ", Bcube.get_ndofs(Q)) + + Profile.init(; n = 10^7) # returns the current settings + Profile.clear() + Profile.clear_malloc_data() + @profile begin + for i in 1:100 + forward_euler(q, _rhs1, time, Δt) + limiter_projection && apply_limitation!(q, params, cache) + end + end +end + +function compute_timestep!(q, dΩ, dimcar, CFL) + q_mean = map(Base.Fix2(Bcube.cell_mean, dΩ), get_fe_functions(q)) + λ(x...) = shallow_water_maxeigval(x, stateInit.gravity) + λmax = map(λ, get_values.(q_mean)...) + Δt = CFL * minimum(dimcar ./ λmax) + return Δt +end + +function shallow_water_maxeigval(q, gravity) + h, hu = q + u = velocity(h, hu) + return norm(u) + √(abs(gravity) * max(h, eps_h)) +end + +function shallow_water_maxeigval(q, n, gravity) + h, hu = q + un = velocity(h, hu) ⋅ n + return abs(un) + √(abs(gravity) * max(h, eps_h)) +end + +function shallow_water_eigval(q, n, gravity) + h, hu = q + un = velocity(h, hu) ⋅ n + c = √(abs(gravity) * max(h, eps_h)) + return un - c, un + c +end + +function compute_dimcar(mesh) + fs = FunctionSpace(:Lagrange, 0) + V = TestFESpace(fs, mesh; size = 1, isContinuous = false) + + # Define measures for cell and interior face integrations + dΩ = Measure(CellDomain(mesh), QUAD) + dΓ = Measure(InteriorFaceDomain(mesh), QUAD) + dΓ_bc = Measure(BoundaryFaceDomain(mesh), QUAD) + + f1 = PhysicalFunction(x -> 1.0) + l(v) = ∫(f1 ⋅ v)dΩ + l_face(v, dω) = ∫(side⁻(f1) ⋅ side⁻(v) + side⁺(f1) ⋅ side⁺(v))dω + + vol = assemble_linear(l, V) + surf = assemble_linear(Base.Fix2(l_face, dΓ), V) + assemble_linear!(surf, Base.Fix2(l_face, dΓ_bc), V) + return vol ./ surf +end + +function apply_limitation!(q, params, cache) + h, hu = q + dΩ = params.dΩ + + q_mean = Bcube.cell_mean(q, cache.cacheCellMean) + + _limh, _h_proj = linear_scaling_limiter( + h, + params.dΩ; + bounds = (hmin₀, hmax₀), + DMPrelax = params.DMPrelax, + mass = cache.mass_sca, + ) + set_dof_values!(h, get_dof_values(_h_proj)) + + h_mean, hu_mean, = q_mean + limited_var(a, a̅, lim_a) = a̅ + lim_a * (a - a̅) + projection_l2!(hu, limited_var(hu, hu_mean, _limh), params.dΩ; mass = cache.mass_vec) + + if eltype(_limh) == eltype(params.limh) # skip Dual number case + set_values!(params.limh, get_values(_limh)) + end + return nothing +end + +# Function-space get_degree (Taylor(0) = first order Finite Volume) +const degree = 1 +const mesh_degree = 1 +const fspace = Bcube.Lagrange(:Lobatto) #:Lagrange +# The degree of quadrature is chosen such as mass matrix are integrated exactly. +const QUAD = Quadrature(QuadratureLobatto(), max(1, 2 * degree)) +const limiter_projection = true +const hmin₀ = 1.e-8 +const hmax₀ = 1.0e10 +const DMPcurv₀ = 10.0 + +const stateInit = ( + gravity = 9.81, + nx = 65, + ny = 65, + lx = 2.0, + ly = 2.0, + x0 = 0.0, + y0 = 0.0, + Lstep = 1.0, + hstep = 2.5, +) +const nite = 5000 #300000 # Number of time iteration(s) +const timeScheme = :ForwardEuler # :ForwardEuler, :RK3_SPP +const CFL = 0.4 / (2 * degree + 1) +const nout = 100 # Number of time steps to save +const outputpath = string(@__DIR__, "/../myout/shallow_water_newapi/") +const output = outputpath * "sw_deg$degree" +const Δt₀ = 1.e-7 + +@show get_degree(QUAD) + +mkpath(outputpath) +run_simulation(stateInit) + +end #hide diff --git a/src/tutorial/Project.toml b/src/tutorial/Project.toml new file mode 100644 index 0000000..551909a --- /dev/null +++ b/src/tutorial/Project.toml @@ -0,0 +1,3 @@ +[deps] +Bcube = "cf06320b-b7f3-4748-8003-81a6b6979792" +WriteVTK = "64499a7a-5c06-52f2-abe2-ccb03c286192" diff --git a/src/tutorial/heat_equation.jl b/src/tutorial/heat_equation.jl new file mode 100644 index 0000000..2dedd68 --- /dev/null +++ b/src/tutorial/heat_equation.jl @@ -0,0 +1,156 @@ +module heat_equation_API #hide +println("Running heat equation API example...") #hide +# # Heat equation (FE) +# In this tutorial, the heat equation (first steady and then unsteady) is solved using finite-elements. +# +# # 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 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. + +# # Steady case +# As usual, start by importing the necessary packages. +using Bcube +using LinearAlgebra +using WriteVTK + +# First we define some physical and numerical constants +const htc = 100.0 # Heat transfer coefficient (bnd cdt) +const Tr = 268.0 # Recovery temperature (bnd cdt) +const phi = 100.0 +const q = 1500.0 +const λ = 100.0 +const η = λ +const ρCp = 100.0 * 200.0 +const degree = 2 +const outputpath = joinpath(@__DIR__, "../myout/heat_equation/") + +# Read 2D mesh +mesh_path = joinpath(@__DIR__, "../input/mesh/domainSquare_tri.msh") +mesh = read_msh(mesh_path) + +# Build function space and associated Trial and Test FE spaces. +# We impose a Dirichlet condition with a temperature of 260K +# on boundary "West" +fs = FunctionSpace(:Lagrange, degree) +U = TrialFESpace(fs, mesh, Dict("West" => 260.0)) +V = TestFESpace(U) + +# Define measures for cell integration +dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + +# Define bilinear and linear forms +a(u, v) = ∫(η * ∇(u) ⋅ ∇(v))dΩ +l(v) = ∫(q * v)dΩ + +# Create an affine FE system and solve it using the `AffineFESystem` structure. +# The package `LinearSolve` is used behind the scenes, so different solver may +# be used to invert the system (ex: `solve(...; alg = IterativeSolversJL_GMRES())`) +# The result is a FEFunction (`ϕ`). +# We can interpolate it on mesh centers : the result is named `Tcn`. +sys = AffineFESystem(a, l, U, V) +ϕ = solve(sys) +Tcn = var_on_centers(ϕ, mesh) + +# Compute analytical solution for comparison. Apply the analytical solution +# on mesh centers +T_analytical = x -> 260.0 + (q / λ) * x[1] * (1.0 - 0.5 * x[1]) +Tca = map(T_analytical, get_cell_centers(mesh)) + +# Write both the obtained FE solution and the analytical solution to a vtk file. +mkpath(outputpath) +dict_vars = + Dict("Temperature" => (Tcn, VTKCellData()), "Temperature_a" => (Tca, VTKCellData())) +write_vtk(outputpath * "result_steady_heat_equation", 0, 0.0, mesh, dict_vars) + +# Compute and display the error +@show norm(Tcn .- Tca, Inf) / norm(Tca, Inf) + +# # Unsteady case +# The code for the unsteady case if of course very similar to the steady case, at least for the +# beginning. Start by defining two additional parameters: +totalTime = 100.0 +Δt = 0.1 + +# Read a slightly different mesh +mesh_path = joinpath(@__DIR__, "../input/mesh/domainSquare_tri_2.msh") +mesh = read_msh(mesh_path) + +# The rest is similar to the steady case +fs = FunctionSpace(:Lagrange, degree) +U = TrialFESpace(fs, mesh, Dict("West" => 260.0)) +V = TestFESpace(U) +dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + +# Compute matrices associated to bilinear and linear forms, and assemble +a(u, v) = ∫(η * ∇(u) ⋅ ∇(v))dΩ +m(u, v) = ∫(ρCp * u ⋅ v)dΩ +l(v) = ∫(q * v)dΩ + +A = assemble_bilinear(a, U, V) +M = assemble_bilinear(m, U, V) +L = assemble_linear(l, V) + +# Compute a vector of dofs whose values are zeros everywhere +# except on dofs lying on a Dirichlet boundary, where they +# take the Dirichlet value +Ud = assemble_dirichlet_vector(U, V, mesh) + +# Apply lift +L = L - A * Ud + +# Apply homogeneous dirichlet condition +apply_homogeneous_dirichlet_to_vector!(L, U, V, mesh) +apply_dirichlet_to_matrix!((A, M), U, V, mesh) + +# Form time iteration matrix +# (note that this is bad for performance since up to now, +# M and A are sparse matrices) +Miter = factorize(M + Δt * A) + +# Init the solution with a constant temperature of 260K +ϕ = FEFunction(U, 260.0) + +# Write initial solution to a file +mkpath(outputpath) +dict_vars = Dict("Temperature" => (var_on_centers(ϕ, mesh), VTKCellData())) +write_vtk(outputpath * "result_unsteady_heat_equation", 0, 0.0, mesh, dict_vars) + +# Time loop +itime = 0 +t = 0.0 +while t <= totalTime + global t, itime + t += Δt + itime = itime + 1 + @show t, itime + + ## Compute rhs + rhs = Δt * L + M * (get_dof_values(ϕ) .- Ud) + + ## Invert system and apply inverse shift + set_dof_values!(ϕ, Miter \ rhs .+ Ud) + + ## Write solution (every 10 iterations) + if itime % 10 == 0 + dict_vars = Dict("Temperature" => (var_on_centers(ϕ, mesh), VTKCellData())) + write_vtk( + outputpath * "result_unsteady_heat_equation", + itime, + t, + mesh, + dict_vars; + append = true, + ) + end +end + +end #hide diff --git a/src/tutorial/helmholtz.jl b/src/tutorial/helmholtz.jl new file mode 100644 index 0000000..035777f --- /dev/null +++ b/src/tutorial/helmholtz.jl @@ -0,0 +1,145 @@ +module Helmholtz #hide +println("Running Helmholtz example...") #hide +# # Helmholtz equation (FE) +# This tutorial shows how to solve the Helmholtz eigen problem with a finite-element approach using Bcube. +# # Theory +# We consider the following Helmholtz equation, representing for instance the acoustic wave propagation with Neuman boundary condition(s): +# ```math +# \begin{cases} +# \Delta u + \omega^2 u = 0 \\ +# \dfrac{\partial u}{\partial n} = 0 \textrm{ on } \Gamma +# \end{cases} +# ``` +# +# An analytic solution of this equation can be obtained: for a rectangular domain $$\Omega = [0,L_x] \times [0,L_y]$$, +# ```math +# u(x,y) = \cos \left( \frac{k_x \pi}{L_x} x \right) \cos \left( \frac{k_y \pi}{L_y} y \right) \mathrm{~~with~~} k_x,~k_y \in \mathbb{N} +# ``` +# with $$\omega^2 = \pi^2 \left( \dfrac{k_x^2}{L_x^2} + \dfrac{k_y^2}{L_y^2} \right)$$ +# +# Now, both the finite-element method and the discontinuous Galerkin method requires to write the weak form of the problem: +# ```math +# \int_\Omega (\Delta u \Delta v + \omega^2u)v \mathrm{\,d}\Omega = 0 +# ``` +# ```math +# - \int_\Omega \nabla u \cdot \nabla v \mathrm{\,d}\Omega +# + \underbrace{\left[ (\nabla u \cdot n) v \right]_\Gamma}_{=0} + \omega^2 \int_\Omega u v \mathrm{\,d} \Omega = 0 +# ``` +# ```math +# \int_\Omega \nabla u \cdot \nabla v \mathrm{\,d} \Omega = \omega^2 \int_\Omega u v \mathrm{\,d} \Omega +# ``` +# Introducing to bilinear forms ``a(u,v)`` and ``b(u,v)`` for respectively the left and right side terms, +# this equation can be written +# ```math +# a(u,v) = \omega^2 b(u,v) +# ``` +# This is actually a generalized eigenvalue problem which can be written: +# ```math +# A u = \alpha B u +# ``` +# where +# ```math +# A u = \int_\Omega \nabla u \cdot \nabla v \mathrm{\,d} \Omega,~~ B u = \int_\Omega u v \mathrm{\,d} \Omega,~~ \alpha = \omega^2 +# ``` + +# # Uncommented code +# The code below solves the described Helmholtz eigen problem. The code with detailed comments +# is provided in the next section. +using Bcube +using LinearAlgebra + +mesh = rectangle_mesh(21, 21) + +degree = 1 + +U = TrialFESpace(FunctionSpace(:Lagrange, degree), mesh) +V = TestFESpace(U) + +dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + +a(u, v) = ∫(∇(u) ⋅ ∇(v))dΩ +b(u, v) = ∫(u ⋅ v)dΩ + +A = assemble_bilinear(a, U, V) +B = assemble_bilinear(b, U, V) + +vp, vecp = eigen(Matrix(A), Matrix(B)) +@show sqrt.(abs.(vp[3:8])) + +# # Commented code +# Load the necessary packages +using Bcube +using LinearAlgebra + +# Mesh a 2D rectangular domain with quads. +mesh = rectangle_mesh(21, 21) + +# Next, create the function space that will be used for the trial and test spaces. +# The Lagrange polynomial space is used here. The degree is set to `1`. +degree = 1 +fs = FunctionSpace(:Lagrange, degree) + +# The trial space is created from the function space and the mesh. By default, a scalar "continuous" +# FESpace is created. For "discontinuous" ("DG") example, check out the linear transport tutorial. +U = TrialFESpace(fs, mesh) +V = TestFESpace(U) + +# Then, define the geometrical dommain on which the operators will apply. For this finite-element example, +# we only need a `CellDomain` (no `FaceDomain`). +domain = CellDomain(mesh) + +# Now, integrating on a domain necessitates a "measure", basically a quadrature of given degree. +dΩ = Measure(domain, Quadrature(2 * degree + 1)) + +# The definition of the two bilinear forms is quite natural. Note that these definitions are lazy, +# no computation is done at this step : the computations will be triggered by the assembly. +a(u, v) = ∫(∇(u) ⋅ ∇(v))dΩ +b(u, v) = ∫(u ⋅ v)dΩ + +# To obtain the two sparse matrices corresponding to the discretisation of these bilinear forms, +# simply call the `assemble_bilinear` function, providing the trial and test spaces. +A = assemble_bilinear(a, U, V) +B = assemble_bilinear(b, U, V) + +# Compute eigen-values and vectors : we convert to dense matrix to avoid importing additionnal packages, +# but it is quite easy to solve it in a "sparse way". +vp, vecp = eigen(Matrix(A), Matrix(B)) + +# Display the "first" five eigenvalues: +@show sqrt.(abs.(vp[3:8])) +results = sqrt.(abs.(vp[3:8])) #hide +ref_results = [ + 3.144823462554393, + 4.447451992013584, + 6.309054755690625, + 6.309054755690786, + 7.049403274103087, + 7.049403274103147, +] #hide +@assert all(results .≈ ref_results) "Invalid Helmholtz results" #hide + +# Now we can export the solution (the eigenvectors) at nodes of the mesh for several eigenvalues. +# We will restrict to the first 20 eigenvectors. To do so, we will create a `FEFunction` for each +# eigenvector. This `FEFunction` can then be evaluated on the mesh centers, nodes, etc. +ϕ = FEFunction(U) +nvecs = min(20, get_ndofs(U)) +values = zeros(nnodes(mesh), nvecs) +for ivec in 1:nvecs + set_dof_values!(ϕ, vecp[:, ivec]) + values[:, ivec] = var_on_vertices(ϕ, mesh) +end + +# To write a VTK file, we need to build a dictionnary linking the variable name with its +# values and type +using WriteVTK +out_dir = joinpath(@__DIR__, "../myout") # output directory +isdir(out_dir) || mkdir(out_dir) #hide +dict_vars = Dict("u_$i" => (values[:, i], VTKPointData()) for i in 1:nvecs) +write_vtk(joinpath(out_dir, "helmholtz_rectangle_mesh"), 0, 0.0, mesh, dict_vars) + +# And here is the eigenvector corresponding to the 4th eigenvalue: +# ```@raw html +# drawing +# ``` + +end #hide diff --git a/src/tutorial/helmholtz_multi.jl b/src/tutorial/helmholtz_multi.jl new file mode 100644 index 0000000..efc88e6 --- /dev/null +++ b/src/tutorial/helmholtz_multi.jl @@ -0,0 +1,108 @@ +module Helmholtz #hide +println("Running Helmholtz example...") #hide +# # Helmholtz +# # Theory +# We consider the following Helmholtz equation, representing for instance the acoustic wave propagation with Neuman boundary condition(s): +# ```math +# \begin{cases} +# \Delta u + \omega^2 u = 0 \\ +# \dfrac{\partial u}{\partial n} = 0 \textrm{ on } \Gamma +# \end{cases} +# ``` +# +# An analytic solution of this equation can be obtained: for a rectangular domain $$\Omega = [0,L_x] \times [0,L_y]$$, +# ```math +# u(x,y) = \cos \left( \frac{k_x \pi}{L_x} x \right) \cos \left( \frac{k_y \pi}{L_y} y \right) \mathrm{~~with~~} k_x,~k_y \in \mathbb{N} +# ``` +# with $$\omega^2 = \pi^2 \left( \dfrac{k_x^2}{L_x^2} + \dfrac{k_y^2}{L_y^2} \right)$$ +# +# Now, both the finite-element method and the discontinuous Galerkin method requires to write the weak form of the problem: +# ```math +# - \int_\Omega \nabla u \cdot \nabla v \mathrm{\,d}\Omega +# + \underbrace{\left[ (\nabla u \cdot n) v \right]_\Gamma}_{=0} + \omega^2 \int_\Omega u v \mathrm{\,d} \Omega = 0 +# ``` +# ```math +# \int_\Omega \nabla u \cdot \nabla v \mathrm{\,d} \Omega = \omega^2 \int_\Omega u v \mathrm{\,d} \Omega +# ``` +# This equation is actually a generalized eigenvalue problem which can be writtin in matrix / linear operator form: +# ```math +# A u = \alpha B u +# ``` +# where +# ```math +# A u = \int_\Omega \nabla u \cdot \nabla v \mathrm{\,d} \Omega,~~ B u = \int_\Omega u v \mathrm{\,d} \Omega,~~ \alpha = \omega^2 +# ``` +# # Code +# Load the necessary packages +const dir = string(@__DIR__, "/") +using Bcube +using LinearAlgebra +using WriteVTK +using Printf + +# Mesh a 2D rectangular domain with quads. +# mesh = one_cell_mesh(:line) +mesh = line_mesh(4) +# mesh = rectangle_mesh(3, 3) +# mesh = rectangle_mesh(21, 21) + +# Next, create a scalar variable named `:u`. The Lagrange polynomial space is used here. By default, +# a "continuous" function space is created (by opposition to a "discontinuous" one). The degree is set to `1`. +degree = 1 +fs = FunctionSpace(:Lagrange, degree) +U1 = TrialFESpace(fs, mesh) +U2 = TrialFESpace(fs, mesh) +V1 = TestFESpace(U1) +V2 = TestFESpace(U2) + +U = MultiFESpace(U1, U2; arrayOfStruct = false) # `false` only to facilitate debug +V = MultiFESpace(V1, V2; arrayOfStruct = false) # `false` only to facilitate debug + +# Define measures for cell and interior face integrations +dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + +# compute volume residuals +a1((u1, u2), (v1, v2)) = ∫(∇(u1) ⋅ ∇(v1) + ∇(u2) ⋅ ∇(v2))dΩ +a2((u1, u2), (v1, v2)) = ∫(u1 ⋅ v1 + u2 ⋅ v2)dΩ + +# build sparse matrices from integration result +A = assemble_bilinear(a1, U, V) +B = assemble_bilinear(a2, U, V) + +display(A) +display(B) +# @show display(B) + +# # Compute eigen-values and vectors : we convert to dense matrix to avoid importing additionnal packages, +# # but it is quite easy to solve it in a "sparse way". +# vp, vecp = eigen(Array(A), Array(B)) + +# # Display the "first" five eigenvalues: +# @show sqrt.(abs.(vp[3:8])) + +# # Check results with expected ones #hide +# results = sqrt.(abs.(vp[3:8])) #hide +# ref_results = [3.144823462554393, 4.447451992013584, 6.309054755690625, 6.309054755690786, 7.049403274103087, 7.049403274103147] #hide +# @assert all(results .≈ ref_results) "Invalid results" #hide + +# # Now we can export the solution at nodes of the mesh for several eigenvalues. +# # We will restrict to the first 20 eigenvectors. +# nd = length(get_values(ϕ)) +# nvecs = min(20, nd) +# values = zeros(nnodes(mesh), nvecs) +# for i in 1:nvecs +# set_values!(ϕ, vecp[:, i]) +# values[:, i] = var_on_nodes(ϕ) +# end + +# # To write a VTK file, we need to build a dictionnary linking the variable name with its +# # values and type +# dict_vars = Dict(@sprintf("u_%02d", i) => (values[:, i], VTKPointData()) for i in 1:nvecs) +# write_vtk(dir * "../myout/helmholtz_rectangle_mesh", 0, 0.0, mesh, dict_vars) + +# And here is the eigenvector corresponding to the 6th eigenvalue: +# ```@raw html +# drawing +# ``` + +end #hide diff --git a/src/tutorial/linear_transport.jl b/src/tutorial/linear_transport.jl new file mode 100644 index 0000000..6564f4c --- /dev/null +++ b/src/tutorial/linear_transport.jl @@ -0,0 +1,199 @@ +module LinearTransport #hide +println("Running linear transport example...") #hide +# # Linear transport (DG) +# In this tutorial, we show how to solve a linear transport equation using a discontinuous-Galerkin +# framework with Bcube. +# # Theory +# In this example, we solve the following linear transport equation using discontinuous elements: +# ```math +# \frac{\partial \phi}{\partial t} + \nabla \cdot (c \phi) = 0 +# ``` +# where ``c`` is a constant velocity. Using an explicit time scheme, one obtains: +# ```math +# \phi^{n+1} = \phi^n - \Delta t \nabla \cdot (c \phi^n) +# ``` +# The corresponding weak form of this equation is: +# ```math +# \int_\Omega \phi^{n+1} v \mathrm{\,d}\Omega = \int_\Omega \phi^n v \mathrm{\,d}\Omega + \Delta t \left[ +# \int_\Omega c \phi^n \cdot \nabla v \mathrm{\,d}\Omega - \oint_\Gamma \left( c \phi \cdot n \right) v \mathrm{\,d}\Gamma +# \right] +# ``` +# where ``\Gamma = \delta \Omega``. Adopting the discontinuous Galerkin framework, this equation is written in every mesh cell +# ``\Omega_i``. The cell boundary term involves discontinuous quantities and is replaced by a "numerical flux", +# leading to the expression: +# ```math +# \int_{\Omega_i} \phi^{n+1} v \mathrm{\,d}\Omega_i = \int_{\Omega_i} \phi^n v \mathrm{\,d}\Omega_i + \Delta t \left[ +# \int_{\Omega_i} c \phi^n \cdot \nabla v \mathrm{\,d}\Omega_i - \oint_{\Gamma_i} F^*(\phi) v \mathrm{\,d} \Gamma_i +# \right] +# ``` +# For this example, an upwind flux will be used for ``F^*``. Using a matrix formulation, the above equation can be written as: +# ```math +# \phi^{n+1} = \phi^n + M^{-1}(f_\Omega - f_\Gamma) +# ``` +# where ``M^{-1}`` is the inverse of the mass matrix, ``f_\Omega`` the volumic flux term and ``f_\Gamma`` the surfacic flux term. +# +# # Commented code +# Start by importing the necessary packages: +# Load the necessary packages +using Bcube +using LinearAlgebra +using WriteVTK + +# Before all, to ease to ease the solution VTK output we will write a +# structure to store the vtk filename and the number of iteration; and a function +# that exports the solution on demand. Note the use of `var_on_nodes_discontinuous` +# to export the solution on the mesh nodes, respecting the discontinuous feature of the +# solution. +mutable struct VtkHandler + basename::Any + ite::Any + mesh::Any + VtkHandler(basename, mesh) = new(basename, 0, mesh) +end + +function append_vtk(vtk, u::Bcube.AbstractFEFunction, t) + ## Values on center + values = var_on_nodes_discontinuous(u, vtk.mesh) + + ## Write + Bcube.write_vtk_discontinuous( + vtk.basename, + vtk.ite, + t, + vtk.mesh, + Dict("u" => (values, VTKPointData())), + 1; + append = vtk.ite > 0, + ) + + ## Update counter + vtk.ite += 1 +end + +# First, we define some physical and numerical constant parameters +const degree = 0 # Function-space degree (Taylor(0) = first order Finite Volume) +const c = [1.0, 0.0] # Convection velocity (must be a vector) +const nite = 100 # Number of time iteration(s) +const CFL = 1 # 0.1 for degree 1 +const nx = 41 # Number of nodes in the x-direction +const ny = 41 # Number of nodes in the y-direction +const lx = 2.0 # Domain width +const ly = 2.0 # Domain height +const Δt = CFL * min(lx / nx, ly / ny) / norm(c) # Time step + +# Then generate the mesh of a rectangle using Gmsh and read it +tmp_path = "tmp.msh" +gen_rectangle_mesh(tmp_path, :quad; nx = nx, ny = ny, lx = lx, ly = ly, xc = 0.0, yc = 0.0) +mesh = read_msh(tmp_path) +rm(tmp_path) + +# We can now init our `VtkHandler` +out_dir = joinpath(@__DIR__, "../myout") +isdir(out_dir) || mkdir(out_dir) #hide +vtk = VtkHandler(joinpath(out_dir, "linear_transport"), mesh) + +# As seen in the previous tutorial, the definition of trial and test spaces needs a mesh and +# a function space. Here, we select Taylor space, and build discontinuous FE spaces with it. +# Then an FEFunction, that will represent our solution, is created. +fs = FunctionSpace(:Taylor, degree) +U = TrialFESpace(fs, mesh, :discontinuous) +V = TestFESpace(U) +u = FEFunction(U) + +# Define measures for cell and interior face integrations +Γ = InteriorFaceDomain(mesh) +Γ_in = BoundaryFaceDomain(mesh, "West") +Γ_out = BoundaryFaceDomain(mesh, ("North", "East", "South")) + +dΩ = Measure(CellDomain(mesh), 2 * degree + 1) +dΓ = Measure(Γ, 2 * degree + 1) +dΓ_in = Measure(Γ_in, 2 * degree + 1) +dΓ_out = Measure(Γ_out, 2 * degree + 1) + +# We will also need the face normals associated to the different face domains. +# Note that this operation is lazy, `nΓ` is just an abstract representation on +# face normals of `Γ`. +nΓ = get_face_normals(Γ) +nΓ_in = get_face_normals(Γ_in) +nΓ_out = get_face_normals(Γ_out) + +# Let's move on to the bilinear and linear forms. First, the two easiest ones: +m(u, v) = ∫(u ⋅ v)dΩ # Mass matrix +l_Ω(v) = ∫((c * u) ⋅ ∇(v))dΩ # Volumic convective term + +# For the flux term, we first need to define a numerical flux. It is convenient to define it separately +# in a dedicated function. Here is the definition of simple upwind flux. +function upwind(ui, uj, nij) + cij = c ⋅ nij + if cij > zero(cij) + flux = cij * ui + else + flux = cij * uj + end + flux +end +# We then define the "flux" as the composition of the upwind function and the needed entries: namely the +# solution on the negative side of the face, the solution on the positive face, and the face normal. The +# orientation negative/positive is arbitrary, the only convention is that the face normals are oriented from +# the negative side to the positive side. +flux = upwind ∘ (side⁻(u), side⁺(u), side⁻(nΓ)) +l_Γ(v) = ∫(flux * jump(v))dΓ + +# Finally, we define what to perform on the "two" boundaries : inlet / oulet. +# On the inlet, we directly impose the flux with a user defined function that depends on the time +# (the input is an oscillating wave). +# On the outlet, we keep our upwind flux but we impose the ghost cell value. +bc_in = t -> PhysicalFunction(x -> c .* cos(3 * x[2]) * sin(4 * t)) # flux +l_Γ_in(v, t) = ∫(side⁻(bc_in(t)) ⋅ side⁻(nΓ_in) * side⁻(v))dΓ_in +flux_out = upwind ∘ (side⁻(u), 0.0, side⁻(nΓ_out)) +l_Γ_out(v) = ∫(flux_out * side⁻(v))dΓ_out + +# Assemble the (constant) mass matrix. The returned matrix is a sparse matrix. To simplify the +# tutorial, we will directly compute the inverse mass matrix. But note that way more performant +# strategies should be employed to solve such a problem (since we don't need the inverse, only the +# matrix-vector product). +M = assemble_bilinear(m, U, V) +invM = inv(Matrix(M)) #WARNING : really expensive !!! + +# Let's also create three vectors to avoid allocating them at each time step +nd = get_ndofs(V) +b_vol = zeros(nd) +b_fac = similar(b_vol) +rhs = similar(b_vol) + +# The time loop is trivial : at each time step we compute the linear forms using +# the `assemble_` methods, we complete the rhs, perform an explicit step and write +# the solution. +t = 0.0 +for i in 1:nite + global t + + ## Reset pre-allocated vectors + b_vol .= 0.0 + b_fac .= 0.0 + + ## Compute linear forms + assemble_linear!(b_vol, l_Ω, V) + assemble_linear!(b_fac, l_Γ, V) + assemble_linear!(b_fac, v -> l_Γ_in(v, t), V) + assemble_linear!(b_fac, l_Γ_out, V) + + ## Assemble rhs + rhs .= Δt .* invM * (b_vol - b_fac) + + ## Update solution + u.dofValues .+= rhs + + ## Update time + t += Δt + + ## Write to file + append_vtk(vtk, u, t) +end + +# And here is an animation of the result: +# ```@raw html +# drawing +# ``` + +end #hide diff --git a/src/tutorial/linear_transport_profile.jl b/src/tutorial/linear_transport_profile.jl new file mode 100644 index 0000000..44d6b1b --- /dev/null +++ b/src/tutorial/linear_transport_profile.jl @@ -0,0 +1,314 @@ +module LinearTransport #hide + +using Bcube +using StaticArrays +using LinearAlgebra +using BenchmarkTools +using Profile +#using Cthulhu + +const degree = 1 # Function-space degree (Taylor(0) = first degree Finite Volume) +const nite = 100 # Number of time iteration(s) +# const c = 2.0 # Convection velocity (must be a vector) +const c = SA[-2.5, 4.0] # Convection velocity (must be a vector) +const CFL = 1 # 0.1 for degree 1 +const nx = 128 # Number of nodes in the x-direction +const ny = 128 # Number of nodes in the y-direction +const lx = 2.0 # Domain width +const ly = 2.0 # Domain height +const Δt = CFL * (lx / nx) / norm(c) # Time step + +function run_linear_transport() + + # tmp_path = "tmp.msh" + # gen_rectangle_mesh(tmp_path, :quad; nx = nx, ny = ny, lx = lx, ly = ly, xc = 0., yc = 0.) + # mesh = read_msh(tmp_path) + # rm(tmp_path) + # mesh = line_mesh(nx; xmin=-lx / 2, xmax=lx / 2) + mesh = + rectangle_mesh(nx, ny; xmin = -lx / 2, xmax = lx / 2, ymin = -ly / 2, ymax = ly / 2) + # mesh = rectangle_mesh(3, 3) + + fs = FunctionSpace(:Lagrange, degree) + U = TrialFESpace(fs, mesh; isContinuous = false) + V = TestFESpace(U) + + # Define measures for cell and interior face integrations + Γ = InteriorFaceDomain(mesh) + dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + dΓ = Measure(Γ, 2 * degree + 1) + nΓ = get_face_normals(Γ) + + m(u, v) = ∫(u ⋅ v)dΩ + M = assemble_bilinear(m, U, V) + #invM = inv(Matrix(M)) + + u = FEFunction(V, [1.0 * i for i in 1:get_ndofs(V)]) + + function upwind(ui, uj, nij) + cij = c ⋅ nij + if cij > zero(cij) + flux = cij * ui + else + flux = cij * uj + end + flux + end + + l1(v) = ∫((c * u) ⋅ ∇(v))dΩ # + l2(v) = ∫(side_n(v))dΓ + l3(v) = ∫((c ⋅ Bcube.side_p(nΓ)) ⋅ Bcube.side_p(v))dΓ + + flux4 = upwind ∘ (side_n(u), Bcube.side_p(u), side_n(nΓ)) + l4(v) = ∫(flux4 * side_n(v))dΓ + + # multi-linear variant of case 4 + function l5((v1, v2, v3)) + ∫(flux4 * (side_n(v1) + 2 * side_n(v2) + 3 * side_n(v3)))dΓ + end + + # Allocate + # b1 = assemble_linear(l1, V) + # b01 = assemble_linear(l01, V) + # @show all(isapprox.(b1, b01)) + b2 = assemble_linear(l2, V) + b3 = assemble_linear(l3, V) + b4 = assemble_linear(l4, V) + + V3 = MultiFESpace(V, V, V; arrayOfStruct = false) + b5 = assemble_linear(l5, V3) + #@descend assemble_linear(l5, V3) + + f6(x) = 3x - 5x + 10x + l6(v) = f6(l5(v)) + #Bcube.show_lazy_operator(l6(V3)) + b6 = assemble_linear(l6, V3) + + TEST_2 = true + BENCH_2 = true + PROFILING_2 = false + TEST_3 = true + BENCH_3 = true + PROFILING_3 = false + TEST_4 = true + BENCH_4 = true + PROFILING_4 = false + TEST_5 = true + BENCH_5 = true + PROFILING_5 = true + TEST_6 = true + BENCH_6 = true + PROFILING_6 = false + + # TESTS with Legacy + if true + fes = FESpace(fs, :discontinuous; size = 1) + ϕ = CellVariable(:ϕ, mesh, fes) + λ = TestFunction(mesh, fes) + nΓ_legacy = FaceNormals(dΓ) + set_values!(ϕ, get_dof_values(u)) + + for icell in 1:ncells(mesh) + @assert all(get_dof_values(u, icell) .≈ get_values(ϕ, icell)) + end + + b2_legacy(λ) = zeros(ϕ) + ∫(side⁻(λ))dΓ + if TEST_2 + _b2_legacy = b2_legacy(λ) + if b2 ≉ get_values(_b2_legacy) + @show b2 + @show get_values(_b2_legacy) + error("b2 ≉ get_values(_b2_legacy) ") + end + println("TEST_2: OK") + end + ## bench + if BENCH_2 + println("=== TEST 2") + print("Legacy : ") + @btime $b2_legacy($λ) # Legacy : 22.129 ms (383041 allocations: 31.25 MiB) + print("New API : ") + @btime assemble_linear($l2, $V) # New API : 9.544 ms (47 allocations: 5.32 MiB) + end + ## profiling + if PROFILING_2 + Profile.init(; n = 5 * 10^7) + Profile.clear() + Profile.clear_malloc_data() + @profile begin + for i in 1:100 + assemble_linear(l2, V) + end + end + end + + b3_legacy(λ) = zeros(ϕ) + ∫((c ⋅ side⁺(nΓ_legacy)) * side⁺(λ))dΓ + if TEST_3 + _b3_legacy = b3_legacy(λ) + if b3 ≉ get_values(_b3_legacy) + @show b3 + @show get_values(_b3_legacy) + @show length(b3), length(get_values(_b3_legacy)) + error("b3 ≉ get_values(_b3_legacy) ") + end + println("TEST_3: OK") + end + ## bench + if BENCH_3 + println("=== TEST 3") + print("Legacy : ") + @btime $b3_legacy($λ) # Legacy : 28.970 ms (383041 allocations: 61.53 MiB) + print("New API : ") + @btime assemble_linear($l3, $V) # New API : 14.680 ms (47 allocations: 5.32 MiB) + end + ## profiling + if PROFILING_3 + Profile.init(; n = 5 * 10^7) + Profile.clear() + Profile.clear_malloc_data() + @profile begin + for i in 1:100 + #b3_legacy(λ) + assemble_linear(l3, V) + end + end + end + + function upwind_legacy((ui, uj, nij, λi)) + flux = λi * upwind(ui, uj, nij) + end + flux4_legacy(λ) = upwind_legacy ∘ (side⁻(ϕ), side⁺(ϕ), side⁻(nΓ_legacy), side⁻(λ)) + b4_legacy(λ) = zeros(ϕ) + ∫(flux4_legacy(λ))dΓ + if TEST_4 + _b4_legacy = b4_legacy(λ) + if b4 ≉ get_values(_b4_legacy) + display(b4[1:10]) + display(get_values(_b4_legacy)[1:10]) + display(findall(x -> abs(x) > 1.0e-10, b4 .- get_values(_b4_legacy))[1:40]) + @show length(b4), length(get_values(_b4_legacy)) + error("b4 ≉ get_values(_b4_legacy) ") + end + println("TEST_4: OK") + end + ## bench + if BENCH_4 + println("=== TEST 4") + print("Legacy : ") + @btime $b4_legacy($λ) # Legacy : 33.259 ms (383041 allocations: 69.35 MiB) + print("New API : ") + @btime assemble_linear($l4, $V) # New API : 18.733 ms (47 allocations: 5.32 MiB) + end + ## profiling + if PROFILING_4 + Profile.init(; n = 5 * 10^7) + Profile.clear() + Profile.clear_malloc_data() + @profile begin + for i in 1:100 + assemble_linear(l4, V) + end + end + end + + function upwind_legacy_multi(w) + ui, uj, nij, λi = w + λi1, λi2, λi3 = λi + flux = upwind(ui, uj, nij) + (λi1 * flux, λi2 * flux, λi3 * flux) + end + function flux5_legacy(λ) + upwind_legacy_multi ∘ (side⁻(ϕ), side⁺(ϕ), side⁻(nΓ_legacy), side⁻(λ)) + end + b5_legacy(λ) = zeros.((ϕ, ϕ, ϕ)) + ∫(flux5_legacy(λ))dΓ + if TEST_5 + _b5_legacy = vcat(map(get_values, b5_legacy((λ, 2 * λ, 3 * λ)))...) + if b5 ≉ _b5_legacy + display(b5[1:10]) + display(_b5_legacy[1:10]) + display(findall(x -> abs(x) > 1.0e-10, b5 .- _b5_legacy)[1:40]) + @show length(b5), length(_b5_legacy) + @show b5 ≈ vcat(b4, b4, b4) + display( + findall(x -> abs(x) > 1.0e-10, b5 .- vcat(b4, 2 * b4, 3 .* b4))[1:40], + ) + error("b5 ≉ get_values(_b5_legacy) ") + end + println("TEST_5: OK") + end + ## bench + if BENCH_5 + println("=== TEST 5") + print("Legacy : ") + @btime $b5_legacy(($λ, 2 * $λ, 3 * $λ)) # Legacy : 24.119 ms (127034 allocations: 33.24 MiB) + print("New API : ") + @btime assemble_linear($l5, $V3) # New API : 27.059 ms (131 allocations: 15.96 MiB) + end + ## profiling + if PROFILING_5 + Profile.init(; n = 5 * 10^7) + Profile.clear() + Profile.clear_malloc_data() + @profile begin + for i in 1:100 + assemble_linear(l5, V3) + end + end + end + + if TEST_6 + _b5_legacy = vcat(map(get_values, b5_legacy((λ, 2 * λ, 3 * λ)))...) + _b6_legacy = f6(_b5_legacy) + if b6 ≉ _b6_legacy + display(b6[1:10]) + display(_b6_legacy[1:10]) + display(findall(x -> abs(x) > 1.0e-10, b6 .- _b6_legacy)[1:40]) + @show length(b5), length(_b6_legacy) + error("b6 ≉ get_values(_b6_legacy) ") + end + _b6 = assemble_linear(l5, V3) + @assert f6(_b6) ≈ _b6_legacy + println("TEST_6: OK") + end + ## bench + if BENCH_6 + println("=== TEST 6") + print("New API (combine b) : ") + @btime $f6(assemble_linear($l5, $V3)) # New API (combine b) : 28.634 ms (143 allocations: 23.35 MiB) + print("New API (MuliIntegration{3}) : ") + @btime assemble_linear($l6, $V3) # New API (MuliIntegration{3}) : 73.027 ms (148 allocations: 15.97 MiB) + end + ## profiling + if PROFILING_6 + Profile.init(; n = 5 * 10^7) + Profile.clear() + Profile.clear_malloc_data() + @profile begin + for i in 1:100 + assemble_linear(l6, V3) + end + end + end + + @show length(get_values(_b2_legacy)) + @show get_ndofs(V) + println("ALL TESTS OK") + end + # + # du = zeros.((ϕ,)) + # du += du_Γ + # b3 = get_values(du[1]) + # @show b3 + + # for i = 1:nite + # assemble_linear!(b1, l1, V) + # assemble_linear!(b2, l2, V) + + # rhs = Δt .* invM * (b1 - b2) + # u.dofValues .+= rhs + # end + +end + +run_linear_transport() + +end #hide diff --git a/src/tutorial/phase_field_supercooled.jl b/src/tutorial/phase_field_supercooled.jl new file mode 100644 index 0000000..5dba307 --- /dev/null +++ b/src/tutorial/phase_field_supercooled.jl @@ -0,0 +1,158 @@ +module PhaseFieldSupercooled #hide +println("Running phase field supercooled equation example...") #hide + +# # Phase field model - solidification of a liquid in supercooled state +# In this tutorial, a coupled system of two unsteady equations is solved using finite elements +# and an imex time scheme. This tutorial doesn't introduce `MultiFESpace`, check the "euler" example +# for this. Warning : this file is currently quite long to run (a few minutes). +# +# # Theory +# This case is taken from: Kobayashi, R. (1993). Modeling and numerical simulations of dendritic crystal growth. Physica D: Nonlinear Phenomena, 63(3-4), 410-423. +# In particular, the variables of the problem are denoted in the same way ($p$ for the phase indicator and $T$ for temperature). +# Consider a rectangular domain $$\Omega = [0, L_x] \times [0, L_y]$$ on which we wish to solve the following equations: +# ```math +# \tau \partial_t p = \epsilon^2 \Delta p + p (1-p)(p - \frac{1}{2} + m(T)) +# ``` +# ```math +# \partial_t T = \Delta T + K \partial_t p +# ``` +# where $m(T) = \frac{\alpha}{\pi} atan \left[ \gamma (T_e - T) \right]$. +# This set of equations represents the solidification of a liquid in a supercooled state. Here $T$ is a dimensionless temperature and $p$ is the solid volume fraction. +# Lagrange finite elements are used to discretize both equations. Time marching is performed with a forward Euler scheme for the first equation and a backward Euler scheme for the second one. +# +# To initiate the solidification process, a Dirichlet boundary condition ($p=1$,$T=1$) is applied at $x=0$ ("West" boundary). +# +# # Code +# Load the necessary packages +using Bcube +using LinearAlgebra +using WriteVTK +using Random + +Random.seed!(1234) # to obtain reproductible results + +# Define some physical and numerical constants, as well as the `g` function +# appearing in the problem definition. +const dir = string(@__DIR__, "/../") # Bcube dir +const ε = 0.01 +const τ = 0.0003 +const α = 0.9 +const γ = 10.0 +const K = 1.6 +const Te = 1.0 +const β = 0.0 # noise amplitude, original value : 0.01 +const Δt = 0.0001 # time step +const totalTime = 1.0 # original value : 1 +const nout = 50 # Number of iterations to skip before writing file +const degree = 1 # function space degree +const lx = 3.0 +const ly = 1.0 +const nx = 100 +const ny = 20 + +g(T) = (α / π) * atan(γ * (Te - T)) + +# Read the mesh using `gmsh` +const mesh_path = dir * "input/mesh/domainPhaseField_tri.msh" +const mesh = read_msh(mesh_path) + +# Noise function : random between [-1/2,1/2] +const χ = MeshCellData(rand(ncells(mesh)) .- 0.5) + +# Build the function space and the FE Spaces. The two unknowns will share the +# same FE spaces for this tutorial. Note the way we specify the Dirichlet condition +# in the definition of `U`. +fs = FunctionSpace(:Lagrange, degree) +U = TrialFESpace(fs, mesh, Dict("West" => (x, t) -> 1.0)) +V = TestFESpace(U) + +# Build FE functions +ϕ = FEFunction(U) +T = FEFunction(U) + +# Define measures for cell integration +dΩ = Measure(CellDomain(mesh), 2 * degree + 1) + +# Define bilinear and linear forms +a(u, v) = ∫(∇(u) ⋅ ∇(v))dΩ +m(u, v) = ∫(u ⋅ v)dΩ +l(v) = ∫(v * ϕ * (1.0 - ϕ) * (ϕ - 0.5 + g(T) + β * χ))dΩ + +# Assemble the two constant matrices +A = assemble_bilinear(a, U, V) +M = assemble_bilinear(m, U, V) + +# Create iterative matrices +C_ϕ = M + Δt / τ * ε^2 * A +C_T = M + Δt * A + +# Apply Dirichlet conditions. +# For this example, we don't use a lifting method to impose the Dirichlet, but `d` +# is used to initialize the solution. +d = assemble_dirichlet_vector(U, V, mesh) +apply_dirichlet_to_matrix!((C_ϕ, C_T), U, V, mesh) + +# Init solution and write it to a VTK file +set_dof_values!(ϕ, d) +set_dof_values!(T, d) + +dict_vars = Dict( + "Temperature" => (var_on_vertices(T, mesh), VTKPointData()), + "Phi" => (var_on_vertices(ϕ, mesh), VTKPointData()), +) +write_vtk(dir * "myout/result_phaseField_imex_1space", 0, 0.0, mesh, dict_vars) + +# Factorize and allocate some vectors to increase performance +C_ϕ = factorize(C_ϕ) +C_T = factorize(C_T) +L = zero(d) +rhs = zero(d) +ϕ_new = zero(d) + +# Time loop (imex time integration) +t = 0.0 +itime = 0 +while t <= totalTime + global t, itime + t += Δt + itime += 1 + @show t, totalTime + + ## Integrate equation on ϕ + L .= 0.0 # reset L + assemble_linear!(L, l, V) + rhs .= M * get_dof_values(ϕ) .+ Δt / τ .* L + apply_dirichlet_to_vector!(rhs, U, V, mesh) + ϕ_new .= C_ϕ \ rhs + + ## Integrate equation on T + rhs .= M * (get_dof_values(T) .+ K .* (ϕ_new .- get_dof_values(ϕ))) + apply_dirichlet_to_vector!(rhs, U, V, mesh) + + ## Update solution + set_dof_values!(ϕ, ϕ_new) + set_dof_values!(T, C_T \ rhs) + + ## write solution in vtk format + if itime % nout == 0 + dict_vars = Dict( + "Temperature" => (var_on_vertices(T, mesh), VTKPointData()), + "Phi" => (var_on_vertices(ϕ, mesh), VTKPointData()), + ) + write_vtk( + dir * "myout/result_phaseField_imex_1space", + itime, + t, + mesh, + dict_vars; + append = true, + ) + end +end + +# And here is an animation of the result: +# ```@raw html +# drawing +# ``` + +end #hide