diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..3494a9f --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,3 @@ +style = "sciml" +format_markdown = true +format_docstrings = true diff --git a/README.md b/README.md index b8585f3..f17a304 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://SciML.github.io/FiniteVolumeMethod.jl/stable) [![Coverage](https://codecov.io/gh/SciML/FiniteVolumeMethod.jl/branch/main/graph/badge.svg?token=XPM5KN89R6)](https://codecov.io/gh/SciML/FiniteVolumeMethod.jl) -This is a Julia package for solving partial differential equations (PDEs) of the form +This is a Julia package for solving partial differential equations (PDEs) of the form $$ \dfrac{\partial u(\boldsymbol x, t)}{\partial t} + \boldsymbol{\nabla} \boldsymbol{\cdot} \boldsymbol{q}(\boldsymbol x, t, u) = S(\boldsymbol x, t, u), \quad (x, y)^{\mkern-1.5mu\mathsf{T}} \in \Omega \subset \mathbb R^2,t>0, @@ -13,23 +13,23 @@ $$ in two dimensions using the finite volume method, with support also provided for steady-state problems and for systems of PDEs of the above form. In addition to this generic form above, we also provide support for specific problems that can be solved in a more efficient manner, namely: -1. `DiffusionEquation`s: $\partial_tu = \boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla u]$. -2. `MeanExitTimeProblem`s: $\boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla T(\boldsymbol x)] = -1$. -3. `LinearReactionDiffusionEquation`s: $\partial_tu = \boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla u] + f(\boldsymbol x)u$. -4. `PoissonsEquation`: $\boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla u] = f(\boldsymbol x)$. -5. `LaplacesEquation`: $\boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla u] = 0$. + 1. `DiffusionEquation`s: $\partial_tu = \boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla u]$. + 2. `MeanExitTimeProblem`s: $\boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla T(\boldsymbol x)] = -1$. + 3. `LinearReactionDiffusionEquation`s: $\partial_tu = \boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla u] + f(\boldsymbol x)u$. + 4. `PoissonsEquation`: $\boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla u] = f(\boldsymbol x)$. + 5. `LaplacesEquation`: $\boldsymbol\nabla\boldsymbol\cdot[D(\boldsymbol x)\boldsymbol\nabla u] = 0$. See the documentation for more information. If this package doesn't suit what you need, you may like to review some of the other PDE packages shown [here](https://github.com/JuliaPDE/SurveyofPDEPackages). - As a very quick demonstration, here is how we could solve a diffusion equation with Dirichlet boundary conditions on a square domain using the standard `FVMProblem` formulation; please see the docs for more information. +As a very quick demonstration, here is how we could solve a diffusion equation with Dirichlet boundary conditions on a square domain using the standard `FVMProblem` formulation; please see the docs for more information. ```julia using FiniteVolumeMethod, DelaunayTriangulation, CairoMakie, OrdinaryDiffEq a, b, c, d = 0.0, 2.0, 0.0, 2.0 nx, ny = 50, 50 -tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary=true) +tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary = true) mesh = FVMGeometry(tri) bc = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, bc, Dirichlet) @@ -37,10 +37,10 @@ f = (x, y) -> y ≤ 1.0 ? 50.0 : 0.0 initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] D = (x, y, t, u, p) -> 1 / 9 final_time = 0.5 -prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) -sol = solve(prob, Tsit5(), saveat=0.001) +prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) +sol = solve(prob, Tsit5(), saveat = 0.001) u = Observable(sol.u[1]) -fig, ax, sc = tricontourf(tri, u, levels=0:5:50, colormap=:matter) +fig, ax, sc = tricontourf(tri, u, levels = 0:5:50, colormap = :matter) tightlimits!(ax) record(fig, "anim.gif", eachindex(sol)) do i u[] = sol.u[i] @@ -49,10 +49,10 @@ end ![Animation of a solution](https://github.com/SciML/FiniteVolumeMethod.jl/blob/main/anim.gif) -We could have equivalently used the `DiffusionEquation` template, so that `prob` could have also been defined by +We could have equivalently used the `DiffusionEquation` template, so that `prob` could have also been defined by ```julia -prob = DiffusionEquation(mesh, BCs; diffusion_function=D, initial_condition, final_time) +prob = DiffusionEquation(mesh, BCs; diffusion_function = D, initial_condition, final_time) ``` and be solved much more efficiently. See the documentation for more information. diff --git a/docs/liveserver.jl b/docs/liveserver.jl index 57318e9..02137d1 100644 --- a/docs/liveserver.jl +++ b/docs/liveserver.jl @@ -5,12 +5,12 @@ Pkg.instantiate() import LiveServer withenv("LIVESERVER_ACTIVE" => "true") do LiveServer.servedocs(; - launch_browser=true, - foldername=joinpath(repo_root, "docs"), - include_dirs=[joinpath(repo_root, "src")], - skip_dirs=[joinpath(repo_root, "docs/src/tutorials"), + launch_browser = true, + foldername = joinpath(repo_root, "docs"), + include_dirs = [joinpath(repo_root, "src")], + skip_dirs = [joinpath(repo_root, "docs/src/tutorials"), joinpath(repo_root, "docs/src/wyos"), - joinpath(repo_root, "docs/src/figures"), - ], + joinpath(repo_root, "docs/src/figures") + ] ) -end \ No newline at end of file +end diff --git a/docs/make.jl b/docs/make.jl index be5648b..76295cf 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -22,7 +22,8 @@ if RUN_EXAMPLES function update_edit_url(content, file, folder) content = replace(content, "" => "https://github.com/SciML/FiniteVolumeMethod.jl/tree/main") content = replace(content, "temp/" => "") # as of Literate 2.14.1 - content = replace(content, r"EditURL\s*=\s*\"[^\"]*\"" => "EditURL = \"https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_$(folder)/$file\"") + content = replace(content, + r"EditURL\s*=\s*\"[^\"]*\"" => "EditURL = \"https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_$(folder)/$file\"") return content end # We can add the code to the end of each file in its uncommented form programatically. @@ -30,13 +31,14 @@ if RUN_EXAMPLES file_name, file_ext = splitext(file) file_path = joinpath(dir, file) new_file_path = joinpath(session_tmp, file_name * "_just_the_code" * file_ext) - cp(file_path, new_file_path, force=true) + cp(file_path, new_file_path, force = true) folder = splitpath(dir)[end] # literate_tutorials or literate_applications open(new_file_path, "a") do io write(io, "\n") write(io, "# ## Just the code\n") write(io, "# An uncommented version of this example is given below.\n") - write(io, "# You can view the source code for this file [here](/docs/src/$folder/@__NAME__.jl).\n") + write(io, + "# You can view the source code for this file [here](/docs/src/$folder/@__NAME__.jl).\n") write(io, "\n") write(io, "# ```julia\n") write(io, "# @__CODE__\n") @@ -59,7 +61,7 @@ if RUN_EXAMPLES "tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.jl", "tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.jl", "tutorials/laplaces_equation_with_internal_dirichlet_conditions.jl", - "tutorials/diffusion_equation_on_an_annulus.jl", + "tutorials/diffusion_equation_on_an_annulus.jl" ] wyos_files = [ "wyos/diffusion_equations.jl", @@ -71,7 +73,6 @@ if RUN_EXAMPLES example_files = vcat(tutorial_files, wyos_files) session_tmp = mktempdir() - map(1:length(example_files)) do n example = example_files[n] folder, file = splitpath(example) @@ -80,7 +81,8 @@ if RUN_EXAMPLES file_path = joinpath(dir, file) # See also https://github.com/Ferrite-FEM/Ferrite.jl/blob/d474caf357c696cdb80d7c5e1edcbc7b4c91af6b/docs/generate.jl for some of this new_file_path = add_just_the_code_section(dir, file) - script = Literate.script(file_path, session_tmp, name=splitext(file)[1] * "_just_the_code_cleaned") + script = Literate.script(file_path, session_tmp, name = splitext(file)[1] * + "_just_the_code_cleaned") code = strip(read(script, String)) @info "[$(ct())] Processing $file: Converting markdown script" line_ending_symbol = occursin(code, "\r\n") ? "\r\n" : "\n" @@ -93,12 +95,12 @@ if RUN_EXAMPLES Literate.markdown( new_file_path, outputdir; - documenter=true, - postprocess=editurl_update ∘ post_strip, - credit=true, - execute=!IS_LIVESERVER, - flavor=Literate.DocumenterFlavor(), - name=splitext(file)[1] + documenter = true, + postprocess = editurl_update ∘ post_strip, + credit = true, + execute = !IS_LIVESERVER, + flavor = Literate.DocumenterFlavor(), + name = splitext(file)[1] ) end end @@ -128,7 +130,7 @@ _PAGES = [ "Diffusion Equation on an Annulus" => "tutorials/diffusion_equation_on_an_annulus.md", "Mean Exit Time" => "tutorials/mean_exit_time.md", "Solving Mazes with Laplace's Equation" => "tutorials/solving_mazes_with_laplaces_equation.md", - "Keller-Segel Model of Chemotaxis" => "tutorials/keller_segel_chemotaxis.md", + "Keller-Segel Model of Chemotaxis" => "tutorials/keller_segel_chemotaxis.md" ], "Solvers for Specific Problems, and Writing Your Own" => [ "Section Overview" => "wyos/overview.md", @@ -136,7 +138,7 @@ _PAGES = [ "Mean Exit Time Problems" => "wyos/mean_exit_time.md", "Linear Reaction-Diffusion Equations" => "wyos/linear_reaction_diffusion_equations.md", "Poisson's Equation" => "wyos/poissons_equation.md", - "Laplace's Equation" => "wyos/laplaces_equation.md", + "Laplace's Equation" => "wyos/laplaces_equation.md" ], "Mathematical and Implementation Details" => "math.md" ] @@ -172,32 +174,32 @@ end # Make and deploy DocMeta.setdocmeta!(FiniteVolumeMethod, :DocTestSetup, :(using FiniteVolumeMethod, Test); - recursive=true) + recursive = true) IS_LIVESERVER = get(ENV, "LIVESERVER_ACTIVE", "false") == "true" IS_CI = get(ENV, "CI", "false") == "true" makedocs(; - modules=[FiniteVolumeMethod], - authors="Daniel VandenHeuvel ", - sitename="FiniteVolumeMethod.jl", - format=Documenter.HTML(; - canonical="https://SciML.github.io/FiniteVolumeMethod.jl", - edit_link="main", - collapselevel=1, - assets=String[], - mathengine=MathJax3(Dict( + modules = [FiniteVolumeMethod], + authors = "Daniel VandenHeuvel ", + sitename = "FiniteVolumeMethod.jl", + format = Documenter.HTML(; + canonical = "https://SciML.github.io/FiniteVolumeMethod.jl", + edit_link = "main", + collapselevel = 1, + assets = String[], + mathengine = MathJax3(Dict( :loader => Dict("load" => ["[tex]/physics"]), :tex => Dict( "inlineMath" => [["\$", "\$"], ["\\(", "\\)"]], "tags" => "ams", - "packages" => ["base", "ams", "autoload", "physics"], - ), + "packages" => ["base", "ams", "autoload", "physics"] + ) ))), - draft=IS_LIVESERVER, - pages=_PAGES, - warnonly=true + draft = IS_LIVESERVER, + pages = _PAGES, + warnonly = true ) deploydocs(; - repo="github.com/SciML/FiniteVolumeMethod.jl", - devbranch="main", - push_preview=true) + repo = "github.com/SciML/FiniteVolumeMethod.jl", + devbranch = "main", + push_preview = true) diff --git a/docs/src/index.md b/docs/src/index.md index 5137791..582abec 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -2,7 +2,7 @@ CurrentModule = FiniteVolumeMethod ``` -# Introduction +# Introduction This is the documentation for FiniteVolumeMethod.jl. [Click here to go back to the GitHub repository](https://github.com/SciML/FiniteVolumeMethod.jl). @@ -20,10 +20,10 @@ using the finite volume method, with additional support for steady-state problem The [tutorials](tutorials/overview.md) in the sidebar demonstrate the many possibilities of this package. In addition to these two generic forms, we also provide support for specific problems that can be solved in a more efficient manner, namely: -1. `DiffusionEquation`s: $\partial_tu = \div[D(\vb x)\grad u]$. -2. `MeanExitTimeProblem`s: $\div[D(\vb x)\grad T(\vb x)] = -1$. -3. `LinearReactionDiffusionEquation`s: $\partial_tu = \div[D(\vb x)\grad u] + f(\vb x)u$. -4. `PoissonsEquation`: $\div[D(\vb x)\grad u] = f(\vb x)$. -5. `LaplacesEquation`: $\div[D(\vb x)\grad u] = 0$. + 1. `DiffusionEquation`s: $\partial_tu = \div[D(\vb x)\grad u]$. + 2. `MeanExitTimeProblem`s: $\div[D(\vb x)\grad T(\vb x)] = -1$. + 3. `LinearReactionDiffusionEquation`s: $\partial_tu = \div[D(\vb x)\grad u] + f(\vb x)u$. + 4. `PoissonsEquation`: $\div[D(\vb x)\grad u] = f(\vb x)$. + 5. `LaplacesEquation`: $\div[D(\vb x)\grad u] = 0$. -See the [Solvers for Specific Problems, and Writing Your Own](wyos/overview.md) section for more information on these templates. \ No newline at end of file +See the [Solvers for Specific Problems, and Writing Your Own](wyos/overview.md) section for more information on these templates. diff --git a/docs/src/interface.md b/docs/src/interface.md index e76788b..dfa8e42 100644 --- a/docs/src/interface.md +++ b/docs/src/interface.md @@ -2,34 +2,34 @@ CurrentModule = FiniteVolumeMethod ``` -# Interface +# Interface -```@contents +```@contents Pages = ["interface.md"] ``` In this section, we describe the basic interface for defining and solving PDEs using this package. This interface will also be made clearer in the tutorials. The basic summary of the discussion below is as follows: -1. Use `FVMGeometry` to define the problem's mesh. -2. Provide boundary conditions using `BoundaryConditions`. -3. (Optional) Provide internal conditions using `InternalConditions`. -4. Convert the problem into an `FVMProblem`. -5. If you want to make the problem steady, use `SteadyFVMProblem` on the `FVMProblem`. -6. If you want a system of equations, construct an `FVMSystem` from multiple `FVMProblem`s; if you want this problem to be steady, skip step 5 and only now apply `SteadyFVMProblem`. -7. Solve the problem using `solve`. -8. For a discussion of custom constraints, see the tutorials. -9. For interpolation, we provide `pl_interpolate` (but you might prefer [NaturalNeighbours.jl](https://github.com/DanielVandH/NaturalNeighbours.jl) - see [this tutorial for an example](tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.md)). + 1. Use `FVMGeometry` to define the problem's mesh. + 2. Provide boundary conditions using `BoundaryConditions`. + 3. (Optional) Provide internal conditions using `InternalConditions`. + 4. Convert the problem into an `FVMProblem`. + 5. If you want to make the problem steady, use `SteadyFVMProblem` on the `FVMProblem`. + 6. If you want a system of equations, construct an `FVMSystem` from multiple `FVMProblem`s; if you want this problem to be steady, skip step 5 and only now apply `SteadyFVMProblem`. + 7. Solve the problem using `solve`. + 8. For a discussion of custom constraints, see the tutorials. + 9. For interpolation, we provide `pl_interpolate` (but you might prefer [NaturalNeighbours.jl](https://github.com/DanielVandH/NaturalNeighbours.jl) - see [this tutorial for an example](tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.md)). -# `FVMGeometry`: Defining the mesh +# `FVMGeometry`: Defining the mesh -The finite volume method (FVM) requires an underlying triangular mesh, as outlined in the [mathematical details section](math.md). This triangular mesh is to be defined from [DelaunayTriangulation.jl](https://github.com/JuliaGeometry/DelaunayTriangulation.jl). The `FVMGeometry` type wraps the resulting `Triangulation` and computes information about the geometry required for solving the PDEs. The docstring for `FVMGeometry` is below; the fields of `FVMGeometry` are public API. +The finite volume method (FVM) requires an underlying triangular mesh, as outlined in the [mathematical details section](math.md). This triangular mesh is to be defined from [DelaunayTriangulation.jl](https://github.com/JuliaGeometry/DelaunayTriangulation.jl). The `FVMGeometry` type wraps the resulting `Triangulation` and computes information about the geometry required for solving the PDEs. The docstring for `FVMGeometry` is below; the fields of `FVMGeometry` are public API. ```@docs FVMGeometry ``` -The `FVMGeometry` struct uses `TriangleProperties` for storing properties of a control volume that intersects a given triangle, defined below. This struct is -public API, although it is unlikely you would ever need it. +The `FVMGeometry` struct uses `TriangleProperties` for storing properties of a control volume that intersects a given triangle, defined below. This struct is +public API, although it is unlikely you would ever need it. ```@docs TriangleProperties @@ -152,7 +152,7 @@ These `solve` functions rely on `fvm_eqs!` for evaluating the equations. You sho fvm_eqs! ``` -# Custom constraints +# Custom constraints You can also provide custom constraints. Rather than outlining this precisely here, it is best explained in the tutorials, namely [this tutorial](tutorials/solving_mazes_with_laplaces_equation.md). We note that one useful function for this is `compute_flux`, which allows you to compute the flux across a given edge. The docstring for `compute_flux` is below, and this function is public API. @@ -160,7 +160,6 @@ You can also provide custom constraints. Rather than outlining this precisely he compute_flux ``` - # Piecewise linear interpolation You can evaluate the piecewise linear interpolation corresponding to a solution using `pl_interpolate`, defined below; this function is public API. @@ -169,4 +168,4 @@ You can evaluate the piecewise linear interpolation corresponding to a solution pl_interpolate ``` -Better interpolants are available from [NaturalNeighbours.jl](https://github.com/DanielVandH/NaturalNeighbours.jl) - see the [this tutorial](tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.md) for some examples. \ No newline at end of file +Better interpolants are available from [NaturalNeighbours.jl](https://github.com/DanielVandH/NaturalNeighbours.jl) - see the [this tutorial](tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.md) for some examples. diff --git a/docs/src/literate_tutorials/diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.jl b/docs/src/literate_tutorials/diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.jl index 58f6c69..2b010fb 100644 --- a/docs/src/literate_tutorials/diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.jl +++ b/docs/src/literate_tutorials/diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.jl @@ -43,7 +43,7 @@ upper_edge = [3, 1] boundary_nodes = [bottom_edge, [arc], upper_edge] tri = triangulate(points; boundary_nodes) A = get_area(tri) -refine!(tri; max_area=1e-4A) +refine!(tri; max_area = 1e-4A) mesh = FVMGeometry(tri) # This is the mesh we've constructed. @@ -68,7 +68,7 @@ f = (x, y) -> 1 - sqrt(x^2 + y^2) D = (x, y, t, u, p) -> one(u) initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 0.1 -prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) +prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) # If you did want to use the flux formulation, you would need to provide flux = (x, y, t, α, β, γ, p) -> (-α, -β) @@ -80,7 +80,7 @@ flux = (x, y, t, α, β, γ, p) -> (-α, -β) # In my experience, I've found that `TRBDF2(linsolve=KLUFactorization())` typically # has the best performance for these problems. using OrdinaryDiffEq, LinearSolve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=0.01, parallel=Val(false)) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.01, parallel = Val(false)) ind = findall(DelaunayTriangulation.each_point_index(tri)) do i #hide !DelaunayTriangulation.has_vertex(tri, i) #hide end #hide @@ -90,25 +90,26 @@ sol |> tc #hide #- using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) local ax - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.01:1, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:0.01:1, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) fig -@test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.png") fig #src +@test_reference joinpath( + @__DIR__, "../figures", "diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.png") fig #src function get_ζ_terms(M, N, α) #src ζ = zeros(M, N + 2) #src - for n in 0:(N+1) #src + for n in 0:(N + 1) #src order = n * π / α #src - @views ζ[:, n+1] .= approx_besselroots(order, M) #src + @views ζ[:, n + 1] .= approx_besselroots(order, M) #src end #src return ζ #src end #src @@ -118,8 +119,10 @@ function get_sum_coefficients(M, N, α, ζ) #src for n in 0:N #src order = n * π / α #src for m in 1:M #src - integrand = rθ -> _f(rθ[2], rθ[1]) * besselj(order, ζ[m, n+1] * rθ[2]) * cos(order * rθ[1]) * rθ[2] #src - A[m, n+1] = 4.0 / (α * besselj(order + 1, ζ[m, n+1])^2) * hcubature(integrand, [0.0, 0.0], [α, 1.0]; abstol=1e-8)[1] #src + integrand = rθ -> _f(rθ[2], rθ[1]) * besselj(order, ζ[m, n + 1] * rθ[2]) * + cos(order * rθ[1]) * rθ[2] #src + A[m, n + 1] = 4.0 / (α * besselj(order + 1, ζ[m, n + 1])^2) * + hcubature(integrand, [0.0, 0.0], [α, 1.0]; abstol = 1e-8)[1] #src end #src end #src return A #src @@ -136,8 +139,9 @@ function exact_solution(x, y, t, A, ζ, f, α) #src end #src for n in 1:N #src order = n * π / α #src - for m = 1:M #src - s += +A[m, n+1] * exp(-ζ[m, n+1]^2 * t) * besselj(order, ζ[m, n+1] * r) * cos(order * θ) #src + for m in 1:M #src + s += +A[m, n + 1] * exp(-ζ[m, n + 1]^2 * t) * besselj(order, ζ[m, n + 1] * r) * + cos(order * θ) #src end #src end #src return s #src @@ -158,14 +162,15 @@ function compare_solutions(sol, tri, α, f) #src return x, y, u #src end #src x, y, u = compare_solutions(sol, tri, α, f) #src -fig = Figure(fontsize=64) #src +fig = Figure(fontsize = 64) #src for i in eachindex(sol) #src local ax #src - ax = Axis(fig[1, i], width=600, height=600) #src - tricontourf!(ax, tri, sol.u[i], levels=0:0.01:1, colormap=:matter) #src - ax = Axis(fig[2, i], width=600, height=600) #src - tricontourf!(ax, tri, u[:, i], levels=0:0.01:1, colormap=:matter) #src + ax = Axis(fig[1, i], width = 600, height = 600) #src + tricontourf!(ax, tri, sol.u[i], levels = 0:0.01:1, colormap = :matter) #src + ax = Axis(fig[2, i], width = 600, height = 600) #src + tricontourf!(ax, tri, u[:, i], levels = 0:0.01:1, colormap = :matter) #src end #src resize_to_layout!(fig) #src fig #src -@test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_in_a_wedge_with_mixed_boundary_conditions_exact_comparisons.png") fig #src \ No newline at end of file +@test_reference joinpath(@__DIR__, "../figures", + "diffusion_equation_in_a_wedge_with_mixed_boundary_conditions_exact_comparisons.png") fig #src diff --git a/docs/src/literate_tutorials/diffusion_equation_on_a_square_plate.jl b/docs/src/literate_tutorials/diffusion_equation_on_a_square_plate.jl index 578fa77..6df2f46 100644 --- a/docs/src/literate_tutorials/diffusion_equation_on_a_square_plate.jl +++ b/docs/src/literate_tutorials/diffusion_equation_on_a_square_plate.jl @@ -22,7 +22,7 @@ using ReferenceTests #src using StatsBase #src a, b, c, d = 0.0, 2.0, 0.0, 2.0 nx, ny = 50, 50 -tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary=true) +tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary = true) mesh = FVMGeometry(tri) # This mesh is shown below. @@ -41,7 +41,7 @@ D = (x, y, t, u, p) -> 1 / 9 # We can now define the problem: final_time = 0.5 -prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) +prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) # Note that in `prob`, it is not a diffusion function that is used but instead it is a flux function: prob.flux_function @@ -56,18 +56,18 @@ prob.flux_function # and simply call `solve(prob, saveat=0.05)` so that the algorithm is chosen automatically instead # of using `Tsit5()`.) using OrdinaryDiffEq -sol = solve(prob, Tsit5(), saveat=0.05) +sol = solve(prob, Tsit5(), saveat = 0.05) sol |> tc #hide # To visualise the solution, we can use `tricontourf!` from Makie.jl. -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) local ax - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:5:50, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:5:50, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) @@ -80,7 +80,8 @@ function exact_solution(x, y, t) #src for m in 1:2:50 #src mterm = 2 / m * sin(m * π * x / 2) * exp(-π^2 * m^2 * t / 36) #src for n in 1:50 #src - nterm = (1 - cos(n * π / 2)) / n * sin(n * π * y / 2) * exp(-π^2 * n^2 * t / 36) #src + nterm = (1 - cos(n * π / 2)) / n * sin(n * π * y / 2) * + exp(-π^2 * n^2 * t / 36) #src s += mterm * nterm #src end #src end #src @@ -103,14 +104,14 @@ function compare_solutions(sol, tri) #src return x, y, u #src end #src x, y, u = compare_solutions(sol, tri) #src -fig = Figure(fontsize=64) #src +fig = Figure(fontsize = 64) #src for i in eachindex(sol) #src local ax #src - ax = Axis(fig[1, i], width=600, height=600) #src - tricontourf!(ax, tri, sol.u[i], levels=0:5:50, colormap=:matter) #src - ax = Axis(fig[2, i], width=600, height=600) #src - tricontourf!(ax, tri, u[:, i], levels=0:5:50, colormap=:matter) #src + ax = Axis(fig[1, i], width = 600, height = 600) #src + tricontourf!(ax, tri, sol.u[i], levels = 0:5:50, colormap = :matter) #src + ax = Axis(fig[2, i], width = 600, height = 600) #src + tricontourf!(ax, tri, u[:, i], levels = 0:5:50, colormap = :matter) #src end #src resize_to_layout!(fig) #src fig #src -@test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_on_a_square_plate_exact_comparisons.png") fig #src \ No newline at end of file +@test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_on_a_square_plate_exact_comparisons.png") fig #src diff --git a/docs/src/literate_tutorials/diffusion_equation_on_an_annulus.jl b/docs/src/literate_tutorials/diffusion_equation_on_an_annulus.jl index cd0ef92..73a19a8 100644 --- a/docs/src/literate_tutorials/diffusion_equation_on_an_annulus.jl +++ b/docs/src/literate_tutorials/diffusion_equation_on_an_annulus.jl @@ -23,16 +23,15 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # For the mesh, we use two `CircularArc`s to define the annulus. using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie R₁, R₂ = 0.2, 1.0 -inner = CircularArc((R₁, 0.0), (R₁, 0.0), (0.0, 0.0), positive=false) +inner = CircularArc((R₁, 0.0), (R₁, 0.0), (0.0, 0.0), positive = false) outer = CircularArc((R₂, 0.0), (R₂, 0.0), (0.0, 0.0)) boundary_nodes = [[[outer]], [[inner]]] -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) A = get_area(tri) -refine!(tri; max_area=1e-4A) +refine!(tri; max_area = 1e-4A) triplot(tri) - #- mesh = FVMGeometry(tri) @@ -47,8 +46,8 @@ ax = Axis(fig[1, 1]) outer = [get_point(tri, i) for i in get_neighbours(tri, -1)] inner = [get_point(tri, i) for i in get_neighbours(tri, -2)] triplot!(ax, tri) -scatter!(ax, outer, color=:red) -scatter!(ax, inner, color=:blue) +scatter!(ax, outer, color = :red) +scatter!(ax, inner, color = :blue) fig # So, the boundary conditions are: @@ -58,11 +57,15 @@ types = (Neumann, Dirichlet) BCs = BoundaryConditions(mesh, (outer_bc, inner_bc), types) # Finally, let's define the problem and solve it. -initial_condition_f = (x, y) -> begin - 10 * exp(-25 * ((x + 0.5) * (x + 0.5) + (y + 0.5) * (y + 0.5))) - 5 * exp(-50 * ((x + 0.3) * (x + 0.3) + (y + 0.5) * (y + 0.5))) - 10 * exp(-45 * ((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5))) +initial_condition_f = (x, + y) -> begin + 10 * exp(-25 * ((x + 0.5) * (x + 0.5) + (y + 0.5) * (y + 0.5))) - + 5 * exp(-50 * ((x + 0.3) * (x + 0.3) + (y + 0.5) * (y + 0.5))) - + 10 * exp(-45 * ((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5))) end diffusion_function = (x, y, t, u, p) -> one(u) -initial_condition = [initial_condition_f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] +initial_condition = [initial_condition_f(x, y) + for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 2.0 prob = FVMProblem(mesh, BCs; diffusion_function, @@ -71,18 +74,18 @@ prob = FVMProblem(mesh, BCs; #- using OrdinaryDiffEq, LinearSolve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=0.2) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.2) sol |> tc #hide #- -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) local ax - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=-10:2:40, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = -10:2:40, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) @@ -113,7 +116,7 @@ u = sol.u[6] last_triangle = Ref((1, 1, 1)) for (j, _y) in enumerate(y) for (i, _x) in enumerate(x) - T = jump_and_march(tri, (_x, _y), try_points=last_triangle[]) + T = jump_and_march(tri, (_x, _y), try_points = last_triangle[]) last_triangle[] = triangle_vertices(T) # used to accelerate jump_and_march, since the points we're looking for are close to each other if DelaunayTriangulation.is_ghost_triangle(T) # don't extrapolate interp_vals[i, j] = NaN @@ -122,9 +125,9 @@ for (j, _y) in enumerate(y) end end end -fig, ax, sc = contourf(x, y, interp_vals, levels=-10:2:40, colormap=:matter) +fig, ax, sc = contourf(x, y, interp_vals, levels = -10:2:40, colormap = :matter) fig -tricontourf!(Axis(fig[1, 2]), tri, u, levels=-10:2:40, colormap=:matter) #src +tricontourf!(Axis(fig[1, 2]), tri, u, levels = -10:2:40, colormap = :matter) #src @test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_on_an_annulus_interpolated.png") fig #src # Let's now consider applying NaturalNeighbours.jl. We apply it naively first to @@ -132,25 +135,31 @@ tricontourf!(Axis(fig[1, 2]), tri, u, levels=-10:2:40, colormap=:matter) #src using NaturalNeighbours _x = vec([x for x in x, y in y]) # NaturalNeighbours.jl needs vector data _y = vec([y for x in x, y in y]) -itp = interpolate(tri, u, derivatives=true) +itp = interpolate(tri, u, derivatives = true) itp |> tc #hide #- -itp_vals = itp(_x, _y; method=Farin()) +itp_vals = itp(_x, _y; method = Farin()) itp_vals |> tc #hide #- -fig, ax, sc = contourf(x, y, reshape(itp_vals, length(x), length(y)), colormap=:matter, levels=-10:2:40) +fig, ax, +sc = contourf( + x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40) fig -@test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_on_an_annulus_interpolated_with_naturalneighbours_bad.png") fig #src +@test_reference joinpath(@__DIR__, "../figures", + "diffusion_equation_on_an_annulus_interpolated_with_naturalneighbours_bad.png") fig #src # The issue here is that the interpolant is trying to extrapolate inside the hole and # outside of the annulus. To avoid this, you need to pass `project=false`. -itp_vals = itp(_x, _y; method=Farin(), project=false) +itp_vals = itp(_x, _y; method = Farin(), project = false) itp_vals |> tc #hide #- -fig, ax, sc = contourf(x, y, reshape(itp_vals, length(x), length(y)), colormap=:matter, levels=-10:2:40) +fig, ax, +sc = contourf( + x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40) fig -tricontourf!(Axis(fig[1, 2]), tri, u, levels=-10:2:40, colormap=:matter) #src -@test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_on_an_annulus_interpolated_with_naturalneighbours.png") fig #src \ No newline at end of file +tricontourf!(Axis(fig[1, 2]), tri, u, levels = -10:2:40, colormap = :matter) #src +@test_reference joinpath( + @__DIR__, "../figures", "diffusion_equation_on_an_annulus_interpolated_with_naturalneighbours.png") fig #src diff --git a/docs/src/literate_tutorials/equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.jl b/docs/src/literate_tutorials/equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.jl index f305896..5ae9c8c 100644 --- a/docs/src/literate_tutorials/equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.jl +++ b/docs/src/literate_tutorials/equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.jl @@ -22,22 +22,27 @@ G = (0.05, 0.03) #hide C = (0.06, 0.03) #hide D = (0.06, 0.0) #hide E = (0.0, 0.0) #hide -fig = Figure(fontsize=33) #hide -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") #hide -lines!(ax, [A, E, D], color=:red, linewidth=5) #hide -lines!(ax, [B, F, G, C], color=:blue, linewidth=5) #hide -lines!(ax, [C, D], color=:black, linewidth=5) #hide -lines!(ax, [A, B], color=:magenta, linewidth=5) #hide -text!(ax, [(0.03, 0.001)], text=L"\Gamma_1", fontsize=44) #hide -text!(ax, [(0.055, 0.01)], text=L"\Gamma_2", fontsize=44) #hide -text!(ax, [(0.04, 0.04)], text=L"\Gamma_3", fontsize=44) #hide -text!(ax, [(0.015, 0.053)], text=L"\Gamma_4", fontsize=44) #hide -text!(ax, [(0.001, 0.03)], text=L"\Gamma_1", fontsize=44) #hide +fig = Figure(fontsize = 33) #hide +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") #hide +lines!(ax, [A, E, D], color = :red, linewidth = 5) #hide +lines!(ax, [B, F, G, C], color = :blue, linewidth = 5) #hide +lines!(ax, [C, D], color = :black, linewidth = 5) #hide +lines!(ax, [A, B], color = :magenta, linewidth = 5) #hide +text!(ax, [(0.03, 0.001)], text = L"\Gamma_1", fontsize = 44) #hide +text!(ax, [(0.055, 0.01)], text = L"\Gamma_2", fontsize = 44) #hide +text!(ax, [(0.04, 0.04)], text = L"\Gamma_3", fontsize = 44) #hide +text!(ax, [(0.015, 0.053)], text = L"\Gamma_4", fontsize = 44) #hide +text!(ax, [(0.001, 0.03)], text = L"\Gamma_1", fontsize = 44) #hide fig #hide # Let us start by defining the mesh. using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie -A, B, C, D, E, F, G = (0.0, 0.0), +A, B, +C, +D, +E, +F, +G = (0.0, 0.0), (0.06, 0.0), (0.06, 0.03), (0.05, 0.03), @@ -51,7 +56,7 @@ bn4 = [F, G] bn = [bn1, bn2, bn3, bn4] boundary_nodes, points = convert_boundary_points_to_indices(bn) tri = triangulate(points; boundary_nodes) -refine!(tri; max_area=1e-4get_area(tri)) +refine!(tri; max_area = 1e-4get_area(tri)) triplot(tri) #- @@ -65,9 +70,9 @@ h = 20.0 T∞ = 20.0 bc1 = (x, y, t, T, p) -> zero(T) # ∇T⋅n=0 bc2 = (x, y, t, T, p) -> oftype(T, 40.0) # T=40 -bc3 = (x, y, t, T, p) -> -p.h * (p.T∞- T) / p.k # k∇T⋅n=h(T∞-T). The minus is since q = -∇T +bc3 = (x, y, t, T, p) -> -p.h * (p.T∞ - T) / p.k # k∇T⋅n=h(T∞-T). The minus is since q = -∇T bc4 = (x, y, t, T, p) -> oftype(T, 70.0) # T=70 -parameters = (nothing, nothing, (h=h, T∞=T∞, k=k), nothing) +parameters = (nothing, nothing, (h = h, T∞ = T∞, k = k), nothing) BCs = BoundaryConditions(mesh, (bc1, bc2, bc3, bc4), (Neumann, Dirichlet, Neumann, Dirichlet); parameters) @@ -94,7 +99,9 @@ sol = solve(steady_prob, DynamicSS(Rosenbrock23())) sol |> tc #hide #- -fig, ax, sc = tricontourf(tri, sol.u, levels=40:70, axis=(xlabel="x", ylabel="y")) +fig, ax, sc = tricontourf(tri, sol.u, levels = 40:70, axis = (xlabel = "x", ylabel = "y")) fig using ReferenceTests #src -@test_reference joinpath(@__DIR__, "../figures", "equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.png") fig #src +@test_reference joinpath(@__DIR__, + "../figures", + "equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.png") fig #src diff --git a/docs/src/literate_tutorials/gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.jl b/docs/src/literate_tutorials/gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.jl index 55755c3..39aa4fc 100644 --- a/docs/src/literate_tutorials/gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.jl +++ b/docs/src/literate_tutorials/gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.jl @@ -23,7 +23,7 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # The domain we use is $[-1, 1]^2$, and we use # zero flux boundary conditions. using FiniteVolumeMethod, DelaunayTriangulation -tri = triangulate_rectangle(-1, 1, -1, 1, 200, 200, single_boundary=true) +tri = triangulate_rectangle(-1, 1, -1, 1, 200, 200, single_boundary = true) mesh = FVMGeometry(tri) #- @@ -45,58 +45,60 @@ v_qp = ε₂ u_Sp = b v_Sp = d u_icf = (x, y) -> 1 - exp(-80 * (x^2 + y^2)) -v_icf = (x, y) -> exp(-80 * (x^ 2 + y^2)) +v_icf = (x, y) -> exp(-80 * (x ^ 2 + y^2)) u_ic = [u_icf(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] v_ic = [v_icf(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] u_prob = FVMProblem(mesh, u_BCs; - flux_function=u_q, flux_parameters=u_qp, - source_function=u_S, source_parameters=u_Sp, - initial_condition=u_ic, final_time=6000.0) + flux_function = u_q, flux_parameters = u_qp, + source_function = u_S, source_parameters = u_Sp, + initial_condition = u_ic, final_time = 6000.0) v_prob = FVMProblem(mesh, v_BCs; - flux_function=v_q, flux_parameters=v_qp, - source_function=v_S, source_parameters=v_Sp, - initial_condition=v_ic, final_time=6000.0) + flux_function = v_q, flux_parameters = v_qp, + source_function = v_S, source_parameters = v_Sp, + initial_condition = v_ic, final_time = 6000.0) prob = FVMSystem(u_prob, v_prob) # Now that we have our system, we can solve. using OrdinaryDiffEq, LinearSolve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=10.0, parallel=Val(false)) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 10.0, parallel = Val(false)) sol |> tc #hide # Here is an animation of the solution, looking only at the $v$ variable. using CairoMakie -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel=L"x", ylabel=L"y") +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = L"x", ylabel = L"y") tightlimits!(ax) i = Observable(1) u = map(i -> reshape(sol.u[i][2, :], 200, 200), i) x = LinRange(-1, 1, 200) y = LinRange(-1, 1, 200) -heatmap!(ax, x, y, u, colorrange=(0.0, 0.4)) +heatmap!(ax, x, y, u, colorrange = (0.0, 0.4)) hidedecorations!(ax) record(fig, joinpath(@__DIR__, "../figures", "gray_scott_patterns.mp4"), eachindex(sol); - framerate=60) do _i + framerate = 60) do _i i[] = _i end # ![Animation of the Gray-Scott model](../figures/gray_scott_patterns.mp4) using ReferenceTests #src -fig = Figure(fontsize=66) #src -times = [0,1000,2000,3000,4000,5000,6000] #src +fig = Figure(fontsize = 66) #src +times = [0, 1000, 2000, 3000, 4000, 5000, 6000] #src t_idx = [findlast(≤(τ), sol.t) for τ in times] #src -plotij = [(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1)] #src +plotij = [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1)] #src for (i, j) in enumerate(t_idx) #src - ax = Axis(fig[plotij[i]...], width=600,height=600, xlabel=L"x", ylabel=L"y", title="t = $(sol.t[j])") #src - heatmap!(ax, x, y, reshape(sol.u[j][2,:],200,200), colorrange=(0.0, 0.4)) #src + ax = Axis(fig[plotij[i]...], width = 600, height = 600, + xlabel = L"x", ylabel = L"y", title = "t = $(sol.t[j])") #src + heatmap!(ax, x, y, reshape(sol.u[j][2, :], 200, 200), colorrange = (0.0, 0.4)) #src tightlimits!(ax) #src end #src -plotij = [(4,1),(4,2),(4,3),(5,1),(5,2),(5,3),(6,1)] #src +plotij = [(4, 1), (4, 2), (4, 3), (5, 1), (5, 2), (5, 3), (6, 1)] #src for (i, j) in enumerate(t_idx) #src - ax = Axis(fig[plotij[i]...], width=600,height=600, xlabel=L"x", ylabel=L"y", title="t = $(sol.t[j])") #src - heatmap!(ax, x, y, reshape(sol.u[j][1,:],200,200), colorrange=(0.0, 1)) #src + ax = Axis(fig[plotij[i]...], width = 600, height = 600, + xlabel = L"x", ylabel = L"y", title = "t = $(sol.t[j])") #src + heatmap!(ax, x, y, reshape(sol.u[j][1, :], 200, 200), colorrange = (0.0, 1)) #src tightlimits!(ax) #src end #src resize_to_layout!(fig) #src fig #src -@test_reference joinpath(@__DIR__, "../figures", "gray_scott_patterns.png") fig #src \ No newline at end of file +@test_reference joinpath(@__DIR__, "../figures", "gray_scott_patterns.png") fig #src diff --git a/docs/src/literate_tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.jl b/docs/src/literate_tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.jl index b9bdb48..735e9b5 100644 --- a/docs/src/literate_tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.jl +++ b/docs/src/literate_tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.jl @@ -14,7 +14,7 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # except that the final `FVMProblem` must be wrapped in a `SteadyFVMProblem`. # Let us start by defining the mesh and the boundary conditions. using DelaunayTriangulation, FiniteVolumeMethod -tri = triangulate_rectangle(-1, 1, -1, 1, 125, 125, single_boundary=true) +tri = triangulate_rectangle(-1, 1, -1, 1, 125, 125, single_boundary = true) mesh = FVMGeometry(tri) # For the boundary condition, @@ -63,7 +63,7 @@ using NonlinearSolve sol = solve(steady_prob, NewtonRaphson()) copyto!(prob.initial_condition, sol.u) # this also changes steady_prob's initial condition using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq -sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) +sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) sol |> tc #hide # For this problem, this correction by `DynamicSS` doesn't seem to actually be needed. @@ -71,9 +71,10 @@ sol |> tc #hide using CairoMakie using ReferenceTests #src -fig, ax, sc = tricontourf(tri, sol.u, levels=-2.5:0.15:-1.0, colormap=:matter) +fig, ax, sc = tricontourf(tri, sol.u, levels = -2.5:0.15:-1.0, colormap = :matter) fig -@test_reference joinpath(@__DIR__, "../figures", "helmholtz_equation_with_inhomogeneous_boundary_conditions.png") fig #src +@test_reference joinpath( + @__DIR__, "../figures", "helmholtz_equation_with_inhomogeneous_boundary_conditions.png") fig #src function exact_solution(x, y) #src return -(cos(x + 1) + cos(1 - x) + cos(y + 1) + cos(1 - y)) / sin(2) #src @@ -90,11 +91,12 @@ function compare_solutions(tri) #src return x, y, u #src end #src x, y, u = compare_solutions(tri) #src -fig = Figure(fontsize=44) #src -ax = Axis(fig[1, 1], width=400, height=400) #src -tricontourf!(ax, tri, sol.u, levels=-2.5:0.15:-1.0, colormap=:matter) #src -ax = Axis(fig[1, 2], width=400, height=400) #src -tricontourf!(ax, tri, u, levels=-2.5:0.15:-1.0, colormap=:matter) #src +fig = Figure(fontsize = 44) #src +ax = Axis(fig[1, 1], width = 400, height = 400) #src +tricontourf!(ax, tri, sol.u, levels = -2.5:0.15:-1.0, colormap = :matter) #src +ax = Axis(fig[1, 2], width = 400, height = 400) #src +tricontourf!(ax, tri, u, levels = -2.5:0.15:-1.0, colormap = :matter) #src resize_to_layout!(fig) #src fig #src -@test_reference joinpath(@__DIR__, "../figures", "helmholtz_equation_with_inhomogeneous_boundary_conditions_exact_comparisons.png") fig #src \ No newline at end of file +@test_reference joinpath(@__DIR__, "../figures", + "helmholtz_equation_with_inhomogeneous_boundary_conditions_exact_comparisons.png") fig #src diff --git a/docs/src/literate_tutorials/laplaces_equation_with_internal_dirichlet_conditions.jl b/docs/src/literate_tutorials/laplaces_equation_with_internal_dirichlet_conditions.jl index 29def30..5519538 100644 --- a/docs/src/literate_tutorials/laplaces_equation_with_internal_dirichlet_conditions.jl +++ b/docs/src/literate_tutorials/laplaces_equation_with_internal_dirichlet_conditions.jl @@ -17,7 +17,7 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # ``` # To start with solving this problem, let us define an initial mesh. using DelaunayTriangulation, FiniteVolumeMethod -tri = triangulate_rectangle(0, 1, 0, 1, 50, 50, single_boundary=false) +tri = triangulate_rectangle(0, 1, 0, 1, 50, 50, single_boundary = false) # In this mesh, we don't have any points that lie exactly on the # line $\{x = 1/2, 0 \leq y \leq 2/5\}$, so we cannot enforce this @@ -37,7 +37,7 @@ fig, ax, sc = triplot(tri) fig # It may also help to refine the mesh slightly. -refine!(tri, max_area=1e-4) +refine!(tri, max_area = 1e-4) fig, ax, sc = triplot(tri) fig @@ -74,7 +74,7 @@ end vertices = find_all_points_on_line(tri) fig, ax, sc = triplot(tri) points = [get_point(tri, i) for i in vertices] -scatter!(ax, points, color=:red, markersize=10) +scatter!(ax, points, color = :red, markersize = 10) fig # Now that we have the vertices, we can define the internal conditions. @@ -83,7 +83,7 @@ fig # condition for that vertex. In this case, that function index # is `1` as we only have a single function. ICs = InternalConditions((x, y, t, u, p) -> zero(u), - dirichlet_nodes=Dict(vertices .=> 1)) + dirichlet_nodes = Dict(vertices .=> 1)) # Now we can define the problem. As discussed in # the [Helmholtz tutorial](helmholtz_equation_with_inhomogeneous_boundary_conditions.md), @@ -113,13 +113,12 @@ steady_prob = SteadyFVMProblem(prob) # Now let's solve the problem. using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq -sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) +sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) sol |> tc #hide #- -fig, ax, sc = tricontourf(tri, sol.u, levels=LinRange(0, 100, 28)) +fig, ax, sc = tricontourf(tri, sol.u, levels = LinRange(0, 100, 28)) tightlimits!(ax) fig using ReferenceTests #src @test_reference joinpath(@__DIR__, "../figures", "laplaces_equation_with_internal_dirichlet_conditions.png") fig #src - diff --git a/docs/src/literate_tutorials/mean_exit_time.jl b/docs/src/literate_tutorials/mean_exit_time.jl index 2f26036..2df2126 100644 --- a/docs/src/literate_tutorials/mean_exit_time.jl +++ b/docs/src/literate_tutorials/mean_exit_time.jl @@ -118,11 +118,11 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie R₁, R₂ = 2.0, 3.0 circle = CircularArc((0.0, R₂), (0.0, R₂), (0.0, 0.0)) -points = NTuple{2,Float64}[] -tri = triangulate(points; boundary_nodes=[circle]) +points = NTuple{2, Float64}[] +tri = triangulate(points; boundary_nodes = [circle]) θ = LinRange(0, 2π, 250) -xin = @views @. R₁ * cos(θ)[begin:end-1] -yin = @views @. R₁ * sin(θ)[begin:end-1] +xin = @views @. R₁ * cos(θ)[begin:(end - 1)] +yin = @views @. R₁ * sin(θ)[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -131,7 +131,7 @@ for i in 2:length(xin) end n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) #- @@ -145,7 +145,7 @@ D₁, D₂ = 6.25e-4, 6.25e-5 diffusion_function = (x, y, t, u, p) -> let r = sqrt(x^2 + y^2) return ifelse(r < p.R₁, p.D₁, p.D₂) end -diffusion_parameters = (R₁=R₁, D₁=D₁, D₂=D₂) +diffusion_parameters = (R₁ = R₁, D₁ = D₁, D₂ = D₂) # For the initial condition, which recall is the # initial guess for the steady problem, let us use the @@ -163,7 +163,7 @@ source_function = (x, y, t, u, p) -> one(u) prob = FVMProblem(mesh, BCs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) #- steady_prob = SteadyFVMProblem(prob) @@ -174,9 +174,9 @@ sol = solve(steady_prob, DynamicSS(Rosenbrock23())) sol |> tc #hide #- -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:500:20000, extendhigh=:auto) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:500:20000, extendhigh = :auto) fig using ReferenceTests #src @test_reference joinpath(@__DIR__, "../figures", "mean_exit_time_unperturbed_interface.png") fig #src @@ -190,7 +190,7 @@ function T_exact(x, y) #src end #src end #src _T_exact = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] #src -tricontourf!(Axis(fig[1, 2]), tri, _T_exact, levels=0:500:20000, extendhigh=:auto) #src +tricontourf!(Axis(fig[1, 2]), tri, _T_exact, levels = 0:500:20000, extendhigh = :auto) #src @test_reference joinpath(@__DIR__, "../figures", "mean_exit_time_unperturbed_interface_exact_comparison.png") fig #src # ## Perturbed interface @@ -199,11 +199,11 @@ tricontourf!(Axis(fig[1, 2]), tri, _T_exact, levels=0:500:20000, extendhigh=:aut g = θ -> sin(3θ) + cos(5θ) ε = 0.05 R1_f = θ -> R₁ * (1 + ε * g(θ)) -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] circle = CircularArc((0.0, R₂), (0.0, R₂), (0.0, 0.0)) -tri = triangulate(points; boundary_nodes=[circle]) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +tri = triangulate(points; boundary_nodes = [circle]) +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -212,7 +212,7 @@ for i in 2:length(xin) end n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) #- @@ -236,13 +236,13 @@ diffusion_function = (x, y, t, u, p) -> let r = sqrt(x^2 + y^2), θ = atan(y, x) interface_val = p.R1_f(θ) return ifelse(r < interface_val, p.D₁, p.D₂) end -diffusion_parameters = (D₁=D₁, D₂=D₂, R1_f=R1_f) +diffusion_parameters = (D₁ = D₁, D₂ = D₂, R1_f = R1_f) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] source_function = (x, y, t, u, p) -> one(u) prob = FVMProblem(mesh, BCs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) #- @@ -250,10 +250,10 @@ sol = solve(steady_prob, DynamicSS(Rosenbrock23())) sol |> tc #hide #- -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:500:20000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:500:20000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig @test_reference joinpath(@__DIR__, "../figures", "mean_exit_time_perturbed_interface.png") fig #src @@ -269,19 +269,20 @@ fig # any nearby particles, i.e. $T(0,0)=0$. add_point!(tri, 0.0, 0.0) mesh = FVMGeometry(tri) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(DelaunayTriangulation.num_points(tri) => 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), + dirichlet_nodes = Dict(DelaunayTriangulation.num_points(tri) => 1)) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(u), Dirichlet) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs, ICs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:500:10000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:500:10000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig @test_reference joinpath(@__DIR__, "../figures", "mean_exit_time_perturbed_interface_with_hole.png") fig #src @@ -291,13 +292,17 @@ fig # reflecting off all other parts. For the reflecting boundary condition, # this is enforced by using Neumann boundary conditions. ϵr = 0.25 -dirichlet_circle = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), (R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) -neumann_circle = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (R₂ * cos(ϵr), R₂ * sin(ϵr)), (0.0, 0.0)) +dirichlet_circle = CircularArc( + (R₂ * cos(ϵr), R₂ * sin(ϵr)), ( + R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) +neumann_circle = CircularArc( + (R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), ( + R₂ * cos(ϵr), R₂ * sin(ϵr)), (0.0, 0.0)) boundary_nodes = [[dirichlet_circle], [neumann_circle]] -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -308,37 +313,38 @@ n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) add_point!(tri, 0.0, 0.0) origin_idx = DelaunayTriangulation.num_points(tri) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) #- mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, (zero_f, zero_f), (Neumann, Dirichlet)) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(origin_idx => 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes = Dict(origin_idx => 1)) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs, ICs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:2500:35000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:2500:35000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig -@test_reference joinpath(@__DIR__, "../figures", "mean_exit_time_perturbed_interface_with_hole_and_reflecting_boundary.png") fig #src +@test_reference joinpath( + @__DIR__, "../figures", "mean_exit_time_perturbed_interface_with_hole_and_reflecting_boundary.png") fig #src # Now, as a last constraint, let's add a hole. We'll put hole at the origin, and we'll # move the point hole to $(-2, 0)$ rather than at the origin, and we'll also put a hole at # $(0, 2.95)$. -hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive=false) +hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive = false) boundary_nodes = [[[dirichlet_circle], [neumann_circle]], [[hole]]] -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -349,8 +355,9 @@ n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) add_point!(tri, -2.0, 0.0) add_point!(tri, 0.0, 2.95) -pointhole_idxs = [DelaunayTriangulation.num_points(tri), DelaunayTriangulation.num_points(tri) - 1] -refine!(tri; max_area=1e-3get_area(tri)) +pointhole_idxs = [ + DelaunayTriangulation.num_points(tri), DelaunayTriangulation.num_points(tri) - 1] +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) # The boundary condition we'll use at the new interior hole @@ -358,17 +365,18 @@ triplot(tri) mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, (zero_f, zero_f, zero_f), (Neumann, Dirichlet, Dirichlet)) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(pointhole_idxs .=> 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes = Dict(pointhole_idxs .=> 1)) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs, ICs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:1000:15000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:1000:15000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig -@test_reference joinpath(@__DIR__, "../figures", "mean_exit_time_perturbed_interface_with_hole_and_reflecting_boundary_and_holes.png") fig #src \ No newline at end of file +@test_reference joinpath(@__DIR__, "../figures", + "mean_exit_time_perturbed_interface_with_hole_and_reflecting_boundary_and_holes.png") fig #src diff --git a/docs/src/literate_tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.jl b/docs/src/literate_tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.jl index 463ce60..d5f141e 100644 --- a/docs/src/literate_tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.jl +++ b/docs/src/literate_tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.jl @@ -27,7 +27,7 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # near the origin, so we need to use `refine!` on an initial mesh. using DelaunayTriangulation, FiniteVolumeMethod, LinearAlgebra, CairoMakie L = 30 -tri = triangulate_rectangle(-L, L, -L, L, 2, 2, single_boundary=true) +tri = triangulate_rectangle(-L, L, -L, L, 2, 2, single_boundary = true) tot_area = get_area(tri) max_area_function = (A, r) -> 1e-6tot_area * r^2 / A area_constraint = (_tri, T) -> begin @@ -39,7 +39,7 @@ area_constraint = (_tri, T) -> begin flag = A ≥ max_area_function(A, dist_to_origin) return flag end -refine!(tri; min_angle=33.0, custom_constraint=area_constraint) +refine!(tri; min_angle = 33.0, custom_constraint = area_constraint) triplot(tri) #- @@ -85,7 +85,7 @@ flux_function = (x, y, t, α, β, γ, p) -> begin qy = -p.D * ∂y return (qx, qy) end -flux_parameters = (D=0.02, ν=0.05) +flux_parameters = (D = 0.02, ν = 0.05) final_time = 250.0 prob = FVMProblem(mesh, BCs; initial_condition, @@ -96,28 +96,31 @@ prob = FVMProblem(mesh, BCs; # Now we can solve and visualise the solution. using OrdinaryDiffEq, LinearSolve times = [0, 10, 25, 50, 100, 200, 250] -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=times) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = times) sol |> tc #hide #- using CairoMakie using ReferenceTests #src -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for i in eachindex(sol) - ax = Axis(fig[1, i], width=400, height=400, - xlabel="x", ylabel="y", - title="t = $(sol.t[i])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[i], levels=0:0.00001:0.001, extendhigh=:auto, extendlow=:auto, colormap=:matter) + ax = Axis(fig[1, i], width = 400, height = 400, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[i])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[i], levels = 0:0.00001:0.001, + extendhigh = :auto, extendlow = :auto, colormap = :matter) tightlimits!(ax) ylims!(ax, -10, 10) end resize_to_layout!(fig) fig -@test_reference joinpath(@__DIR__, "../figures", "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.png") fig #src +@test_reference joinpath(@__DIR__, + "../figures", + "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.png") fig #src using StatsBase, Test #src -_sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=1.0) #src +_sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 1.0) #src function exact_solution(x, y, t, D, ν) #src return 1 / (4 * D * π * t) * exp(1 / (4 * D * t) * (-(x - ν * t)^2 - y^2)) #src end #src @@ -138,18 +141,28 @@ function get_errs(_sol, tri, flux_parameters) #src end #src _errs = get_errs(_sol, tri, flux_parameters) #src @test all(≤(0.16), _errs) #src -fig = Figure(fontsize=64) #src +fig = Figure(fontsize = 64) #src t = [10, 25, 50, 100, 200, 250] #src t_idx = [findlast(≤(τ), _sol.t) for τ in t] #src for (i, j) in enumerate(t_idx) #src - ax = Axis(fig[1, i], width=400, height=400) #src - tricontourf!(ax, tri, _sol.u[j], levels=0:0.00001:0.001, extendhigh=:auto, extendlow=:auto, colormap=:matter) #src - ax = Axis(fig[2, i], width=400, height=400) #src - tricontourf!(ax, tri, [exact_solution(x, y, _sol.t[j], flux_parameters.D, flux_parameters.ν) for (x, y) in DelaunayTriangulation.each_point(tri)], levels=0:0.00001:0.001, extendhigh=:auto, extendlow=:auto, colormap=:matter) #src + ax = Axis(fig[1, i], width = 400, height = 400) #src + tricontourf!(ax, tri, _sol.u[j], levels = 0:0.00001:0.001, + extendhigh = :auto, extendlow = :auto, colormap = :matter) #src + ax = Axis(fig[2, i], width = 400, height = 400) #src + tricontourf!(ax, + tri, + [exact_solution(x, y, _sol.t[j], flux_parameters.D, flux_parameters.ν) + for (x, y) in DelaunayTriangulation.each_point(tri)], + levels = 0:0.00001:0.001, + extendhigh = :auto, + extendlow = :auto, + colormap = :matter) #src end #src resize_to_layout!(fig) #src fig #src -@test_reference joinpath(@__DIR__, "../figures", "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation_exact_comparisons.png") fig #src +@test_reference joinpath(@__DIR__, + "../figures", + "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation_exact_comparisons.png") fig #src # ## Piecewise linear interpolation # As mentioned in [mathematical details section](../math.md), a key part of the finite volume method is the assumption that @@ -170,7 +183,7 @@ fig #src # allowing you to avoid storing the triangle since you only consider each point a single time. x = LinRange(-L, L, 250) y = LinRange(-L, L, 250) -triangles = Matrix{NTuple{3,Int}}(undef, length(x), length(y)) +triangles = Matrix{NTuple{3, Int}}(undef, length(x), length(y)) for j in eachindex(y) for i in eachindex(x) triangles[i, j] = jump_and_march(tri, (x[i], y[j])) @@ -180,7 +193,8 @@ interpolated_vals = zeros(length(x), length(y), length(sol)) for k in eachindex(sol) for j in eachindex(y) for i in eachindex(x) - interpolated_vals[i, j, k] = pl_interpolate(prob, triangles[i, j], sol.u[k], x[i], y[j]) + interpolated_vals[i, j, k] = pl_interpolate( + prob, triangles[i, j], sol.u[k], x[i], y[j]) end end end @@ -188,19 +202,22 @@ end # Let's visualise these results to check their accuracy. We compute the triangulation of # our grid to make the `tricontourf` call faster. _tri = triangulate([[x for x in x, _ in y] |> vec [y for _ in x, y in y] |> vec]') -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for i in eachindex(sol) - ax = Axis(fig[1, i], width=400, height=400, - xlabel="x", ylabel="y", - title="t = $(sol.t[i])", - titlealign=:left) - tricontourf!(ax, _tri, interpolated_vals[:, :, i] |> vec, levels=0:0.00001:0.001, extendhigh=:auto, extendlow=:auto, colormap=:matter) + ax = Axis(fig[1, i], width = 400, height = 400, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[i])", + titlealign = :left) + tricontourf!(ax, _tri, interpolated_vals[:, :, i] |> vec, levels = 0:0.00001:0.001, + extendhigh = :auto, extendlow = :auto, colormap = :matter) tightlimits!(ax) ylims!(ax, -10, 10) end resize_to_layout!(fig) fig -@test_reference joinpath(@__DIR__, "../figures", "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation_piecewise_linear_interpolation.png") fig #src +@test_reference joinpath(@__DIR__, + "../figures", + "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation_piecewise_linear_interpolation.png") fig #src # ## Natural neighbour interpolation # Since the solution is defined over a triangulation, the most natural form of inteprolation to use, @@ -212,7 +229,7 @@ fig # The way to construct a natural neighbour interpolant is as follows, where we provide # the interpolant with the solution at $t = 50$. using NaturalNeighbours -itp = interpolate(tri, sol.u[4], derivatives=true) # sol.t[4] == 50 +itp = interpolate(tri, sol.u[4], derivatives = true) # sol.t[4] == 50 sol |> tc #hide # We need `derivatives = true` so that we can use the higher order interpolants `Sibson(1)`, `Hiyoshi(2)`, @@ -229,51 +246,61 @@ _y = [y for _ in x, y in y] |> vec; # We will look at all the interpolants provided by NaturalNeighbours.jl.[^1] # [^1]: This list is available from `?NaturalNeighbours.AbstractInterpolator`. Look at the help page (`?`) for the respective interpolators or NaturalNeighbours.jl's documentation for more information. -sibson_vals = itp(_x, _y; method=Sibson()) -triangle_vals = itp(_x, _y; method=Triangle()) # this is the same as pl_interpolate -laplace_vals = itp(_x, _y; method=Laplace()) -sibson_1_vals = itp(_x, _y; method=Sibson(1)) -nearest_vals = itp(_x, _y; method=Nearest()) -farin_vals = itp(_x, _y; method=Farin()) -hiyoshi_vals = itp(_x, _y; method=Hiyoshi(2)) +sibson_vals = itp(_x, _y; method = Sibson()) +triangle_vals = itp(_x, _y; method = Triangle()) # this is the same as pl_interpolate +laplace_vals = itp(_x, _y; method = Laplace()) +sibson_1_vals = itp(_x, _y; method = Sibson(1)) +nearest_vals = itp(_x, _y; method = Nearest()) +farin_vals = itp(_x, _y; method = Farin()) +hiyoshi_vals = itp(_x, _y; method = Hiyoshi(2)) pde_vals = sol.u[4]; # We visualise these results as follows. -fig = Figure(fontsize=38) -all_vals = (sibson_vals, triangle_vals, laplace_vals, sibson_1_vals, nearest_vals, farin_vals, hiyoshi_vals, pde_vals) -titles = ("(a): Sibson", "(b): Triangle", "(c): Laplace", "(d): Sibson-1", "(e): Nearest", "(f): Farin", "(g): Hiyoshi", "(h): PDE") -fig = Figure(fontsize=55, resolution=(6350, 1550)) # resolution from resize_to_layout!(fig) - had to manually adjust to fix missing ticks +fig = Figure(fontsize = 38) +all_vals = (sibson_vals, triangle_vals, laplace_vals, sibson_1_vals, + nearest_vals, farin_vals, hiyoshi_vals, pde_vals) +titles = ("(a): Sibson", "(b): Triangle", "(c): Laplace", "(d): Sibson-1", + "(e): Nearest", "(f): Farin", "(g): Hiyoshi", "(h): PDE") +fig = Figure(fontsize = 55, resolution = (6350, 1550)) # resolution from resize_to_layout!(fig) - had to manually adjust to fix missing ticks for (i, (vals, title)) in enumerate(zip(all_vals, titles)) - ax2d = Axis(fig[1, i], xlabel="x", ylabel="y", width=600, height=600, title=title, titlealign=:left) - ax3d = Axis3(fig[2, i], xlabel="x", ylabel="y", width=600, height=600, title=title, titlealign=:left) + ax2d = Axis(fig[1, i], xlabel = "x", ylabel = "y", width = 600, + height = 600, title = title, titlealign = :left) + ax3d = Axis3(fig[2, i], xlabel = "x", ylabel = "y", width = 600, + height = 600, title = title, titlealign = :left) ax3d.zlabeloffset[] = 125 xlims!(ax2d, -4, 6) ylims!(ax2d, -4, 4) xlims!(ax3d, -4, 6) ylims!(ax3d, -4, 4) if vals ≠ pde_vals - contourf!(ax2d, _x, _y, vals, colormap=:matter, levels=0:0.001:0.1, extendlow=:auto, extendhigh=:auto) + contourf!(ax2d, _x, _y, vals, colormap = :matter, + levels = 0:0.001:0.1, extendlow = :auto, extendhigh = :auto) vals = copy(vals) - vals[(_x.<-4).|(_x.>6)] .= NaN - vals[(_y.<-4).|(_y.>4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... - surface!(ax3d, _x, _y, vals, color=vals, colormap=:matter, colorrange=(0, 0.1)) + vals[(_x .< -4) .| (_x .> 6)] .= NaN + vals[(_y .< -4) .| (_y .> 4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... + surface!( + ax3d, _x, _y, vals, color = vals, colormap = :matter, colorrange = (0, 0.1)) else - tricontourf!(ax2d, tri, vals, colormap=:matter, levels=0:0.001:0.1, extendlow=:auto, extendhigh=:auto) + tricontourf!(ax2d, tri, vals, colormap = :matter, levels = 0:0.001:0.1, + extendlow = :auto, extendhigh = :auto) triangles = [T[j] for T in each_solid_triangle(tri), j in 1:3] x = getx.(get_points(tri)) y = gety.(get_points(tri)) vals = copy(vals) - vals[(x.<-4).|(x.>6)] .= NaN - vals[(y.<-4).|(y.>4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... - mesh!(ax3d, hcat(x, y, vals), triangles, color=vals, colormap=:matter, colorrange=(0, 0.1)) + vals[(x .< -4) .| (x .> 6)] .= NaN + vals[(y .< -4) .| (y .> 4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... + mesh!(ax3d, hcat(x, y, vals), triangles, color = vals, + colormap = :matter, colorrange = (0, 0.1)) end end fig -@test_reference joinpath(@__DIR__, "../figures", "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation_natural_neighbour_interpolation.png") fig #src +@test_reference joinpath(@__DIR__, + "../figures", + "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation_natural_neighbour_interpolation.png") fig #src # We note that natural neighbour interpolation is not technically well defined # for constrained triangulations. In this case it is fine, but for regions # with, say, holes or non-convex boundaries, you may run into issues. For such # cases, you should usually call the interpolant with `project=false` to at least # help the procedure a bit. You may also be interested in `identify_exterior_points`. -# We consider interpolating data over a region with holes in [this annulus example](diffusion_equation_on_an_annulus.md). \ No newline at end of file +# We consider interpolating data over a region with holes in [this annulus example](diffusion_equation_on_an_annulus.md). diff --git a/docs/src/literate_tutorials/porous_fisher_equation_and_travelling_waves.jl b/docs/src/literate_tutorials/porous_fisher_equation_and_travelling_waves.jl index d26f782..59d71e6 100644 --- a/docs/src/literate_tutorials/porous_fisher_equation_and_travelling_waves.jl +++ b/docs/src/literate_tutorials/porous_fisher_equation_and_travelling_waves.jl @@ -49,7 +49,7 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # Now with this preamble out of the way, let us solve this problem. using DelaunayTriangulation, FiniteVolumeMethod, OrdinaryDiffEq, LinearSolve a, b, c, d, nx, ny = 0.0, 3.0, 0.0, 40.0, 60, 80 -tri = triangulate_rectangle(a, b, c, d, nx, ny; single_boundary=false) +tri = triangulate_rectangle(a, b, c, d, nx, ny; single_boundary = false) mesh = FVMGeometry(tri) one_bc = (x, y, t, u, p) -> one(u) zero_bc = (x, y, t, u, p) -> zero(u) @@ -68,7 +68,7 @@ prob = FVMProblem(mesh, BCs; diffusion_function, diffusion_parameters, source_function, source_parameters, initial_condition, final_time) -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()); saveat=0.5) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 0.5) sol |> tc #hide # Let us now look at the travelling wave behaviour. We will plot the evolution over @@ -89,27 +89,29 @@ for (i, t_idx) in pairs(large_time_idx:lastindex(sol)) y = c + (k - 1) * (d - c) / (ny - 1) z = y - c * τ z_vals[k, i] = z - travelling_wave_values[k, i] = u[nx÷2, k] + travelling_wave_values[k, i] = u[nx ÷ 2, k] end end # Now we are in a position to plot. using CairoMakie -fig = Figure(resolution=(3200.72f0, 800.64f0), fontsize=38) +fig = Figure(resolution = (3200.72f0, 800.64f0), fontsize = 38) for (i, j) in zip(1:3, (1, 51, 101)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.05:1, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:0.05:1, colormap = :matter) tightlimits!(ax) end -ax = Axis(fig[1, 4], width=900, height=600) -colors = cgrad(:matter, length(sol) - large_time_idx + 1; categorical=false) -[lines!(ax, z_vals[:, i], travelling_wave_values[:, i], color=colors[i], linewidth=2) for i in 1:(length(sol)-large_time_idx+1)] +ax = Axis(fig[1, 4], width = 900, height = 600) +colors = cgrad(:matter, length(sol) - large_time_idx + 1; categorical = false) +[lines!(ax, z_vals[:, i], travelling_wave_values[:, i], color = colors[i], linewidth = 2) + for i in 1:(length(sol) - large_time_idx + 1)] exact_z_vals = collect(LinRange(extrema(z_vals)..., 500)) exact_travelling_wave_values = exact_solution.(exact_z_vals) -lines!(ax, exact_z_vals, exact_travelling_wave_values, color=:red, linewidth=4, linestyle=:dash) +lines!(ax, exact_z_vals, exact_travelling_wave_values, + color = :red, linewidth = 4, linestyle = :dash) fig using ReferenceTests #src -@test_reference joinpath(@__DIR__, "../figures", "porous_fisher_equation_and_travelling_waves.png") fig #src \ No newline at end of file +@test_reference joinpath(@__DIR__, "../figures", "porous_fisher_equation_and_travelling_waves.png") fig #src diff --git a/docs/src/literate_tutorials/porous_medium_equation.jl b/docs/src/literate_tutorials/porous_medium_equation.jl index 83604ad..9b83d6f 100644 --- a/docs/src/literate_tutorials/porous_medium_equation.jl +++ b/docs/src/literate_tutorials/porous_medium_equation.jl @@ -37,7 +37,7 @@ final_time = 12.0 ## Step 1: Define the mesh RmM = 4m / (m - 1) * (M / (4π))^((m - 1) / m) L = sqrt(RmM) * (D * final_time)^(1 / (2m)) -tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary=true) +tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary = true) mesh = FVMGeometry(tri) #- @@ -59,20 +59,21 @@ prob = FVMProblem(mesh, BCs; #- ## Step 4: Solve using LinearSolve, OrdinaryDiffEq -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()); saveat=3.0) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 3.0) sol |> tc #hide #- ## Step 5: Visualise using CairoMakie using ReferenceTests #src -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 3, 5)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.005:0.05, colormap=:matter, extendhigh=:auto) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!( + ax, tri, sol.u[j], levels = 0:0.005:0.05, colormap = :matter, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -81,7 +82,9 @@ fig function exact_solution(x, y, t, m, M, D) #src if x^2 + y^2 < RmM * (D * t)^(1 / m) #src - u_exact = (D * t)^(-1 / m) * ((M / (4π))^((m - 1) / m) - (m - 1) / (4m) * (x^2 + y^2) * (D * t)^(-1 / m))^(1 / (m - 1)) #src + u_exact = (D * t)^(-1 / m) * + ((M / (4π))^((m - 1) / m) - + (m - 1) / (4m) * (x^2 + y^2) * (D * t)^(-1 / m))^(1 / (m - 1)) #src else #src u_exact = 0.0 #src end #src @@ -102,12 +105,12 @@ function compare_solutions(sol, tri, m, M, D) #src return x, y, u #src end #src x, y, u = compare_solutions(sol, tri, m, M, D) #src -fig = Figure(fontsize=64) #src +fig = Figure(fontsize = 64) #src for i in eachindex(sol) #src - ax = Axis(fig[1, i], width=600, height=600) #src - tricontourf!(ax, tri, sol.u[i], levels=0:0.005:0.1, colormap=:matter) #src - ax = Axis(fig[2, i], width=600, height=600) #src - tricontourf!(ax, tri, u[:, i], levels=0:0.005:0.1, colormap=:matter) #src + ax = Axis(fig[1, i], width = 600, height = 600) #src + tricontourf!(ax, tri, sol.u[i], levels = 0:0.005:0.1, colormap = :matter) #src + ax = Axis(fig[2, i], width = 600, height = 600) #src + tricontourf!(ax, tri, u[:, i], levels = 0:0.005:0.1, colormap = :matter) #src end #src resize_to_layout!(fig) #src fig #src @@ -139,7 +142,7 @@ final_time = 10.0 ## Step 1: Define the mesh RmM = 4m / (m - 1) * (M / (4π))^((m - 1) / m) L = sqrt(RmM) * (D / (λ * (m - 1)) * (exp(λ * (m - 1) * final_time) - 1))^(1 / (2m)) -tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary=true) +tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary = true) mesh = FVMGeometry(tri) #- @@ -153,7 +156,7 @@ BCs = BoundaryConditions(mesh, bc, type) f = (x, y) -> M * 1 / (ε^2 * π) * exp(-1 / (ε^2) * (x^2 + y^2)) diffusion_function = (x, y, t, u, p) -> p.D * abs(u)^(p.m - 1) source_function = (x, y, t, u, λ) -> λ * u -diffusion_parameters = (D=D, m=m) +diffusion_parameters = (D = D, m = m) source_parameters = λ initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs; @@ -166,18 +169,19 @@ prob = FVMProblem(mesh, BCs; #- ## Step 4: Solve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()); saveat=2.5) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 2.5) sol |> tc #hide #- ## Step 5: Visualise -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 3, 5)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.05:1, extendlow=:auto, colormap=:matter, extendhigh=:auto) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:0.05:1, extendlow = :auto, + colormap = :matter, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -185,7 +189,8 @@ fig @test_reference joinpath(@__DIR__, "../figures", "porous_medium_equation_linear_source.png") fig #src function exact_solution(x, y, t, m, M, D, λ) #src - return exp(λ * t) * exact_solution(x, y, D / (λ * (m - 1)) * (exp(λ * (m - 1) * t) - 1), m, M, 1.0) #src + return exp(λ * t) * + exact_solution(x, y, D / (λ * (m - 1)) * (exp(λ * (m - 1) * t) - 1), m, M, 1.0) #src end #src function compare_solutions(sol, tri, m, M, D, λ) #src n = DelaunayTriangulation.num_solid_vertices(tri) #src @@ -201,12 +206,14 @@ function compare_solutions(sol, tri, m, M, D, λ) #src return x, y, u #src end #src x, y, u = compare_solutions(sol, tri, m, M, D, λ) #src -fig = Figure(fontsize=64) #src +fig = Figure(fontsize = 64) #src for i in eachindex(sol) #src - ax = Axis(fig[1, i], width=600, height=600) #src - tricontourf!(ax, tri, sol.u[i], levels=0:0.05:1, extendlow=:auto, colormap=:matter, extendhigh=:auto) #src - ax = Axis(fig[2, i], width=600, height=600) #src - tricontourf!(ax, tri, u[:, i], levels=0:0.05:1, extendlow=:auto, extendhigh=:auto, colormap=:matter) #src + ax = Axis(fig[1, i], width = 600, height = 600) #src + tricontourf!(ax, tri, sol.u[i], levels = 0:0.05:1, extendlow = :auto, + colormap = :matter, extendhigh = :auto) #src + ax = Axis(fig[2, i], width = 600, height = 600) #src + tricontourf!(ax, tri, u[:, i], levels = 0:0.05:1, extendlow = :auto, + extendhigh = :auto, colormap = :matter) #src end #src resize_to_layout!(fig) #src fig #src diff --git a/docs/src/literate_tutorials/reaction_diffusion_brusselator_system_of_pdes.jl b/docs/src/literate_tutorials/reaction_diffusion_brusselator_system_of_pdes.jl index d3590af..44de420 100644 --- a/docs/src/literate_tutorials/reaction_diffusion_brusselator_system_of_pdes.jl +++ b/docs/src/literate_tutorials/reaction_diffusion_brusselator_system_of_pdes.jl @@ -83,7 +83,7 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \end{equation*} # ``` using FiniteVolumeMethod, DelaunayTriangulation -tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary=false) +tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary = false) mesh = FVMGeometry(tri) # Now we define the boundary conditions. When considering a system of PDEs, @@ -128,19 +128,19 @@ mesh = FVMGeometry(tri) Ψ₀ = [Ψ_exact(x, y, 0) for (x, y) in DelaunayTriangulation.each_point(tri)]; # Next, we can define the `FVMProblem`s for each variable. -Φ_prob = FVMProblem(mesh, Φ_BCs; flux_function=Φ_q, source_function=Φ_S, - initial_condition=Φ₀, final_time=5.0) +Φ_prob = FVMProblem(mesh, Φ_BCs; flux_function = Φ_q, source_function = Φ_S, + initial_condition = Φ₀, final_time = 5.0) #- -Ψ_prob = FVMProblem(mesh, Ψ_BCs; flux_function=Ψ_q, source_function=Ψ_S, - initial_condition=Ψ₀, final_time=5.0) +Ψ_prob = FVMProblem(mesh, Ψ_BCs; flux_function = Ψ_q, source_function = Ψ_S, + initial_condition = Ψ₀, final_time = 5.0) # Finally, the `FVMSystem` is constructed by these two problems: system = FVMSystem(Φ_prob, Ψ_prob) # We can now solve the problem just as we've done previously. using OrdinaryDiffEq, LinearSolve -sol = solve(system, TRBDF2(linsolve=KLUFactorization()), saveat=1.0) +sol = solve(system, TRBDF2(linsolve = KLUFactorization()), saveat = 1.0) sol |> tc #hide # For this solution, note that the `u` values are matrices. For example: @@ -154,16 +154,16 @@ sol.u[3][1, :] |> tc #hide # are the value of $\Phi$ at the third time, and similarly `sol.u[3][2, :]` # are the values of $\Psi$ at the third time. We can visualise the solutions as follows: using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for i in eachindex(sol) - ax1 = Axis(fig[1, i], xlabel=L"x", ylabel=L"y", - width=400, height=400, - title=L"\Phi: t = %$(sol.t[i])", titlealign=:left) - ax2 = Axis(fig[2, i], xlabel=L"x", ylabel=L"y", - width=400, height=400, - title=L"\Psi: t = %$(sol.t[i])", titlealign=:left) - tricontourf!(ax1, tri, sol[i][1, :], levels=0:0.1:1, colormap=:matter) - tricontourf!(ax2, tri, sol[i][2, :], levels=1:10:100, colormap=:matter) + ax1 = Axis(fig[1, i], xlabel = L"x", ylabel = L"y", + width = 400, height = 400, + title = L"\Phi: t = %$(sol.t[i])", titlealign = :left) + ax2 = Axis(fig[2, i], xlabel = L"x", ylabel = L"y", + width = 400, height = 400, + title = L"\Psi: t = %$(sol.t[i])", titlealign = :left) + tricontourf!(ax1, tri, sol[i][1, :], levels = 0:0.1:1, colormap = :matter) + tricontourf!(ax2, tri, sol[i][2, :], levels = 1:10:100, colormap = :matter) end resize_to_layout!(fig) fig @@ -173,15 +173,16 @@ using ReferenceTests #src x = getx.(get_points(tri)) #src y = gety.(get_points(tri)) #src for i in eachindex(sol) #src - ax3 = Axis(fig[3, i], xlabel=L"x", ylabel=L"y", #src - width=400, height=400, #src - title=L"Exact $\Phi: t = %$(sol.t[i])$", titlealign=:left) #src - ax4 = Axis(fig[4, i], xlabel=L"x", ylabel=L"y", #src - width=400, height=400, #src - title=L"Exact $\Psi: t = %$(sol.t[i])$", titlealign=:left) #src - tricontourf!(ax3, tri, Φ_exact.(x, y, sol.t[i]), levels=0:0.1:1, colormap=:matter) #src - tricontourf!(ax4, tri, Ψ_exact.(x, y, sol.t[i]), levels=1:10:100, colormap=:matter) #src + ax3 = Axis(fig[3, i], xlabel = L"x", ylabel = L"y", #src + width = 400, height = 400, #src + title = L"Exact $\Phi: t = %$(sol.t[i])$", titlealign = :left) #src + ax4 = Axis(fig[4, i], xlabel = L"x", ylabel = L"y", #src + width = 400, height = 400, #src + title = L"Exact $\Psi: t = %$(sol.t[i])$", titlealign = :left) #src + tricontourf!(ax3, tri, Φ_exact.(x, y, sol.t[i]), levels = 0:0.1:1, colormap = :matter) #src + tricontourf!(ax4, tri, Ψ_exact.(x, y, sol.t[i]), levels = 1:10:100, colormap = :matter) #src end #src resize_to_layout!(fig) #src fig #src -@test_reference joinpath(@__DIR__, "../figures", "reaction_diffusion_brusselator_system_of_pdes_exact_comparisons.png") fig #src \ No newline at end of file +@test_reference joinpath( + @__DIR__, "../figures", "reaction_diffusion_brusselator_system_of_pdes_exact_comparisons.png") fig #src diff --git a/docs/src/literate_tutorials/reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.jl b/docs/src/literate_tutorials/reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.jl index 65c49aa..9aaa35f 100644 --- a/docs/src/literate_tutorials/reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.jl +++ b/docs/src/literate_tutorials/reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.jl @@ -26,7 +26,7 @@ points = NTuple{2, Float64}[] boundary_nodes = [circle] tri = triangulate(points; boundary_nodes) A = get_area(tri) -refine!(tri; max_area=1e-4A) +refine!(tri; max_area = 1e-4A) mesh = FVMGeometry(tri) #- @@ -44,31 +44,33 @@ R = (x, y, t, u, p) -> u * (1 - u) initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 0.10 prob = FVMProblem(mesh, BCs; - diffusion_function=D, - source_function=R, + diffusion_function = D, + source_function = R, final_time, initial_condition) # We can now solve. using OrdinaryDiffEq, LinearSolve -alg = FBDF(linsolve=UMFPACKFactorization(), autodiff=false) -sol = solve(prob, alg, saveat=0.01) +alg = FBDF(linsolve = UMFPACKFactorization(), autodiff = false) +sol = solve(prob, alg, saveat = 0.01) sol |> tc #hide #- using ReferenceTests #src -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=1:0.01:1.4, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 1:0.01:1.4, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) fig -@test_reference joinpath(@__DIR__, "../figures", "reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.png") fig #src +@test_reference joinpath(@__DIR__, + "../figures", + "reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.png") fig #src using ReferenceTests #src function exact_solution(x, y, t) #src @@ -90,13 +92,15 @@ function compare_solutions(sol, tri) #src return x, y, u #src end #src x, y, u = compare_solutions(sol, tri) #src -fig = Figure(fontsize=64) #src +fig = Figure(fontsize = 64) #src for i in eachindex(sol) #src - ax = Axis(fig[1, i], width=600, height=600) #src - tricontourf!(ax, tri, sol.u[i], levels=1:0.01:1.4, colormap=:matter) #src - ax = Axis(fig[2, i], width=600, height=600) #src - tricontourf!(ax, tri, u[:, i], levels=1:0.01:1.4, colormap=:matter) #src + ax = Axis(fig[1, i], width = 600, height = 600) #src + tricontourf!(ax, tri, sol.u[i], levels = 1:0.01:1.4, colormap = :matter) #src + ax = Axis(fig[2, i], width = 600, height = 600) #src + tricontourf!(ax, tri, u[:, i], levels = 1:0.01:1.4, colormap = :matter) #src end #src resize_to_layout!(fig) #src fig #src -@test_reference joinpath(@__DIR__, "../figures", "reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk_exact_comparisons.png") fig #src \ No newline at end of file +@test_reference joinpath(@__DIR__, + "../figures", + "reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk_exact_comparisons.png") fig #src diff --git a/docs/src/literate_tutorials/solving_mazes_with_laplaces_equation.jl b/docs/src/literate_tutorials/solving_mazes_with_laplaces_equation.jl index 15effc4..3cd9b3e 100644 --- a/docs/src/literate_tutorials/solving_mazes_with_laplaces_equation.jl +++ b/docs/src/literate_tutorials/solving_mazes_with_laplaces_equation.jl @@ -25,7 +25,7 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # looks like, where the start is in blue and the end is in red. using DelaunayTriangulation, CairoMakie, DelimitedFiles A = readdlm(joinpath(@__DIR__, "../tutorials/maze.txt")) -A = unique(A, dims=1) +A = unique(A, dims = 1) x = A[1:10:end, 2] # downsample to make the problem faster y = A[1:10:end, 1] start = findall(y .== 648) @@ -47,10 +47,10 @@ tri = triangulate(points; boundary_nodes) # takes a while because maze.txt conta refine!(tri) fig, ax, sc, = triplot(tri, - show_convex_hull=false, - show_constrained_edges=false) -lines!(ax, [get_point(tri, get_boundary_nodes(tri, 1)...)...], color=:blue, linewidth=6) -lines!(ax, [get_point(tri, get_boundary_nodes(tri, 3)...)...], color=:red, linewidth=6) + show_convex_hull = false, + show_constrained_edges = false) +lines!(ax, [get_point(tri, get_boundary_nodes(tri, 1)...)...], color = :blue, linewidth = 6) +lines!(ax, [get_point(tri, get_boundary_nodes(tri, 3)...)...], color = :red, linewidth = 6) fig # Now we can solve the problem. @@ -67,27 +67,27 @@ diffusion_function = (x, y, t, u, p) -> one(u) initial_condition = 0.05randn(StableRNG(123), DelaunayTriangulation.num_points(tri)) # random initial condition - this is the initial guess for the solution final_time = Inf prob = FVMProblem(mesh, BCs; - diffusion_function=diffusion_function, - initial_condition=initial_condition, - final_time=final_time) + diffusion_function = diffusion_function, + initial_condition = initial_condition, + final_time = final_time) steady_prob = SteadyFVMProblem(prob) #- using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq -sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization(), autodiff=false))) +sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization(), autodiff = false))) sol |> tc #hide # We now have our solution. -tricontourf(tri, sol.u, colormap=:matter) +tricontourf(tri, sol.u, colormap = :matter) # This is not what we use to compute the solution to the maze, # instead we need $\grad\phi$. We compute the gradient at each point using # NaturalNeighbours.jl. using NaturalNeighbours, LinearAlgebra -itp = interpolate(tri, sol.u; derivatives=true) +itp = interpolate(tri, sol.u; derivatives = true) ∇ = NaturalNeighbours.get_gradient(itp) ∇norms = norm.(∇) -tricontourf(tri, ∇norms, colormap=:matter) +tricontourf(tri, ∇norms, colormap = :matter) # The solution to the maze is now extremely clear from this plot! @@ -97,19 +97,22 @@ tricontourf(tri, ∇norms, colormap=:matter) using Accessors prob = @set prob.final_time = 1e8 LogRange(a, b, n) = exp10.(LinRange(log10(a), log10(b), n)) -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=LogRange(1e2, prob.final_time, 24 * 10)) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = LogRange(1e2, prob.final_time, 24 * + 10)) all_∇norms = map(sol.u) do u - itp = interpolate(tri, u; derivatives=true) + itp = interpolate(tri, u; derivatives = true) ∇ = NaturalNeighbours.get_gradient(itp) norm.(∇) end i = Observable(1) ∇norms = map(i -> all_∇norms[i], i) -fig, ax, sc = tricontourf(tri, ∇norms, colormap=:matter, levels=LinRange(0, 0.0035, 25), extendlow=:auto, extendhigh=:auto) +fig, ax, +sc = tricontourf(tri, ∇norms, colormap = :matter, levels = LinRange(0, 0.0035, 25), + extendlow = :auto, extendhigh = :auto) hidedecorations!(ax) tightlimits!(ax) record(fig, joinpath(@__DIR__, "../figures", "maze_solution_1.mp4"), eachindex(sol); - framerate=24) do _i + framerate = 24) do _i i[] = _i end; -# ![Animation of the solution of the maze](../figures/maze_solution_1.mp4) \ No newline at end of file +# ![Animation of the solution of the maze](../figures/maze_solution_1.mp4) diff --git a/docs/src/literate_wyos/diffusion_equations.jl b/docs/src/literate_wyos/diffusion_equations.jl index 7f028da..b29ac1c 100644 --- a/docs/src/literate_wyos/diffusion_equations.jl +++ b/docs/src/literate_wyos/diffusion_equations.jl @@ -107,7 +107,8 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # Let us start by writing out the contribution from all the triangles. using FiniteVolumeMethod const FVM = FiniteVolumeMethod -function triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) +function triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) for T in each_solid_triangle(mesh.triangulation) ijk = triangle_vertices(T) i, j, k = ijk @@ -132,7 +133,7 @@ end # Now we need the function that gets the contributions from the boundary edges. function boundary_edge_contributions!(A, b, mesh, conditions, - diffusion_function, diffusion_parameters) + diffusion_function, diffusion_parameters) for e in keys(get_boundary_edge_map(mesh.triangulation)) i, j = DelaunayTriangulation.edge_vertices(e) nx, ny, mᵢx, mᵢy, mⱼx, mⱼy, ℓ, T, props = FVM.get_boundary_cv_components(mesh, i, j) @@ -170,14 +171,16 @@ end function apply_dirichlet_conditions!(initial_condition, mesh, conditions) for (i, function_index) in FVM.get_dirichlet_nodes(conditions) x, y = get_point(mesh, i) - initial_condition[i] = FVM.eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) + initial_condition[i] = FVM.eval_condition_fnc( + conditions, function_index, x, y, nothing, nothing) end end function apply_dudt_conditions!(b, mesh, conditions) for (i, function_index) in FVM.get_dudt_nodes(conditions) if !FVM.is_dirichlet_node(conditions, i) # overlapping edges can be both Dudt and Dirichlet. Dirichlet takes precedence x, y = get_point(mesh, i) - b[i] = FVM.eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) + b[i] = FVM.eval_condition_fnc( + conditions, function_index, x, y, nothing, nothing) end end end @@ -201,21 +204,22 @@ end # ``` # Note that this also requires that we append a `1` to the initial condition. function diffusion_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) - A = @views Afull[begin:end-1, begin:end-1] - b = @views Afull[begin:end-1, end] + A = @views Afull[begin:(end - 1), begin:(end - 1)] + b = @views Afull[begin:(end - 1), end] _ic = vcat(initial_condition, 1) triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) apply_dudt_conditions!(b, mesh, conditions) apply_dirichlet_conditions!(_ic, mesh, conditions) A_op = MatrixOperator(sparse(Afull)) @@ -225,17 +229,18 @@ end # Let's now test the function. We use the same problem as in [this tutorial](../tutorials/diffusion_equation_on_a_square_plate.md). using DelaunayTriangulation, OrdinaryDiffEq, LinearAlgebra, SparseArrays -tri = triangulate_rectangle(0, 2, 0, 2, 50, 50, single_boundary=true) +tri = triangulate_rectangle(0, 2, 0, 2, 50, 50, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(x), Dirichlet) diffusion_function = (x, y, p) -> 1 / 9 -initial_condition = [y ≤ 1.0 ? 50.0 : 0.0 for (x, y) in DelaunayTriangulation.each_point(tri)] +initial_condition = [y ≤ 1.0 ? 50.0 : 0.0 + for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 0.5 prob = diffusion_equation(mesh, BCs; diffusion_function, initial_condition, final_time) -sol = solve(prob, Tsit5(); saveat=0.05) +sol = solve(prob, Tsit5(); saveat = 0.05) sol |> tc #hide # (It would be nice to use `LinearExponential()` in the call above, but it just seems to be extremely numerically unstable, so it's unusable.) @@ -252,14 +257,15 @@ DelaunayTriangulation.num_solid_vertices(tri) # Let's now plot. using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) u = j == 1 ? initial_condition : sol.u[j] # sol.u[1] is modified slightly to force the Dirichlet conditions at t = 0 - tricontourf!(ax, tri, u, levels=0:5:50, colormap=:matter, extendlow=:auto, extendhigh=:auto) # don't need to do u[begin:end-1], since tri doesn't have that extra vertex. + tricontourf!(ax, tri, u, levels = 0:5:50, colormap = :matter, + extendlow = :auto, extendhigh = :auto) # don't need to do u[begin:end-1], since tri doesn't have that extra vertex. tightlimits!(ax) end resize_to_layout!(fig) @@ -278,7 +284,7 @@ diff_eq = DiffusionEquation(mesh, BCs; # Let's compare `DiffusionEquation` to the `FVMProblem` approach. fvm_prob = FVMProblem(mesh, BCs; - diffusion_function=let D = diffusion_function + diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, initial_condition, @@ -306,8 +312,8 @@ using LinearSolve #src # Much better! The `DiffusionEquation` approach is about 10 times faster. -sol1 = solve(diff_eq, Tsit5(); saveat=0.05) #src -sol2 = solve(fvm_prob, TRBDF2(linsolve=KLUFactorization()), saveat=0.05) #src +sol1 = solve(diff_eq, Tsit5(); saveat = 0.05) #src +sol2 = solve(fvm_prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.05) #src using Test #src # To finish this example, let's solve a diffusion equation with constant Neumann boundary conditions: @@ -321,7 +327,7 @@ using Test #src # ``` # Here, $\Omega = [0, 320]^2$. L = 320.0 -tri = triangulate_rectangle(0, L, 0, L, 100, 100, single_boundary=true) +tri = triangulate_rectangle(0, L, 0, L, 100, 100, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> 2.0, Neumann) diffusion_function = (x, y, p) -> 2.0 @@ -340,18 +346,19 @@ prob = DiffusionEquation(mesh, BCs; final_time) # Let's solve and plot. -sol = solve(prob, Tsit5(); saveat=100.0) +sol = solve(prob, Tsit5(); saveat = 100.0) sol |> tc #hide #- -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for j in eachindex(sol) - ax = Axis(fig[1, j], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) + ax = Axis(fig[1, j], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) u = j == 1 ? initial_condition : sol.u[j] - tricontourf!(ax, tri, u, levels=0:0.1:1, colormap=:turbo, extendlow=:auto, extendhigh=:auto) + tricontourf!(ax, tri, u, levels = 0:0.1:1, colormap = :turbo, + extendlow = :auto, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -363,27 +370,28 @@ fig # $\vb q \vdot \vu n = -4$. Here is a comparison of the two solutions. BCs_prob = BoundaryConditions(mesh, (x, y, t, u, p) -> -4, Neumann) fvm_prob = FVMProblem(mesh, BCs_prob; - diffusion_function=let D = diffusion_function + diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, initial_condition, final_time) -fvm_sol = solve(fvm_prob, TRBDF2(linsolve=KLUFactorization()); saveat=100.0) +fvm_sol = solve(fvm_prob, TRBDF2(linsolve = KLUFactorization()); saveat = 100.0) fvm_sol |> tc #hide for j in eachindex(fvm_sol) - ax = Axis(fig[2, j], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(fvm_sol.t[j])", - titlealign=:left) + ax = Axis(fig[2, j], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(fvm_sol.t[j])", + titlealign = :left) u = j == 1 ? initial_condition : fvm_sol.u[j] - tricontourf!(ax, tri, u, levels=0:0.1:1, colormap=:turbo, extendlow=:auto, extendhigh=:auto) + tricontourf!(ax, tri, u, levels = 0:0.1:1, colormap = :turbo, + extendlow = :auto, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) fig @test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_template_1.png") fig #src -u_template = sol[begin:end-1, 2:end] #src +u_template = sol[begin:(end - 1), 2:end] #src u_fvm = fvm_sol[begin:end, 2:end] #src @test u_template ≈ u_fvm rtol = 1e-3 #src @@ -410,4 +418,5 @@ q = (30.0, 45.0) T = jump_and_march(tri, q) val = pl_interpolate(prob, T, sol.u[3], q[1], q[2]) using Test #src -@test pl_interpolate(prob, T, sol.u[3], q[1], q[2]) ≈ pl_interpolate(fvm_prob, T, fvm_sol.u[3], q[1], q[2]) rtol = 1e-3 #src +@test pl_interpolate(prob, T, sol.u[3], q[1], q[2]) ≈ + pl_interpolate(fvm_prob, T, fvm_sol.u[3], q[1], q[2]) rtol = 1e-3 #src diff --git a/docs/src/literate_wyos/laplaces_equation.jl b/docs/src/literate_wyos/laplaces_equation.jl index 6875550..d94ae25 100644 --- a/docs/src/literate_wyos/laplaces_equation.jl +++ b/docs/src/literate_wyos/laplaces_equation.jl @@ -25,16 +25,18 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod function laplaces_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function=(x, y, p) -> 1.0, - diffusion_parameters=nothing) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function = (x, y, p) -> 1.0, + diffusion_parameters = nothing) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) b = zeros(DelaunayTriangulation.num_points(mesh.triangulation)) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) FVM.apply_steady_dirichlet_conditions!(A, b, mesh, conditions) FVM.fix_missing_vertices!(A, b, mesh) Asp = sparse(A) @@ -76,7 +78,7 @@ boundary_x = [lower_x, outer_arc_x, left_x, inner_arc_x] boundary_y = [lower_y, outer_arc_y, left_y, inner_arc_y] boundary_nodes, points = convert_boundary_points_to_indices(boundary_x, boundary_y) tri = triangulate(points; boundary_nodes) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) #- @@ -92,7 +94,7 @@ bc_types = (Dirichlet, Dirichlet, Dirichlet, Dirichlet) BCs = BoundaryConditions(mesh, bc_f, bc_types) # Now we can define and solve the problem. -prob = laplaces_equation(mesh, BCs, diffusion_function=(x, y, p) -> 1.0) +prob = laplaces_equation(mesh, BCs, diffusion_function = (x, y, p) -> 1.0) prob |> tc #hide #- @@ -100,9 +102,9 @@ sol = solve(prob, KLUFactorization()) sol |> tc #hide #- -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y", width=600, height=600) -tricontourf!(ax, tri, sol.u, levels=0:0.1:1, colormap=:jet) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y", width = 600, height = 600) +tricontourf!(ax, tri, sol.u, levels = 0:0.1:1, colormap = :jet) resize_to_layout!(fig) fig @@ -110,9 +112,9 @@ fig initial_condition = zeros(DelaunayTriangulation.num_points(tri)) FVM.apply_dirichlet_conditions!(initial_condition, mesh, Conditions(mesh, BCs, InternalConditions())) # a good initial guess fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; - diffusion_function=(x, y, t, u, p) -> 1.0, + diffusion_function = (x, y, t, u, p) -> 1.0, initial_condition, - final_time=Inf)) + final_time = Inf)) #- using SteadyStateDiffEq, OrdinaryDiffEq @@ -120,8 +122,8 @@ fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2())) fvm_sol |> tc #hide #- -ax = Axis(fig[1, 2], xlabel="x", ylabel="y", width=600, height=600) -tricontourf!(ax, tri, fvm_sol.u, levels=0:0.1:1, colormap=:jet) +ax = Axis(fig[1, 2], xlabel = "x", ylabel = "y", width = 600, height = 600) +tricontourf!(ax, tri, fvm_sol.u, levels = 0:0.1:1, colormap = :jet) resize_to_layout!(fig) fig using ReferenceTests #src @@ -148,7 +150,7 @@ using Test #src # # where $D(\vb x) = (x+1)(y+2)$. The exact solution # is $u(x, y) = 5\log_6(1+x)$. We define this problem as follows. -tri = triangulate_rectangle(0, 5, 0, 5, 100, 100, single_boundary=false) +tri = triangulate_rectangle(0, 5, 0, 5, 100, 100, single_boundary = false) mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> 0.0 five_f = (x, y, t, u, p) -> 5.0 @@ -163,16 +165,16 @@ sol = solve(prob, KLUFactorization()) sol |> tc #hide #- -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y", - width=600, height=600, - title="Numerical", titlealign=:left) -tricontourf!(ax, tri, sol.u, levels=0:0.25:5, colormap=:jet) -ax = Axis(fig[1, 2], xlabel="x", ylabel="y", - width=600, height=600, - title="Exact", titlealign=:left) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y", + width = 600, height = 600, + title = "Numerical", titlealign = :left) +tricontourf!(ax, tri, sol.u, levels = 0:0.25:5, colormap = :jet) +ax = Axis(fig[1, 2], xlabel = "x", ylabel = "y", + width = 600, height = 600, + title = "Exact", titlealign = :left) u_exact = [5log(1 + x) / log(6) for (x, y) in DelaunayTriangulation.each_point(tri)] -tricontourf!(ax, tri, u_exact, levels=0:0.25:5, colormap=:jet) +tricontourf!(ax, tri, u_exact, levels = 0:0.25:5, colormap = :jet) resize_to_layout!(fig) fig @test_reference joinpath(@__DIR__, "../figures", "laplaces_equation_template_2.png") fig #src @@ -183,8 +185,8 @@ fig initial_condition = zeros(DelaunayTriangulation.num_points(tri)) FVM.apply_dirichlet_conditions!(initial_condition, mesh, Conditions(mesh, BCs, InternalConditions())) # a good initial guess fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; - diffusion_function=(x, y, t, u, p) -> (x + 1) * (y + 2), - final_time=Inf, + diffusion_function = (x, y, t, u, p) -> (x + 1) * (y + 2), + final_time = Inf, initial_condition)) # ````julia @@ -204,5 +206,5 @@ fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; # 495.417 ms (223001 allocations: 114.30 MiB) # ```` -fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) #src -@test sol.u ≈ fvm_sol.u rtol = 1e-5 #src \ No newline at end of file +fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) #src +@test sol.u ≈ fvm_sol.u rtol = 1e-5 #src diff --git a/docs/src/literate_wyos/linear_reaction_diffusion_equations.jl b/docs/src/literate_wyos/linear_reaction_diffusion_equations.jl index 8ab7828..c128d5c 100644 --- a/docs/src/literate_wyos/linear_reaction_diffusion_equations.jl +++ b/docs/src/literate_wyos/linear_reaction_diffusion_equations.jl @@ -24,7 +24,8 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # `boundary_edge_contributions!` from the diffusion equation example. Here is our implementation. using FiniteVolumeMethod, SparseArrays, OrdinaryDiffEq, LinearAlgebra const FVM = FiniteVolumeMethod -function linear_source_contributions!(A, mesh, conditions, source_function, source_parameters) +function linear_source_contributions!( + A, mesh, conditions, source_function, source_parameters) for i in each_solid_vertex(mesh.triangulation) if !FVM.has_condition(conditions, i) x, y = get_point(mesh, i) @@ -33,23 +34,25 @@ function linear_source_contributions!(A, mesh, conditions, source_function, sour end end function linear_reaction_diffusion_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - source_function, - source_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + source_function, + source_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) - A = @views Afull[begin:end-1, begin:end-1] - b = @views Afull[begin:end-1, end] + A = @views Afull[begin:(end - 1), begin:(end - 1)] + b = @views Afull[begin:(end - 1), end] _ic = vcat(initial_condition, 1) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) linear_source_contributions!(A, mesh, conditions, source_function, source_parameters) FVM.apply_dudt_conditions!(b, mesh, conditions) FVM.apply_dirichlet_conditions!(_ic, mesh, conditions) @@ -70,11 +73,11 @@ end # ``` # with $\grad T \vdot\vu n = 1$ on the boundary. using DelaunayTriangulation -tri = triangulate_rectangle(0, 1, 0, 1, 150, 150, single_boundary=true) +tri = triangulate_rectangle(0, 1, 0, 1, 150, 150, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> one(x), Neumann) diffusion_function = (x, y, p) -> p.D * x^2 * y -diffusion_parameters = (D=1e-3,) +diffusion_parameters = (D = 1e-3,) source_function = (x, y, p) -> (x - 1) * (y - 1) initial_condition = [x^2 + y^2 for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 8.0 @@ -84,17 +87,18 @@ prob = linear_reaction_diffusion_equation(mesh, BCs; prob |> tc #hide #- -sol = solve(prob, Tsit5(); saveat=2) +sol = solve(prob, Tsit5(); saveat = 2) sol |> tc #hide #- using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for j in eachindex(sol) - ax = Axis(fig[1, j], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])") - tricontourf!(ax, tri, sol.u[j], levels=0:0.1:1, extendlow=:auto, extendhigh=:auto, colormap=:turbo) + ax = Axis(fig[1, j], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])") + tricontourf!(ax, tri, sol.u[j], levels = 0:0.1:1, extendlow = :auto, + extendhigh = :auto, colormap = :turbo) tightlimits!(ax) end resize_to_layout!(fig) @@ -105,28 +109,29 @@ fig # we need them in the form $\vb q\vdot\vu n = \ldots$. For this problem, $\vb q=-D\grad T$, # which gives $\vb q\vdot\vu n = -D$. _BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> -p.D(x, y, p.Dp), Neumann; - parameters=(D=diffusion_function, Dp=diffusion_parameters)) + parameters = (D = diffusion_function, Dp = diffusion_parameters)) fvm_prob = FVMProblem( mesh, _BCs; - diffusion_function=let D=diffusion_function + diffusion_function = let D=diffusion_function (x, y, t, u, p) -> D(x, y, p) end, - diffusion_parameters=diffusion_parameters, - source_function=let S=source_function + diffusion_parameters = diffusion_parameters, + source_function = let S=source_function (x, y, t, u, p) -> S(x, y, p) * u end, - final_time=final_time, - initial_condition=initial_condition + final_time = final_time, + initial_condition = initial_condition ) -fvm_sol = solve(fvm_prob, Tsit5(), saveat=2.0) +fvm_sol = solve(fvm_prob, Tsit5(), saveat = 2.0) fvm_sol |> tc #hide for j in eachindex(fvm_sol) #src - ax = Axis(fig[2, j], width=600, height=600, #src - xlabel="x", ylabel="y", #src - title="t = $(fvm_sol.t[j])") #src - tricontourf!(ax, tri, fvm_sol.u[j], levels=0:0.1:1, extendlow=:auto, extendhigh=:auto, colormap=:turbo) #src + ax = Axis(fig[2, j], width = 600, height = 600, #src + xlabel = "x", ylabel = "y", #src + title = "t = $(fvm_sol.t[j])") #src + tricontourf!(ax, tri, fvm_sol.u[j], levels = 0:0.1:1, + extendlow = :auto, extendhigh = :auto, colormap = :turbo) #src tightlimits!(ax) #src end #src resize_to_layout!(fig) #src @@ -137,12 +142,12 @@ using ReferenceTests #src # The above code is implemented in `LinearReactionDiffusionEquation` in FiniteVolumeMethod.jl. prob = LinearReactionDiffusionEquation(mesh, BCs; diffusion_function, diffusion_parameters, - source_function, initial_condition, final_time) -sol = solve(prob, Tsit5(); saveat=2) + source_function, initial_condition, final_time) +sol = solve(prob, Tsit5(); saveat = 2) sol |> tc #hide using Test #src -@test sol[begin:end-1, 2:end] ≈ fvm_sol[:, 2:end] rtol=1e-1 #src +@test sol[begin:(end - 1), 2:end] ≈ fvm_sol[:, 2:end] rtol=1e-1 #src # Here is a benchmark comparison of `LinearReactionDiffusionEquation` versus `FVMProblem`. # ````julia @@ -161,4 +166,4 @@ using Test #src # # ```` # 163.686 ms (83267 allocations: 90.84 MiB) -# ```` \ No newline at end of file +# ```` diff --git a/docs/src/literate_wyos/mean_exit_time.jl b/docs/src/literate_wyos/mean_exit_time.jl index 303e1bc..d565737 100644 --- a/docs/src/literate_wyos/mean_exit_time.jl +++ b/docs/src/literate_wyos/mean_exit_time.jl @@ -62,14 +62,15 @@ end using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod function met_problem(mesh::FVMGeometry, - BCs::BoundaryConditions, # the actual implementation also checks that the types are only Dirichlet/Neumann - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing) + BCs::BoundaryConditions, # the actual implementation also checks that the types are only Dirichlet/Neumann + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) b = create_met_b!(A, mesh, conditions) FVM.fix_missing_vertices!(A, b, mesh) return LinearProblem(sparse(A), b) @@ -86,15 +87,18 @@ R1_f = let R₁ = R₁, ε = ε, g = g # use let for type stability θ -> R₁ * (1.0 + ε * g(θ)) end ϵr = 0.25 -dirichlet = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), (R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) -neumann = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (R₂ * cos(ϵr), R₂ * sin(ϵr)), (0.0, 0.0)) -hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive=false) +dirichlet = CircularArc( + (R₂ * cos(ϵr), R₂ * sin(ϵr)), ( + R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) +neumann = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (R₂ * cos(ϵr), R₂ * sin(ϵr)), ( + 0.0, 0.0)) +hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive = false) boundary_nodes = [[[dirichlet], [neumann]], [[hole]]] points = [(-2.0, 0.0), (0.0, 2.95)] tri = triangulate(points; boundary_nodes) θ = LinRange(0, 2π, 250) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -104,12 +108,12 @@ end n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) pointhole_idxs = [1, 2] -refine!(tri; max_area=1e-3get_area(tri)); +refine!(tri; max_area = 1e-3get_area(tri)); ## Define the problem mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> zero(u) # the function doesn't actually matter, but it still needs to be provided BCs = BoundaryConditions(mesh, (zero_f, zero_f, zero_f), (Neumann, Dirichlet, Dirichlet)) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(pointhole_idxs .=> 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes = Dict(pointhole_idxs .=> 1)) D₁, D₂ = 6.25e-4, 6.25e-5 diffusion_function = (x, y, p) -> begin r = sqrt(x^2 + y^2) @@ -117,7 +121,7 @@ diffusion_function = (x, y, p) -> begin interface_val = p.R1_f(ϕ) return r < interface_val ? p.D₁ : p.D₂ end -diffusion_parameters = (D₁=D₁, D₂=D₂, R1_f=R1_f) +diffusion_parameters = (D₁ = D₁, D₂ = D₂, R1_f = R1_f) prob = met_problem(mesh, BCs, ICs; diffusion_function, diffusion_parameters) prob |> tc #hide @@ -132,8 +136,9 @@ sol |> tc #hide # We can easily visualise our solution: using CairoMakie -fig, ax, sc = tricontourf(tri, sol.u, levels=0:1000:15000, extendhigh=:auto, - axis=(width=600, height=600, title="Template")) +fig, ax, +sc = tricontourf(tri, sol.u, levels = 0:1000:15000, extendhigh = :auto, + axis = (width = 600, height = 600, title = "Template")) fig # This result is a great match to what we found in the [tutorial](../tutorials/mean_exit_time.md). @@ -149,12 +154,12 @@ function T_exact(x, y) end initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] # an initial guess fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; - diffusion_function=let D = diffusion_function + diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, diffusion_parameters, - source_function=(x, y, t, u, p) -> one(u), - final_time=Inf, + source_function = (x, y, t, u, p) -> one(u), + final_time = Inf, initial_condition)) # Let's compare the two solutions. @@ -163,8 +168,8 @@ fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2())) fvm_sol |> tc #hide #- -ax = Axis(fig[1, 2], width=600, height=600, title="Template") -tricontourf!(ax, tri, fvm_sol.u, levels=0:1000:15000, extendhigh=:auto) +ax = Axis(fig[1, 2], width = 600, height = 600, title = "Template") +tricontourf!(ax, tri, fvm_sol.u, levels = 0:1000:15000, extendhigh = :auto) resize_to_layout!(fig) fig using ReferenceTests #src @@ -185,8 +190,9 @@ sol |> tc #hide @test sol.u == _u #src #- -fig, ax, sc = tricontourf(tri, sol.u, levels=0:1000:15000, extendhigh=:auto, - axis=(width=600, height=600)) +fig, ax, +sc = tricontourf(tri, sol.u, levels = 0:1000:15000, extendhigh = :auto, + axis = (width = 600, height = 600)) fig @test_reference joinpath(@__DIR__, "../figures", "mean_exit_time_template_2.png") fig #src @@ -208,4 +214,4 @@ fig # 221.851 ms (314440 allocations: 90.23 MiB) # ```` -# Very fast! \ No newline at end of file +# Very fast! diff --git a/docs/src/literate_wyos/poissons_equation.jl b/docs/src/literate_wyos/poissons_equation.jl index 76e8a90..4919010 100644 --- a/docs/src/literate_wyos/poissons_equation.jl +++ b/docs/src/literate_wyos/poissons_equation.jl @@ -56,18 +56,20 @@ end using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod function poissons_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function=(x,y,p)->1.0, - diffusion_parameters=nothing, - source_function, - source_parameters=nothing) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function = (x, y, p)->1.0, + diffusion_parameters = nothing, + source_function, + source_parameters = nothing) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) b = create_rhs_b(mesh, conditions, source_function, source_parameters) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) apply_steady_dirichlet_conditions!(A, b, mesh, conditions) FVM.fix_missing_vertices!(A, b, mesh) return LinearProblem(sparse(A), b) @@ -82,11 +84,11 @@ end # \end{aligned} # \end{equation*} # ``` -tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary=true) +tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(x), Dirichlet) source_function = (x, y, p) -> -sin(π * x) * sin(π * y) -prob = poissons_equation(mesh, BCs; source_function) +prob = poissons_equation(mesh, BCs; source_function) using DisplayAs #hide prob |> tc #hide @@ -96,11 +98,14 @@ sol |> tc #hide #- using CairoMakie -fig, ax, sc = tricontourf(tri, sol.u, levels=LinRange(0, 0.05, 10), colormap=:matter, extendhigh=:auto) +fig, ax, +sc = tricontourf( + tri, sol.u, levels = LinRange(0, 0.05, 10), colormap = :matter, extendhigh = :auto) tightlimits!(ax) fig using Test #src -@test sol.u ≈ [1 / (2π^2) * sin(π * x) * sin(π * y) for (x, y) in DelaunayTriangulation.each_point(tri)] rtol = 1e-4 #src +@test sol.u ≈ [1 / (2π^2) * sin(π * x) * sin(π * y) + for (x, y) in DelaunayTriangulation.each_point(tri)] rtol = 1e-4 #src # If we wanted to turn this into a `SteadyFVMProblem`, we use a similar call to `poissons_equation` # above except with an `initial_condition` for the initial guess. Moreover, we need to @@ -108,20 +113,21 @@ using Test #src # when `FVMProblem`s assume that we are solving $0 = \div[D(\vb x)\grad u] + f(\vb x)$. initial_condition = zeros(DelaunayTriangulation.num_points(tri)) fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; - diffusion_function= (x, y, t, u, p) -> 1.0, - source_function=let S = source_function + diffusion_function = (x, y, t, u, p) -> 1.0, + source_function = let S = source_function (x, y, t, u, p) -> -S(x, y, p) end, initial_condition, - final_time=Inf)) + final_time = Inf)) #- using SteadyStateDiffEq, OrdinaryDiffEq -fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) +fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) fvm_sol |> tc #hide using ReferenceTests #src ax = Axis(fig[1, 2]) #src -tricontourf!(ax, tri, fvm_sol.u, levels=LinRange(0, 0.05, 10), colormap=:matter, extendhigh=:auto) #src +tricontourf!(ax, tri, fvm_sol.u, levels = LinRange(0, 0.05, 10), + colormap = :matter, extendhigh = :auto) #src tightlimits!(ax) #src resize_to_layout!(fig) #src fig #src @@ -131,12 +137,13 @@ fig #src # ## Using the Provided Template # Let's now use the built-in `PoissonsEquation` which implements the above template # inside FiniteVolumeMethod.jl. The above problem can be constructed as follows: -prob = PoissonsEquation(mesh, BCs; source_function=source_function) +prob = PoissonsEquation(mesh, BCs; source_function = source_function) #- sol = solve(prob, KLUFactorization()) sol |> tc #hide -@test sol.u ≈ [1 / (2π^2) * sin(π * x) * sin(π * y) for (x, y) in DelaunayTriangulation.each_point(tri)] rtol = 1e-4 #src +@test sol.u ≈ [1 / (2π^2) * sin(π * x) * sin(π * y) + for (x, y) in DelaunayTriangulation.each_point(tri)] rtol = 1e-4 #src @test sol.u ≈ fvm_sol.u rtol = 1e-4 #src # Here is a benchmark comparison of the `PoissonsEquation` approach against the `FVMProblem` approach. @@ -189,9 +196,9 @@ g, h = (2.0, 7.0), (8.0, 7.0) points = [(a, c), (b, c), (b, d), (a, d), e, f, g, h] boundary_nodes = [[1, 2], [2, 3], [3, 4], [4, 1]] segments = Set(((5, 6), (7, 8))) -tri = triangulate(points; boundary_nodes, segments, delete_ghosts=false) -refine!(tri; max_area=1e-4get_area(tri)) -fig, ax, sc = triplot(tri, show_constrained_edges=true, constrained_edge_linewidth=5) +tri = triangulate(points; boundary_nodes, segments, delete_ghosts = false) +refine!(tri; max_area = 1e-4get_area(tri)) +fig, ax, sc = triplot(tri, show_constrained_edges = true, constrained_edge_linewidth = 5) fig #- @@ -223,7 +230,7 @@ dirichlet_nodes = Dict( (upper_plate .=> 2)... ) internal_f = ((x, y, t, u, p) -> -one(x), (x, y, t, u, p) -> one(x)) -ICs = InternalConditions(internal_f, dirichlet_nodes=dirichlet_nodes) +ICs = InternalConditions(internal_f, dirichlet_nodes = dirichlet_nodes) # Next, we define $\epsilon(\vb x)$ and $\rho(\vb x)$. We need a function that computes the distance # between a point and the plates. For the distance between a point and a line segment, we can use:[^3] @@ -265,13 +272,13 @@ function plate_source_function(x, y, p) end # Now we can define our problem. -diffusion_parameters = (ϵ₀=8.8541878128e-12, Δ=4.0) -source_parameters = (ϵ₀=8.8541878128e-12, Q=1e-6) +diffusion_parameters = (ϵ₀ = 8.8541878128e-12, Δ = 4.0) +source_parameters = (ϵ₀ = 8.8541878128e-12, Q = 1e-6) prob = PoissonsEquation(mesh, BCs, ICs; - diffusion_function=dielectric_function, - diffusion_parameters=diffusion_parameters, - source_function=plate_source_function, - source_parameters=source_parameters) + diffusion_function = dielectric_function, + diffusion_parameters = diffusion_parameters, + source_function = plate_source_function, + source_parameters = source_parameters) #- sol = solve(prob, KLUFactorization()) @@ -280,7 +287,7 @@ sol |> tc #hide # With this solution, we can also define the electric field $\vb E$, using $\vb E = -\grad V$. # To compute the gradients, we use NaturalNeighbours.jl. using NaturalNeighbours -itp = interpolate(tri, sol.u; derivatives=true) +itp = interpolate(tri, sol.u; derivatives = true) E = map(.-, itp.gradient) # E = -∇V E |> tc #hide @@ -293,41 +300,41 @@ x = LinRange(0, 10, 25) y = LinRange(0, 10, 25) x_vec = [x for x in x, y in y] |> vec y_vec = [y for x in x, y in y] |> vec -E_itp = map(.-, ∂(x_vec, y_vec, interpolant_method=Hiyoshi(2))) +E_itp = map(.-, ∂(x_vec, y_vec, interpolant_method = Hiyoshi(2))) E_intensity = norm.(E_itp) -fig = Figure(fontsize=38) -ax = Axis(fig[1, 1], width=600, height=600, titlealign=:left, - xlabel="x", ylabel="y", title="Voltage") -tricontourf!(ax, tri, sol.u, levels=15, colormap=:ocean) +fig = Figure(fontsize = 38) +ax = Axis(fig[1, 1], width = 600, height = 600, titlealign = :left, + xlabel = "x", ylabel = "y", title = "Voltage") +tricontourf!(ax, tri, sol.u, levels = 15, colormap = :ocean) arrow_positions = [Point2f(x, y) for (x, y) in zip(x_vec, y_vec)] arrow_directions = [Point2f(e...) for e in E_itp] arrows!(ax, arrow_positions, arrow_directions, - lengthscale=0.3, normalize=true, arrowcolor=E_intensity, linecolor=E_intensity) - xlims!(ax,0,10) - ylims!(ax,0,10) -ax = Axis(fig[1, 2], width=600, height=600, titlealign=:left, - xlabel="x", ylabel="y", title="Electric Field") -tricontourf!(ax, tri, norm.(E), levels=15, colormap=:ocean) + lengthscale = 0.3, normalize = true, arrowcolor = E_intensity, linecolor = E_intensity) +xlims!(ax, 0, 10) +ylims!(ax, 0, 10) +ax = Axis(fig[1, 2], width = 600, height = 600, titlealign = :left, + xlabel = "x", ylabel = "y", title = "Electric Field") +tricontourf!(ax, tri, norm.(E), levels = 15, colormap = :ocean) arrows!(ax, arrow_positions, arrow_directions, - lengthscale=0.3, normalize=true, arrowcolor=E_intensity, linecolor=E_intensity) -xlims!(ax,0,10) -ylims!(ax,0,10) + lengthscale = 0.3, normalize = true, arrowcolor = E_intensity, linecolor = E_intensity) +xlims!(ax, 0, 10) +ylims!(ax, 0, 10) resize_to_layout!(fig) fig @test_reference joinpath(@__DIR__, "../figures", "poissons_equation_template_2.png") fig by = psnr_equality(20) #src # To finish, let us benchmark the `PoissonsEquation` approach against the `FVMProblem` approach. fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; - diffusion_function=let D = dielectric_function + diffusion_function = let D = dielectric_function (x, y, t, u, p) -> D(x, y, p) end, - source_function=let S = plate_source_function + source_function = let S = plate_source_function (x, y, t, u, p) -> -S(x, y, p) end, - diffusion_parameters=diffusion_parameters, - source_parameters=source_parameters, - initial_condition=zeros(DelaunayTriangulation.num_points(tri)), - final_time=Inf)) + diffusion_parameters = diffusion_parameters, + source_parameters = source_parameters, + initial_condition = zeros(DelaunayTriangulation.num_points(tri)), + final_time = Inf)) # ````julia # @btime solve($prob, $KLUFactorization()); @@ -345,5 +352,5 @@ fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; # 329.327 ms (201134 allocations: 93.70 MiB) # ```` -fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) #src -@test sol.u ≈ fvm_sol.u rtol = 1e-4 #src \ No newline at end of file +fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) #src +@test sol.u ≈ fvm_sol.u rtol = 1e-4 #src diff --git a/docs/src/math.md b/docs/src/math.md index 29a1d19..1d00563 100644 --- a/docs/src/math.md +++ b/docs/src/math.md @@ -4,21 +4,25 @@ Pages = ["math.md"] ``` -Here we outline the mathematical and implementation details involved in implementing the finite volume method (FVM). We assume that our partial differential equation (PDE) is given by +Here we outline the mathematical and implementation details involved in implementing the finite volume method (FVM). We assume that our partial differential equation (PDE) is given by + ```math \begin{equation} \label{eq:pde} \pdv{u(\vb x, t)}{t} + \div \vb q(\vb x, t, u) = S(\vb x, t, u), \quad \vb x \in \Omega, \end{equation} ``` + together with some boundary conditions or internal conditions that we discuss later. We also discuss steady-state problems and systems of PDEs of the form \eqref{eq:pde}. -# Interior Discretisation +# Interior Discretisation + +Let us start by discretising \eqref{eq:pde} inside $\Omega$. The first step in this discretisation is to compute a triangulation of $\Omega$, decomposing $\Omega$ into a collection of disjoint triangles $\{T_k\}$ so that -Let us start by discretising \eqref{eq:pde} inside $\Omega$. The first step in this discretisation is to compute a triangulation of $\Omega$, decomposing $\Omega$ into a collection of disjoint triangles $\{T_k\}$ so that ```math \Omega = \bigcup_k T_k. ``` + This triangulation is typically a _constrained Delaunay triangulation_, denoted $\mathcal T(\Omega)$, with appropriate care taken if $\Omega$ is multiply-connected; these triangulations can be computed using [DelaunayTriangulation.jl](https://github.com/JuliaGeometry/DelaunayTriangulation.jl). An example of such a domain $\Omega$ and its triangulation $\mathcal T(\Omega)$ is shown below, where we use a multiply-connected domain to emphasise that these details are not necessarily restricted to simple domains. ```@setup ex_tri @@ -53,7 +57,7 @@ fig #hide Key to the FVM are the _control volumes_, which are used to define volumes $\Omega_i$ around individual vertices $\vb x_i$ that we integrate the PDE over. To define these volumes, take $\vb x_i \in \Omega$, which is a vertex of $\mathcal T(\Omega)$, and take the set of triangles $\mathcal T_i = \{T_k\}$ that have $\vb x_i$ as a vertex. For each of these triangles $T_{k'} \in \mathcal T_i$, connect its centroid to the midpoints of the triangle's edges. Once this procedure is complete, we obtain a closed polygon around $\vb x_i$ that we denote by $\partial\Omega_i$ and whose interior defines the control volume $\Omega_i$. We show the result of this procedure, applied to the above domain, below, where we show the centroids in red and the control volume boundaries in blue. -```@setup ex_tri +```@setup ex_tri centroids = NTuple{2,Float64}[] linesegments = NTuple{2,Float64}[] for T in each_solid_triangle(tri) @@ -68,25 +72,25 @@ linesegments!(ax, linesegments, color=:blue) scatter!(ax, centroids, color=:red, markersize=8) ``` -```@example ex_tri +```@example ex_tri fig #hide ``` -Let us now establish some notation for referring to these control volumes, using the figure below +Let us now establish some notation for referring to these control volumes, using the figure below as a reference. -| Symbol | Description | Example | -| :--- | :--- | :--- | -| $\vb x_i$ | A vertex of $\mathcal T(\Omega)$ | The blue point below | -| $\Omega_i$ | The control volume around $\vb x_i$ | The green region below | -| $\partial\Omega_i$ | The boundary of $\Omega_i$ | The blue edges below | -| $V_i$ | The volume of $\Omega_i$ | The volume of the green region below | -| $\mathcal E_i$ | The set of edges of $\partial\Omega_i$ | The blue edges below | -| $\sigma$ | An edge $\sigma \in \mathcal E_i$ | The magenta edge below. Note that $\bigcup_{\sigma \in \mathcal E_i} \sigma = \partial\Omega_i$ | -| $\vb x_{\sigma}$ | The midpoint of $\sigma \in \mathcal E_i$ | The blue point below on $\sigma$ | -| $\hat{\vb n}_{\sigma}$ | The outward unit normal vector to $\sigma \in \mathcal E_i$ | The black arrow below | -| $\mathcal T_i$ | The set of triangles that have $\vb x_i$ as a vertex | The black triangles surrounding $\vb x_i$ below | -| $L_\sigma$ | The length of $\sigma \in \mathcal E_i$ | The length of the magenta edge below +| Symbol | Description | Example | +|:---------------------- |:----------------------------------------------------------- |:----------------------------------------------------------------------------------------------- | +| $\vb x_i$ | A vertex of $\mathcal T(\Omega)$ | The blue point below | +| $\Omega_i$ | The control volume around $\vb x_i$ | The green region below | +| $\partial\Omega_i$ | The boundary of $\Omega_i$ | The blue edges below | +| $V_i$ | The volume of $\Omega_i$ | The volume of the green region below | +| $\mathcal E_i$ | The set of edges of $\partial\Omega_i$ | The blue edges below | +| $\sigma$ | An edge $\sigma \in \mathcal E_i$ | The magenta edge below. Note that $\bigcup_{\sigma \in \mathcal E_i} \sigma = \partial\Omega_i$ | +| $\vb x_{\sigma}$ | The midpoint of $\sigma \in \mathcal E_i$ | The blue point below on $\sigma$ | +| $\hat{\vb n}_{\sigma}$ | The outward unit normal vector to $\sigma \in \mathcal E_i$ | The black arrow below | +| $\mathcal T_i$ | The set of triangles that have $\vb x_i$ as a vertex | The black triangles surrounding $\vb x_i$ below | +| $L_\sigma$ | The length of $\sigma \in \mathcal E_i$ | The length of the magenta edge below | ```@setup cv_notation using DelaunayTriangulation, CairoMakie, LinearAlgebra @@ -167,56 +171,71 @@ resize_to_layout!(fig) fig #hide ``` -## Discretising the PDE +## Discretising the PDE -Now that we have our concept of control volumes, we can discretise the PDE \eqref{eq:pde}. We do this +Now that we have our concept of control volumes, we can discretise the PDE \eqref{eq:pde}. We do this by considering each PDE inside each $\Omega_i$ and integrating. For a given control volume $\Omega_i$, we can integrate the PDE to give -```math + +```math \begin{equation}\label{eq:integratedi} \dv{t}\iint_{\Omega_i} u\dd{V} + \iint_{\Omega_i} \div\vb q \dd{V} = \iint_{\Omega_i} S \dd{V}. \end{equation} ``` + Using the divergence theorem, the second integral in \eqref{eq:integratedi} becomes + ```math \begin{equation}\label{eq:applieddthm} \iint_{\Omega_i} \div\vb q = \oint_{\partial\Omega_i} \vb q \vdot \vu n_\sigma \dd{s} = \sum_{\sigma \in \mathcal E_i} \int_\sigma \vb q \vdot \vu n_\sigma \dd{s}, \end{equation} ``` + where the last equality in \eqref{eq:applieddthm} follows from integrating over each individual line segment that defines $\partial\Omega_i$, which is simply $\mathcal E_i$. We then define the _control volume averages_, + ```math \begin{equation}\label{eq:averages} \bar u_i = \frac{1}{V_i}\iint_{\Omega_i} u\dd{V},\quad \bar S_i = \frac{1}{V_i}\iint_{\Omega_i} S\dd{V}, \end{equation} ``` + so that our integral formulation \eqref{eq:integratedi} becomes + ```math \begin{equation}\label{eq:intform} \dv{\bar u_i}{t} + \frac{1}{V_i}\sum_{\sigma\in\mathcal E_i}\int_\sigma \vb q \vdot \vu n_\sigma \dd{s} = \bar S_i. \end{equation} ``` + Note that \eqref{eq:intform} is still an exact expression. To proceed, we need to approximate the integrals $\int_\sigma \vb q \vdot \vu n_\sigma\dd{s}$. To accomplish this, we use a midpoint rule, writing + ```math \begin{equation}\label{eq:midpt_rule} \int_\sigma \vb q \vdot \vu n_\sigma \dd{s} \approx \left[\vb q(\vb x_\sigma, t, u(\vb x_\sigma, t))\vdot \vu n_\sigma\right]L_\sigma. \end{equation} ``` + Then, replacing the control volume averages with their value at $\vb x_i$, we obtain the following approximation: + ```math \begin{equation}\label{eq:nextapprox} \dv{u_i}{t} + \frac{1}{V_i}\sum_{\sigma\in\mathcal E_i} \left[\vb q(\vb x_\sigma, t, u(\vb x_\sigma, t)) \vdot \vu n_\sigma\right]L_\sigma = S_i, \end{equation} ``` -where $u_i = u(\vb x_i, t)$ and $S_i = S(\vb x_i, t)$. + +where $u_i = u(\vb x_i, t)$ and $S_i = S(\vb x_i, t)$. The final step in this part of the approximation is the evaluation of $\vb q(\vb x_\sigma, t, u(\vb x_\sigma, t))$. To deal with this function, consider some $T_k \in \mathcal T_i$ so that $\vb x_\sigma$ is inside $T_k$. We use a linear shape function inside $T_k$ to approximate $u$, writing + ```math \begin{equation}\label{eq:shape} u(\vb x, t) = \alpha_kx + \beta_ky + \gamma_k, \quad \vb x \in T_k, \end{equation} ``` + where we have suppressed the dependence of the coefficients $(\alpha_k, \beta_k,\gamma_k)$ on $t$. The vertices of $T_k$ are given by $v_{k1}, v_{k2}, v_{k3}$ with corresponding coordinates $\vb x_{k1}, \vb x_{k2}, \vb x_{k3}$, respectively. We can then solve for the coefficients in \eqref{eq:shape} by requiring that $u$ is equal to the values at the vertices of $T_k$, giving the system of equations + ```math \begin{equation}\label{eq:near_cramer} \begin{aligned} @@ -226,19 +245,25 @@ u(\vb x_{k3}, t) &= \alpha_kx_{k3} + \beta_ky_{k3} + \gamma_k, \end{aligned} \end{equation} ``` + where $\vb x_{ki} = (x_{ki}, y_{ki})^{\mkern-1.5mu\mathsf{T}}$. Note that the values on the left-hand side of \eqref{eq:near_cramer} are all known from either the initial condition or the previous time-step. Using Cramer's rule, we define + ```math \begin{equation}\label{eq:shape_coeffs} \vb S_k = \frac{1}{\Delta_k}\begin{bmatrix} y_{k2}-y_{k3} & y_{k3}-y_{k1} & y_{k1}-y_{k2} \\ x_{k3} - x_{k2} & x_{k1}-x_{k3}&x_{k2}-x_{k1} \\ x_{k2}y_{k3}-x_{k3}y_{k2} & x_{k3}y_{k1}-x_{k1}y_{k3}&x_{k1}y_{k2}-x_{k2}y_{k1} \end{bmatrix}, \end{equation} ``` + where + ```math \begin{equation}\label{eq:deltak} \Delta_k = x_{k1}y_{k2}-x_{k2}y_{k1}-x_{k1}y_{k3}+x_{k3}y_{k1}+x_{k2}y_{k3}-x_{k3}y_{k2}, \end{equation} ``` + and thus we obtain + ```math \begin{equation}\label{eq:shapecoeffvals} \begin{aligned} @@ -248,17 +273,21 @@ and thus we obtain \end{aligned} \end{equation} ``` + where $u_{ki} = u(\vb x_{ki}, t)$ and $s_{k,ij}$ are the elements of $\vb S_k$. With \eqref{eq:shape} and \eqref{eq:shapecoeffvals}, we can approximate $\vb q(\vb x_\sigma, t, u(\vb x_\sigma, t))$ and thus obtain the approximation + ```math \begin{equation}\label{eq:interiorapproximation} \dv{u_i}{t} + \frac{1}{V_i}\sum_{\sigma\in\mathcal E_i} \left[\vb q\left(\vb x_\sigma, t, \alpha_{k(\sigma)}x_\sigma + \beta_{k(\sigma)}y_\sigma + \gamma_{k(\sigma)}\right)\vdot \vu n_\sigma\right]L_\sigma = S_i, \end{equation} ``` + where $k(\sigma)$ is the index of the triangle that contains $\vb x_\sigma$. This linear shape function also allows us to compute gradients like $\grad u(\vb x_\sigma, t)$, since $\grad u(\vb x_\sigma, t) = (\alpha_{k(\sigma)}, \beta_{k(\sigma)})^{\mkern-1.5mu\mathsf{T}}$. # Boundary Conditions Let us now discuss how boundary conditions (BCs) are handled. We assume that BCs take on any of the following forms: + ```math \begin{align} \vb q(\vb x, t, u) \vdot \vu n_\sigma &= a(\vb x, t, u) & \vb x \in \mathcal B \subseteq \partial\Omega, \label{eq:neumann} \\ @@ -266,30 +295,35 @@ Let us now discuss how boundary conditions (BCs) are handled. We assume that BCs u(\vb x, t) &= a(\vb x, t, u) & \vb x \in \mathcal B \subseteq \partial\Omega, \label{eq:dirichlet} \end{align} ``` -where the functions $a$ are user-provided functions. The conditions \eqref{eq:neumann}--\eqref{eq:dirichlet} are called _Neumann_, _time-dependent Dirichlet_, and _Dirichlet_, respectively. We discuss how we handle incompatible BCs below, and then how each of these three types are implemented. + +where the functions $a$ are user-provided functions. The conditions \eqref{eq:neumann}--\eqref{eq:dirichlet} are called _Neumann_, _time-dependent Dirichlet_, and _Dirichlet_, respectively. We discuss how we handle incompatible BCs below, and then how each of these three types are implemented. ## Dirichlet boundary conditions When we have a Dirichlet BC of the form \eqref{eq:dirichlet}, the implementation is simple: Rather than using \eqref{eq:interiorapproximation}, we instead leave $\mathrm du_i/\mathrm dt = 0$ and update the value of $u_i$ with $a(\vb x_i, t, u_i)$ at the end of the iteration using a callback; note that the expression $u_i = a(\vb x_i, t, u_i)$ is __not__ an implicit equation for $u_i$, rather it is simply a reassignment of $u_i$ to $a(\vb x_i, t, u_i)$, i.e. $u_i \leftarrow a(\vb x_i, t, u_i)$. -## Time-dependent Dirichlet boundary conditions +## Time-dependent Dirichlet boundary conditions -For a time-dependent Dirichlet BC of the form \eqref{eq:dudtdirichlet}, the implementation is again simple: Rather than using \eqref{eq:interiorapproximation}, simply compute $\mathrm du_i/\mathrm dt = a(\vb x_i, t, u_i)$ instead. +For a time-dependent Dirichlet BC of the form \eqref{eq:dudtdirichlet}, the implementation is again simple: Rather than using \eqref{eq:interiorapproximation}, simply compute $\mathrm du_i/\mathrm dt = a(\vb x_i, t, u_i)$ instead. ## Neumann boundary conditions Neumann boundary conditions \eqref{eq:neumann} are the most complex out of the three. Let us return to our integral formulation \eqref{eq:intform}. Let $\mathcal E_i^n$ be the set of edges in $\mathcal E_i$ that have a Neumann BC associated with them, and $\mathcal E_i^c = \mathcal E_i \setminus \mathcal E_i^n$. Then, also using \eqref{eq:interiorapproximation}, in the case of \eqref{eq:neumann} we can write + ```math \begin{equation}\label{eq:neumanndecomp} \dv{u_i}{t} + \frac{1}{V_i}\sum_{\sigma\in \mathcal E_i^c} \left[\vb q(\vb x_\sigma, t, \alpha_{k(\sigma)}x_\sigma + \beta_{k(\sigma)}y_\sigma + \gamma_{k(\sigma)}) \vdot \vu n_\sigma\right]L_\sigma + \frac{1}{V_i}\sum_{\sigma\in\mathcal E_i^n} \int_{\sigma} a_{\sigma}(\vb x, t, u)\dd{s} = S_i, \end{equation} ``` + where $a_\sigma$ is the BC function associated with $\sigma$. This integral is then approximated using a midpoint rule as done previously, giving + ```math \begin{equation}\label{eq:neumanndecompapprox} \dv{u_i}{t} + \frac{1}{V_i}\sum_{\sigma\in \mathcal E_i^c} \left[\vb q(\vb x_\sigma, t, u(\vb x_\sigma, t)) \vdot \vu n_\sigma\right]L_\sigma + \frac{1}{V_i}\sum_{\sigma\in\mathcal E_i^n} \left[a_{\sigma}(\vb x_\sigma, t, u(\vb x_\sigma, t))\right]L_\sigma = S_i, \end{equation} ``` + where $u(\vb x_\sigma, t) = \alpha_{k(\sigma)}x_\sigma + \beta_{k(\sigma)}y_\sigma + \gamma_{k(\sigma)}$. # Internal Conditions @@ -297,8 +331,7 @@ where $u(\vb x_\sigma, t) = \alpha_{k(\sigma)}x_\sigma + \beta_{k(\sigma)}y_\sig We also allow for specifying internal conditions, meaning conditions of the form \eqref{eq:neumann}--\eqref{eq:dirichlet} that are applied away from the boundary. We do not currently allow for internal Neumann conditions directly.[^1] These conditions are handled in the same way as BCs, except that the user is to provide them per-vertex rather than per-edge. [^1]: This is a technical limitation due to how the control volumes are defined. For vertices away from the boundary, the control volume edges do not lie along any of the triangle's edges, which is where we would like to impose Neumann conditions. - -# Putting Everything Together +# Putting Everything Together We have now specified how we discretise the PDE itself, and how we handle both boundary and internal conditions. The remaining task is to actually discuss how we compute $\mathrm du_i/\mathrm dt$. As written, \eqref{eq:interiorapproximation} indicates that we loop over each vertex and, within each vertex, loop over each edge of its control volume. On average, $|\mathcal E_i^c| = 12$ (since, on average, a point in a Delaunay triangulation has six neighbours), and so computing $\mathrm du_i/\mathrm dt$ for each $i$ requires $\mathcal O(12n)$ loop iterates and many repeated computations (since each control volume edge appears in another control volume), where $n$ is the number of points in the mesh. An alternative approach is to instead loop over each triangle in $\mathcal T(\Omega)$ and to then loop over each edge, adding the contributions from each to the associated vertices. This instead requires $\mathcal O(3|\mathcal T|)$ loop iterates, where $|\mathcal T|$ is the number of triangles in $\mathcal T(\Omega)$, and we instead only need to compute the relevant quantities for each control volume edge a single time; note that $|\mathcal T| = \mathcal O(n)$ by Euler's formula. This is the approach we take in our implementation. @@ -387,9 +420,10 @@ resize_to_layout!(fig) fig #hide ``` -We denote the triangle in blue by $T$, and refer to the blue, red, and green vertices by $v_b$, $v_r$, and $v_g$, respectively. The relevant edges that contribute to $\mathrm du_b/\mathrm dt$, $\mathrm du_r/\mathrm dt$, and $\mathrm du_g/\mathrm dt$ are $\sigma_{br}$, $\sigma_{rg}$, and $\sigma_{gb}$, as annotated above. In particular, $\sigma_{br}$ contributes to both $\mathrm du_b/\mathrm dt$ and $\mathrm du_r/\mathrm dt$, $\sigma_{rg}$ contributes to both $\mathrm du_r/\mathrm dt$ and $\mathrm du_g/\mathrm dt$, and $\sigma_{gb}$ contributes to both $\mathrm du_g/\mathrm dt$ and $\mathrm du_b/\mathrm dt$. +We denote the triangle in blue by $T$, and refer to the blue, red, and green vertices by $v_b$, $v_r$, and $v_g$, respectively. The relevant edges that contribute to $\mathrm du_b/\mathrm dt$, $\mathrm du_r/\mathrm dt$, and $\mathrm du_g/\mathrm dt$ are $\sigma_{br}$, $\sigma_{rg}$, and $\sigma_{gb}$, as annotated above. In particular, $\sigma_{br}$ contributes to both $\mathrm du_b/\mathrm dt$ and $\mathrm du_r/\mathrm dt$, $\sigma_{rg}$ contributes to both $\mathrm du_r/\mathrm dt$ and $\mathrm du_g/\mathrm dt$, and $\sigma_{gb}$ contributes to both $\mathrm du_g/\mathrm dt$ and $\mathrm du_b/\mathrm dt$. Let us focus on $u_b$ and $u_r$. The contribution from $e_{br}$ to $\mathrm du_b/\mathrm dt$ and $\mathrm du_r/\mathrm dt$ is given by: + ```math \begin{equation}\label{eq:triangleupdate} \begin{aligned} @@ -398,20 +432,24 @@ Let us focus on $u_b$ and $u_r$. The contribution from $e_{br}$ to $\mathrm du_b \end{aligned} \end{equation} ``` -where + +where + ```math Q = \left[\vb q\left(\vb x_{br}, t, \alpha x_{br} + \beta y_{br} + \gamma\right) \vdot \vu n_{br}\right]L_{br}, ``` -and $\vb x_{br}$ is the midpoint of $e_{br}$; $\vu n_{br}$ is the unit normal vector to the edge $\sigma_{br} = \overrightarrow{\vb x_{br}\vb x_T}$, where $\vb x_T$ is the centroid of $T$ which should only be computed once for the current $T$, which should point away from $\vb x_b$ but towards $\vb x_r$; $L_{br} = \|\vb x_T - \vb x_{br}\|$, and $\alpha,\beta,\gamma$ are computed from \eqref{eq:shapecoeffvals} using the vertices of $T$. Notice that \eqref{eq:triangleupdate} uses a minus sign for $\mathrm du_b/\mathrm dt$ and a plus sign for $\mathrm du_r/\mathrm dt$, because we have brought the sum in \eqref{eq:interiorapproximation} to the right-hand side of the equation. + +and $\vb x_{br}$ is the midpoint of $e_{br}$; $\vu n_{br}$ is the unit normal vector to the edge $\sigma_{br} = \overrightarrow{\vb x_{br}\vb x_T}$, where $\vb x_T$ is the centroid of $T$ which should only be computed once for the current $T$, which should point away from $\vb x_b$ but towards $\vb x_r$; $L_{br} = \|\vb x_T - \vb x_{br}\|$, and $\alpha,\beta,\gamma$ are computed from \eqref{eq:shapecoeffvals} using the vertices of $T$. Notice that \eqref{eq:triangleupdate} uses a minus sign for $\mathrm du_b/\mathrm dt$ and a plus sign for $\mathrm du_r/\mathrm dt$, because we have brought the sum in \eqref{eq:interiorapproximation} to the right-hand side of the equation. When we apply the procedure above to each triangle, we will have computed the contribution from each edge to each vertex - almost. The only issue is with boundary triangles, where the edges that lie on the boundary will not be iterated over as they not of the form $\overrightarrow{\vb x_{br}\vb x_T}$ (i.e., they are not connected to a centroid). There are two ways to handle this: -1. For each triangle looped over, also check if it is a boundary triangle and then consider its boundary edges. -2. After looping over all triangles, loop over all boundary edges to pick up the missing contributions. -The second approach is preferable, as we don't need to worry about needless checks for boundary triangles, the number of boundary edges it has, etc. + + 1. For each triangle looped over, also check if it is a boundary triangle and then consider its boundary edges. + 2. After looping over all triangles, loop over all boundary edges to pick up the missing contributions. + The second approach is preferable, as we don't need to worry about needless checks for boundary triangles, the number of boundary edges it has, etc. To understand how to pick up contributions from a single edge, consider the figure below which shows some control volumes in the corner of a domain: -```@setup ex_focus +```@setup ex_focus tri = triangulate_rectangle(0, 1, 0, 1, 10, 10, single_boundary = false) add_ghost_triangles!(tri) linesegs = NTuple{2, Float64}[] @@ -443,6 +481,7 @@ fig #hide ``` Consider the edge $e_{ij}$ shown in red. There two control volumes that lie on $e_{ij}$, the one for $v_i$ and the other for $v_j$. We denote the midpoint of $e_{ij}$ by $\vb x_{ij} = (\vb x_i + \vb x_j)/2$, so that the two control volume edges are $\overrightarrow{\vb x_i\vb x_{ij}}$ and $\overrightarrow{\vb x_{ij}\vb x_j}$ for $v_i$ and $v_j$, respectively. The contributions from the flux over each edge gives + ```math \begin{equation}\label{eq:bndedgecontrbi} \begin{aligned} @@ -451,7 +490,9 @@ Consider the edge $e_{ij}$ shown in red. There two control volumes that lie on $ \end{aligned} \end{equation} ``` -where + +where + ```math \begin{equation}\label{eq:bndedgecontrbiflux} \begin{aligned} @@ -460,15 +501,18 @@ Q_j &= \left[\vb q\left(\vb m_j, t, \alpha_{ij} m_{jx} + \beta_{ij} m_{jy} + \ga \end{aligned} \end{equation} ``` + where $\vb m_i = (\vb x_i + \vb x_{ij})/2 = (m_{ix}, m_{iy})^{\mkern-1.5mu\mathsf{T}}$, $\vb m_j = (\vb x_{ij} + \vb x_j)/2 = (m_{jx}, m_{jy})^{\mkern-1.5mu\mathsf{T}}$, $\vb n_{ij}$ is the outward unit normal vector to $e_{ij}$, $L_i = \|\vb x_{ij} - \vb x_i\|$, $L_j = \|\vb x_j - \vb x_{ij}\|$, and $\alpha_{ij}, \beta_{ij}, \gamma_{ij}$ are computed from \eqref{eq:shapecoeffvals} using the vertices of the triangle that contains $e_{ij}$. If there is a Neumann boundary condition on $e_{ij}$, \eqref{eq:bndedgecontrbiflux} uses the boundary condition functions for computing the $\vb q \vdot \vu n$ terms. -Now that we have looped over all triangles and also over all boundary edges, the final values for each $\mathrm du_i/\mathrm dt$ is given by +Now that we have looped over all triangles and also over all boundary edges, the final values for each $\mathrm du_i/\mathrm dt$ is given by + ```math \dv{u_i}{t} \leftarrow \frac{1}{V_i}\dv{u_i}{t} + S(\vb x_i, t, u_i). ``` + Of course, if there is a Dirichlet boundary condition at $u_i$ we set $\mathrm du_i/\mathrm dt = 0$, and if there is a boundary condition on $\mathrm du_i/\mathrm dt$ we use that boundary condition instead. -# Systems of Equations +# Systems of Equations We also provide support for systems of PDEs that take the form @@ -535,4 +579,4 @@ We provide support for steady-state problems, in which case \eqref{eq:pde} (and \end{equation} ``` -This is solved in exactly the same way, except rootfinding is used and there is no timestepping. \ No newline at end of file +This is solved in exactly the same way, except rootfinding is used and there is no timestepping. diff --git a/docs/src/tutorials/diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.md b/docs/src/tutorials/diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.md index fdae678..a00b355 100644 --- a/docs/src/tutorials/diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.md +++ b/docs/src/tutorials/diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.md @@ -9,8 +9,10 @@ nothing #hide ```` # Diffusion Equation in a Wedge with Mixed Boundary Conditions + In this example, we consider a diffusion equation on a wedge with angle $\alpha$ and mixed boundary conditions: + ```math \begin{equation*} \begin{aligned} @@ -22,6 +24,7 @@ u(r, \theta, 0) &= f(r,\theta) & 0 1 - sqrt(x^2 + y^2) D = (x, y, t, u, p) -> one(u) initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 0.1 -prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) +prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) ```` If you did want to use the flux formulation, you would need to provide @@ -107,7 +110,7 @@ has the best performance for these problems. ````@example diffusion_equation_in_a_wedge_with_mixed_boundary_conditions using OrdinaryDiffEq, LinearSolve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=0.01, parallel=Val(false)) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.01, parallel = Val(false)) ind = findall(DelaunayTriangulation.each_point_index(tri)) do i #hide !DelaunayTriangulation.has_vertex(tri, i) #hide end #hide @@ -118,14 +121,14 @@ sol |> tc #hide ````@example diffusion_equation_in_a_wedge_with_mixed_boundary_conditions using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) local ax - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.01:1, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:0.01:1, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) @@ -133,6 +136,7 @@ fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.jl). @@ -147,7 +151,7 @@ upper_edge = [3, 1] boundary_nodes = [bottom_edge, [arc], upper_edge] tri = triangulate(points; boundary_nodes) A = get_area(tri) -refine!(tri; max_area=1e-4A) +refine!(tri; max_area = 1e-4A) mesh = FVMGeometry(tri) using CairoMakie @@ -164,29 +168,28 @@ f = (x, y) -> 1 - sqrt(x^2 + y^2) D = (x, y, t, u, p) -> one(u) initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 0.1 -prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) +prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) flux = (x, y, t, α, β, γ, p) -> (-α, -β) using OrdinaryDiffEq, LinearSolve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=0.01, parallel=Val(false)) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.01, parallel = Val(false)) using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) local ax - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.01:1, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:0.01:1, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/diffusion_equation_on_a_square_plate.md b/docs/src/tutorials/diffusion_equation_on_a_square_plate.md index 83ccd9b..587eb3b 100644 --- a/docs/src/tutorials/diffusion_equation_on_a_square_plate.md +++ b/docs/src/tutorials/diffusion_equation_on_a_square_plate.md @@ -9,7 +9,9 @@ nothing #hide ```` # Diffusion Equation on a Square Plate + This tutorial considers a diffusion equation on a square plate: + ```math \begin{equation*} \begin{aligned} @@ -19,17 +21,20 @@ u(\vb x, 0) &= f(\vb x) & \vb x \in \Omega, \end{aligned} \end{equation*} ``` + where $\Omega = [0, 2]^2$ and + ```math f(x, y) = \begin{cases} 50 & y \leq 1, \\ 0 & y > 1. \end{cases} ``` + To solve this problem, the first step is to define the mesh. ````@example diffusion_equation_on_a_square_plate using FiniteVolumeMethod, DelaunayTriangulation a, b, c, d = 0.0, 2.0, 0.0, 2.0 nx, ny = 50, 50 -tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary=true) +tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary = true) mesh = FVMGeometry(tri) ```` @@ -60,7 +65,7 @@ We can now define the problem: ````@example diffusion_equation_on_a_square_plate final_time = 0.5 -prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) +prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) ```` Note that in `prob`, it is not a diffusion function that is used but instead it is a flux function: @@ -81,21 +86,21 @@ of using `Tsit5()`.) ````@example diffusion_equation_on_a_square_plate using OrdinaryDiffEq -sol = solve(prob, Tsit5(), saveat=0.05) +sol = solve(prob, Tsit5(), saveat = 0.05) sol |> tc #hide ```` To visualise the solution, we can use `tricontourf!` from Makie.jl. ````@example diffusion_equation_on_a_square_plate -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) local ax - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:5:50, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:5:50, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) @@ -103,6 +108,7 @@ fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/diffusion_equation_on_a_square_plate.jl). @@ -110,7 +116,7 @@ You can view the source code for this file [here](https://github.com/SciML/Finit using FiniteVolumeMethod, DelaunayTriangulation a, b, c, d = 0.0, 2.0, 0.0, 2.0 nx, ny = 50, 50 -tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary=true) +tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary = true) mesh = FVMGeometry(tri) using CairoMakie @@ -125,28 +131,27 @@ initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri) D = (x, y, t, u, p) -> 1 / 9 final_time = 0.5 -prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) +prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) prob.flux_function using OrdinaryDiffEq -sol = solve(prob, Tsit5(), saveat=0.05) +sol = solve(prob, Tsit5(), saveat = 0.05) -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) local ax - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:5:50, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:5:50, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/diffusion_equation_on_an_annulus.md b/docs/src/tutorials/diffusion_equation_on_an_annulus.md index 1cd351c..eb1b922 100644 --- a/docs/src/tutorials/diffusion_equation_on_an_annulus.md +++ b/docs/src/tutorials/diffusion_equation_on_an_annulus.md @@ -9,8 +9,10 @@ nothing #hide ```` # Diffusion Equation on an Annulus + In this tutorial, we consider a diffusion equation on an annulus: + ```math \begin{equation} \begin{aligned} @@ -21,25 +23,28 @@ u(\vb x, t) &= u_0(\vb x), \end{aligned} \end{equation} ``` + demonstrating how we can solve PDEs over multiply-connected domains. Here, $\mathcal D(0, r)$ is a circle of radius $r$ centred at the origin, $\Omega$ is the annulus between $\mathcal D(0,0.2)$ and $\mathcal D(0, 1)$, $c(t) = 50[1-\mathrm{e}^{-t/2}]$, and + ```math u_0(x) = 10\mathrm{e}^{-25\left[\left(x+\frac12\right)^2+\left(y+\frac12\right)^2\right]} - 10\mathrm{e}^{-45\left[\left(x-\frac12\right)^2+\left(y-\frac12\right)^2\right]} - 5\mathrm{e}^{-50\left[\left(x+\frac{3}{10}\right)^2+\left(y+\frac12\right)^2\right]}. ``` + For the mesh, we use two `CircularArc`s to define the annulus. ````@example diffusion_equation_on_an_annulus using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie R₁, R₂ = 0.2, 1.0 -inner = CircularArc((R₁, 0.0), (R₁, 0.0), (0.0, 0.0), positive=false) +inner = CircularArc((R₁, 0.0), (R₁, 0.0), (0.0, 0.0), positive = false) outer = CircularArc((R₂, 0.0), (R₂, 0.0), (0.0, 0.0)) boundary_nodes = [[[outer]], [[inner]]] -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) A = get_area(tri) -refine!(tri; max_area=1e-4A) +refine!(tri; max_area = 1e-4A) triplot(tri) ```` @@ -60,8 +65,8 @@ ax = Axis(fig[1, 1]) outer = [get_point(tri, i) for i in get_neighbours(tri, -1)] inner = [get_point(tri, i) for i in get_neighbours(tri, -2)] triplot!(ax, tri) -scatter!(ax, outer, color=:red) -scatter!(ax, inner, color=:blue) +scatter!(ax, outer, color = :red) +scatter!(ax, inner, color = :blue) fig ```` @@ -77,11 +82,15 @@ BCs = BoundaryConditions(mesh, (outer_bc, inner_bc), types) Finally, let's define the problem and solve it. ````@example diffusion_equation_on_an_annulus -initial_condition_f = (x, y) -> begin - 10 * exp(-25 * ((x + 0.5) * (x + 0.5) + (y + 0.5) * (y + 0.5))) - 5 * exp(-50 * ((x + 0.3) * (x + 0.3) + (y + 0.5) * (y + 0.5))) - 10 * exp(-45 * ((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5))) +initial_condition_f = (x, + y) -> begin + 10 * exp(-25 * ((x + 0.5) * (x + 0.5) + (y + 0.5) * (y + 0.5))) - + 5 * exp(-50 * ((x + 0.3) * (x + 0.3) + (y + 0.5) * (y + 0.5))) - + 10 * exp(-45 * ((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5))) end diffusion_function = (x, y, t, u, p) -> one(u) -initial_condition = [initial_condition_f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] +initial_condition = [initial_condition_f(x, y) + for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 2.0 prob = FVMProblem(mesh, BCs; diffusion_function, @@ -91,19 +100,19 @@ prob = FVMProblem(mesh, BCs; ````@example diffusion_equation_on_an_annulus using OrdinaryDiffEq, LinearSolve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=0.2) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.2) sol |> tc #hide ```` ````@example diffusion_equation_on_an_annulus -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) local ax - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=-10:2:40, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = -10:2:40, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) @@ -138,7 +147,7 @@ u = sol.u[6] last_triangle = Ref((1, 1, 1)) for (j, _y) in enumerate(y) for (i, _x) in enumerate(x) - T = jump_and_march(tri, (_x, _y), try_points=last_triangle[]) + T = jump_and_march(tri, (_x, _y), try_points = last_triangle[]) last_triangle[] = triangle_vertices(T) # used to accelerate jump_and_march, since the points we're looking for are close to each other if DelaunayTriangulation.is_ghost_triangle(T) # don't extrapolate interp_vals[i, j] = NaN @@ -147,7 +156,7 @@ for (j, _y) in enumerate(y) end end end -fig, ax, sc = contourf(x, y, interp_vals, levels=-10:2:40, colormap=:matter) +fig, ax, sc = contourf(x, y, interp_vals, levels = -10:2:40, colormap = :matter) fig ```` @@ -158,17 +167,19 @@ highlight some complications. using NaturalNeighbours _x = vec([x for x in x, y in y]) # NaturalNeighbours.jl needs vector data _y = vec([y for x in x, y in y]) -itp = interpolate(tri, u, derivatives=true) +itp = interpolate(tri, u, derivatives = true) itp |> tc #hide ```` ````@example diffusion_equation_on_an_annulus -itp_vals = itp(_x, _y; method=Farin()) +itp_vals = itp(_x, _y; method = Farin()) itp_vals |> tc #hide ```` ````@example diffusion_equation_on_an_annulus -fig, ax, sc = contourf(x, y, reshape(itp_vals, length(x), length(y)), colormap=:matter, levels=-10:2:40) +fig, ax, +sc = contourf( + x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40) fig ```` @@ -176,29 +187,32 @@ The issue here is that the interpolant is trying to extrapolate inside the hole outside of the annulus. To avoid this, you need to pass `project=false`. ````@example diffusion_equation_on_an_annulus -itp_vals = itp(_x, _y; method=Farin(), project=false) +itp_vals = itp(_x, _y; method = Farin(), project = false) itp_vals |> tc #hide ```` ````@example diffusion_equation_on_an_annulus -fig, ax, sc = contourf(x, y, reshape(itp_vals, length(x), length(y)), colormap=:matter, levels=-10:2:40) +fig, ax, +sc = contourf( + x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40) fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/diffusion_equation_on_an_annulus.jl). ```julia using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie R₁, R₂ = 0.2, 1.0 -inner = CircularArc((R₁, 0.0), (R₁, 0.0), (0.0, 0.0), positive=false) +inner = CircularArc((R₁, 0.0), (R₁, 0.0), (0.0, 0.0), positive = false) outer = CircularArc((R₂, 0.0), (R₂, 0.0), (0.0, 0.0)) boundary_nodes = [[[outer]], [[inner]]] -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) A = get_area(tri) -refine!(tri; max_area=1e-4A) +refine!(tri; max_area = 1e-4A) triplot(tri) mesh = FVMGeometry(tri) @@ -208,8 +222,8 @@ ax = Axis(fig[1, 1]) outer = [get_point(tri, i) for i in get_neighbours(tri, -1)] inner = [get_point(tri, i) for i in get_neighbours(tri, -2)] triplot!(ax, tri) -scatter!(ax, outer, color=:red) -scatter!(ax, inner, color=:blue) +scatter!(ax, outer, color = :red) +scatter!(ax, inner, color = :blue) fig outer_bc = (x, y, t, u, p) -> zero(u) @@ -217,11 +231,15 @@ inner_bc = (x, y, t, u, p) -> oftype(u, 50(1 - exp(-t / 2))) types = (Neumann, Dirichlet) BCs = BoundaryConditions(mesh, (outer_bc, inner_bc), types) -initial_condition_f = (x, y) -> begin - 10 * exp(-25 * ((x + 0.5) * (x + 0.5) + (y + 0.5) * (y + 0.5))) - 5 * exp(-50 * ((x + 0.3) * (x + 0.3) + (y + 0.5) * (y + 0.5))) - 10 * exp(-45 * ((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5))) +initial_condition_f = (x, + y) -> begin + 10 * exp(-25 * ((x + 0.5) * (x + 0.5) + (y + 0.5) * (y + 0.5))) - + 5 * exp(-50 * ((x + 0.3) * (x + 0.3) + (y + 0.5) * (y + 0.5))) - + 10 * exp(-45 * ((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5))) end diffusion_function = (x, y, t, u, p) -> one(u) -initial_condition = [initial_condition_f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] +initial_condition = [initial_condition_f(x, y) + for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 2.0 prob = FVMProblem(mesh, BCs; diffusion_function, @@ -229,16 +247,16 @@ prob = FVMProblem(mesh, BCs; initial_condition) using OrdinaryDiffEq, LinearSolve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=0.2) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.2) -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) local ax - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=-10:2:40, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = -10:2:40, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) @@ -253,7 +271,7 @@ u = sol.u[6] last_triangle = Ref((1, 1, 1)) for (j, _y) in enumerate(y) for (i, _x) in enumerate(x) - T = jump_and_march(tri, (_x, _y), try_points=last_triangle[]) + T = jump_and_march(tri, (_x, _y), try_points = last_triangle[]) last_triangle[] = triangle_vertices(T) # used to accelerate jump_and_march, since the points we're looking for are close to each other if DelaunayTriangulation.is_ghost_triangle(T) # don't extrapolate interp_vals[i, j] = NaN @@ -262,26 +280,29 @@ for (j, _y) in enumerate(y) end end end -fig, ax, sc = contourf(x, y, interp_vals, levels=-10:2:40, colormap=:matter) +fig, ax, sc = contourf(x, y, interp_vals, levels = -10:2:40, colormap = :matter) fig using NaturalNeighbours _x = vec([x for x in x, y in y]) # NaturalNeighbours.jl needs vector data _y = vec([y for x in x, y in y]) -itp = interpolate(tri, u, derivatives=true) +itp = interpolate(tri, u, derivatives = true) -itp_vals = itp(_x, _y; method=Farin()) +itp_vals = itp(_x, _y; method = Farin()) -fig, ax, sc = contourf(x, y, reshape(itp_vals, length(x), length(y)), colormap=:matter, levels=-10:2:40) +fig, ax, +sc = contourf( + x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40) fig -itp_vals = itp(_x, _y; method=Farin(), project=false) +itp_vals = itp(_x, _y; method = Farin(), project = false) -fig, ax, sc = contourf(x, y, reshape(itp_vals, length(x), length(y)), colormap=:matter, levels=-10:2:40) +fig, ax, +sc = contourf( + x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.md b/docs/src/tutorials/equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.md index db60710..6bc597d 100644 --- a/docs/src/tutorials/equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.md +++ b/docs/src/tutorials/equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.md @@ -9,7 +9,9 @@ nothing #hide ```` # Equilibrium Temperature Distribution with Mixed Boundary Conditions and using EnsembleProblems + For this tutorial, we consider the following problem: + ```math \begin{equation} \begin{aligned} @@ -21,6 +23,7 @@ T &= 70 & \vb x \in \Gamma_4. \\ \end{aligned} \end{equation} ``` + This domain $\Omega$ with boundary $\partial\Omega=\Gamma_1\cup\Gamma_2\cup\Gamma_3\cup\Gamma_4$ is shown below. ````@example equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems @@ -32,17 +35,17 @@ G = (0.05, 0.03) #hide C = (0.06, 0.03) #hide D = (0.06, 0.0) #hide E = (0.0, 0.0) #hide -fig = Figure(fontsize=33) #hide -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") #hide -lines!(ax, [A, E, D], color=:red, linewidth=5) #hide -lines!(ax, [B, F, G, C], color=:blue, linewidth=5) #hide -lines!(ax, [C, D], color=:black, linewidth=5) #hide -lines!(ax, [A, B], color=:magenta, linewidth=5) #hide -text!(ax, [(0.03, 0.001)], text=L"\Gamma_1", fontsize=44) #hide -text!(ax, [(0.055, 0.01)], text=L"\Gamma_2", fontsize=44) #hide -text!(ax, [(0.04, 0.04)], text=L"\Gamma_3", fontsize=44) #hide -text!(ax, [(0.015, 0.053)], text=L"\Gamma_4", fontsize=44) #hide -text!(ax, [(0.001, 0.03)], text=L"\Gamma_1", fontsize=44) #hide +fig = Figure(fontsize = 33) #hide +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") #hide +lines!(ax, [A, E, D], color = :red, linewidth = 5) #hide +lines!(ax, [B, F, G, C], color = :blue, linewidth = 5) #hide +lines!(ax, [C, D], color = :black, linewidth = 5) #hide +lines!(ax, [A, B], color = :magenta, linewidth = 5) #hide +text!(ax, [(0.03, 0.001)], text = L"\Gamma_1", fontsize = 44) #hide +text!(ax, [(0.055, 0.01)], text = L"\Gamma_2", fontsize = 44) #hide +text!(ax, [(0.04, 0.04)], text = L"\Gamma_3", fontsize = 44) #hide +text!(ax, [(0.015, 0.053)], text = L"\Gamma_4", fontsize = 44) #hide +text!(ax, [(0.001, 0.03)], text = L"\Gamma_1", fontsize = 44) #hide fig #hide ```` @@ -50,7 +53,12 @@ Let us start by defining the mesh. ````@example equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie -A, B, C, D, E, F, G = (0.0, 0.0), +A, B, +C, +D, +E, +F, +G = (0.0, 0.0), (0.06, 0.0), (0.06, 0.03), (0.05, 0.03), @@ -64,7 +72,7 @@ bn4 = [F, G] bn = [bn1, bn2, bn3, bn4] boundary_nodes, points = convert_boundary_points_to_indices(bn) tri = triangulate(points; boundary_nodes) -refine!(tri; max_area=1e-4get_area(tri)) +refine!(tri; max_area = 1e-4get_area(tri)) triplot(tri) ```` @@ -82,9 +90,9 @@ h = 20.0 T∞ = 20.0 bc1 = (x, y, t, T, p) -> zero(T) # ∇T⋅n=0 bc2 = (x, y, t, T, p) -> oftype(T, 40.0) # T=40 -bc3 = (x, y, t, T, p) -> -p.h * (p.T∞- T) / p.k # k∇T⋅n=h(T∞-T). The minus is since q = -∇T +bc3 = (x, y, t, T, p) -> -p.h * (p.T∞ - T) / p.k # k∇T⋅n=h(T∞-T). The minus is since q = -∇T bc4 = (x, y, t, T, p) -> oftype(T, 70.0) # T=70 -parameters = (nothing, nothing, (h=h, T∞=T∞, k=k), nothing) +parameters = (nothing, nothing, (h = h, T∞ = T∞, k = k), nothing) BCs = BoundaryConditions(mesh, (bc1, bc2, bc3, bc4), (Neumann, Dirichlet, Neumann, Dirichlet); parameters) @@ -119,17 +127,23 @@ sol |> tc #hide ```` ````@example equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems -fig, ax, sc = tricontourf(tri, sol.u, levels=40:70, axis=(xlabel="x", ylabel="y")) +fig, ax, sc = tricontourf(tri, sol.u, levels = 40:70, axis = (xlabel = "x", ylabel = "y")) fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.jl). ```julia using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie -A, B, C, D, E, F, G = (0.0, 0.0), +A, B, +C, +D, +E, +F, +G = (0.0, 0.0), (0.06, 0.0), (0.06, 0.03), (0.05, 0.03), @@ -143,7 +157,7 @@ bn4 = [F, G] bn = [bn1, bn2, bn3, bn4] boundary_nodes, points = convert_boundary_points_to_indices(bn) tri = triangulate(points; boundary_nodes) -refine!(tri; max_area=1e-4get_area(tri)) +refine!(tri; max_area = 1e-4get_area(tri)) triplot(tri) mesh = FVMGeometry(tri) @@ -153,9 +167,9 @@ h = 20.0 T∞ = 20.0 bc1 = (x, y, t, T, p) -> zero(T) # ∇T⋅n=0 bc2 = (x, y, t, T, p) -> oftype(T, 40.0) # T=40 -bc3 = (x, y, t, T, p) -> -p.h * (p.T∞- T) / p.k # k∇T⋅n=h(T∞-T). The minus is since q = -∇T +bc3 = (x, y, t, T, p) -> -p.h * (p.T∞ - T) / p.k # k∇T⋅n=h(T∞-T). The minus is since q = -∇T bc4 = (x, y, t, T, p) -> oftype(T, 70.0) # T=70 -parameters = (nothing, nothing, (h=h, T∞=T∞, k=k), nothing) +parameters = (nothing, nothing, (h = h, T∞ = T∞, k = k), nothing) BCs = BoundaryConditions(mesh, (bc1, bc2, bc3, bc4), (Neumann, Dirichlet, Neumann, Dirichlet); parameters) @@ -174,11 +188,10 @@ steady_prob = SteadyFVMProblem(prob) using OrdinaryDiffEq, SteadyStateDiffEq sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig, ax, sc = tricontourf(tri, sol.u, levels=40:70, axis=(xlabel="x", ylabel="y")) +fig, ax, sc = tricontourf(tri, sol.u, levels = 40:70, axis = (xlabel = "x", ylabel = "y")) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.md b/docs/src/tutorials/gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.md index 031290a..ec9e9dc 100644 --- a/docs/src/tutorials/gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.md +++ b/docs/src/tutorials/gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.md @@ -12,6 +12,7 @@ nothing #hide In this tutorial, we explore some pattern formation from the Gray-Scott model: + ```math \begin{equation} \begin{aligned} @@ -20,20 +21,23 @@ Gray-Scott model: \end{aligned} \end{equation} ``` + where $u$ and $v$ are the concentrations of two chemical species. The initial conditions we use are: + ```math \begin{align*} u(x, y, 0) &= 1 -\exp\left[-80\left(x^2 + y^2\right)\right], \\ v(x, y, 0) &= \exp\left[-80\left(x^2+y^2\right)\right]. \end{align*} ``` + The domain we use is $[-1, 1]^2$, and we use zero flux boundary conditions. ````@example gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system using FiniteVolumeMethod, DelaunayTriangulation -tri = triangulate_rectangle(-1, 1, -1, 1, 200, 200, single_boundary=true) +tri = triangulate_rectangle(-1, 1, -1, 1, 200, 200, single_boundary = true) mesh = FVMGeometry(tri) ```` @@ -57,17 +61,17 @@ v_qp = ε₂ u_Sp = b v_Sp = d u_icf = (x, y) -> 1 - exp(-80 * (x^2 + y^2)) -v_icf = (x, y) -> exp(-80 * (x^ 2 + y^2)) +v_icf = (x, y) -> exp(-80 * (x ^ 2 + y^2)) u_ic = [u_icf(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] v_ic = [v_icf(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] u_prob = FVMProblem(mesh, u_BCs; - flux_function=u_q, flux_parameters=u_qp, - source_function=u_S, source_parameters=u_Sp, - initial_condition=u_ic, final_time=6000.0) + flux_function = u_q, flux_parameters = u_qp, + source_function = u_S, source_parameters = u_Sp, + initial_condition = u_ic, final_time = 6000.0) v_prob = FVMProblem(mesh, v_BCs; - flux_function=v_q, flux_parameters=v_qp, - source_function=v_S, source_parameters=v_Sp, - initial_condition=v_ic, final_time=6000.0) + flux_function = v_q, flux_parameters = v_qp, + source_function = v_S, source_parameters = v_Sp, + initial_condition = v_ic, final_time = 6000.0) prob = FVMSystem(u_prob, v_prob) ```` @@ -75,7 +79,7 @@ Now that we have our system, we can solve. ````@example gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system using OrdinaryDiffEq, LinearSolve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=10.0, parallel=Val(false)) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 10.0, parallel = Val(false)) sol |> tc #hide ```` @@ -83,17 +87,17 @@ Here is an animation of the solution, looking only at the $v$ variable. ````@example gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system using CairoMakie -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel=L"x", ylabel=L"y") +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = L"x", ylabel = L"y") tightlimits!(ax) i = Observable(1) u = map(i -> reshape(sol.u[i][2, :], 200, 200), i) x = LinRange(-1, 1, 200) y = LinRange(-1, 1, 200) -heatmap!(ax, x, y, u, colorrange=(0.0, 0.4)) +heatmap!(ax, x, y, u, colorrange = (0.0, 0.4)) hidedecorations!(ax) record(fig, joinpath(@__DIR__, "../figures", "gray_scott_patterns.mp4"), eachindex(sol); - framerate=60) do _i + framerate = 60) do _i i[] = _i end ```` @@ -101,12 +105,13 @@ end ![Animation of the Gray-Scott model](../figures/gray_scott_patterns.mp4) ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.jl). ```julia using FiniteVolumeMethod, DelaunayTriangulation -tri = triangulate_rectangle(-1, 1, -1, 1, 200, 200, single_boundary=true) +tri = triangulate_rectangle(-1, 1, -1, 1, 200, 200, single_boundary = true) mesh = FVMGeometry(tri) bc = (x, y, t, (u, v), p) -> zero(u) * zero(v) @@ -126,39 +131,38 @@ v_qp = ε₂ u_Sp = b v_Sp = d u_icf = (x, y) -> 1 - exp(-80 * (x^2 + y^2)) -v_icf = (x, y) -> exp(-80 * (x^ 2 + y^2)) +v_icf = (x, y) -> exp(-80 * (x ^ 2 + y^2)) u_ic = [u_icf(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] v_ic = [v_icf(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] u_prob = FVMProblem(mesh, u_BCs; - flux_function=u_q, flux_parameters=u_qp, - source_function=u_S, source_parameters=u_Sp, - initial_condition=u_ic, final_time=6000.0) + flux_function = u_q, flux_parameters = u_qp, + source_function = u_S, source_parameters = u_Sp, + initial_condition = u_ic, final_time = 6000.0) v_prob = FVMProblem(mesh, v_BCs; - flux_function=v_q, flux_parameters=v_qp, - source_function=v_S, source_parameters=v_Sp, - initial_condition=v_ic, final_time=6000.0) + flux_function = v_q, flux_parameters = v_qp, + source_function = v_S, source_parameters = v_Sp, + initial_condition = v_ic, final_time = 6000.0) prob = FVMSystem(u_prob, v_prob) using OrdinaryDiffEq, LinearSolve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=10.0, parallel=Val(false)) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 10.0, parallel = Val(false)) using CairoMakie -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel=L"x", ylabel=L"y") +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = L"x", ylabel = L"y") tightlimits!(ax) i = Observable(1) u = map(i -> reshape(sol.u[i][2, :], 200, 200), i) x = LinRange(-1, 1, 200) y = LinRange(-1, 1, 200) -heatmap!(ax, x, y, u, colorrange=(0.0, 0.4)) +heatmap!(ax, x, y, u, colorrange = (0.0, 0.4)) hidedecorations!(ax) record(fig, joinpath(@__DIR__, "../figures", "gray_scott_patterns.mp4"), eachindex(sol); - framerate=60) do _i + framerate = 60) do _i i[] = _i end ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.md b/docs/src/tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.md index bd3ad98..8f57b20 100644 --- a/docs/src/tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.md +++ b/docs/src/tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.md @@ -9,7 +9,9 @@ nothing #hide ```` # Helmholtz Equation with Inhomogeneous Boundary Conditions + In this tutorial, we consider the following steady state problem: + ```math \begin{equation} \begin{aligned} @@ -18,20 +20,23 @@ In this tutorial, we consider the following steady state problem: \end{aligned} \end{equation} ``` + We can define this problem in the same way we have defined previous problems, except that the final `FVMProblem` must be wrapped in a `SteadyFVMProblem`. Let us start by defining the mesh and the boundary conditions. ````@example helmholtz_equation_with_inhomogeneous_boundary_conditions using DelaunayTriangulation, FiniteVolumeMethod -tri = triangulate_rectangle(-1, 1, -1, 1, 125, 125, single_boundary=true) +tri = triangulate_rectangle(-1, 1, -1, 1, 125, 125, single_boundary = true) mesh = FVMGeometry(tri) ```` For the boundary condition, + ```math \pdv{u}{\vb n} = 1, ``` + which is the same as $\grad u \vdot \vu n = 1$, this needs to be expressed in terms of $\vb q$. Since $\vb q = -\grad u$ for this problem, the boundary condition is $\vb q \vdot \vu n = -1$. @@ -46,13 +51,17 @@ which is needed for the nonlinear solver, and `final_time` should now be `Inf`. For the initial condition, let us simply let the initial estimate be all zeros. For the diffusion and source terms, note that previously we have been considered equations of the form + ```math \pdv{u}{t} + \div\vb q = S \quad \textnormal{or} \quad \pdv{u}{t} = \div[D\grad u] + S, ``` + while steady state problems take the form + ```math \div\vb q = S \quad \textnormal{or} \quad \div[D\grad u] + S = 0. ``` + So, for this problem, $D = 1$ and $S = u$. ````@example helmholtz_equation_with_inhomogeneous_boundary_conditions @@ -83,7 +92,7 @@ using NonlinearSolve sol = solve(steady_prob, NewtonRaphson()) copyto!(prob.initial_condition, sol.u) # this also changes steady_prob's initial condition using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq -sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) +sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) sol |> tc #hide ```` @@ -92,17 +101,18 @@ Now let's visualise. ````@example helmholtz_equation_with_inhomogeneous_boundary_conditions using CairoMakie -fig, ax, sc = tricontourf(tri, sol.u, levels=-2.5:0.15:-1.0, colormap=:matter) +fig, ax, sc = tricontourf(tri, sol.u, levels = -2.5:0.15:-1.0, colormap = :matter) fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/helmholtz_equation_with_inhomogeneous_boundary_conditions.jl). ```julia using DelaunayTriangulation, FiniteVolumeMethod -tri = triangulate_rectangle(-1, 1, -1, 1, 125, 125, single_boundary=true) +tri = triangulate_rectangle(-1, 1, -1, 1, 125, 125, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> -one(u), Neumann) @@ -123,14 +133,13 @@ using NonlinearSolve sol = solve(steady_prob, NewtonRaphson()) copyto!(prob.initial_condition, sol.u) # this also changes steady_prob's initial condition using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq -sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) +sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) using CairoMakie -fig, ax, sc = tricontourf(tri, sol.u, levels=-2.5:0.15:-1.0, colormap=:matter) +fig, ax, sc = tricontourf(tri, sol.u, levels = -2.5:0.15:-1.0, colormap = :matter) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/keller_segel_chemotaxis.md b/docs/src/tutorials/keller_segel_chemotaxis.md index 47d387b..0aebbec 100644 --- a/docs/src/tutorials/keller_segel_chemotaxis.md +++ b/docs/src/tutorials/keller_segel_chemotaxis.md @@ -1,6 +1,8 @@ -# Keller-Segel Model of Chemotaxis +# Keller-Segel Model of Chemotaxis + In this tutorial, we consider the following Keller-Segel model of chemotaxis: -```math + +```math \begin{equation*} \begin{aligned} \pdv{u}{t} &= \grad^2u - \div \left(\frac{cu}{1+u^2}\grad v\right) + u(1-u), \\ @@ -8,13 +10,14 @@ In this tutorial, we consider the following Keller-Segel model of chemotaxis: \end{aligned} \end{equation*} ``` -inside the square $[0, 100]^2$ with homogeneous Neumann boundary conditions. We + +inside the square $[0, 100]^2$ with homogeneous Neumann boundary conditions. We start by defining the problem, remembering that we need one problem for each variable $u$ and $v$. ```@example ex12 using FiniteVolumeMethod, DelaunayTriangulation -tri = triangulate_rectangle(0, 100, 0, 100, 250, 250, single_boundary=true) +tri = triangulate_rectangle(0, 100, 0, 100, 250, 250, single_boundary = true) mesh = FVMGeometry(tri) bc_u = (x, y, t, (u, v), p) -> zero(u) bc_v = (x, y, t, (u, v), p) -> zero(v) @@ -39,42 +42,44 @@ end S_v = (x, y, t, (u, v), p) -> begin return u - p.a * v end -q_u_parameters = (c=4.0,) -q_v_parameters = (D=1.0,) -S_v_parameters = (a=0.1,) +q_u_parameters = (c = 4.0,) +q_v_parameters = (D = 1.0,) +S_v_parameters = (a = 0.1,) u_initial_condition = 0.01rand(DelaunayTriangulation.num_points(tri)) v_initial_condition = zeros(DelaunayTriangulation.num_points(tri)) final_time = 1000.0 u_prob = FVMProblem(mesh, BCs_u; - flux_function=q_u, flux_parameters=q_u_parameters, - source_function=S_u, - initial_condition=u_initial_condition, final_time=final_time) + flux_function = q_u, flux_parameters = q_u_parameters, + source_function = S_u, + initial_condition = u_initial_condition, final_time = final_time) v_prob = FVMProblem(mesh, BCs_v; - flux_function=q_v, flux_parameters=q_v_parameters, - source_function=S_v, source_parameters=S_v_parameters, - initial_condition=v_initial_condition, final_time=final_time) + flux_function = q_v, flux_parameters = q_v_parameters, + source_function = S_v, source_parameters = S_v_parameters, + initial_condition = v_initial_condition, final_time = final_time) prob = FVMSystem(u_prob, v_prob) ``` # Now let's solve and animate the problem. + ```julia using OrdinaryDiffEq, Sundials, CairoMakie -sol = solve(prob, CVODE_BDF(linear_solver=:GMRES), saveat=1.0, parallel=Val(false)) -fig = Figure(fontsize=44) +sol = solve(prob, CVODE_BDF(linear_solver = :GMRES), saveat = 1.0, parallel = Val(false)) +fig = Figure(fontsize = 44) x = LinRange(0, 100, 250) y = LinRange(0, 100, 250) i = Observable(1) -axu = Axis(fig[1, 1], width=600, height=600, - title=map(i -> L"u(x,~ y,~ %$(sol.t[i]))", i), xlabel=L"x", ylabel=L"y") -axv = Axis(fig[1, 2], width=600, height=600, - title=map(i -> L"v(x,~ y,~ %$(sol.t[i]))", i), xlabel=L"x", ylabel=L"y") +axu = Axis(fig[1, 1], width = 600, height = 600, + title = map(i -> L"u(x,~ y,~ %$(sol.t[i]))", i), xlabel = L"x", ylabel = L"y") +axv = Axis(fig[1, 2], width = 600, height = 600, + title = map(i -> L"v(x,~ y,~ %$(sol.t[i]))", i), xlabel = L"x", ylabel = L"y") u = map(i -> reshape(sol.u[i][1, :], 250, 250), i) v = map(i -> reshape(sol.u[i][2, :], 250, 250), i) -heatmap!(axu, x, y, u, colorrange=(0.0, 2.5), colormap=:turbo) -heatmap!(axv, x, y, v, colorrange=(0.0, 10.0), colormap=:turbo) +heatmap!(axu, x, y, u, colorrange = (0.0, 2.5), colormap = :turbo) +heatmap!(axv, x, y, v, colorrange = (0.0, 10.0), colormap = :turbo) resize_to_layout!(fig) -record(fig, joinpath(@__DIR__, "../figures", "keller_segel_chemotaxis.mp4"), eachindex(sol); - framerate=60) do _i +record( + fig, joinpath(@__DIR__, "../figures", "keller_segel_chemotaxis.mp4"), eachindex(sol); + framerate = 60) do _i i[] = _i end; ``` @@ -84,11 +89,12 @@ end; Some pretty amazing patterns! ## Just the code + An uncommented version of this example is given below. ```julia using FiniteVolumeMethod, DelaunayTriangulation -tri = triangulate_rectangle(0, 100, 0, 100, 250, 250, single_boundary=true) +tri = triangulate_rectangle(0, 100, 0, 100, 250, 250, single_boundary = true) mesh = FVMGeometry(tri) bc_u = (x, y, t, (u, v), p) -> zero(u) bc_v = (x, y, t, (u, v), p) -> zero(v) @@ -113,43 +119,44 @@ end S_v = (x, y, t, (u, v), p) -> begin return u - p.a * v end -q_u_parameters = (c=4.0,) -q_v_parameters = (D=1.0,) -S_v_parameters = (a=0.1,) +q_u_parameters = (c = 4.0,) +q_v_parameters = (D = 1.0,) +S_v_parameters = (a = 0.1,) u_initial_condition = 0.01rand(DelaunayTriangulation.num_points(tri)) v_initial_condition = zeros(DelaunayTriangulation.num_points(tri)) final_time = 1000.0 u_prob = FVMProblem(mesh, BCs_u; - flux_function=q_u, flux_parameters=q_u_parameters, - source_function=S_u, - initial_condition=u_initial_condition, final_time=final_time) + flux_function = q_u, flux_parameters = q_u_parameters, + source_function = S_u, + initial_condition = u_initial_condition, final_time = final_time) v_prob = FVMProblem(mesh, BCs_v; - flux_function=q_v, flux_parameters=q_v_parameters, - source_function=S_v, source_parameters=S_v_parameters, - initial_condition=v_initial_condition, final_time=final_time) + flux_function = q_v, flux_parameters = q_v_parameters, + source_function = S_v, source_parameters = S_v_parameters, + initial_condition = v_initial_condition, final_time = final_time) prob = FVMSystem(u_prob, v_prob); using OrdinaryDiffEq, Sundials, CairoMakie -sol = solve(prob, CVODE_BDF(linear_solver=:GMRES), saveat=1.0, parallel=Val(false)) -fig = Figure(fontsize=44) +sol = solve(prob, CVODE_BDF(linear_solver = :GMRES), saveat = 1.0, parallel = Val(false)) +fig = Figure(fontsize = 44) x = LinRange(0, 100, 250) y = LinRange(0, 100, 250) i = Observable(1) -axu = Axis(fig[1, 1], width=600, height=600, - title=map(i -> L"u(x,~ y,~ %$(sol.t[i]))", i), xlabel=L"x", ylabel=L"y") -axv = Axis(fig[1, 2], width=600, height=600, - title=map(i -> L"v(x,~ y,~ %$(sol.t[i]))", i), xlabel=L"x", ylabel=L"y") +axu = Axis(fig[1, 1], width = 600, height = 600, + title = map(i -> L"u(x,~ y,~ %$(sol.t[i]))", i), xlabel = L"x", ylabel = L"y") +axv = Axis(fig[1, 2], width = 600, height = 600, + title = map(i -> L"v(x,~ y,~ %$(sol.t[i]))", i), xlabel = L"x", ylabel = L"y") u = map(i -> reshape(sol.u[i][1, :], 250, 250), i) v = map(i -> reshape(sol.u[i][2, :], 250, 250), i) -heatmap!(axu, x, y, u, colorrange=(0.0, 2.5), colormap=:turbo) -heatmap!(axv, x, y, v, colorrange=(0.0, 10.0), colormap=:turbo) +heatmap!(axu, x, y, u, colorrange = (0.0, 2.5), colormap = :turbo) +heatmap!(axv, x, y, v, colorrange = (0.0, 10.0), colormap = :turbo) resize_to_layout!(fig) -record(fig, joinpath(@__DIR__, "../figures", "keller_segel_chemotaxis.mp4"), eachindex(sol); - framerate=60) do _i +record( + fig, joinpath(@__DIR__, "../figures", "keller_segel_chemotaxis.mp4"), eachindex(sol); + framerate = 60) do _i i[] = _i end; ``` ---- +* * * -*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* \ No newline at end of file +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* diff --git a/docs/src/tutorials/laplaces_equation_with_internal_dirichlet_conditions.md b/docs/src/tutorials/laplaces_equation_with_internal_dirichlet_conditions.md index fdcb16b..48d84ff 100644 --- a/docs/src/tutorials/laplaces_equation_with_internal_dirichlet_conditions.md +++ b/docs/src/tutorials/laplaces_equation_with_internal_dirichlet_conditions.md @@ -9,8 +9,10 @@ nothing #hide ```` # Laplace's Equation with Internal Dirichlet Conditions + In this tutorial, we consider Laplace's equation with some additional complexity put into the problem via internal Dirichlet conditions: + ```math \begin{equation} \begin{aligned} @@ -23,11 +25,12 @@ u(1/2, y) &= 0 & 0 \leq y \leq 2/5. \end{aligned} \end{equation} ``` + To start with solving this problem, let us define an initial mesh. ````@example laplaces_equation_with_internal_dirichlet_conditions using DelaunayTriangulation, FiniteVolumeMethod -tri = triangulate_rectangle(0, 1, 0, 1, 50, 50, single_boundary=false) +tri = triangulate_rectangle(0, 1, 0, 1, 50, 50, single_boundary = false) ```` In this mesh, we don't have any points that lie exactly on the @@ -37,7 +40,6 @@ We do not need to add any constrained edges in this case, since these internal conditions are enforced only at points. [^1]: Of course, by defining the grid spacing appropriately we could have such points, but we just want to show here how we can add these points in if needed. - Let us now add in the points. ````@example laplaces_equation_with_internal_dirichlet_conditions @@ -53,7 +55,7 @@ fig It may also help to refine the mesh slightly. ````@example laplaces_equation_with_internal_dirichlet_conditions -refine!(tri, max_area=1e-4) +refine!(tri, max_area = 1e-4) fig, ax, sc = triplot(tri) fig ```` @@ -97,7 +99,7 @@ end vertices = find_all_points_on_line(tri) fig, ax, sc = triplot(tri) points = [get_point(tri, i) for i in vertices] -scatter!(ax, points, color=:red, markersize=10) +scatter!(ax, points, color = :red, markersize = 10) fig ```` @@ -109,7 +111,7 @@ is `1` as we only have a single function. ````@example laplaces_equation_with_internal_dirichlet_conditions ICs = InternalConditions((x, y, t, u, p) -> zero(u), - dirichlet_nodes=Dict(vertices .=> 1)) + dirichlet_nodes = Dict(vertices .=> 1)) ```` Now we can define the problem. As discussed in @@ -149,23 +151,24 @@ Now let's solve the problem. ````@example laplaces_equation_with_internal_dirichlet_conditions using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq -sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) +sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) sol |> tc #hide ```` ````@example laplaces_equation_with_internal_dirichlet_conditions -fig, ax, sc = tricontourf(tri, sol.u, levels=LinRange(0, 100, 28)) +fig, ax, sc = tricontourf(tri, sol.u, levels = LinRange(0, 100, 28)) tightlimits!(ax) fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/laplaces_equation_with_internal_dirichlet_conditions.jl). ```julia using DelaunayTriangulation, FiniteVolumeMethod -tri = triangulate_rectangle(0, 1, 0, 1, 50, 50, single_boundary=false) +tri = triangulate_rectangle(0, 1, 0, 1, 50, 50, single_boundary = false) using CairoMakie new_points = LinRange(0, 2 / 5, 250) @@ -175,7 +178,7 @@ end fig, ax, sc = triplot(tri) fig -refine!(tri, max_area=1e-4) +refine!(tri, max_area = 1e-4) fig, ax, sc = triplot(tri) fig @@ -202,11 +205,11 @@ end vertices = find_all_points_on_line(tri) fig, ax, sc = triplot(tri) points = [get_point(tri, i) for i in vertices] -scatter!(ax, points, color=:red, markersize=10) +scatter!(ax, points, color = :red, markersize = 10) fig ICs = InternalConditions((x, y, t, u, p) -> zero(u), - dirichlet_nodes=Dict(vertices .=> 1)) + dirichlet_nodes = Dict(vertices .=> 1)) initial_condition = zeros(DelaunayTriangulation.num_points(tri)) for i in each_solid_vertex(tri) @@ -224,14 +227,13 @@ prob = FVMProblem(mesh, BCs, ICs; steady_prob = SteadyFVMProblem(prob) using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq -sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) +sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) -fig, ax, sc = tricontourf(tri, sol.u, levels=LinRange(0, 100, 28)) +fig, ax, sc = tricontourf(tri, sol.u, levels = LinRange(0, 100, 28)) tightlimits!(ax) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/mean_exit_time.md b/docs/src/tutorials/mean_exit_time.md index f9ef495..02137cb 100644 --- a/docs/src/tutorials/mean_exit_time.md +++ b/docs/src/tutorials/mean_exit_time.md @@ -9,13 +9,17 @@ nothing #hide ```` # Mean Exit Time + ```@contents Pages = ["mean_exit_time.md"] ``` + ## Definition of the problem + In this tutorial, we consider the problem of mean exit time, based on some of my previous work.[^1] Typically, mean exit time problems with linear diffusion take the form + ```math \begin{equation}\label{eq:met} \begin{aligned} @@ -24,6 +28,7 @@ T(\vb x) &= 0 & \vb x \in \partial \Omega, \end{aligned} \end{equation} ``` + for some diffusivity $D$. $T(\vb x)$ is the mean exit time at $\vb x$, meaning the average time it would take a particle starting at $\vb x$ to exit the domain through $\partial\Omega$. For this interpretation of $T$, we are letting $D = \mathcal P\delta^2/(4\tau)$, @@ -31,22 +36,24 @@ where $\delta > 0$ is the step length of the particle, $\tau>0$ is the duration is the probability that the particle actually moves at a given time step. [^1]: See [Simpson et al. (2021)](https://iopscience.iop.org/article/10.1088/1367-2630/abe60d) and [Carr et al. (2022)](https://iopscience.iop.org/article/10.1088/1751-8121/ac4a1d). - In this previous work, we also use the finite volume method, but the problems are instead formulated - as linear problems, which makes the solution significantly simpler to implement. The approach we give here - is more generally applicable for other nonlinear problems, though. - + In this previous work, we also use the finite volume method, but the problems are instead formulated + as linear problems, which makes the solution significantly simpler to implement. The approach we give here + is more generally applicable for other nonlinear problems, though. A more complicated extension of \eqref{eq:met} is to allow the particle to be moving through a _heterogenous_ media, so that the diffusivity depends on $\vb x$. In particular, let us consider a compound disk $\Omega = \{0 < r < R_1\} \cup \{R_1 < r < R_2\}$, and let $\mathcal P$ (the probability of movement) be piecewise constant across $\Omega$ (and thus also $D$): + ```math P = \begin{cases} P_1 & 0 let r = sqrt(x^2 + y^2) return ifelse(r < p.R₁, p.D₁, p.D₂) end -diffusion_parameters = (R₁=R₁, D₁=D₁, D₂=D₂) +diffusion_parameters = (R₁ = R₁, D₁ = D₁, D₂ = D₂) ```` For the initial condition, which recall is the @@ -186,7 +203,7 @@ source_function = (x, y, t, u, p) -> one(u) prob = FVMProblem(mesh, BCs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) ```` ````@example mean_exit_time @@ -202,13 +219,14 @@ sol |> tc #hide ```` ````@example mean_exit_time -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:500:20000, extendhigh=:auto) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:500:20000, extendhigh = :auto) fig ```` ## Perturbed interface + Let us now solve the problem with a perturbed interface. The mesh is defined as follows. @@ -216,11 +234,11 @@ The mesh is defined as follows. g = θ -> sin(3θ) + cos(5θ) ε = 0.05 R1_f = θ -> R₁ * (1 + ε * g(θ)) -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] circle = CircularArc((0.0, R₂), (0.0, R₂), (0.0, 0.0)) -tri = triangulate(points; boundary_nodes=[circle]) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +tri = triangulate(points; boundary_nodes = [circle]) +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -229,7 +247,7 @@ for i in 2:length(xin) end n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) ```` @@ -260,13 +278,13 @@ diffusion_function = (x, y, t, u, p) -> let r = sqrt(x^2 + y^2), θ = atan(y, x) interface_val = p.R1_f(θ) return ifelse(r < interface_val, p.D₁, p.D₂) end -diffusion_parameters = (D₁=D₁, D₂=D₂, R1_f=R1_f) +diffusion_parameters = (D₁ = D₁, D₂ = D₂, R1_f = R1_f) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] source_function = (x, y, t, u, p) -> one(u) prob = FVMProblem(mesh, BCs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) ```` @@ -276,14 +294,15 @@ sol |> tc #hide ```` ````@example mean_exit_time -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:500:20000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:500:20000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig ```` ## Adding obstacles + Let us now add some obstacles into the problem. We add in components one at a time, exploring the impact of each component individually. When we update the triangulation, we do need to update the `mesh` since it is @@ -297,19 +316,20 @@ any nearby particles, i.e. $T(0,0)=0$. ````@example mean_exit_time add_point!(tri, 0.0, 0.0) mesh = FVMGeometry(tri) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(DelaunayTriangulation.num_points(tri) => 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), + dirichlet_nodes = Dict(DelaunayTriangulation.num_points(tri) => 1)) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(u), Dirichlet) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs, ICs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:500:10000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:500:10000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig ```` @@ -321,13 +341,15 @@ this is enforced by using Neumann boundary conditions. ````@example mean_exit_time ϵr = 0.25 -dirichlet_circle = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), (R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) -neumann_circle = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (R₂ * cos(ϵr), R₂ * sin(ϵr)), (0.0, 0.0)) +dirichlet_circle = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), ( + R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) +neumann_circle = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), ( + R₂ * cos(ϵr), R₂ * sin(ϵr)), (0.0, 0.0)) boundary_nodes = [[dirichlet_circle], [neumann_circle]] -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -338,7 +360,7 @@ n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) add_point!(tri, 0.0, 0.0) origin_idx = DelaunayTriangulation.num_points(tri) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) ```` @@ -346,18 +368,18 @@ triplot(tri) mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, (zero_f, zero_f), (Neumann, Dirichlet)) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(origin_idx => 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes = Dict(origin_idx => 1)) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs, ICs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:2500:35000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:2500:35000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig ```` @@ -366,12 +388,12 @@ move the point hole to $(-2, 0)$ rather than at the origin, and we'll also put a $(0, 2.95)$. ````@example mean_exit_time -hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive=false) +hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive = false) boundary_nodes = [[[dirichlet_circle], [neumann_circle]], [[hole]]] -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -382,8 +404,9 @@ n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) add_point!(tri, -2.0, 0.0) add_point!(tri, 0.0, 2.95) -pointhole_idxs = [DelaunayTriangulation.num_points(tri), DelaunayTriangulation.num_points(tri) - 1] -refine!(tri; max_area=1e-3get_area(tri)) +pointhole_idxs = [ + DelaunayTriangulation.num_points(tri), DelaunayTriangulation.num_points(tri) - 1] +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) ```` @@ -394,22 +417,23 @@ will be an absorbing boundary condition. mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, (zero_f, zero_f, zero_f), (Neumann, Dirichlet, Dirichlet)) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(pointhole_idxs .=> 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes = Dict(pointhole_idxs .=> 1)) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs, ICs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:1000:15000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:1000:15000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/mean_exit_time.jl). @@ -417,11 +441,11 @@ You can view the source code for this file [here](https://github.com/SciML/Finit using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie R₁, R₂ = 2.0, 3.0 circle = CircularArc((0.0, R₂), (0.0, R₂), (0.0, 0.0)) -points = NTuple{2,Float64}[] -tri = triangulate(points; boundary_nodes=[circle]) +points = NTuple{2, Float64}[] +tri = triangulate(points; boundary_nodes = [circle]) θ = LinRange(0, 2π, 250) -xin = @views @. R₁ * cos(θ)[begin:end-1] -yin = @views @. R₁ * sin(θ)[begin:end-1] +xin = @views @. R₁ * cos(θ)[begin:(end - 1)] +yin = @views @. R₁ * sin(θ)[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -430,7 +454,7 @@ for i in 2:length(xin) end n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) mesh = FVMGeometry(tri) @@ -441,7 +465,7 @@ D₁, D₂ = 6.25e-4, 6.25e-5 diffusion_function = (x, y, t, u, p) -> let r = sqrt(x^2 + y^2) return ifelse(r < p.R₁, p.D₁, p.D₂) end -diffusion_parameters = (R₁=R₁, D₁=D₁, D₂=D₂) +diffusion_parameters = (R₁ = R₁, D₁ = D₁, D₂ = D₂) f = (x, y) -> let r = sqrt(x^2 + y^2) return (R₂^2 - r^2) / (4D₂) @@ -452,26 +476,26 @@ source_function = (x, y, t, u, p) -> one(u) prob = FVMProblem(mesh, BCs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:500:20000, extendhigh=:auto) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:500:20000, extendhigh = :auto) fig g = θ -> sin(3θ) + cos(5θ) ε = 0.05 R1_f = θ -> R₁ * (1 + ε * g(θ)) -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] circle = CircularArc((0.0, R₂), (0.0, R₂), (0.0, 0.0)) -tri = triangulate(points; boundary_nodes=[circle]) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +tri = triangulate(points; boundary_nodes = [circle]) +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -480,7 +504,7 @@ for i in 2:length(xin) end n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) mesh = FVMGeometry(tri) @@ -499,48 +523,51 @@ diffusion_function = (x, y, t, u, p) -> let r = sqrt(x^2 + y^2), θ = atan(y, x) interface_val = p.R1_f(θ) return ifelse(r < interface_val, p.D₁, p.D₂) end -diffusion_parameters = (D₁=D₁, D₂=D₂, R1_f=R1_f) +diffusion_parameters = (D₁ = D₁, D₂ = D₂, R1_f = R1_f) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] source_function = (x, y, t, u, p) -> one(u) prob = FVMProblem(mesh, BCs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:500:20000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:500:20000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig add_point!(tri, 0.0, 0.0) mesh = FVMGeometry(tri) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(DelaunayTriangulation.num_points(tri) => 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), + dirichlet_nodes = Dict(DelaunayTriangulation.num_points(tri) => 1)) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(u), Dirichlet) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs, ICs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:500:10000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:500:10000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig ϵr = 0.25 -dirichlet_circle = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), (R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) -neumann_circle = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (R₂ * cos(ϵr), R₂ * sin(ϵr)), (0.0, 0.0)) +dirichlet_circle = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), ( + R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) +neumann_circle = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), ( + R₂ * cos(ϵr), R₂ * sin(ϵr)), (0.0, 0.0)) boundary_nodes = [[dirichlet_circle], [neumann_circle]] -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -551,32 +578,32 @@ n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) add_point!(tri, 0.0, 0.0) origin_idx = DelaunayTriangulation.num_points(tri) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, (zero_f, zero_f), (Neumann, Dirichlet)) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(origin_idx => 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes = Dict(origin_idx => 1)) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs, ICs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:2500:35000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:2500:35000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig -hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive=false) +hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive = false) boundary_nodes = [[[dirichlet_circle], [neumann_circle]], [[hole]]] -points = NTuple{2,Float64}[] +points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -587,29 +614,29 @@ n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) add_point!(tri, -2.0, 0.0) add_point!(tri, 0.0, 2.95) -pointhole_idxs = [DelaunayTriangulation.num_points(tri), DelaunayTriangulation.num_points(tri) - 1] -refine!(tri; max_area=1e-3get_area(tri)) +pointhole_idxs = [ + DelaunayTriangulation.num_points(tri), DelaunayTriangulation.num_points(tri) - 1] +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, (zero_f, zero_f, zero_f), (Neumann, Dirichlet, Dirichlet)) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(pointhole_idxs .=> 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes = Dict(pointhole_idxs .=> 1)) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs, ICs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time=Inf) + final_time = Inf) steady_prob = SteadyFVMProblem(prob) sol = solve(steady_prob, DynamicSS(Rosenbrock23())) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y") -tricontourf!(ax, tri, sol.u, levels=0:1000:15000, extendhigh=:auto) -lines!(ax, [xin; xin[1]], [yin; yin[1]], color=:magenta, linewidth=5) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y") +tricontourf!(ax, tri, sol.u, levels = 0:1000:15000, extendhigh = :auto) +lines!(ax, [xin; xin[1]], [yin; yin[1]], color = :magenta, linewidth = 5) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/overview.md b/docs/src/tutorials/overview.md index cf331fd..454fc85 100644 --- a/docs/src/tutorials/overview.md +++ b/docs/src/tutorials/overview.md @@ -1,10 +1,10 @@ -# Section Overview +# Section Overview ```@contents Pages = ["overview.md"] ``` -We provide many tutorials in this section. Each tutorial is self-contained, although +We provide many tutorials in this section. Each tutorial is self-contained, although more detail will be offered in the earlier examples. At the end of each tutorial we show the uncommented code, but if you want to see the actual script itself that generates these tutorials, then click on the `Edit on GitHub` section on the top right of the respective tutorial. We list all the examples below, but the tutorials can be accessed in their respective sections of the sidebar. @@ -12,6 +12,7 @@ We list all the examples below, but the tutorials can be accessed in their respe Note that this tutorial covers problems of the form $\partial u/\partial_t + \div\vb q = S$, and converting problems into this form. For an explanation of how to use the templates we provide for specific problems, e.g. the diffusion equation, see the [Solvers for Specific Problems, and Writing Your Own](../wyos/overview.md) section. # Diffusion Equation on a Square Plate + [This tutorial](diffusion_equation_on_a_square_plate.md) considers a diffusion equation problem on a square plate: ```math @@ -23,7 +24,9 @@ u(\vb x, 0) &= f(\vb x) & \vb x \in \Omega, \end{aligned} \end{equation*} ``` + where $\Omega = [0, 2]^2$ and + ```math f(x, y) = \begin{cases} 50 & y \leq 1, \\ 0 & y > 1. \end{cases} ``` @@ -37,8 +40,8 @@ u(\vb x, t) = \frac{200}{\pi^2}\sum_{m=1}^\infty\sum_{n=1}^\infty \frac{\left[1 although we do not refer to this in the tutorial (only in the tests). [^1]: See [here](http://ramanujan.math.trinity.edu/rdaileda/teach/s12/m3357/lectures/lecture_3_6_short.pdf) for a derivation. +# Diffusion Equation in a Wedge with Mixed Boundary Conditions -# Diffusion Equation in a Wedge with Mixed Boundary Conditions This [tutorial](diffusion_equation_in_a_wedge_with_mixed_boundary_conditions.md) considers a similar example as in the first example, except now the diffusion is in a wedge, has mixed boundary conditions, and is also now in polar coordinates: ```math @@ -52,21 +55,26 @@ u(r, \theta, 0) &= f(r,\theta) & 0 1e-6tot_area * r^2 / A area_constraint = (_tri, T) -> begin @@ -49,7 +53,7 @@ area_constraint = (_tri, T) -> begin flag = A ≥ max_area_function(A, dist_to_origin) return flag end -refine!(tri; min_angle=33.0, custom_constraint=area_constraint) +refine!(tri; min_angle = 33.0, custom_constraint = area_constraint) triplot(tri) ```` @@ -65,10 +69,13 @@ BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(u), Dirichlet) We now need to define the actual problem. We need to write \eqref{eq:advdiffeq} in the form + ```math \pdv{u}{t} + \div\vb q = 0. ``` + To do this, write: + ```math \begin{align*} \div \vb q &= \nu\pdv{u}{x} - D\pdv[2]{u}{x} - D\pdv[2]{u}{y} \\ @@ -77,15 +84,20 @@ To do this, write: &= \div \left(\boldsymbol\nu u - D\grad u\right), \end{align*} ``` + where $\boldsymbol\nu = (\nu, 0)^{\mkern-1.5mu\mathsf{T}}$. Thus, we can write + ```math \vb q = \boldsymbol\nu u - D\grad u. ``` + We now have our flux function. Next, let us define the initial condition. We approximate by + ```math \delta(\vb x) \approx g(\vb x) \approx \frac{1}{\varepsilon^2\pi}\exp\left[-\frac{1}{\varepsilon^2}\left(x^2+y^2\right)\right], ``` + taking $\varepsilon=1/10$. We can now define the problem. Remember that the flux function takes argument $(\alpha, \beta, \gamma)$ rather than $u$, replacing $u$ with $u(x, y) = \alpha x + \beta y + \gamma$, and it returns a `Tuple` representing the vector. We let $D = 0.02$ and $\nu = 0.05$. @@ -102,7 +114,7 @@ flux_function = (x, y, t, α, β, γ, p) -> begin qy = -p.D * ∂y return (qx, qy) end -flux_parameters = (D=0.02, ν=0.05) +flux_parameters = (D = 0.02, ν = 0.05) final_time = 250.0 prob = FVMProblem(mesh, BCs; initial_condition, @@ -116,19 +128,20 @@ Now we can solve and visualise the solution. ````@example piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation using OrdinaryDiffEq, LinearSolve times = [0, 10, 25, 50, 100, 200, 250] -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=times) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = times) sol |> tc #hide ```` ````@example piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for i in eachindex(sol) - ax = Axis(fig[1, i], width=400, height=400, - xlabel="x", ylabel="y", - title="t = $(sol.t[i])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[i], levels=0:0.00001:0.001, extendhigh=:auto, extendlow=:auto, colormap=:matter) + ax = Axis(fig[1, i], width = 400, height = 400, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[i])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[i], levels = 0:0.00001:0.001, + extendhigh = :auto, extendlow = :auto, colormap = :matter) tightlimits!(ax) ylims!(ax, -10, 10) end @@ -137,6 +150,7 @@ fig ```` ## Piecewise linear interpolation + As mentioned in [mathematical details section](../math.md), a key part of the finite volume method is the assumption that $u$ is piecewise linear between each triangular element, letting $u(x, y) = \alpha x + \beta y + \gamma$. Thus, it may be natural to want to interpolate the solution using piecewise linear interpolation. This could be done @@ -146,9 +160,9 @@ that gets this triangle for you and then interpolates without this intermediate as it is typically more efficient to first obtain all the triangles you need and then interpolate. In what follows, we: -1. Define a grid to interpolate over. -2. Find the triangles containing each point in the grid. -3. Interpolate at each point for the given times. + 1. Define a grid to interpolate over. + 2. Find the triangles containing each point in the grid. + 3. Interpolate at each point for the given times. We consider the times $t = 10, 25, 50, 100, 200, 250$. You could also of course amend the procedure so that you evaluate the interpolant at each time for a given point first, @@ -157,7 +171,7 @@ allowing you to avoid storing the triangle since you only consider each point a ````@example piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation x = LinRange(-L, L, 250) y = LinRange(-L, L, 250) -triangles = Matrix{NTuple{3,Int}}(undef, length(x), length(y)) +triangles = Matrix{NTuple{3, Int}}(undef, length(x), length(y)) for j in eachindex(y) for i in eachindex(x) triangles[i, j] = jump_and_march(tri, (x[i], y[j])) @@ -167,7 +181,8 @@ interpolated_vals = zeros(length(x), length(y), length(sol)) for k in eachindex(sol) for j in eachindex(y) for i in eachindex(x) - interpolated_vals[i, j, k] = pl_interpolate(prob, triangles[i, j], sol.u[k], x[i], y[j]) + interpolated_vals[i, j, k] = pl_interpolate( + prob, triangles[i, j], sol.u[k], x[i], y[j]) end end end @@ -178,13 +193,14 @@ our grid to make the `tricontourf` call faster. ````@example piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation _tri = triangulate([[x for x in x, _ in y] |> vec [y for _ in x, y in y] |> vec]') -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for i in eachindex(sol) - ax = Axis(fig[1, i], width=400, height=400, - xlabel="x", ylabel="y", - title="t = $(sol.t[i])", - titlealign=:left) - tricontourf!(ax, _tri, interpolated_vals[:, :, i] |> vec, levels=0:0.00001:0.001, extendhigh=:auto, extendlow=:auto, colormap=:matter) + ax = Axis(fig[1, i], width = 400, height = 400, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[i])", + titlealign = :left) + tricontourf!(ax, _tri, interpolated_vals[:, :, i] |> vec, levels = 0:0.00001:0.001, + extendhigh = :auto, extendlow = :auto, colormap = :matter) tightlimits!(ax) ylims!(ax, -10, 10) end @@ -193,6 +209,7 @@ fig ```` ## Natural neighbour interpolation + Since the solution is defined over a triangulation, the most natural form of inteprolation to use, other than piecewise linear interpolation, is natural neighbour interpolation. We can use [NaturalNeighbours.jl](https://github.com/DanielVandH/NaturalNeighbours.jl) for this; @@ -204,7 +221,7 @@ the interpolant with the solution at $t = 50$. ````@example piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation using NaturalNeighbours -itp = interpolate(tri, sol.u[4], derivatives=true) # sol.t[4] == 50 +itp = interpolate(tri, sol.u[4], derivatives = true) # sol.t[4] == 50 sol |> tc #hide ```` @@ -226,15 +243,14 @@ nothing #hide We will look at all the interpolants provided by NaturalNeighbours.jl.[^1] [^1]: This list is available from `?NaturalNeighbours.AbstractInterpolator`. Look at the help page (`?`) for the respective interpolators or NaturalNeighbours.jl's documentation for more information. - ````@example piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation -sibson_vals = itp(_x, _y; method=Sibson()) -triangle_vals = itp(_x, _y; method=Triangle()) # this is the same as pl_interpolate -laplace_vals = itp(_x, _y; method=Laplace()) -sibson_1_vals = itp(_x, _y; method=Sibson(1)) -nearest_vals = itp(_x, _y; method=Nearest()) -farin_vals = itp(_x, _y; method=Farin()) -hiyoshi_vals = itp(_x, _y; method=Hiyoshi(2)) +sibson_vals = itp(_x, _y; method = Sibson()) +triangle_vals = itp(_x, _y; method = Triangle()) # this is the same as pl_interpolate +laplace_vals = itp(_x, _y; method = Laplace()) +sibson_1_vals = itp(_x, _y; method = Sibson(1)) +nearest_vals = itp(_x, _y; method = Nearest()) +farin_vals = itp(_x, _y; method = Farin()) +hiyoshi_vals = itp(_x, _y; method = Hiyoshi(2)) pde_vals = sol.u[4]; nothing #hide ```` @@ -242,33 +258,41 @@ nothing #hide We visualise these results as follows. ````@example piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation -fig = Figure(fontsize=38) -all_vals = (sibson_vals, triangle_vals, laplace_vals, sibson_1_vals, nearest_vals, farin_vals, hiyoshi_vals, pde_vals) -titles = ("(a): Sibson", "(b): Triangle", "(c): Laplace", "(d): Sibson-1", "(e): Nearest", "(f): Farin", "(g): Hiyoshi", "(h): PDE") -fig = Figure(fontsize=55, resolution=(6350, 1550)) # resolution from resize_to_layout!(fig) - had to manually adjust to fix missing ticks +fig = Figure(fontsize = 38) +all_vals = (sibson_vals, triangle_vals, laplace_vals, sibson_1_vals, + nearest_vals, farin_vals, hiyoshi_vals, pde_vals) +titles = ("(a): Sibson", "(b): Triangle", "(c): Laplace", "(d): Sibson-1", + "(e): Nearest", "(f): Farin", "(g): Hiyoshi", "(h): PDE") +fig = Figure(fontsize = 55, resolution = (6350, 1550)) # resolution from resize_to_layout!(fig) - had to manually adjust to fix missing ticks for (i, (vals, title)) in enumerate(zip(all_vals, titles)) - ax2d = Axis(fig[1, i], xlabel="x", ylabel="y", width=600, height=600, title=title, titlealign=:left) - ax3d = Axis3(fig[2, i], xlabel="x", ylabel="y", width=600, height=600, title=title, titlealign=:left) + ax2d = Axis(fig[1, i], xlabel = "x", ylabel = "y", width = 600, + height = 600, title = title, titlealign = :left) + ax3d = Axis3(fig[2, i], xlabel = "x", ylabel = "y", width = 600, + height = 600, title = title, titlealign = :left) ax3d.zlabeloffset[] = 125 xlims!(ax2d, -4, 6) ylims!(ax2d, -4, 4) xlims!(ax3d, -4, 6) ylims!(ax3d, -4, 4) if vals ≠ pde_vals - contourf!(ax2d, _x, _y, vals, colormap=:matter, levels=0:0.001:0.1, extendlow=:auto, extendhigh=:auto) + contourf!(ax2d, _x, _y, vals, colormap = :matter, + levels = 0:0.001:0.1, extendlow = :auto, extendhigh = :auto) vals = copy(vals) - vals[(_x.<-4).|(_x.>6)] .= NaN - vals[(_y.<-4).|(_y.>4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... - surface!(ax3d, _x, _y, vals, color=vals, colormap=:matter, colorrange=(0, 0.1)) + vals[(_x .< -4) .| (_x .> 6)] .= NaN + vals[(_y .< -4) .| (_y .> 4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... + surface!( + ax3d, _x, _y, vals, color = vals, colormap = :matter, colorrange = (0, 0.1)) else - tricontourf!(ax2d, tri, vals, colormap=:matter, levels=0:0.001:0.1, extendlow=:auto, extendhigh=:auto) + tricontourf!(ax2d, tri, vals, colormap = :matter, levels = 0:0.001:0.1, + extendlow = :auto, extendhigh = :auto) triangles = [T[j] for T in each_solid_triangle(tri), j in 1:3] x = getx.(get_points(tri)) y = gety.(get_points(tri)) vals = copy(vals) - vals[(x.<-4).|(x.>6)] .= NaN - vals[(y.<-4).|(y.>4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... - mesh!(ax3d, hcat(x, y, vals), triangles, color=vals, colormap=:matter, colorrange=(0, 0.1)) + vals[(x .< -4) .| (x .> 6)] .= NaN + vals[(y .< -4) .| (y .> 4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... + mesh!(ax3d, hcat(x, y, vals), triangles, color = vals, + colormap = :matter, colorrange = (0, 0.1)) end end fig @@ -280,14 +304,16 @@ with, say, holes or non-convex boundaries, you may run into issues. For such cases, you should usually call the interpolant with `project=false` to at least help the procedure a bit. You may also be interested in `identify_exterior_points`. We consider interpolating data over a region with holes in [this annulus example](diffusion_equation_on_an_annulus.md). + ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.jl). ```julia using DelaunayTriangulation, FiniteVolumeMethod, LinearAlgebra, CairoMakie L = 30 -tri = triangulate_rectangle(-L, L, -L, L, 2, 2, single_boundary=true) +tri = triangulate_rectangle(-L, L, -L, L, 2, 2, single_boundary = true) tot_area = get_area(tri) max_area_function = (A, r) -> 1e-6tot_area * r^2 / A area_constraint = (_tri, T) -> begin @@ -299,7 +325,7 @@ area_constraint = (_tri, T) -> begin flag = A ≥ max_area_function(A, dist_to_origin) return flag end -refine!(tri; min_angle=33.0, custom_constraint=area_constraint) +refine!(tri; min_angle = 33.0, custom_constraint = area_constraint) triplot(tri) mesh = FVMGeometry(tri) @@ -317,7 +343,7 @@ flux_function = (x, y, t, α, β, γ, p) -> begin qy = -p.D * ∂y return (qx, qy) end -flux_parameters = (D=0.02, ν=0.05) +flux_parameters = (D = 0.02, ν = 0.05) final_time = 250.0 prob = FVMProblem(mesh, BCs; initial_condition, @@ -327,16 +353,17 @@ prob = FVMProblem(mesh, BCs; using OrdinaryDiffEq, LinearSolve times = [0, 10, 25, 50, 100, 200, 250] -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=times) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = times) using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for i in eachindex(sol) - ax = Axis(fig[1, i], width=400, height=400, - xlabel="x", ylabel="y", - title="t = $(sol.t[i])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[i], levels=0:0.00001:0.001, extendhigh=:auto, extendlow=:auto, colormap=:matter) + ax = Axis(fig[1, i], width = 400, height = 400, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[i])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[i], levels = 0:0.00001:0.001, + extendhigh = :auto, extendlow = :auto, colormap = :matter) tightlimits!(ax) ylims!(ax, -10, 10) end @@ -345,7 +372,7 @@ fig x = LinRange(-L, L, 250) y = LinRange(-L, L, 250) -triangles = Matrix{NTuple{3,Int}}(undef, length(x), length(y)) +triangles = Matrix{NTuple{3, Int}}(undef, length(x), length(y)) for j in eachindex(y) for i in eachindex(x) triangles[i, j] = jump_and_march(tri, (x[i], y[j])) @@ -355,19 +382,21 @@ interpolated_vals = zeros(length(x), length(y), length(sol)) for k in eachindex(sol) for j in eachindex(y) for i in eachindex(x) - interpolated_vals[i, j, k] = pl_interpolate(prob, triangles[i, j], sol.u[k], x[i], y[j]) + interpolated_vals[i, j, k] = pl_interpolate( + prob, triangles[i, j], sol.u[k], x[i], y[j]) end end end _tri = triangulate([[x for x in x, _ in y] |> vec [y for _ in x, y in y] |> vec]') -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for i in eachindex(sol) - ax = Axis(fig[1, i], width=400, height=400, - xlabel="x", ylabel="y", - title="t = $(sol.t[i])", - titlealign=:left) - tricontourf!(ax, _tri, interpolated_vals[:, :, i] |> vec, levels=0:0.00001:0.001, extendhigh=:auto, extendlow=:auto, colormap=:matter) + ax = Axis(fig[1, i], width = 400, height = 400, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[i])", + titlealign = :left) + tricontourf!(ax, _tri, interpolated_vals[:, :, i] |> vec, levels = 0:0.00001:0.001, + extendhigh = :auto, extendlow = :auto, colormap = :matter) tightlimits!(ax) ylims!(ax, -10, 10) end @@ -375,53 +404,60 @@ resize_to_layout!(fig) fig using NaturalNeighbours -itp = interpolate(tri, sol.u[4], derivatives=true) # sol.t[4] == 50 +itp = interpolate(tri, sol.u[4], derivatives = true) # sol.t[4] == 50 _x = [x for x in x, _ in y] |> vec _y = [y for _ in x, y in y] |> vec; -sibson_vals = itp(_x, _y; method=Sibson()) -triangle_vals = itp(_x, _y; method=Triangle()) # this is the same as pl_interpolate -laplace_vals = itp(_x, _y; method=Laplace()) -sibson_1_vals = itp(_x, _y; method=Sibson(1)) -nearest_vals = itp(_x, _y; method=Nearest()) -farin_vals = itp(_x, _y; method=Farin()) -hiyoshi_vals = itp(_x, _y; method=Hiyoshi(2)) +sibson_vals = itp(_x, _y; method = Sibson()) +triangle_vals = itp(_x, _y; method = Triangle()) # this is the same as pl_interpolate +laplace_vals = itp(_x, _y; method = Laplace()) +sibson_1_vals = itp(_x, _y; method = Sibson(1)) +nearest_vals = itp(_x, _y; method = Nearest()) +farin_vals = itp(_x, _y; method = Farin()) +hiyoshi_vals = itp(_x, _y; method = Hiyoshi(2)) pde_vals = sol.u[4]; -fig = Figure(fontsize=38) -all_vals = (sibson_vals, triangle_vals, laplace_vals, sibson_1_vals, nearest_vals, farin_vals, hiyoshi_vals, pde_vals) -titles = ("(a): Sibson", "(b): Triangle", "(c): Laplace", "(d): Sibson-1", "(e): Nearest", "(f): Farin", "(g): Hiyoshi", "(h): PDE") -fig = Figure(fontsize=55, resolution=(6350, 1550)) # resolution from resize_to_layout!(fig) - had to manually adjust to fix missing ticks +fig = Figure(fontsize = 38) +all_vals = (sibson_vals, triangle_vals, laplace_vals, sibson_1_vals, + nearest_vals, farin_vals, hiyoshi_vals, pde_vals) +titles = ("(a): Sibson", "(b): Triangle", "(c): Laplace", "(d): Sibson-1", + "(e): Nearest", "(f): Farin", "(g): Hiyoshi", "(h): PDE") +fig = Figure(fontsize = 55, resolution = (6350, 1550)) # resolution from resize_to_layout!(fig) - had to manually adjust to fix missing ticks for (i, (vals, title)) in enumerate(zip(all_vals, titles)) - ax2d = Axis(fig[1, i], xlabel="x", ylabel="y", width=600, height=600, title=title, titlealign=:left) - ax3d = Axis3(fig[2, i], xlabel="x", ylabel="y", width=600, height=600, title=title, titlealign=:left) + ax2d = Axis(fig[1, i], xlabel = "x", ylabel = "y", width = 600, + height = 600, title = title, titlealign = :left) + ax3d = Axis3(fig[2, i], xlabel = "x", ylabel = "y", width = 600, + height = 600, title = title, titlealign = :left) ax3d.zlabeloffset[] = 125 xlims!(ax2d, -4, 6) ylims!(ax2d, -4, 4) xlims!(ax3d, -4, 6) ylims!(ax3d, -4, 4) if vals ≠ pde_vals - contourf!(ax2d, _x, _y, vals, colormap=:matter, levels=0:0.001:0.1, extendlow=:auto, extendhigh=:auto) + contourf!(ax2d, _x, _y, vals, colormap = :matter, + levels = 0:0.001:0.1, extendlow = :auto, extendhigh = :auto) vals = copy(vals) - vals[(_x.<-4).|(_x.>6)] .= NaN - vals[(_y.<-4).|(_y.>4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... - surface!(ax3d, _x, _y, vals, color=vals, colormap=:matter, colorrange=(0, 0.1)) + vals[(_x .< -4) .| (_x .> 6)] .= NaN + vals[(_y .< -4) .| (_y .> 4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... + surface!( + ax3d, _x, _y, vals, color = vals, colormap = :matter, colorrange = (0, 0.1)) else - tricontourf!(ax2d, tri, vals, colormap=:matter, levels=0:0.001:0.1, extendlow=:auto, extendhigh=:auto) + tricontourf!(ax2d, tri, vals, colormap = :matter, levels = 0:0.001:0.1, + extendlow = :auto, extendhigh = :auto) triangles = [T[j] for T in each_solid_triangle(tri), j in 1:3] x = getx.(get_points(tri)) y = gety.(get_points(tri)) vals = copy(vals) - vals[(x.<-4).|(x.>6)] .= NaN - vals[(y.<-4).|(y.>4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... - mesh!(ax3d, hcat(x, y, vals), triangles, color=vals, colormap=:matter, colorrange=(0, 0.1)) + vals[(x .< -4) .| (x .> 6)] .= NaN + vals[(y .< -4) .| (y .> 4)] .= NaN # This is the only way to fix the weird issues with Axis3 when changing the (x/y/z)lims... + mesh!(ax3d, hcat(x, y, vals), triangles, color = vals, + colormap = :matter, colorrange = (0, 0.1)) end end fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/porous_fisher_equation_and_travelling_waves.md b/docs/src/tutorials/porous_fisher_equation_and_travelling_waves.md index dac7916..05dfb0e 100644 --- a/docs/src/tutorials/porous_fisher_equation_and_travelling_waves.md +++ b/docs/src/tutorials/porous_fisher_equation_and_travelling_waves.md @@ -9,8 +9,10 @@ nothing #hide ```` # Porous-Fisher Equation and Travelling Waves + This tutorial considers a more involved example, where we discuss travelling wave solutions of a Porous-Fisher equation: + ```math \begin{equation*} \begin{aligned} @@ -23,6 +25,7 @@ u(\vb x, 0) & = f(y) & 0 \leq x \leq a,\, 0 \leq y\leq b. \end{aligned} \end{equation*} ``` + This problem is defined on the rectangle $[0, a] \times [0, b]$ and we assume that $b \gg a$ so that the rectangle is much taller than it is wide. This problem has $u$ ranging from $u=1$ at the bottom of the rectangle down to $u=0$ at the top @@ -31,20 +34,24 @@ We take the initial condition $f$ to be independent of $x$. This setup implies that the solution along each constant line $x=x_0$ should be about the same, i.e. the problem is invariant in $x$. If indeed we have $u(\vb x, t) = u(y, t)$ then the PDE becomes + ```math \begin{equation}\label{eq:onedproblem} \pdv{u(y, t)}{t} = D\pdv{y}\left(u\pdv{u}{y}\right) + \lambda u(1-u), \end{equation} ``` + which has travelling wave solutions. Following the analysis given in Section 13.4 of the book _Mathematical biology I: An introduction_ by J. D. Murray (2002), we can show that a travelling wave solution to the one-dimensional problem \eqref{eq:onedproblem} is given by + ```math \begin{equation}\label{eq:onedproblemexact} u(y, t) = \begin{cases} 1-\mathrm{e}^{c_{\min}z} & z \leq 0, \\ 0 & z > 0, \end{cases} \end{equation} ``` + where $c_{\min} = \sqrt{\lambda/(2D)}$, $c = \sqrt{D\lambda/2}$, and $z = x-ct$ is the travelling wave coordinates. This travelling wave would mathc our problem exactly if the rectangle were instead $[0, a] \times \mathbb R$, but by choosing $b$ large @@ -59,7 +66,7 @@ Now with this preamble out of the way, let us solve this problem. ````@example porous_fisher_equation_and_travelling_waves using DelaunayTriangulation, FiniteVolumeMethod, OrdinaryDiffEq, LinearSolve a, b, c, d, nx, ny = 0.0, 3.0, 0.0, 40.0, 60, 80 -tri = triangulate_rectangle(a, b, c, d, nx, ny; single_boundary=false) +tri = triangulate_rectangle(a, b, c, d, nx, ny; single_boundary = false) mesh = FVMGeometry(tri) one_bc = (x, y, t, u, p) -> one(u) zero_bc = (x, y, t, u, p) -> zero(u) @@ -78,7 +85,7 @@ prob = FVMProblem(mesh, BCs; diffusion_function, diffusion_parameters, source_function, source_parameters, initial_condition, final_time) -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()); saveat=0.5) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 0.5) sol |> tc #hide ```` @@ -102,7 +109,7 @@ for (i, t_idx) in pairs(large_time_idx:lastindex(sol)) y = c + (k - 1) * (d - c) / (ny - 1) z = y - c * τ z_vals[k, i] = z - travelling_wave_values[k, i] = u[nx÷2, k] + travelling_wave_values[k, i] = u[nx ÷ 2, k] end end ```` @@ -111,32 +118,35 @@ Now we are in a position to plot. ````@example porous_fisher_equation_and_travelling_waves using CairoMakie -fig = Figure(resolution=(3200.72f0, 800.64f0), fontsize=38) +fig = Figure(resolution = (3200.72f0, 800.64f0), fontsize = 38) for (i, j) in zip(1:3, (1, 51, 101)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.05:1, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:0.05:1, colormap = :matter) tightlimits!(ax) end -ax = Axis(fig[1, 4], width=900, height=600) -colors = cgrad(:matter, length(sol) - large_time_idx + 1; categorical=false) -[lines!(ax, z_vals[:, i], travelling_wave_values[:, i], color=colors[i], linewidth=2) for i in 1:(length(sol)-large_time_idx+1)] +ax = Axis(fig[1, 4], width = 900, height = 600) +colors = cgrad(:matter, length(sol) - large_time_idx + 1; categorical = false) +[lines!(ax, z_vals[:, i], travelling_wave_values[:, i], color = colors[i], linewidth = 2) + for i in 1:(length(sol) - large_time_idx + 1)] exact_z_vals = collect(LinRange(extrema(z_vals)..., 500)) exact_travelling_wave_values = exact_solution.(exact_z_vals) -lines!(ax, exact_z_vals, exact_travelling_wave_values, color=:red, linewidth=4, linestyle=:dash) +lines!(ax, exact_z_vals, exact_travelling_wave_values, + color = :red, linewidth = 4, linestyle = :dash) fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/porous_fisher_equation_and_travelling_waves.jl). ```julia using DelaunayTriangulation, FiniteVolumeMethod, OrdinaryDiffEq, LinearSolve a, b, c, d, nx, ny = 0.0, 3.0, 0.0, 40.0, 60, 80 -tri = triangulate_rectangle(a, b, c, d, nx, ny; single_boundary=false) +tri = triangulate_rectangle(a, b, c, d, nx, ny; single_boundary = false) mesh = FVMGeometry(tri) one_bc = (x, y, t, u, p) -> one(u) zero_bc = (x, y, t, u, p) -> zero(u) @@ -155,7 +165,7 @@ prob = FVMProblem(mesh, BCs; diffusion_function, diffusion_parameters, source_function, source_parameters, initial_condition, final_time) -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()); saveat=0.5) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 0.5) large_time_idx = findfirst(≥(10.0), sol.t) c = sqrt(λ / (2D)) @@ -172,30 +182,31 @@ for (i, t_idx) in pairs(large_time_idx:lastindex(sol)) y = c + (k - 1) * (d - c) / (ny - 1) z = y - c * τ z_vals[k, i] = z - travelling_wave_values[k, i] = u[nx÷2, k] + travelling_wave_values[k, i] = u[nx ÷ 2, k] end end using CairoMakie -fig = Figure(resolution=(3200.72f0, 800.64f0), fontsize=38) +fig = Figure(resolution = (3200.72f0, 800.64f0), fontsize = 38) for (i, j) in zip(1:3, (1, 51, 101)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.05:1, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:0.05:1, colormap = :matter) tightlimits!(ax) end -ax = Axis(fig[1, 4], width=900, height=600) -colors = cgrad(:matter, length(sol) - large_time_idx + 1; categorical=false) -[lines!(ax, z_vals[:, i], travelling_wave_values[:, i], color=colors[i], linewidth=2) for i in 1:(length(sol)-large_time_idx+1)] +ax = Axis(fig[1, 4], width = 900, height = 600) +colors = cgrad(:matter, length(sol) - large_time_idx + 1; categorical = false) +[lines!(ax, z_vals[:, i], travelling_wave_values[:, i], color = colors[i], linewidth = 2) + for i in 1:(length(sol) - large_time_idx + 1)] exact_z_vals = collect(LinRange(extrema(z_vals)..., 500)) exact_travelling_wave_values = exact_solution.(exact_z_vals) -lines!(ax, exact_z_vals, exact_travelling_wave_values, color=:red, linewidth=4, linestyle=:dash) +lines!(ax, exact_z_vals, exact_travelling_wave_values, + color = :red, linewidth = 4, linestyle = :dash) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/porous_medium_equation.md b/docs/src/tutorials/porous_medium_equation.md index 43f0409..a80b9ca 100644 --- a/docs/src/tutorials/porous_medium_equation.md +++ b/docs/src/tutorials/porous_medium_equation.md @@ -9,30 +9,37 @@ nothing #hide ```` # Porous-Medium Equation + ## No source + In this tutorial, we consider the porous-medium equation, given by + ```math \pdv{u}{t} = D\div[u^{m-1}\grad u], ``` + with initial condition $u(\vb x, 0) = M\delta(\vb x)$ where $\delta(\vb x)$ is the Dirac delta function and $M = \iint_{\mathbb R^2} u(\vb x, t) \dd{A}$. The diffusion function for this problem is $D(\vb x, t, u) = Du^{m-1}$. To approximate $\delta(\vb x)$, we use + ```math \delta(\vb x) \approx g(\vb x) = \frac{1}{\varepsilon^2\pi}\exp\left[-\frac{1}{\varepsilon^2}\left(x^2 + y^2\right)\right], ``` + taking $\varepsilon = 0.1$. It can be shown[^1] that $u(\vb x, t)$ is zero for $x^2 + y^2 \geq R_{m, M}(Dt)^{1/m}$, where + ```math R_{m, M} = \left(\frac{4m}{m-1}\right)\left[\frac{M}{4\pi}\right]^{(m-1)/m}, ``` + so we can replace the domain $\mathbb R^2$ with the domain $\Omega = [-L, L]^2$ where $L = R_{m, M}^{1/2}(DT)^{1/2m}$ and $T$ is the time that we solve up. We use a Dirichlet boundary condition on $\partial\Omega$. [^1]: This comes from the exact solution that we define in the [overview](overview.md). - Let us now solve this problem, taking $m = 2$, $M = 0.37$, $D = 2.53$, and $T = 12$. ````@example porous_medium_equation @@ -47,7 +54,7 @@ final_time = 12.0 # Step 1: Define the mesh RmM = 4m / (m - 1) * (M / (4π))^((m - 1) / m) L = sqrt(RmM) * (D * final_time)^(1 / (2m)) -tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary=true) +tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary = true) mesh = FVMGeometry(tri) ```` @@ -72,20 +79,21 @@ prob = FVMProblem(mesh, BCs; ````@example porous_medium_equation # Step 4: Solve using LinearSolve, OrdinaryDiffEq -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()); saveat=3.0) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 3.0) sol |> tc #hide ```` ````@example porous_medium_equation # Step 5: Visualise using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 3, 5)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.005:0.05, colormap=:matter, extendhigh=:auto) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!( + ax, tri, sol.u[j], levels = 0:0.005:0.05, colormap = :matter, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -93,19 +101,26 @@ fig ```` ## Linear source + Let us now extend the problem above so that a linear source is now included: + ```math \pdv{u}{t} = D\div [u^{m-1}\grad u] + \lambda u, \quad \lambda > 0. ``` + We again let the initial condition be $u(\vb x, 0) = M\delta(\vb x)$. For the domain, we use + ```math \Omega = \left[-R_{m, M}^{1/2}\tau(T)^{1/2m}, R_{m,M}^{1/2}\tau(T)^{1/2m}\right]^2, ``` + where + ```math \tau(T) = \frac{D}{\lambda(m-1)}\left[\mathrm{e}^{\lambda(m-1)T}-1\right]. ``` + The code below solves this problem. ````@example porous_medium_equation @@ -119,7 +134,7 @@ final_time = 10.0 # Step 1: Define the mesh RmM = 4m / (m - 1) * (M / (4π))^((m - 1) / m) L = sqrt(RmM) * (D / (λ * (m - 1)) * (exp(λ * (m - 1) * final_time) - 1))^(1 / (2m)) -tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary=true) +tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary = true) mesh = FVMGeometry(tri) ```` @@ -135,7 +150,7 @@ BCs = BoundaryConditions(mesh, bc, type) f = (x, y) -> M * 1 / (ε^2 * π) * exp(-1 / (ε^2) * (x^2 + y^2)) diffusion_function = (x, y, t, u, p) -> p.D * abs(u)^(p.m - 1) source_function = (x, y, t, u, λ) -> λ * u -diffusion_parameters = (D=D, m=m) +diffusion_parameters = (D = D, m = m) source_parameters = λ initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs; @@ -149,19 +164,20 @@ prob = FVMProblem(mesh, BCs; ````@example porous_medium_equation # Step 4: Solve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()); saveat=2.5) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 2.5) sol |> tc #hide ```` ````@example porous_medium_equation # Step 5: Visualise -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 3, 5)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.05:1, extendlow=:auto, colormap=:matter, extendhigh=:auto) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:0.05:1, extendlow = :auto, + colormap = :matter, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -169,6 +185,7 @@ fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/porous_medium_equation.jl). @@ -184,7 +201,7 @@ final_time = 12.0 # Step 1: Define the mesh RmM = 4m / (m - 1) * (M / (4π))^((m - 1) / m) L = sqrt(RmM) * (D * final_time)^(1 / (2m)) -tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary=true) +tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary = true) mesh = FVMGeometry(tri) # Step 2: Define the boundary conditions @@ -203,17 +220,18 @@ prob = FVMProblem(mesh, BCs; # Step 4: Solve using LinearSolve, OrdinaryDiffEq -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()); saveat=3.0) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 3.0) # Step 5: Visualise using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 3, 5)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.005:0.05, colormap=:matter, extendhigh=:auto) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!( + ax, tri, sol.u[j], levels = 0:0.005:0.05, colormap = :matter, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -229,7 +247,7 @@ final_time = 10.0 # Step 1: Define the mesh RmM = 4m / (m - 1) * (M / (4π))^((m - 1) / m) L = sqrt(RmM) * (D / (λ * (m - 1)) * (exp(λ * (m - 1) * final_time) - 1))^(1 / (2m)) -tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary=true) +tri = triangulate_rectangle(-L, L, -L, L, 125, 125, single_boundary = true) mesh = FVMGeometry(tri) # Step 2: Define the boundary conditions @@ -241,7 +259,7 @@ BCs = BoundaryConditions(mesh, bc, type) f = (x, y) -> M * 1 / (ε^2 * π) * exp(-1 / (ε^2) * (x^2 + y^2)) diffusion_function = (x, y, t, u, p) -> p.D * abs(u)^(p.m - 1) source_function = (x, y, t, u, λ) -> λ * u -diffusion_parameters = (D=D, m=m) +diffusion_parameters = (D = D, m = m) source_parameters = λ initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] prob = FVMProblem(mesh, BCs; @@ -253,23 +271,23 @@ prob = FVMProblem(mesh, BCs; final_time) # Step 4: Solve -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()); saveat=2.5) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 2.5) # Step 5: Visualise -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 3, 5)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=0:0.05:1, extendlow=:auto, colormap=:matter, extendhigh=:auto) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 0:0.05:1, extendlow = :auto, + colormap = :matter, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/reaction_diffusion_brusselator_system_of_pdes.md b/docs/src/tutorials/reaction_diffusion_brusselator_system_of_pdes.md index 4149de6..045a8d3 100644 --- a/docs/src/tutorials/reaction_diffusion_brusselator_system_of_pdes.md +++ b/docs/src/tutorials/reaction_diffusion_brusselator_system_of_pdes.md @@ -9,8 +9,10 @@ nothing #hide ```` # A Reaction-Diffusion Brusselator System of PDEs + In this tutorial, we show how we can solve systems of PDEs. We consider the reaction-diffusion Brusselator system: + ```math \begin{equation}\label{eq:brusleeq} \begin{aligned} @@ -19,8 +21,10 @@ We consider the reaction-diffusion Brusselator system: \end{aligned} \end{equation} ``` + Since this is a somewhat contrived example, we will be using the exact solution to define sensible initial and boundary conditions:[^1] + ```math \begin{equation}\label{eq:brusleexct} \begin{aligned} @@ -29,9 +33,10 @@ solution to define sensible initial and boundary conditions:[^1] \end{aligned} \end{equation} ``` + [^1]: See [Islam, Ali, and Haq (2010)](https://doi.org/10.1016/j.apm.2010.03.028). -We can use these exact solutions \eqref{eq:brusleexct} to also show how we can mix boundary conditions. -We use: + We can use these exact solutions \eqref{eq:brusleexct} to also show how we can mix boundary conditions. + We use: ```math \begin{equation*} \begin{aligned} @@ -48,6 +53,7 @@ We use: \end{aligned} \end{equation*} ``` + For implementing these equations, we need to write the Neumann boundary conditions in the forms $\vb q_1 \vdot \vu n = f(\vb x, t)$ and $\vb q_2 \vdot \vu n = f(\vb x, t)$, @@ -56,6 +62,7 @@ So, we need to rewrite \eqref{eq:brusleeq} in the conservation form; previously, we've also allowed for reaction-diffusion formulations, but unfortunately we do not allow this specification for systems due to some technical limitations. We can write \eqref{eq:brusleeq} in the conservation form as follows: + ```math \begin{equation} \begin{aligned} @@ -64,16 +71,20 @@ We can write \eqref{eq:brusleeq} in the conservation form as follows: \end{aligned} \end{equation} ``` + where $\vb q_1 = -\grad\Phi/4$, $S_1 = \Phi^2\Psi - 2\Phi$, $\vb q_2 = -\grad\Psi/4$, and $S_2 = -\Phi^2\Psi + \Phi$. Now that we have these flux functions, let us rewrite our boundary conditions. Remember that $\vu n$ is the outward unit normal, so for example on the bottom boundary we have + ```math \vb q_1 \vdot \vu n = -\frac14\grad\Phi \vdot -\vu j = \frac{1}{4}\pdv{\Phi}{y}. ``` + The normal vectors are $-\vu j$, $\vu i$, $\vu j$, and $-\vu i$ for the bottom, right, top, and left sides of the square, respectively. So, our boundary become: + ```math \begin{equation*} \begin{aligned} @@ -93,7 +104,7 @@ our boundary become: ````@example reaction_diffusion_brusselator_system_of_pdes using FiniteVolumeMethod, DelaunayTriangulation -tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary=false) +tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary = false) mesh = FVMGeometry(tri) ```` @@ -154,13 +165,13 @@ nothing #hide Next, we can define the `FVMProblem`s for each variable. ````@example reaction_diffusion_brusselator_system_of_pdes -Φ_prob = FVMProblem(mesh, Φ_BCs; flux_function=Φ_q, source_function=Φ_S, - initial_condition=Φ₀, final_time=5.0) +Φ_prob = FVMProblem(mesh, Φ_BCs; flux_function = Φ_q, source_function = Φ_S, + initial_condition = Φ₀, final_time = 5.0) ```` ````@example reaction_diffusion_brusselator_system_of_pdes -Ψ_prob = FVMProblem(mesh, Ψ_BCs; flux_function=Ψ_q, source_function=Ψ_S, - initial_condition=Ψ₀, final_time=5.0) +Ψ_prob = FVMProblem(mesh, Ψ_BCs; flux_function = Ψ_q, source_function = Ψ_S, + initial_condition = Ψ₀, final_time = 5.0) ```` Finally, the `FVMSystem` is constructed by these two problems: @@ -173,7 +184,7 @@ We can now solve the problem just as we've done previously. ````@example reaction_diffusion_brusselator_system_of_pdes using OrdinaryDiffEq, LinearSolve -sol = solve(system, TRBDF2(linsolve=KLUFactorization()), saveat=1.0) +sol = solve(system, TRBDF2(linsolve = KLUFactorization()), saveat = 1.0) sol |> tc #hide ```` @@ -196,28 +207,29 @@ are the values of $\Psi$ at the third time. We can visualise the solutions as fo ````@example reaction_diffusion_brusselator_system_of_pdes using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for i in eachindex(sol) - ax1 = Axis(fig[1, i], xlabel=L"x", ylabel=L"y", - width=400, height=400, - title=L"\Phi: t = %$(sol.t[i])", titlealign=:left) - ax2 = Axis(fig[2, i], xlabel=L"x", ylabel=L"y", - width=400, height=400, - title=L"\Psi: t = %$(sol.t[i])", titlealign=:left) - tricontourf!(ax1, tri, sol[i][1, :], levels=0:0.1:1, colormap=:matter) - tricontourf!(ax2, tri, sol[i][2, :], levels=1:10:100, colormap=:matter) + ax1 = Axis(fig[1, i], xlabel = L"x", ylabel = L"y", + width = 400, height = 400, + title = L"\Phi: t = %$(sol.t[i])", titlealign = :left) + ax2 = Axis(fig[2, i], xlabel = L"x", ylabel = L"y", + width = 400, height = 400, + title = L"\Psi: t = %$(sol.t[i])", titlealign = :left) + tricontourf!(ax1, tri, sol[i][1, :], levels = 0:0.1:1, colormap = :matter) + tricontourf!(ax2, tri, sol[i][2, :], levels = 1:10:100, colormap = :matter) end resize_to_layout!(fig) fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/reaction_diffusion_brusselator_system_of_pdes.jl). ```julia using FiniteVolumeMethod, DelaunayTriangulation -tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary=false) +tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary = false) mesh = FVMGeometry(tri) Φ_bot = (x, y, t, u, p) -> -1 / 4 * exp(-x - t / 2) @@ -246,38 +258,37 @@ mesh = FVMGeometry(tri) Φ₀ = [Φ_exact(x, y, 0) for (x, y) in DelaunayTriangulation.each_point(tri)] Ψ₀ = [Ψ_exact(x, y, 0) for (x, y) in DelaunayTriangulation.each_point(tri)]; -Φ_prob = FVMProblem(mesh, Φ_BCs; flux_function=Φ_q, source_function=Φ_S, - initial_condition=Φ₀, final_time=5.0) +Φ_prob = FVMProblem(mesh, Φ_BCs; flux_function = Φ_q, source_function = Φ_S, + initial_condition = Φ₀, final_time = 5.0) -Ψ_prob = FVMProblem(mesh, Ψ_BCs; flux_function=Ψ_q, source_function=Ψ_S, - initial_condition=Ψ₀, final_time=5.0) +Ψ_prob = FVMProblem(mesh, Ψ_BCs; flux_function = Ψ_q, source_function = Ψ_S, + initial_condition = Ψ₀, final_time = 5.0) system = FVMSystem(Φ_prob, Ψ_prob) using OrdinaryDiffEq, LinearSolve -sol = solve(system, TRBDF2(linsolve=KLUFactorization()), saveat=1.0) +sol = solve(system, TRBDF2(linsolve = KLUFactorization()), saveat = 1.0) sol.u[3] sol.u[3][1, :] using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for i in eachindex(sol) - ax1 = Axis(fig[1, i], xlabel=L"x", ylabel=L"y", - width=400, height=400, - title=L"\Phi: t = %$(sol.t[i])", titlealign=:left) - ax2 = Axis(fig[2, i], xlabel=L"x", ylabel=L"y", - width=400, height=400, - title=L"\Psi: t = %$(sol.t[i])", titlealign=:left) - tricontourf!(ax1, tri, sol[i][1, :], levels=0:0.1:1, colormap=:matter) - tricontourf!(ax2, tri, sol[i][2, :], levels=1:10:100, colormap=:matter) + ax1 = Axis(fig[1, i], xlabel = L"x", ylabel = L"y", + width = 400, height = 400, + title = L"\Phi: t = %$(sol.t[i])", titlealign = :left) + ax2 = Axis(fig[2, i], xlabel = L"x", ylabel = L"y", + width = 400, height = 400, + title = L"\Psi: t = %$(sol.t[i])", titlealign = :left) + tricontourf!(ax1, tri, sol[i][1, :], levels = 0:0.1:1, colormap = :matter) + tricontourf!(ax2, tri, sol[i][2, :], levels = 1:10:100, colormap = :matter) end resize_to_layout!(fig) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.md b/docs/src/tutorials/reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.md index 68d7547..61d4193 100644 --- a/docs/src/tutorials/reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.md +++ b/docs/src/tutorials/reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.md @@ -9,8 +9,10 @@ nothing #hide ```` # Reaction-Diffusion Equation with a Time-dependent Dirichlet Boundary Condition on a Disk + In this tutorial, we consider a reaction-diffusion equation on a disk with a boundary condition of the form $\mathrm du/\mathrm dt = u$: + ```math \begin{equation*} \begin{aligned} @@ -20,12 +22,15 @@ u(r,\theta,0) &= \sqrt{I_0(\sqrt{2}r)} & 0 u * (1 - u) initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 0.10 prob = FVMProblem(mesh, BCs; - diffusion_function=D, - source_function=R, + diffusion_function = D, + source_function = R, final_time, initial_condition) ```` @@ -69,19 +74,19 @@ We can now solve. ````@example reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk using OrdinaryDiffEq, LinearSolve -alg = FBDF(linsolve=UMFPACKFactorization(), autodiff=false) -sol = solve(prob, alg, saveat=0.01) +alg = FBDF(linsolve = UMFPACKFactorization(), autodiff = false) +sol = solve(prob, alg, saveat = 0.01) sol |> tc #hide ```` ````@example reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=1:0.01:1.4, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 1:0.01:1.4, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) @@ -89,6 +94,7 @@ fig ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.jl). @@ -100,7 +106,7 @@ points = NTuple{2, Float64}[] boundary_nodes = [circle] tri = triangulate(points; boundary_nodes) A = get_area(tri) -refine!(tri; max_area=1e-4A) +refine!(tri; max_area = 1e-4A) mesh = FVMGeometry(tri) using CairoMakie @@ -115,29 +121,28 @@ R = (x, y, t, u, p) -> u * (1 - u) initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 0.10 prob = FVMProblem(mesh, BCs; - diffusion_function=D, - source_function=R, + diffusion_function = D, + source_function = R, final_time, initial_condition) using OrdinaryDiffEq, LinearSolve -alg = FBDF(linsolve=UMFPACKFactorization(), autodiff=false) -sol = solve(prob, alg, saveat=0.01) +alg = FBDF(linsolve = UMFPACKFactorization(), autodiff = false) +sol = solve(prob, alg, saveat = 0.01) -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) - tricontourf!(ax, tri, sol.u[j], levels=1:0.01:1.4, colormap=:matter) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) + tricontourf!(ax, tri, sol.u[j], levels = 1:0.01:1.4, colormap = :matter) tightlimits!(ax) end resize_to_layout!(fig) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/tutorials/solving_mazes_with_laplaces_equation.md b/docs/src/tutorials/solving_mazes_with_laplaces_equation.md index 30af518..5640e40 100644 --- a/docs/src/tutorials/solving_mazes_with_laplaces_equation.md +++ b/docs/src/tutorials/solving_mazes_with_laplaces_equation.md @@ -9,12 +9,14 @@ nothing #hide ```` # Solving Mazes with Laplace's Equation + In this tutorial, we consider solving mazes using Laplace's equation, applying the result of [Conolly, Burns, and Weis (1990)](https://doi.org/10.1109/ROBOT.1990.126315). In particular, given a maze $\mathcal M$, represented as a collection of edges together with some starting point $\mathcal S_1$ and an endpoint $\mathcal S_2$, Laplace's equation can be used to find the solution: + ```math \begin{equation} \begin{aligned} @@ -25,17 +27,18 @@ Laplace's equation can be used to find the solution: \end{aligned} \end{equation} ``` + The gradient $\grad\phi$ will reveal the solution to the maze. We just look at $\|\grad\phi\|$ for revealing this solution, although other methods could e.g. use $\grad\phi$ to follow the associated streamlines. - Here is what the maze +Here is what the maze looks like, where the start is in blue and the end is in red. ````@example solving_mazes_with_laplaces_equation using DelaunayTriangulation, CairoMakie, DelimitedFiles A = readdlm(joinpath(@__DIR__, "../tutorials/maze.txt")) -A = unique(A, dims=1) +A = unique(A, dims = 1) x = A[1:10:end, 2] # downsample to make the problem faster y = A[1:10:end, 1] start = findall(y .== 648) @@ -57,10 +60,10 @@ tri = triangulate(points; boundary_nodes) # takes a while because maze.txt conta refine!(tri) fig, ax, sc, = triplot(tri, - show_convex_hull=false, - show_constrained_edges=false) -lines!(ax, [get_point(tri, get_boundary_nodes(tri, 1)...)...], color=:blue, linewidth=6) -lines!(ax, [get_point(tri, get_boundary_nodes(tri, 3)...)...], color=:red, linewidth=6) + show_convex_hull = false, + show_constrained_edges = false) +lines!(ax, [get_point(tri, get_boundary_nodes(tri, 1)...)...], color = :blue, linewidth = 6) +lines!(ax, [get_point(tri, get_boundary_nodes(tri, 3)...)...], color = :red, linewidth = 6) fig ```` @@ -80,22 +83,22 @@ diffusion_function = (x, y, t, u, p) -> one(u) initial_condition = 0.05randn(StableRNG(123), DelaunayTriangulation.num_points(tri)) # random initial condition - this is the initial guess for the solution final_time = Inf prob = FVMProblem(mesh, BCs; - diffusion_function=diffusion_function, - initial_condition=initial_condition, - final_time=final_time) + diffusion_function = diffusion_function, + initial_condition = initial_condition, + final_time = final_time) steady_prob = SteadyFVMProblem(prob) ```` ````@example solving_mazes_with_laplaces_equation using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq -sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization(), autodiff=false))) +sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization(), autodiff = false))) sol |> tc #hide ```` We now have our solution. ````@example solving_mazes_with_laplaces_equation -tricontourf(tri, sol.u, colormap=:matter) +tricontourf(tri, sol.u, colormap = :matter) ```` This is not what we use to compute the solution to the maze, @@ -104,10 +107,10 @@ NaturalNeighbours.jl. ````@example solving_mazes_with_laplaces_equation using NaturalNeighbours, LinearAlgebra -itp = interpolate(tri, sol.u; derivatives=true) +itp = interpolate(tri, sol.u; derivatives = true) ∇ = NaturalNeighbours.get_gradient(itp) ∇norms = norm.(∇) -tricontourf(tri, ∇norms, colormap=:matter) +tricontourf(tri, ∇norms, colormap = :matter) ```` The solution to the maze is now extremely clear from this plot! @@ -120,33 +123,38 @@ steady state problem and instead view the solution over time. using Accessors prob = @set prob.final_time = 1e8 LogRange(a, b, n) = exp10.(LinRange(log10(a), log10(b), n)) -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=LogRange(1e2, prob.final_time, 24 * 10)) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = LogRange(1e2, prob.final_time, 24 * + 10)) all_∇norms = map(sol.u) do u - itp = interpolate(tri, u; derivatives=true) + itp = interpolate(tri, u; derivatives = true) ∇ = NaturalNeighbours.get_gradient(itp) norm.(∇) end i = Observable(1) ∇norms = map(i -> all_∇norms[i], i) -fig, ax, sc = tricontourf(tri, ∇norms, colormap=:matter, levels=LinRange(0, 0.0035, 25), extendlow=:auto, extendhigh=:auto) +fig, ax, +sc = tricontourf(tri, ∇norms, colormap = :matter, levels = LinRange(0, 0.0035, 25), + extendlow = :auto, extendhigh = :auto) hidedecorations!(ax) tightlimits!(ax) record(fig, joinpath(@__DIR__, "../figures", "maze_solution_1.mp4"), eachindex(sol); - framerate=24) do _i + framerate = 24) do _i i[] = _i end; nothing #hide ```` ![Animation of the solution of the maze](../figures/maze_solution_1.mp4) + ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_tutorials/solving_mazes_with_laplaces_equation.jl). ```julia using DelaunayTriangulation, CairoMakie, DelimitedFiles A = readdlm(joinpath(@__DIR__, "../tutorials/maze.txt")) -A = unique(A, dims=1) +A = unique(A, dims = 1) x = A[1:10:end, 2] # downsample to make the problem faster y = A[1:10:end, 1] start = findall(y .== 648) @@ -168,10 +176,10 @@ tri = triangulate(points; boundary_nodes) # takes a while because maze.txt conta refine!(tri) fig, ax, sc, = triplot(tri, - show_convex_hull=false, - show_constrained_edges=false) -lines!(ax, [get_point(tri, get_boundary_nodes(tri, 1)...)...], color=:blue, linewidth=6) -lines!(ax, [get_point(tri, get_boundary_nodes(tri, 3)...)...], color=:red, linewidth=6) + show_convex_hull = false, + show_constrained_edges = false) +lines!(ax, [get_point(tri, get_boundary_nodes(tri, 1)...)...], color = :blue, linewidth = 6) +lines!(ax, [get_point(tri, get_boundary_nodes(tri, 3)...)...], color = :red, linewidth = 6) fig using FiniteVolumeMethod, StableRNGs @@ -187,43 +195,45 @@ diffusion_function = (x, y, t, u, p) -> one(u) initial_condition = 0.05randn(StableRNG(123), DelaunayTriangulation.num_points(tri)) # random initial condition - this is the initial guess for the solution final_time = Inf prob = FVMProblem(mesh, BCs; - diffusion_function=diffusion_function, - initial_condition=initial_condition, - final_time=final_time) + diffusion_function = diffusion_function, + initial_condition = initial_condition, + final_time = final_time) steady_prob = SteadyFVMProblem(prob) using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq -sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization(), autodiff=false))) +sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization(), autodiff = false))) -tricontourf(tri, sol.u, colormap=:matter) +tricontourf(tri, sol.u, colormap = :matter) using NaturalNeighbours, LinearAlgebra -itp = interpolate(tri, sol.u; derivatives=true) +itp = interpolate(tri, sol.u; derivatives = true) ∇ = NaturalNeighbours.get_gradient(itp) ∇norms = norm.(∇) -tricontourf(tri, ∇norms, colormap=:matter) +tricontourf(tri, ∇norms, colormap = :matter) using Accessors prob = @set prob.final_time = 1e8 LogRange(a, b, n) = exp10.(LinRange(log10(a), log10(b), n)) -sol = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=LogRange(1e2, prob.final_time, 24 * 10)) +sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = LogRange(1e2, prob.final_time, 24 * + 10)) all_∇norms = map(sol.u) do u - itp = interpolate(tri, u; derivatives=true) + itp = interpolate(tri, u; derivatives = true) ∇ = NaturalNeighbours.get_gradient(itp) norm.(∇) end i = Observable(1) ∇norms = map(i -> all_∇norms[i], i) -fig, ax, sc = tricontourf(tri, ∇norms, colormap=:matter, levels=LinRange(0, 0.0035, 25), extendlow=:auto, extendhigh=:auto) +fig, ax, +sc = tricontourf(tri, ∇norms, colormap = :matter, levels = LinRange(0, 0.0035, 25), + extendlow = :auto, extendhigh = :auto) hidedecorations!(ax) tightlimits!(ax) record(fig, joinpath(@__DIR__, "../figures", "maze_solution_1.mp4"), eachindex(sol); - framerate=24) do _i + framerate = 24) do _i i[] = _i end; ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/wyos/diffusion_equations.md b/docs/src/wyos/diffusion_equations.md index d0c02d4..d297163 100644 --- a/docs/src/wyos/diffusion_equations.md +++ b/docs/src/wyos/diffusion_equations.md @@ -9,14 +9,18 @@ nothing #hide ```` # Diffusion Equations + ```@contents Pages = ["diffusion_equations.md"] ``` + We start by writing a specialised solver for solving diffusion equations. What we produce in this section can also be accessed in `FiniteVolumeMethod.DiffusionEquation`. ## Mathematical Details + Let us start by considering the mathematical details. The equation we consider is + ```math \begin{equation} \begin{aligned} @@ -24,23 +28,31 @@ Let us start by considering the mathematical details. The equation we consider i \end{aligned} \end{equation} ``` + From the [mathematical details section](../math.md) (where we also define the notation that follows), we know that discretising this problem leads to an equation of the form + ```math \dv{u_i}{t} + \frac{1}{V_i}\sum_{\sigma\in\mathcal E_i}\left[\vb q\left(\vb x_\sigma, t, \alpha_{k(\sigma)}x_\sigma+\beta_{k(\sigma)}y_\sigma+\gamma_{k(\sigma)}\right)\vdot\vu n\right]L_\sigma = S_i, ``` + For the diffusion equation, the flux function is $\vb q = -D\grad u$, meaning for an interior node we have + ```math \vb q(\vb x_\sigma, t, \alpha_{k(\sigma)}x_\sigma+\beta_{k(\sigma)}y_\sigma+\gamma_{k(\sigma)}) = -D(\vb x_\sigma)(\alpha_{k(\sigma)}, \beta_{k(\sigma)})^{\mkern-1.5mu\mathsf{T}}. ``` + Thus, also using $S_i=0$, + ```math \dv{u_i}{t} = \frac{1}{V_i}\sum_{\sigma\in\mathcal E_i} D(\vb x_\sigma)\left[\alpha_{k(\sigma)}n_\sigma^x + \beta_{k(\sigma)}n_\sigma^y\right]L_\sigma, ``` + where $\vu n = (n_\sigma^x, n_\sigma^y)^{\mkern-1.5mu\mathsf{T}}$. It is still not immediately obvious how we can turn this into a linear problem. To see the linearity, note that + ```math \begin{equation} \begin{aligned} @@ -49,7 +61,9 @@ note that \end{aligned} \end{equation} ``` + thus, now writing $k=k(\sigma)$ for simplicity, + ```math \begin{equation*} \begin{aligned} @@ -59,48 +73,53 @@ thus, now writing $k=k(\sigma)$ for simplicity, \end{aligned} \end{equation*} ``` + Now, the result + ```math \begin{equation}\label{eq:disc1} \dv{u_i}{t} = \vb a_i^{\mkern-1.5mu\mathsf{T}}\vb u + b_i, \end{equation} ``` + where $b_i=0$, is for the case that $i$ is an interior node. We need to think about how boundary conditions get incorporated. For this problem, we will not allow the boundary conditions to depend on $u$ or $t$.[^1] [^1]: It would be fine to allow the boundary conditions to depend on $t$ - we would still have linearity. The issue would just be that we need to reconstruct the matrix at every time step. So, for simplicity, let's not allow it so that the template we build is efficient for the most common case (where there is no $t$ dependence). - Let's think about what we each type of boundary condition would to our problem. - 1. For a Dirichlet boundary condition, we have $u_i = a(\vb x_i)$ for some $\vb x_i$. - To implement this, we let the $i$th row of $\vb A$ be zero and $b_i=0$. Then, - as long as we start the Dirichlet nodes at $u_i=a(\vb x_i)$, they will stay at - that value as $u_i' = 0$ there.[^2] - 2. Suppose we have a Neumann boundary condition, say $\grad u \vdot \vu n = a(\vb x)$, - we need to write the sum over $\sigma \in \mathcal E_i$ so that the differences - between the boundary edges and the interior edges are made explicit. Over these - boundary edges, we get sums that go into $\vb b$ rather than into $\vb A$. - 3. For conditions of the form $\mathrm du_i/\mathrm dt = a(\vb x_i)$, we should just - set $\vb a_i = \vb 0$ and $b_i = a(\vb x_i)$. Note that here $\vb A$ is singular. -[^2]: If the boundary condition was non-autonomous, we could use a mass matrix instead, or build the condition into $\vb A$ and $\vb b$ directly by using the exact values of $u$ where applicable. + 1. For a Dirichlet boundary condition, we have $u_i = a(\vb x_i)$ for some $\vb x_i$. + To implement this, we let the $i$th row of $\vb A$ be zero and $b_i=0$. Then, + as long as we start the Dirichlet nodes at $u_i=a(\vb x_i)$, they will stay at + that value as $u_i' = 0$ there.[^2] + 2. Suppose we have a Neumann boundary condition, say $\grad u \vdot \vu n = a(\vb x)$, + we need to write the sum over $\sigma \in \mathcal E_i$ so that the differences + between the boundary edges and the interior edges are made explicit. Over these + boundary edges, we get sums that go into $\vb b$ rather than into $\vb A$. + 3. For conditions of the form $\mathrm du_i/\mathrm dt = a(\vb x_i)$, we should just + set $\vb a_i = \vb 0$ and $b_i = a(\vb x_i)$. Note that here $\vb A$ is singular. +[^2]: If the boundary condition was non-autonomous, we could use a mass matrix instead, or build the condition into $\vb A$ and $\vb b$ directly by using the exact values of $u$ where applicable. ## Implementation + We now know enough to implement our solver. Let us walk through this slowly, defining our function and then iterating it slowly to incorporate different features. The function signature will be similar to how we define an `FVMProblem`, namely + ```julia function diffusion_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time) # return the ODEProblem end ``` + For the boundary and internal conditions, we'll assume that the functions take the same form, i.e. `(x, y, t, u, p) -> Number`, but the `t` and `u` arguments will both be passed as `nothing`. The diffusion function should be of the form `(x, y, p) -> Number`, @@ -117,7 +136,8 @@ Let us start by writing out the contribution from all the triangles. ````@example diffusion_equations using FiniteVolumeMethod const FVM = FiniteVolumeMethod -function triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) +function triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) for T in each_solid_triangle(mesh.triangulation) ijk = triangle_vertices(T) i, j, k = ijk @@ -145,7 +165,7 @@ Now we need the function that gets the contributions from the boundary edges. ````@example diffusion_equations function boundary_edge_contributions!(A, b, mesh, conditions, - diffusion_function, diffusion_parameters) + diffusion_function, diffusion_parameters) for e in keys(get_boundary_edge_map(mesh.triangulation)) i, j = DelaunayTriangulation.edge_vertices(e) nx, ny, mᵢx, mᵢy, mⱼx, mⱼy, ℓ, T, props = FVM.get_boundary_cv_components(mesh, i, j) @@ -186,14 +206,16 @@ need to worry about e.g. zeroing out rows of $\vb A$ for a node with a boundary function apply_dirichlet_conditions!(initial_condition, mesh, conditions) for (i, function_index) in FVM.get_dirichlet_nodes(conditions) x, y = get_point(mesh, i) - initial_condition[i] = FVM.eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) + initial_condition[i] = FVM.eval_condition_fnc( + conditions, function_index, x, y, nothing, nothing) end end function apply_dudt_conditions!(b, mesh, conditions) for (i, function_index) in FVM.get_dudt_nodes(conditions) if !FVM.is_dirichlet_node(conditions, i) # overlapping edges can be both Dudt and Dirichlet. Dirichlet takes precedence x, y = get_point(mesh, i) - b[i] = FVM.eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) + b[i] = FVM.eval_condition_fnc( + conditions, function_index, x, y, nothing, nothing) end end end @@ -201,40 +223,49 @@ end Now let's define `diffusion_equation`. For this, we note we want to write the problem in the form + ```math \dv{\vb u}{t} = \vb A\vb u ``` + to get the most out of our linearity in OrdinaryDiffEq.jl, whereas we currently have + ```math \dv{\vb u}{t} = \vb A\vb u + \vb b. ``` + To get around this, we define + ```math \tilde{\vb u} = \begin{bmatrix} \vb u \\ 1 \end{bmatrix}, \quad \tilde{\vb A} = \begin{bmatrix}\vb A & \vb b \\ \vb 0^{\mkern-1.5mu\mathsf{T}} & 0 \end{bmatrix}, ``` + so that + ```math \dv{\tilde{\vb u}}{t} = \begin{bmatrix} \vb u' \\ 0 \end{bmatrix} = \begin{bmatrix} \vb A\vb u + \vb b \\ 0 \end{bmatrix} = \tilde{\vb A}\tilde{\vb u}. ``` + Note that this also requires that we append a `1` to the initial condition. ````@example diffusion_equations function diffusion_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) - A = @views Afull[begin:end-1, begin:end-1] - b = @views Afull[begin:end-1, end] + A = @views Afull[begin:(end - 1), begin:(end - 1)] + b = @views Afull[begin:(end - 1), end] _ic = vcat(initial_condition, 1) triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) apply_dudt_conditions!(b, mesh, conditions) apply_dirichlet_conditions!(_ic, mesh, conditions) A_op = MatrixOperator(sparse(Afull)) @@ -247,17 +278,18 @@ Let's now test the function. We use the same problem as in [this tutorial](../tu ````@example diffusion_equations using DelaunayTriangulation, OrdinaryDiffEq, LinearAlgebra, SparseArrays -tri = triangulate_rectangle(0, 2, 0, 2, 50, 50, single_boundary=true) +tri = triangulate_rectangle(0, 2, 0, 2, 50, 50, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(x), Dirichlet) diffusion_function = (x, y, p) -> 1 / 9 -initial_condition = [y ≤ 1.0 ? 50.0 : 0.0 for (x, y) in DelaunayTriangulation.each_point(tri)] +initial_condition = [y ≤ 1.0 ? 50.0 : 0.0 + for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 0.5 prob = diffusion_equation(mesh, BCs; diffusion_function, initial_condition, final_time) -sol = solve(prob, Tsit5(); saveat=0.05) +sol = solve(prob, Tsit5(); saveat = 0.05) sol |> tc #hide ```` @@ -280,14 +312,15 @@ Let's now plot. ````@example diffusion_equations using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) u = j == 1 ? initial_condition : sol.u[j] # sol.u[1] is modified slightly to force the Dirichlet conditions at t = 0 - tricontourf!(ax, tri, u, levels=0:5:50, colormap=:matter, extendlow=:auto, extendhigh=:auto) # don't need to do u[begin:end-1], since tri doesn't have that extra vertex. + tricontourf!(ax, tri, u, levels = 0:5:50, colormap = :matter, + extendlow = :auto, extendhigh = :auto) # don't need to do u[begin:end-1], since tri doesn't have that extra vertex. tightlimits!(ax) end resize_to_layout!(fig) @@ -297,6 +330,7 @@ fig This is exactly the solution we expect! ## Using the Provided Template + Let's now use the built-in `DiffusionEquation()` which implements the above template inside FiniteVolumeMethod.jl. ````@example diffusion_equations @@ -310,7 +344,7 @@ Let's compare `DiffusionEquation` to the `FVMProblem` approach. ````@example diffusion_equations fvm_prob = FVMProblem(mesh, BCs; - diffusion_function=let D = diffusion_function + diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, initial_condition, @@ -319,7 +353,7 @@ fvm_prob = FVMProblem(mesh, BCs; ````julia using BenchmarkTools -@btime solve($diff_eq, $Tsit5(), saveat=$0.05); +@btime solve($diff_eq, $Tsit5(), saveat = $0.05); ```` ```` @@ -328,7 +362,7 @@ using BenchmarkTools ````julia using LinearSolve -@btime solve($fvm_prob, $TRBDF2(linsolve=KLUFactorization()), saveat=$0.05); +@btime solve($fvm_prob, $TRBDF2(linsolve = KLUFactorization()), saveat = $0.05); ```` ```` @@ -338,6 +372,7 @@ using LinearSolve Much better! The `DiffusionEquation` approach is about 10 times faster. To finish this example, let's solve a diffusion equation with constant Neumann boundary conditions: + ```math \begin{equation*} \begin{aligned} @@ -346,11 +381,12 @@ To finish this example, let's solve a diffusion equation with constant Neumann b \end{aligned} \end{equation*} ``` + Here, $\Omega = [0, 320]^2$. ````@example diffusion_equations L = 320.0 -tri = triangulate_rectangle(0, L, 0, L, 100, 100, single_boundary=true) +tri = triangulate_rectangle(0, L, 0, L, 100, 100, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> 2.0, Neumann) diffusion_function = (x, y, p) -> 2.0 @@ -372,19 +408,20 @@ prob = DiffusionEquation(mesh, BCs; Let's solve and plot. ````@example diffusion_equations -sol = solve(prob, Tsit5(); saveat=100.0) +sol = solve(prob, Tsit5(); saveat = 100.0) sol |> tc #hide ```` ````@example diffusion_equations -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for j in eachindex(sol) - ax = Axis(fig[1, j], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) + ax = Axis(fig[1, j], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) u = j == 1 ? initial_condition : sol.u[j] - tricontourf!(ax, tri, u, levels=0:0.1:1, colormap=:turbo, extendlow=:auto, extendhigh=:auto) + tricontourf!(ax, tri, u, levels = 0:0.1:1, colormap = :turbo, + extendlow = :auto, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -399,21 +436,22 @@ $\vb q \vdot \vu n = -4$. Here is a comparison of the two solutions. ````@example diffusion_equations BCs_prob = BoundaryConditions(mesh, (x, y, t, u, p) -> -4, Neumann) fvm_prob = FVMProblem(mesh, BCs_prob; - diffusion_function=let D = diffusion_function + diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, initial_condition, final_time) -fvm_sol = solve(fvm_prob, TRBDF2(linsolve=KLUFactorization()); saveat=100.0) +fvm_sol = solve(fvm_prob, TRBDF2(linsolve = KLUFactorization()); saveat = 100.0) fvm_sol |> tc #hide for j in eachindex(fvm_sol) - ax = Axis(fig[2, j], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(fvm_sol.t[j])", - titlealign=:left) + ax = Axis(fig[2, j], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(fvm_sol.t[j])", + titlealign = :left) u = j == 1 ? initial_condition : fvm_sol.u[j] - tricontourf!(ax, tri, u, levels=0:0.1:1, colormap=:turbo, extendlow=:auto, extendhigh=:auto) + tricontourf!(ax, tri, u, levels = 0:0.1:1, colormap = :turbo, + extendlow = :auto, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -421,8 +459,9 @@ fig ```` Here is a benchmark comparison. + ````julia -@btime solve($prob, $Tsit5(), saveat=$100.0); +@btime solve($prob, $Tsit5(), saveat = $100.0); ```` ```` @@ -431,7 +470,7 @@ Here is a benchmark comparison. ````julia using Sundials -@btime solve($fvm_prob, $CVODE_BDF(linear_solver=:GMRES), saveat=$100.0); +@btime solve($fvm_prob, $CVODE_BDF(linear_solver = :GMRES), saveat = $100.0); ```` ```` @@ -447,13 +486,15 @@ val = pl_interpolate(prob, T, sol.u[3], q[1], q[2]) ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_wyos/diffusion_equations.jl). ```julia using FiniteVolumeMethod const FVM = FiniteVolumeMethod -function triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) +function triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) for T in each_solid_triangle(mesh.triangulation) ijk = triangle_vertices(T) i, j, k = ijk @@ -477,7 +518,7 @@ function triangle_contributions!(A, mesh, conditions, diffusion_function, diffus end function boundary_edge_contributions!(A, b, mesh, conditions, - diffusion_function, diffusion_parameters) + diffusion_function, diffusion_parameters) for e in keys(get_boundary_edge_map(mesh.triangulation)) i, j = DelaunayTriangulation.edge_vertices(e) nx, ny, mᵢx, mᵢy, mⱼx, mⱼy, ℓ, T, props = FVM.get_boundary_cv_components(mesh, i, j) @@ -511,34 +552,37 @@ end function apply_dirichlet_conditions!(initial_condition, mesh, conditions) for (i, function_index) in FVM.get_dirichlet_nodes(conditions) x, y = get_point(mesh, i) - initial_condition[i] = FVM.eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) + initial_condition[i] = FVM.eval_condition_fnc( + conditions, function_index, x, y, nothing, nothing) end end function apply_dudt_conditions!(b, mesh, conditions) for (i, function_index) in FVM.get_dudt_nodes(conditions) if !FVM.is_dirichlet_node(conditions, i) # overlapping edges can be both Dudt and Dirichlet. Dirichlet takes precedence x, y = get_point(mesh, i) - b[i] = FVM.eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) + b[i] = FVM.eval_condition_fnc( + conditions, function_index, x, y, nothing, nothing) end end end function diffusion_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) - A = @views Afull[begin:end-1, begin:end-1] - b = @views Afull[begin:end-1, end] + A = @views Afull[begin:(end - 1), begin:(end - 1)] + b = @views Afull[begin:(end - 1), end] _ic = vcat(initial_condition, 1) triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) apply_dudt_conditions!(b, mesh, conditions) apply_dirichlet_conditions!(_ic, mesh, conditions) A_op = MatrixOperator(sparse(Afull)) @@ -547,31 +591,33 @@ function diffusion_equation(mesh::FVMGeometry, end using DelaunayTriangulation, OrdinaryDiffEq, LinearAlgebra, SparseArrays -tri = triangulate_rectangle(0, 2, 0, 2, 50, 50, single_boundary=true) +tri = triangulate_rectangle(0, 2, 0, 2, 50, 50, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(x), Dirichlet) diffusion_function = (x, y, p) -> 1 / 9 -initial_condition = [y ≤ 1.0 ? 50.0 : 0.0 for (x, y) in DelaunayTriangulation.each_point(tri)] +initial_condition = [y ≤ 1.0 ? 50.0 : 0.0 + for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 0.5 prob = diffusion_equation(mesh, BCs; diffusion_function, initial_condition, final_time) -sol = solve(prob, Tsit5(); saveat=0.05) +sol = solve(prob, Tsit5(); saveat = 0.05) length(sol.u[1]) DelaunayTriangulation.num_solid_vertices(tri) using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) - ax = Axis(fig[1, i], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) + ax = Axis(fig[1, i], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) u = j == 1 ? initial_condition : sol.u[j] # sol.u[1] is modified slightly to force the Dirichlet conditions at t = 0 - tricontourf!(ax, tri, u, levels=0:5:50, colormap=:matter, extendlow=:auto, extendhigh=:auto) # don't need to do u[begin:end-1], since tri doesn't have that extra vertex. + tricontourf!(ax, tri, u, levels = 0:5:50, colormap = :matter, + extendlow = :auto, extendhigh = :auto) # don't need to do u[begin:end-1], since tri doesn't have that extra vertex. tightlimits!(ax) end resize_to_layout!(fig) @@ -583,14 +629,14 @@ diff_eq = DiffusionEquation(mesh, BCs; final_time) fvm_prob = FVMProblem(mesh, BCs; - diffusion_function=let D = diffusion_function + diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, initial_condition, final_time) L = 320.0 -tri = triangulate_rectangle(0, L, 0, L, 100, 100, single_boundary=true) +tri = triangulate_rectangle(0, L, 0, L, 100, 100, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> 2.0, Neumann) diffusion_function = (x, y, p) -> 2.0 @@ -608,16 +654,17 @@ prob = DiffusionEquation(mesh, BCs; initial_condition, final_time) -sol = solve(prob, Tsit5(); saveat=100.0) +sol = solve(prob, Tsit5(); saveat = 100.0) -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for j in eachindex(sol) - ax = Axis(fig[1, j], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])", - titlealign=:left) + ax = Axis(fig[1, j], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])", + titlealign = :left) u = j == 1 ? initial_condition : sol.u[j] - tricontourf!(ax, tri, u, levels=0:0.1:1, colormap=:turbo, extendlow=:auto, extendhigh=:auto) + tricontourf!(ax, tri, u, levels = 0:0.1:1, colormap = :turbo, + extendlow = :auto, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -625,20 +672,21 @@ fig BCs_prob = BoundaryConditions(mesh, (x, y, t, u, p) -> -4, Neumann) fvm_prob = FVMProblem(mesh, BCs_prob; - diffusion_function=let D = diffusion_function + diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, initial_condition, final_time) -fvm_sol = solve(fvm_prob, TRBDF2(linsolve=KLUFactorization()); saveat=100.0) +fvm_sol = solve(fvm_prob, TRBDF2(linsolve = KLUFactorization()); saveat = 100.0) for j in eachindex(fvm_sol) - ax = Axis(fig[2, j], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(fvm_sol.t[j])", - titlealign=:left) + ax = Axis(fig[2, j], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(fvm_sol.t[j])", + titlealign = :left) u = j == 1 ? initial_condition : fvm_sol.u[j] - tricontourf!(ax, tri, u, levels=0:0.1:1, colormap=:turbo, extendlow=:auto, extendhigh=:auto) + tricontourf!(ax, tri, u, levels = 0:0.1:1, colormap = :turbo, + extendlow = :auto, extendhigh = :auto) tightlimits!(ax) end resize_to_layout!(fig) @@ -649,7 +697,6 @@ T = jump_and_march(tri, q) val = pl_interpolate(prob, T, sol.u[3], q[1], q[2]) ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/wyos/laplaces_equation.md b/docs/src/wyos/laplaces_equation.md index a4c4e5a..d3a2149 100644 --- a/docs/src/wyos/laplaces_equation.md +++ b/docs/src/wyos/laplaces_equation.md @@ -9,24 +9,29 @@ nothing #hide ```` # Laplace's Equation + ```@contents Pages = ["laplaces_equation.md"] ``` + Now we consider Laplace's equation. What we produce in this section can also be accessed in `FiniteVolumeMethod.LaplacesEquation`. ## Mathematical Details + The mathematical details for this solver are the same as for our [Poisson equation example](poissons_equation.md), except with $f = 0$. The problems being solved are of the form + ```math \div\left[D(\vb x)\grad u\right] = 0, ``` + known as the generalised Laplace equation.[^1] [^1]: See, for example, [this paper](https://doi.org/10.1016/0307-904X(87)90036-9) by Rangogni and Occhi (1987). - ## Implementation + For the implementation, we can reuse a lot of what we had for Poisson's equation, except that we don't need `create_rhs_b`. @@ -35,16 +40,18 @@ we don't need `create_rhs_b`. using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod function laplaces_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function=(x, y, p) -> 1.0, - diffusion_parameters=nothing) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function = (x, y, p) -> 1.0, + diffusion_parameters = nothing) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) b = zeros(DelaunayTriangulation.num_points(mesh.triangulation)) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) FVM.apply_steady_dirichlet_conditions!(A, b, mesh, conditions) FVM.fix_missing_vertices!(A, b, mesh) Asp = sparse(A) @@ -55,6 +62,7 @@ end Now let's test this problem. We consider Laplace's equation on a sector of an annulus, so that[^2] + ```math \begin{equation*} \begin{aligned} @@ -68,7 +76,6 @@ u(x, y) &= \left(\frac{\pi}{2} - \arctan\frac{y}{x}\right)\arctan\frac{y}{x} & r ``` [^2]: This problem comes from [here](https://sites.millersville.edu/rbuchanan/math467/LaplaceDisk.pdf#Navigation23). - To start, we define our mesh. We need to define each part of the annulus separately, which takes some care. @@ -89,7 +96,7 @@ boundary_x = [lower_x, outer_arc_x, left_x, inner_arc_x] boundary_y = [lower_y, outer_arc_y, left_y, inner_arc_y] boundary_nodes, points = convert_boundary_points_to_indices(boundary_x, boundary_y) tri = triangulate(points; boundary_nodes) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) ```` @@ -112,7 +119,7 @@ BCs = BoundaryConditions(mesh, bc_f, bc_types) Now we can define and solve the problem. ````@example laplaces_equation -prob = laplaces_equation(mesh, BCs, diffusion_function=(x, y, p) -> 1.0) +prob = laplaces_equation(mesh, BCs, diffusion_function = (x, y, p) -> 1.0) prob |> tc #hide ```` @@ -122,9 +129,9 @@ sol |> tc #hide ```` ````@example laplaces_equation -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y", width=600, height=600) -tricontourf!(ax, tri, sol.u, levels=0:0.1:1, colormap=:jet) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y", width = 600, height = 600) +tricontourf!(ax, tri, sol.u, levels = 0:0.1:1, colormap = :jet) resize_to_layout!(fig) fig ```` @@ -135,9 +142,9 @@ We can turn this type of problem into its corresponding `SteadyFVMProblem` as fo initial_condition = zeros(DelaunayTriangulation.num_points(tri)) FVM.apply_dirichlet_conditions!(initial_condition, mesh, Conditions(mesh, BCs, InternalConditions())) # a good initial guess fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; - diffusion_function=(x, y, t, u, p) -> 1.0, + diffusion_function = (x, y, t, u, p) -> 1.0, initial_condition, - final_time=Inf)) + final_time = Inf)) ```` ````@example laplaces_equation @@ -147,18 +154,18 @@ fvm_sol |> tc #hide ```` ````@example laplaces_equation -ax = Axis(fig[1, 2], xlabel="x", ylabel="y", width=600, height=600) -tricontourf!(ax, tri, fvm_sol.u, levels=0:0.1:1, colormap=:jet) +ax = Axis(fig[1, 2], xlabel = "x", ylabel = "y", width = 600, height = 600) +tricontourf!(ax, tri, fvm_sol.u, levels = 0:0.1:1, colormap = :jet) resize_to_layout!(fig) fig ```` ## Using the Provided Template + Let's now use the built-in `LaplacesEquation` which implements the above inside FiniteVolumeMethod.jl. We consider the problem[^3] [^3]: This is the first example from [this paper](https://doi.org/10.1016/0307-904X(87)90036-9) by Rangogni and Occhi (1987). - ```math \begin{equation*} \begin{aligned} @@ -174,7 +181,7 @@ where $D(\vb x) = (x+1)(y+2)$. The exact solution is $u(x, y) = 5\log_6(1+x)$. We define this problem as follows. ````@example laplaces_equation -tri = triangulate_rectangle(0, 5, 0, 5, 100, 100, single_boundary=false) +tri = triangulate_rectangle(0, 5, 0, 5, 100, 100, single_boundary = false) mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> 0.0 five_f = (x, y, t, u, p) -> 5.0 @@ -191,16 +198,16 @@ sol |> tc #hide ```` ````@example laplaces_equation -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y", - width=600, height=600, - title="Numerical", titlealign=:left) -tricontourf!(ax, tri, sol.u, levels=0:0.25:5, colormap=:jet) -ax = Axis(fig[1, 2], xlabel="x", ylabel="y", - width=600, height=600, - title="Exact", titlealign=:left) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y", + width = 600, height = 600, + title = "Numerical", titlealign = :left) +tricontourf!(ax, tri, sol.u, levels = 0:0.25:5, colormap = :jet) +ax = Axis(fig[1, 2], xlabel = "x", ylabel = "y", + width = 600, height = 600, + title = "Exact", titlealign = :left) u_exact = [5log(1 + x) / log(6) for (x, y) in DelaunayTriangulation.each_point(tri)] -tricontourf!(ax, tri, u_exact, levels=0:0.25:5, colormap=:jet) +tricontourf!(ax, tri, u_exact, levels = 0:0.25:5, colormap = :jet) resize_to_layout!(fig) fig ```` @@ -212,8 +219,8 @@ To finish, here is a benchmark comparing this problem to the corresponding initial_condition = zeros(DelaunayTriangulation.num_points(tri)) FVM.apply_dirichlet_conditions!(initial_condition, mesh, Conditions(mesh, BCs, InternalConditions())) # a good initial guess fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; - diffusion_function=(x, y, t, u, p) -> (x + 1) * (y + 2), - final_time=Inf, + diffusion_function = (x, y, t, u, p) -> (x + 1) * (y + 2), + final_time = Inf, initial_condition)) ```` @@ -227,7 +234,7 @@ using BenchmarkTools ```` ````julia -@btime solve($fvm_prob, $DynamicSS(TRBDF2(linsolve=KLUFactorization()))); +@btime solve($fvm_prob, $DynamicSS(TRBDF2(linsolve = KLUFactorization()))); ```` ```` @@ -235,6 +242,7 @@ using BenchmarkTools ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_wyos/laplaces_equation.jl). @@ -242,16 +250,18 @@ You can view the source code for this file [here](https://github.com/SciML/Finit using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod function laplaces_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function=(x, y, p) -> 1.0, - diffusion_parameters=nothing) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function = (x, y, p) -> 1.0, + diffusion_parameters = nothing) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) b = zeros(DelaunayTriangulation.num_points(mesh.triangulation)) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) FVM.apply_steady_dirichlet_conditions!(A, b, mesh, conditions) FVM.fix_missing_vertices!(A, b, mesh) Asp = sparse(A) @@ -275,7 +285,7 @@ boundary_x = [lower_x, outer_arc_x, left_x, inner_arc_x] boundary_y = [lower_y, outer_arc_y, left_y, inner_arc_y] boundary_nodes, points = convert_boundary_points_to_indices(boundary_x, boundary_y) tri = triangulate(points; boundary_nodes) -refine!(tri; max_area=1e-3get_area(tri)) +refine!(tri; max_area = 1e-3get_area(tri)) triplot(tri) mesh = FVMGeometry(tri) @@ -288,32 +298,32 @@ bc_f = (lower_f, outer_arc_f, left_f, inner_arc_f) bc_types = (Dirichlet, Dirichlet, Dirichlet, Dirichlet) BCs = BoundaryConditions(mesh, bc_f, bc_types) -prob = laplaces_equation(mesh, BCs, diffusion_function=(x, y, p) -> 1.0) +prob = laplaces_equation(mesh, BCs, diffusion_function = (x, y, p) -> 1.0) sol = solve(prob, KLUFactorization()) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y", width=600, height=600) -tricontourf!(ax, tri, sol.u, levels=0:0.1:1, colormap=:jet) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y", width = 600, height = 600) +tricontourf!(ax, tri, sol.u, levels = 0:0.1:1, colormap = :jet) resize_to_layout!(fig) fig initial_condition = zeros(DelaunayTriangulation.num_points(tri)) FVM.apply_dirichlet_conditions!(initial_condition, mesh, Conditions(mesh, BCs, InternalConditions())) # a good initial guess fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; - diffusion_function=(x, y, t, u, p) -> 1.0, + diffusion_function = (x, y, t, u, p) -> 1.0, initial_condition, - final_time=Inf)) + final_time = Inf)) using SteadyStateDiffEq, OrdinaryDiffEq fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2())) -ax = Axis(fig[1, 2], xlabel="x", ylabel="y", width=600, height=600) -tricontourf!(ax, tri, fvm_sol.u, levels=0:0.1:1, colormap=:jet) +ax = Axis(fig[1, 2], xlabel = "x", ylabel = "y", width = 600, height = 600) +tricontourf!(ax, tri, fvm_sol.u, levels = 0:0.1:1, colormap = :jet) resize_to_layout!(fig) fig -tri = triangulate_rectangle(0, 5, 0, 5, 100, 100, single_boundary=false) +tri = triangulate_rectangle(0, 5, 0, 5, 100, 100, single_boundary = false) mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> 0.0 five_f = (x, y, t, u, p) -> 5.0 @@ -325,28 +335,27 @@ prob = LaplacesEquation(mesh, BCs; diffusion_function) sol = solve(prob, KLUFactorization()) -fig = Figure(fontsize=33) -ax = Axis(fig[1, 1], xlabel="x", ylabel="y", - width=600, height=600, - title="Numerical", titlealign=:left) -tricontourf!(ax, tri, sol.u, levels=0:0.25:5, colormap=:jet) -ax = Axis(fig[1, 2], xlabel="x", ylabel="y", - width=600, height=600, - title="Exact", titlealign=:left) +fig = Figure(fontsize = 33) +ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y", + width = 600, height = 600, + title = "Numerical", titlealign = :left) +tricontourf!(ax, tri, sol.u, levels = 0:0.25:5, colormap = :jet) +ax = Axis(fig[1, 2], xlabel = "x", ylabel = "y", + width = 600, height = 600, + title = "Exact", titlealign = :left) u_exact = [5log(1 + x) / log(6) for (x, y) in DelaunayTriangulation.each_point(tri)] -tricontourf!(ax, tri, u_exact, levels=0:0.25:5, colormap=:jet) +tricontourf!(ax, tri, u_exact, levels = 0:0.25:5, colormap = :jet) resize_to_layout!(fig) fig initial_condition = zeros(DelaunayTriangulation.num_points(tri)) FVM.apply_dirichlet_conditions!(initial_condition, mesh, Conditions(mesh, BCs, InternalConditions())) # a good initial guess fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; - diffusion_function=(x, y, t, u, p) -> (x + 1) * (y + 2), - final_time=Inf, + diffusion_function = (x, y, t, u, p) -> (x + 1) * (y + 2), + final_time = Inf, initial_condition)) ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/wyos/linear_reaction_diffusion_equations.md b/docs/src/wyos/linear_reaction_diffusion_equations.md index dd29ba3..3cd6bc3 100644 --- a/docs/src/wyos/linear_reaction_diffusion_equations.md +++ b/docs/src/wyos/linear_reaction_diffusion_equations.md @@ -9,23 +9,29 @@ nothing #hide ```` # Linear Reaction-Diffusion Equations + ```@contents Pages = ["linear_reaction_diffusion_equations.md"] ``` + Next, we write a specialised solver for solving linear reaction-diffusion equations. What we produce in this section can also be accessed in `FiniteVolumeMethod.LinearReactionDiffusionEquation`. ## Mathematical Details + To start, let's give the mathematical details. The problems we will be solving take the form + ```math \pdv{u}{t} = \div\left[D(\vb x)\grad u\right] + f(\vb x)u. ``` + We want to turn this into an equation of the form $\mathrm d\vb u/\mathrm dt = \vb A\vb u + \vb b$ as usual. This takes the same form as our [diffusion equation example](diffusion_equations.md), except with the extra $f(\vb x)u$ term, which just adds an exta $f(\vb x)$ term to the diagonal of $\vb A$. See the previois sections for further mathematical details. ## Implementation + Let us now implement the solver. For constructing $\vb A$, we can use `FiniteVolumeMethod.triangle_contributions!` as in the previous sections, but we will need an extra function to add $f(\vb x)$ to the appropriate diagonals. We can also reuse `apply_dirichlet_conditions!`, `apply_dudt_conditions`, and @@ -34,7 +40,8 @@ We can also reuse `apply_dirichlet_conditions!`, `apply_dudt_conditions`, and ````@example linear_reaction_diffusion_equations using FiniteVolumeMethod, SparseArrays, OrdinaryDiffEq, LinearAlgebra const FVM = FiniteVolumeMethod -function linear_source_contributions!(A, mesh, conditions, source_function, source_parameters) +function linear_source_contributions!( + A, mesh, conditions, source_function, source_parameters) for i in each_solid_vertex(mesh.triangulation) if !FVM.has_condition(conditions, i) x, y = get_point(mesh, i) @@ -43,23 +50,25 @@ function linear_source_contributions!(A, mesh, conditions, source_function, sour end end function linear_reaction_diffusion_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - source_function, - source_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + source_function, + source_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) - A = @views Afull[begin:end-1, begin:end-1] - b = @views Afull[begin:end-1, end] + A = @views Afull[begin:(end - 1), begin:(end - 1)] + b = @views Afull[begin:(end - 1), end] _ic = vcat(initial_condition, 1) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) linear_source_contributions!(A, mesh, conditions, source_function, source_parameters) FVM.apply_dudt_conditions!(b, mesh, conditions) FVM.apply_dirichlet_conditions!(_ic, mesh, conditions) @@ -76,18 +85,20 @@ this is essentially the same function except we now have `linear_source_contribu and `source_function` and `source_parameters` arguments. Let's now test this function. We consider the problem + ```math \pdv{T}{t} = \div\left[10^{-3}x^2y\grad T\right] + (x-1)(y-1)T, \quad \vb x \in [0,1]^2, ``` + with $\grad T \vdot\vu n = 1$ on the boundary. ````@example linear_reaction_diffusion_equations using DelaunayTriangulation -tri = triangulate_rectangle(0, 1, 0, 1, 150, 150, single_boundary=true) +tri = triangulate_rectangle(0, 1, 0, 1, 150, 150, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> one(x), Neumann) diffusion_function = (x, y, p) -> p.D * x^2 * y -diffusion_parameters = (D=1e-3,) +diffusion_parameters = (D = 1e-3,) source_function = (x, y, p) -> (x - 1) * (y - 1) initial_condition = [x^2 + y^2 for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 8.0 @@ -98,18 +109,19 @@ prob |> tc #hide ```` ````@example linear_reaction_diffusion_equations -sol = solve(prob, Tsit5(); saveat=2) +sol = solve(prob, Tsit5(); saveat = 2) sol |> tc #hide ```` ````@example linear_reaction_diffusion_equations using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for j in eachindex(sol) - ax = Axis(fig[1, j], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])") - tricontourf!(ax, tri, sol.u[j], levels=0:0.1:1, extendlow=:auto, extendhigh=:auto, colormap=:turbo) + ax = Axis(fig[1, j], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])") + tricontourf!(ax, tri, sol.u[j], levels = 0:0.1:1, extendlow = :auto, + extendhigh = :auto, colormap = :turbo) tightlimits!(ax) end resize_to_layout!(fig) @@ -123,40 +135,42 @@ which gives $\vb q\vdot\vu n = -D$. ````@example linear_reaction_diffusion_equations _BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> -p.D(x, y, p.Dp), Neumann; - parameters=(D=diffusion_function, Dp=diffusion_parameters)) + parameters = (D = diffusion_function, Dp = diffusion_parameters)) fvm_prob = FVMProblem( mesh, _BCs; - diffusion_function=let D=diffusion_function + diffusion_function = let D=diffusion_function (x, y, t, u, p) -> D(x, y, p) end, - diffusion_parameters=diffusion_parameters, - source_function=let S=source_function + diffusion_parameters = diffusion_parameters, + source_function = let S=source_function (x, y, t, u, p) -> S(x, y, p) * u end, - final_time=final_time, - initial_condition=initial_condition + final_time = final_time, + initial_condition = initial_condition ) -fvm_sol = solve(fvm_prob, Tsit5(), saveat=2.0) +fvm_sol = solve(fvm_prob, Tsit5(), saveat = 2.0) fvm_sol |> tc #hide ```` ## Using the Provided Template + The above code is implemented in `LinearReactionDiffusionEquation` in FiniteVolumeMethod.jl. ````@example linear_reaction_diffusion_equations prob = LinearReactionDiffusionEquation(mesh, BCs; diffusion_function, diffusion_parameters, - source_function, initial_condition, final_time) -sol = solve(prob, Tsit5(); saveat=2) + source_function, initial_condition, final_time) +sol = solve(prob, Tsit5(); saveat = 2) sol |> tc #hide ```` Here is a benchmark comparison of `LinearReactionDiffusionEquation` versus `FVMProblem`. + ````julia using BenchmarkTools using Sundials -@btime solve($prob, $CVODE_BDF(linear_solver=:GMRES); saveat=$2); +@btime solve($prob, $CVODE_BDF(linear_solver = :GMRES); saveat = $2); ```` ```` @@ -164,20 +178,23 @@ using Sundials ```` ````julia -@btime solve($fvm_prob, $CVODE_BDF(linear_solver=:GMRES); saveat=$2); +@btime solve($fvm_prob, $CVODE_BDF(linear_solver = :GMRES); saveat = $2); ```` ```` 163.686 ms (83267 allocations: 90.84 MiB) ```` + ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_wyos/linear_reaction_diffusion_equations.jl). ```julia using FiniteVolumeMethod, SparseArrays, OrdinaryDiffEq, LinearAlgebra const FVM = FiniteVolumeMethod -function linear_source_contributions!(A, mesh, conditions, source_function, source_parameters) +function linear_source_contributions!( + A, mesh, conditions, source_function, source_parameters) for i in each_solid_vertex(mesh.triangulation) if !FVM.has_condition(conditions, i) x, y = get_point(mesh, i) @@ -186,23 +203,25 @@ function linear_source_contributions!(A, mesh, conditions, source_function, sour end end function linear_reaction_diffusion_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - source_function, - source_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + source_function, + source_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) - A = @views Afull[begin:end-1, begin:end-1] - b = @views Afull[begin:end-1, end] + A = @views Afull[begin:(end - 1), begin:(end - 1)] + b = @views Afull[begin:(end - 1), end] _ic = vcat(initial_condition, 1) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) linear_source_contributions!(A, mesh, conditions, source_function, source_parameters) FVM.apply_dudt_conditions!(b, mesh, conditions) FVM.apply_dirichlet_conditions!(_ic, mesh, conditions) @@ -213,11 +232,11 @@ function linear_reaction_diffusion_equation(mesh::FVMGeometry, end using DelaunayTriangulation -tri = triangulate_rectangle(0, 1, 0, 1, 150, 150, single_boundary=true) +tri = triangulate_rectangle(0, 1, 0, 1, 150, 150, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> one(x), Neumann) diffusion_function = (x, y, p) -> p.D * x^2 * y -diffusion_parameters = (D=1e-3,) +diffusion_parameters = (D = 1e-3,) source_function = (x, y, p) -> (x - 1) * (y - 1) initial_condition = [x^2 + y^2 for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = 8.0 @@ -225,44 +244,44 @@ prob = linear_reaction_diffusion_equation(mesh, BCs; diffusion_function, diffusion_parameters, source_function, initial_condition, final_time) -sol = solve(prob, Tsit5(); saveat=2) +sol = solve(prob, Tsit5(); saveat = 2) using CairoMakie -fig = Figure(fontsize=38) +fig = Figure(fontsize = 38) for j in eachindex(sol) - ax = Axis(fig[1, j], width=600, height=600, - xlabel="x", ylabel="y", - title="t = $(sol.t[j])") - tricontourf!(ax, tri, sol.u[j], levels=0:0.1:1, extendlow=:auto, extendhigh=:auto, colormap=:turbo) + ax = Axis(fig[1, j], width = 600, height = 600, + xlabel = "x", ylabel = "y", + title = "t = $(sol.t[j])") + tricontourf!(ax, tri, sol.u[j], levels = 0:0.1:1, extendlow = :auto, + extendhigh = :auto, colormap = :turbo) tightlimits!(ax) end resize_to_layout!(fig) fig _BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> -p.D(x, y, p.Dp), Neumann; - parameters=(D=diffusion_function, Dp=diffusion_parameters)) + parameters = (D = diffusion_function, Dp = diffusion_parameters)) fvm_prob = FVMProblem( mesh, _BCs; - diffusion_function=let D=diffusion_function + diffusion_function = let D=diffusion_function (x, y, t, u, p) -> D(x, y, p) end, - diffusion_parameters=diffusion_parameters, - source_function=let S=source_function + diffusion_parameters = diffusion_parameters, + source_function = let S=source_function (x, y, t, u, p) -> S(x, y, p) * u end, - final_time=final_time, - initial_condition=initial_condition + final_time = final_time, + initial_condition = initial_condition ) -fvm_sol = solve(fvm_prob, Tsit5(), saveat=2.0) +fvm_sol = solve(fvm_prob, Tsit5(), saveat = 2.0) prob = LinearReactionDiffusionEquation(mesh, BCs; diffusion_function, diffusion_parameters, - source_function, initial_condition, final_time) -sol = solve(prob, Tsit5(); saveat=2) + source_function, initial_condition, final_time) +sol = solve(prob, Tsit5(); saveat = 2) ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/wyos/mean_exit_time.md b/docs/src/wyos/mean_exit_time.md index 0ca5c4a..791e3c6 100644 --- a/docs/src/wyos/mean_exit_time.md +++ b/docs/src/wyos/mean_exit_time.md @@ -9,19 +9,24 @@ nothing #hide ```` # Mean Exit Time Problems + ```@contents Pages = ["mean_exit_time.md"] ``` + We now write a specialised solver for solving mean exit time problems. What we produce in this section can also be accessed in `FiniteVolumeMethod.MeanExitTimeProblem`. ## Mathematical Details + To start, we give the mathematical details. We will be solving mean exit time problems of the form + ```math \begin{equation} \div \left[D(\vb x)\grad T\right] = -1, \end{equation} ``` + with homogeneous Neumann or Dirichlet conditions on parts of the boundary; homogeneous Neumann conditions represent reflecting parts of the boundary, while homogeneous Dirichlet @@ -31,20 +36,25 @@ The mathematical details for this section are similar to those from the diffusio discussion [here](diffusion_equations.md), except that the source term is $1$ instead of $0$, and $\mathrm dT_i/\mathrm dt = 0$ everywhere. In particular, we can reuse some details from the diffusion equation discussion to immediately write + ```math \frac{1}{V_i}\sum_{\sigma\in\mathcal E_i} D(\vb x_\sigma)\left[\left(s_{k, 11}n_\sigma^x+s_{k,21}n_\sigma^y\right)T_{k1} + \left(s_{k,12}n_\sigma^x+s_{k,22}n_\sigma^y\right)T_{k2}+\left(s_{k,13}n_\sigma^x+s_{k,23}n_\sigma^y\right)T_{k3}\right]L_\sigma = -1. ``` + Equivalently, defining $\vb a_i$ appropriately and $b_i=-1$ (we don't normalise by $V_i$ in $b_i$ and instead keep it in $\vb a_i$, since we want to reuse some existing functions later), we can write + ```math \vb a_i^{\mkern-1.5mu\mathsf T}\vb T = b_i. ``` + Since we have homogeneous Neumann boundary conditions (wherever a Neumann boundary condition is given, at least), we don't have to worry about looping over the boundary edges - they just get skipped. For the Dirichlet nodes $i$, we let $\vb a_i = \vb e_i$ and $b_i = 0$ (since the Dirichlet conditions should be homogeneous). ## Implementation + Let us now implement this. There is a lot that we can reuse from our diffusion equation template. The function that gets the contributions from each triangle can be reused exactly, which is available in `FiniteVolumeMethod.triangle_contributions!`. For applying @@ -75,14 +85,15 @@ return the problem as a `LinearProblem` from LinearSolve.jl. using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod function met_problem(mesh::FVMGeometry, - BCs::BoundaryConditions, # the actual implementation also checks that the types are only Dirichlet/Neumann - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing) + BCs::BoundaryConditions, # the actual implementation also checks that the types are only Dirichlet/Neumann + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) b = create_met_b!(A, mesh, conditions) FVM.fix_missing_vertices!(A, b, mesh) return LinearProblem(sparse(A), b) @@ -102,15 +113,17 @@ R1_f = let R₁ = R₁, ε = ε, g = g # use let for type stability θ -> R₁ * (1.0 + ε * g(θ)) end ϵr = 0.25 -dirichlet = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), (R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) -neumann = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (R₂ * cos(ϵr), R₂ * sin(ϵr)), (0.0, 0.0)) -hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive=false) +dirichlet = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), ( + R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) +neumann = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (R₂ * cos(ϵr), R₂ * sin(ϵr)), ( + 0.0, 0.0)) +hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive = false) boundary_nodes = [[[dirichlet], [neumann]], [[hole]]] points = [(-2.0, 0.0), (0.0, 2.95)] tri = triangulate(points; boundary_nodes) θ = LinRange(0, 2π, 250) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -120,12 +133,12 @@ end n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) pointhole_idxs = [1, 2] -refine!(tri; max_area=1e-3get_area(tri)); +refine!(tri; max_area = 1e-3get_area(tri)); # Define the problem mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> zero(u) # the function doesn't actually matter, but it still needs to be provided BCs = BoundaryConditions(mesh, (zero_f, zero_f, zero_f), (Neumann, Dirichlet, Dirichlet)) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(pointhole_idxs .=> 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes = Dict(pointhole_idxs .=> 1)) D₁, D₂ = 6.25e-4, 6.25e-5 diffusion_function = (x, y, p) -> begin r = sqrt(x^2 + y^2) @@ -133,7 +146,7 @@ diffusion_function = (x, y, p) -> begin interface_val = p.R1_f(ϕ) return r < interface_val ? p.D₁ : p.D₂ end -diffusion_parameters = (D₁=D₁, D₂=D₂, R1_f=R1_f) +diffusion_parameters = (D₁ = D₁, D₂ = D₂, R1_f = R1_f) prob = met_problem(mesh, BCs, ICs; diffusion_function, diffusion_parameters) prob |> tc #hide ```` @@ -157,8 +170,9 @@ We can easily visualise our solution: ````@example mean_exit_time using CairoMakie -fig, ax, sc = tricontourf(tri, sol.u, levels=0:1000:15000, extendhigh=:auto, - axis=(width=600, height=600, title="Template")) +fig, ax, +sc = tricontourf(tri, sol.u, levels = 0:1000:15000, extendhigh = :auto, + axis = (width = 600, height = 600, title = "Template")) fig ```` @@ -177,12 +191,12 @@ function T_exact(x, y) end initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] # an initial guess fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; - diffusion_function=let D = diffusion_function + diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, diffusion_parameters, - source_function=(x, y, t, u, p) -> one(u), - final_time=Inf, + source_function = (x, y, t, u, p) -> one(u), + final_time = Inf, initial_condition)) ```` @@ -195,14 +209,15 @@ fvm_sol |> tc #hide ```` ````@example mean_exit_time -ax = Axis(fig[1, 2], width=600, height=600, title="Template") -tricontourf!(ax, tri, fvm_sol.u, levels=0:1000:15000, extendhigh=:auto) +ax = Axis(fig[1, 2], width = 600, height = 600, title = "Template") +tricontourf!(ax, tri, fvm_sol.u, levels = 0:1000:15000, extendhigh = :auto) resize_to_layout!(fig) fig ind = findall(i -> DelaunayTriangulation.has_vertex(tri, i), DelaunayTriangulation.each_point_index(tri)) ```` ## Using the Provided Template + Let's now use the built-in `MeanExitTimeProblem` which implements the above template inside FiniteVolumeMethod.jl. @@ -215,12 +230,14 @@ sol |> tc #hide ```` ````@example mean_exit_time -fig, ax, sc = tricontourf(tri, sol.u, levels=0:1000:15000, extendhigh=:auto, - axis=(width=600, height=600)) +fig, ax, +sc = tricontourf(tri, sol.u, levels = 0:1000:15000, extendhigh = :auto, + axis = (width = 600, height = 600)) fig ```` This matches what we have above. To finish, here is a benchmark comparing the approaches. + ````julia using BenchmarkTools @btime solve($prob, $KLUFactorization()); @@ -231,7 +248,7 @@ using BenchmarkTools ```` ````julia -@btime solve($fvm_prob, $DynamicSS($KenCarp47(linsolve=KLUFactorization()))); +@btime solve($fvm_prob, $DynamicSS($KenCarp47(linsolve = KLUFactorization()))); ```` ```` @@ -239,7 +256,9 @@ using BenchmarkTools ```` Very fast! + ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_wyos/mean_exit_time.jl). @@ -259,14 +278,15 @@ end using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod function met_problem(mesh::FVMGeometry, - BCs::BoundaryConditions, # the actual implementation also checks that the types are only Dirichlet/Neumann - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing) + BCs::BoundaryConditions, # the actual implementation also checks that the types are only Dirichlet/Neumann + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) b = create_met_b!(A, mesh, conditions) FVM.fix_missing_vertices!(A, b, mesh) return LinearProblem(sparse(A), b) @@ -280,15 +300,17 @@ R1_f = let R₁ = R₁, ε = ε, g = g # use let for type stability θ -> R₁ * (1.0 + ε * g(θ)) end ϵr = 0.25 -dirichlet = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), (R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) -neumann = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (R₂ * cos(ϵr), R₂ * sin(ϵr)), (0.0, 0.0)) -hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive=false) +dirichlet = CircularArc((R₂ * cos(ϵr), R₂ * sin(ϵr)), ( + R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (0.0, 0.0)) +neumann = CircularArc((R₂ * cos(2π - ϵr), R₂ * sin(2π - ϵr)), (R₂ * cos(ϵr), R₂ * sin(ϵr)), ( + 0.0, 0.0)) +hole = CircularArc((0.0, 1.0), (0.0, 1.0), (0.0, 0.0), positive = false) boundary_nodes = [[[dirichlet], [neumann]], [[hole]]] points = [(-2.0, 0.0), (0.0, 2.95)] tri = triangulate(points; boundary_nodes) θ = LinRange(0, 2π, 250) -xin = @views (@. R1_f(θ) * cos(θ))[begin:end-1] -yin = @views (@. R1_f(θ) * sin(θ))[begin:end-1] +xin = @views (@. R1_f(θ) * cos(θ))[begin:(end - 1)] +yin = @views (@. R1_f(θ) * sin(θ))[begin:(end - 1)] add_point!(tri, xin[1], yin[1]) for i in 2:length(xin) add_point!(tri, xin[i], yin[i]) @@ -298,12 +320,12 @@ end n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) pointhole_idxs = [1, 2] -refine!(tri; max_area=1e-3get_area(tri)); +refine!(tri; max_area = 1e-3get_area(tri)); # Define the problem mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> zero(u) # the function doesn't actually matter, but it still needs to be provided BCs = BoundaryConditions(mesh, (zero_f, zero_f, zero_f), (Neumann, Dirichlet, Dirichlet)) -ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes=Dict(pointhole_idxs .=> 1)) +ICs = InternalConditions((x, y, t, u, p) -> zero(u), dirichlet_nodes = Dict(pointhole_idxs .=> 1)) D₁, D₂ = 6.25e-4, 6.25e-5 diffusion_function = (x, y, p) -> begin r = sqrt(x^2 + y^2) @@ -311,7 +333,7 @@ diffusion_function = (x, y, p) -> begin interface_val = p.R1_f(ϕ) return r < interface_val ? p.D₁ : p.D₂ end -diffusion_parameters = (D₁=D₁, D₂=D₂, R1_f=R1_f) +diffusion_parameters = (D₁ = D₁, D₂ = D₂, R1_f = R1_f) prob = met_problem(mesh, BCs, ICs; diffusion_function, diffusion_parameters) prob.A @@ -319,8 +341,9 @@ prob.A sol = solve(prob, KLUFactorization()) using CairoMakie -fig, ax, sc = tricontourf(tri, sol.u, levels=0:1000:15000, extendhigh=:auto, - axis=(width=600, height=600, title="Template")) +fig, ax, +sc = tricontourf(tri, sol.u, levels = 0:1000:15000, extendhigh = :auto, + axis = (width = 600, height = 600, title = "Template")) fig function T_exact(x, y) @@ -333,19 +356,19 @@ function T_exact(x, y) end initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] # an initial guess fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; - diffusion_function=let D = diffusion_function + diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, diffusion_parameters, - source_function=(x, y, t, u, p) -> one(u), - final_time=Inf, + source_function = (x, y, t, u, p) -> one(u), + final_time = Inf, initial_condition)) using SteadyStateDiffEq, OrdinaryDiffEq fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2())) -ax = Axis(fig[1, 2], width=600, height=600, title="Template") -tricontourf!(ax, tri, fvm_sol.u, levels=0:1000:15000, extendhigh=:auto) +ax = Axis(fig[1, 2], width = 600, height = 600, title = "Template") +tricontourf!(ax, tri, fvm_sol.u, levels = 0:1000:15000, extendhigh = :auto) resize_to_layout!(fig) fig ind = findall(i -> DelaunayTriangulation.has_vertex(tri, i), DelaunayTriangulation.each_point_index(tri)) @@ -355,12 +378,12 @@ prob = MeanExitTimeProblem(mesh, BCs, ICs; diffusion_parameters) sol = solve(prob, KLUFactorization()) -fig, ax, sc = tricontourf(tri, sol.u, levels=0:1000:15000, extendhigh=:auto, - axis=(width=600, height=600)) +fig, ax, +sc = tricontourf(tri, sol.u, levels = 0:1000:15000, extendhigh = :auto, + axis = (width = 600, height = 600)) fig ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/wyos/overview.md b/docs/src/wyos/overview.md index fba45e0..e1b48dc 100644 --- a/docs/src/wyos/overview.md +++ b/docs/src/wyos/overview.md @@ -1,31 +1,37 @@ # Solvers for Specific Problems, and Writing Your Own The problems solved by this package are quite general, taking the form + ```math \pdv{u}{t} + \div\vb q = S. ``` + For some problems, though, this is not the most efficient form to implement. For example, the diffusion equation + ```math \pdv{u}{t} = D\grad^2 u ``` + might be better treated by converting the problem into + ```math \dv{\vb u}{t} = \vb A\vb u + \vb b, ``` + which is faster to solve than if we were to treat it as a nonlinear problem -(which is done by default). For this reason, we define some templates +(which is done by default). For this reason, we define some templates for specific types of problems, namely: -1. `DiffusionEquation`s: $\partial_tu = \div[D(\vb x)\grad u]$. -2. `MeanExitTimeProblem`s: $\div[D(\vb x)\grad T(\vb x)] = -1$. -3. `LinearReactionDiffusionEquation`s: $\partial_tu = \div[D(\vb x)\grad u] + f(\vb x)u$. -4. `PoissonsEquation`: $\div[D(\vb x)\grad u] = f(\vb x)$. -5. `LaplacesEquation`: $\div[D(\vb x)\grad u] = 0$. + 1. `DiffusionEquation`s: $\partial_tu = \div[D(\vb x)\grad u]$. + 2. `MeanExitTimeProblem`s: $\div[D(\vb x)\grad T(\vb x)] = -1$. + 3. `LinearReactionDiffusionEquation`s: $\partial_tu = \div[D(\vb x)\grad u] + f(\vb x)u$. + 4. `PoissonsEquation`: $\div[D(\vb x)\grad u] = f(\vb x)$. + 5. `LaplacesEquation`: $\div[D(\vb x)\grad u] = 0$. The docstrings below define the templates for these problems. -```@docs +```@docs FiniteVolumeMethod.AbstractFVMTemplate solve(::FiniteVolumeMethod.AbstractFVMTemplate, args...; kwargs...) DiffusionEquation @@ -34,21 +40,21 @@ LinearReactionDiffusionEquation PoissonsEquation LaplacesEquation ``` - -Now, again, we note that all these problems can already be implemented using the main interface `FVMProblem`. However, the templates we provide are more efficient, and also provide a good starting point for writing your own solver, meaning your own function + +Now, again, we note that all these problems can already be implemented using the main interface `FVMProblem`. However, the templates we provide are more efficient, and also provide a good starting point for writing your own solver, meaning your own function that evaluates the system of ODEs. In the sections that follow, we will demonstrate two things for each of the problems above: -1. The mathematical details involved in implementing each template. -2. Examples of using the templates from FiniteVolumeMethod.jl. + 1. The mathematical details involved in implementing each template. + 2. Examples of using the templates from FiniteVolumeMethod.jl. -With these two steps, you should be able to also know how to write your own solver for any problem you like. +With these two steps, you should be able to also know how to write your own solver for any problem you like. ## Relevant Docstrings for Writing Your Own Solver -For writing these solvers, there are some specific functions that might be of use to you. +For writing these solvers, there are some specific functions that might be of use to you. Here, we provide the docstrings for these functions. These functions are public API. -```@docs +```@docs FiniteVolumeMethod.get_dirichlet_fidx FiniteVolumeMethod.is_dirichlet_node FiniteVolumeMethod.get_dirichlet_nodes @@ -84,4 +90,4 @@ FiniteVolumeMethod.two_point_interpolant FiniteVolumeMethod.get_dirichlet_callback FiniteVolumeMethod.jacobian_sparsity FiniteVolumeMethod.fix_missing_vertices! -``` \ No newline at end of file +``` diff --git a/docs/src/wyos/poissons_equation.md b/docs/src/wyos/poissons_equation.md index f8a363b..453867b 100644 --- a/docs/src/wyos/poissons_equation.md +++ b/docs/src/wyos/poissons_equation.md @@ -9,33 +9,40 @@ nothing #hide ```` # Poisson's Equation + ```@contents Pages = ["poissons_equation.md"] ``` + We now write a solver for Poisson's equation. What we produce in this section can also be accessed in `FiniteVolumeMethod.PoissonsEquation`. ## Mathematical Details + We start by describing the mathematical details. The problems we will be solving take the form + ```math \div[D(\vb x)\grad u] = f(\vb x). ``` + Note that this is very similar to a mean exit time problem, except $f(\vb x) = -1$ for mean exit time problems. Note that this is actually a generalised Poisson equation - typically these equations look like $\grad^2 u = f$.[^1] -[^1]: See, for example, [this paper](https://my.ece.utah.edu/~ece6340/LECTURES/Feb1/Nagel%202012%20-%20Solving%20the%20Generalized%20Poisson%20Equation%20using%20FDM.pdf). - +[^1]: See, for example, [this paper](https://my.ece.utah.edu/%7Eece6340/LECTURES/Feb1/Nagel%202012%20-%20Solving%20the%20Generalized%20Poisson%20Equation%20using%20FDM.pdf). From these similarities, we already know that + ```math \frac{1}{V_i}\sum_{\sigma\in\mathcal E_i} D(\vb x_\sigma)\left[\left(s_{k, 11}n_\sigma^x+s_{k,21}n_\sigma^y\right)u_{k1} + \left(s_{k,12}n_\sigma^x+s_{k,22}n_\sigma^y\right)u_{k2}+\left(s_{k,13}n_\sigma^x+s_{k,23}n_\sigma^y\right)u_{k3}\right]L_\sigma = f(\vb x_i), ``` + and thus we can write this as $\vb a_i^{\mkern-1.5mu\mathsf T}\vb u = b_i$ as usual, with $b_i = f(\vb x_i)$. The boundary conditions are handled in the same way as in [mean exit time problems](mean_exit_time.md). ## Implementation + Let us now implement our problem. For [mean exit time problems](mean_exit_time.md), we had a function `create_met_b` that we used for defining $\vb b$. We should generalise that function to now accept a source function: @@ -72,18 +79,20 @@ So, our problem can be defined by: using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod function poissons_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function=(x,y,p)->1.0, - diffusion_parameters=nothing, - source_function, - source_parameters=nothing) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function = (x, y, p)->1.0, + diffusion_parameters = nothing, + source_function, + source_parameters = nothing) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) b = create_rhs_b(mesh, conditions, source_function, source_parameters) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) apply_steady_dirichlet_conditions!(A, b, mesh, conditions) FVM.fix_missing_vertices!(A, b, mesh) return LinearProblem(sparse(A), b) @@ -91,6 +100,7 @@ end ```` Now let's test this problem. We consider + ```math \begin{equation*} \begin{aligned} @@ -101,11 +111,11 @@ u &= 0 & \vb x \in\partial[0,1]^2. ``` ````@example poissons_equation -tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary=true) +tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(x), Dirichlet) source_function = (x, y, p) -> -sin(π * x) * sin(π * y) -prob = poissons_equation(mesh, BCs; source_function) +prob = poissons_equation(mesh, BCs; source_function) using DisplayAs #hide prob |> tc #hide ```` @@ -117,7 +127,9 @@ sol |> tc #hide ````@example poissons_equation using CairoMakie -fig, ax, sc = tricontourf(tri, sol.u, levels=LinRange(0, 0.05, 10), colormap=:matter, extendhigh=:auto) +fig, ax, +sc = tricontourf( + tri, sol.u, levels = LinRange(0, 0.05, 10), colormap = :matter, extendhigh = :auto) tightlimits!(ax) fig ```` @@ -130,26 +142,27 @@ when `FVMProblem`s assume that we are solving $0 = \div[D(\vb x)\grad u] + f(\vb ````@example poissons_equation initial_condition = zeros(DelaunayTriangulation.num_points(tri)) fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; - diffusion_function= (x, y, t, u, p) -> 1.0, - source_function=let S = source_function + diffusion_function = (x, y, t, u, p) -> 1.0, + source_function = let S = source_function (x, y, t, u, p) -> -S(x, y, p) end, initial_condition, - final_time=Inf)) + final_time = Inf)) ```` ````@example poissons_equation using SteadyStateDiffEq, OrdinaryDiffEq -fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) +fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) fvm_sol |> tc #hide ```` ## Using the Provided Template + Let's now use the built-in `PoissonsEquation` which implements the above template inside FiniteVolumeMethod.jl. The above problem can be constructed as follows: ````@example poissons_equation -prob = PoissonsEquation(mesh, BCs; source_function=source_function) +prob = PoissonsEquation(mesh, BCs; source_function = source_function) ```` ````@example poissons_equation @@ -158,6 +171,7 @@ sol |> tc #hide ```` Here is a benchmark comparison of the `PoissonsEquation` approach against the `FVMProblem` approach. + ````julia using BenchmarkTools @btime solve($prob, $KLUFactorization()); @@ -168,7 +182,7 @@ using BenchmarkTools ```` ````julia -@btime solve($fvm_prob, $DynamicSS(TRBDF2(linsolve=KLUFactorization()))); +@btime solve($fvm_prob, $DynamicSS(TRBDF2(linsolve = KLUFactorization()))); ```` ```` @@ -176,29 +190,34 @@ using BenchmarkTools ```` Let's now also solve a generalised Poisson equation. Based -on Section 7 of [this paper](https://my.ece.utah.edu/~ece6340/LECTURES/Feb1/Nagel%202012%20-%20Solving%20the%20Generalized%20Poisson%20Equation%20using%20FDM.pdf) +on Section 7 of [this paper](https://my.ece.utah.edu/%7Eece6340/LECTURES/Feb1/Nagel%202012%20-%20Solving%20the%20Generalized%20Poisson%20Equation%20using%20FDM.pdf) by Nagel (2012), we consider an equation of the form + ```math \div\left[\epsilon(\vb x)\grad V(\vb x)\right] = -\frac{\rho(\vb x)}{\epsilon_0}. ``` + We consider this equation on the domain $\Omega = [0, 10]^2$. We put two parallel capacitor plates inside the domain, with the first in $2 \leq x \leq 8$ along $y = 3$, and the other at $2 \leq x \leq 8$ along $y=7$. We use $V = 1$ on the top plate, and $V = -1$ on the bottom plate. For the domain boundaries, $\partial\Omega$, we use homogeneous Dirichlet conditions on the top and bottom sides, and homogeneous Neumann conditions on the left and right sides. For the space-varying electric constant $\epsilon(\vb x)$, we use[^2] + ```math \epsilon(\vb x) = 1 + \frac12(\epsilon_0 - 1)\left[1 + \erf\left(\frac{r}{\Delta}\right)\right], ``` + where $r$ is the distance between $\vb x$ and the parallel plates, $\Delta = 4$. The value of $\epsilon_0$ is approximately $\epsilon_0 = 8.8541878128 \times 10^{-12}$. For the charge density $\rho(\vb x)$, we use a Gaussian density, + ```math \rho(\vb x) = \frac{Q}{2\pi}\mathrm{e}^{-r^2/2}, ``` + where, again, $r$ is the distance between $\vb x$ and the parallel plates, and $Q = 10^{-6}$. [^2]: This form of $\epsilon(\vb x)$ is based on [this paper](https://doi.org/10.1063/1.4939125) by Fisicaro et al. (2016). - To define this problem, let us first define the mesh. We will need to manually put in the capacitor plates so that we can enforce Dirichlet conditions on them. @@ -209,9 +228,9 @@ g, h = (2.0, 7.0), (8.0, 7.0) points = [(a, c), (b, c), (b, d), (a, d), e, f, g, h] boundary_nodes = [[1, 2], [2, 3], [3, 4], [4, 1]] segments = Set(((5, 6), (7, 8))) -tri = triangulate(points; boundary_nodes, segments, delete_ghosts=false) -refine!(tri; max_area=1e-4get_area(tri)) -fig, ax, sc = triplot(tri, show_constrained_edges=true, constrained_edge_linewidth=5) +tri = triangulate(points; boundary_nodes, segments, delete_ghosts = false) +refine!(tri; max_area = 1e-4get_area(tri)) +fig, ax, sc = triplot(tri, show_constrained_edges = true, constrained_edge_linewidth = 5) fig ```` @@ -250,7 +269,7 @@ dirichlet_nodes = Dict( (upper_plate .=> 2)... ) internal_f = ((x, y, t, u, p) -> -one(x), (x, y, t, u, p) -> one(x)) -ICs = InternalConditions(internal_f, dirichlet_nodes=dirichlet_nodes) +ICs = InternalConditions(internal_f, dirichlet_nodes = dirichlet_nodes) ```` Next, we define $\epsilon(\vb x)$ and $\rho(\vb x)$. We need a function that computes the distance @@ -267,8 +286,7 @@ end ```` [^3]: Taken from [here](https://stackoverflow.com/a/1501725). -Thus, the distance between a point and the two plates is: - + Thus, the distance between a point and the two plates is: ````@example poissons_equation function dist_to_plates(x, y) p = (x, y) @@ -311,13 +329,13 @@ end Now we can define our problem. ````@example poissons_equation -diffusion_parameters = (ϵ₀=8.8541878128e-12, Δ=4.0) -source_parameters = (ϵ₀=8.8541878128e-12, Q=1e-6) +diffusion_parameters = (ϵ₀ = 8.8541878128e-12, Δ = 4.0) +source_parameters = (ϵ₀ = 8.8541878128e-12, Q = 1e-6) prob = PoissonsEquation(mesh, BCs, ICs; - diffusion_function=dielectric_function, - diffusion_parameters=diffusion_parameters, - source_function=plate_source_function, - source_parameters=source_parameters) + diffusion_function = dielectric_function, + diffusion_parameters = diffusion_parameters, + source_function = plate_source_function, + source_parameters = source_parameters) ```` ````@example poissons_equation @@ -330,7 +348,7 @@ To compute the gradients, we use NaturalNeighbours.jl. ````@example poissons_equation using NaturalNeighbours -itp = interpolate(tri, sol.u; derivatives=true) +itp = interpolate(tri, sol.u; derivatives = true) E = map(.-, itp.gradient) # E = -∇V E |> tc #hide ```` @@ -346,25 +364,25 @@ x = LinRange(0, 10, 25) y = LinRange(0, 10, 25) x_vec = [x for x in x, y in y] |> vec y_vec = [y for x in x, y in y] |> vec -E_itp = map(.-, ∂(x_vec, y_vec, interpolant_method=Hiyoshi(2))) +E_itp = map(.-, ∂(x_vec, y_vec, interpolant_method = Hiyoshi(2))) E_intensity = norm.(E_itp) -fig = Figure(fontsize=38) -ax = Axis(fig[1, 1], width=600, height=600, titlealign=:left, - xlabel="x", ylabel="y", title="Voltage") -tricontourf!(ax, tri, sol.u, levels=15, colormap=:ocean) +fig = Figure(fontsize = 38) +ax = Axis(fig[1, 1], width = 600, height = 600, titlealign = :left, + xlabel = "x", ylabel = "y", title = "Voltage") +tricontourf!(ax, tri, sol.u, levels = 15, colormap = :ocean) arrow_positions = [Point2f(x, y) for (x, y) in zip(x_vec, y_vec)] arrow_directions = [Point2f(e...) for e in E_itp] arrows!(ax, arrow_positions, arrow_directions, - lengthscale=0.3, normalize=true, arrowcolor=E_intensity, linecolor=E_intensity) - xlims!(ax,0,10) - ylims!(ax,0,10) -ax = Axis(fig[1, 2], width=600, height=600, titlealign=:left, - xlabel="x", ylabel="y", title="Electric Field") -tricontourf!(ax, tri, norm.(E), levels=15, colormap=:ocean) + lengthscale = 0.3, normalize = true, arrowcolor = E_intensity, linecolor = E_intensity) +xlims!(ax, 0, 10) +ylims!(ax, 0, 10) +ax = Axis(fig[1, 2], width = 600, height = 600, titlealign = :left, + xlabel = "x", ylabel = "y", title = "Electric Field") +tricontourf!(ax, tri, norm.(E), levels = 15, colormap = :ocean) arrows!(ax, arrow_positions, arrow_directions, - lengthscale=0.3, normalize=true, arrowcolor=E_intensity, linecolor=E_intensity) -xlims!(ax,0,10) -ylims!(ax,0,10) + lengthscale = 0.3, normalize = true, arrowcolor = E_intensity, linecolor = E_intensity) +xlims!(ax, 0, 10) +ylims!(ax, 0, 10) resize_to_layout!(fig) fig ```` @@ -373,16 +391,16 @@ To finish, let us benchmark the `PoissonsEquation` approach against the `FVMProb ````@example poissons_equation fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; - diffusion_function=let D = dielectric_function + diffusion_function = let D = dielectric_function (x, y, t, u, p) -> D(x, y, p) end, - source_function=let S = plate_source_function + source_function = let S = plate_source_function (x, y, t, u, p) -> -S(x, y, p) end, - diffusion_parameters=diffusion_parameters, - source_parameters=source_parameters, - initial_condition=zeros(DelaunayTriangulation.num_points(tri)), - final_time=Inf)) + diffusion_parameters = diffusion_parameters, + source_parameters = source_parameters, + initial_condition = zeros(DelaunayTriangulation.num_points(tri)), + final_time = Inf)) ```` ````julia @@ -394,7 +412,7 @@ fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; ```` ````julia -@btime solve($fvm_prob, $DynamicSS(TRBDF2(linsolve=KLUFactorization()))); +@btime solve($fvm_prob, $DynamicSS(TRBDF2(linsolve = KLUFactorization()))); ```` ```` @@ -402,6 +420,7 @@ fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; ```` ## Just the code + An uncommented version of this example is given below. You can view the source code for this file [here](https://github.com/SciML/FiniteVolumeMethod.jl/tree/main/docs/src/literate_wyos/poissons_equation.jl). @@ -429,49 +448,53 @@ end using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod function poissons_equation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function=(x,y,p)->1.0, - diffusion_parameters=nothing, - source_function, - source_parameters=nothing) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function = (x, y, p)->1.0, + diffusion_parameters = nothing, + source_function, + source_parameters = nothing) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) b = create_rhs_b(mesh, conditions, source_function, source_parameters) - FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) + FVM.boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) apply_steady_dirichlet_conditions!(A, b, mesh, conditions) FVM.fix_missing_vertices!(A, b, mesh) return LinearProblem(sparse(A), b) end -tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary=true) +tri = triangulate_rectangle(0, 1, 0, 1, 100, 100, single_boundary = true) mesh = FVMGeometry(tri) BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(x), Dirichlet) source_function = (x, y, p) -> -sin(π * x) * sin(π * y) -prob = poissons_equation(mesh, BCs; source_function) +prob = poissons_equation(mesh, BCs; source_function) sol = solve(prob, KLUFactorization()) using CairoMakie -fig, ax, sc = tricontourf(tri, sol.u, levels=LinRange(0, 0.05, 10), colormap=:matter, extendhigh=:auto) +fig, ax, +sc = tricontourf( + tri, sol.u, levels = LinRange(0, 0.05, 10), colormap = :matter, extendhigh = :auto) tightlimits!(ax) fig initial_condition = zeros(DelaunayTriangulation.num_points(tri)) fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; - diffusion_function= (x, y, t, u, p) -> 1.0, - source_function=let S = source_function + diffusion_function = (x, y, t, u, p) -> 1.0, + source_function = let S = source_function (x, y, t, u, p) -> -S(x, y, p) end, initial_condition, - final_time=Inf)) + final_time = Inf)) using SteadyStateDiffEq, OrdinaryDiffEq -fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve=KLUFactorization()))) +fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) -prob = PoissonsEquation(mesh, BCs; source_function=source_function) +prob = PoissonsEquation(mesh, BCs; source_function = source_function) sol = solve(prob, KLUFactorization()) @@ -481,9 +504,9 @@ g, h = (2.0, 7.0), (8.0, 7.0) points = [(a, c), (b, c), (b, d), (a, d), e, f, g, h] boundary_nodes = [[1, 2], [2, 3], [3, 4], [4, 1]] segments = Set(((5, 6), (7, 8))) -tri = triangulate(points; boundary_nodes, segments, delete_ghosts=false) -refine!(tri; max_area=1e-4get_area(tri)) -fig, ax, sc = triplot(tri, show_constrained_edges=true, constrained_edge_linewidth=5) +tri = triangulate(points; boundary_nodes, segments, delete_ghosts = false) +refine!(tri; max_area = 1e-4get_area(tri)) +fig, ax, sc = triplot(tri, show_constrained_edges = true, constrained_edge_linewidth = 5) fig mesh = FVMGeometry(tri) @@ -512,7 +535,7 @@ dirichlet_nodes = Dict( (upper_plate .=> 2)... ) internal_f = ((x, y, t, u, p) -> -one(x), (x, y, t, u, p) -> one(x)) -ICs = InternalConditions(internal_f, dirichlet_nodes=dirichlet_nodes) +ICs = InternalConditions(internal_f, dirichlet_nodes = dirichlet_nodes) using LinearAlgebra function dist_to_line(p, a, b) @@ -547,18 +570,18 @@ function plate_source_function(x, y, p) return -ρ / p.ϵ₀ end -diffusion_parameters = (ϵ₀=8.8541878128e-12, Δ=4.0) -source_parameters = (ϵ₀=8.8541878128e-12, Q=1e-6) +diffusion_parameters = (ϵ₀ = 8.8541878128e-12, Δ = 4.0) +source_parameters = (ϵ₀ = 8.8541878128e-12, Q = 1e-6) prob = PoissonsEquation(mesh, BCs, ICs; - diffusion_function=dielectric_function, - diffusion_parameters=diffusion_parameters, - source_function=plate_source_function, - source_parameters=source_parameters) + diffusion_function = dielectric_function, + diffusion_parameters = diffusion_parameters, + source_function = plate_source_function, + source_parameters = source_parameters) sol = solve(prob, KLUFactorization()) using NaturalNeighbours -itp = interpolate(tri, sol.u; derivatives=true) +itp = interpolate(tri, sol.u; derivatives = true) E = map(.-, itp.gradient) # E = -∇V ∂ = differentiate(itp, 1) @@ -566,42 +589,41 @@ x = LinRange(0, 10, 25) y = LinRange(0, 10, 25) x_vec = [x for x in x, y in y] |> vec y_vec = [y for x in x, y in y] |> vec -E_itp = map(.-, ∂(x_vec, y_vec, interpolant_method=Hiyoshi(2))) +E_itp = map(.-, ∂(x_vec, y_vec, interpolant_method = Hiyoshi(2))) E_intensity = norm.(E_itp) -fig = Figure(fontsize=38) -ax = Axis(fig[1, 1], width=600, height=600, titlealign=:left, - xlabel="x", ylabel="y", title="Voltage") -tricontourf!(ax, tri, sol.u, levels=15, colormap=:ocean) +fig = Figure(fontsize = 38) +ax = Axis(fig[1, 1], width = 600, height = 600, titlealign = :left, + xlabel = "x", ylabel = "y", title = "Voltage") +tricontourf!(ax, tri, sol.u, levels = 15, colormap = :ocean) arrow_positions = [Point2f(x, y) for (x, y) in zip(x_vec, y_vec)] arrow_directions = [Point2f(e...) for e in E_itp] arrows!(ax, arrow_positions, arrow_directions, - lengthscale=0.3, normalize=true, arrowcolor=E_intensity, linecolor=E_intensity) - xlims!(ax,0,10) - ylims!(ax,0,10) -ax = Axis(fig[1, 2], width=600, height=600, titlealign=:left, - xlabel="x", ylabel="y", title="Electric Field") -tricontourf!(ax, tri, norm.(E), levels=15, colormap=:ocean) + lengthscale = 0.3, normalize = true, arrowcolor = E_intensity, linecolor = E_intensity) +xlims!(ax, 0, 10) +ylims!(ax, 0, 10) +ax = Axis(fig[1, 2], width = 600, height = 600, titlealign = :left, + xlabel = "x", ylabel = "y", title = "Electric Field") +tricontourf!(ax, tri, norm.(E), levels = 15, colormap = :ocean) arrows!(ax, arrow_positions, arrow_directions, - lengthscale=0.3, normalize=true, arrowcolor=E_intensity, linecolor=E_intensity) -xlims!(ax,0,10) -ylims!(ax,0,10) + lengthscale = 0.3, normalize = true, arrowcolor = E_intensity, linecolor = E_intensity) +xlims!(ax, 0, 10) +ylims!(ax, 0, 10) resize_to_layout!(fig) fig fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; - diffusion_function=let D = dielectric_function + diffusion_function = let D = dielectric_function (x, y, t, u, p) -> D(x, y, p) end, - source_function=let S = plate_source_function + source_function = let S = plate_source_function (x, y, t, u, p) -> -S(x, y, p) end, - diffusion_parameters=diffusion_parameters, - source_parameters=source_parameters, - initial_condition=zeros(DelaunayTriangulation.num_points(tri)), - final_time=Inf)) + diffusion_parameters = diffusion_parameters, + source_parameters = source_parameters, + initial_condition = zeros(DelaunayTriangulation.num_points(tri)), + final_time = Inf)) ``` ---- +* * * *This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/src/FiniteVolumeMethod.jl b/src/FiniteVolumeMethod.jl index 10e5376..3c96a44 100644 --- a/src/FiniteVolumeMethod.jl +++ b/src/FiniteVolumeMethod.jl @@ -26,19 +26,19 @@ include("utils.jl") include("specific_problems/abstract_templates.jl") export FVMGeometry, - FVMProblem, - FVMSystem, - SteadyFVMProblem, - BoundaryConditions, - InternalConditions, - Conditions, - Neumann, - Dudt, - Dirichlet, - Constrained, - solve, - compute_flux, - pl_interpolate + FVMProblem, + FVMSystem, + SteadyFVMProblem, + BoundaryConditions, + InternalConditions, + Conditions, + Neumann, + Dudt, + Dirichlet, + Constrained, + solve, + compute_flux, + pl_interpolate using PrecompileTools @setup_workload begin @@ -66,15 +66,17 @@ using PrecompileTools BCs = BoundaryConditions(mesh, (lower_bc, arc_bc, upper_bc), types) f = (x, y) -> 1 - sqrt(x^2 + y^2) D = (x, y, t, u, p) -> one(u) - initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.DelaunayTriangulation.each_point(tri)] + initial_condition = [f(x, y) + for (x, y) in + DelaunayTriangulation.DelaunayTriangulation.each_point(tri)] final_time = 0.1 - prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) + prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) ode_prob = ODEProblem(prob) steady_prob = SteadyFVMProblem(prob) nl_prob = SteadyStateProblem(steady_prob) # Compile a system - tri = triangulate_rectangle(0, 100, 0, 100, 5, 5, single_boundary=true) + tri = triangulate_rectangle(0, 100, 0, 100, 5, 5, single_boundary = true) mesh = FVMGeometry(tri) bc_u = (x, y, t, (u, v), p) -> zero(u) bc_v = (x, y, t, (u, v), p) -> zero(v) @@ -99,20 +101,20 @@ using PrecompileTools S_v = (x, y, t, (u, v), p) -> begin return u - p.a * v end - q_u_parameters = (c=4.0,) - q_v_parameters = (D=1.0,) - S_v_parameters = (a=0.1,) + q_u_parameters = (c = 4.0,) + q_v_parameters = (D = 1.0,) + S_v_parameters = (a = 0.1,) u_initial_condition = 0.01rand(DelaunayTriangulation.num_solid_vertices(tri)) v_initial_condition = zeros(DelaunayTriangulation.num_solid_vertices(tri)) final_time = 1000.0 u_prob = FVMProblem(mesh, BCs_u; - flux_function=q_u, flux_parameters=q_u_parameters, - source_function=S_u, - initial_condition=u_initial_condition, final_time=final_time) + flux_function = q_u, flux_parameters = q_u_parameters, + source_function = S_u, + initial_condition = u_initial_condition, final_time = final_time) v_prob = FVMProblem(mesh, BCs_v; - flux_function=q_v, flux_parameters=q_v_parameters, - source_function=S_v, source_parameters=S_v_parameters, - initial_condition=v_initial_condition, final_time=final_time) + flux_function = q_v, flux_parameters = q_v_parameters, + source_function = S_v, source_parameters = S_v_parameters, + initial_condition = v_initial_condition, final_time = final_time) prob = FVMSystem(u_prob, v_prob) ode_prob = ODEProblem(prob) steady_prob = SteadyFVMProblem(prob) diff --git a/src/conditions.jl b/src/conditions.jl index 69d73f1..f3b0208 100644 --- a/src/conditions.jl +++ b/src/conditions.jl @@ -4,30 +4,32 @@ This is a `struct` that wraps a function `f` and some parameters `p` into a single object. # Fields -- `fnc::F` + + - `fnc::F` The function that is wrapped. -- `parameters::P` + + - `parameters::P` The parameters that are wrapped. """ -struct ParametrisedFunction{F<:Function,P} <: Function +struct ParametrisedFunction{F <: Function, P} <: Function fnc::F parameters::P end -@inline (f::ParametrisedFunction{F,P})(args...) where {F,P} = f.fnc(args..., f.parameters) +@inline (f::ParametrisedFunction{F, P})(args...) where {F, P} = f.fnc(args..., f.parameters) """ ConditionType This is an `Enum`-type, with four instances: -- [`Neumann`](@ref) -- [`Dudt`](@ref) -- [`Dirichlet`](@ref) -- [`Constrained`](@ref) + - [`Neumann`](@ref) + - [`Dudt`](@ref) + - [`Dirichlet`](@ref) + - [`Constrained`](@ref) -This is used for declaring conditions in the PDEs. See +This is used for declaring conditions in the PDEs. See the associated docstrings, and also [`BoundaryConditions`](@ref) and [`InternalConditions`](@ref). """ @@ -130,37 +132,42 @@ end """ BoundaryConditions(mesh::FVMGeometry, functions, conditions; parameters=nothing) -This is a constructor for the [`BoundaryConditions`](@ref) struct, which holds the boundary conditions for the PDE. +This is a constructor for the [`BoundaryConditions`](@ref) struct, which holds the boundary conditions for the PDE. See also [`Conditions`](@ref) (which [`FVMProblem`](@ref) wraps this into), [`ConditionType`](@ref), and [`InternalConditions`](@ref). # Arguments -- `mesh::FVMGeometry` + + - `mesh::FVMGeometry` The mesh on which the PDE is defined. -- `functions::Union{<:Tuple,<:Function}` -The functions that define the boundary conditions. The `i`th function should correspond to the part of the boundary of -the `mesh` corresponding to the `i`th boundary index, as defined in DelaunayTriangulation.jl. -- `conditions::Union{<:Tuple,<:ConditionType}` + - `functions::Union{<:Tuple,<:Function}` + +The functions that define the boundary conditions. The `i`th function should correspond to the part of the boundary of +the `mesh` corresponding to the `i`th boundary index, as defined in DelaunayTriangulation.jl. + + - `conditions::Union{<:Tuple,<:ConditionType}` -The classification for the boundary condition type corresponding to each boundary index as above. See +The classification for the boundary condition type corresponding to each boundary index as above. See [`ConditionType`](@ref) for possible conditions - should be one of [`Neumann`](@ref), [`Dudt`](@ref), [`Dirichlet`](@ref), or [`Constrained`](@ref). # Keyword Arguments -- `parameters=ntuple(_ -> nothing, length(functions))` + + - `parameters=ntuple(_ -> nothing, length(functions))` The parameters for the functions, with `parameters[i]` giving the argument `p` in `functions[i]`. # Outputs + The returned value is the corresponding [`BoundaryConditions`](@ref) struct. """ -struct BoundaryConditions{F<:Tuple,C<:Tuple} +struct BoundaryConditions{F <: Tuple, C <: Tuple} functions::F condition_types::C - function BoundaryConditions(functions::F, condition_types::C) where {F,C} + function BoundaryConditions(functions::F, condition_types::C) where {F, C} @assert length(functions) == length(condition_types) "The number of functions and types must be the same." @assert all(t -> t isa ConditionType, condition_types) "The condition types must be ConditionType instances." - return new{F,C}(functions, condition_types) + return new{F, C}(functions, condition_types) end end function Base.show(io::IO, ::MIME"text/plain", bc::BoundaryConditions) @@ -182,35 +189,40 @@ This is a constructor for the [`InternalConditions`](@ref) struct, which holds t See also [`Conditions`](@ref) (which [`FVMProblem`](@ref) wraps this into), [`ConditionType`](@ref), and [`BoundaryConditions`](@ref). # Arguments -- `functions::Union{<:Tuple,<:Function}=()` + + - `functions::Union{<:Tuple,<:Function}=()` The functions that define the internal conditions. These are the functions referred to in `edge_conditions` and `point_conditions`. # Keyword Arguments -- `dirichlet_nodes::Dict{Int,Int}=Dict{Int,Int}()` + + - `dirichlet_nodes::Dict{Int,Int}=Dict{Int,Int}()` A `Dict` that stores all [`Dirichlet`](@ref) points `u` as keys, with keys mapping to indices `idx` that refer to the corresponding condition function and parameters in `functions` and `parameters`. -- `dudt_nodes::Dict{Int,Int}=Dict{Int,Int}()` + + - `dudt_nodes::Dict{Int,Int}=Dict{Int,Int}()` A `Dict` that stores all [`Dudt`](@ref) points `u` as keys, with keys mapping to indices `idx` that refer to the corresponding condition function and parameters in `functions` and `parameters`. -- `parameters::Tuple=ntuple(_ -> nothing, length(functions))` + + - `parameters::Tuple=ntuple(_ -> nothing, length(functions))` The parameters for the functions, with `parameters[i]` giving the argument `p` in `functions[i]`. # Outputs + The returned value is the corresponding [`InternalConditions`](@ref) struct. -!!! note +!!! note - When the internal conditions get merged with the boundary conditions, - any internal conditions that are placed onto the boundary will + When the internal conditions get merged with the boundary conditions, + any internal conditions that are placed onto the boundary will be replaced with the boundary condition at that point on the boundary. """ -struct InternalConditions{F<:Tuple} - dirichlet_nodes::Dict{Int,Int} - dudt_nodes::Dict{Int,Int} +struct InternalConditions{F <: Tuple} + dirichlet_nodes::Dict{Int, Int} + dudt_nodes::Dict{Int, Int} functions::F function InternalConditions(dirichlet_conditions, dudt_conditions, functions::F) where {F} return new{F}(dirichlet_conditions, dudt_conditions, functions) @@ -223,29 +235,29 @@ function Base.show(io::IO, ::MIME"text/plain", ic::InternalConditions) end function BoundaryConditions(mesh::FVMGeometry, functions::Tuple, types::Tuple; - parameters::Tuple=ntuple(_ -> nothing, length(functions))) + parameters::Tuple = ntuple(_ -> nothing, length(functions))) nbnd_idx = DelaunayTriangulation.num_ghost_vertices(mesh.triangulation_statistics) @assert length(functions) == nbnd_idx "The number of boundary conditions must be the same as the number of parts of the mesh's boundary." wrapped_functions = wrap_functions(functions, parameters) return BoundaryConditions(wrapped_functions, types) end function BoundaryConditions(mesh::FVMGeometry, functions::Function, types::ConditionType; - parameters=nothing) - return BoundaryConditions(mesh, (functions,), (types,), parameters=(parameters,)) + parameters = nothing) + return BoundaryConditions(mesh, (functions,), (types,), parameters = (parameters,)) end -@inline function InternalConditions(functions::Tuple=(); - dirichlet_nodes::Dict{Int,Int}=Dict{Int,Int}(), - dudt_nodes::Dict{Int,Int}=Dict{Int,Int}(), - parameters::Tuple=ntuple(_ -> nothing, length(functions))) +@inline function InternalConditions(functions::Tuple = (); + dirichlet_nodes::Dict{Int, Int} = Dict{Int, Int}(), + dudt_nodes::Dict{Int, Int} = Dict{Int, Int}(), + parameters::Tuple = ntuple(_ -> nothing, length(functions))) wrapped_functions = wrap_functions(functions, parameters) return InternalConditions(dirichlet_nodes, dudt_nodes, wrapped_functions) end @inline function InternalConditions(functions::Function; - dirichlet_nodes::Dict{Int,Int}=Dict{Int,Int}(), - dudt_nodes::Dict{Int,Int}=Dict{Int,Int}(), - parameters=nothing) - return InternalConditions((functions,); dirichlet_nodes, dudt_nodes, parameters=(parameters,)) + dirichlet_nodes::Dict{Int, Int} = Dict{Int, Int}(), + dudt_nodes::Dict{Int, Int} = Dict{Int, Int}(), + parameters = nothing) + return InternalConditions((functions,); dirichlet_nodes, dudt_nodes, parameters = (parameters,)) end abstract type AbstractConditions end @@ -253,43 +265,50 @@ abstract type AbstractConditions end """ Conditions{F} <: AbstractConditions -This is a `struct` that holds the boundary and internal conditions for the PDE. The constructor is +This is a `struct` that holds the boundary and internal conditions for the PDE. The constructor is Conditions(mesh::FVMGeometry, bc::BoundaryConditions, ic::InternalConditions=InternalConditions()) The fields are: -# Fields -- `neumann_conditions::Dict{NTuple{2,Int},Int}` +# Fields -A `Dict` that stores all [`Neumann`](@ref) edges `(u, v)` as keys, with keys mapping to indices + - `neumann_conditions::Dict{NTuple{2,Int},Int}` + +A `Dict` that stores all [`Neumann`](@ref) edges `(u, v)` as keys, with keys mapping to indices `idx` that refer to the corresponding condition function and parameters in `functions`. -- `constrained_conditions::Dict{NTuple{2,Int},Int}` + + - `constrained_conditions::Dict{NTuple{2,Int},Int}` A `Dict` that stores all [`Constrained`](@ref) edges `(u, v)` as keys, with keys mapping to indices `idx` that refer to the corresponding condition function and parameters in `functions`. -- `dirichlet_conditions::Dict{Int,Int}` + + - `dirichlet_conditions::Dict{Int,Int}` A `Dict` that stores all [`Dirichlet`](@ref) points `u` as keys, with keys mapping to indices `idx` that refer to the corresponding condition function and parameters in `functions`. -- `dudt_conditions::Dict{Int,Int}` + + - `dudt_conditions::Dict{Int,Int}` A `Dict` that stores all [`Dudt`](@ref) points `u` as keys, with keys mapping to indices `idx` that refer to the corresponding condition function and parameters in `functions`. -- `functions::F<:Tuple` + + - `functions::F<:Tuple` The functions that define the boundary and internal conditions. The `i`th function should correspond to the part of the boundary of -the `mesh` corresponding to the `i`th boundary index, as defined in DelaunayTriangulation.jl. The `i`th function is stored +the `mesh` corresponding to the `i`th boundary index, as defined in DelaunayTriangulation.jl. The `i`th function is stored as a [`ParametrisedFunction`](@ref). """ -struct Conditions{F<:Tuple} <: AbstractConditions - neumann_edges::Dict{NTuple{2,Int},Int} - constrained_edges::Dict{NTuple{2,Int},Int} - dirichlet_nodes::Dict{Int,Int} - dudt_nodes::Dict{Int,Int} +struct Conditions{F <: Tuple} <: AbstractConditions + neumann_edges::Dict{NTuple{2, Int}, Int} + constrained_edges::Dict{NTuple{2, Int}, Int} + dirichlet_nodes::Dict{Int, Int} + dudt_nodes::Dict{Int, Int} functions::F - @inline function Conditions(neumann_edges, constrained_edges, dirichlet_nodes, dudt_nodes, functions::F) where {F} - return new{F}(neumann_edges, constrained_edges, dirichlet_nodes, dudt_nodes, functions) + @inline function Conditions(neumann_edges, constrained_edges, dirichlet_nodes, + dudt_nodes, functions::F) where {F} + return new{F}( + neumann_edges, constrained_edges, dirichlet_nodes, dudt_nodes, functions) end end function Base.show(io::IO, ::MIME"text/plain", conds::AbstractConditions) @@ -316,7 +335,8 @@ Get the index of the function that corresponds to the [`Dudt`](@ref) condition a Get the index of the function that corresponds to the [`Neumann`](@ref) condition at the edge `(i, j)`. """ -@inline get_neumann_fidx(conds::AbstractConditions, i, j) = conds.neumann_edges[(i, j)] +@inline get_neumann_fidx(conds::AbstractConditions, i, j) = conds.neumann_edges[( + i, j)] """ get_dirichlet_fidx(conds, node) @@ -330,7 +350,8 @@ Get the index of the function that corresponds to the [`Dirichlet`](@ref) condit Get the index of the function that corresponds to the [`Constrained`](@ref) condition at the edge `(i, j)`. """ -@inline get_constrained_fidx(conds::AbstractConditions, i, j) = conds.constrained_edges[(i, j)] +@inline get_constrained_fidx(conds::AbstractConditions, i, j) = conds.constrained_edges[( + i, j)] @inline get_f(conds::Conditions{F}, fidx) where {F} = conds.functions[fidx] @@ -339,7 +360,8 @@ Get the index of the function that corresponds to the [`Constrained`](@ref) cond Evaluate the function that corresponds to the condition at `fidx` at the point `(x, y)` at time `t` with solution `u`. """ -@inline eval_condition_fnc(conds::Conditions, fidx, x, y, t, u) = eval_fnc_in_het_tuple(conds.functions, fidx, x, y, t, u) +@inline eval_condition_fnc(conds::Conditions, fidx, x, y, t, + u) = eval_fnc_in_het_tuple(conds.functions, fidx, x, y, t, u) """ is_dudt_node(conds, node) @@ -352,7 +374,8 @@ Check if `node` has a [`Dudt`](@ref) condition. Check if the edge `(i, j)` has a [`Neumann`](@ref) condition. """ -@inline is_neumann_edge(conds::AbstractConditions, i, j) = haskey(conds.neumann_edges, (i, j)) +@inline is_neumann_edge(conds::AbstractConditions, i, j) = haskey(conds.neumann_edges, ( + i, j)) """ is_dirichlet_node(conds, node) @@ -366,7 +389,8 @@ Check if `node` has a [`Dirichlet`](@ref) condition. Check if the edge `(i, j)` has a [`Constrained`](@ref) condition. """ -@inline is_constrained_edge(conds::AbstractConditions, i, j) = haskey(conds.constrained_edges, (i, j)) +@inline is_constrained_edge(conds::AbstractConditions, i, j) = haskey(conds.constrained_edges, ( + i, j)) """ has_constrained_edges(conds) @@ -387,7 +411,9 @@ Check if any edge has a [`Neumann`](@ref) condition. Check if `node` has any condition. """ -@inline has_condition(conds::AbstractConditions, node) = is_dudt_node(conds, node) || is_dirichlet_node(conds, node) +@inline has_condition( + conds::AbstractConditions, node) = is_dudt_node(conds, node) || + is_dirichlet_node(conds, node) """ has_dirichlet_nodes(conds) @@ -434,8 +460,8 @@ Get all edges that have a [`Constrained`](@ref) condition. @inline function prepare_conditions(mesh::FVMGeometry, bc::BoundaryConditions, ic::InternalConditions) bc_functions = bc.functions ic_functions = ic.functions - neumann_edges = Dict{NTuple{2,Int},Int}() - constrained_edges = Dict{NTuple{2,Int},Int}() + neumann_edges = Dict{NTuple{2, Int}, Int}() + constrained_edges = Dict{NTuple{2, Int}, Int}() dirichlet_nodes = copy(ic.dirichlet_nodes) dudt_nodes = copy(ic.dudt_nodes) ne = DelaunayTriangulation.num_segments(mesh.triangulation_statistics) @@ -445,7 +471,8 @@ Get all edges that have a [`Constrained`](@ref) condition. sizehint!(dirichlet_nodes, nv) sizehint!(dudt_nodes, nv) functions = (ic_functions..., bc_functions...) - conditions = Conditions(neumann_edges, constrained_edges, dirichlet_nodes, dudt_nodes, functions) + conditions = Conditions( + neumann_edges, constrained_edges, dirichlet_nodes, dudt_nodes, functions) return conditions end @@ -489,16 +516,16 @@ function merge_conditions!(conditions::Conditions, mesh::FVMGeometry, bc_conditi return conditions end -@inline function Conditions(mesh::FVMGeometry, bc::BoundaryConditions, ic::InternalConditions=InternalConditions()) +@inline function Conditions( + mesh::FVMGeometry, bc::BoundaryConditions, ic::InternalConditions = InternalConditions()) conditions = prepare_conditions(mesh, bc, ic) merge_conditions!(conditions, mesh, bc.condition_types, length(ic.functions)) return conditions end struct SimpleConditions <: AbstractConditions # in 2.0, this needs to be part of Conditions. need this for type stability in FVMSystems - neumann_edges::Dict{NTuple{2,Int},Int} - constrained_edges::Dict{NTuple{2,Int},Int} - dirichlet_nodes::Dict{Int,Int} - dudt_nodes::Dict{Int,Int} + neumann_edges::Dict{NTuple{2, Int}, Int} + constrained_edges::Dict{NTuple{2, Int}, Int} + dirichlet_nodes::Dict{Int, Int} + dudt_nodes::Dict{Int, Int} end - diff --git a/src/equations/boundary_edge_contributions.jl b/src/equations/boundary_edge_contributions.jl index 28ed456..991d5a9 100644 --- a/src/equations/boundary_edge_contributions.jl +++ b/src/equations/boundary_edge_contributions.jl @@ -1,5 +1,6 @@ # primitive: get flux contribution across a boundary edge (i, j), taking care for a Neumann boundary condition -@inline function _get_boundary_flux(prob::AbstractFVMProblem, x, y, t, α, β, γ, nx, ny, i, j, u::T) where {T} +@inline function _get_boundary_flux( + prob::AbstractFVMProblem, x, y, t, α, β, γ, nx, ny, i, j, u::T) where {T} # For checking if an edge is Neumann, we need only check e.g. (i, j) and not (j, i), since we do not allow for internal Neumann edges. ij_is_neumann = is_neumann_edge(prob, i, j) qn = if !ij_is_neumann @@ -11,7 +12,8 @@ end # primitive: get flux contribution across a boundary edge (i, j) in a system, taking care for a Neumann boundary condition for a single variable. This is used as a function barrier -@inline function _get_boundary_flux(prob::FVMSystem, x, y, t, nx, ny, i, j, ℓ, u::T, var, flux) where {T} +@inline function _get_boundary_flux( + prob::FVMSystem, x, y, t, nx, ny, i, j, ℓ, u::T, var, flux) where {T} ij_is_neumann = is_neumann_edge(prob, i, j, var) if !ij_is_neumann _qx, _qy = flux[var] @@ -23,7 +25,8 @@ end end # get flux contribution across a boundary edge (i, j), taking care for a Neumann boundary condition for all variables in a system -@inline function _get_boundary_fluxes(prob::FVMSystem, x, y, t, α, β, γ, nx, ny, i, j, u::T, ℓ) where {T} +@inline function _get_boundary_fluxes( + prob::FVMSystem, x, y, t, α, β, γ, nx, ny, i, j, u::T, ℓ) where {T} all_flux = eval_flux_function(prob, x, y, t, α, β, γ) # if we use the primitive, then we need to recursively evaluate the flux over and over normal_flux = ntuple(_neqs(prob)) do var _get_boundary_flux(prob, x, y, t, nx, ny, i, j, ℓ, u, var, all_flux) @@ -32,7 +35,8 @@ end end # function for getting both fluxes for a non-system problem -@inline function get_boundary_fluxes(prob::AbstractFVMProblem, α::T, β, γ, i, j, t) where {T} +@inline function get_boundary_fluxes( + prob::AbstractFVMProblem, α::T, β, γ, i, j, t) where {T} nx, ny, mᵢx, mᵢy, mⱼx, mⱼy, ℓ = get_boundary_cv_components(prob.mesh, i, j) q1 = _get_boundary_flux(prob, mᵢx, mᵢy, t, α, β, γ, nx, ny, i, j, α * mᵢx + β * mᵢy + γ) q2 = _get_boundary_flux(prob, mⱼx, mⱼy, t, α, β, γ, nx, ny, i, j, α * mⱼx + β * mⱼy + γ) @@ -86,19 +90,23 @@ function get_boundary_edge_contributions!(du, u, prob, t) end # get the contributions to the dudt system across all boundary edges in parallel -function get_parallel_boundary_edge_contributions!(duplicated_du, u, prob, t, chunked_boundary_edges, boundary_edges) +function get_parallel_boundary_edge_contributions!( + duplicated_du, u, prob, t, chunked_boundary_edges, boundary_edges) Threads.@threads for (edge_range, chunk_idx) in chunked_boundary_edges - _get_parallel_boundary_edge_contributions!(duplicated_du, u, prob, t, edge_range, chunk_idx, boundary_edges) + _get_parallel_boundary_edge_contributions!( + duplicated_du, u, prob, t, edge_range, chunk_idx, boundary_edges) end return nothing end # get the contributions to the dudt system across a chunk of boundary edges -function _get_parallel_boundary_edge_contributions!(duplicated_du, u, prob, t, edge_range, chunk_idx, boundary_edges) +function _get_parallel_boundary_edge_contributions!( + duplicated_du, u, prob, t, edge_range, chunk_idx, boundary_edges) for edge_idx in edge_range e = boundary_edges[edge_idx] if prob isa FVMSystem - @views fvm_eqs_single_boundary_edge!(duplicated_du[:, :, chunk_idx], u, prob, t, e) + @views fvm_eqs_single_boundary_edge!( + duplicated_du[:, :, chunk_idx], u, prob, t, e) else @views fvm_eqs_single_boundary_edge!(duplicated_du[:, chunk_idx], u, prob, t, e) end diff --git a/src/equations/control_volumes.jl b/src/equations/control_volumes.jl index 482e45c..e28b5a0 100644 --- a/src/equations/control_volumes.jl +++ b/src/equations/control_volumes.jl @@ -5,12 +5,13 @@ Get the quantities for a control volume edge interior to the associated triangulation, relative to the `edge_index`th edge of the triangle corresponding to `props`. -# Outputs -- `x`: The `x`-coordinate of the edge's midpoint. -- `y`: The `y`-coordinate of the edge's midpoint. -- `nx`: The `x`-component of the edge's normal vector. -- `ny`: The `y`-component of the edge's normal vector. -- `ℓ`: The length of the edge. +# Outputs + + - `x`: The `x`-coordinate of the edge's midpoint. + - `y`: The `y`-coordinate of the edge's midpoint. + - `nx`: The `x`-component of the edge's normal vector. + - `ny`: The `y`-component of the edge's normal vector. + - `ℓ`: The length of the edge. """ @inline function get_cv_components(props, edge_index) x, y = props.cv_edge_midpoints[edge_index] @@ -25,16 +26,17 @@ end Get the quantities for both control volume edges lying a boundary edge `(i, j)`. -# Outputs -- `nx`: The `x`-component of the edge's normal vector. -- `ny`: The `y`-component of the edge's normal vector. -- `mᵢx`: The `x`-coordinate of the midpoint of the `i`th vertex and the edge's midpoint. -- `mᵢy`: The `y`-coordinate of the midpoint of the `i`th vertex and the edge's midpoint. -- `mⱼx`: The `x`-coordinate of the midpoint of the `j`th vertex and the edge's midpoint. -- `mⱼy`: The `y`-coordinate of the midpoint of the `j`th vertex and the edge's midpoint. -- `ℓᵢ`: Half the length of the boundary edge, which is the length of the control volume edge. -- `T`: The triangle containing the boundary edge. -- `props`: The [`TriangleProperties`](@ref) for `T`. +# Outputs + + - `nx`: The `x`-component of the edge's normal vector. + - `ny`: The `y`-component of the edge's normal vector. + - `mᵢx`: The `x`-coordinate of the midpoint of the `i`th vertex and the edge's midpoint. + - `mᵢy`: The `y`-coordinate of the midpoint of the `i`th vertex and the edge's midpoint. + - `mⱼx`: The `x`-coordinate of the midpoint of the `j`th vertex and the edge's midpoint. + - `mⱼy`: The `y`-coordinate of the midpoint of the `j`th vertex and the edge's midpoint. + - `ℓᵢ`: Half the length of the boundary edge, which is the length of the control volume edge. + - `T`: The triangle containing the boundary edge. + - `props`: The [`TriangleProperties`](@ref) for `T`. """ @inline function get_boundary_cv_components(mesh, i, j) p, q = get_point(mesh, i, j) @@ -52,4 +54,3 @@ Get the quantities for both control volume edges lying a boundary edge `(i, j)`. T, props = _safe_get_triangle_props(mesh, T) return nx, ny, mᵢx, mᵢy, mⱼx, mⱼy, ℓᵢ, T, props end - diff --git a/src/equations/dirichlet.jl b/src/equations/dirichlet.jl index 0343b26..f02beb5 100644 --- a/src/equations/dirichlet.jl +++ b/src/equations/dirichlet.jl @@ -1,26 +1,30 @@ # primitive: get dirichlet value for a non-system -@inline function get_dirichlet_condition(prob::AbstractFVMProblem, u::T, t, i, function_index) where {T} +@inline function get_dirichlet_condition( + prob::AbstractFVMProblem, u::T, t, i, function_index) where {T} p = get_point(prob, i) x, y = getxy(p) - return eval_condition_fnc(prob, function_index, x, y, t, u[i]) + return eval_condition_fnc(prob, function_index, x, y, t, u[i]) end # primitive: get dirichlet value for a system -@inline function get_dirichlet_condition(prob::FVMSystem, u::T, t, i, var, function_index) where {T} +@inline function get_dirichlet_condition( + prob::FVMSystem, u::T, t, i, var, function_index) where {T} p = get_point(prob, i) x, y = getxy(p) - return @views eval_condition_fnc(prob, function_index, var, x, y, t, u[:, i]) + return @views eval_condition_fnc(prob, function_index, var, x, y, t, u[:, i]) end # get the dirichlet value and update u non-system. need this function barriers for inference -@inline function update_dirichlet_nodes_single!(u::T, t, prob::AbstractFVMProblem, i, function_index) where {T} +@inline function update_dirichlet_nodes_single!( + u::T, t, prob::AbstractFVMProblem, i, function_index) where {T} d = get_dirichlet_condition(prob, u, t, i, function_index) u[i] = d return nothing end # get the dirichlet value and update u for a system. need this function barriers for inference -@inline function update_dirichlet_nodes_single!(u::T, t, prob::FVMSystem, i, var, function_index) where {T} +@inline function update_dirichlet_nodes_single!( + u::T, t, prob::FVMSystem, i, var, function_index) where {T} d = get_dirichlet_condition(prob, u, t, i, var, function_index) u[var, i] = d return nothing @@ -35,7 +39,7 @@ function serial_update_dirichlet_nodes!(u, t, prob::AbstractFVMProblem) end # get the dirichlet value and update u for a system for each dirichlet_node -function serial_update_dirichlet_nodes!(u, t, prob::FVMSystem) +function serial_update_dirichlet_nodes!(u, t, prob::FVMSystem) for var in 1:_neqs(prob) for (i, function_index) in get_dirichlet_nodes(prob, var) update_dirichlet_nodes_single!(u, t, prob, i, var, function_index) @@ -54,7 +58,7 @@ function parallel_update_dirichlet_nodes!(u, t, p, prob::AbstractFVMProblem) end # get the dirichlet value and update u for a system for each dirichlet_node in parallel -function parallel_update_dirichlet_nodes!(u, t, p, prob::FVMSystem) +function parallel_update_dirichlet_nodes!(u, t, p, prob::FVMSystem) dirichlet_nodes = p.dirichlet_nodes for var in 1:_neqs(prob) Threads.@threads for i in dirichlet_nodes[var] @@ -74,4 +78,4 @@ function update_dirichlet_nodes!(integrator) return parallel_update_dirichlet_nodes!(integrator.u, integrator.t, integrator.p, prob) end return nothing -end \ No newline at end of file +end diff --git a/src/equations/individual_flux_contributions.jl b/src/equations/individual_flux_contributions.jl index 9cd5a40..9cae263 100644 --- a/src/equations/individual_flux_contributions.jl +++ b/src/equations/individual_flux_contributions.jl @@ -24,7 +24,8 @@ end end # get flux contribution for a non-system, also picking up the cv components first -@inline function get_flux(prob::AbstractFVMProblem, props, α::A, β, γ, t::T, edge_index) where {A,T} +@inline function get_flux( + prob::AbstractFVMProblem, props, α::A, β, γ, t::T, edge_index) where {A, T} x, y, nx, ny, ℓ = get_cv_components(props, edge_index) qn = _get_flux(prob, x, y, t, α, β, γ, nx, ny) return qn * ℓ @@ -38,11 +39,11 @@ end end # get flux contribution for a system, also picking up the cv components first, and getting it for all variables -function get_flux(prob::FVMSystem, props, α::A, β, γ, t::T, edge_index) where {A,T} +function get_flux(prob::FVMSystem, props, α::A, β, γ, t::T, edge_index) where {A, T} x, y, nx, ny, ℓ = get_cv_components(props, edge_index) q = eval_flux_function(prob, x, y, t, α, β, γ) qn = ntuple(_neqs(prob)) do var _get_flux(nx, ny, q, ℓ, var) end return qn -end \ No newline at end of file +end diff --git a/src/equations/main_equations.jl b/src/equations/main_equations.jl index 5b3f30f..f3b526b 100644 --- a/src/equations/main_equations.jl +++ b/src/equations/main_equations.jl @@ -2,27 +2,27 @@ """ fvm_eqs!(du, u, p, t) -Computes the finite volume method equations for the current time `t` +Computes the finite volume method equations for the current time `t` and solution `u`. This function is public API. -The argument `p` depends on whether the problem is being solved +The argument `p` depends on whether the problem is being solved in parallel or not. If it is solved serially, than the fields are: -- `p.prob`: The `prob <: AbstractFVMProblem`. -- `p.parallel`: `Val(false)`. + - `p.prob`: The `prob <: AbstractFVMProblem`. + - `p.parallel`: `Val(false)`. If the problem is solved in parallel, then the fields are: -- `p.prob`: The `prob <: AbstractFVMProblem`. -- `p.parallel`: `Val(true)`. -- `p.duplicated_du`: A `Matrix` of the same size as `du` that is used to store the contributions to `du` from each thread. -- `p.solid_triangles`: A `Vector` of the solid triangles in the triangulation. -- `p.solid_vertices`: A `Vector` of the solid vertices in the triangulation. -- `p.chunked_solid_triangles`: A `Vector` of tuples of the form `(range, chunk_idx)` where `range` is a range of indices into `p.solid_triangles` and `chunk_idx` is the index of the chunk. -- `p.boundary_edges`: A `Vector` of the boundary edges in the triangulation. -- `p.chunked_boundary_edges`: A `Vector` of tuples of the form `(range, chunk_idx)` where `range` is a range of indices into `p.boundary_edges` and `chunk_idx` is the index of the chunk. + - `p.prob`: The `prob <: AbstractFVMProblem`. + - `p.parallel`: `Val(true)`. + - `p.duplicated_du`: A `Matrix` of the same size as `du` that is used to store the contributions to `du` from each thread. + - `p.solid_triangles`: A `Vector` of the solid triangles in the triangulation. + - `p.solid_vertices`: A `Vector` of the solid vertices in the triangulation. + - `p.chunked_solid_triangles`: A `Vector` of tuples of the form `(range, chunk_idx)` where `range` is a range of indices into `p.solid_triangles` and `chunk_idx` is the index of the chunk. + - `p.boundary_edges`: A `Vector` of the boundary edges in the triangulation. + - `p.chunked_boundary_edges`: A `Vector` of tuples of the form `(range, chunk_idx)` where `range` is a range of indices into `p.boundary_edges` and `chunk_idx` is the index of the chunk. -These fields are public API, although note that they are not intended to be modified by the user, and we may freely +These fields are public API, although note that they are not intended to be modified by the user, and we may freely add in new fields over new versions. """ function fvm_eqs!(du, u, p, t) @@ -55,8 +55,10 @@ function parallel_fvm_eqs!(du, u, p, t) fill!(du, zero(eltype(du))) _duplicated_du = get_tmp(duplicated_du, du) fill!(_duplicated_du, zero(eltype(du))) - get_parallel_triangle_contributions!(_duplicated_du, u, prob, t, chunked_solid_triangles, solid_triangles) - get_parallel_boundary_edge_contributions!(_duplicated_du, u, prob, t, chunked_boundary_edges, boundary_edges) + get_parallel_triangle_contributions!( + _duplicated_du, u, prob, t, chunked_solid_triangles, solid_triangles) + get_parallel_boundary_edge_contributions!( + _duplicated_du, u, prob, t, chunked_boundary_edges, boundary_edges) combine_duplicated_du!(du, _duplicated_du, prob) get_parallel_source_contributions!(du, u, prob, t, solid_vertices) return du @@ -74,4 +76,4 @@ function combine_duplicated_du!(du, duplicated_du, prob) end end return nothing -end \ No newline at end of file +end diff --git a/src/equations/shape_functions.jl b/src/equations/shape_functions.jl index 4c842d8..152bd45 100644 --- a/src/equations/shape_functions.jl +++ b/src/equations/shape_functions.jl @@ -9,11 +9,11 @@ end # primitive: get the shape function coefficients for a system -@inline function get_shape_function_coefficients(props::TriangleProperties, T, u, prob::FVMSystem) +@inline function get_shape_function_coefficients(props::TriangleProperties, T, u, prob::FVMSystem) i, j, k = triangle_vertices(T) s₁, s₂, s₃, s₄, s₅, s₆, s₇, s₈, s₉ = props.shape_function_coefficients α = ntuple(ℓ -> s₁ * u[ℓ, i] + s₂ * u[ℓ, j] + s₃ * u[ℓ, k], _neqs(prob)) β = ntuple(ℓ -> s₄ * u[ℓ, i] + s₅ * u[ℓ, j] + s₆ * u[ℓ, k], _neqs(prob)) γ = ntuple(ℓ -> s₇ * u[ℓ, i] + s₈ * u[ℓ, j] + s₉ * u[ℓ, k], _neqs(prob)) return α, β, γ -end \ No newline at end of file +end diff --git a/src/equations/source_contributions.jl b/src/equations/source_contributions.jl index ed966ef..751467d 100644 --- a/src/equations/source_contributions.jl +++ b/src/equations/source_contributions.jl @@ -23,13 +23,15 @@ end S = zero(eltype(T)) else # Dudt function_index = get_dudt_fidx(prob, i, var) - S = @views eval_condition_fnc(prob, function_index, var, x, y, t, u[:, i]) * one(eltype(T)) + S = @views eval_condition_fnc(prob, function_index, var, x, y, t, u[:, i]) * + one(eltype(T)) end return S end # add on the final source term for a single node for a non-system -@inline function fvm_eqs_single_source_contribution!(du, u::T, prob::AbstractFVMProblem, t, i) where {T} +@inline function fvm_eqs_single_source_contribution!( + du, u::T, prob::AbstractFVMProblem, t, i) where {T} if !DelaunayTriangulation.has_vertex(prob.mesh.triangulation, i) du[i] = zero(eltype(T)) else @@ -44,7 +46,8 @@ end end # add on the final source term for a single node for a system for all variables -@inline function fvm_eqs_single_source_contribution!(du, u::T, prob::FVMSystem, t, i) where {T} +@inline function fvm_eqs_single_source_contribution!( + du, u::T, prob::FVMSystem, t, i) where {T} if !DelaunayTriangulation.has_vertex(prob.mesh.triangulation, i) for var in 1:_neqs(prob) du[var, i] = zero(eltype(T)) diff --git a/src/equations/triangle_contributions.jl b/src/equations/triangle_contributions.jl index 75df2e0..e693f9c 100644 --- a/src/equations/triangle_contributions.jl +++ b/src/equations/triangle_contributions.jl @@ -15,7 +15,7 @@ end end # update du with the fluxes from each centroid-edge edge for a system for all variables -@inline function update_du!(du, prob::FVMSystem, i, j, k, summand₁, summand₂, summand₃) +@inline function update_du!(du, prob::FVMSystem, i, j, k, summand₁, summand₂, summand₃) for var in 1:_neqs(prob) du[var, i] = du[var, i] + summand₃[var] - summand₁[var] du[var, j] = du[var, j] + summand₁[var] - summand₂[var] @@ -43,15 +43,18 @@ function get_triangle_contributions!(du, u, prob, t) end # get the contributions to the dudt system across all triangles in parallel -function get_parallel_triangle_contributions!(duplicated_du, u, prob, t, chunked_solid_triangles, solid_triangles) +function get_parallel_triangle_contributions!( + duplicated_du, u, prob, t, chunked_solid_triangles, solid_triangles) Threads.@threads for (triangle_range, chunk_idx) in chunked_solid_triangles - _get_parallel_triangle_contributions!(duplicated_du, u, prob, t, triangle_range, chunk_idx, solid_triangles) + _get_parallel_triangle_contributions!( + duplicated_du, u, prob, t, triangle_range, chunk_idx, solid_triangles) end return nothing end # get the contributions to the dudt system across a chunk of triangles -function _get_parallel_triangle_contributions!(duplicated_du, u, prob, t, triangle_range, chunk_idx, solid_triangles) +function _get_parallel_triangle_contributions!( + duplicated_du, u, prob, t, triangle_range, chunk_idx, solid_triangles) for triangle_idx in triangle_range T = solid_triangles[triangle_idx] if prob isa FVMSystem @@ -61,4 +64,4 @@ function _get_parallel_triangle_contributions!(duplicated_du, u, prob, t, triang end end return nothing -end \ No newline at end of file +end diff --git a/src/geometry.jl b/src/geometry.jl index d5e6563..3db007b 100644 --- a/src/geometry.jl +++ b/src/geometry.jl @@ -5,23 +5,24 @@ This is a struct for holding the properties of a control volume's intersection with a triangle. # Fields -- `shape_function_coefficients::NTuple{9,Float64}`: The shape function coefficients for the triangle. -- `cv_edge_midpoints::NTuple{3,NTuple{2,Float64}}`: The midpoints of the control volume edges. If the triangle is `(i, j, k)`, then the edges are given in the order `(i, j)`, `(j, k)`, and `(k, i)`, where 'edge' refers to the edge joining e.g. the midpoint of the edge `(i, j)` to the centroid of the triangle. -- `cv_edge_normals::NTuple{3,NTuple{2,Float64}}`: The normal vectors to the control volume edges, in the same order as in `cv_edge_midpoints`. -- `cv_edge_lengths::NTuple{3,Float64}`: The lengths of the control volume edges, in the same order as in `cv_edge_midpoints`. -!!! notes + - `shape_function_coefficients::NTuple{9,Float64}`: The shape function coefficients for the triangle. + - `cv_edge_midpoints::NTuple{3,NTuple{2,Float64}}`: The midpoints of the control volume edges. If the triangle is `(i, j, k)`, then the edges are given in the order `(i, j)`, `(j, k)`, and `(k, i)`, where 'edge' refers to the edge joining e.g. the midpoint of the edge `(i, j)` to the centroid of the triangle. + - `cv_edge_normals::NTuple{3,NTuple{2,Float64}}`: The normal vectors to the control volume edges, in the same order as in `cv_edge_midpoints`. + - `cv_edge_lengths::NTuple{3,Float64}`: The lengths of the control volume edges, in the same order as in `cv_edge_midpoints`. + +!!! notes The shape function coefficients are defined so that, if `s` are the coefficients and the triangle is `T = (i, j, k)`, - with function values `u[i]`, `u[j]`, and `u[k]` at the vertices `i`, `j`, and `k`, respectively, - then `αxₙ + βyₙ + γₙ = u[n]` for `n = i, j, k`, where `xₙ` and `yₙ` are the `x`- and `y`-coordinates of the `n`th vertex, respectively, + with function values `u[i]`, `u[j]`, and `u[k]` at the vertices `i`, `j`, and `k`, respectively, + then `αxₙ + βyₙ + γₙ = u[n]` for `n = i, j, k`, where `xₙ` and `yₙ` are the `x`- and `y`-coordinates of the `n`th vertex, respectively, `α = s₁u₁ + s₂u₂ + s₃u₃`, `β = s₄u₁ + s₅u₂ + s₆u₃`, and `γ = s₇u₁ + s₈u₂ + s₉u₃`. """ struct TriangleProperties - shape_function_coefficients::NTuple{9,Float64} - cv_edge_midpoints::NTuple{3,NTuple{2,Float64}} - cv_edge_normals::NTuple{3,NTuple{2,Float64}} - cv_edge_lengths::NTuple{3,Float64} + shape_function_coefficients::NTuple{9, Float64} + cv_edge_midpoints::NTuple{3, NTuple{2, Float64}} + cv_edge_normals::NTuple{3, NTuple{2, Float64}} + cv_edge_lengths::NTuple{3, Float64} end """ @@ -30,19 +31,21 @@ end This is a constructor for the [`FVMGeometry`](@ref) struct, which holds the mesh and associated data for the PDE. !!! note + It is assumed that all vertices in `tri` are in the triangulation, meaning `v` is in `tri` for each `v` in `DelaunayTriangulation.each_point_index(tri)`. -# Fields -- `triangulation`: The underlying `Triangulation` from DelaunayTriangulation.jl. -- `triangulation_statistics`: The statistics of the triangulation. -- `cv_volumes::Vector{Float64}`: A `Vector` of the volumes of each control volume. -- `triangle_props::Dict{NTuple{3,Int},TriangleProperties}`: A `Dict` mapping the indices of each triangle to its [`TriangleProperties`]. +# Fields + + - `triangulation`: The underlying `Triangulation` from DelaunayTriangulation.jl. + - `triangulation_statistics`: The statistics of the triangulation. + - `cv_volumes::Vector{Float64}`: A `Vector` of the volumes of each control volume. + - `triangle_props::Dict{NTuple{3,Int},TriangleProperties}`: A `Dict` mapping the indices of each triangle to its [`TriangleProperties`]. """ -struct FVMGeometry{T,S} +struct FVMGeometry{T, S} triangulation::T triangulation_statistics::S cv_volumes::Vector{Float64} - triangle_props::Dict{NTuple{3,Int},TriangleProperties} + triangle_props::Dict{NTuple{3, Int}, TriangleProperties} end function Base.show(io::IO, ::MIME"text/plain", geo::FVMGeometry) nv = DelaunayTriangulation.num_solid_vertices(geo.triangulation_statistics) @@ -55,7 +58,8 @@ end Get the [`TriangleProperties`](@ref) for the triangle `(i, j, k)` in `mesh`. """ -get_triangle_props(mesh::FVMGeometry, i, j, k) = mesh.triangle_props[(i, j, k)] +get_triangle_props(mesh::FVMGeometry, i, j, k) = mesh.triangle_props[( + i, j, k)] """ get_volume(mesh, i) @@ -69,7 +73,9 @@ get_volume(mesh::FVMGeometry, i) = mesh.cv_volumes[i] Get the `i`th point in `mesh`. """ -DelaunayTriangulation.get_point(mesh::FVMGeometry, i) = DelaunayTriangulation.get_point(mesh.triangulation, i) +function DelaunayTriangulation.get_point(mesh::FVMGeometry, i) + DelaunayTriangulation.get_point(mesh.triangulation, i) +end #= function build_vertex_map(tri::Triangulation) @@ -92,7 +98,7 @@ function FVMGeometry(tri::Triangulation) nn = DelaunayTriangulation.num_points(tri) nt = num_solid_triangles(stats) cv_volumes = zeros(nn) - triangle_props = Dict{NTuple{3,Int},TriangleProperties}() + triangle_props = Dict{NTuple{3, Int}, TriangleProperties}() sizehint!(cv_volumes, nn) sizehint!(triangle_props, nt) for T in each_solid_triangle(tri) @@ -151,7 +157,9 @@ function FVMGeometry(tri::Triangulation) n₂x, n₂y = e₂y / ℓ₂, -e₂x / ℓ₂ n₃x, n₃y = e₃y / ℓ₃, -e₃x / ℓ₃ ## Now construct the TriangleProperties - triangle_props[triangle_vertices(T)] = TriangleProperties(shape_function_coefficients, ((m₁cx, m₁cy), (m₂cx, m₂cy), (m₃cx, m₃cy)), ((n₁x, n₁y), (n₂x, n₂y), (n₃x, n₃y)), (ℓ₁, ℓ₂, ℓ₃)) + triangle_props[triangle_vertices(T)] = TriangleProperties( + shape_function_coefficients, ((m₁cx, m₁cy), (m₂cx, m₂cy), (m₃cx, m₃cy)), + ((n₁x, n₁y), (n₂x, n₂y), (n₃x, n₃y)), (ℓ₁, ℓ₂, ℓ₃)) end return FVMGeometry(tri, stats, cv_volumes, triangle_props) -end \ No newline at end of file +end diff --git a/src/problem.jl b/src/problem.jl index 35bcc6c..38eadd0 100644 --- a/src/problem.jl +++ b/src/problem.jl @@ -3,8 +3,10 @@ abstract type AbstractFVMProblem end @inline get_neumann_fidx(prob::AbstractFVMProblem, i, j) = get_neumann_fidx(prob.conditions, i, j) @inline get_dirichlet_fidx(prob::AbstractFVMProblem, i) = get_dirichlet_fidx(prob.conditions, i) @inline get_constrained_fidx(prob::AbstractFVMProblem, i, j) = get_constrained_fidx(prob.conditions, i, j) -@inline eval_condition_fnc(prob::AbstractFVMProblem, fidx, x, y, t, u) = eval_condition_fnc(prob.conditions, fidx, x, y, t, u) -@inline eval_source_fnc(prob::AbstractFVMProblem, x, y, t, u) = prob.source_function(x, y, t, u, prob.source_parameters) +@inline eval_condition_fnc(prob::AbstractFVMProblem, fidx, x, y, t, + u) = eval_condition_fnc(prob.conditions, fidx, x, y, t, u) +@inline eval_source_fnc(prob::AbstractFVMProblem, x, y, t, + u) = prob.source_function(x, y, t, u, prob.source_parameters) @inline is_dudt_node(prob::AbstractFVMProblem, node) = is_dudt_node(prob.conditions, node) @inline is_neumann_edge(prob::AbstractFVMProblem, i, j) = is_neumann_edge(prob.conditions, i, j) @inline is_dirichlet_node(prob::AbstractFVMProblem, node) = is_dirichlet_node(prob.conditions, node) @@ -30,51 +32,64 @@ abstract type AbstractFVMProblem end Constructs an `FVMProblem`. See also [`FVMSystem`](@ref) and [`SteadyFVMProblem`](@ref). -# Arguments -- `mesh::FVMGeometry` +# Arguments + + - `mesh::FVMGeometry` The mesh on which the PDE is defined, given as a [`FVMGeometry`](@ref). -- `boundary_conditions::BoundaryConditions` + + - `boundary_conditions::BoundaryConditions` The boundary conditions for the PDE, given as a [`BoundaryConditions`](@ref). -- `internal_conditions::InternalConditions=InternalConditions()` -The internal conditions for the PDE, given as an [`InternalConditions`](@ref). This argument + - `internal_conditions::InternalConditions=InternalConditions()` + +The internal conditions for the PDE, given as an [`InternalConditions`](@ref). This argument is optional. # Keyword Arguments -- `diffusion_function=nothing` + + - `diffusion_function=nothing` If `isnothing(flux_function)`, then this can be provided to give the diffusion-source formulation. See also [`construct_flux_function`](@ref). Should be of the form `D(x, y, t, u, p)`. -- `diffusion_parameters=nothing` + + - `diffusion_parameters=nothing` The argument `p` for `diffusion_function`. -- `source_function=(x, y, t, u, p) -> zero(u)` + + - `source_function=(x, y, t, u, p) -> zero(u)` The source term, given in the form `S(x, y, t, u, p)`. -- `source_parameters=nothing` + + - `source_parameters=nothing` The argument `p` for `source_function`. -- `flux_function=nothing` + + - `flux_function=nothing` The flux function, given in the form `q(x, y, t, α, β, γ, p) ↦ (qx, qy)`, where `(qx, qy)` is the flux vector, `(α, β, γ)` are the shape function coefficients for estimating `u ≈ αx+βy+γ`. If `isnothing(flux_function)`, then `diffusion_function` is used instead to construct the function. -- `flux_parameters=nothing` + + - `flux_parameters=nothing` The argument `p` for `flux_function`. -- `initial_condition` + + - `initial_condition` The initial condition, with `initial_condition[i]` the initial value at the `i`th node of the `mesh`. -- `initial_time=0.0` + + - `initial_time=0.0` The initial time. -- `final_time` + + - `final_time` The final time. # Outputs + The returned value is the corresponding [`FVMProblem`](@ref) struct. You can then solve the problem using [`solve(::Union{FVMProblem,FVMSystem}, ::Any; kwargs...)`](@ref) from DifferentialEquations.jl. """ -struct FVMProblem{FG,BC,F,FP,R,RP,IC,FT} <: AbstractFVMProblem +struct FVMProblem{FG, BC, F, FP, R, RP, IC, FT} <: AbstractFVMProblem mesh::FG conditions::BC flux_function::F @@ -91,19 +106,22 @@ function Base.show(io::IO, ::MIME"text/plain", prob::FVMProblem) tf = prob.final_time print(io, "FVMProblem with $(nv) nodes and time span ($t0, $tf)") end -@inline eval_flux_function(prob::FVMProblem, x, y, t, α, β, γ) = prob.flux_function(x, y, t, α, β, γ, prob.flux_parameters) - -function FVMProblem(mesh::FVMGeometry, boundary_conditions::BoundaryConditions, internal_conditions::InternalConditions=InternalConditions(); - diffusion_function=nothing, - diffusion_parameters=nothing, - source_function=(x, y, t, u, p) -> zero(eltype(u)), - source_parameters=nothing, - flux_function=nothing, - flux_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time) - @assert length(initial_condition) == DelaunayTriangulation.num_points(mesh.triangulation) "The initial condition must have the same number of elements as the number of nodes in the mesh (including nodes that aren't vertices in the mesh itself)." +@inline eval_flux_function(prob::FVMProblem, x, y, t, α, β, + γ) = prob.flux_function(x, y, t, α, β, γ, prob.flux_parameters) + +function FVMProblem(mesh::FVMGeometry, boundary_conditions::BoundaryConditions, + internal_conditions::InternalConditions = InternalConditions(); + diffusion_function = nothing, + diffusion_parameters = nothing, + source_function = (x, y, t, u, p) -> zero(eltype(u)), + source_parameters = nothing, + flux_function = nothing, + flux_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time) + @assert length(initial_condition) == + DelaunayTriangulation.num_points(mesh.triangulation) "The initial condition must have the same number of elements as the number of nodes in the mesh (including nodes that aren't vertices in the mesh itself)." conditions = Conditions(mesh, boundary_conditions, internal_conditions) return FVMProblem(mesh, conditions; diffusion_function, diffusion_parameters, @@ -112,16 +130,17 @@ function FVMProblem(mesh::FVMGeometry, boundary_conditions::BoundaryConditions, initial_condition, initial_time, final_time) end function FVMProblem(mesh::FVMGeometry, conditions::Conditions; - diffusion_function=nothing, - diffusion_parameters=nothing, - source_function=(x, y, t, u, p) -> zero(eltype(u)), - source_parameters=nothing, - flux_function=nothing, - flux_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time) - @assert length(initial_condition) == DelaunayTriangulation.num_points(mesh.triangulation) "The initial condition must have the same number of elements as the number of nodes in the mesh (including nodes that aren't vertices in the mesh itself)." + diffusion_function = nothing, + diffusion_parameters = nothing, + source_function = (x, y, t, u, p) -> zero(eltype(u)), + source_parameters = nothing, + flux_function = nothing, + flux_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time) + @assert length(initial_condition) == + DelaunayTriangulation.num_points(mesh.triangulation) "The initial condition must have the same number of elements as the number of nodes in the mesh (including nodes that aren't vertices in the mesh itself)." updated_flux_fnc = construct_flux_function(flux_function, diffusion_function, diffusion_parameters) return FVMProblem(mesh, conditions, updated_flux_fnc, flux_parameters, @@ -132,18 +151,18 @@ end """ SteadyFVMProblem(prob::AbstractFVMProblem) -This is a wrapper for an `AbstractFVMProblem` that indicates that the problem is to be solved as a steady-state problem. -You can then solve the problem using [`solve(::SteadyFVMProblem, ::Any; kwargs...)`](@ref) from NonlinearSolve.jl. Note that you +This is a wrapper for an `AbstractFVMProblem` that indicates that the problem is to be solved as a steady-state problem. +You can then solve the problem using [`solve(::SteadyFVMProblem, ::Any; kwargs...)`](@ref) from NonlinearSolve.jl. Note that you need to have set the final time to `Inf` if you want a steady state out at infinity rather than some finite actual time. See also [`FVMProblem`](@ref) and [`FVMSystem`](@ref). """ -struct SteadyFVMProblem{P<:AbstractFVMProblem,M<:FVMGeometry} <: AbstractFVMProblem +struct SteadyFVMProblem{P <: AbstractFVMProblem, M <: FVMGeometry} <: AbstractFVMProblem problem::P mesh::M end function SteadyFVMProblem(prob::P) where {P} - return SteadyFVMProblem{P,typeof(prob.mesh)}(prob, prob.mesh) + return SteadyFVMProblem{P, typeof(prob.mesh)}(prob, prob.mesh) end function Base.show(io::IO, ::MIME"text/plain", prob::SteadyFVMProblem) @@ -155,69 +174,83 @@ function Base.show(io::IO, ::MIME"text/plain", prob::SteadyFVMProblem) print(io, "SteadyFVMProblem with $(nv) nodes and $(_neqs(prob)) equations") end end -@inline eval_flux_function(prob::SteadyFVMProblem, x, y, t, α, β, γ) = eval_flux_function(prob.problem, x, y, t, α, β, γ) +@inline eval_flux_function(prob::SteadyFVMProblem, x, y, t, α, β, + γ) = eval_flux_function(prob.problem, x, y, t, α, β, γ) """ FVMSystem(prob1, prob2, ..., probN) -Constructs a representation for a system of PDEs, where each `probi` is +Constructs a representation for a system of PDEs, where each `probi` is a [`FVMProblem`](@ref) for the `i`th component of the system. -For these [`FVMProblem`](@ref)s, the functions involved, such as the condition functions, should -all be defined so that the `u` argument assumes the form `u = (u₁, u₂, ..., uN)` (both `Tuple`s and `Vector`s will be passed), -where `uᵢ` is the solution for the `i`th component of the system. For the flux functions, +For these [`FVMProblem`](@ref)s, the functions involved, such as the condition functions, should +all be defined so that the `u` argument assumes the form `u = (u₁, u₂, ..., uN)` (both `Tuple`s and `Vector`s will be passed), +where `uᵢ` is the solution for the `i`th component of the system. For the flux functions, which for a [`FVMProblem`](@ref) takes the form q(x, y, t, α, β, γ, p) ↦ (qx, qy), -the same form is used, except `α`, `β`, `γ` are all `Tuple`s so that `α[i]*x + β[i]*y + γ[i]` is the -approximation to `uᵢ`. +the same form is used, except `α`, `β`, `γ` are all `Tuple`s so that `α[i]*x + β[i]*y + γ[i]` is the +approximation to `uᵢ`. !!! warning "Providing default flux functions" - Due to this difference in flux functions, and the need to provide - `α`, `β`, and `γ` to the flux function, for `FVMSystem`s you need to + Due to this difference in flux functions, and the need to provide + `α`, `β`, and `γ` to the flux function, for `FVMSystem`s you need to provide a flux function rather than a diffusion function. If you do - provide a diffusion function, it will error when you try to solve + provide a diffusion function, it will error when you try to solve the problem. -This problem is solved in the same way as a [`FVMProblem`](@ref), except the problem is defined such that -the solution returns a matrix at each time, where the `(j, i)`th component corresponds to the solution at the `i`th +This problem is solved in the same way as a [`FVMProblem`](@ref), except the problem is defined such that +the solution returns a matrix at each time, where the `(j, i)`th component corresponds to the solution at the `i`th node for the `j`th component. See also [`FVMProblem`](@ref) and [`SteadyFVMProblem`](@ref). -!!! note - To construct a steady-state problem for an `FVMSystem`, you need to apply - [`SteadyFVMProblem`](@ref) to the system rather than first applying it to - each individual [`FVMProblem`](@ref) in the system. +!!! note + + +To construct a steady-state problem for an `FVMSystem`, you need to apply +[`SteadyFVMProblem`](@ref) to the system rather than first applying it to +each individual [`FVMProblem`](@ref) in the system. """ -struct FVMSystem{N,FG,P,IC,FT,F,S,FF} <: AbstractFVMProblem +struct FVMSystem{N, FG, P, IC, FT, F, S, FF} <: AbstractFVMProblem mesh::FG problems::P initial_condition::IC initial_time::FT final_time::FT - conditions::NTuple{N,SimpleConditions} - cnum_fncs::NTuple{N,Int} # cumulative numbers. e.g. if num_fncs is the number of functions for each variable, then cnum_fncs[i] = sum(num_fncs[1:i-1]). Can't just use the number of boundary vertices since we might also have internal conditions + conditions::NTuple{N, SimpleConditions} + cnum_fncs::NTuple{N, Int} # cumulative numbers. e.g. if num_fncs is the number of functions for each variable, then cnum_fncs[i] = sum(num_fncs[1:i-1]). Can't just use the number of boundary vertices since we might also have internal conditions functions::F - source_functions::S + source_functions::S flux_functions::FF - function FVMSystem(mesh::FG, problems::P, initial_condition::IC, initial_time::FT, final_time::FT, conditions, num_fncs, functions::F, source_functions::S, flux_functions::FF) where {FG<:FVMGeometry,P,IC,FT,F,S,FF} + function FVMSystem(mesh::FG, problems::P, initial_condition::IC, initial_time::FT, + final_time::FT, conditions, num_fncs, functions::F, source_functions::S, + flux_functions::FF) where {FG <: FVMGeometry, P, IC, FT, F, S, FF} @assert length(problems) > 0 "There must be at least one problem." @assert all(p -> p.mesh === mesh, problems) "All problems must have the same mesh." @assert all(p -> p.initial_time === initial_time, problems) "All problems must have the same initial time." @assert all(p -> p.final_time === final_time, problems) "All problems must have the same final time." - @assert size(initial_condition) == (length(problems), length(problems[1].initial_condition)) "The initial condition must be a matrix with the same number of rows as the number of problems." - @assert all(i -> problems[i].conditions.neumann_edges == conditions[i].neumann_edges, 1:length(problems)) "The Neumann edges in the `i`th `SimpleConditions` must match those from the `i`th problem." - @assert all(i -> problems[i].conditions.constrained_edges == conditions[i].constrained_edges, 1:length(problems)) "The constrained edges in the `i`th `SimpleConditions` must match those from the `i`th problem." - @assert all(i -> problems[i].conditions.dirichlet_nodes == conditions[i].dirichlet_nodes, 1:length(problems)) "The Dirichlet nodes in the `i`th `SimpleConditions` must match those from the `i`th problem." + @assert size(initial_condition) == + (length(problems), length(problems[1].initial_condition)) "The initial condition must be a matrix with the same number of rows as the number of problems." + @assert all( + i -> problems[i].conditions.neumann_edges == conditions[i].neumann_edges, 1:length(problems)) "The Neumann edges in the `i`th `SimpleConditions` must match those from the `i`th problem." + @assert all( + i -> problems[i].conditions.constrained_edges == + conditions[i].constrained_edges, + 1:length(problems)) "The constrained edges in the `i`th `SimpleConditions` must match those from the `i`th problem." + @assert all( + i -> problems[i].conditions.dirichlet_nodes == conditions[i].dirichlet_nodes, + 1:length(problems)) "The Dirichlet nodes in the `i`th `SimpleConditions` must match those from the `i`th problem." @assert all(i -> problems[i].conditions.dudt_nodes == conditions[i].dudt_nodes, 1:length(problems)) "The dudt nodes in the `i`th `SimpleConditions` must match those from the `i`th problem." @assert all(i -> problems[i].source_function === source_functions[i].fnc, 1:length(problems)) "The source functions must match those from the problems." @assert all(i -> problems[i].source_parameters === source_functions[i].parameters, 1:length(problems)) "The source parameters must match those from the problems." @assert all(i -> problems[i].flux_function === flux_functions[i].fnc, 1:length(problems)) "The flux functions must match those from the problems." @assert all(i -> problems[i].flux_parameters === flux_functions[i].parameters, 1:length(problems)) "The flux parameters must match those from the problems." - sys = new{length(problems),FG,P,IC,FT,F,S,FF}(mesh, problems, initial_condition, initial_time, final_time, conditions, num_fncs, functions, source_functions, flux_functions) + sys = new{length(problems), FG, P, IC, FT, F, S, FF}( + mesh, problems, initial_condition, initial_time, final_time, + conditions, num_fncs, functions, source_functions, flux_functions) _check_fvmsystem_flux_function(sys) return sys end @@ -260,16 +293,23 @@ function _check_fvmsystem_flux_function(prob::FVMSystem) end end -Base.show(io::IO, ::MIME"text/plain", prob::FVMSystem{N}) where {N} = print(io, "FVMSystem with $N equations and time span ($(prob.initial_time), $(prob.final_time))") +function Base.show(io::IO, ::MIME"text/plain", prob::FVMSystem{N}) where {N} + print(io, "FVMSystem with $N equations and time span ($(prob.initial_time), $(prob.final_time))") +end @inline map_fidx(prob::FVMSystem, fidx, var) = fidx + prob.cnum_fncs[var] @inline get_dudt_fidx(prob::FVMSystem, i, var) = get_dudt_nodes(get_conditions(prob, var))[i] -@inline get_neumann_fidx(prob::FVMSystem, i, j, var) = get_neumann_edges(get_conditions(prob, var))[(i, j)] +@inline get_neumann_fidx(prob::FVMSystem, i, j, var) = get_neumann_edges(get_conditions(prob, var))[( + i, j)] @inline get_dirichlet_fidx(prob::FVMSystem, i, var) = get_dirichlet_nodes(get_conditions(prob, var))[i] -@inline get_constrained_fidx(prob::FVMSystem, i, j, var) = get_constrained_edges(get_conditions(prob, var))[(i, j)] -@inline eval_condition_fnc(prob::FVMSystem, fidx, var, x, y, t, u) = eval_fnc_in_het_tuple(prob.functions, map_fidx(prob, fidx, var), x, y, t, u) -@inline eval_source_fnc(prob::FVMSystem, var, x, y, t, u) = eval_fnc_in_het_tuple(prob.source_functions, var, x, y, t, u) +@inline get_constrained_fidx( + prob::FVMSystem, i, j, var) = get_constrained_edges(get_conditions(prob, var))[( + i, j)] +@inline eval_condition_fnc(prob::FVMSystem, fidx, var, x, y, t, + u) = eval_fnc_in_het_tuple(prob.functions, map_fidx(prob, fidx, var), x, y, t, u) +@inline eval_source_fnc(prob::FVMSystem, var, x, y, t, + u) = eval_fnc_in_het_tuple(prob.source_functions, var, x, y, t, u) @inline is_dudt_node(prob::FVMSystem, node, var) = is_dudt_node(get_conditions(prob, var), node) @inline is_neumann_edge(prob::FVMSystem, i, j, var) = is_neumann_edge(get_conditions(prob, var), i, j) @inline is_dirichlet_node(prob::FVMSystem, node, var) = is_dirichlet_node(get_conditions(prob, var), node) @@ -278,9 +318,10 @@ Base.show(io::IO, ::MIME"text/plain", prob::FVMSystem{N}) where {N} = print(io, @inline has_dirichlet_nodes(prob::FVMSystem, var) = has_dirichlet_nodes(get_conditions(prob, var)) @inline has_dirichlet_nodes(prob::FVMSystem{N}) where {N} = any(i -> has_dirichlet_nodes(prob, i), 1:N) @inline get_dirichlet_nodes(prob::FVMSystem, var) = get_dirichlet_nodes(get_conditions(prob, var)) -@inline eval_flux_function(prob::FVMSystem, x, y, t, α, β, γ) = eval_all_fncs_in_tuple(prob.flux_functions, x, y, t, α, β, γ) +@inline eval_flux_function(prob::FVMSystem, x, y, t, α, β, + γ) = eval_all_fncs_in_tuple(prob.flux_functions, x, y, t, α, β, γ) -function FVMSystem(probs::Vararg{FVMProblem,N}) where {N} +function FVMSystem(probs::Vararg{FVMProblem, N}) where {N} N == 0 && error("There must be at least one problem.") mesh = probs[1].mesh initial_time = probs[1].initial_time @@ -290,8 +331,10 @@ function FVMSystem(probs::Vararg{FVMProblem,N}) where {N} for (i, prob) in enumerate(probs) initial_condition[i, :] .= prob.initial_condition end - conditions, num_fncs, fncs, source_functions, flux_functions = merge_problem_conditions(probs) - return FVMSystem(mesh, probs, initial_condition, initial_time, final_time, conditions, num_fncs, fncs, source_functions, flux_functions) + conditions, num_fncs, fncs, source_functions, + flux_functions = merge_problem_conditions(probs) + return FVMSystem(mesh, probs, initial_condition, initial_time, final_time, + conditions, num_fncs, fncs, source_functions, flux_functions) end function merge_problem_conditions(probs) @@ -335,8 +378,8 @@ get_conditions(system::FVMSystem, var) = system.conditions[var] """ construct_flux_function(q, D, Dp) -If `isnothing(q)`, then this returns the flux function based on the diffusion function `D` and -diffusion parameters `Dp`, so that the new function is +If `isnothing(q)`, then this returns the flux function based on the diffusion function `D` and +diffusion parameters `Dp`, so that the new function is (x, y, t, α, β, γ, _) -> -D(x, y, t, α*x + β*y + γ, Dp)[α, β] @@ -367,12 +410,12 @@ is_system(prob::AbstractFVMProblem) = _neqs(prob) > 0 """ compute_flux(prob::AbstractFVMProblem, i, j, u, t) -Given an edge with indices `(i, j)`, a solution vector `u`, a current time `t`, -and a problem `prob`, computes the flux `∇⋅q(x, y, t, α, β, γ, p) ⋅ n`, +Given an edge with indices `(i, j)`, a solution vector `u`, a current time `t`, +and a problem `prob`, computes the flux `∇⋅q(x, y, t, α, β, γ, p) ⋅ n`, where `n` is the normal vector to the edge, `q` is the flux function from `prob`, -`(x, y)` is the midpoint of the edge, `(α, β, γ)` are the shape function coefficients, -and `p` are the flux parameters from `prob`. If `prob` is an [`FVMSystem`](@ref), the returned -value is a `Tuple` for each individual flux. The normal vector `n` is a clockwise rotation of +`(x, y)` is the midpoint of the edge, `(α, β, γ)` are the shape function coefficients, +and `p` are the flux parameters from `prob`. If `prob` is an [`FVMSystem`](@ref), the returned +value is a `Tuple` for each individual flux. The normal vector `n` is a clockwise rotation of the edge, meaning pointing right of the edge `(i, j)`. """ function compute_flux(prob::AbstractFVMProblem, i, j, u, t) diff --git a/src/solve.jl b/src/solve.jl index b43b3b2..bb2cad7 100644 --- a/src/solve.jl +++ b/src/solve.jl @@ -1,4 +1,4 @@ -function get_multithreading_parameters(prob::Union{FVMProblem,FVMSystem}) +function get_multithreading_parameters(prob::Union{FVMProblem, FVMSystem}) u = prob.initial_condition nt = Threads.nthreads() if prob isa FVMProblem @@ -14,26 +14,26 @@ function get_multithreading_parameters(prob::Union{FVMProblem,FVMSystem}) boundary_edges = collect(keys(get_boundary_edge_map(prob.mesh.triangulation))) chunked_boundary_edges = chunks(boundary_edges, nt) return ( - duplicated_du=duplicated_du, - dirichlet_nodes=dirichlet_nodes, - solid_triangles=solid_triangles, - solid_vertices=solid_vertices, - chunked_solid_triangles=chunked_solid_triangles, - boundary_edges=boundary_edges, - chunked_boundary_edges=chunked_boundary_edges, - parallel=Val(true), - prob=prob + duplicated_du = duplicated_du, + dirichlet_nodes = dirichlet_nodes, + solid_triangles = solid_triangles, + solid_vertices = solid_vertices, + chunked_solid_triangles = chunked_solid_triangles, + boundary_edges = boundary_edges, + chunked_boundary_edges = chunked_boundary_edges, + parallel = Val(true), + prob = prob ) end -function get_serial_parameters(prob::Union{FVMProblem,FVMSystem}) +function get_serial_parameters(prob::Union{FVMProblem, FVMSystem}) return ( - parallel=Val(false), - prob=prob + parallel = Val(false), + prob = prob ) end -function get_fvm_parameters(prob::Union{FVMProblem,FVMSystem}, parallel::Val{B}) where {B} +function get_fvm_parameters(prob::Union{FVMProblem, FVMSystem}, parallel::Val{B}) where {B} if B return get_multithreading_parameters(prob) else @@ -48,7 +48,9 @@ Returns a prototype for the Jacobian of the given `prob`. """ jacobian_sparsity jacobian_sparsity(prob::FVMProblem) = jacobian_sparsity(prob.mesh.triangulation) -jacobian_sparsity(prob::FVMSystem{N}) where {N} = jacobian_sparsity(prob.mesh.triangulation, N) +function jacobian_sparsity(prob::FVMSystem{N}) where {N} + jacobian_sparsity(prob.mesh.triangulation, N) +end jacobian_sparsity(prob::SteadyFVMProblem) = jacobian_sparsity(prob.problem) function jacobian_sparsity(tri) @@ -133,7 +135,7 @@ end cb = DiscreteCallback( (u, t, integrator) -> true, f, - save_positions=(!has_saveat, !has_saveat), + save_positions = (!has_saveat, !has_saveat) ) else cb = CallbackSet() @@ -144,65 +146,67 @@ end """ get_dirichlet_callback(prob[, f=update_dirichlet_nodes!]; kwargs...) -Get the callback for updating [`Dirichlet`](@ref) nodes. The `kwargs...` argument is ignored, -except to detect if a user has already provided a callback, in which case the -callback gets merged into a `CallbackSet` with the [`Dirichlet`](@ref) callback. If the problem -`prob` has no [`Dirichlet`](@ref) nodes, the returned callback does nothing and is never +Get the callback for updating [`Dirichlet`](@ref) nodes. The `kwargs...` argument is ignored, +except to detect if a user has already provided a callback, in which case the +callback gets merged into a `CallbackSet` with the [`Dirichlet`](@ref) callback. If the problem +`prob` has no [`Dirichlet`](@ref) nodes, the returned callback does nothing and is never called. You can provide `f` to change the function that updates the [`Dirichlet`](@ref) nodes. """ -@inline function get_dirichlet_callback(prob, f::F=update_dirichlet_nodes!;saveat=(), callback=CallbackSet(), kwargs...) where {F} +@inline function get_dirichlet_callback(prob, f::F = update_dirichlet_nodes!; saveat = (), + callback = CallbackSet(), kwargs...) where {F} has_dir_nodes = has_dirichlet_nodes(prob) dir_callback = dirichlet_callback(f, !isempty(saveat), has_dir_nodes) cb = CallbackSet(dir_callback, callback) return cb end -function SciMLBase.ODEProblem(prob::Union{FVMProblem,FVMSystem}; - specialization::Type{S}=SciMLBase.AutoSpecialize, - jac_prototype=jacobian_sparsity(prob), - parallel::Val{B}=Val(true), - kwargs...) where {S,B} +function SciMLBase.ODEProblem(prob::Union{FVMProblem, FVMSystem}; + specialization::Type{S} = SciMLBase.AutoSpecialize, + jac_prototype = jacobian_sparsity(prob), + parallel::Val{B} = Val(true), + kwargs...) where {S, B} initial_time = prob.initial_time final_time = prob.final_time time_span = (initial_time, final_time) initial_condition = prob.initial_condition cb = get_dirichlet_callback(prob; kwargs...) - f = ODEFunction{true,S}(fvm_eqs!; jac_prototype) + f = ODEFunction{true, S}(fvm_eqs!; jac_prototype) p = get_fvm_parameters(prob, parallel) - ode_problem = ODEProblem{true,S}(f, initial_condition, time_span, p; callback=cb) + ode_problem = ODEProblem{true, S}(f, initial_condition, time_span, p; callback = cb) return ode_problem end function SciMLBase.SteadyStateProblem(prob::SteadyFVMProblem; - specialization::Type{S}=SciMLBase.AutoSpecialize, - jac_prototype=jacobian_sparsity(prob), - parallel::Val{B}=Val(true), - kwargs...) where {S,B} + specialization::Type{S} = SciMLBase.AutoSpecialize, + jac_prototype = jacobian_sparsity(prob), + parallel::Val{B} = Val(true), + kwargs...) where {S, B} ode_prob = ODEProblem(prob.problem; specialization, jac_prototype, parallel, kwargs...) nl_prob = SteadyStateProblem{true}(ode_prob.f, ode_prob.u0, ode_prob.p; ode_prob.kwargs...) return nl_prob end -function CommonSolve.init(prob::Union{FVMProblem,FVMSystem}, args...; - specialization::Type{S}=SciMLBase.AutoSpecialize, - jac_prototype=jacobian_sparsity(prob), - parallel::Val{B}=Val(true), - kwargs...) where {S,B} - ode_prob = SciMLBase.ODEProblem(prob; specialization, jac_prototype, parallel, kwargs...) +function CommonSolve.init(prob::Union{FVMProblem, FVMSystem}, args...; + specialization::Type{S} = SciMLBase.AutoSpecialize, + jac_prototype = jacobian_sparsity(prob), + parallel::Val{B} = Val(true), + kwargs...) where {S, B} + ode_prob = SciMLBase.ODEProblem( + prob; specialization, jac_prototype, parallel, kwargs...) return CommonSolve.init(ode_prob, args...; kwargs...) end function CommonSolve.solve(prob::SteadyFVMProblem, args...; - specialization::Type{S}=SciMLBase.AutoSpecialize, - jac_prototype=jacobian_sparsity(prob), - parallel::Val{B}=Val(true), - kwargs...) where {S,B} - nl_prob = SciMLBase.SteadyStateProblem(prob; specialization, jac_prototype, parallel, kwargs...) + specialization::Type{S} = SciMLBase.AutoSpecialize, + jac_prototype = jacobian_sparsity(prob), + parallel::Val{B} = Val(true), + kwargs...) where {S, B} + nl_prob = SciMLBase.SteadyStateProblem( + prob; specialization, jac_prototype, parallel, kwargs...) return CommonSolve.solve(nl_prob, args...; kwargs...) end - @doc """ solve(prob::Union{FVMProblem,FVMSystem}, alg; specialization=SciMLBase.AutoSpecialize, @@ -237,7 +241,7 @@ In this case, `sol::ODESolution` is such that the `i`th component of `sol` refer - [`FVMSystem`](@ref) In this case, the `(j, i)`th component of `sol::ODESolution` refers to the `i`th node of the underlying mesh for the `j`th component of the system. -""" solve(::Union{FVMProblem,FVMSystem}, ::Any; kwargs...) +""" solve(::Union{FVMProblem, FVMSystem}, ::Any; kwargs...) @doc """ solve(prob::SteadyFVMProblem, alg; @@ -268,4 +272,4 @@ In this case, `sol` is such that the `i`th component of `sol` refers to the `i`t - [`FVMSystem`](@ref) In this case, the `(j, i)`th component of `sol` refers to the `i`th node of the underlying mesh for the `j`th component of the system. -""" solve(::SteadyFVMProblem, ::Any; kwargs...) \ No newline at end of file +""" solve(::SteadyFVMProblem, ::Any; kwargs...) diff --git a/src/specific_problems/abstract_templates.jl b/src/specific_problems/abstract_templates.jl index 831e3fd..608e4e7 100644 --- a/src/specific_problems/abstract_templates.jl +++ b/src/specific_problems/abstract_templates.jl @@ -1,20 +1,21 @@ """ abstract type AbstractFVMTemplate <: AbstractFVMProblem -An abstract type that defines some specific problems. These problems are those that could +An abstract type that defines some specific problems. These problems are those that could be defined directly using [`FVMProblem`](@ref)s, but are common enough that (1) it is useful to have them defined here, and (2) it is useful to have them defined in a way that is more efficient than -with a default implementation (e.g. exploiting linearity). The -problems are all defined as subtypes of a common abstract type, +with a default implementation (e.g. exploiting linearity). The +problems are all defined as subtypes of a common abstract type, namely, `AbstractFVMTemplate` (the home of this docstring), which itself is a subtype of -`AbstractFVMProblem`. +`AbstractFVMProblem`. -To understand how to make use of these specific problems, either -see the docstring for each problem, or see the +To understand how to make use of these specific problems, either +see the docstring for each problem, or see the "Solvers for Specific Problems, and Writing Your Own" section of the docs. -To see the full list of problems, do +To see the full list of problems, do + ```julia-repl julia> using FiniteVolumeMethod @@ -29,9 +30,9 @@ julia> subtypes(FiniteVolumeMethod.AbstractFVMTemplate) The constructor for each problem is defined in its docstring. Note that all the problems above are exported. -These problems can all be solved using the standard `solve` interface from -DifferentialEquations.jl, just like for [`FVMProblem`](@ref)s. The only exception -is for steady state problems, in which case the `solve` interface is still used, except +These problems can all be solved using the standard `solve` interface from +DifferentialEquations.jl, just like for [`FVMProblem`](@ref)s. The only exception +is for steady state problems, in which case the `solve` interface is still used, except the interface is from LinearSolve.jl. """ abstract type AbstractFVMTemplate <: AbstractFVMProblem end @@ -51,10 +52,12 @@ export LaplacesEquation """ solve(prob::AbstractFVMTemplate, args...; kwargs...) -Solve the problem `prob` using the standard `solve` interface from DifferentialEquations.jl. For +Solve the problem `prob` using the standard `solve` interface from DifferentialEquations.jl. For steady state problems, the interface is from LinearSolve.jl. """ -CommonSolve.solve(prob::AbstractFVMTemplate, args...; kwargs...) = CommonSolve.solve(prob.problem, args...; kwargs...) +function CommonSolve.solve(prob::AbstractFVMTemplate, args...; kwargs...) + CommonSolve.solve(prob.problem, args...; kwargs...) +end @doc raw""" triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) @@ -67,7 +70,8 @@ Add the contributions from each triangle to the matrix `A`, based on the equatio as explained in the docs. Will not update any rows corresponding to [`Dirichlet`](@ref) or [`Dudt`](@ref) nodes. """ -function triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) +function triangle_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) for T in each_solid_triangle(mesh.triangulation) ijk = triangle_vertices(T) i, j, k = ijk @@ -101,7 +105,8 @@ zero, and `b[i]` is zero, where `i` is a node with a Dirichlet condition. function apply_dirichlet_conditions!(initial_condition, mesh, conditions) for (i, function_index) in get_dirichlet_nodes(conditions) x, y = get_point(mesh, i) - initial_condition[i] = eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) + initial_condition[i] = eval_condition_fnc( + conditions, function_index, x, y, nothing, nothing) end end @@ -135,9 +140,11 @@ as explained in the docs. Will not update any rows corresponding to [`Dirichlet`](@ref) or [`Dudt`](@ref) nodes. """ function boundary_edge_contributions!(A, b, mesh, conditions, - diffusion_function, diffusion_parameters) - non_neumann_boundary_edge_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) # this is split to make it easier to, in the future, support semilinear equations where the Neumann contributions do need to go into the nonlinear component - neumann_boundary_edge_contributions!(b, mesh, conditions, diffusion_function, diffusion_parameters) + diffusion_function, diffusion_parameters) + non_neumann_boundary_edge_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) # this is split to make it easier to, in the future, support semilinear equations where the Neumann contributions do need to go into the nonlinear component + neumann_boundary_edge_contributions!( + b, mesh, conditions, diffusion_function, diffusion_parameters) return nothing end @@ -154,7 +161,8 @@ as explained in the docs. Will not update any rows corresponding to [`Dirichlet`](@ref) or [`Dudt`](@ref) nodes. This function will pass `nothing` in place of the arguments `u` and `t` in the boundary condition functions. """ -function neumann_boundary_edge_contributions!(b, mesh, conditions, diffusion_function, diffusion_parameters) +function neumann_boundary_edge_contributions!( + b, mesh, conditions, diffusion_function, diffusion_parameters) for (e, fidx) in get_neumann_edges(conditions) i, j = DelaunayTriangulation.edge_vertices(e) _, _, mᵢx, mᵢy, mⱼx, mⱼy, ℓ, _, _ = get_boundary_cv_components(mesh, i, j) @@ -182,7 +190,8 @@ Add the contributions from each Neumann boundary edge to the vector `F`, based o as explained in the docs. Will not update any rows corresponding to [`Dirichlet`](@ref) or [`Dudt`](@ref) nodes. """ -function neumann_boundary_edge_contributions!(F, mesh, conditions, diffusion_function, diffusion_parameters, u, t) +function neumann_boundary_edge_contributions!( + F, mesh, conditions, diffusion_function, diffusion_parameters, u, t) for (e, fidx) in FVM.get_neumann_edges(conditions) i, j = DelaunayTriangulation.edge_vertices(e) _, _, mᵢx, mᵢy, mⱼx, mⱼy, ℓ, _, _ = FVM.get_boundary_cv_components(mesh, i, j) @@ -212,7 +221,8 @@ Add the contributions from each non-Neumann boundary edge to the matrix `A`, bas as explained in the docs. Will not update any rows corresponding to [`Dirichlet`](@ref) or [`Dudt`](@ref) nodes. """ -function non_neumann_boundary_edge_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) +function non_neumann_boundary_edge_contributions!( + A, mesh, conditions, diffusion_function, diffusion_parameters) for e in keys(get_boundary_edge_map(mesh.triangulation)) i, j = DelaunayTriangulation.edge_vertices(e) if !is_neumann_edge(conditions, i, j) @@ -241,7 +251,7 @@ end """ create_rhs_b(mesh, conditions, source_function, source_parameters) -Create the vector `b` defined by +Create the vector `b` defined by b = [source_function(x, y, source_parameters) for (x, y) in DelaunayTriangulation.each_point(mesh.triangulation)], @@ -293,4 +303,4 @@ function fix_missing_vertices!(A, b, mesh) end end return nothing -end \ No newline at end of file +end diff --git a/src/specific_problems/diffusion_equation.jl b/src/specific_problems/diffusion_equation.jl index 5f4a31b..8f61c39 100644 --- a/src/specific_problems/diffusion_equation.jl +++ b/src/specific_problems/diffusion_equation.jl @@ -46,7 +46,7 @@ The struct has extra fields in addition to the arguments above: - `Aop`: The `MatrixOperator` that represents the system so that `du/dt = Aop*u` (with `u` padded with an extra component since `A` is now inside `Aop`). - `problem`: The `ODEProblem` that represents the problem. This is the problem that is solved when you call [`solve`](@ref solve(::AbstractFVMTemplate, args...; kwargs...)) on the struct. """ -struct DiffusionEquation{M,C,D,DP,IC,FT,A,B,OP,ODE} <: AbstractFVMTemplate +struct DiffusionEquation{M, C, D, DP, IC, FT, A, B, OP, ODE} <: AbstractFVMTemplate mesh::M conditions::C diffusion_function::D @@ -67,22 +67,23 @@ function Base.show(io::IO, ::MIME"text/plain", prob::DiffusionEquation) end function DiffusionEquation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time, - kwargs...) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time, + kwargs...) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) - A = @views Afull[begin:end-1, begin:end-1] - b = @views Afull[begin:end-1, end] + A = @views Afull[begin:(end - 1), begin:(end - 1)] + b = @views Afull[begin:(end - 1), end] _ic = vcat(initial_condition, 1) triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) apply_dudt_conditions!(b, mesh, conditions) apply_dirichlet_conditions!(_ic, mesh, conditions) fix_missing_vertices!(A, b, mesh) @@ -92,4 +93,4 @@ function DiffusionEquation(mesh::FVMGeometry, diffusion_function, diffusion_parameters, initial_condition, initial_time, final_time, sparse(A), b, A_op, prob) -end \ No newline at end of file +end diff --git a/src/specific_problems/laplaces_equation.jl b/src/specific_problems/laplaces_equation.jl index 497bc91..ce04685 100644 --- a/src/specific_problems/laplaces_equation.jl +++ b/src/specific_problems/laplaces_equation.jl @@ -34,7 +34,7 @@ The struct has extra fields in addition to the arguments above: - `b`: The `b` above. - `problem`: The `LinearProblem` that represents the problem. This is the problem that is solved when you call [`solve`](@ref solve(::AbstractFVMTemplate, args...; kwargs...)) on the struct. """ -struct LaplacesEquation{M,C,D,DP,A,B,ODE} <: AbstractFVMTemplate +struct LaplacesEquation{M, C, D, DP, A, B, ODE} <: AbstractFVMTemplate mesh::M conditions::C diffusion_function::D @@ -49,18 +49,20 @@ function Base.show(io::IO, ::MIME"text/plain", prob::LaplacesEquation) end function LaplacesEquation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function=(x, y, p) -> 1.0, - diffusion_parameters=nothing, - kwargs...) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function = (x, y, p) -> 1.0, + diffusion_parameters = nothing, + kwargs...) conditions = Conditions(mesh, BCs, ICs) - has_dudt_nodes(conditions) && throw(ArgumentError("PoissonsEquation does not support Dudt nodes.")) + has_dudt_nodes(conditions) && + throw(ArgumentError("PoissonsEquation does not support Dudt nodes.")) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) A = zeros(n, n) b = zeros(DelaunayTriangulation.num_points(mesh.triangulation)) triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) apply_steady_dirichlet_conditions!(A, b, mesh, conditions) fix_missing_vertices!(A, b, mesh) Asp = sparse(A) @@ -68,4 +70,4 @@ function LaplacesEquation(mesh::FVMGeometry, return LaplacesEquation(mesh, conditions, diffusion_function, diffusion_parameters, Asp, b, prob) -end \ No newline at end of file +end diff --git a/src/specific_problems/linear_reaction_diffusion_equations.jl b/src/specific_problems/linear_reaction_diffusion_equations.jl index 5626547..8fcc8f6 100644 --- a/src/specific_problems/linear_reaction_diffusion_equations.jl +++ b/src/specific_problems/linear_reaction_diffusion_equations.jl @@ -50,7 +50,8 @@ The struct has extra fields in addition to the arguments above: - `Aop`: The `MatrixOperator` that represents the system so that `du/dt = Aop*u` (with `u` padded with an extra component since `A` is now inside `Aop`). - `problem`: The `ODEProblem` that represents the problem. This is the problem that is solved when you call [`solve`](@ref solve(::AbstractFVMTemplate, args...; kwargs...)) on the struct. """ -struct LinearReactionDiffusionEquation{M,C,D,DP,S,SP,IC,FT,A,B,OP,ODE} <: AbstractFVMTemplate +struct LinearReactionDiffusionEquation{M, C, D, DP, S, SP, IC, FT, A, B, OP, ODE} <: + AbstractFVMTemplate mesh::M conditions::C diffusion_function::D @@ -73,24 +74,25 @@ function Base.show(io::IO, ::MIME"text/plain", prob::LinearReactionDiffusionEqua end function LinearReactionDiffusionEquation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - source_function, - source_parameters=nothing, - initial_condition, - initial_time=0.0, - final_time, - kwargs...) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + source_function, + source_parameters = nothing, + initial_condition, + initial_time = 0.0, + final_time, + kwargs...) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) - A = @views Afull[begin:end-1, begin:end-1] - b = @views Afull[begin:end-1, end] + A = @views Afull[begin:(end - 1), begin:(end - 1)] + b = @views Afull[begin:(end - 1), end] _ic = vcat(initial_condition, 1) triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) linear_source_contributions!(A, mesh, conditions, source_function, source_parameters) apply_dudt_conditions!(b, mesh, conditions) apply_dirichlet_conditions!(_ic, mesh, conditions) @@ -105,11 +107,12 @@ function LinearReactionDiffusionEquation(mesh::FVMGeometry, sparse(A), b, Aop, prob) end -function linear_source_contributions!(A, mesh, conditions, source_function, source_parameters) +function linear_source_contributions!( + A, mesh, conditions, source_function, source_parameters) for i in each_solid_vertex(mesh.triangulation) if !has_condition(conditions, i) x, y = get_point(mesh, i) A[i, i] += source_function(x, y, source_parameters) end end -end \ No newline at end of file +end diff --git a/src/specific_problems/mean_exit_time.jl b/src/specific_problems/mean_exit_time.jl index 21c5d3e..b3380a7 100644 --- a/src/specific_problems/mean_exit_time.jl +++ b/src/specific_problems/mean_exit_time.jl @@ -40,7 +40,7 @@ The struct has extra fields in addition to the arguments above: - `b`: The `b` above. - `problem`: The `LinearProblem` that represents the problem. This is the problem that is solved when you call [`solve`](@ref solve(::AbstractFVMTemplate, args...; kwargs...)) on the struct. """ -struct MeanExitTimeProblem{M,C,D,DP,A,B,LP} <: AbstractFVMTemplate +struct MeanExitTimeProblem{M, C, D, DP, A, B, LP} <: AbstractFVMTemplate mesh::M conditions::C diffusion_function::D @@ -55,14 +55,16 @@ function Base.show(io::IO, ::MIME"text/plain", prob::MeanExitTimeProblem) end function MeanExitTimeProblem(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function, - diffusion_parameters=nothing, - kwargs...) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function, + diffusion_parameters = nothing, + kwargs...) conditions = Conditions(mesh, BCs, ICs) - has_dudt_nodes(conditions) && throw(ArgumentError("MeanExitTimeProblem does not support Dudt nodes.")) - has_constrained_edges(conditions) && throw(ArgumentError("MeanExitTimeProblem does not support Constrained edges.")) + has_dudt_nodes(conditions) && + throw(ArgumentError("MeanExitTimeProblem does not support Dudt nodes.")) + has_constrained_edges(conditions) && + throw(ArgumentError("MeanExitTimeProblem does not support Constrained edges.")) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) @@ -85,4 +87,4 @@ function create_met_b!(A, mesh, conditions) end end return b -end \ No newline at end of file +end diff --git a/src/specific_problems/poissons_equation.jl b/src/specific_problems/poissons_equation.jl index 0bcb5d8..e356a44 100644 --- a/src/specific_problems/poissons_equation.jl +++ b/src/specific_problems/poissons_equation.jl @@ -39,7 +39,7 @@ The struct has extra fields in addition to the arguments above: - `b`: The `b` above. - `problem`: The `LinearProblem` that represents the problem. This is the problem that is solved when you call [`solve`](@ref solve(::AbstractFVMTemplate, args...; kwargs...)) on the struct. """ -struct PoissonsEquation{M,C,D,DP,S,SP,A,B,ODE} <: AbstractFVMTemplate +struct PoissonsEquation{M, C, D, DP, S, SP, A, B, ODE} <: AbstractFVMTemplate mesh::M conditions::C diffusion_function::D @@ -56,20 +56,22 @@ function Base.show(io::IO, ::MIME"text/plain", prob::PoissonsEquation) end function PoissonsEquation(mesh::FVMGeometry, - BCs::BoundaryConditions, - ICs::InternalConditions=InternalConditions(); - diffusion_function=(x, y, p) -> 1.0, - diffusion_parameters=nothing, - source_function, - source_parameters=nothing, - kwargs...) + BCs::BoundaryConditions, + ICs::InternalConditions = InternalConditions(); + diffusion_function = (x, y, p) -> 1.0, + diffusion_parameters = nothing, + source_function, + source_parameters = nothing, + kwargs...) conditions = Conditions(mesh, BCs, ICs) - has_dudt_nodes(conditions) && throw(ArgumentError("PoissonsEquation does not support Dudt nodes.")) + has_dudt_nodes(conditions) && + throw(ArgumentError("PoissonsEquation does not support Dudt nodes.")) n = DelaunayTriangulation.num_points(mesh.triangulation) A = zeros(n, n) b = create_rhs_b(mesh, conditions, source_function, source_parameters) triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters) - boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters) + boundary_edge_contributions!( + A, b, mesh, conditions, diffusion_function, diffusion_parameters) apply_steady_dirichlet_conditions!(A, b, mesh, conditions) fix_missing_vertices!(A, b, mesh) Asp = sparse(A) @@ -78,4 +80,4 @@ function PoissonsEquation(mesh::FVMGeometry, diffusion_function, diffusion_parameters, source_function, source_parameters, Asp, b, prob) -end \ No newline at end of file +end diff --git a/src/utils.jl b/src/utils.jl index 47bac7d..3fdd527 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -9,13 +9,15 @@ function _safe_get_triangle_props(mesh::FVMGeometry, T) return (k, i, j), get_triangle_props(mesh, k, i, j) end end -_safe_get_triangle_props(prob::AbstractFVMProblem, T) = _safe_get_triangle_props(prob.mesh, T) +function _safe_get_triangle_props(prob::AbstractFVMProblem, T) + _safe_get_triangle_props(prob.mesh, T) +end """ pl_interpolate(prob, T, u, x, y) -Given a `prob <: AbstractFVMProblem`, a triangle `T` containing a point `(x, y)`, -and a set of function values `u` at the corresponding nodes of `prob`, interpolates +Given a `prob <: AbstractFVMProblem`, a triangle `T` containing a point `(x, y)`, +and a set of function values `u` at the corresponding nodes of `prob`, interpolates the solution at the point `(x, y)` using piecewise linear interpolation. """ function pl_interpolate(prob, T, u, x, y) @@ -27,7 +29,7 @@ end """ two_point_interpolant(mesh, u, i, j, mx, my) -Given a `mesh <: FVMGeometry`, a set of function values `u` at the nodes of `mesh`, +Given a `mesh <: FVMGeometry`, a set of function values `u` at the nodes of `mesh`, and a point `(mx, my)` on the line segment between the nodes `i` and `j`, interpolates the solution at the point `(mx, my)` using two-point interpolation. """ @@ -39,7 +41,7 @@ function two_point_interpolant(mesh, u::AbstractVector, i, j, mx, my) return u[i] + (u[j] - u[i]) * ℓ′ / ℓ end -function flatten_tuples(f::NTuple{N,Any}) where {N} +function flatten_tuples(f::NTuple{N, Any}) where {N} tail_f = Base.tail(f) return (f[1]..., flatten_tuples(tail_f)...) end @@ -65,4 +67,4 @@ end end @inline function _eval_all_fncs_in_tuple(x, y, t, α, β, γ, f::F) where {F} return (f(x, y, t, α, β, γ),) -end \ No newline at end of file +end diff --git a/test/README.jl b/test/README.jl index 4ca79d8..5eed045 100644 --- a/test/README.jl +++ b/test/README.jl @@ -3,7 +3,7 @@ using ..FiniteVolumeMethod, DelaunayTriangulation, CairoMakie, OrdinaryDiffEq using ReferenceTests a, b, c, d = 0.0, 2.0, 0.0, 2.0 nx, ny = 50, 50 -tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary=true) +tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary = true) mesh = FVMGeometry(tri) bc = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, bc, Dirichlet) @@ -11,10 +11,10 @@ f = (x, y) -> y ≤ 1.0 ? 50.0 : 0.0 initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] D = (x, y, t, u, p) -> 1 / 9 final_time = 0.5 -prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) -sol = solve(prob, Tsit5(), saveat=0.001) +prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) +sol = solve(prob, Tsit5(), saveat = 0.001) u = Observable(sol.u[421]) -fig, ax, sc = tricontourf(tri, u, levels=0:5:50, colormap=:matter) +fig, ax, sc = tricontourf(tri, u, levels = 0:5:50, colormap = :matter) tightlimits!(ax) -fig -@test_reference "test_figures/readme_example.png" fig \ No newline at end of file +fig +@test_reference "test_figures/readme_example.png" fig diff --git a/test/conditions.jl b/test/conditions.jl index 0e5db64..35db9b4 100644 --- a/test/conditions.jl +++ b/test/conditions.jl @@ -12,19 +12,20 @@ include("test_functions.jl") tri = example_tri() mesh = FVMGeometry(tri) f, t, p = example_bc_setup() - BCs = BoundaryConditions(mesh, f, t; parameters=p) + BCs = BoundaryConditions(mesh, f, t; parameters = p) conds = FVM.Conditions(mesh, BCs) @test BCs.condition_types == t - dirichlet_nodes = NTuple{2,Float64}[] - dudt_nodes = NTuple{2,Float64}[] - constrained_edges = NTuple{2,Float64}[] - neumann_edges = NTuple{2,Float64}[] - test_bc_conditions!(tri, conds, t, dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges) - fig, ax, sc = triplot(tri, show_constrained_edges=false) - scatter!(ax, dudt_nodes, color=:green, markersize=18) - scatter!(ax, dirichlet_nodes, color=:red, markersize=18) - linesegments!(ax, constrained_edges, color=:blue, linewidth=6) - linesegments!(ax, neumann_edges, color=:yellow, linewidth=6) + dirichlet_nodes = NTuple{2, Float64}[] + dudt_nodes = NTuple{2, Float64}[] + constrained_edges = NTuple{2, Float64}[] + neumann_edges = NTuple{2, Float64}[] + test_bc_conditions!( + tri, conds, t, dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges) + fig, ax, sc = triplot(tri, show_constrained_edges = false) + scatter!(ax, dudt_nodes, color = :green, markersize = 18) + scatter!(ax, dirichlet_nodes, color = :red, markersize = 18) + linesegments!(ax, constrained_edges, color = :blue, linewidth = 6) + linesegments!(ax, neumann_edges, color = :yellow, linewidth = 6) @test_reference "test_figures/conditions.png" fig @test FVM.has_dirichlet_nodes(conds) end @@ -33,27 +34,31 @@ end tri = example_tri_rect() mesh = FVMGeometry(tri) f, t, p, g, q, dirichlet_nodes, dudt_nodes = example_bc_ic_setup() - BCs = BoundaryConditions(mesh, f, t; parameters=p) - @test sprint(show, MIME"text/plain"(), BCs) == "BoundaryConditions with $(length(tri.ghost_vertex_ranges)) boundary conditions with types $(BCs.condition_types)" - ICs = InternalConditions(g; dirichlet_nodes, dudt_nodes, parameters=q) - @test sprint(show, MIME"text/plain"(), ICs) == "InternalConditions with $(length(ICs.dirichlet_nodes)) Dirichlet nodes and $(length(ICs.dudt_nodes)) Dudt nodes" + BCs = BoundaryConditions(mesh, f, t; parameters = p) + @test sprint(show, MIME"text/plain"(), BCs) == + "BoundaryConditions with $(length(tri.ghost_vertex_ranges)) boundary conditions with types $(BCs.condition_types)" + ICs = InternalConditions(g; dirichlet_nodes, dudt_nodes, parameters = q) + @test sprint(show, MIME"text/plain"(), ICs) == + "InternalConditions with $(length(ICs.dirichlet_nodes)) Dirichlet nodes and $(length(ICs.dudt_nodes)) Dudt nodes" conds = FVM.Conditions(mesh, BCs, ICs) - @test sprint(show, MIME"text/plain"(), conds) == "Conditions with\n $(length(conds.neumann_edges)) Neumann edges\n $(length(conds.constrained_edges)) Constrained edges\n $(length(conds.dirichlet_nodes)) Dirichlet nodes\n $(length(conds.dudt_nodes)) Dudt nodes" + @test sprint(show, MIME"text/plain"(), conds) == + "Conditions with\n $(length(conds.neumann_edges)) Neumann edges\n $(length(conds.constrained_edges)) Constrained edges\n $(length(conds.dirichlet_nodes)) Dirichlet nodes\n $(length(conds.dudt_nodes)) Dudt nodes" @test FVM.get_f(conds, 1) == conds.functions[1] @test FVM.get_f(conds, 2) == conds.functions[2] @test BCs.condition_types == t @test ICs.dirichlet_nodes == dirichlet_nodes @test ICs.dudt_nodes == dudt_nodes - dirichlet_nodes = NTuple{2,Float64}[] - dudt_nodes = NTuple{2,Float64}[] - constrained_edges = NTuple{2,Float64}[] - neumann_edges = NTuple{2,Float64}[] - test_bc_ic_conditions!(tri, conds, t, dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges, ICs) - fig, ax, sc = triplot(tri, show_constrained_edges=false) - scatter!(ax, dudt_nodes, color=:green, markersize=18) - scatter!(ax, dirichlet_nodes, color=:red, markersize=18) - linesegments!(ax, constrained_edges, color=:blue, linewidth=6) - linesegments!(ax, neumann_edges, color=:yellow, linewidth=6) + dirichlet_nodes = NTuple{2, Float64}[] + dudt_nodes = NTuple{2, Float64}[] + constrained_edges = NTuple{2, Float64}[] + neumann_edges = NTuple{2, Float64}[] + test_bc_ic_conditions!( + tri, conds, t, dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges, ICs) + fig, ax, sc = triplot(tri, show_constrained_edges = false) + scatter!(ax, dudt_nodes, color = :green, markersize = 18) + scatter!(ax, dirichlet_nodes, color = :red, markersize = 18) + linesegments!(ax, constrained_edges, color = :blue, linewidth = 6) + linesegments!(ax, neumann_edges, color = :yellow, linewidth = 6) @test_reference "test_figures/internal_conditions.png" fig @test FVM.has_dirichlet_nodes(conds) empty!(conds.dirichlet_nodes) @@ -67,9 +72,9 @@ end @testset "apply_dudt_conditions!" begin tri = example_tri_rect() mesh = FVMGeometry(tri) - f, t, p, g, q, dirichlet_nodes, dudt_nodes = example_bc_ic_setup(; nothing_dudt=true) - BCs = BoundaryConditions(mesh, f, t; parameters=p) - ICs = InternalConditions(g; dirichlet_nodes, dudt_nodes, parameters=q) + f, t, p, g, q, dirichlet_nodes, dudt_nodes = example_bc_ic_setup(; nothing_dudt = true) + BCs = BoundaryConditions(mesh, f, t; parameters = p) + ICs = InternalConditions(g; dirichlet_nodes, dudt_nodes, parameters = q) conds = FVM.Conditions(mesh, BCs, ICs) b = zeros(DelaunayTriangulation.num_points(tri)) FVM.apply_dudt_conditions!(b, mesh, conds) @@ -130,4 +135,4 @@ end @test vals == ntuple(i -> f[i](x, y, t, α, β, γ), 5) @inferred FVM.eval_all_fncs_in_tuple(f, x, y, t, α, β, γ) end -end \ No newline at end of file +end diff --git a/test/equations.jl b/test/equations.jl index a657583..d6ae795 100644 --- a/test/equations.jl +++ b/test/equations.jl @@ -44,9 +44,11 @@ end x1 = q[1] y1 = r[2] u1, u2, u3 = u[[T...]] - A = PolygonOps.area(((0.0, 0.0), (x1 / 2, 0.0), (x1 / 3, y1 / 3), (0, y1 / 2), (0.0, 0.0))) + A = PolygonOps.area(( + (0.0, 0.0), (x1 / 2, 0.0), (x1 / 3, y1 / 3), (0, y1 / 2), (0.0, 0.0))) @test prob.mesh.cv_volumes[1] ≈ A - @test FVM.get_shape_function_coefficients(prob.mesh.triangle_props[T], T, u, prob) == (0.0, 0.0, 10.0) + @test FVM.get_shape_function_coefficients(prob.mesh.triangle_props[T], T, u, prob) == + (0.0, 0.0, 10.0) props = prob.mesh.triangle_props[T] c1, c2, c3, c4 = (0.0, 0.0), (x1 / 2, 0.0), (x1 / 3, y1 / 3), (0.0, y1 / 2) m1 = (c1 .+ c2) ./ 2 @@ -73,8 +75,10 @@ end fl = -(1 / A) * sum((flux1, flux2, flux3, flux4)) fltest = get_dudt_val(prob, u, t, i, false) @test fl ≈ fltest - flpar = FVM.fvm_eqs!(zeros(DelaunayTriangulation.num_solid_vertices(tri)), u, (prob=prob, parallel=Val(false)), t)[i] - flser = FVM.fvm_eqs!(zeros(DelaunayTriangulation.num_solid_vertices(tri)), u, FVM.get_multithreading_parameters(prob), t)[i] + flpar = FVM.fvm_eqs!(zeros(DelaunayTriangulation.num_solid_vertices(tri)), + u, (prob = prob, parallel = Val(false)), t)[i] + flser = FVM.fvm_eqs!(zeros(DelaunayTriangulation.num_solid_vertices(tri)), + u, FVM.get_multithreading_parameters(prob), t)[i] @test fl ≈ flpar @test fl ≈ flser end @@ -95,10 +99,10 @@ end prob = example_diffusion_problem() @test_throws FVM.InvalidFluxError FVMSystem(prob, prob) sys, prob = example_diffusion_problem_system() - solsys = solve(sys, TRBDF2(linsolve=KLUFactorization()), saveat=0.05) - solsysser = solve(sys, TRBDF2(linsolve=KLUFactorization()), saveat=0.05, parallel=Val(false)) - solprob = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=0.05) - solprobser = solve(prob, TRBDF2(linsolve=KLUFactorization()), saveat=0.05, parallel=Val(false)) + solsys = solve(sys, TRBDF2(linsolve = KLUFactorization()), saveat = 0.05) + solsysser = solve(sys, TRBDF2(linsolve = KLUFactorization()), saveat = 0.05, parallel = Val(false)) + solprob = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.05) + solprobser = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.05, parallel = Val(false)) solprobu = reduce(hcat, solprob.u) solprobseru = reduce(hcat, solprobser.u) solsysu1 = reduce(hcat, [solsys.u[i][1, :] for i in eachindex(solsys)]) @@ -106,4 +110,4 @@ end solsysser1 = reduce(hcat, [solsysser.u[i][1, :] for i in eachindex(solsysser)]) solsysser2 = reduce(hcat, [solsysser.u[i][2, :] for i in eachindex(solsysser)]) @test all(≈(solprobu), (solprobseru, solsysu1, solsysu2, solsysser1, solsysser2)) -end \ No newline at end of file +end diff --git a/test/geometry.jl b/test/geometry.jl index 9467039..9204b3d 100644 --- a/test/geometry.jl +++ b/test/geometry.jl @@ -73,4 +73,4 @@ for tri in (example_tri(), example_tri_rect()) @test ℓ2 ≈ L2 @test ℓ3 ≈ L3 end -end \ No newline at end of file +end diff --git a/test/problem.jl b/test/problem.jl index 77970fc..d07a1b4 100644 --- a/test/problem.jl +++ b/test/problem.jl @@ -15,7 +15,8 @@ include("test_functions.jl") flux_function, flux_parameters, source_function, source_parameters, initial_condition = example_problem() - @test sprint(show, MIME"text/plain"(), prob) == "FVMProblem with $(DelaunayTriangulation.num_points(tri)) nodes and time span ($(prob.initial_time), $(prob.final_time))" + @test sprint(show, MIME"text/plain"(), prob) == + "FVMProblem with $(DelaunayTriangulation.num_points(tri)) nodes and time span ($(prob.initial_time), $(prob.final_time))" conds = FVM.Conditions(mesh, BCs, ICs) @test prob.mesh == mesh @test prob.conditions == conds @@ -49,7 +50,8 @@ include("test_functions.jl") @test FVM.is_neumann_edge(prob, e...) end end - for i in DelaunayTriangulation.DelaunayTriangulation.each_point_index(prob.mesh.triangulation) + for i in + DelaunayTriangulation.DelaunayTriangulation.each_point_index(prob.mesh.triangulation) if i ∈ keys(prob.conditions.dirichlet_nodes) @test FVM.has_condition(prob, i) @test FVM.is_dirichlet_node(prob, i) @@ -75,27 +77,33 @@ include("test_functions.jl") qx = -α * u * p[1] + t qy = x + t - β * u * p[2] steady = SteadyFVMProblem(prob) - @test sprint(show, MIME"text/plain"(), steady) == "SteadyFVMProblem with $(DelaunayTriangulation.num_points(tri)) nodes" + @test sprint(show, MIME"text/plain"(), steady) == + "SteadyFVMProblem with $(DelaunayTriangulation.num_points(tri)) nodes" @inferred SteadyFVMProblem(prob) @test FVM.eval_flux_function(steady, x, y, t, α, β, γ) == (qx, qy) @inferred FVM.eval_flux_function(steady, x, y, t, α, β, γ) @test FVM._neqs(steady) == 0 @test !FVM.is_system(steady) - prob1, prob2, prob3, prob4, prob5 = example_problem(1; tri, mesh, initial_condition)[1], + prob1, prob2, + prob3, + prob4, + prob5 = example_problem(1; tri, mesh, initial_condition)[1], example_problem(2; tri, mesh, initial_condition)[1], example_problem(3; tri, mesh, initial_condition)[1], example_problem(4; tri, mesh, initial_condition)[1], example_problem(5; tri, mesh, initial_condition)[1] system = FVMSystem(prob1, prob2, prob3, prob4, prob5) - @test sprint(show, MIME"text/plain"(), system) == "FVMSystem with 5 equations and time span ($(system.initial_time), $(system.final_time))" + @test sprint(show, MIME"text/plain"(), system) == + "FVMSystem with 5 equations and time span ($(system.initial_time), $(system.final_time))" @inferred FVMSystem(prob1, prob2, prob3, prob4, prob5) _α = ntuple(_ -> α, 5) _β = ntuple(_ -> β, 5) _γ = ntuple(_ -> γ, 5) @test FVM.eval_flux_function(system, x, y, t, _α, _β, _γ) == ntuple(_ -> (qx, qy), 5) @inferred FVM.eval_flux_function(system, x, y, t, _α, _β, _γ) - @test system.initial_condition ≈ [initial_condition initial_condition initial_condition initial_condition initial_condition]' + @test system.initial_condition ≈ + [initial_condition initial_condition initial_condition initial_condition initial_condition]' @test FVM._neqs(system) == 5 @test FVM.is_system(system) @test system.initial_time == 2.0 @@ -133,7 +141,8 @@ include("test_functions.jl") steady_system = SteadyFVMProblem(system) @inferred SteadyFVMProblem(system) - @test FVM.eval_flux_function(steady_system, x, y, t, _α, _β, _γ) == ntuple(_ -> (qx, qy), 5) + @test FVM.eval_flux_function(steady_system, x, y, t, _α, _β, _γ) == + ntuple(_ -> (qx, qy), 5) @inferred FVM.eval_flux_function(steady_system, x, y, t, _α, _β, _γ) @test FVM._neqs(steady_system) == 5 @test FVM.is_system(steady_system) @@ -157,7 +166,7 @@ include("test_functions.jl") end @testset "FVMSystem" begin - tri = triangulate_rectangle(0, 1, 0, 1, 10, 10, single_boundary=false) + tri = triangulate_rectangle(0, 1, 0, 1, 10, 10, single_boundary = false) mesh = FVMGeometry(tri) Φ_bot = (x, y, t, u, p) -> -1 / 4 * exp(-x - t / 2) Φ_right = (x, y, t, u, p) -> 1 / 4 * exp(-1 - y - t / 2) @@ -181,18 +190,22 @@ end Ψ_exact = (x, y, t) -> exp(x + y + t / 2) Φ₀ = [Φ_exact(x, y, 0) for (x, y) in DelaunayTriangulation.each_point(tri)] Ψ₀ = [Ψ_exact(x, y, 0) for (x, y) in DelaunayTriangulation.each_point(tri)] - Φ_prob = FVMProblem(mesh, Φ_BCs; flux_function=Φ_q, source_function=Φ_S, - initial_condition=Φ₀, final_time=5.0) - Ψ_prob = FVMProblem(mesh, Ψ_BCs; flux_function=Ψ_q, source_function=Ψ_S, - initial_condition=Ψ₀, final_time=5.0) + Φ_prob = FVMProblem(mesh, Φ_BCs; flux_function = Φ_q, source_function = Φ_S, + initial_condition = Φ₀, final_time = 5.0) + Ψ_prob = FVMProblem(mesh, Ψ_BCs; flux_function = Ψ_q, source_function = Ψ_S, + initial_condition = Ψ₀, final_time = 5.0) prob = FVMSystem(Φ_prob, Ψ_prob) @test prob.mesh === mesh @test prob.initial_condition == [Φ₀'; Ψ₀'] @test prob.initial_time == 0.0 @test prob.final_time == 5.0 - cond1 = FVM.SimpleConditions(Φ_prob.conditions.neumann_edges, Φ_prob.conditions.constrained_edges, Φ_prob.conditions.dirichlet_nodes, Φ_prob.conditions.dudt_nodes) - cond2 = FVM.SimpleConditions(Ψ_prob.conditions.neumann_edges, Ψ_prob.conditions.constrained_edges, Ψ_prob.conditions.dirichlet_nodes, Ψ_prob.conditions.dudt_nodes) + cond1 = FVM.SimpleConditions( + Φ_prob.conditions.neumann_edges, Φ_prob.conditions.constrained_edges, + Φ_prob.conditions.dirichlet_nodes, Φ_prob.conditions.dudt_nodes) + cond2 = FVM.SimpleConditions( + Ψ_prob.conditions.neumann_edges, Ψ_prob.conditions.constrained_edges, + Ψ_prob.conditions.dirichlet_nodes, Ψ_prob.conditions.dudt_nodes) syscond = (cond1, cond2) @test prob.conditions == syscond @test prob.cnum_fncs == (0, 4) @@ -219,8 +232,10 @@ end @test FVM.get_neumann_fidx(prob, i, j, 1) == fidx x, y, t, u = rand(4) - @test Φ_prob.conditions.functions[fidx](x, y, t, u) == prob.functions[fidx](x, y, t, u) - @test FVM.eval_condition_fnc(prob, fidx, 1, x, y, t, u) == prob.functions[fidx](x, y, t, u) + @test Φ_prob.conditions.functions[fidx](x, y, t, u) == + prob.functions[fidx](x, y, t, u) + @test FVM.eval_condition_fnc(prob, fidx, 1, x, y, t, u) == + prob.functions[fidx](x, y, t, u) @inferred FVM.eval_condition_fnc(prob, fidx, 1, x, y, t, u) @test FVM.is_neumann_edge(prob, i, j, 1) @@ -229,8 +244,10 @@ end @test FVM.get_neumann_fidx(prob, i, j, 2) == fidx x, y, t, u = rand(4) - @test Ψ_prob.conditions.functions[fidx](x, y, t, u) == prob.functions[fidx+4](x, y, t, u) - @test FVM.eval_condition_fnc(prob, fidx, 2, x, y, t, u) == prob.functions[fidx+4](x, y, t, u) + @test Ψ_prob.conditions.functions[fidx](x, y, t, u) == + prob.functions[fidx + 4](x, y, t, u) + @test FVM.eval_condition_fnc(prob, fidx, 2, x, y, t, u) == + prob.functions[fidx + 4](x, y, t, u) @inferred FVM.eval_condition_fnc(prob, fidx, 2, x, y, t, u) @test FVM.is_neumann_edge(prob, i, j, 2) @@ -239,8 +256,10 @@ end @test FVM.get_dirichlet_fidx(prob, i, 1) == fidx x, y, t, u = rand(4) - @test Φ_prob.conditions.functions[fidx](x, y, t, u) == prob.functions[fidx](x, y, t, u) - @test FVM.eval_condition_fnc(prob, fidx, 1, x, y, t, u) == prob.functions[fidx](x, y, t, u) + @test Φ_prob.conditions.functions[fidx](x, y, t, u) == + prob.functions[fidx](x, y, t, u) + @test FVM.eval_condition_fnc(prob, fidx, 1, x, y, t, u) == + prob.functions[fidx](x, y, t, u) @inferred FVM.eval_condition_fnc(prob, fidx, 1, x, y, t, u) @test FVM.is_dirichlet_node(prob, i, 1) @@ -249,8 +268,10 @@ end @test FVM.get_dirichlet_fidx(prob, i, 2) == fidx x, y, t, u = rand(4) - @test Ψ_prob.conditions.functions[fidx](x, y, t, u) == prob.functions[fidx+4](x, y, t, u) - @test FVM.eval_condition_fnc(prob, fidx, 2, x, y, t, u) == prob.functions[fidx+4](x, y, t, u) + @test Ψ_prob.conditions.functions[fidx](x, y, t, u) == + prob.functions[fidx + 4](x, y, t, u) + @test FVM.eval_condition_fnc(prob, fidx, 2, x, y, t, u) == + prob.functions[fidx + 4](x, y, t, u) @inferred FVM.eval_condition_fnc(prob, fidx, 2, x, y, t, u) @test FVM.is_dirichlet_node(prob, i, 2) diff --git a/test/runtests.jl b/test/runtests.jl index cdf433c..213da10 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,7 +4,7 @@ using Dates using Aqua ct() = Dates.format(now(), "HH:MM:SS") -function safe_include(filename; name=filename) # Workaround for not being able to interpolate into SafeTestset test names +function safe_include(filename; name = filename) # Workaround for not being able to interpolate into SafeTestset test names mod = @eval module $(gensym()) end @info "[$(ct())] Testing $name" @testset verbose = true "Example: $name" begin @@ -18,7 +18,7 @@ end end @testset verbose = true "Conditions" begin safe_include("conditions.jl") - end + end @testset verbose = true "Problem" begin safe_include("problem.jl") end @@ -46,7 +46,7 @@ end "reaction_diffusion_brusselator_system_of_pdes.jl", "reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.jl", "solving_mazes_with_laplaces_equation.jl", - "gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.jl", + "gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system.jl" ] # do it manually just to make it easier for testing individual files rather than in a loop, e.g. one like #= for file in files @@ -56,20 +56,20 @@ end end =# @test length(files) == length(file_names) # make sure we didn't miss any - safe_include(joinpath(dir, file_names[1]); name=file_names[1]) # diffusion_equation_in_a_wedge_with_mixed_boundary_conditions - safe_include(joinpath(dir, file_names[2]); name=file_names[2]) # diffusion_equation_on_a_square_plate - safe_include(joinpath(dir, file_names[3]); name=file_names[3]) # diffusion_equation_on_an_annulus - safe_include(joinpath(dir, file_names[4]); name=file_names[4]) # equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems - safe_include(joinpath(dir, file_names[5]); name=file_names[5]) # helmholtz_equation_with_inhomogeneous_boundary_conditions - safe_include(joinpath(dir, file_names[6]); name=file_names[6]) # laplaces_equation_with_internal_dirichlet_conditions - safe_include(joinpath(dir, file_names[7]); name=file_names[7]) # mean_exit_time - safe_include(joinpath(dir, file_names[8]); name=file_names[8]) # piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation - safe_include(joinpath(dir, file_names[9]); name=file_names[9]) # porous_fisher_equation_and_travelling_waves - safe_include(joinpath(dir, file_names[10]); name=file_names[10]) # porous_medium_equation - safe_include(joinpath(dir, file_names[11]); name=file_names[11]) # reaction_diffusion_brusselator_system_of_pdes - safe_include(joinpath(dir, file_names[12]); name=file_names[12]) # reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk - safe_include(joinpath(dir, file_names[13]); name=file_names[13]) # solving_mazes_with_laplaces_equation - safe_include(joinpath(dir, file_names[14]); name=file_names[14]) # gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system + safe_include(joinpath(dir, file_names[1]); name = file_names[1]) # diffusion_equation_in_a_wedge_with_mixed_boundary_conditions + safe_include(joinpath(dir, file_names[2]); name = file_names[2]) # diffusion_equation_on_a_square_plate + safe_include(joinpath(dir, file_names[3]); name = file_names[3]) # diffusion_equation_on_an_annulus + safe_include(joinpath(dir, file_names[4]); name = file_names[4]) # equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems + safe_include(joinpath(dir, file_names[5]); name = file_names[5]) # helmholtz_equation_with_inhomogeneous_boundary_conditions + safe_include(joinpath(dir, file_names[6]); name = file_names[6]) # laplaces_equation_with_internal_dirichlet_conditions + safe_include(joinpath(dir, file_names[7]); name = file_names[7]) # mean_exit_time + safe_include(joinpath(dir, file_names[8]); name = file_names[8]) # piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation + safe_include(joinpath(dir, file_names[9]); name = file_names[9]) # porous_fisher_equation_and_travelling_waves + safe_include(joinpath(dir, file_names[10]); name = file_names[10]) # porous_medium_equation + safe_include(joinpath(dir, file_names[11]); name = file_names[11]) # reaction_diffusion_brusselator_system_of_pdes + safe_include(joinpath(dir, file_names[12]); name = file_names[12]) # reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk + safe_include(joinpath(dir, file_names[13]); name = file_names[13]) # solving_mazes_with_laplaces_equation + safe_include(joinpath(dir, file_names[14]); name = file_names[14]) # gray_scott_model_turing_patterns_from_a_coupled_reaction_diffusion_system # safe_include(joinpath(dir, file_names[15]); name=file_names[15]) # keller_segel_chemotaxis end @@ -81,18 +81,18 @@ end "mean_exit_time.jl", "linear_reaction_diffusion_equations.jl", "poissons_equation.jl", - "laplaces_equation.jl", + "laplaces_equation.jl" ] @test length(files) == length(file_names) # make sure we didn't miss any - safe_include(joinpath(dir, file_names[1]); name=file_names[1]) # diffusion_equations - safe_include(joinpath(dir, file_names[2]); name=file_names[2]) # mean_exit_time - safe_include(joinpath(dir, file_names[3]); name=file_names[3]) # linear_reaction_diffusion_equations - safe_include(joinpath(dir, file_names[4]); name=file_names[4]) # poissons_equation - safe_include(joinpath(dir, file_names[5]); name=file_names[5]) # laplaces_equation + safe_include(joinpath(dir, file_names[1]); name = file_names[1]) # diffusion_equations + safe_include(joinpath(dir, file_names[2]); name = file_names[2]) # mean_exit_time + safe_include(joinpath(dir, file_names[3]); name = file_names[3]) # linear_reaction_diffusion_equations + safe_include(joinpath(dir, file_names[4]); name = file_names[4]) # poissons_equation + safe_include(joinpath(dir, file_names[5]); name = file_names[5]) # laplaces_equation end @testset verbose = true "Aqua" begin - Aqua.test_all(FiniteVolumeMethod; ambiguities=false, project_extras=false) # don't care about julia < 1.2 + Aqua.test_all(FiniteVolumeMethod; ambiguities = false, project_extras = false) # don't care about julia < 1.2 Aqua.test_ambiguities(FiniteVolumeMethod) # don't pick up Base and Core... end -end \ No newline at end of file +end diff --git a/test/test_functions.jl b/test/test_functions.jl index 0fd83fb..61aef1f 100644 --- a/test/test_functions.jl +++ b/test/test_functions.jl @@ -1,6 +1,6 @@ function get_control_volume(tri, i) is_bnd, bnd_idx = DelaunayTriangulation.is_boundary_node(tri, i) - cv = NTuple{2,Float64}[] + cv = NTuple{2, Float64}[] if is_bnd j = DelaunayTriangulation.get_right_boundary_node(tri, i, bnd_idx) k = get_adjacent(tri, i, j) @@ -91,14 +91,14 @@ end function example_tri_rect() a, b, c, d, nx, ny = 0.0, 2.0, 0.0, 5.0, 12, 19 - tri = triangulate_rectangle(a, b, c, d, nx, ny; single_boundary=false) + tri = triangulate_rectangle(a, b, c, d, nx, ny; single_boundary = false) return tri end -function example_problem(idx=1; - tri=example_tri_rect(), - mesh=FVMGeometry(tri), - initial_condition=rand(DelaunayTriangulation.num_solid_vertices(tri))) +function example_problem(idx = 1; + tri = example_tri_rect(), + mesh = FVMGeometry(tri), + initial_condition = rand(DelaunayTriangulation.num_solid_vertices(tri))) f1 = (x, y, t, u, p) -> x * y + u - p f2 = (x, y, t, u, p) -> u + p - t f3 = (x, y, t, u, p) -> x @@ -106,10 +106,12 @@ function example_problem(idx=1; f = (f1, f2, f3, f4) conditions = (Dirichlet, Neumann, Dirichlet, Neumann) parameters = (0.5, 0.2, 0.3, 0.4) - BCs = BoundaryConditions(mesh, f, conditions; parameters=parameters) + BCs = BoundaryConditions(mesh, f, conditions; parameters = parameters) internal_dirichlet_nodes = Dict([7 + (i - 1) * 12 for i in 2:18] .=> 1) - ICs = InternalConditions((x, y, t, u, p) -> x + y + t + u + p, ; dirichlet_nodes=internal_dirichlet_nodes, parameters=0.29) - flux_function = (x, y, t, α, β, γ, p) -> let u = α[idx] * x + β[idx] * y + γ[idx] + ICs = InternalConditions((x, y, t, u, p) -> x + y + t + u + p, ; + dirichlet_nodes = internal_dirichlet_nodes, parameters = 0.29) + flux_function = ( + x, y, t, α, β, γ, p) -> let u = α[idx] * x + β[idx] * y + γ[idx] (-α[idx] * u * p[1] + t, x + t - β[idx] * u * p[2]) end flux_parameters = (-0.5, 1.3) @@ -125,10 +127,11 @@ function example_problem(idx=1; initial_condition, initial_time, final_time) - return prob, tri, mesh, BCs, ICs, flux_function, flux_parameters, source_function, source_parameters, initial_condition + return prob, tri, mesh, BCs, ICs, flux_function, flux_parameters, + source_function, source_parameters, initial_condition end -function example_bc_ic_setup(; nothing_dudt=false) +function example_bc_ic_setup(; nothing_dudt = false) if !nothing_dudt f1 = (x, y, t, u, p) -> x * y + u - p f2 = (x, y, t, u, p) -> u + p - t @@ -173,14 +176,14 @@ function example_bc_ic_setup(; nothing_dudt=false) 2 + 2 * 12 => 3 ) dudt_nodes = Dict( - (38:(38+7) .=> 2)..., - ([9 + (i - 1) * 12 for i in 2:18] .=> 4)..., + (38:(38 + 7) .=> 2)..., + ([9 + (i - 1) * 12 for i in 2:18] .=> 4)... ) return f, t, p, g, q, dirichlet_nodes, dudt_nodes end function test_bc_conditions!(tri, conds, t, - dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges, shift=0) + dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges, shift = 0) for e in keys(get_boundary_edge_map(tri)) u, v = DelaunayTriangulation.edge_vertices(e) w = get_adjacent(tri, v, u) @@ -199,7 +202,8 @@ function test_bc_conditions!(tri, conds, t, @test FiniteVolumeMethod.get_dirichlet_fidx(conds, v) == -w push!(dirichlet_nodes, get_point(tri, u, v)...) x, y, tt, u = rand(4) - @test FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) ≈ conds.functions[-w](x, y, tt, u) + @test FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) ≈ + conds.functions[-w](x, y, tt, u) @inferred conds.functions[-w](x, y, tt, u) @inferred FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) elseif bc_type == Dudt @@ -213,7 +217,8 @@ function test_bc_conditions!(tri, conds, t, @test FiniteVolumeMethod.get_dudt_fidx(conds, v) == -w push!(dudt_nodes, get_point(tri, u, v)...) x, y, tt, u = rand(4) - @test FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) ≈ conds.functions[-w](x, y, tt, u) + @test FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) ≈ + conds.functions[-w](x, y, tt, u) @inferred conds.functions[-w](x, y, tt, u) @inferred FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) elseif bc_type == Neumann @@ -224,7 +229,8 @@ function test_bc_conditions!(tri, conds, t, @test FiniteVolumeMethod.get_neumann_fidx(conds, u, v) == -w @test FiniteVolumeMethod.has_neumann_edges(conds) x, y, tt, u = rand(4) - @test FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) ≈ conds.functions[-w](x, y, tt, u) + @test FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) ≈ + conds.functions[-w](x, y, tt, u) @inferred conds.functions[-w](x, y, tt, u) @inferred FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) else @@ -234,7 +240,8 @@ function test_bc_conditions!(tri, conds, t, @test FiniteVolumeMethod.is_constrained_edge(conds, u, v) @test FiniteVolumeMethod.get_constrained_fidx(conds, u, v) == -w x, y, tt, u = rand(4) - @test FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) ≈ conds.functions[-w](x, y, tt, u) + @test FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) ≈ + conds.functions[-w](x, y, tt, u) @inferred conds.functions[-w](x, y, tt, u) @inferred FiniteVolumeMethod.eval_condition_fnc(conds, -w, x, y, tt, u) end @@ -243,8 +250,8 @@ function test_bc_conditions!(tri, conds, t, end function test_bc_ic_conditions!(tri, conds, t, - dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges, - ics) + dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges, + ics) nif = length(conds.functions) - DelaunayTriangulation.num_ghost_vertices(tri) test_bc_conditions!(tri, conds, t, dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges, nif) @@ -258,7 +265,8 @@ function test_bc_ic_conditions!(tri, conds, t, @test FiniteVolumeMethod.has_condition(conds, i) @test FiniteVolumeMethod.get_dirichlet_fidx(conds, i) == idx x, y, t, u = rand(4) - @test FiniteVolumeMethod.eval_condition_fnc(conds, idx, x, y, t, u) ≈ conds.functions[idx](x, y, t, u) + @test FiniteVolumeMethod.eval_condition_fnc(conds, idx, x, y, t, u) ≈ + conds.functions[idx](x, y, t, u) @inferred conds.functions[idx](x, y, t, u) @inferred FiniteVolumeMethod.eval_condition_fnc(conds, idx, x, y, t, u) end @@ -271,7 +279,8 @@ function test_bc_ic_conditions!(tri, conds, t, @test FiniteVolumeMethod.has_condition(conds, i) @test FiniteVolumeMethod.get_dudt_fidx(conds, i) == idx x, y, t, u = rand(4) - @test FiniteVolumeMethod.eval_condition_fnc(conds, idx, x, y, t, u) ≈ conds.functions[idx](x, y, t, u) + @test FiniteVolumeMethod.eval_condition_fnc(conds, idx, x, y, t, u) ≈ + conds.functions[idx](x, y, t, u) @inferred conds.functions[idx](x, y, t, u) @inferred FiniteVolumeMethod.eval_condition_fnc(conds, idx, x, y, t, u) end @@ -281,7 +290,7 @@ end function example_diffusion_problem() a, b, c, d = 0.0, 2.0, 0.0, 2.0 nx, ny = 25, 25 - tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary=true) + tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary = true) mesh = FVMGeometry(tri) bc = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, bc, Dirichlet) @@ -289,13 +298,13 @@ function example_diffusion_problem() initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] D = (x, y, t, u, p) -> 1 / 9 final_time = 0.5 - prob = FVMProblem(mesh, BCs; diffusion_function=D, initial_condition, final_time) + prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_time) return prob end function example_diffusion_problem_system() a, b, c, d = 0.0, 2.0, 0.0, 2.0 nx, ny = 25, 25 - tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary=true) + tri = triangulate_rectangle(a, b, c, d, nx, ny, single_boundary = true) mesh = FVMGeometry(tri) bc = (x, y, t, u, p) -> zero(u[1]) * zero(u[2]) BCs = BoundaryConditions(mesh, bc, Dirichlet) @@ -305,8 +314,8 @@ function example_diffusion_problem_system() q1 = (x, y, t, α, β, γ, p) -> (-α[1] / 9, -β[1] / 9) q2 = (x, y, t, α, β, γ, p) -> (-α[2] / 9, -β[2] / 9) final_time = 0.5 - prob1 = FVMProblem(mesh, BCs; flux_function=q1, initial_condition, final_time) - prob2 = FVMProblem(mesh, BCs; flux_function=q2, initial_condition, final_time) + prob1 = FVMProblem(mesh, BCs; flux_function = q1, initial_condition, final_time) + prob2 = FVMProblem(mesh, BCs; flux_function = q2, initial_condition, final_time) return FVMSystem(prob1, prob2), example_diffusion_problem() end @@ -318,7 +327,7 @@ function example_heat_convection_problem() α = 80e-6 q = 10.0 h = 25.0 - tri = triangulate_rectangle(0, L, 0, L, 200, 200; single_boundary=false) + tri = triangulate_rectangle(0, L, 0, L, 200, 200; single_boundary = false) mesh = FVMGeometry(tri) bot_wall = (x, y, t, T, p) -> -p.α * p.q / p.k right_wall = (x, y, t, T, p) -> zero(T) @@ -326,9 +335,9 @@ function example_heat_convection_problem() left_wall = (x, y, t, T, p) -> zero(T) bc_fncs = (bot_wall, right_wall, top_wall, left_wall) # the order is important types = (Neumann, Neumann, Neumann, Neumann) - bot_parameters = (α=α, q=q, k=k) + bot_parameters = (α = α, q = q, k = k) right_parameters = nothing - top_parameters = (α=α, h=h, k=k, T∞=T∞) + top_parameters = (α = α, h = h, k = k, T∞ = T∞) left_parameters = nothing parameters = (bot_parameters, right_parameters, top_parameters, left_parameters) BCs = BoundaryConditions(mesh, bc_fncs, types; parameters) @@ -336,7 +345,7 @@ function example_heat_convection_problem() ∇u = (α, β) return -p.α .* ∇u end - flux_parameters = (α=α,) + flux_parameters = (α = α,) final_time = 2000.0 f = (x, y) -> T₀ initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] @@ -351,7 +360,8 @@ end function test_shape_function_coefficients(prob, u) for T in each_solid_triangle(prob.mesh.triangulation) i, j, k = triangle_vertices(T) - s₁, s₂, s₃, s₄, s₅, s₆, s₇, s₈, s₉ = prob.mesh.triangle_props[T].shape_function_coefficients + s₁, s₂, s₃, s₄, s₅, s₆, s₇, + s₈, s₉ = prob.mesh.triangle_props[T].shape_function_coefficients ui, uj, uk = u[i], u[j], u[k] α = s₁ * ui + s₂ * uj + s₃ * uk β = s₄ * ui + s₅ * uj + s₆ * uk @@ -365,7 +375,8 @@ function test_shape_function_coefficients(prob, u) @test α * xk + β * yk + γ ≈ uk atol = 1e-9 cx, cy = (xi + xj + xk) / 3, (yi + yj + yk) / 3 @test α * cx + β * cy + γ ≈ (ui + uj + uk) / 3 - a, b, c = FVM.get_shape_function_coefficients(prob.mesh.triangle_props[T], T, u, prob) + a, b, + c = FVM.get_shape_function_coefficients(prob.mesh.triangle_props[T], T, u, prob) @test a ≈ α atol = 1e-9 @test b ≈ β atol = 1e-9 @test c ≈ γ atol = 1e-9 @@ -377,7 +388,8 @@ function test_get_flux(prob, u, t) for T in each_solid_triangle(prob.mesh.triangulation) p, q, r = get_point(prob.mesh.triangulation, T...) c = (p .+ q .+ r) ./ 3 - s₁, s₂, s₃, s₄, s₅, s₆, s₇, s₈, s₉ = prob.mesh.triangle_props[T].shape_function_coefficients + s₁, s₂, s₃, s₄, s₅, s₆, s₇, + s₈, s₉ = prob.mesh.triangle_props[T].shape_function_coefficients α = s₁ * u[T[1]] + s₂ * u[T[2]] + s₃ * u[T[3]] β = s₄ * u[T[1]] + s₅ * u[T[2]] + s₆ * u[T[3]] γ = s₇ * u[T[1]] + s₈ * u[T[2]] + s₉ * u[T[3]] @@ -393,8 +405,10 @@ function test_get_flux(prob, u, t) @inferred prob.flux_function(x, y, t, α, β, γ, prob.flux_parameters) @inferred FVM.eval_flux_function(prob, x, y, t, α, β, γ) qn = (qx * nx + qy * ny) * ℓ - @test qn ≈ FVM.get_flux(prob, prob.mesh.triangle_props[T], α, β, γ, t, edge_index) atol = 1e-9 - @inferred FVM.get_flux(prob, prob.mesh.triangle_props[T], α, β, γ, t, edge_index) + @test qn ≈ + FVM.get_flux(prob, prob.mesh.triangle_props[T], α, β, γ, t, edge_index) atol = 1e-9 + @inferred FVM.get_flux( + prob, prob.mesh.triangle_props[T], α, β, γ, t, edge_index) push!(_qn, qn) end @test _qn ≈ collect(FVM.get_fluxes(prob, prob.mesh.triangle_props[T], α, β, γ, t)) @@ -402,7 +416,7 @@ function test_get_flux(prob, u, t) end end -function test_get_boundary_flux(prob, u, t, is_diff=true) +function test_get_boundary_flux(prob, u, t, is_diff = true) tri = prob.mesh.triangulation for e in keys(get_boundary_edge_map(tri)) i, j = e @@ -416,7 +430,8 @@ function test_get_boundary_flux(prob, u, t, is_diff=true) elseif haskey(prob.mesh.triangle_props, (k, i, j)) T = (k, i, j) end - s₁, s₂, s₃, s₄, s₅, s₆, s₇, s₈, s₉ = prob.mesh.triangle_props[T].shape_function_coefficients + s₁, s₂, s₃, s₄, s₅, s₆, s₇, + s₈, s₉ = prob.mesh.triangle_props[T].shape_function_coefficients α = s₁ * u[T[1]] + s₂ * u[T[2]] + s₃ * u[T[3]] β = s₄ * u[T[1]] + s₅ * u[T[2]] + s₆ * u[T[3]] γ = s₇ * u[T[1]] + s₈ * u[T[2]] + s₉ * u[T[3]] @@ -428,16 +443,24 @@ function test_get_boundary_flux(prob, u, t, is_diff=true) @inferred prob.flux_function(x, y, t, α, β, γ, prob.flux_parameters) @inferred FVM.eval_flux_function(prob, x, y, t, α, β, γ) q1 = (_qx * nx + _qy * ny) * norm(p .- (p .+ q) ./ 2) - @test q1 ≈ FVM._get_boundary_flux(prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) * norm(p .- (p .+ q) ./ 2) atol = 1e-6 - @inferred FVM._get_boundary_flux(prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) + @test q1 ≈ + FVM._get_boundary_flux( + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) * + norm(p .- (p .+ q) ./ 2) atol = 1e-6 + @inferred FVM._get_boundary_flux( + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) # Second edge x, y = ((px + qx) / 2 + qx) / 2, ((py + qy) / 2 + qy) / 2 _qx, _qy = prob.flux_function(x, y, t, α, β, γ, prob.flux_parameters) @inferred prob.flux_function(x, y, t, α, β, γ, prob.flux_parameters) @inferred FVM.eval_flux_function(prob, x, y, t, α, β, γ) q2 = (_qx * nx + _qy * ny) * norm((p .+ q) ./ 2 .- q) - @test q2 ≈ FVM._get_boundary_flux(prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) * norm((p .+ q) ./ 2 .- q) - @inferred FVM._get_boundary_flux(prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) + @test q2 ≈ + FVM._get_boundary_flux( + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) * + norm((p .+ q) ./ 2 .- q) + @inferred FVM._get_boundary_flux( + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) else # First edge x, y = (px + (px + qx) / 2) / 2, (py + (py + qy) / 2) / 2 @@ -446,8 +469,12 @@ function test_get_boundary_flux(prob, u, t, is_diff=true) fnc = prob.conditions.functions[idx] q1 = fnc(x, y, t, α * x + β * y + γ) * norm(p .- (p .+ q) ./ 2) @inferred FVM.eval_condition_fnc(prob, idx, x, y, t, α * x + β * y + γ) - @test q1 ≈ FVM._get_boundary_flux(prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) * norm(p .- (p .+ q) ./ 2) - @inferred FVM._get_boundary_flux(prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) + @test q1 ≈ + FVM._get_boundary_flux( + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) * + norm(p .- (p .+ q) ./ 2) + @inferred FVM._get_boundary_flux( + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) # Second edge x, y = ((px + qx) / 2 + qx) / 2, ((py + qy) / 2 + qy) / 2 nx, ny = (qy - py) / norm(p .- q), -(qx - px) / norm(p .- q) @@ -455,8 +482,12 @@ function test_get_boundary_flux(prob, u, t, is_diff=true) fnc = prob.conditions.functions[idx] q2 = fnc(x, y, t, α * x + β * y + γ) * norm((p .+ q) ./ 2 .- q) @inferred FVM.eval_condition_fnc(prob, idx, x, y, t, α * x + β * y + γ) - @test q2 ≈ FVM._get_boundary_flux(prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) * norm((p .+ q) ./ 2 .- q) - @inferred FVM._get_boundary_flux(prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) + @test q2 ≈ + FVM._get_boundary_flux( + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) * + norm((p .+ q) ./ 2 .- q) + @inferred FVM._get_boundary_flux( + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) end # Compare _q1, _q2 = FVM.get_boundary_fluxes(prob, α, β, γ, e..., t) @@ -471,13 +502,15 @@ function test_single_triangle(prob, u, t) fill!(du, 0.0) p, q, r = get_point(prob.mesh.triangulation, T...) c = (p .+ q .+ r) ./ 3 - s₁, s₂, s₃, s₄, s₅, s₆, s₇, s₈, s₉ = prob.mesh.triangle_props[T].shape_function_coefficients + s₁, s₂, s₃, s₄, s₅, s₆, s₇, + s₈, s₉ = prob.mesh.triangle_props[T].shape_function_coefficients α = s₁ * u[T[1]] + s₂ * u[T[2]] + s₃ * u[T[3]] β = s₄ * u[T[1]] + s₅ * u[T[2]] + s₆ * u[T[3]] γ = s₇ * u[T[1]] + s₈ * u[T[2]] + s₉ * u[T[3]] _qn = Float64[] for (edge_index, (i, j)) in enumerate(DelaunayTriangulation.triangle_edges(T)) - push!(_qn, FVM.get_flux(prob, prob.mesh.triangle_props[T], α, β, γ, t, edge_index)) + push!(_qn, FVM.get_flux( + prob, prob.mesh.triangle_props[T], α, β, γ, t, edge_index)) end q1, q2, q3 = _qn dui = -(q1 - q3) @@ -532,7 +565,7 @@ function _both_on_same_edge(p, q, L) end end -function get_dudt_val(prob, u, t, i, is_diff=true) +function get_dudt_val(prob, u, t, i, is_diff = true) i ∈ keys(prob.conditions.dirichlet_nodes) && return 0.0 mesh = prob.mesh tri = mesh.triangulation @@ -540,7 +573,7 @@ function get_dudt_val(prob, u, t, i, is_diff=true) int = 0.0 nedges = length(cv) - 1 for j in 1:nedges - p, q = cv[j], cv[j+1] + p, q = cv[j], cv[j + 1] px, py = p qx, qy = q mx, my = (px + qx) / 2, (py + qy) / 2 @@ -574,13 +607,15 @@ function get_dudt_val(prob, u, t, i, is_diff=true) int += L * _q end end - dudt = prob.source_function(get_point(tri, i)..., t, u[i], prob.source_parameters) - int / mesh.cv_volumes[i] + dudt = prob.source_function(get_point(tri, i)..., t, u[i], prob.source_parameters) - + int / mesh.cv_volumes[i] return dudt end -function test_dudt_val(prob, u, t, is_diff=true) - dudt = [get_dudt_val(prob, u, t, i, is_diff) for i in DelaunayTriangulation.each_point_index(prob.mesh.triangulation)] - dudt_fnc = FVM.fvm_eqs!(zero(dudt), u, (prob=prob, parallel=Val(false)), t) +function test_dudt_val(prob, u, t, is_diff = true) + dudt = [get_dudt_val(prob, u, t, i, is_diff) + for i in DelaunayTriangulation.each_point_index(prob.mesh.triangulation)] + dudt_fnc = FVM.fvm_eqs!(zero(dudt), u, (prob = prob, parallel = Val(false)), t) @test dudt ≈ dudt_fnc dudt_fnc = FVM.fvm_eqs!(zero(dudt), u, FVM.get_multithreading_parameters(prob), t) @test dudt ≈ dudt_fnc @@ -617,8 +652,10 @@ function test_compute_flux(_prob, steady, system, steady_system) qv = FVM.eval_flux_function(prob, ((p .+ q) ./ 2)..., 2.5, α, β, γ) ex, ey = (q .- p) ./ norm(p .- q) nx, ny = ey, -ex - @test DelaunayTriangulation.distance_to_polygon((p .+ q) ./ 2 .+ (nx, ny), get_points(tri), get_boundary_nodes(tri)) < 0.0 - @test DelaunayTriangulation.is_right(DelaunayTriangulation.point_position_relative_to_line(p, q, (p .+ q) ./ 2 .+ (nx, ny))) + @test DelaunayTriangulation.distance_to_polygon( + (p .+ q) ./ 2 .+ (nx, ny), get_points(tri), get_boundary_nodes(tri)) < 0.0 + @test DelaunayTriangulation.is_right(DelaunayTriangulation.point_position_relative_to_line( + p, q, (p .+ q) ./ 2 .+ (nx, ny))) _qv = compute_flux(prob, _i, _j, u, 2.5) if !FVM.is_system(prob) @test _qv ≈ dot(qv, (nx, ny)) @@ -650,7 +687,8 @@ function test_compute_flux(_prob, steady, system, steady_system) qv = FVM.eval_flux_function(prob, ((p .+ q) ./ 2)..., 2.5, α, β, γ) ex, ey = (q .- p) ./ norm(p .- q) nx, ny = ey, -ex - @test DelaunayTriangulation.is_right(DelaunayTriangulation.point_position_relative_to_line(p, q, (p .+ q) ./ 2 .+ (nx, ny))) + @test DelaunayTriangulation.is_right(DelaunayTriangulation.point_position_relative_to_line( + p, q, (p .+ q) ./ 2 .+ (nx, ny))) _qv = compute_flux(prob, i, j, u, 2.5) if !FVM.is_system(prob) @test _qv ≈ dot(qv, (nx, ny)) @@ -666,7 +704,8 @@ function test_compute_flux(_prob, steady, system, steady_system) end function test_jacobian_sparsity(prob::FVMProblem) - A = zeros(DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation), DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation)) + A = zeros(DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation), + DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation)) for i in each_solid_vertex(prob.mesh.triangulation) A[i, i] = 1.0 for j in get_neighbours(prob.mesh.triangulation, i) @@ -707,4 +746,4 @@ function test_jacobian_sparsity(prob::FVMSystem{N}) where {N} end @test A == FVM.jacobian_sparsity(prob) end -test_jacobian_sparsity(prob::SteadyFVMProblem) = test_jacobian_sparsity(prob.problem) \ No newline at end of file +test_jacobian_sparsity(prob::SteadyFVMProblem) = test_jacobian_sparsity(prob.problem)