diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml deleted file mode 100644 index 3494a9f..0000000 --- a/.JuliaFormatter.toml +++ /dev/null @@ -1,3 +0,0 @@ -style = "sciml" -format_markdown = true -format_docstrings = true diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml new file mode 100644 index 0000000..6762c6f --- /dev/null +++ b/.github/workflows/FormatCheck.yml @@ -0,0 +1,19 @@ +name: format-check + +on: + push: + branches: + - 'master' + - 'main' + - 'release-' + tags: '*' + pull_request: + +jobs: + runic: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: fredrikekre/runic-action@v1 + with: + version: '1' diff --git a/docs/liveserver.jl b/docs/liveserver.jl index 02137d1..cc4b061 100644 --- a/docs/liveserver.jl +++ b/docs/liveserver.jl @@ -8,9 +8,10 @@ withenv("LIVESERVER_ACTIVE" => "true") do launch_browser = true, foldername = joinpath(repo_root, "docs"), include_dirs = [joinpath(repo_root, "src")], - skip_dirs = [joinpath(repo_root, "docs/src/tutorials"), + 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 diff --git a/docs/make.jl b/docs/make.jl index 76295cf..9883cc1 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -10,11 +10,11 @@ if RUN_EXAMPLES using CairoMakie CairoMakie.activate!() - # When running docs locally, the EditURL is incorrect. For example, we might get + # When running docs locally, the EditURL is incorrect. For example, we might get # ```@meta # EditURL = "/docs/src/literate_tutorials/name.jl" # ``` - # We need to replace this EditURL if we are running the docs locally. The last case is more complicated because, + # We need to replace this EditURL if we are running the docs locally. The last case is more complicated because, # after changing to use temporary directories, it can now look like... # ```@meta # EditURL = "../../../../../../../AppData/Local/Temp/jl_8nsMGu/name_just_the_code.jl" @@ -22,8 +22,10 @@ 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. @@ -37,8 +39,10 @@ if RUN_EXAMPLES 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") @@ -61,14 +65,14 @@ 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", "wyos/laplaces_equation.jl", "wyos/mean_exit_time.jl", "wyos/poissons_equation.jl", - "wyos/linear_reaction_diffusion_equations.jl" + "wyos/linear_reaction_diffusion_equations.jl", ] example_files = vcat(tutorial_files, wyos_files) session_tmp = mktempdir() @@ -81,8 +85,10 @@ 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" @@ -130,7 +136,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", @@ -138,9 +144,9 @@ _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" + "Mathematical and Implementation Details" => "math.md", ] # Make sure we haven't forgotten any files @@ -173,8 +179,10 @@ end !isempty(missing_set) && error("Missing files: $missing_set") # Make and deploy -DocMeta.setdocmeta!(FiniteVolumeMethod, :DocTestSetup, :(using FiniteVolumeMethod, Test); - recursive = true) +DocMeta.setdocmeta!( + FiniteVolumeMethod, :DocTestSetup, :(using FiniteVolumeMethod, Test); + recursive = true +) IS_LIVESERVER = get(ENV, "LIVESERVER_ACTIVE", "false") == "true" IS_CI = get(ENV, "CI", "false") == "true" makedocs(; @@ -186,14 +194,17 @@ makedocs(; 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"] + mathengine = MathJax3( + Dict( + :loader => Dict("load" => ["[tex]/physics"]), + :tex => Dict( + "inlineMath" => [["\$", "\$"], ["\\(", "\\)"]], + "tags" => "ams", + "packages" => ["base", "ams", "autoload", "physics"] + ) ) - ))), + ) + ), draft = IS_LIVESERVER, pages = _PAGES, warnonly = true @@ -202,4 +213,5 @@ makedocs(; deploydocs(; repo = "github.com/SciML/FiniteVolumeMethod.jl", devbranch = "main", - push_preview = true) + push_preview = true +) 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 2b010fb..b4a0e5f 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 @@ -1,7 +1,7 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Diffusion Equation in a Wedge with Mixed Boundary Conditions -# In this example, we consider a diffusion equation on a wedge +# # 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*} @@ -16,21 +16,21 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # ``` # where we take $f(r,\theta) = 1-r$ and $\alpha=\pi/4$. # -# Note that the PDE is provided in polar form, but Cartesian coordinates -# are assumed for the operators in our code. The conversion is easy, noting +# Note that the PDE is provided in polar form, but Cartesian coordinates +# are assumed for the operators in our code. The conversion is easy, noting # that the two Neumann conditions are just equations of the form $\grad u \vdot \vu n = 0$. -# Moreover, although the right-hand side of the PDE is given as a Laplacian, +# Moreover, although the right-hand side of the PDE is given as a Laplacian, # recall that $\grad^2 = \div\grad$, so we can write the PDE as $\partial u/\partial t + \div \vb q = 0$, # where $\vb q = -\grad u$. # -# Let us now setup the problem. To define the geometry, -# we need to be careful that the `Triangulation` recognises +# Let us now setup the problem. To define the geometry, +# we need to be careful that the `Triangulation` recognises # that we need to split the boundary into three parts, -# one part for each boundary condition. This is accomplished +# one part for each boundary condition. This is accomplished # by providing a single vector for each part of the boundary as follows # (and as described in DelaunayTriangulation.jl's documentation), -# where we also `refine!` the mesh to get a better mesh. For the arc, -# we use the `CircularArc` so that the mesh knows that it is triangulating +# where we also `refine!` the mesh to get a better mesh. For the arc, +# we use the `CircularArc` so that the mesh knows that it is triangulating # a certain arc in that area. using DelaunayTriangulation, FiniteVolumeMethod, ElasticArrays using ReferenceTests, Bessels, FastGaussQuadrature, Cubature #src @@ -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 = 1.0e-4A) mesh = FVMGeometry(tri) # This is the mesh we've constructed. @@ -54,30 +54,30 @@ fig # To confirm that the boundary is now in three parts, see: get_boundary_nodes(tri) -# We now need to define the boundary conditions. For this, -# we need to provide `Tuple`s, where the `i`th element of the -# `Tuple`s refers to the `i`th part of the boundary. The boundary +# We now need to define the boundary conditions. For this, +# we need to provide `Tuple`s, where the `i`th element of the +# `Tuple`s refers to the `i`th part of the boundary. The boundary # conditions are thus: lower_bc = arc_bc = upper_bc = (x, y, t, u, p) -> zero(u) types = (Neumann, Dirichlet, Neumann) BCs = BoundaryConditions(mesh, (lower_bc, arc_bc, upper_bc), types) -# Now we can define the PDE. We use the reaction-diffusion formulation, -# specifying the diffusion function as a constant. +# Now we can define the PDE. We use the reaction-diffusion formulation, +# specifying the diffusion function as a constant. 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) -# If you did want to use the flux formulation, you would need to provide +# If you did want to use the flux formulation, you would need to provide flux = (x, y, t, α, β, γ, p) -> (-α, -β) # which replaces `u` with `αx + βy + γ` so that we approximate $\grad u$ by $(\alpha,\beta)^{\mkern-1.5mu\mathsf{T}}$, # and the negative is needed since $\vb q = -\grad u$. # We now solve the problem. We provide the solver for this problem. -# In my experience, I've found that `TRBDF2(linsolve=KLUFactorization())` typically +# 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)) @@ -93,17 +93,20 @@ using CairoMakie 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, + ax = Axis( + fig[1, i], width = 600, height = 600, xlabel = "x", ylabel = "y", title = "t = $(sol.t[j])", - titlealign = :left) + 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 + @__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 @@ -120,9 +123,9 @@ function get_sum_coefficients(M, 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 + 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 + hcubature(integrand, [0.0, 0.0], [α, 1.0]; abstol = 1.0e-8)[1] #src end #src end #src return A #src @@ -141,7 +144,7 @@ function exact_solution(x, y, t, A, ζ, f, α) #src order = n * π / α #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 + cos(order * θ) #src end #src end #src return s #src @@ -172,5 +175,7 @@ for i in eachindex(sol) #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 +@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 6df2f46..2916ce2 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 @@ -34,7 +34,7 @@ fig bc = (x, y, t, u, p) -> zero(u) BCs = BoundaryConditions(mesh, bc, Dirichlet) -# We can now define the actual PDE. We start by defining the initial condition and the diffusion function. +# We can now define the actual PDE. We start by defining the initial condition and the diffusion function. 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 @@ -47,26 +47,28 @@ prob = FVMProblem(mesh, BCs; diffusion_function = D, initial_condition, final_ti prob.flux_function # When providing `diffusion_function`, the flux is given by $\vb q(\vb x, t, \alpha,\beta,\gamma) = (-\alpha/9, -\beta/9)^{\mkern-1.5mu\mathsf{T}}$, -# where $(\alpha, \beta, \gamma)$ defines the approximation to $u$ via $u(x, y) = \alpha x + \beta y + \gamma$ so that +# where $(\alpha, \beta, \gamma)$ defines the approximation to $u$ via $u(x, y) = \alpha x + \beta y + \gamma$ so that # $\grad u(\vb x, t) = (\alpha,\beta)^{\mkern-1.5mu\mathsf{T}}$. -# To now solve the problem, we simply use `solve`. Note that, +# To now solve the problem, we simply use `solve`. Note that, # in the `solve` call below, multithreading is enabled by default. -# (If you don't know what algorithm to consider, do `using DifferentialEquations` instead -# and simply call `solve(prob, saveat=0.05)` so that the algorithm is chosen automatically instead +# (If you don't know what algorithm to consider, do `using DifferentialEquations` instead +# 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 |> tc #hide -# To visualise the solution, we can use `tricontourf!` from Makie.jl. +# To visualise the solution, we can use `tricontourf!` from Makie.jl. 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, + ax = Axis( + fig[1, i], width = 600, height = 600, xlabel = "x", ylabel = "y", title = "t = $(sol.t[j])", - titlealign = :left) + titlealign = :left + ) tricontourf!(ax, tri, sol.u[j], levels = 0:5:50, colormap = :matter) tightlimits!(ax) end @@ -81,7 +83,7 @@ function exact_solution(x, y, t) #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 + exp(-π^2 * n^2 * t / 36) #src s += mterm * nterm #src end #src end #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 73a19a8..b23d618 100644 --- a/docs/src/literate_tutorials/diffusion_equation_on_an_annulus.jl +++ b/docs/src/literate_tutorials/diffusion_equation_on_an_annulus.jl @@ -1,7 +1,7 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Diffusion Equation on an Annulus -# In this tutorial, we consider a +# # Diffusion Equation on an Annulus +# In this tutorial, we consider a # diffusion equation on an annulus: # ```math # \begin{equation} @@ -13,10 +13,10 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \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 +# 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]}. # ``` @@ -29,17 +29,17 @@ boundary_nodes = [[[outer]], [[inner]]] points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) A = get_area(tri) -refine!(tri; max_area = 1e-4A) +refine!(tri; max_area = 1.0e-4A) triplot(tri) #- mesh = FVMGeometry(tri) -# Now let us define the boundary conditions. Remember, -# the order of the boundary conditions follows the order -# of the boundaries in the mesh. The outer boundary -# came first, and then came the inner boundary. We can verify -# that this is the order of the boundary indices as +# Now let us define the boundary conditions. Remember, +# the order of the boundary conditions follows the order +# of the boundaries in the mesh. The outer boundary +# came first, and then came the inner boundary. We can verify +# that this is the order of the boundary indices as # follows: fig = Figure() ax = Axis(fig[1, 1]) @@ -50,29 +50,35 @@ scatter!(ax, outer, color = :red) scatter!(ax, inner, color = :blue) fig -# So, the boundary conditions are: +# So, the boundary conditions are: outer_bc = (x, y, t, u, p) -> zero(u) 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) -# Finally, let's define the problem and solve it. -initial_condition_f = (x, - y) -> begin +# 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))) + 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; +prob = FVMProblem( + mesh, BCs; diffusion_function, final_time, - initial_condition) + initial_condition +) -#- +#- using OrdinaryDiffEq, LinearSolve sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.2) sol |> tc #hide @@ -81,10 +87,12 @@ sol |> tc #hide 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, + ax = Axis( + fig[1, i], width = 600, height = 600, xlabel = "x", ylabel = "y", title = "t = $(sol.t[j])", - titlealign = :left) + titlealign = :left + ) tricontourf!(ax, tri, sol.u[j], levels = -10:2:40, colormap = :matter) tightlimits!(ax) end @@ -93,15 +101,15 @@ fig using ReferenceTests #src @test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_on_an_annulus.png") fig #src -# To finish this example, let us consider how -# natural neighbour interpolation can be applied here. The -# application is more complicated for this problem since -# the mesh has holes. Before we do that, though, let us -# show how we could use `pl_interpolate`, which could -# be useful if we did not need a higher quality interpolant. -# Let us interpolate the solution at $t = 1$, which -# is `sol.t[6]`. For this, we need to put the ghost -# triangles back into `tri` so that we can safely +# To finish this example, let us consider how +# natural neighbour interpolation can be applied here. The +# application is more complicated for this problem since +# the mesh has holes. Before we do that, though, let us +# show how we could use `pl_interpolate`, which could +# be useful if we did not need a higher quality interpolant. +# Let us interpolate the solution at $t = 1$, which +# is `sol.t[6]`. For this, we need to put the ghost +# triangles back into `tri` so that we can safely # apply `jump_and_march`. This is done with `add_ghost_triangles!`. add_ghost_triangles!(tri) @@ -130,36 +138,41 @@ 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.png") fig #src -# Let's now consider applying NaturalNeighbours.jl. We apply it naively first to -# highlight some complications. +# Let's now consider applying NaturalNeighbours.jl. We apply it naively first to +# highlight some complications. using NaturalNeighbours -_x = vec([x for x in x, y in y]) # NaturalNeighbours.jl needs vector data +_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 |> tc #hide -#- +#- 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) + 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 +# 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 |> tc #hide #- fig, ax, -sc = contourf( - x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40) + 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 + @__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 5ae9c8c..d17384b 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 @@ -38,17 +38,17 @@ fig #hide # Let us start by defining the mesh. using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie A, B, -C, -D, -E, -F, -G = (0.0, 0.0), -(0.06, 0.0), -(0.06, 0.03), -(0.05, 0.03), -(0.03, 0.05), -(0.03, 0.06), -(0.0, 0.06) + C, + D, + E, + F, + G = (0.0, 0.0), + (0.06, 0.0), + (0.06, 0.03), + (0.05, 0.03), + (0.03, 0.05), + (0.03, 0.06), + (0.0, 0.06) bn1 = [G, A, B] bn2 = [B, C] bn3 = [C, D, E, F] @@ -56,44 +56,48 @@ 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 = 1.0e-4get_area(tri)) triplot(tri) #- mesh = FVMGeometry(tri) -# For the boundary conditions, the parameters that we use are -# $k = 3$, $h = 20$, and $T_{\infty} = 20$ for thermal conductivity, +# For the boundary conditions, the parameters that we use are +# $k = 3$, $h = 20$, and $T_{\infty} = 20$ for thermal conductivity, # heat transfer coefficient, and ambient temperature, respectively. k = 3.0 h = 20.0 T∞ = 20.0 -bc1 = (x, y, t, T, p) -> zero(T) # ∇T⋅n=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) -BCs = BoundaryConditions(mesh, (bc1, bc2, bc3, bc4), +BCs = BoundaryConditions( + mesh, (bc1, bc2, bc3, bc4), (Neumann, Dirichlet, Neumann, Dirichlet); - parameters) + parameters +) -# Now we can define the actual problem. For the initial condition, -# which recall is used as an initial guess for steady state problems, +# Now we can define the actual problem. For the initial condition, +# which recall is used as an initial guess for steady state problems, # let us use an initial condition which ranges from $T=70$ at $y=0.06$ # down to $T=40$ at $y=0$. diffusion_function = (x, y, t, T, p) -> one(T) f = (x, y) -> 500y + 40 initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] final_time = Inf -prob = FVMProblem(mesh, BCs; +prob = FVMProblem( + mesh, BCs; diffusion_function, initial_condition, - final_time) + final_time +) #- steady_prob = SteadyFVMProblem(prob) -# Now we can solve. +# Now we can solve. using OrdinaryDiffEq, SteadyStateDiffEq sol = solve(steady_prob, DynamicSS(Rosenbrock23())) sol |> tc #hide @@ -102,6 +106,8 @@ sol |> tc #hide fig, ax, sc = tricontourf(tri, sol.u, levels = 40:70, axis = (xlabel = "x", ylabel = "y")) fig using ReferenceTests #src -@test_reference joinpath(@__DIR__, +@test_reference joinpath( + @__DIR__, "../figures", - "equilibrium_temperature_distribution_with_mixed_boundary_conditions_and_using_ensembleproblems.png") fig #src + "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 39aa4fc..74b20aa 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 @@ -45,17 +45,21 @@ 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; +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) -v_prob = FVMProblem(mesh, v_BCs; + 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) + initial_condition = v_ic, final_time = 6000.0 +) prob = FVMSystem(u_prob, v_prob) # Now that we have our system, we can solve. @@ -74,8 +78,10 @@ x = LinRange(-1, 1, 200) y = LinRange(-1, 1, 200) 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 +record( + fig, joinpath(@__DIR__, "../figures", "gray_scott_patterns.mp4"), eachindex(sol); + framerate = 60 +) do _i i[] = _i end @@ -87,15 +93,19 @@ 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 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 + 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 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 + 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 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 735e9b5..df2600d 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 @@ -1,6 +1,6 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Helmholtz Equation with Inhomogeneous Boundary Conditions +# # Helmholtz Equation with Inhomogeneous Boundary Conditions # In this tutorial, we consider the following steady state problem: # ```math # \begin{equation} @@ -10,54 +10,56 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \end{aligned} # \end{equation} # ``` -# We can define this problem in the same way we have defined previous problems, +# 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. using DelaunayTriangulation, FiniteVolumeMethod 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, +# 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$. BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> -one(u), Neumann) -# To now define the problem, we note that the `initial_condition` and `final_time` -# fields have different interpretations for steady state problems. The +# To now define the problem, we note that the `initial_condition` and `final_time` +# fields have different interpretations for steady state problems. The # `initial_condition` now serves as an initial estimate for the steady state solution, -# 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 +# 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, +# \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 +# 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$. +# So, for this problem, $D = 1$ and $S = u$. diffusion_function = (x, y, t, u, p) -> one(u) source_function = (x, y, t, u, p) -> u initial_condition = zeros(DelaunayTriangulation.num_solid_vertices(tri)) final_time = Inf -prob = FVMProblem(mesh, BCs; +prob = FVMProblem( + mesh, BCs; diffusion_function, source_function, initial_condition, - final_time) + final_time +) #- steady_prob = SteadyFVMProblem(prob) -# To now solve this problem, we use a Newton-Raphson solver. Alternative solvers, -# such as `DynamicSS(TRBDF2(linsolve=KLUFactorization()), reltol=1e-4)` from -# SteadyStateDiffEq can also be used. A good method could be to use -# a simple solver, like `NewtonRaphson()`, and then use that solution -# as the initial guess in a finer algorithm like the `DynamicSS` +# To now solve this problem, we use a Newton-Raphson solver. Alternative solvers, +# such as `DynamicSS(TRBDF2(linsolve=KLUFactorization()), reltol=1e-4)` from +# SteadyStateDiffEq can also be used. A good method could be to use +# a simple solver, like `NewtonRaphson()`, and then use that solution +# as the initial guess in a finer algorithm like the `DynamicSS` # algorithm above. using NonlinearSolve sol = solve(steady_prob, NewtonRaphson()) @@ -74,7 +76,8 @@ using ReferenceTests #src 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 + @__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 @@ -98,5 +101,7 @@ 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 +@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 5519538..be12bd0 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 @@ -1,12 +1,12 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Laplace's Equation with Internal Dirichlet Conditions -# In this tutorial, we consider Laplace's equation with some additional complexity +# # 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} -# \grad^2 u &= 0 & \vb x \in [0, 1]^2, \\ +# \grad^2 u &= 0 & \vb x \in [0, 1]^2, \\ # u(0, y) &= 100y & 0 \leq y \leq 1, \\ # u(1, y) &= 100y & 0 \leq y \leq 1, \\ # u(x, 0) &= 0 & 0 \leq x \leq 1, \\ @@ -15,19 +15,19 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \end{aligned} # \end{equation} # ``` -# To start with solving this problem, let us define an initial mesh. +# 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) -# 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 +# 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 # constraint exactly.[^1] Instead, we need to add these points into `tri`. -# We do not need to add any constrained edges in this case, since these internal +# 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. +# [^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. +# Let us now add in the points. using CairoMakie new_points = LinRange(0, 2 / 5, 250) for y in new_points @@ -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 = 1.0e-4) fig, ax, sc = triplot(tri) fig @@ -45,10 +45,10 @@ fig mesh = FVMGeometry(tri) # Now that we have the mesh, we can define the boundary conditions. -# Remember that the order of the boundary indices is the bottom wall, -# right wall, top wall, and then the left wall. +# Remember that the order of the boundary indices is the bottom wall, +# right wall, top wall, and then the left wall. bc_bot = (x, y, t, u, p) -> zero(u) -bc_right = (x, y, t, u, p) -> oftype(u, 100y) # helpful to have each bc return the same type +bc_right = (x, y, t, u, p) -> oftype(u, 100y) # helpful to have each bc return the same type bc_top = (x, y, t, u, p) -> oftype(u, 100) bc_left = (x, y, t, u, p) -> oftype(u, 100y) bcs = (bc_bot, bc_right, bc_top, bc_left) @@ -56,10 +56,10 @@ types = (Dirichlet, Dirichlet, Dirichlet, Dirichlet) BCs = BoundaryConditions(mesh, bcs, types) # We now need to define the internal conditions. -# This is done using `InternalConditions`. First, -# we need to find all the vertices that lie on -# the line $\{x = 1/2, 0 \leq y \leq 2/5\}$. We could -# compute these manually, but let's find them programatically +# This is done using `InternalConditions`. First, +# we need to find all the vertices that lie on +# the line $\{x = 1/2, 0 \leq y \leq 2/5\}$. We could +# compute these manually, but let's find them programatically # instead for the sake of demonstration. function find_all_points_on_line(tri) vertices = Int[] @@ -78,40 +78,44 @@ scatter!(ax, points, color = :red, markersize = 10) fig # Now that we have the vertices, we can define the internal conditions. -# We need to provide `InternalConditions` with a `Dict` that maps -# each vertex in `vertices` to a function index that corresponds to the -# condition for that vertex. In this case, that function index +# We need to provide `InternalConditions` with a `Dict` that maps +# each vertex in `vertices` to a function index that corresponds to the +# 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)) +ICs = InternalConditions( + (x, y, t, u, p) -> zero(u), + dirichlet_nodes = Dict(vertices .=> 1) +) -# Now we can define the problem. As discussed in +# Now we can define the problem. As discussed in # the [Helmholtz tutorial](helmholtz_equation_with_inhomogeneous_boundary_conditions.md), -# we are looking to define a steady state problem, and so -# the initial condition needs to be a suitable initial guess of -# what the solution could be. Looking to the boundary and internal conditions, +# we are looking to define a steady state problem, and so +# the initial condition needs to be a suitable initial guess of +# what the solution could be. Looking to the boundary and internal conditions, # one suitable guess is $u(x, y) = 100y$ with $u(1/2, y) = 0$ for $0 \leq y \leq 2/5$; -# in fact, $u(x, y) = 100y$ is the solution of the problem without the internal condition. -# Let us now use this to define our initial condition. +# in fact, $u(x, y) = 100y$ is the solution of the problem without the internal condition. +# Let us now use this to define our initial condition. initial_condition = zeros(DelaunayTriangulation.num_points(tri)) for i in each_solid_vertex(tri) x, y = get_point(tri, i) initial_condition[i] = ifelse(x == 1 / 2 && 0 ≤ y ≤ 2 / 5, 0, 100y) end -# Now let's define the problem. The internal conditions are +# Now let's define the problem. The internal conditions are # provided as the third argument of `FVMProblem`. -diffusion_function = (x, y, t, u, p) -> one(u) # ∇²u = ∇⋅[D∇u], D = 1 +diffusion_function = (x, y, t, u, p) -> one(u) # ∇²u = ∇⋅[D∇u], D = 1 final_time = Inf -prob = FVMProblem(mesh, BCs, ICs; +prob = FVMProblem( + mesh, BCs, ICs; diffusion_function, initial_condition, - final_time) + final_time +) -#- +#- steady_prob = SteadyFVMProblem(prob) -# Now let's solve the problem. +# Now let's solve the problem. using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) sol |> tc #hide diff --git a/docs/src/literate_tutorials/mean_exit_time.jl b/docs/src/literate_tutorials/mean_exit_time.jl index 2df2126..3ca372d 100644 --- a/docs/src/literate_tutorials/mean_exit_time.jl +++ b/docs/src/literate_tutorials/mean_exit_time.jl @@ -1,83 +1,83 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Mean Exit Time +# # 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 +# ## 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} # D\grad^2T(\vb x) &= -1 & \vb x \in \Omega, \\ -# T(\vb x) &= 0 & \vb x \in \partial \Omega, +# 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)$, +# 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)$, # where $\delta > 0$ is the step length of the particle, $\tau>0$ is the duration between steps, and $\mathcal P \in [0, 1]$ # 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 +# 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$ +# +# 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 +# ```math # P = \begin{cases} P_1 & 0 (15, 80), :limit => true); #hide # \end{aligned} # \end{equation} # ``` -# This problem has no exact solution (it has a perturbation solution, though, +# This problem has no exact solution (it has a perturbation solution, though, # derived in [Carr et al. (2022)](https://iopscience.iop.org/article/10.1088/1751-8121/ac4a1d)). -# -# At the end of this tutorial, we also consider modifying \eqref{eq:met3} even further so that there are holes +# +# At the end of this tutorial, we also consider modifying \eqref{eq:met3} even further so that there are holes # in the domain, and an internal Dirichlet condition at the origin. # ## Unperturbed interface -# Let us start by solving the problem on an unperturbed interface. We note that, while \eqref{eq:met2} is defined -# so that there are two variables $T^{(1)}$ and $T^{(2)}$, which therefore requires continuity equations across -# the interface, numerically we can solve this in terms of a single variable $T$ with a space-varying diffusivity. Moreover, -# the finiteness condition at the origin is not needed. Thus, we can solve -# ```math +# Let us start by solving the problem on an unperturbed interface. We note that, while \eqref{eq:met2} is defined +# so that there are two variables $T^{(1)}$ and $T^{(2)}$, which therefore requires continuity equations across +# the interface, numerically we can solve this in terms of a single variable $T$ with a space-varying diffusivity. Moreover, +# the finiteness condition at the origin is not needed. Thus, we can solve +# ```math # \begin{equation} # \begin{aligned} # D(\vb x)\grad^2 T(\vb x) &= -1 & \vb x \in \mathcal D(0, R_2), \\ @@ -108,13 +108,13 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \end{aligned} # \end{equation} # ``` -# Here, $\mathcal D(0,R_2)$ is the circle of radius $R_2$ centred at the origin, and -# ```math +# Here, $\mathcal D(0,R_2)$ is the circle of radius $R_2$ centred at the origin, and +# ```math # D(\vb x) = \begin{cases} D_1 & \|\vb x\| < R_1, \\ D_2 & R_1 \leq \|\vb x\| \leq R_2. \end{cases} -# ``` -# The mesh is defined as follows. To help the accuracy of the solution, -# we add more triangles around the interior circle by putting some constrained -# edges there. +# ``` +# The mesh is defined as follows. To help the accuracy of the solution, +# we add more triangles around the interior circle by putting some constrained +# edges there. using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie R₁, R₂ = 2.0, 3.0 circle = CircularArc((0.0, R₂), (0.0, R₂), (0.0, 0.0)) @@ -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 = 1.0e-3get_area(tri)) triplot(tri) #- @@ -140,17 +140,17 @@ mesh = FVMGeometry(tri) # The boundary conditions are simple absorbing conditions. BCs = BoundaryConditions(mesh, ((x, y, t, u, p) -> zero(u),), (Dirichlet,)) -# For the problem, let us first define the diffusivity. +# For the problem, let us first define the diffusivity. 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₂) -# For the initial condition, which recall is the -# initial guess for the steady problem, let us use the +# For the initial condition, which recall is the +# initial guess for the steady problem, let us use the # exact solution for the mean exit time problem on a -# disk with a uniform diffusivity, which is given by +# disk with a uniform diffusivity, which is given by # $(R_2^2 - r^2)/(4D_2)$. f = (x, y) -> let r = sqrt(x^2 + y^2) return (R₂^2 - r^2) / (4D₂) @@ -158,12 +158,14 @@ end initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] initial_condition |> tc #hide -# We now define the problem. +# We now define the problem. source_function = (x, y, t, u, p) -> one(u) -prob = FVMProblem(mesh, BCs; +prob = FVMProblem( + mesh, BCs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time = Inf) + final_time = Inf +) #- steady_prob = SteadyFVMProblem(prob) @@ -194,8 +196,8 @@ tricontourf!(Axis(fig[1, 2]), tri, _T_exact, levels = 0:500:20000, extendhigh = @test_reference joinpath(@__DIR__, "../figures", "mean_exit_time_unperturbed_interface_exact_comparison.png") fig #src # ## Perturbed interface -# Let us now solve the problem with a perturbed interface. -# The mesh is defined as follows. +# Let us now solve the problem with a perturbed interface. +# The mesh is defined as follows. g = θ -> sin(3θ) + cos(5θ) ε = 0.05 R1_f = θ -> R₁ * (1 + ε * g(θ)) @@ -212,7 +214,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 = 1.0e-3get_area(tri)) triplot(tri) #- @@ -221,9 +223,9 @@ mesh = FVMGeometry(tri) # The boundary conditions are simple absorbing conditions. BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(u), Dirichlet) -# Now we define the problem. For the initial condition that we use, -# we will use the exact solution for the problem with an unperturbed -# interface. +# Now we define the problem. For the initial condition that we use, +# we will use the exact solution for the problem with an unperturbed +# interface. function T_exact(x, y) r = sqrt(x^2 + y^2) if r < R₁ @@ -239,10 +241,12 @@ end 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; +prob = FVMProblem( + mesh, BCs; diffusion_function, diffusion_parameters, source_function, initial_condition, - final_time = Inf) + final_time = Inf +) steady_prob = SteadyFVMProblem(prob) #- @@ -257,26 +261,30 @@ 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 -# ## 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 -# constructed from the initial `tri`. +# ## 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 +# constructed from the initial `tri`. -# The first obstacle we consider adding is a single hole at the origin, -# which we accomplish by adding a point at the origin and then -# using `InternalConditions`. This point will be used to absorb +# The first obstacle we consider adding is a single hole at the origin, +# which we accomplish by adding a point at the origin and then +# using `InternalConditions`. This point will be used to absorb # 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; +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) @@ -286,18 +294,22 @@ 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 -# We see that the hole has changed the interior significantly. For the -# next constraint, let us change the boundary so that -# we only allow particles to exit through a small part of the boundary, -# reflecting off all other parts. For the reflecting boundary condition, -# this is enforced by using Neumann boundary conditions. +# We see that the hole has changed the interior significantly. For the +# next constraint, let us change the boundary so that +# we only allow particles to exit through a small part of the boundary, +# 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)) + 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)) + R₂ * cos(ϵr), R₂ * sin(ϵr), + ), (0.0, 0.0) +) boundary_nodes = [[dirichlet_circle], [neumann_circle]] points = NTuple{2, Float64}[] tri = triangulate(points; boundary_nodes) @@ -313,7 +325,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 = 1.0e-3get_area(tri)) triplot(tri) #- @@ -322,10 +334,12 @@ 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)) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] -prob = FVMProblem(mesh, BCs, ICs; +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) @@ -334,10 +348,11 @@ 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 + @__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 +# 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) boundary_nodes = [[[dirichlet_circle], [neumann_circle]], [[hole]]] @@ -356,21 +371,24 @@ 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)) + DelaunayTriangulation.num_points(tri), DelaunayTriangulation.num_points(tri) - 1, +] +refine!(tri; max_area = 1.0e-3get_area(tri)) triplot(tri) -# The boundary condition we'll use at the new interior hole -# will be an absorbing boundary condition. +# The boundary condition we'll use at the new interior hole +# 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)) initial_condition = [T_exact(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] -prob = FVMProblem(mesh, BCs, ICs; +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) @@ -378,5 +396,7 @@ 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 +@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 d5f141e..b96426e 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 @@ -1,13 +1,13 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Piecewise Linear and Natural Neighbour Inteprolation for an Advection-Diffusion Equation +# # Piecewise Linear and Natural Neighbour Inteprolation for an Advection-Diffusion Equation # In this tutorial, we have three aims: # -# 1. Demonstrate how to solve an advection-diffusion equation. +# 1. Demonstrate how to solve an advection-diffusion equation. # 2. Demonstrate how piecewise linear interpolation can be applied to a PDE solution at each time. -# 3. Demonstrate how [NaturalNeighbours.jl](https://github.com/DanielVandH/NaturalNeighbours.jl) can be applied to compute more accurate interpolants than piecewise linear interpolation at each time. +# 3. Demonstrate how [NaturalNeighbours.jl](https://github.com/DanielVandH/NaturalNeighbours.jl) can be applied to compute more accurate interpolants than piecewise linear interpolation at each time. # -# The equation we will be considering is +# The equation we will be considering is # ```math # \begin{equation}\label{eq:advdiffeq} # \begin{aligned} @@ -15,21 +15,21 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \end{aligned} # \end{equation} # ``` -# with $u(\vb x, 0) = \delta(\vb x)$ and homogeneous Dirichlet conditions, -# where $\delta$ is the Dirac delta function. This equation is defined on -# $\mathbb R^2$, but we will replace $\mathbb R^2$ with $\Omega = [-L, L]^2$ +# with $u(\vb x, 0) = \delta(\vb x)$ and homogeneous Dirichlet conditions, +# where $\delta$ is the Dirac delta function. This equation is defined on +# $\mathbb R^2$, but we will replace $\mathbb R^2$ with $\Omega = [-L, L]^2$ # for $L = 30$. -# ## Solving the problem -# We start by defining and solving the problem associated -# with \eqref{eq:advdiffeq}. For the mesh, we could use -# `triangulate_rectangle`, but we want to put most of the triangles +# ## Solving the problem +# We start by defining and solving the problem associated +# with \eqref{eq:advdiffeq}. For the mesh, we could use +# `triangulate_rectangle`, but we want to put most of the triangles # 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) tot_area = get_area(tri) -max_area_function = (A, r) -> 1e-6tot_area * r^2 / A +max_area_function = (A, r) -> 1.0e-6tot_area * r^2 / A area_constraint = (_tri, T) -> begin u, v, w = triangle_vertices(T) p, q, r = get_point(_tri, u, v, w) @@ -45,16 +45,16 @@ triplot(tri) #- mesh = FVMGeometry(tri) -# The boundary conditions are homogeneous `Dirichlet` conditions. +# The boundary conditions are homogeneous `Dirichlet` conditions. 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 +# in the form # ```math # \pdv{u}{t} + \div\vb q = 0. # ``` # To do this, write: -# ```math +# ```math # \begin{align*} # \div \vb q &= \nu\pdv{u}{x} - D\pdv[2]{u}{x} - D\pdv[2]{u}{y} \\ # &= \pdv{x}\left(\nu u - D\pdv{u}{x}\right) - \pdv{y}\left(D\pdv{u}{y}\right) \\ @@ -64,14 +64,14 @@ BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(u), Dirichlet) # ``` # where $\boldsymbol\nu = (\nu, 0)^{\mkern-1.5mu\mathsf{T}}$. Thus, we can write # ```math -# \vb q = \boldsymbol\nu u - D\grad u. +# \vb q = \boldsymbol\nu u - D\grad u. # ``` -# We now have our flux function. Next, let us define the initial condition. -# We approximate by +# 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 +# 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$. ε = 1 / 10 @@ -87,11 +87,13 @@ flux_function = (x, y, t, α, β, γ, p) -> begin end flux_parameters = (D = 0.02, ν = 0.05) final_time = 250.0 -prob = FVMProblem(mesh, BCs; +prob = FVMProblem( + mesh, BCs; initial_condition, flux_function, flux_parameters, - final_time) + final_time +) # Now we can solve and visualise the solution. using OrdinaryDiffEq, LinearSolve @@ -104,20 +106,26 @@ using CairoMakie using ReferenceTests #src fig = Figure(fontsize = 38) for i in eachindex(sol) - ax = Axis(fig[1, i], width = 400, height = 400, + 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) + 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__, +@test_reference joinpath( + @__DIR__, "../figures", - "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation.png") fig #src + "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 @@ -146,40 +154,48 @@ 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 + 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, + tricontourf!( + ax, tri, - [exact_solution(x, y, _sol.t[j], flux_parameters.D, flux_parameters.ν) - for (x, y) in DelaunayTriangulation.each_point(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 + colormap = :matter + ) #src end #src resize_to_layout!(fig) #src fig #src -@test_reference joinpath(@__DIR__, +@test_reference joinpath( + @__DIR__, "../figures", - "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation_exact_comparisons.png") fig #src + "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 -# $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 -# by making use of `jump_and_march` from DelaunayTriangulation.jl to find the triangle containing a given point -# $(x, y)$ and then use `pl_interpolate` to interpolate the solution at the point; we do not provide a method -# that gets this triangle for you and then interpolates without this intermediate `jump_and_march`, -# as it is typically more efficient to first obtain all the triangles you need +# 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 +# by making use of `jump_and_march` from DelaunayTriangulation.jl to find the triangle containing a given point +# $(x, y)$ and then use `pl_interpolate` to interpolate the solution at the point; we do not provide a method +# that gets this triangle for you and then interpolates without this intermediate `jump_and_march`, +# 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. +# 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, +# amend the procedure so that you evaluate the interpolant at each time for a given point first, # 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) @@ -194,51 +210,58 @@ 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]) + prob, triangles[i, j], sol.u[k], x[i], y[j] + ) end end end -# Let's visualise these results to check their accuracy. We compute the triangulation of +# 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) for i in eachindex(sol) - ax = Axis(fig[1, i], width = 400, height = 400, + 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) + 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__, +@test_reference joinpath( + @__DIR__, "../figures", - "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation_piecewise_linear_interpolation.png") fig #src + "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, +# ## 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; -# NaturalNeighbours.jl also provides the same piecewise linear interpolant above via its -# `Triangle()` interpolator, which may be more efficient as it has multithreading built in. +# [NaturalNeighbours.jl](https://github.com/DanielVandH/NaturalNeighbours.jl) for this; +# NaturalNeighbours.jl also provides the same piecewise linear interpolant above via its +# `Triangle()` interpolator, which may be more efficient as it has multithreading built in. # -# The way to construct a natural neighbour interpolant is as follows, where we provide +# 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 sol |> tc #hide # We need `derivatives = true` so that we can use the higher order interpolants `Sibson(1)`, `Hiyoshi(2)`, -# and `Farin()` below - if you don't use those, then you shouldn't need this option (unless you +# and `Farin()` below - if you don't use those, then you shouldn't need this option (unless you # want to later differentiate the interpolant using `differentiate`, then yes you do need it). -# We can then evaluate this interpolant by simply calling it. The most efficient -# way to call it is by providing it with a vector of points, rather than broadcasting -# over points, since multithreading can be used in this case. Let us +# We can then evaluate this interpolant by simply calling it. The most efficient +# way to call it is by providing it with a vector of points, rather than broadcasting +# over points, since multithreading can be used in this case. Let us # interpolate at the grid from before, which requires us to collect it into a vector: _x = [x for x in x, _ in y] |> vec _y = [y for _ in x, y in y] |> vec; @@ -257,50 +280,67 @@ 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") +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)) + 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)) + mesh!( + ax3d, hcat(x, y, vals), triangles, color = vals, + colormap = :matter, colorrange = (0, 0.1) + ) end end fig -@test_reference joinpath(@__DIR__, +@test_reference joinpath( + @__DIR__, "../figures", - "piecewise_linear_and_natural_neighbour_interpolation_for_an_advection_diffusion_equation_natural_neighbour_interpolation.png") fig #src + "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 +# 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). 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 59d71e6..dfdb71d 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 @@ -1,7 +1,7 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Porous-Fisher Equation and Travelling Waves -# This tutorial considers a more involved example, where we discuss +# # 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*} @@ -15,36 +15,36 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \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 -# on the rectangle, and with zero flux conditions on the two vertical walls. -# 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 +# 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 +# on the rectangle, and with zero flux conditions on the two vertical walls. +# 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), +# \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 +# 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 +# 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 -# enough we can at least emulate the travelling wave behaviour closely; the -# homogeneous Neumann conditions are to ensure no energy is lost, thus allowing the travelling -# waves to exist. Moreover, note that the approximations of the solution with $u(y, t)$ -# in \eqref{eq:onedproblemexact} will only be accurate for large time as it -# takes the solution some time to evolve towards the travelling wave solution. +# 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 +# enough we can at least emulate the travelling wave behaviour closely; the +# homogeneous Neumann conditions are to ensure no energy is lost, thus allowing the travelling +# waves to exist. Moreover, note that the approximations of the solution with $u(y, t)$ +# in \eqref{eq:onedproblemexact} will only be accurate for large time as it +# takes the solution some time to evolve towards the travelling wave solution. # # Now with this preamble out of the way, let us solve this problem. using DelaunayTriangulation, FiniteVolumeMethod, OrdinaryDiffEq, LinearSolve @@ -64,15 +64,17 @@ diffusion_parameters = D source_parameters = λ final_time = 50.0 initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] -prob = FVMProblem(mesh, BCs; +prob = FVMProblem( + mesh, BCs; diffusion_function, diffusion_parameters, source_function, source_parameters, - initial_condition, final_time) + initial_condition, final_time +) 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 -# time, and also the travelling wave view of the solution. First, +# Let us now look at the travelling wave behaviour. We will plot the evolution over +# time, and also the travelling wave view of the solution. First, # let us get these travelling wave values. large_time_idx = findfirst(≥(10.0), sol.t) c = sqrt(λ / (2D)) @@ -97,21 +99,27 @@ end using CairoMakie 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, + ax = Axis( + fig[1, i], width = 600, height = 600, xlabel = "x", ylabel = "y", title = "t = $(sol.t[j])", - titlealign = :left) + 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)] +[ + 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 diff --git a/docs/src/literate_tutorials/porous_medium_equation.jl b/docs/src/literate_tutorials/porous_medium_equation.jl index 9b83d6f..54d6708 100644 --- a/docs/src/literate_tutorials/porous_medium_equation.jl +++ b/docs/src/literate_tutorials/porous_medium_equation.jl @@ -1,30 +1,30 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Porous-Medium Equation -# ## No source -# In this tutorial, we consider the porous-medium equation, given by +# # 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 +# 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 +# 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$. +# 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$. using DelaunayTriangulation, FiniteVolumeMethod @@ -34,46 +34,51 @@ M = 0.37 D = 2.53 final_time = 12.0 ε = 0.1 -## Step 1: Define the mesh +## 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) mesh = FVMGeometry(tri) #- -## Step 2: Define the boundary conditions +## Step 2: Define the boundary conditions BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> zero(u), Dirichlet) #- -## Step 3: Define the actual PDE +## Step 3: Define the actual PDE f = (x, y) -> M * 1 / (ε^2 * π) * exp(-1 / (ε^2) * (x^2 + y^2)) diffusion_function = (x, y, t, u, p) -> p[1] * u^(p[2] - 1) diffusion_parameters = (D, m) initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] -prob = FVMProblem(mesh, BCs; +prob = FVMProblem( + mesh, BCs; diffusion_function, diffusion_parameters, initial_condition, - final_time) + final_time +) #- -## Step 4: Solve +## Step 4: Solve using LinearSolve, OrdinaryDiffEq sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 3.0) sol |> tc #hide #- -## Step 5: Visualise +## Step 5: Visualise using CairoMakie using ReferenceTests #src fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 3, 5)) - ax = Axis(fig[1, i], width = 600, height = 600, + ax = Axis( + fig[1, i], width = 600, height = 600, xlabel = "x", ylabel = "y", title = "t = $(sol.t[j])", - titlealign = :left) + titlealign = :left + ) tricontourf!( - ax, tri, sol.u[j], levels = 0:0.005:0.05, colormap = :matter, extendhigh = :auto) + ax, tri, sol.u[j], levels = 0:0.005:0.05, colormap = :matter, extendhigh = :auto + ) tightlimits!(ax) end resize_to_layout!(fig) @@ -83,8 +88,10 @@ 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 + ( + (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 @@ -116,30 +123,30 @@ resize_to_layout!(fig) #src fig #src @test_reference joinpath(@__DIR__, "../figures", "porous_medium_equation_exact_comparisons.png") fig #src -# ## Linear source +# ## 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 +# 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 +# where # ```math # \tau(T) = \frac{D}{\lambda(m-1)}\left[\mathrm{e}^{\lambda(m-1)T}-1\right]. # ``` -# The code below solves this problem. +# The code below solves this problem. -## Step 0: Define all the parameters +## Step 0: Define all the parameters m = 3.4 M = 2.3 D = 0.581 λ = 0.2 final_time = 10.0 ε = 0.1 -## Step 1: Define the mesh +## 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) @@ -152,36 +159,42 @@ type = Dirichlet BCs = BoundaryConditions(mesh, bc, type) #- -## Step 3: Define the actual PDE +## Step 3: Define the actual PDE 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) source_parameters = λ initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] -prob = FVMProblem(mesh, BCs; +prob = FVMProblem( + mesh, BCs; diffusion_function, diffusion_parameters, source_function, source_parameters, initial_condition, - final_time) + final_time +) #- -## Step 4: Solve +## Step 4: Solve sol = solve(prob, TRBDF2(linsolve = KLUFactorization()); saveat = 2.5) sol |> tc #hide #- -## Step 5: Visualise +## Step 5: Visualise fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 3, 5)) - ax = Axis(fig[1, i], width = 600, height = 600, + 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) + 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) @@ -190,7 +203,7 @@ fig 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 + 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 @@ -209,11 +222,15 @@ x, y, u = compare_solutions(sol, tri, m, M, D, λ) #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 + 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 + 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 44de420..cd8c1b9 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 @@ -1,8 +1,8 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #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: +# # 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} @@ -11,7 +11,7 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \end{aligned} # \end{equation} # ``` -# Since this is a somewhat contrived example, we will be using the exact +# 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} @@ -22,7 +22,7 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \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 can use these exact solutions \eqref{eq:brusleexct} to also show how we can mix boundary conditions. # We use: # ```math # \begin{equation*} @@ -32,39 +32,39 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \pdv{\Phi}{y} &= -\exp(-x-t/2) & y = 0, \\[6pt] # \Psi &= \exp(x + t/2) & y = 0, \\ # \pdv{\Phi}{x} &= -\exp(-1-y-t/2) & x=1, \\[6pt] -# \pdv{\Psi}{x} &= \exp(1 + y+ t/2) & x=1, \\[6pt] +# \pdv{\Psi}{x} &= \exp(1 + y+ t/2) & x=1, \\[6pt] # \Phi &= \exp(-1-x-t/2) & y=1, \\ # \pdv{\Psi}{y} &= \exp(1 + x + t/2) & y=1, \\[6pt] # \pdv{\Phi}{x} &= -\exp(-y-t/2) & x=0, \\[6pt] -# \Psi &= \exp(y + t/2) & x=0. +# \Psi &= \exp(y + t/2) & x=0. # \end{aligned} # \end{equation*} # ``` -# For implementing these equations, we need to write the Neumann boundary conditions -# in the forms +# 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)$, # where $\vb q_1$ and $\vb q_2$ are the fluxes for $\Phi$ and $\Psi$, respectively. -# 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. +# 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 +# ```math # \begin{equation} -# \begin{aligned} +# \begin{aligned} # \pdv{\Phi}{t} + \div\vb q_1 &= S_1, \\ -# \pdv{\Psi}{t} + \div\vb q_2 &= S_2, -# \end{aligned} +# \pdv{\Psi}{t} + \div\vb q_2 &= S_2, +# \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, +# ``` +# 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*} @@ -74,11 +74,11 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \vb q_1 \vdot \vu n &= -\frac{1}{4}\exp(-x-t/2) & y = 0, \\[6pt] # \Psi &= \exp(x + t/2) & y = 0, \\ # \vb q_1 \vdot \vu n &= \frac{1}{4}\exp(-1-y-t/2) & x=1, \\[6pt] -# \vb q_2 \vdot \vu n &= -\frac{1}{4}\exp(1 + y+ t/2) & x=1, \\[6pt] +# \vb q_2 \vdot \vu n &= -\frac{1}{4}\exp(1 + y+ t/2) & x=1, \\[6pt] # \Phi &= \exp(-1-x-t/2) & y=1, \\ # \vb q_2 \vdot \vu n &= -\frac14\exp(1 + x + t/2) & y=1, \\[6pt] # \vb q_1 \vdot \vu n &= -\frac14\exp(-y-t/2) & x=0, \\[6pt] -# \Psi &= \exp(y + t/2) & x=0. +# \Psi &= \exp(y + t/2) & x=0. # \end{aligned} # \end{equation*} # ``` @@ -86,13 +86,13 @@ using FiniteVolumeMethod, DelaunayTriangulation 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, -# you need to define the boundary conditions for each variable separately. -# The signatures are the same, namely `(x, y, t, u, p) -> Number`, except now -# `u` is a vector (or `Tuple`) of the solution values for each variable instead -# of just a scalar. This last point is not relevant here, but you do need to know -# about it for other problems more generally. So, let us now define the -# boundary conditions. First, for $\Phi$: +# Now we define the boundary conditions. When considering a system of PDEs, +# you need to define the boundary conditions for each variable separately. +# The signatures are the same, namely `(x, y, t, u, p) -> Number`, except now +# `u` is a vector (or `Tuple`) of the solution values for each variable instead +# of just a scalar. This last point is not relevant here, but you do need to know +# about it for other problems more generally. So, let us now define the +# boundary conditions. First, for $\Phi$: Φ_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) Φ_top = (x, y, t, u, p) -> exp(-1 - x - t / 2) @@ -101,7 +101,7 @@ mesh = FVMGeometry(tri) Φ_bc_types = (Neumann, Neumann, Dirichlet, Neumann) Φ_BCs = BoundaryConditions(mesh, Φ_bc_fncs, Φ_bc_types) -# Now, for $\Psi$: +# Now, for $\Psi$: Ψ_bot = (x, y, t, u, p) -> exp(x + t / 2) Ψ_right = (x, y, t, u, p) -> -1 / 4 * exp(1 + y + t / 2) Ψ_top = (x, y, t, u, p) -> -1 / 4 * exp(1 + x + t / 2) @@ -110,58 +110,66 @@ mesh = FVMGeometry(tri) Ψ_bc_types = (Dirichlet, Neumann, Neumann, Dirichlet) Ψ_BCs = BoundaryConditions(mesh, Ψ_bc_fncs, Ψ_bc_types) -# Now we need to define the actual problems. Let us first define the flux -# and source functions, remembering that the variables get replaced with -# linear approximants. The flux functions also now take `Tuple`s for $\alpha$, -# $\beta$, and $\gamma$, where the $i$th element of the `Tuple` refers to the -# $i$th variable. Similarly, the source function takes a `Tuple` of the variables +# Now we need to define the actual problems. Let us first define the flux +# and source functions, remembering that the variables get replaced with +# linear approximants. The flux functions also now take `Tuple`s for $\alpha$, +# $\beta$, and $\gamma$, where the $i$th element of the `Tuple` refers to the +# $i$th variable. Similarly, the source function takes a `Tuple` of the variables # in the `u` argument. Φ_q = (x, y, t, α, β, γ, p) -> (-α[1] / 4, -β[1] / 4) Ψ_q = (x, y, t, α, β, γ, p) -> (-α[2] / 4, -β[2] / 4) Φ_S = (x, y, t, (Φ, Ψ), p) -> Φ^2 * Ψ - 2Φ Ψ_S = (x, y, t, (Φ, Ψ), p) -> -Φ^2 * Ψ + Φ -# Now we define the initial conditions. +# Now we define the initial conditions. Φ_exact = (x, y, t) -> exp(-x - y - t / 2) Ψ_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)]; -# 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) +# 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 +) -# Finally, the `FVMSystem` is constructed by these two problems: +# 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. +# 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 |> tc #hide -# For this solution, note that the `u` values are matrices. For example: +# For this solution, note that the `u` values are matrices. For example: sol.u[3] sol.u[3] |> tc #hide -# The `i`th row is the `i`th variable, so +# The `i`th row is the `i`th variable, so sol.u[3][1, :] sol.u[3][1, :] |> tc #hide -# are the value of $\Phi$ at the third time, and similarly `sol.u[3][2, :]` +# 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) for i in eachindex(sol) - ax1 = Axis(fig[1, i], xlabel = L"x", ylabel = L"y", + 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", + 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) + 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 @@ -173,16 +181,21 @@ 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 + 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 + 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 + 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 + @__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 9aaa35f..14b9d65 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 @@ -1,7 +1,7 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Reaction-Diffusion Equation with a Time-dependent Dirichlet Boundary Condition on a Disk -# In this tutorial, we consider a reaction-diffusion equation +# # 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*} @@ -12,9 +12,9 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \end{aligned} # \end{equation*} # ``` -# where $I_0$ is the modified Bessel function of the first kind of order zero. -# For this problem the diffusion function is $D(\vb x, t, u) = u$ and the source function -# is $R(\vb x, t, u) = u(1-u)$, or equivalently the force function is +# where $I_0$ is the modified Bessel function of the first kind of order zero. +# For this problem the diffusion function is $D(\vb x, t, u) = u$ and the source function +# is $R(\vb x, t, u) = u(1-u)$, or equivalently the force function is # ```math # \vb q(\vb x, t, \alpha,\beta,\gamma) = \left(-\alpha(\alpha x + \beta y + \gamma), -\beta(\alpha x + \beta y + \gamma)\right)^{\mkern-1.5mu\mathsf{T}}. # ``` @@ -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 = 1.0e-4A) mesh = FVMGeometry(tri) #- @@ -42,14 +42,16 @@ f = (x, y) -> sqrt(besseli(0.0, sqrt(2) * sqrt(x^2 + y^2))) D = (x, y, t, u, p) -> u 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; +final_time = 0.1 +prob = FVMProblem( + mesh, BCs; diffusion_function = D, source_function = R, final_time, - initial_condition) + initial_condition +) -# We can now solve. +# We can now solve. using OrdinaryDiffEq, LinearSolve alg = FBDF(linsolve = UMFPACKFactorization(), autodiff = false) sol = solve(prob, alg, saveat = 0.01) @@ -59,18 +61,22 @@ sol |> tc #hide using ReferenceTests #src fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) - ax = Axis(fig[1, i], width = 600, height = 600, + ax = Axis( + fig[1, i], width = 600, height = 600, xlabel = "x", ylabel = "y", title = "t = $(sol.t[j])", - titlealign = :left) + 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__, +@test_reference joinpath( + @__DIR__, "../figures", - "reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk.png") fig #src + "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 @@ -101,6 +107,8 @@ for i in eachindex(sol) #src end #src resize_to_layout!(fig) #src fig #src -@test_reference joinpath(@__DIR__, +@test_reference joinpath( + @__DIR__, "../figures", - "reaction_diffusion_equation_with_a_time_dependent_dirichlet_boundary_condition_on_a_disk_exact_comparisons.png") fig #src + "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 3cd9b3e..535987d 100644 --- a/docs/src/literate_tutorials/solving_mazes_with_laplaces_equation.jl +++ b/docs/src/literate_tutorials/solving_mazes_with_laplaces_equation.jl @@ -1,11 +1,11 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #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$, +# # 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} @@ -17,11 +17,11 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \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 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. using DelaunayTriangulation, CairoMakie, DelimitedFiles A = readdlm(joinpath(@__DIR__, "../tutorials/maze.txt")) @@ -46,14 +46,16 @@ boundary_nodes, points = convert_boundary_points_to_indices(x_bnd, y_bnd) tri = triangulate(points; boundary_nodes) # takes a while because maze.txt contains so many points refine!(tri) -fig, ax, sc, = triplot(tri, +fig, ax, sc, = triplot( + tri, show_convex_hull = false, - show_constrained_edges = 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. +# Now we can solve the problem. using FiniteVolumeMethod, StableRNGs mesh = FVMGeometry(tri) start_bc = (x, y, t, u, p) -> zero(u) @@ -66,10 +68,12 @@ BCs = BoundaryConditions(mesh, fncs, types) 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; +prob = FVMProblem( + mesh, BCs; diffusion_function = diffusion_function, initial_condition = initial_condition, - final_time = final_time) + final_time = final_time +) steady_prob = SteadyFVMProblem(prob) #- @@ -77,12 +81,12 @@ using SteadyStateDiffEq, LinearSolve, OrdinaryDiffEq sol = solve(steady_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization(), autodiff = false))) sol |> tc #hide -# We now have our solution. +# We now have our solution. 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. +# 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) ∇ = NaturalNeighbours.get_gradient(itp) @@ -91,14 +95,18 @@ tricontourf(tri, ∇norms, colormap = :matter) # The solution to the maze is now extremely clear from this plot! -# An alternative way to look at this solution is to -# consider the transient problem, where we do not solve the -# steady state problem and instead view the solution over time. +# An alternative way to look at this solution is to +# consider the transient problem, where we do not solve the +# steady state problem and instead view the solution over time. using Accessors -prob = @set prob.final_time = 1e8 +prob = @set prob.final_time = 1.0e8 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( + 1.0e2, prob.final_time, 24 * + 10 + ) +) all_∇norms = map(sol.u) do u itp = interpolate(tri, u; derivatives = true) ∇ = NaturalNeighbours.get_gradient(itp) @@ -107,12 +115,16 @@ 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) + 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 +record( + fig, joinpath(@__DIR__, "../figures", "maze_solution_1.mp4"), eachindex(sol); + framerate = 24 +) do _i i[] = _i end; # ![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 b29ac1c..77e6f1f 100644 --- a/docs/src/literate_wyos/diffusion_equations.jl +++ b/docs/src/literate_wyos/diffusion_equations.jl @@ -1,14 +1,14 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Diffusion Equations -# ```@contents +# # Diffusion Equations +# ```@contents # Pages = ["diffusion_equations.md"] # ``` -# We start by writing a specialised solver for solving diffusion equations. What we produce +# 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 +# ## Mathematical Details +# Let us start by considering the mathematical details. The equation we consider is # ```math # \begin{equation} # \begin{aligned} @@ -27,13 +27,13 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \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, +# ```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 +# 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} # \alpha_{k(\sigma)} = s_{k(\sigma), 11}u_{k(\sigma)1} + s_{k(\sigma), 12}u_{k(\sigma)2} + s_{k(\sigma), 13}u_{k(\sigma)3}, \\ @@ -42,8 +42,8 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \end{equation} # ``` # thus, now writing $k=k(\sigma)$ for simplicity, -# ```math -# \begin{equation*} +# ```math +# \begin{equation*} # \begin{aligned} # \dv{u_i}{t} &= \frac{1}{V_i}\sum_{\sigma\in \mathcal E_i} D(\vb x_\sigma)\left[\left(s_{k, 11}u_{k1} + s_{k, 12}u_{k2} + s_{k,13}u_{k3}\right)n_\sigma^x + \left(s_{k, 21}u_{k1} + s_{k, 22}u_{k2} + s_{k, 23}u_{k3}\right)n_\sigma^y\right]L_\sigma \\ # &= \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 \\ @@ -51,14 +51,14 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # \end{aligned} # \end{equation*} # ``` -# Now, the result +# 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 +# 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). @@ -66,49 +66,50 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # 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 +# 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 +# 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 +# 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 +# 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, +# function diffusion_equation(mesh::FVMGeometry, # BCs::BoundaryConditions, # ICs::InternalConditions=InternalConditions(); -# diffusion_function, -# diffusion_parameters=nothing, -# initial_condition, +# 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`, +# 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`, # or simply a `Number`. -# -# We need to first write a function that will construct $(\vb A, \vb b)$. -# The idea for this is to loop over each triangle and then pick up the contributions, -# and then over all the boundary edges, just as we describe in the [mathematical details section](../math.md). The main difference -# being that, rather than adding terms to $\mathrm du_i/\mathrm dt$, we are picking out +# +# We need to first write a function that will construct $(\vb A, \vb b)$. +# The idea for this is to loop over each triangle and then pick up the contributions, +# and then over all the boundary edges, just as we describe in the [mathematical details section](../math.md). The main difference +# being that, rather than adding terms to $\mathrm du_i/\mathrm dt$, we are picking out # terms for $b_i$ and also to put into $\vb A$. # 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) + A, mesh, conditions, diffusion_function, diffusion_parameters + ) for T in each_solid_triangle(mesh.triangulation) ijk = triangle_vertices(T) i, j, k = ijk @@ -118,9 +119,11 @@ function triangle_contributions!( x, y, nx, ny, ℓ = FVM.get_cv_components(props, edge_index) D = diffusion_function(x, y, diffusion_parameters) Dℓ = D * ℓ - a123 = (Dℓ * (s₁₁ * nx + s₂₁ * ny), + a123 = ( + Dℓ * (s₁₁ * nx + s₂₁ * ny), Dℓ * (s₁₂ * nx + s₂₂ * ny), - Dℓ * (s₁₃ * nx + s₂₃ * ny)) + Dℓ * (s₁₃ * nx + s₂₃ * ny), + ) e1_hascond = FVM.has_condition(conditions, e1) e2_hascond = FVM.has_condition(conditions, e2) for vert in 1:3 @@ -129,11 +132,14 @@ function triangle_contributions!( end end end + return 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) +function boundary_edge_contributions!( + A, b, mesh, conditions, + 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) @@ -150,67 +156,78 @@ function boundary_edge_contributions!(A, b, mesh, conditions, i_hascond || (b[i] += Dᵢ * aᵢ * ℓ / FVM.get_volume(mesh, i)) j_hascond || (b[j] += Dⱼ * aⱼ * ℓ / FVM.get_volume(mesh, j)) else - aᵢ123 = (Dᵢ * ℓ * (s₁₁ * nx + s₂₁ * ny), + aᵢ123 = ( + Dᵢ * ℓ * (s₁₁ * nx + s₂₁ * ny), Dᵢ * ℓ * (s₁₂ * nx + s₂₂ * ny), - Dᵢ * ℓ * (s₁₃ * nx + s₂₃ * ny)) - aⱼ123 = (Dⱼ * ℓ * (s₁₁ * nx + s₂₁ * ny), + Dᵢ * ℓ * (s₁₃ * nx + s₂₃ * ny), + ) + aⱼ123 = ( + Dⱼ * ℓ * (s₁₁ * nx + s₂₁ * ny), Dⱼ * ℓ * (s₁₂ * nx + s₂₂ * ny), - Dⱼ * ℓ * (s₁₃ * nx + s₂₃ * ny)) + Dⱼ * ℓ * (s₁₃ * nx + s₂₃ * ny), + ) for vert in 1:3 i_hascond || (A[i, ijk[vert]] += aᵢ123[vert] / FVM.get_volume(mesh, i)) j_hascond || (A[j, ijk[vert]] += aⱼ123[vert] / FVM.get_volume(mesh, i)) end end end + return end -# Now that we have the parts for handling the main flux contributions, we need to consider -# the boundary conditions. Note that in the code above we have alredy taken not to update -# $\vb A$ or $\vb b$ if there a boundary condition at the associated node, so we do not -# need to worry about e.g. zeroing out rows of $\vb A$ for a node with a boundary condition. +# Now that we have the parts for handling the main flux contributions, we need to consider +# the boundary conditions. Note that in the code above we have alredy taken not to update +# $\vb A$ or $\vb b$ if there a boundary condition at the associated node, so we do not +# need to worry about e.g. zeroing out rows of $\vb A$ for a node with a boundary condition. 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) + conditions, function_index, x, y, nothing, nothing + ) end + return 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) + conditions, function_index, x, y, nothing, nothing + ) end end + return 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 +# 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 +# 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 +# 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. -function diffusion_equation(mesh::FVMGeometry, +function diffusion_equation( + mesh::FVMGeometry, BCs::BoundaryConditions, ICs::InternalConditions = InternalConditions(); diffusion_function, diffusion_parameters = nothing, initial_condition, initial_time = 0.0, - final_time) + final_time + ) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) @@ -219,7 +236,8 @@ function diffusion_equation(mesh::FVMGeometry, _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) + 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)) @@ -227,31 +245,35 @@ function diffusion_equation(mesh::FVMGeometry, return prob end -# Let's now test the function. We use the same problem as in [this tutorial](../tutorials/diffusion_equation_on_a_square_plate.md). +# 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) 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; +prob = diffusion_equation( + mesh, BCs; diffusion_function, initial_condition, - final_time) + final_time +) 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.) -# Note also that `sol` contains an extra component: +# Note also that `sol` contains an extra component: length(sol.u[1]) #- DelaunayTriangulation.num_solid_vertices(tri) -# This is because we needed to add in an extra component to represent the problem as a linear problem. -# So, the solution is in `sol[begin:end-1, :]`, and you should ignore `sol[end, :]`. (The same applies to +# This is because we needed to add in an extra component to represent the problem as a linear problem. +# So, the solution is in `sol[begin:end-1, :]`, and you should ignore `sol[end, :]`. (The same applies to # `DiffusionEquation` that we introduce later.) # Let's now plot. @@ -259,13 +281,17 @@ DelaunayTriangulation.num_solid_vertices(tri) using CairoMakie fig = Figure(fontsize = 38) for (i, j) in zip(1:3, (1, 6, 11)) - ax = Axis(fig[1, i], width = 600, height = 600, + ax = Axis( + fig[1, i], width = 600, height = 600, xlabel = "x", ylabel = "y", title = "t = $(sol.t[j])", - titlealign = :left) + 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) @@ -273,27 +299,31 @@ fig using ReferenceTests #src @test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_on_a_square_plate.png") fig by = psnr_equality(23) #src -# This is exactly the solution we expect! +# This is exactly the solution we expect! -# ## Using the Provided Template +# ## Using the Provided Template # Let's now use the built-in `DiffusionEquation()` which implements the above template inside FiniteVolumeMethod.jl. -diff_eq = DiffusionEquation(mesh, BCs; +diff_eq = DiffusionEquation( + mesh, BCs; diffusion_function, initial_condition, - final_time) + final_time +) -# Let's compare `DiffusionEquation` to the `FVMProblem` approach. -fvm_prob = FVMProblem(mesh, BCs; +# Let's compare `DiffusionEquation` to the `FVMProblem` approach. +fvm_prob = FVMProblem( + mesh, BCs; diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, initial_condition, - final_time) + final_time +) using LinearSolve #src # ````julia -# using BenchmarkTools +# using BenchmarkTools # @btime solve($diff_eq, $Tsit5(), saveat=$0.05); # ```` @@ -317,7 +347,7 @@ sol2 = solve(fvm_prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.05) #sr using Test #src # To finish this example, let's solve a diffusion equation with constant Neumann boundary conditions: -# ```math +# ```math # \begin{equation*} # \begin{aligned} # \pdv{u}{t} &= 2\grad^2 u & \vb x \in \Omega, \\ @@ -340,10 +370,12 @@ initf = (x, y) -> begin end final_time = 500.0 initial_condition = [initf(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] -prob = DiffusionEquation(mesh, BCs; +prob = DiffusionEquation( + mesh, BCs; diffusion_function, initial_condition, - final_time) + final_time +) # Let's solve and plot. sol = solve(prob, Tsit5(); saveat = 100.0) @@ -352,40 +384,50 @@ sol |> tc #hide #- fig = Figure(fontsize = 38) for j in eachindex(sol) - ax = Axis(fig[1, j], width = 600, height = 600, + ax = Axis( + fig[1, j], width = 600, height = 600, xlabel = "x", ylabel = "y", title = "t = $(sol.t[j])", - titlealign = :left) + 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) fig -# For the corresponding `FVMProblem`, note that the Neumann boundary conditions need to be -# defined in terms of $\vb q = -D(\vb x)\grad u$ rather than $\grad u \vdot \vu n$. So, -# since $\grad u \vdot \vu n = 2$, we have $-D\grad u \vdot \vu n = -2D = -4$, so +# For the corresponding `FVMProblem`, note that the Neumann boundary conditions need to be +# defined in terms of $\vb q = -D(\vb x)\grad u$ rather than $\grad u \vdot \vu n$. So, +# since $\grad u \vdot \vu n = 2$, we have $-D\grad u \vdot \vu n = -2D = -4$, so # $\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; +fvm_prob = FVMProblem( + mesh, BCs_prob; diffusion_function = let D = diffusion_function (x, y, t, u, p) -> D(x, y, p) end, initial_condition, - final_time) + final_time +) 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, + ax = Axis( + fig[2, j], width = 600, height = 600, xlabel = "x", ylabel = "y", title = "t = $(fvm_sol.t[j])", - titlealign = :left) + 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) @@ -393,7 +435,7 @@ fig @test_reference joinpath(@__DIR__, "../figures", "diffusion_equation_template_1.png") fig #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 +@test u_template ≈ u_fvm rtol = 1.0e-3 #src # Here is a benchmark comparison. # ````julia @@ -419,4 +461,4 @@ 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 + pl_interpolate(fvm_prob, T, fvm_sol.u[3], q[1], q[2]) rtol = 1.0e-3 #src diff --git a/docs/src/literate_wyos/laplaces_equation.jl b/docs/src/literate_wyos/laplaces_equation.jl index d94ae25..6ed1c00 100644 --- a/docs/src/literate_wyos/laplaces_equation.jl +++ b/docs/src/literate_wyos/laplaces_equation.jl @@ -1,42 +1,46 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Laplace's Equation -# ```@contents +# # Laplace's Equation +# ```@contents # Pages = ["laplaces_equation.md"] -# ``` -# Now we consider Laplace's equation. What we produce in this +# ``` +# 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 +# ## 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 +# ## 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`. using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod -function laplaces_equation(mesh::FVMGeometry, +function laplaces_equation( + mesh::FVMGeometry, BCs::BoundaryConditions, ICs::InternalConditions = InternalConditions(); diffusion_function = (x, y, p) -> 1.0, - diffusion_parameters = nothing) + 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) + A, mesh, conditions, diffusion_function, diffusion_parameters + ) FVM.boundary_edge_contributions!( - A, b, mesh, conditions, diffusion_function, diffusion_parameters) + 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) @@ -44,9 +48,9 @@ function laplaces_equation(mesh::FVMGeometry, return prob end -# Now let's test this problem. We consider Laplace's equation on a sector of an annulus, -# so that[^2] -# ```math +# 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} # \grad^2 u &= 1 < r > 2,\,0 < \theta < \pi/2, \\ @@ -57,11 +61,11 @@ end # \end{aligned} # \end{equation*} # ``` -# +# # [^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. +# To start, we define our mesh. We need to define each part of the annulus separately, which +# takes some care. using CairoMakie lower_x = [1.0, 2.0] lower_y = [0.0, 0.0] @@ -72,19 +76,19 @@ outer_arc_y = 2sin.(θ) left_x = [0.0, 0.0] left_y = [2.0, 1.0] inner_arc_x = cos.(θ) |> reverse! -inner_arc_x[begin] = 0.0 # must match with left_x +inner_arc_x[begin] = 0.0 # must match with left_x inner_arc_y = sin.(θ) |> reverse! 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 = 1.0e-3get_area(tri)) triplot(tri) #- mesh = FVMGeometry(tri) -# The boundary conditions are defined as follows. +# The boundary conditions are defined as follows. lower_f = (x, y, t, u, p) -> 0.0 outer_arc_f = (x, y, t, u, p) -> (π / 2 - atan(y, x)) * atan(y, x) left_f = (x, y, t, u, p) -> 2x * y @@ -111,10 +115,14 @@ fig # We can turn this type of problem into its corresponding `SteadyFVMProblem` as follows: 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, - initial_condition, - final_time = Inf)) +fvm_prob = SteadyFVMProblem( + FVMProblem( + mesh, BCs; + diffusion_function = (x, y, t, u, p) -> 1.0, + initial_condition, + final_time = Inf + ) +) #- using SteadyStateDiffEq, OrdinaryDiffEq @@ -129,15 +137,15 @@ fig using ReferenceTests #src @test_reference joinpath(@__DIR__, "../figures", "laplaces_equation_template_1.png") fig #src using Test #src -@test sol.u ≈ fvm_sol.u rtol = 1e-5 #src +@test sol.u ≈ fvm_sol.u rtol = 1.0e-5 #src -# ## Using the Provided Template -# Let's now use the built-in `LaplacesEquation` which implements the above +# ## 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 +# ```math # \begin{equation*} # \begin{aligned} # \div\left[D(\vb x)\grad u\right] &= 0,\,0 < x < 5,\, 0 < y < 5, \\ @@ -146,16 +154,16 @@ using Test #src # \grad u \vdot \vu n &= 0 & 0 < x < 5,\, y \in \{0, 5\}, # \end{aligned} # \end{equation*} -# ``` -# -# where $D(\vb x) = (x+1)(y+2)$. The exact solution +# ``` +# +# 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) mesh = FVMGeometry(tri) zero_f = (x, y, t, u, p) -> 0.0 five_f = (x, y, t, u, p) -> 5.0 bc_f = (zero_f, five_f, zero_f, zero_f) -bc_types = (Neumann, Dirichlet, Neumann, Dirichlet) # bottom, right, top, left +bc_types = (Neumann, Dirichlet, Neumann, Dirichlet) # bottom, right, top, left BCs = BoundaryConditions(mesh, bc_f, bc_types) diffusion_function = (x, y, p) -> (x + 1) * (y + 2) prob = LaplacesEquation(mesh, BCs; diffusion_function) @@ -166,28 +174,36 @@ sol |> tc #hide #- fig = Figure(fontsize = 33) -ax = Axis(fig[1, 1], xlabel = "x", ylabel = "y", +ax = Axis( + fig[1, 1], xlabel = "x", ylabel = "y", width = 600, height = 600, - title = "Numerical", titlealign = :left) + 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", +ax = Axis( + fig[1, 2], xlabel = "x", ylabel = "y", width = 600, height = 600, - title = "Exact", titlealign = :left) + 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) resize_to_layout!(fig) fig @test_reference joinpath(@__DIR__, "../figures", "laplaces_equation_template_2.png") fig #src -@test sol.u ≈ u_exact rtol = 1e-3 #src +@test sol.u ≈ u_exact rtol = 1.0e-3 #src -# To finish, here is a benchmark comparing this problem to the corresponding +# To finish, here is a benchmark comparing this problem to the corresponding # `SteadyFVMProblem`. 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, - initial_condition)) +fvm_prob = SteadyFVMProblem( + FVMProblem( + mesh, BCs; + diffusion_function = (x, y, t, u, p) -> (x + 1) * (y + 2), + final_time = Inf, + initial_condition + ) +) # ````julia # using BenchmarkTools @@ -207,4 +223,4 @@ fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs; # ```` fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) #src -@test sol.u ≈ fvm_sol.u rtol = 1e-5 #src +@test sol.u ≈ fvm_sol.u rtol = 1.0e-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 c128d5c..87762b9 100644 --- a/docs/src/literate_wyos/linear_reaction_diffusion_equations.jl +++ b/docs/src/literate_wyos/linear_reaction_diffusion_equations.jl @@ -1,39 +1,42 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Linear Reaction-Diffusion Equations +# # Linear Reaction-Diffusion Equations # ```@contents # Pages = ["linear_reaction_diffusion_equations.md"] # ``` -# Next, we write a specialised solver for solving linear reaction-diffusion equations. What +# 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 +# ## 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$ +# 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. +# 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 +# ## 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 -# `boundary_edge_contributions!` from the diffusion equation example. Here is our implementation. +# We can also reuse `apply_dirichlet_conditions!`, `apply_dudt_conditions`, and +# `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) + 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) A[i, i] += source_function(x, y, source_parameters) end end + return end -function linear_reaction_diffusion_equation(mesh::FVMGeometry, +function linear_reaction_diffusion_equation( + mesh::FVMGeometry, BCs::BoundaryConditions, ICs::InternalConditions = InternalConditions(); diffusion_function, @@ -42,7 +45,8 @@ function linear_reaction_diffusion_equation(mesh::FVMGeometry, source_parameters = nothing, initial_condition, initial_time = 0.0, - final_time) + final_time + ) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) @@ -50,9 +54,11 @@ function linear_reaction_diffusion_equation(mesh::FVMGeometry, b = @views Afull[begin:(end - 1), end] _ic = vcat(initial_condition, 1) FVM.triangle_contributions!( - A, mesh, conditions, diffusion_function, diffusion_parameters) + A, mesh, conditions, diffusion_function, diffusion_parameters + ) FVM.boundary_edge_contributions!( - A, b, mesh, conditions, diffusion_function, diffusion_parameters) + 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) @@ -62,13 +68,13 @@ function linear_reaction_diffusion_equation(mesh::FVMGeometry, return prob end -# If you go and look back at the `diffusion_equation` function from the +# If you go and look back at the `diffusion_equation` function from the # [diffusion equation example](diffusion_equations.md), you will see that # this is essentially the same function except we now have `linear_source_contributions!` # and `source_function` and `source_parameters` arguments. # Let's now test this function. We consider the problem -# ```math +# ```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. @@ -77,13 +83,15 @@ 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 = 1.0e-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 -prob = linear_reaction_diffusion_equation(mesh, BCs; +prob = linear_reaction_diffusion_equation( + mesh, BCs; diffusion_function, diffusion_parameters, - source_function, initial_condition, final_time) + source_function, initial_condition, final_time +) prob |> tc #hide #- @@ -94,30 +102,36 @@ sol |> tc #hide using CairoMakie fig = Figure(fontsize = 38) for j in eachindex(sol) - ax = Axis(fig[1, j], width = 600, height = 600, + 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) + 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 -# Here is how we could convert this into an `FVMProblem`. Note that the Neumann -# boundary conditions are expressed as $\grad T\vdot\vu n = 1$ above, but for `FVMProblem` +# Here is how we could convert this into an `FVMProblem`. Note that the Neumann +# boundary conditions are expressed as $\grad T\vdot\vu n = 1$ above, but for `FVMProblem` # 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)) +_BCs = BoundaryConditions( + mesh, (x, y, t, u, p) -> -p.D(x, y, p.Dp), Neumann; + 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 + source_function = let S = source_function (x, y, t, u, p) -> S(x, y, p) * u end, final_time = final_time, @@ -127,31 +141,37 @@ 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 + 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 + 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 using ReferenceTests #src @test_reference joinpath(@__DIR__, "../figures", "linear_reaction_diffusion_equation_template_1.png") fig #src -# ## Using the Provided Template -# The above code is implemented in `LinearReactionDiffusionEquation` in FiniteVolumeMethod.jl. -prob = LinearReactionDiffusionEquation(mesh, BCs; +# ## Using the Provided Template +# The above code is implemented in `LinearReactionDiffusionEquation` in FiniteVolumeMethod.jl. +prob = LinearReactionDiffusionEquation( + mesh, BCs; diffusion_function, diffusion_parameters, - source_function, initial_condition, final_time) + 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 = 1.0e-1 #src # Here is a benchmark comparison of `LinearReactionDiffusionEquation` versus `FVMProblem`. # ````julia -# using BenchmarkTools +# using BenchmarkTools # using Sundials # @btime solve($prob, $CVODE_BDF(linear_solver=:GMRES); saveat=$2); # ```` diff --git a/docs/src/literate_wyos/mean_exit_time.jl b/docs/src/literate_wyos/mean_exit_time.jl index d565737..a95ad68 100644 --- a/docs/src/literate_wyos/mean_exit_time.jl +++ b/docs/src/literate_wyos/mean_exit_time.jl @@ -4,46 +4,46 @@ tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide # ```@contents # Pages = ["mean_exit_time.md"] # ``` -# We now write a specialised solver for solving mean exit time problems. What +# 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 +# ## 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, +# \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 +# with homogeneous Neumann or Dirichlet conditions on parts +# of the boundary; homogeneous Neumann conditions represent # reflecting parts of the boundary, while homogeneous Dirichlet # conditions represent absorbing parts of the boundary. -# -# The mathematical details for this section are similar to those from the diffusion equation +# +# The mathematical details for this section are similar to those from the diffusion equation # 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 +# 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 +# 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 +# ```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 +# 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 +# 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 # the Dirichlet boundary conditions, we need to know that -# `FiniteVolumeMethod.triangle_contributions!` does not change $\vb A$ -# for nodes with conditions. For this problem, though, we need $a_{ii} = 1$ for -# Dirichlet nodes $i$. So, let's write a function that creates $\vb b$ but also +# `FiniteVolumeMethod.triangle_contributions!` does not change $\vb A$ +# for nodes with conditions. For this problem, though, we need $a_{ii} = 1$ for +# Dirichlet nodes $i$. So, let's write a function that creates $\vb b$ but also # enforces Dirichlet constraints. function create_met_b!(A, mesh, conditions) b = zeros(DelaunayTriangulation.num_points(mesh.triangulation)) @@ -57,28 +57,31 @@ function create_met_b!(A, mesh, conditions) return b end -# Let us now define the function which gives us our matrices $\vb A$ and $\vb b$. We will +# Let us now define the function which gives us our matrices $\vb A$ and $\vb b$. We will # return the problem as a `LinearProblem` from LinearSolve.jl. using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod -function met_problem(mesh::FVMGeometry, +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) + 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) + 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) end -# Now let us test this problem. To test, we will consider the last -# problem [here](../tutorials/mean_exit_time.md) which -# includes mixed boundary conditions and also an internal condition. +# Now let us test this problem. To test, we will consider the last +# problem [here](../tutorials/mean_exit_time.md) which +# includes mixed boundary conditions and also an internal condition. ## Define the triangulation R₁, R₂ = 2.0, 3.0 ε = 0.05 @@ -89,9 +92,14 @@ 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)) + 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)] @@ -108,8 +116,8 @@ end n = DelaunayTriangulation.num_points(tri) add_segment!(tri, n - 1, n) pointhole_idxs = [1, 2] -refine!(tri; max_area = 1e-3get_area(tri)); -## Define the problem +refine!(tri; max_area = 1.0e-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)) @@ -125,7 +133,7 @@ diffusion_parameters = (D₁ = D₁, D₂ = D₂, R1_f = R1_f) prob = met_problem(mesh, BCs, ICs; diffusion_function, diffusion_parameters) prob |> tc #hide -# This problem can now be solved using the `solve` interface from LinearSolve.jl. Note that the matrix +# This problem can now be solved using the `solve` interface from LinearSolve.jl. Note that the matrix # $\vb A$ is very dense, but there is no structure to it: prob.A prob.A |> DisplayAs.withcontext(:compact => true) #hide @@ -134,11 +142,13 @@ prob.A |> DisplayAs.withcontext(:compact => true) #hide sol = solve(prob, KLUFactorization()) sol |> tc #hide -# We can easily visualise our solution: +# 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")) + 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). @@ -152,15 +162,19 @@ function T_exact(x, y) return (R₂^2 - r^2) / (4D₂) end 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 - (x, y, t, u, p) -> D(x, y, p) - end, - diffusion_parameters, - source_function = (x, y, t, u, p) -> one(u), - final_time = Inf, - initial_condition)) +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 + (x, y, t, u, p) -> D(x, y, p) + end, + diffusion_parameters, + source_function = (x, y, t, u, p) -> one(u), + final_time = Inf, + initial_condition + ) +) # Let's compare the two solutions. using SteadyStateDiffEq, OrdinaryDiffEq @@ -176,27 +190,31 @@ using ReferenceTests #src @test_reference joinpath(@__DIR__, "../figures", "mean_exit_time_template_1.png") fig #src using Test #src ind = findall(i -> DelaunayTriangulation.has_vertex(tri, i), DelaunayTriangulation.each_point_index(tri)) -@test fvm_sol.u[ind] ≈ sol.u[ind] rtol = 1e-2 #src +@test fvm_sol.u[ind] ≈ sol.u[ind] rtol = 1.0e-2 #src -# ## Using the Provided Template -# Let's now use the built-in `MeanExitTimeProblem` which implements the above template +# ## Using the Provided Template +# Let's now use the built-in `MeanExitTimeProblem` which implements the above template # inside FiniteVolumeMethod.jl. _u = deepcopy(sol.u) #src -prob = MeanExitTimeProblem(mesh, BCs, ICs; +prob = MeanExitTimeProblem( + mesh, BCs, ICs; diffusion_function, - diffusion_parameters) + diffusion_parameters +) sol = solve(prob, KLUFactorization()) 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)) + 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 -# This matches what we have above. To finish, here is a benchmark comparing the approaches. +# This matches what we have above. To finish, here is a benchmark comparing the approaches. # ````julia # using BenchmarkTools # @btime solve($prob, $KLUFactorization()); diff --git a/docs/src/literate_wyos/poissons_equation.jl b/docs/src/literate_wyos/poissons_equation.jl index 4919010..75a0fcd 100644 --- a/docs/src/literate_wyos/poissons_equation.jl +++ b/docs/src/literate_wyos/poissons_equation.jl @@ -1,27 +1,27 @@ using DisplayAs #hide tc = DisplayAs.withcontext(:displaysize => (15, 80), :limit => true); #hide -# # Poisson's Equation -# ```@contents +# # Poisson's Equation +# ```@contents # Pages = ["poissons_equation.md"] -# ``` -# We now write a solver for Poisson's equation. What we produce +# ``` +# 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 +# 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 +# ``` +# 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). # -# From these similarities, we already know that -# ```math +# 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)$. @@ -50,33 +50,38 @@ function apply_steady_dirichlet_conditions!(A, b, mesh, conditions) b[i] = FVM.eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) A[i, i] = 1.0 end + return end -# So, our problem can be defined by: +# So, our problem can be defined by: using FiniteVolumeMethod, SparseArrays, DelaunayTriangulation, LinearSolve const FVM = FiniteVolumeMethod -function poissons_equation(mesh::FVMGeometry, +function poissons_equation( + mesh::FVMGeometry, BCs::BoundaryConditions, ICs::InternalConditions = InternalConditions(); - diffusion_function = (x, y, p)->1.0, + diffusion_function = (x, y, p) -> 1.0, diffusion_parameters = nothing, source_function, - source_parameters = nothing) + 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) + A, mesh, conditions, diffusion_function, diffusion_parameters + ) FVM.boundary_edge_contributions!( - A, b, mesh, conditions, diffusion_function, diffusion_parameters) + 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 -# Now let's test this problem. We consider -# ```math +# Now let's test this problem. We consider +# ```math # \begin{equation*} # \begin{aligned} # \grad^2 u &= -\sin(\pi x)\sin(\pi y) & \vb x \in [0,1]^2, \\ @@ -99,26 +104,33 @@ sol |> tc #hide #- using CairoMakie fig, ax, -sc = tricontourf( - tri, sol.u, levels = LinRange(0, 0.05, 10), colormap = :matter, extendhigh = :auto) + 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 = 1.0e-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 +# 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 # change the sign of the source function, since above we are solving $\div[D(\vb x)\grad u] = f(\vb x)$, # 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 - (x, y, t, u, p) -> -S(x, y, p) - end, - initial_condition, - final_time = Inf)) +fvm_prob = SteadyFVMProblem( + FVMProblem( + mesh, BCs; + 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 + ) +) #- using SteadyStateDiffEq, OrdinaryDiffEq @@ -126,25 +138,29 @@ 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 @test_reference joinpath(@__DIR__, "../figures", "poissons_equation_template_1.png") fig #src -@test fvm_sol.u ≈ sol.u rtol = 1e-4 #src +@test fvm_sol.u ≈ sol.u rtol = 1.0e-4 #src # ## Using the Provided Template -# Let's now use the built-in `PoissonsEquation` which implements the above 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) #- 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 ≈ fvm_sol.u rtol = 1e-4 #src +@test sol.u ≈ [ + 1 / (2π^2) * sin(π * x) * sin(π * y) + for (x, y) in DelaunayTriangulation.each_point(tri) +] rtol = 1.0e-4 #src +@test sol.u ≈ fvm_sol.u rtol = 1.0e-4 #src # Here is a benchmark comparison of the `PoissonsEquation` approach against the `FVMProblem` approach. # ````julia @@ -164,32 +180,32 @@ sol |> tc #hide # 189.406 ms (185761 allocations: 93.63 MiB) # ```` -# Let's now also solve a generalised Poisson equation. Based +# 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) -# by Nagel (2012), we consider an equation of the form -# ```math +# 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 +# ``` +# 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 +# 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 +# ```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 +# ```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. +# 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. a, b, c, d = 0.0, 10.0, 0.0, 10.0 e, f = (2.0, 3.0), (8.0, 3.0) g, h = (2.0, 7.0), (8.0, 7.0) @@ -197,7 +213,7 @@ 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)) +refine!(tri; max_area = 1.0e-4get_area(tri)) fig, ax, sc = triplot(tri, show_constrained_edges = true, constrained_edge_linewidth = 5) fig @@ -207,10 +223,10 @@ mesh = FVMGeometry(tri) # The boundary conditions are given by: zero_f = (x, y, t, u, p) -> zero(x) bc_f = (zero_f, zero_f, zero_f, zero_f) -bc_types = (Dirichlet, Neumann, Dirichlet, Neumann) # bot, right, top, left +bc_types = (Dirichlet, Neumann, Dirichlet, Neumann) # bot, right, top, left BCs = BoundaryConditions(mesh, bc_f, bc_types) -# To define the internal conditions, we need to get the indices for the plates. +# To define the internal conditions, we need to get the indices for the plates. function find_vertices_on_plates(tri) lower_plate = Int[] upper_plate = Int[] @@ -232,7 +248,7 @@ dirichlet_nodes = Dict( internal_f = ((x, y, t, u, p) -> -one(x), (x, y, t, u, p) -> one(x)) 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 +# 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] using LinearAlgebra function dist_to_line(p, a, b) @@ -242,7 +258,7 @@ function dist_to_line(p, a, b) return norm(p .- pv) 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: function dist_to_plates(x, y) p = (x, y) a1, b1 = (2.0, 3.0), (8.0, 3.0) @@ -271,30 +287,32 @@ function plate_source_function(x, y, p) return -ρ / p.ϵ₀ end -# Now we can define our problem. +# Now we can define our problem. diffusion_parameters = (ϵ₀ = 8.8541878128e-12, Δ = 4.0) -source_parameters = (ϵ₀ = 8.8541878128e-12, Q = 1e-6) -prob = PoissonsEquation(mesh, BCs, ICs; +source_parameters = (ϵ₀ = 8.8541878128e-12, Q = 1.0e-6) +prob = PoissonsEquation( + mesh, BCs, ICs; diffusion_function = dielectric_function, diffusion_parameters = diffusion_parameters, source_function = plate_source_function, - source_parameters = source_parameters) + source_parameters = source_parameters +) #- sol = solve(prob, KLUFactorization()) 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. +# To compute the gradients, we use NaturalNeighbours.jl. using NaturalNeighbours itp = interpolate(tri, sol.u; derivatives = true) E = map(.-, itp.gradient) # E = -∇V E |> tc #hide # For plotting the electric field, we will show the electric field intensity $\|\vb E\|$, -# and we can also show the arrows. Rather than showing all arrows, we will show them at -# a smaller grid of values, which requires differentiating `itp` so that we can get the -# gradients at arbitary points. +# and we can also show the arrows. Rather than showing all arrows, we will show them at +# a smaller grid of values, which requires differentiating `itp` so that we can get the +# gradients at arbitary points. ∂ = differentiate(itp, 1) x = LinRange(0, 10, 25) y = LinRange(0, 10, 25) @@ -303,20 +321,28 @@ y_vec = [y for x in x, y in y] |> vec 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") +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) +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") +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) +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) resize_to_layout!(fig) @@ -324,22 +350,26 @@ 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 - (x, y, t, u, p) -> D(x, y, p) - end, - 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)) +fvm_prob = SteadyFVMProblem( + FVMProblem( + mesh, BCs, ICs; + diffusion_function = let D = dielectric_function + (x, y, t, u, p) -> D(x, y, p) + end, + 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 + ) +) # ````julia # @btime solve($prob, $KLUFactorization()); # ```` -# +# # ```` # 9.061 ms (56 allocations: 10.81 MiB) # ```` @@ -353,4 +383,4 @@ fvm_prob = SteadyFVMProblem(FVMProblem(mesh, BCs, ICs; # ```` fvm_sol = solve(fvm_prob, DynamicSS(TRBDF2(linsolve = KLUFactorization()))) #src -@test sol.u ≈ fvm_sol.u rtol = 1e-4 #src +@test sol.u ≈ fvm_sol.u rtol = 1.0e-4 #src diff --git a/src/FiniteVolumeMethod.jl b/src/FiniteVolumeMethod.jl index 2816f8b..034cf75 100644 --- a/src/FiniteVolumeMethod.jl +++ b/src/FiniteVolumeMethod.jl @@ -37,24 +37,24 @@ 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: PrecompileTools, @compile_workload, @setup_workload @setup_workload begin @compile_workload begin - # Compile a non-steady problem + # Compile a non-steady problem n = 5 α = π / 4 x₁ = [0.0, 1.0] @@ -77,9 +77,11 @@ using PrecompileTools: PrecompileTools, @compile_workload, @setup_workload 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) ode_prob = ODEProblem(prob) @@ -118,14 +120,18 @@ using PrecompileTools: PrecompileTools, @compile_workload, @setup_workload 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; + 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) - v_prob = FVMProblem(mesh, BCs_v; + 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) + 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 f3b0208..e59ebfe 100644 --- a/src/conditions.jl +++ b/src/conditions.jl @@ -172,7 +172,7 @@ struct BoundaryConditions{F <: Tuple, C <: Tuple} end function Base.show(io::IO, ::MIME"text/plain", bc::BoundaryConditions) n = length(bc.functions) - if n > 1 + return if n > 1 print(io, "BoundaryConditions with $(n) boundary conditions with types $(bc.condition_types)") else print(io, "BoundaryConditions with $(n) boundary condition with type $(bc.condition_types[1])") @@ -231,32 +231,40 @@ end function Base.show(io::IO, ::MIME"text/plain", ic::InternalConditions) nd = length(ic.dirichlet_nodes) ndt = length(ic.dudt_nodes) - print(io, "InternalConditions with $(nd) Dirichlet nodes and $(ndt) Dudt nodes") + return print(io, "InternalConditions with $(nd) Dirichlet nodes and $(ndt) Dudt nodes") end -function BoundaryConditions(mesh::FVMGeometry, functions::Tuple, types::Tuple; - parameters::Tuple = ntuple(_ -> nothing, length(functions))) +function BoundaryConditions( + mesh::FVMGeometry, functions::Tuple, types::Tuple; + 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) +function BoundaryConditions( + mesh::FVMGeometry, functions::Function, types::ConditionType; + parameters = nothing + ) return BoundaryConditions(mesh, (functions,), (types,), parameters = (parameters,)) end -@inline function InternalConditions(functions::Tuple = (); +@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))) + 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; +@inline function InternalConditions( + functions::Function; dirichlet_nodes::Dict{Int, Int} = Dict{Int, Int}(), dudt_nodes::Dict{Int, Int} = Dict{Int, Int}(), - parameters = nothing) + parameters = nothing + ) return InternalConditions((functions,); dirichlet_nodes, dudt_nodes, parameters = (parameters,)) end @@ -305,10 +313,13 @@ struct Conditions{F <: Tuple} <: AbstractConditions 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} + @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) + neumann_edges, constrained_edges, dirichlet_nodes, dudt_nodes, functions + ) end end function Base.show(io::IO, ::MIME"text/plain", conds::AbstractConditions) @@ -320,7 +331,7 @@ function Base.show(io::IO, ::MIME"text/plain", conds::AbstractConditions) println(io, " $(nn) Neumann edges") println(io, " $(nc) Constrained edges") println(io, " $(nd) Dirichlet nodes") - print(io, " $(ndt) Dudt nodes") + return print(io, " $(ndt) Dudt nodes") end """ @@ -335,8 +346,11 @@ 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) @@ -350,8 +364,11 @@ 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] @@ -360,8 +377,10 @@ 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) @@ -374,8 +393,11 @@ 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) @@ -389,8 +411,11 @@ 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) @@ -412,8 +437,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) + conds::AbstractConditions, node +) = is_dudt_node(conds, node) || + is_dirichlet_node(conds, node) """ has_dirichlet_nodes(conds) @@ -472,7 +498,8 @@ Get all edges that have a [`Constrained`](@ref) condition. sizehint!(dudt_nodes, nv) functions = (ic_functions..., bc_functions...) conditions = Conditions( - neumann_edges, constrained_edges, dirichlet_nodes, dudt_nodes, functions) + neumann_edges, constrained_edges, dirichlet_nodes, dudt_nodes, functions + ) return conditions end @@ -499,11 +526,11 @@ function merge_conditions!(conditions::Conditions, mesh::FVMGeometry, bc_conditi elseif condition == Dirichlet conditions.dirichlet_nodes[u] = updated_bc_number conditions.dirichlet_nodes[v] = updated_bc_number - else # Dudt - # Strictly speaking, we do need to take care that no Dudt - # nodes are also assigned as Dirichlet nodes, since - # Dirichlet conditions take precedence over Dudt conditions. - # However, in the code we also defend against this by checking + else # Dudt + # Strictly speaking, we do need to take care that no Dudt + # nodes are also assigned as Dirichlet nodes, since + # Dirichlet conditions take precedence over Dudt conditions. + # However, in the code we also defend against this by checking # for Dirichlet first, so this check is not _technically_ # needed at all. conditions.dudt_nodes[u] = updated_bc_number @@ -517,7 +544,8 @@ function merge_conditions!(conditions::Conditions, mesh::FVMGeometry, bc_conditi end @inline function Conditions( - mesh::FVMGeometry, bc::BoundaryConditions, ic::InternalConditions = InternalConditions()) + 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 diff --git a/src/equations/boundary_edge_contributions.jl b/src/equations/boundary_edge_contributions.jl index 991d5a9..4043d28 100644 --- a/src/equations/boundary_edge_contributions.jl +++ b/src/equations/boundary_edge_contributions.jl @@ -1,6 +1,7 @@ # 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} + 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 @@ -13,7 +14,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} + 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] @@ -26,7 +28,8 @@ 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} + 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) @@ -36,7 +39,8 @@ end # function for getting both fluxes for a non-system problem @inline function get_boundary_fluxes( - prob::AbstractFVMProblem, α::T, β, γ, i, j, t) where {T} + 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 + γ) @@ -91,24 +95,29 @@ 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) + 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) + 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) + 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) + duplicated_du[:, :, chunk_idx], u, prob, t, e + ) else @views fvm_eqs_single_boundary_edge!(duplicated_du[:, chunk_idx], u, prob, t, e) end end + return end diff --git a/src/equations/control_volumes.jl b/src/equations/control_volumes.jl index e28b5a0..58a39db 100644 --- a/src/equations/control_volumes.jl +++ b/src/equations/control_volumes.jl @@ -1,4 +1,4 @@ -# get the relevant quantities for a control volume edge not on the boundary +# get the relevant quantities for a control volume edge not on the boundary """ get_cv_components(props, edge_index) diff --git a/src/equations/dirichlet.jl b/src/equations/dirichlet.jl index f02beb5..a58916b 100644 --- a/src/equations/dirichlet.jl +++ b/src/equations/dirichlet.jl @@ -1,6 +1,7 @@ # primitive: get dirichlet value for a non-system @inline function get_dirichlet_condition( - prob::AbstractFVMProblem, u::T, t, i, function_index) where {T} + 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]) @@ -8,7 +9,8 @@ end # primitive: get dirichlet value for a system @inline function get_dirichlet_condition( - prob::FVMSystem, u::T, t, i, var, function_index) where {T} + 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]) @@ -16,7 +18,8 @@ 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} + 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 @@ -24,7 +27,8 @@ 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} + 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 @@ -45,6 +49,7 @@ function serial_update_dirichlet_nodes!(u, t, prob::FVMSystem) update_dirichlet_nodes_single!(u, t, prob, i, var, function_index) end end + return end # get the dirichlet value and update u for a non-system for each dirichlet_node in parallel diff --git a/src/equations/individual_flux_contributions.jl b/src/equations/individual_flux_contributions.jl index 9cae263..f74837f 100644 --- a/src/equations/individual_flux_contributions.jl +++ b/src/equations/individual_flux_contributions.jl @@ -25,7 +25,8 @@ 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} + 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 * ℓ diff --git a/src/equations/main_equations.jl b/src/equations/main_equations.jl index f3b526b..cc6a4be 100644 --- a/src/equations/main_equations.jl +++ b/src/equations/main_equations.jl @@ -37,7 +37,7 @@ end # the serial form of the equations function serial_fvm_eqs!(du, u, prob, t) fill!(du, zero(eltype(du))) - get_triangle_contributions!(du, u, prob, t) # acts on triangles + get_triangle_contributions!(du, u, prob, t) # acts on triangles get_boundary_edge_contributions!(du, u, prob, t) # acts on boundary edges get_source_contributions!(du, u, prob, t) # acts on points return du @@ -46,19 +46,21 @@ end # the parallel form of the equations function parallel_fvm_eqs!(du, u, p, t) duplicated_du, solid_triangles, - solid_vertices, chunked_solid_triangles, - boundary_edges, chunked_boundary_edges, - prob = p.duplicated_du, p.solid_triangles, - p.solid_vertices, p.chunked_solid_triangles, - p.boundary_edges, p.chunked_boundary_edges, - p.prob + solid_vertices, chunked_solid_triangles, + boundary_edges, chunked_boundary_edges, + prob = p.duplicated_du, p.solid_triangles, + p.solid_vertices, p.chunked_solid_triangles, + p.boundary_edges, p.chunked_boundary_edges, + p.prob 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) + _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) + _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 diff --git a/src/equations/source_contributions.jl b/src/equations/source_contributions.jl index 751467d..02afc40 100644 --- a/src/equations/source_contributions.jl +++ b/src/equations/source_contributions.jl @@ -24,14 +24,15 @@ end 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)) + 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} + du, u::T, prob::AbstractFVMProblem, t, i + ) where {T} if !DelaunayTriangulation.has_vertex(prob.mesh.triangulation, i) du[i] = zero(eltype(T)) else @@ -47,7 +48,8 @@ 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} + 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 e693f9c..2728f02 100644 --- a/src/equations/triangle_contributions.jl +++ b/src/equations/triangle_contributions.jl @@ -44,17 +44,20 @@ 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) + 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) + 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) + 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 diff --git a/src/geometry.jl b/src/geometry.jl index 3db007b..fa2c0f6 100644 --- a/src/geometry.jl +++ b/src/geometry.jl @@ -51,15 +51,18 @@ function Base.show(io::IO, ::MIME"text/plain", geo::FVMGeometry) nv = DelaunayTriangulation.num_solid_vertices(geo.triangulation_statistics) nt = DelaunayTriangulation.num_solid_triangles(geo.triangulation_statistics) ne = DelaunayTriangulation.num_solid_edges(geo.triangulation_statistics) - print(io, "FVMGeometry with $(nv) control volumes, $(nt) triangles, and $(ne) edges") + return print(io, "FVMGeometry with $(nv) control volumes, $(nt) triangles, and $(ne) edges") end """ get_triangle_props(mesh, i, j, k) 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) @@ -74,7 +77,7 @@ get_volume(mesh::FVMGeometry, i) = mesh.cv_volumes[i] Get the `i`th point in `mesh`. """ function DelaunayTriangulation.get_point(mesh::FVMGeometry, i) - DelaunayTriangulation.get_point(mesh.triangulation, i) + return DelaunayTriangulation.get_point(mesh.triangulation, i) end #= @@ -111,7 +114,7 @@ function FVMGeometry(tri::Triangulation) centroid = DelaunayTriangulation.get_centroid(stats, T) m₁, m₂, m₃ = DelaunayTriangulation.get_edge_midpoints(stats, T) ## Need to get the sub-control volume areas - # We need to connect the centroid to each vertex + # We need to connect the centroid to each vertex cx, cy = getxy(centroid) pcx, pcy = cx - px, cy - py qcx, qcy = cx - qx, cy - qy @@ -123,7 +126,7 @@ function FVMGeometry(tri::Triangulation) m₁₃x, m₁₃y = m₁x - m₃x, m₁y - m₃y m₂₁x, m₂₁y = m₂x - m₁x, m₂y - m₁y m₃₂x, m₃₂y = m₃x - m₂x, m₃y - m₂y - # We can now contribute the portion of each vertex's control volume inside the triangle to its total volume + # We can now contribute the portion of each vertex's control volume inside the triangle to its total volume S₁ = 1 / 2 * abs(pcx * m₁₃y - pcy * m₁₃x) S₂ = 1 / 2 * abs(qcx * m₂₁y - qcy * m₂₁x) S₃ = 1 / 2 * abs(rcx * m₃₂y - rcy * m₃₂x) @@ -142,11 +145,11 @@ function FVMGeometry(tri::Triangulation) s₈ = (rx * py - px * ry) / Δ s₉ = (px * qy - qx * py) / Δ shape_function_coefficients = (s₁, s₂, s₃, s₄, s₅, s₆, s₇, s₈, s₉) - ## Now we need the control volume edge midpoints + ## Now we need the control volume edge midpoints m₁cx, m₁cy = (m₁x + cx) / 2, (m₁y + cy) / 2 m₂cx, m₂cy = (m₂x + cx) / 2, (m₂y + cy) / 2 m₃cx, m₃cy = (m₃x + cx) / 2, (m₃y + cy) / 2 - ## Next, we need the normal vectors to the control volume edges + ## Next, we need the normal vectors to the control volume edges e₁x, e₁y = cx - m₁x, cy - m₁y e₂x, e₂y = cx - m₂x, cy - m₂y e₃x, e₃y = cx - m₃x, cy - m₃y @@ -159,7 +162,8 @@ function FVMGeometry(tri::Triangulation) ## 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)), (ℓ₁, ℓ₂, ℓ₃)) + ((n₁x, n₁y), (n₂x, n₂y), (n₃x, n₃y)), (ℓ₁, ℓ₂, ℓ₃) + ) end return FVMGeometry(tri, stats, cv_volumes, triangle_props) end diff --git a/src/problem.jl b/src/problem.jl index 38eadd0..80c97cd 100644 --- a/src/problem.jl +++ b/src/problem.jl @@ -3,10 +3,14 @@ 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) @@ -104,12 +108,15 @@ function Base.show(io::IO, ::MIME"text/plain", prob::FVMProblem) nv = DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation) t0 = prob.initial_time tf = prob.final_time - print(io, "FVMProblem with $(nv) nodes and time span ($t0, $tf)") + return 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) +@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, +function FVMProblem( + mesh::FVMGeometry, boundary_conditions::BoundaryConditions, internal_conditions::InternalConditions = InternalConditions(); diffusion_function = nothing, diffusion_parameters = nothing, @@ -119,17 +126,21 @@ function FVMProblem(mesh::FVMGeometry, boundary_conditions::BoundaryConditions, flux_parameters = nothing, initial_condition, initial_time = 0.0, - final_time) + 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)." + 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; + return FVMProblem( + mesh, conditions; diffusion_function, diffusion_parameters, source_function, source_parameters, flux_function, flux_parameters, - initial_condition, initial_time, final_time) + initial_condition, initial_time, final_time + ) end -function FVMProblem(mesh::FVMGeometry, conditions::Conditions; +function FVMProblem( + mesh::FVMGeometry, conditions::Conditions; diffusion_function = nothing, diffusion_parameters = nothing, source_function = (x, y, t, u, p) -> zero(eltype(u)), @@ -138,14 +149,17 @@ function FVMProblem(mesh::FVMGeometry, conditions::Conditions; flux_parameters = nothing, initial_condition, initial_time = 0.0, - final_time) + 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)." + 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, + return FVMProblem( + mesh, conditions, updated_flux_fnc, flux_parameters, source_function, source_parameters, - initial_condition, initial_time, final_time) + initial_condition, initial_time, final_time + ) end """ @@ -168,14 +182,16 @@ end function Base.show(io::IO, ::MIME"text/plain", prob::SteadyFVMProblem) nv = DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation) is_sys = is_system(prob) - if !is_sys + return if !is_sys print(io, "SteadyFVMProblem with $(nv) nodes") else 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) @@ -225,24 +241,29 @@ struct FVMSystem{N, FG, P, IC, FT, F, S, FF} <: AbstractFVMProblem functions::F source_functions::S flux_functions::FF - function FVMSystem(mesh::FG, problems::P, initial_condition::IC, initial_time::FT, + 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} + 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." + (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." + 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." + 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." + 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." @@ -250,25 +271,26 @@ struct FVMSystem{N, FG, P, IC, FT, F, S, FF} <: AbstractFVMProblem @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) + conditions, num_fncs, functions, source_functions, flux_functions + ) _check_fvmsystem_flux_function(sys) return sys end end const FLUX_ERROR = """ - The flux function errored when evaluated. Please recheck your specification of the function. - If any of your problems have been defined in terms of a diffusion function D(x, y, t, u, p) - rather than a flux function, then you need to instead provide a flux function q(x, y, t, α, β, γ, p), - recalling the relationship between the two: +The flux function errored when evaluated. Please recheck your specification of the function. +If any of your problems have been defined in terms of a diffusion function D(x, y, t, u, p) +rather than a flux function, then you need to instead provide a flux function q(x, y, t, α, β, γ, p), +recalling the relationship between the two: - q(x, y, t, α, β, γ, p) = -D(x, y, t, α*x + β*y + γ, p) .* (α, β) + q(x, y, t, α, β, γ, p) = -D(x, y, t, α*x + β*y + γ, p) .* (α, β) - where p is the same argument for both functions. - """ +where p is the same argument for both functions. +""" struct InvalidFluxError <: Exception end function Base.showerror(io::IO, ::InvalidFluxError) - print(io, FLUX_ERROR) + return print(io, FLUX_ERROR) end function _check_fvmsystem_flux_function(prob::FVMSystem) @@ -282,7 +304,7 @@ function _check_fvmsystem_flux_function(prob::FVMSystem) cx, cy = (px + qx + rx) / 3, (py + qy + ry) / 3 u = prob.initial_condition α, β, γ = get_shape_function_coefficients(prob.mesh.triangle_props[T], T, u, prob) - try + return try eval_flux_function(prob, cx, cy, t0, α, β, γ) catch e if e isa MethodError @@ -294,22 +316,33 @@ function _check_fvmsystem_flux_function(prob::FVMSystem) end 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))") + return 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) + 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) @@ -318,8 +351,10 @@ end @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} N == 0 && error("There must be at least one problem.") @@ -332,9 +367,11 @@ function FVMSystem(probs::Vararg{FVMProblem, N}) where {N} 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) + 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) diff --git a/src/solve.jl b/src/solve.jl index bb2cad7..8b28a81 100644 --- a/src/solve.jl +++ b/src/solve.jl @@ -22,14 +22,14 @@ function get_multithreading_parameters(prob::Union{FVMProblem, FVMSystem}) boundary_edges = boundary_edges, chunked_boundary_edges = chunked_boundary_edges, parallel = Val(true), - prob = prob + prob = prob, ) end function get_serial_parameters(prob::Union{FVMProblem, FVMSystem}) return ( parallel = Val(false), - prob = prob + prob = prob, ) end @@ -49,13 +49,13 @@ Returns a prototype for the Jacobian of the given `prob`. jacobian_sparsity jacobian_sparsity(prob::FVMProblem) = jacobian_sparsity(prob.mesh.triangulation) function jacobian_sparsity(prob::FVMSystem{N}) where {N} - jacobian_sparsity(prob.mesh.triangulation, N) + return jacobian_sparsity(prob.mesh.triangulation, N) end jacobian_sparsity(prob::SteadyFVMProblem) = jacobian_sparsity(prob.problem) function jacobian_sparsity(tri) - I = Int64[] # row indices - J = Int64[] # col indices + I = Int64[] # row indices + J = Int64[] # col indices V = Float64[] # values (all 1) n = DelaunayTriangulation.num_solid_vertices(tri) sizehint!(I, 6n) # points have, on average, six neighbours in a DelaunayTriangulation @@ -77,22 +77,22 @@ function jacobian_sparsity(tri) end # Some working: -# Suppose we have a problem that looks like this (↓ vars → node) -# u₁¹ u₂¹ u₃¹ ⋯ uₙ¹ +# Suppose we have a problem that looks like this (↓ vars → node) +# u₁¹ u₂¹ u₃¹ ⋯ uₙ¹ # u₁² u₂² u₃² ⋯ uₙ² # ⋮ ⋮ ⋮ ⋱ ⋮ # u₁ᴺ u₂ᴺ u₃ᴺ ⋯ uₙᴺ -# When we write down the relationships here, we need -# to use the linear subscripts, so that the problem above -# is interpreted as +# When we write down the relationships here, we need +# to use the linear subscripts, so that the problem above +# is interpreted as # u¹ uᴺ⁺¹ u²ᴺ⁺¹ ⋯ u⁽ⁿ⁻¹⁾ᴺ⁺¹ # u² uᴺ⁺² u²ᴺ⁺² ⋯ u⁽ⁿ⁻¹⁾ᴺ⁺² # ⋮ ⋮ ⋮ ⋱ ⋮ # uᴺ u²ᴺ u³ᴺ ⋯ uⁿᴺ -# With this, the ith node is at the linear indices +# With this, the ith node is at the linear indices # (i, 1), (i, 2), …, (i, N) ↦ (i-1)*N + j for j in 1:N. # In the original matrix, a node i being related to a node ℓ -# means that the ith and ℓ columns are all related to eachother. +# means that the ith and ℓ columns are all related to eachother. function jacobian_sparsity(tri, N) I = Int64[] # row indices J = Int64[] # col indices @@ -102,8 +102,8 @@ function jacobian_sparsity(tri, N) sizehint!(J, 6n * N) sizehint!(V, 6n * N) for i in DelaunayTriangulation.each_point_index(tri) - # First, i is related to itself, meaning - # (i, 1), (i, 2), …, (i, N) are all related. + # First, i is related to itself, meaning + # (i, 1), (i, 2), …, (i, N) are all related. for ℓ in 1:N node = (i - 1) * N + ℓ for j in 1:N @@ -154,19 +154,23 @@ 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}; +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} + kwargs... + ) where {S, B} initial_time = prob.initial_time final_time = prob.final_time time_span = (initial_time, final_time) @@ -178,32 +182,40 @@ function SciMLBase.ODEProblem(prob::Union{FVMProblem, FVMSystem}; return ode_problem end -function SciMLBase.SteadyStateProblem(prob::SteadyFVMProblem; +function SciMLBase.SteadyStateProblem( + prob::SteadyFVMProblem; specialization::Type{S} = SciMLBase.AutoSpecialize, jac_prototype = jacobian_sparsity(prob), parallel::Val{B} = Val(true), - kwargs...) where {S, B} + 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...; +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} + kwargs... + ) where {S, B} ode_prob = SciMLBase.ODEProblem( - prob; specialization, jac_prototype, parallel, kwargs...) + prob; specialization, jac_prototype, parallel, kwargs... + ) return CommonSolve.init(ode_prob, args...; kwargs...) end -function CommonSolve.solve(prob::SteadyFVMProblem, args...; +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} + kwargs... + ) where {S, B} nl_prob = SciMLBase.SteadyStateProblem( - prob; specialization, jac_prototype, parallel, kwargs...) + prob; specialization, jac_prototype, parallel, kwargs... + ) return CommonSolve.solve(nl_prob, args...; kwargs...) end diff --git a/src/specific_problems/abstract_templates.jl b/src/specific_problems/abstract_templates.jl index 608e4e7..d8a732a 100644 --- a/src/specific_problems/abstract_templates.jl +++ b/src/specific_problems/abstract_templates.jl @@ -56,7 +56,7 @@ Solve the problem `prob` using the standard `solve` interface from DifferentialE steady state problems, the interface is from LinearSolve.jl. """ function CommonSolve.solve(prob::AbstractFVMTemplate, args...; kwargs...) - CommonSolve.solve(prob.problem, args...; kwargs...) + return CommonSolve.solve(prob.problem, args...; kwargs...) end @doc raw""" @@ -71,7 +71,8 @@ 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) + A, mesh, conditions, diffusion_function, diffusion_parameters + ) for T in each_solid_triangle(mesh.triangulation) ijk = triangle_vertices(T) i, j, k = ijk @@ -81,9 +82,11 @@ function triangle_contributions!( x, y, nx, ny, ℓ = get_cv_components(props, edge_index) D = diffusion_function(x, y, diffusion_parameters) Dℓ = D * ℓ - a123 = (Dℓ * (s₁₁ * nx + s₂₁ * ny), + a123 = ( + Dℓ * (s₁₁ * nx + s₂₁ * ny), Dℓ * (s₁₂ * nx + s₂₂ * ny), - Dℓ * (s₁₃ * nx + s₂₃ * ny)) + Dℓ * (s₁₃ * nx + s₂₃ * ny), + ) e1_hascond = has_condition(conditions, e1) e2_hascond = has_condition(conditions, e2) for vert in 1:3 @@ -92,6 +95,7 @@ function triangle_contributions!( end end end + return end @doc raw""" @@ -106,8 +110,10 @@ 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) + conditions, function_index, x, y, nothing, nothing + ) end + return end @doc raw""" @@ -125,6 +131,7 @@ function apply_dudt_conditions!(b, mesh, conditions) b[i] = eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) end end + return end @doc raw""" @@ -139,12 +146,16 @@ Add the contributions from each boundary edge to the matrix `A`, based on the eq 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) +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 + 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) + b, mesh, conditions, diffusion_function, diffusion_parameters + ) return nothing end @@ -162,7 +173,8 @@ as explained in the docs. Will not update any rows corresponding to of the arguments `u` and `t` in the boundary condition functions. """ function neumann_boundary_edge_contributions!( - b, mesh, conditions, diffusion_function, diffusion_parameters) + 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) @@ -191,7 +203,8 @@ 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) + 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) @@ -222,7 +235,8 @@ 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) + 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) @@ -233,12 +247,16 @@ function non_neumann_boundary_edge_contributions!( Dⱼ = diffusion_function(mⱼx, mⱼy, diffusion_parameters) i_hascond = has_condition(conditions, i) j_hascond = has_condition(conditions, j) - aᵢ123 = (Dᵢ * ℓ * (s₁₁ * nx + s₂₁ * ny), + aᵢ123 = ( + Dᵢ * ℓ * (s₁₁ * nx + s₂₁ * ny), Dᵢ * ℓ * (s₁₂ * nx + s₂₂ * ny), - Dᵢ * ℓ * (s₁₃ * nx + s₂₃ * ny)) - aⱼ123 = (Dⱼ * ℓ * (s₁₁ * nx + s₂₁ * ny), + Dᵢ * ℓ * (s₁₃ * nx + s₂₃ * ny), + ) + aⱼ123 = ( + Dⱼ * ℓ * (s₁₁ * nx + s₂₁ * ny), Dⱼ * ℓ * (s₁₂ * nx + s₂₂ * ny), - Dⱼ * ℓ * (s₁₃ * nx + s₂₃ * ny)) + Dⱼ * ℓ * (s₁₃ * nx + s₂₃ * ny), + ) for vert in 1:3 i_hascond || (A[i, ijk[vert]] += aᵢ123[vert] / get_volume(mesh, i)) j_hascond || (A[j, ijk[vert]] += aⱼ123[vert] / get_volume(mesh, i)) @@ -287,6 +305,7 @@ function apply_steady_dirichlet_conditions!(A, b, mesh, conditions) b[i] = eval_condition_fnc(conditions, function_index, x, y, nothing, nothing) A[i, i] = 1.0 end + return end @doc """ diff --git a/src/specific_problems/diffusion_equation.jl b/src/specific_problems/diffusion_equation.jl index 8f61c39..5fb1d65 100644 --- a/src/specific_problems/diffusion_equation.jl +++ b/src/specific_problems/diffusion_equation.jl @@ -63,10 +63,11 @@ function Base.show(io::IO, ::MIME"text/plain", prob::DiffusionEquation) nv = DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation) t0 = prob.initial_time tf = prob.final_time - print(io, "DiffusionEquation with $(nv) nodes and time span ($t0, $tf)") + return print(io, "DiffusionEquation with $(nv) nodes and time span ($t0, $tf)") end -function DiffusionEquation(mesh::FVMGeometry, +function DiffusionEquation( + mesh::FVMGeometry, BCs::BoundaryConditions, ICs::InternalConditions = InternalConditions(); diffusion_function, @@ -74,7 +75,8 @@ function DiffusionEquation(mesh::FVMGeometry, initial_condition, initial_time = 0.0, final_time, - kwargs...) + kwargs... + ) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) @@ -83,14 +85,17 @@ function DiffusionEquation(mesh::FVMGeometry, _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) + 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) A_op = MatrixOperator(sparse(Afull)) prob = ODEProblem(A_op, _ic, (initial_time, final_time); kwargs...) - return DiffusionEquation(mesh, conditions, + return DiffusionEquation( + mesh, conditions, diffusion_function, diffusion_parameters, initial_condition, initial_time, final_time, - sparse(A), b, A_op, prob) + sparse(A), b, A_op, prob + ) end diff --git a/src/specific_problems/laplaces_equation.jl b/src/specific_problems/laplaces_equation.jl index ce04685..868f1af 100644 --- a/src/specific_problems/laplaces_equation.jl +++ b/src/specific_problems/laplaces_equation.jl @@ -45,15 +45,17 @@ struct LaplacesEquation{M, C, D, DP, A, B, ODE} <: AbstractFVMTemplate end function Base.show(io::IO, ::MIME"text/plain", prob::LaplacesEquation) nv = DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation) - print(io, "LaplacesEquation with $(nv) nodes") + return print(io, "LaplacesEquation with $(nv) nodes") end -function LaplacesEquation(mesh::FVMGeometry, +function LaplacesEquation( + mesh::FVMGeometry, BCs::BoundaryConditions, ICs::InternalConditions = InternalConditions(); diffusion_function = (x, y, p) -> 1.0, diffusion_parameters = nothing, - kwargs...) + kwargs... + ) conditions = Conditions(mesh, BCs, ICs) has_dudt_nodes(conditions) && throw(ArgumentError("PoissonsEquation does not support Dudt nodes.")) @@ -62,12 +64,15 @@ function LaplacesEquation(mesh::FVMGeometry, 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) + 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) prob = LinearProblem(Asp, b; kwargs...) - return LaplacesEquation(mesh, conditions, + return LaplacesEquation( + mesh, conditions, diffusion_function, diffusion_parameters, - Asp, b, prob) + Asp, b, prob + ) end diff --git a/src/specific_problems/linear_reaction_diffusion_equations.jl b/src/specific_problems/linear_reaction_diffusion_equations.jl index 8fcc8f6..53caf44 100644 --- a/src/specific_problems/linear_reaction_diffusion_equations.jl +++ b/src/specific_problems/linear_reaction_diffusion_equations.jl @@ -51,7 +51,7 @@ The struct has extra fields in addition to the arguments above: - `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 + AbstractFVMTemplate mesh::M conditions::C diffusion_function::D @@ -70,10 +70,11 @@ function Base.show(io::IO, ::MIME"text/plain", prob::LinearReactionDiffusionEqua nv = DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation) t0 = prob.initial_time tf = prob.final_time - print(io, "LinearReactionDiffusionEquation with $(nv) nodes and time span ($t0, $tf)") + return print(io, "LinearReactionDiffusionEquation with $(nv) nodes and time span ($t0, $tf)") end -function LinearReactionDiffusionEquation(mesh::FVMGeometry, +function LinearReactionDiffusionEquation( + mesh::FVMGeometry, BCs::BoundaryConditions, ICs::InternalConditions = InternalConditions(); diffusion_function, @@ -83,7 +84,8 @@ function LinearReactionDiffusionEquation(mesh::FVMGeometry, initial_condition, initial_time = 0.0, final_time, - kwargs...) + kwargs... + ) conditions = Conditions(mesh, BCs, ICs) n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation) Afull = zeros(n + 1, n + 1) @@ -92,7 +94,8 @@ function LinearReactionDiffusionEquation(mesh::FVMGeometry, _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) + 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) @@ -100,19 +103,23 @@ function LinearReactionDiffusionEquation(mesh::FVMGeometry, Af = sparse(Afull) Aop = MatrixOperator(Af) prob = ODEProblem(Aop, _ic, (initial_time, final_time); kwargs...) - return LinearReactionDiffusionEquation(mesh, conditions, + return LinearReactionDiffusionEquation( + mesh, conditions, diffusion_function, diffusion_parameters, source_function, source_parameters, initial_condition, initial_time, final_time, - sparse(A), b, Aop, prob) + sparse(A), b, Aop, prob + ) end function linear_source_contributions!( - A, mesh, conditions, source_function, source_parameters) + 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 + return end diff --git a/src/specific_problems/mean_exit_time.jl b/src/specific_problems/mean_exit_time.jl index b3380a7..2387a3a 100644 --- a/src/specific_problems/mean_exit_time.jl +++ b/src/specific_problems/mean_exit_time.jl @@ -51,15 +51,17 @@ struct MeanExitTimeProblem{M, C, D, DP, A, B, LP} <: AbstractFVMTemplate end function Base.show(io::IO, ::MIME"text/plain", prob::MeanExitTimeProblem) nv = DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation) - print(io, "MeanExitTimeProblem with $(nv) nodes") + return print(io, "MeanExitTimeProblem with $(nv) nodes") end -function MeanExitTimeProblem(mesh::FVMGeometry, +function MeanExitTimeProblem( + mesh::FVMGeometry, BCs::BoundaryConditions, ICs::InternalConditions = InternalConditions(); diffusion_function, diffusion_parameters = nothing, - kwargs...) + kwargs... + ) conditions = Conditions(mesh, BCs, ICs) has_dudt_nodes(conditions) && throw(ArgumentError("MeanExitTimeProblem does not support Dudt nodes.")) @@ -72,9 +74,11 @@ function MeanExitTimeProblem(mesh::FVMGeometry, fix_missing_vertices!(A, b, mesh) Asp = sparse(A) prob = LinearProblem(Asp, b; kwargs...) - return MeanExitTimeProblem(mesh, conditions, + return MeanExitTimeProblem( + mesh, conditions, diffusion_function, diffusion_parameters, - Asp, b, prob) + Asp, b, prob + ) end function create_met_b!(A, mesh, conditions) diff --git a/src/specific_problems/poissons_equation.jl b/src/specific_problems/poissons_equation.jl index e356a44..d2048f0 100644 --- a/src/specific_problems/poissons_equation.jl +++ b/src/specific_problems/poissons_equation.jl @@ -52,17 +52,19 @@ struct PoissonsEquation{M, C, D, DP, S, SP, A, B, ODE} <: AbstractFVMTemplate end function Base.show(io::IO, ::MIME"text/plain", prob::PoissonsEquation) nv = DelaunayTriangulation.num_solid_vertices(prob.mesh.triangulation) - print(io, "PoissonsEquation with $(nv) nodes") + return print(io, "PoissonsEquation with $(nv) nodes") end -function PoissonsEquation(mesh::FVMGeometry, +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...) + kwargs... + ) conditions = Conditions(mesh, BCs, ICs) has_dudt_nodes(conditions) && throw(ArgumentError("PoissonsEquation does not support Dudt nodes.")) @@ -71,13 +73,16 @@ function PoissonsEquation(mesh::FVMGeometry, 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) + 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) prob = LinearProblem(Asp, b; kwargs...) - return PoissonsEquation(mesh, conditions, + return PoissonsEquation( + mesh, conditions, diffusion_function, diffusion_parameters, source_function, source_parameters, - Asp, b, prob) + Asp, b, prob + ) end diff --git a/src/utils.jl b/src/utils.jl index 3fdd527..dae2cf3 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -10,7 +10,7 @@ function _safe_get_triangle_props(mesh::FVMGeometry, T) end end function _safe_get_triangle_props(prob::AbstractFVMProblem, T) - _safe_get_triangle_props(prob.mesh, T) + return _safe_get_triangle_props(prob.mesh, T) end """ diff --git a/test/conditions.jl b/test/conditions.jl index 35db9b4..3116424 100644 --- a/test/conditions.jl +++ b/test/conditions.jl @@ -20,7 +20,8 @@ include("test_functions.jl") constrained_edges = NTuple{2, Float64}[] neumann_edges = NTuple{2, Float64}[] test_bc_conditions!( - tri, conds, t, dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges) + 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) @@ -36,13 +37,13 @@ end 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)" + "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" + "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" + "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 @@ -53,7 +54,8 @@ end 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) + 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) diff --git a/test/equations.jl b/test/equations.jl index d6ae795..5b86140 100644 --- a/test/equations.jl +++ b/test/equations.jl @@ -44,11 +44,14 @@ 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) + (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 @@ -75,10 +78,14 @@ 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 diff --git a/test/geometry.jl b/test/geometry.jl index 9204b3d..4e2fc0b 100644 --- a/test/geometry.jl +++ b/test/geometry.jl @@ -53,7 +53,7 @@ for tri in (example_tri(), example_tri_rect()) @test collect(cij_mid) ≈ collect(m1) @test collect(cjk_mid) ≈ collect(m2) @test collect(cki_mid) ≈ collect(m3) - # The edge normals + # The edge normals e1 = centroid .- ij_mid e2 = centroid .- jk_mid e3 = centroid .- ki_mid @@ -64,7 +64,7 @@ for tri in (example_tri(), example_tri_rect()) @test collect(n1) ≈ collect(nn1) @test collect(n2) ≈ collect(nn2) @test collect(n3) ≈ collect(nn3) - # The edge lengths + # The edge lengths ℓ1 = norm(e1) ℓ2 = norm(e2) ℓ3 = norm(e3) diff --git a/test/problem.jl b/test/problem.jl index d07a1b4..ddafec1 100644 --- a/test/problem.jl +++ b/test/problem.jl @@ -12,11 +12,11 @@ include("test_functions.jl") @testset "Some generic tests" begin prob, tri, mesh, BCs, ICs, - flux_function, flux_parameters, - source_function, source_parameters, - initial_condition = example_problem() + 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))" + "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 @@ -78,7 +78,7 @@ include("test_functions.jl") qy = x + t - β * u * p[2] steady = SteadyFVMProblem(prob) @test sprint(show, MIME"text/plain"(), steady) == - "SteadyFVMProblem with $(DelaunayTriangulation.num_points(tri)) nodes" + "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, α, β, γ) @@ -86,16 +86,16 @@ include("test_functions.jl") @test !FVM.is_system(steady) 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] + 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))" + "FVMSystem with 5 equations and time span ($(system.initial_time), $(system.final_time))" @inferred FVMSystem(prob1, prob2, prob3, prob4, prob5) _α = ntuple(_ -> α, 5) _β = ntuple(_ -> β, 5) @@ -103,7 +103,7 @@ include("test_functions.jl") @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]' + [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,16 +133,18 @@ include("test_functions.jl") @test system.conditions[3].dudt_nodes == prob3.conditions.dudt_nodes @test system.conditions[4].dudt_nodes == prob4.conditions.dudt_nodes @test system.conditions[5].dudt_nodes == prob5.conditions.dudt_nodes - @test system.functions == (prob1.conditions.functions..., + @test system.functions == ( + prob1.conditions.functions..., prob2.conditions.functions..., prob3.conditions.functions..., prob4.conditions.functions..., - prob5.conditions.functions...) + prob5.conditions.functions..., + ) steady_system = SteadyFVMProblem(system) @inferred SteadyFVMProblem(system) @test FVM.eval_flux_function(steady_system, x, y, t, _α, _β, _γ) == - ntuple(_ -> (qx, qy), 5) + 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) @@ -190,10 +192,14 @@ 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 @@ -202,10 +208,12 @@ end @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) + Φ_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) + Ψ_prob.conditions.dirichlet_nodes, Ψ_prob.conditions.dudt_nodes + ) syscond = (cond1, cond2) @test prob.conditions == syscond @test prob.cnum_fncs == (0, 4) @@ -233,9 +241,9 @@ end x, y, t, u = rand(4) @test Φ_prob.conditions.functions[fidx](x, y, t, u) == - prob.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) + 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) @@ -245,9 +253,9 @@ end x, y, t, u = rand(4) @test Ψ_prob.conditions.functions[fidx](x, y, t, u) == - prob.functions[fidx + 4](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) + 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) @@ -257,9 +265,9 @@ end x, y, t, u = rand(4) @test Φ_prob.conditions.functions[fidx](x, y, t, u) == - prob.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) + 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) @@ -269,9 +277,9 @@ end x, y, t, u = rand(4) @test Ψ_prob.conditions.functions[fidx](x, y, t, u) == - prob.functions[fidx + 4](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) + 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 3d15971..0b9191c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,7 +7,7 @@ ct() = Dates.format(now(), "HH:MM:SS") 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 + return @testset verbose = true "Example: $name" begin Base.include(mod, filename) end end @@ -46,8 +46,8 @@ 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" - ] # do it manually just to make it easier for testing individual files rather than in a loop, e.g. one like + "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 @testset "Example: $file" begin @@ -55,7 +55,7 @@ end end end =# - @test length(files) == length(file_names) # make sure we didn't miss any + @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 @@ -81,7 +81,7 @@ 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 diff --git a/test/test_functions.jl b/test/test_functions.jl index 61aef1f..596169d 100644 --- a/test/test_functions.jl +++ b/test/test_functions.jl @@ -95,10 +95,12 @@ function example_tri_rect() return tri end -function example_problem(idx = 1; +function example_problem( + idx = 1; tri = example_tri_rect(), mesh = FVMGeometry(tri), - initial_condition = rand(DelaunayTriangulation.num_solid_vertices(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 @@ -108,10 +110,13 @@ function example_problem(idx = 1; parameters = (0.5, 0.2, 0.3, 0.4) 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) + 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] + 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) @@ -119,16 +124,18 @@ function example_problem(idx = 1; source_parameters = 1.5 initial_time = 2.0 final_time = 5.0 - prob = FVMProblem(mesh, BCs, ICs; + prob = FVMProblem( + mesh, BCs, ICs; flux_function, flux_parameters, source_function, source_parameters, initial_condition, initial_time, - final_time) + final_time + ) return prob, tri, mesh, BCs, ICs, flux_function, flux_parameters, - source_function, source_parameters, initial_condition + source_function, source_parameters, initial_condition end function example_bc_ic_setup(; nothing_dudt = false) @@ -182,8 +189,10 @@ function example_bc_ic_setup(; nothing_dudt = false) 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) +function test_bc_conditions!( + tri, conds, t, + 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) @@ -203,7 +212,7 @@ function test_bc_conditions!(tri, conds, t, 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) + 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 @@ -218,7 +227,7 @@ function test_bc_conditions!(tri, conds, t, 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) + 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 @@ -230,7 +239,7 @@ function test_bc_conditions!(tri, conds, t, @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) + 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 @@ -241,7 +250,7 @@ function test_bc_conditions!(tri, conds, t, @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) + 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 @@ -249,12 +258,16 @@ function test_bc_conditions!(tri, conds, t, return nothing end -function test_bc_ic_conditions!(tri, conds, t, +function test_bc_ic_conditions!( + tri, conds, t, dirichlet_nodes, dudt_nodes, constrained_edges, neumann_edges, - ics) + 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) + test_bc_conditions!( + tri, conds, t, dirichlet_nodes, + dudt_nodes, constrained_edges, neumann_edges, nif + ) for (i, idx) in ics.dirichlet_nodes @test FiniteVolumeMethod.has_dirichlet_nodes(conds) @test FiniteVolumeMethod.get_dirichlet_nodes(conds) == conds.dirichlet_nodes @@ -266,7 +279,7 @@ function test_bc_ic_conditions!(tri, conds, t, @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) + 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 @@ -280,11 +293,12 @@ function test_bc_ic_conditions!(tri, conds, t, @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) + 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 end + return end function example_diffusion_problem() @@ -324,7 +338,7 @@ function example_heat_convection_problem() k = 237.0 T₀ = 10.0 T∞ = 10.0 - α = 80e-6 + α = 80.0e-6 q = 10.0 h = 25.0 tri = triangulate_rectangle(0, L, 0, L, 200, 200; single_boundary = false) @@ -333,7 +347,7 @@ function example_heat_convection_problem() right_wall = (x, y, t, T, p) -> zero(T) top_wall = (x, y, t, T, p) -> -p.α * p.h / p.k * (p.T∞ - T) left_wall = (x, y, t, T, p) -> zero(T) - bc_fncs = (bot_wall, right_wall, top_wall, left_wall) # the order is important + 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) right_parameters = nothing @@ -349,11 +363,13 @@ function example_heat_convection_problem() final_time = 2000.0 f = (x, y) -> T₀ initial_condition = [f(x, y) for (x, y) in DelaunayTriangulation.each_point(tri)] - prob = FVMProblem(mesh, BCs; + prob = FVMProblem( + mesh, BCs; flux_function, flux_parameters, initial_condition, - final_time) + final_time + ) return prob end @@ -361,7 +377,7 @@ 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₉ = 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 @@ -370,18 +386,19 @@ function test_shape_function_coefficients(prob, u) xi, yi = get_point(prob.mesh.triangulation, i) xj, yj = get_point(prob.mesh.triangulation, j) xk, yk = get_point(prob.mesh.triangulation, k) - @test α * xi + β * yi + γ ≈ ui atol = 1e-9 - @test α * xj + β * yj + γ ≈ uj atol = 1e-9 - @test α * xk + β * yk + γ ≈ uk atol = 1e-9 + @test α * xi + β * yi + γ ≈ ui atol = 1.0e-9 + @test α * xj + β * yj + γ ≈ uj atol = 1.0e-9 + @test α * xk + β * yk + γ ≈ uk atol = 1.0e-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) - @test a ≈ α atol = 1e-9 - @test b ≈ β atol = 1e-9 - @test c ≈ γ atol = 1e-9 + c = FVM.get_shape_function_coefficients(prob.mesh.triangle_props[T], T, u, prob) + @test a ≈ α atol = 1.0e-9 + @test b ≈ β atol = 1.0e-9 + @test c ≈ γ atol = 1.0e-9 @inferred FVM.get_shape_function_coefficients(prob.mesh.triangle_props[T], T, u, prob) end + return end function test_get_flux(prob, u, t) @@ -389,7 +406,7 @@ function test_get_flux(prob, u, t) 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₉ = 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]] @@ -406,14 +423,16 @@ function test_get_flux(prob, u, t) @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 + FVM.get_flux(prob, prob.mesh.triangle_props[T], α, β, γ, t, edge_index) atol = 1.0e-9 @inferred FVM.get_flux( - prob, prob.mesh.triangle_props[T], α, β, γ, t, edge_index) + 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)) @inferred FVM.get_fluxes(prob, prob.mesh.triangle_props[T], α, β, γ, t) end + return end function test_get_boundary_flux(prob, u, t, is_diff = true) @@ -431,12 +450,12 @@ function test_get_boundary_flux(prob, u, t, is_diff = true) T = (k, i, j) end s₁, s₂, s₃, s₄, s₅, s₆, s₇, - s₈, s₉ = prob.mesh.triangle_props[T].shape_function_coefficients + 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]] if is_diff - # First edge + # First edge x, y = (px + (px + qx) / 2) / 2, (py + (py + qy) / 2) / 2 nx, ny = (qy - py) / norm(p .- q), -(qx - px) / norm(p .- q) _qx, _qy = prob.flux_function(x, y, t, α, β, γ, prob.flux_parameters) @@ -444,23 +463,27 @@ function test_get_boundary_flux(prob, u, t, is_diff = true) @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 + FVM._get_boundary_flux( + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ + ) * + norm(p .- (p .+ q) ./ 2) atol = 1.0e-6 @inferred FVM._get_boundary_flux( - prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ) - # Second edge + 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) + 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 + γ) + 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 @@ -470,11 +493,13 @@ function test_get_boundary_flux(prob, u, t, is_diff = true) 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) + 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 + γ) + 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) @@ -483,17 +508,20 @@ function test_get_boundary_flux(prob, u, t, is_diff = true) 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) + 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 + γ) + prob, x, y, t, α, β, γ, nx, ny, i, j, α * x + β * y + γ + ) end # Compare _q1, _q2 = FVM.get_boundary_fluxes(prob, α, β, γ, e..., t) @test q1 ≈ _q1 @test q2 ≈ _q2 end + return end function test_single_triangle(prob, u, t) @@ -503,24 +531,28 @@ function test_single_triangle(prob, u, t) 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₉ = 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) duj = -(-q1 + q2) duk = -(-q2 + q3) FVM.fvm_eqs_single_triangle!(du, u, prob, t, T) - @test du[T[1]] ≈ dui atol = 1e-9 - @test du[T[2]] ≈ duj atol = 1e-9 - @test du[T[3]] ≈ duk atol = 1e-9 + @test du[T[1]] ≈ dui atol = 1.0e-9 + @test du[T[2]] ≈ duj atol = 1.0e-9 + @test du[T[3]] ≈ duk atol = 1.0e-9 end + return end function test_source_contribution(prob, u, t) @@ -534,6 +566,7 @@ function test_source_contribution(prob, u, t) @test du[i] ≈ _du @inferred FVM.get_source_contribution(prob, u, t, i) end + return end function _is_on_square(p, L) @@ -590,16 +623,16 @@ function get_dudt_val(prob, u, t, i, is_diff = true) _q = (-α / 9, -β / 9) int += L * (_q[1] * nx + _q[2] * ny) elseif !is_diff && !_both_on_same_edge(p, q, 1.0)[1] - _q = (-80e-6 * α, -80e-6 * β) + _q = (-80.0e-6 * α, -80.0e-6 * β) int += L * (_q[1] * nx + _q[2] * ny) else _, idx = _both_on_same_edge(p, q, 1.0) if idx == 1 - _q = -80e-6 * 10.0 / 237.0 + _q = -80.0e-6 * 10.0 / 237.0 elseif idx == 2 _q = 0.0 elseif idx == 3 - _q = -80e-6 * 25.0 / 237.0 * (10.0 - (α * mx + β * my + γ)) + _q = -80.0e-6 * 25.0 / 237.0 * (10.0 - (α * mx + β * my + γ)) elseif idx == 4 _q = 0.0 end @@ -608,17 +641,19 @@ function get_dudt_val(prob, u, t, i, is_diff = true) end end dudt = prob.source_function(get_point(tri, i)..., t, u[i], prob.source_parameters) - - int / mesh.cv_volumes[i] + 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 = [ + 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 + return @test dudt ≈ dudt_fnc end function test_compute_flux(_prob, steady, system, steady_system) @@ -653,9 +688,13 @@ function test_compute_flux(_prob, steady, system, steady_system) 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))) + (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)) @@ -687,8 +726,11 @@ 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)) @@ -701,11 +743,14 @@ function test_compute_flux(_prob, steady, system, steady_system) end end end + return 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) @@ -713,7 +758,7 @@ function test_jacobian_sparsity(prob::FVMProblem) A[i, j] = 1.0 end end - @test A == FVM.jacobian_sparsity(prob) + return @test A == FVM.jacobian_sparsity(prob) end function test_jacobian_sparsity(prob::FVMSystem{N}) where {N} @@ -744,6 +789,6 @@ function test_jacobian_sparsity(prob::FVMSystem{N}) where {N} end end end - @test A == FVM.jacobian_sparsity(prob) + return @test A == FVM.jacobian_sparsity(prob) end test_jacobian_sparsity(prob::SteadyFVMProblem) = test_jacobian_sparsity(prob.problem)