diff --git a/CITATION.cff b/CITATION.cff index 80bc08a6d..d66101c83 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -12,6 +12,8 @@ authors: orcid: 'https://orcid.org/0000-0003-4432-2434' - given-names: Patrick family-names: Jaap + - given-names: Daniel + family-names: Runge - given-names: Dilara family-names: Abdel - given-names: Jan @@ -25,5 +27,3 @@ authors: doi: "10.5281/zenodo.3529808" license: MIT repository-code: "https://github.com/j-fu/VoronoiFVM.jl" - - diff --git a/Project.toml b/Project.toml index 2192e0442..493299709 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "VoronoiFVM" uuid = "82b139dc-5afc-11e9-35da-9b9bdfd336f3" -authors = ["Jürgen Fuhrmann ", "Patrick Jaap", "Dilara Abdel", "Jan Weidner", "Alexander Seiler", "Patricio Farrell", "Matthias Liero"] -version = "2.0.2" +authors = ["Jürgen Fuhrmann ", "Patrick Jaap", "Daniel Runge", "Dilara Abdel", "Jan Weidner", "Alexander Seiler", "Patricio Farrell", "Matthias Liero"] +version = "2.1" [deps] BandedMatrices = "aae01518-5342-5314-be14-df237901396f" @@ -28,13 +28,20 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" +[weakdeps] +ExtendableFEMBase = "12fb9182-3d4c-4424-8fd1-727a0899810c" + +[extensions] +VoronoiFVMExtendableFEMBaseExt = "ExtendableFEMBase" + [compat] BandedMatrices = "0.17,1" Colors = "0.12" CommonSolve = "0.2" DiffResults = "1" DocStringExtensions = "0.8,0.9" -ExtendableGrids = "1.9.2" +ExtendableFEMBase = "0.7.0" +ExtendableGrids = "1.10.1" ExtendableSparse = "1.5.1" ForwardDiff = "0.10.35" GridVisualize = "0.5.2,0.6.1,1" diff --git a/docs/Project.toml b/docs/Project.toml index c30a9aca5..45492c9bf 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -9,6 +9,8 @@ DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" ExampleJuggler = "3bbe58f8-ed81-4c4e-a134-03e85fcf4a1a" +ExtendableFEM = "a722555e-65e0-4074-a036-ca7ce79a4aed" +ExtendableFEMBase = "12fb9182-3d4c-4424-8fd1-727a0899810c" ExtendableGrids = "cfc395e8-590f-11e8-1f13-43a2532b2fa8" ExtendableSparse = "95c220a8-a1cf-11e9-0c77-dbfce5f500b3" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" diff --git a/docs/make.jl b/docs/make.jl index 042edf50c..7acb4b29b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,6 +1,8 @@ using Documenter, ExampleJuggler, PlutoStaticHTML, VoronoiFVM, DocumenterCitations using ExtendableGrids, GridVisualize, LinearAlgebra, RecursiveArrayTools, SciMLBase +using ExtendableFEMBase, ExtendableFEM + using OrdinaryDiffEqBDF, OrdinaryDiffEqLowOrderRK, OrdinaryDiffEqRosenbrock, OrdinaryDiffEqSDIRK, OrdinaryDiffEqTsit5 function make(; with_examples = true, @@ -33,7 +35,8 @@ function make(; with_examples = true, "misc.md", "internal.md", "allindex.md", - "devel.md",] + "devel.md", + "extensions.md"] ] @@ -65,10 +68,14 @@ function make(; with_examples = true, push!(pages, "Examples" => module_examples) end - makedocs(; sitename = "VoronoiFVM.jl", - modules = [VoronoiFVM], - plugins = [bib], - checkdocs = :all, + makedocs(; sitename="VoronoiFVM.jl", + modules=[ + VoronoiFVM, + # define extension modules manually: https://github.com/JuliaDocs/Documenter.jl/issues/2124#issuecomment-1557473415 + Base.get_extension(VoronoiFVM, :VoronoiFVMExtendableFEMBaseExt) + ], + plugins=[bib], + checkdocs=:all, clean = false, doctest = false, authors = "J. Fuhrmann", diff --git a/docs/src/extensions.md b/docs/src/extensions.md new file mode 100644 index 000000000..8766ea490 --- /dev/null +++ b/docs/src/extensions.md @@ -0,0 +1,9 @@ +# [`ExtendableFEMBase`](https://github.com/chmerdon/ExtendableFEMBase.jl/) Extension + +The extension for [`ExtendableFEMBase`](https://github.com/chmerdon/ExtendableFEMBase.jl/) extends the functions below for solution types of [`ExtendableFEM`](https://github.com/chmerdon/ExtendableFEM.jl/). +The name of the extension originates from the solution type `FEVectorBlock` that is defined in `ExtendableFEMBase`. + +```@docs +edgevelocities(grid::ExtendableGrid, vel::FEVectorBlock; kwargs...) +bfacevelocities(grid::ExtendableGrid, vel::FEVectorBlock; kwargs...) +``` diff --git a/docs/src/internal.md b/docs/src/internal.md index b2c9cba8d..2c0a56913 100644 --- a/docs/src/internal.md +++ b/docs/src/internal.md @@ -121,6 +121,8 @@ prepare_diffeq! ## Misc tools ```@docs +VoronoiFVM.integrate(::Type{<:Cartesian2D}, coordl, coordr, hnormal, velofunc; kwargs...) +VoronoiFVM.integrate(::Type{<:Cylindrical2D}, coordl, coordr, hnormal, velofunc; kwargs...) VoronoiFVM.doolittle_ludecomp! VoronoiFVM.doolittle_lusolve! VoronoiFVM.bernoulli_horner diff --git a/docs/src/post.md b/docs/src/post.md index 737c04289..67e06cccb 100644 --- a/docs/src/post.md +++ b/docs/src/post.md @@ -36,7 +36,10 @@ nodevolumes ## Solution integrals ```@docs -VoronoiFVM.integrate +VoronoiFVM.integrate(system::VoronoiFVM.AbstractSystem, tf::Vector, U::AbstractMatrix; kwargs...) +VoronoiFVM.integrate(system::VoronoiFVM.AbstractSystem{Tv, Tc, Ti, Tm}, F::Function, U::AbstractMatrix{Tu}; boundary = false, data = system.physics.data) where {Tu, Tv, Tc, Ti, Tm} +VoronoiFVM.integrate(system::VoronoiFVM.AbstractSystem, U::AbstractMatrix; kwargs...) +VoronoiFVM.integrate(system::VoronoiFVM.AbstractSystem, F::Function, U::AbstractMatrix; boundary, data) VoronoiFVM.edgeintegrate ``` diff --git a/examples/Example240_FiniteElementVelocities.jl b/examples/Example240_FiniteElementVelocities.jl new file mode 100644 index 000000000..a666e879c --- /dev/null +++ b/examples/Example240_FiniteElementVelocities.jl @@ -0,0 +1,297 @@ +#= + +# 240: 2D Convection in quadratic stagnation flow velocity field +([source code](@__SOURCE_URL__)) + +Solve the equation + +```math +-\nabla \cdot ( D \nabla u - \mathbf{v} u) = 0 +``` +in $\Omega=(0,L)\times (0,H)$ with a homogeneous Neumann boundary condition +at $x=0$, an outflow boundary condition at $x=L$, a Dirichlet inflow +condition at $y=H$, and a homogeneous Dirichlet boundary condition +on part of $y=0$. + +The analytical expression for the (quadratic variant of the) velocity field +is $\mathbf{v}(x,y)=(x^2,-2xy)$ in cartesian coordinates and (for the linear variant) +$\mathbf{v}(r,z)=(r,-2z)$ in cylindrical coordinates, i.e. +where the system is solved on $\Omega$ to represent a solution on the solid +of revolution arising from rotating $\Omega$ around $x=0$. + +We compute the solution $u$ in both coordinate systems where $\mathbf{v}$ is given +as an analytical expression and as a finite element interpolation onto +the grid of $\Omega$. +=# + +module Example240_FiniteElementVelocities +using Printf +using ExtendableFEMBase +using ExtendableFEM +using VoronoiFVM +using ExtendableGrids +using GridVisualize +using LinearAlgebra + +function stagnation_flow_cartesian(x, y) + return (x^2, -2x * y) +end + +# In the cylindrical case: since the reconstruction space $\mathtt{HDIVBDM2}$ +# is only quadratic, but we have to reconstruct $r \, \mathbf{v}(r,z)$ for a +# (reconstructed) divergence-free solution, +# we can only resolve at most the linear case exactly. +function stagnation_flow_cylindrical(r, z) + return (r, -2 * z) +end + +function inflow_cylindrical(u, qpinfo) + x = qpinfo.x + u .= stagnation_flow_cylindrical(x[1], x[2]) +end + +function inflow_cartesian(u, qpinfo) + x = qpinfo.x + u .= stagnation_flow_cartesian(x[1], x[2]) +end + +function flux!(f, u, edge, data) + vd = data.evelo[edge.index] / data.D + bp = fbernoulli(vd) + bm = fbernoulli(-vd) + f[1] = data.D * (bp * u[1] - bm * u[2]) +end + +function bconditions!(f, u, node, data) + ## catalytic Dirichlet condition at y=0 + if node.region == 5 + boundary_dirichlet!(f, u, node, 1, node.region, 0.0) + end + + ## outflow condition at x = L + if node.region == 2 + f[1] = data.bfvelo[node.ibnode, node.ibface] * u[1] + end + + ## inflow condition at y = H + if node.region == 3 + boundary_dirichlet!(f, u, node, 1, node.region, data.cin) + end +end + +mutable struct Data + D::Float64 + cin::Float64 + evelo::Vector{Float64} + bfvelo::Matrix{Float64} + + Data() = new() +end + +#= +Calculate the analytical or FEM solution to the stagnation point flow field $\mathbf{v}$ and +use this as input to solve for the species concentration $u$ of the corresponding +convection-diffusion system. + +The passed flags regulate the following behavior: + - `cylindrical_grid`: if true, calculates both the velocity field $\mathbf{v}(r,z)$ + and the species concentration $u(r,z)$ in cylindrical coordinates, assuming + rotationally symmetric solutions for both. + - `usefem`: if true, calculates the velocity field $\mathbf{v}$ using the + finite element method provided by [`ExtendableFEM`](https://github.com/chmerdon/ExtendableFEM.jl). + - `reconst`: if true, interpolates the FEM-calculated + velocity field onto a "reconstruction" finite element space that + provides an exactly divergence-free solution. In a cylindrical grid, + this returns not $\mathbf{v}(r,z)$, but $r \, \mathbf{v}(r,z)$ as the velocity. + - `use_different_grids`: if true, calculates the FEM solution of the + velocity field on a uniform `flowgrid` and the species concentration + on an adaptively refined `chemgrid` while still interpolating the + calculated velocity correctly onto the `chemgrid`. +=# +function main(; + cylindrical_grid = false, usefem = true, reconst = cylindrical_grid, use_different_grids = false, nref = 1, + Plotter = nothing, μ = 1.0e-02, D = 0.01, cin = 1.0, assembly = :edgewise, + interpolation_eps = 1.0e-09) + H = 1.0 + L = 5.0 + + if cylindrical_grid + coord_system = Cylindrical2D + else + coord_system = Cartesian2D + end + + flowgrid = simplexgrid(range(0, L; length = 20 * 2^nref), + range(0, H; length = 5 * 2^nref)) + + if use_different_grids + h_fine = 1.0e-01 + X_bottom = geomspace(0.0, L / 2, 5.0e-01, h_fine) + X_cat = range(L / 2, L; step = h_fine) + chemgrid = simplexgrid([X_bottom; X_cat[2:end]], + geomspace(0.0, H, 1.0e-03, 1.0e-01)) + bfacemask!(chemgrid, [L / 2, 0.0], [3 * L / 4, 0.0], 5) + else + chemgrid = deepcopy(flowgrid) + bfacemask!(chemgrid, [L / 2, 0.0], [3 * L / 4, 0.0], 5) + end + + if usefem + velocity = compute_velocity( + flowgrid, cylindrical_grid, reconst, μ; interpolation_eps) + DivIntegrator = L2NormIntegrator([div(1)]; quadorder = 2 * 2, resultdim = 1) + div_v = sqrt(sum(evaluate(DivIntegrator, [velocity]))) + @info "||div(R(v))||_2 = $(div_v)" + else + if cylindrical_grid + velocity = stagnation_flow_cylindrical + else + velocity = stagnation_flow_cartesian + end + end + + if cylindrical_grid + analytical_velocity = stagnation_flow_cylindrical + else + analytical_velocity = stagnation_flow_cartesian + end + + ## only the chemgrid needs its CoordinateSystem adjusted + ## since the velocity calculation works by adjusting + ## the kernels for the Stokes operator directly while + ## the finite volume machinery relies upon the CoordinateSystem + ## for selecting the correct geometrical factors for the + ## Voronoi cell contributions + if cylindrical_grid + chemgrid[CoordinateSystem] = Cylindrical2D + end + + data = Data() + data.D = D + data.cin = cin + + evelo = edgevelocities(chemgrid, velocity; reconst) + bfvelo = bfacevelocities(chemgrid, velocity; reconst) + + data.evelo = evelo + data.bfvelo = bfvelo + + physics = VoronoiFVM.Physics(; flux = flux!, breaction = bconditions!, data) + sys = VoronoiFVM.System(chemgrid, physics; assembly) + enable_species!(sys, 1, [1]) + + sol = solve(sys; inival = 0.0) + + fvm_divs = VoronoiFVM.calc_divergences(sys, evelo, bfvelo) + @info "||div(v)||_∞ = $(norm(fvm_divs, Inf))" + + vis = GridVisualizer(; Plotter = Plotter) + + scalarplot!(vis[1, 1], chemgrid, sol[1, :]; flimits = (0, cin + 1.0e-5), + show = true) + + minmax = extrema(sol) + @info "Minimal/maximal values of concentration: $(minmax)" + + return sol, evelo, bfvelo, minmax +end + +using Test +function runtests() + cin = 1.0 + for cylindrical_grid in [false, true] + sol_analytical, evelo_analytical, bfvelo_analytical, minmax_analytical = main(; + cylindrical_grid, cin, usefem = false) + sol_fem, evelo_fem, bfvelo_fem, minmax_fem = main(; + cylindrical_grid, cin, usefem = true) + @test norm(evelo_analytical .- evelo_fem, Inf) ≤ 1.0e-11 + @test norm(bfvelo_analytical .- bfvelo_fem, Inf) ≤ 1.0e-09 + @test norm(sol_analytical .- sol_fem, Inf) ≤ 1.0e-10 + @test norm(minmax_analytical .- [0.,cin], Inf) ≤ 1.0e-15 + @test norm(minmax_fem .- [0.,cin], Inf) ≤ 1.0e-11 + end +end + +function compute_velocity( + flowgrid, cylindrical_grid, reconst, μ = 1.0e-02; interpolation_eps = 1.0e-10) + ## define finite element spaces + FE_v, FE_p = H1P2B{2, 2}, L2P1{1} + reconst_FEType = HDIVBDM2{2} + FES = [FESpace{FE_v}(flowgrid), FESpace{FE_p}(flowgrid; broken = true)] + + ## describe problem + Problem = ProblemDescription("incompressible Stokes problem") + v = Unknown("v"; name = "velocity") + p = Unknown("p"; name = "pressure") + assign_unknown!(Problem, v) + assign_unknown!(Problem, p) + + ## assign stokes operator + assign_operator!(Problem, + BilinearOperator( + kernel_stokes!, cylindrical_grid ? [id(v), grad(v), id(p)] : [grad(v), id(p)]; + bonus_quadorder = 2, store = false, + params = [μ, cylindrical_grid])) + + ## assign Dirichlet boundary conditions on all boundary regions to + ## enforce match with analytical solution + if cylindrical_grid + assign_operator!( + Problem, InterpolateBoundaryData(v, inflow_cylindrical; regions = [1, 2, 3, 4])) + else + assign_operator!( + Problem, InterpolateBoundaryData(v, inflow_cartesian; regions = [1, 2, 3, 4])) + end + + velocity_solution = solve(Problem, FES) + + ## ensure divergence free solution by projecting onto reconstruction spaces + FES_reconst = FESpace{reconst_FEType}(flowgrid) + R = FEVector(FES_reconst) + if reconst + if cylindrical_grid + lazy_interpolate!(R[1], velocity_solution, [id(v)]; postprocess = multiply_r, + bonus_quadorder = 2, eps = interpolation_eps) + else + lazy_interpolate!( + R[1], velocity_solution, [id(v)]; + bonus_quadorder = 2, eps = interpolation_eps) + end + else + return velocity_solution[1] + end + + return R[1] +end + +function kernel_stokes!(result, u_ops, qpinfo) + μ = qpinfo.params[1] + cylindrical_grid = qpinfo.params[2] + if cylindrical_grid > 0 + r = qpinfo.x[1] + u, ∇u, p = view(u_ops, 1:2), view(u_ops, 3:6), view(u_ops, 7) + result[1] = μ / r * u[1] - p[1] + result[2] = 0 + result[3] = μ * r * ∇u[1] - r * p[1] + result[4] = μ * r * ∇u[2] + result[5] = μ * r * ∇u[3] + result[6] = μ * r * ∇u[4] - r * p[1] + result[7] = -(r * (∇u[1] + ∇u[4]) + u[1]) + else + ∇u, p = view(u_ops, 1:4), view(u_ops, 5) + result[1] = μ * ∇u[1] - p[1] + result[2] = μ * ∇u[2] + result[3] = μ * ∇u[3] + result[4] = μ * ∇u[4] - p[1] + result[5] = -(∇u[1] + ∇u[4]) + end + return nothing +end + +function multiply_r(result, input, qpinfo) + x = qpinfo.x + result .= input * x[1] + return nothing +end + +end diff --git a/ext/VoronoiFVMExtendableFEMBaseExt.jl b/ext/VoronoiFVMExtendableFEMBaseExt.jl new file mode 100644 index 000000000..c1f4b134d --- /dev/null +++ b/ext/VoronoiFVMExtendableFEMBaseExt.jl @@ -0,0 +1,321 @@ +module VoronoiFVMExtendableFEMBaseExt + +using StaticArrays: @MVector +using VoronoiFVM +using ExtendableFEMBase: CellFinder, evaluate_bary!, + FEVectorBlock, initialize!, Identity, + PointEvaluator, SegmentIntegrator + +using ExtendableGrids: ExtendableGrid, Cartesian2D, CoordinateSystem, Cylindrical2D, + Edge1D, eval_trafo!, gFindLocal!, + mapderiv!, postprocess_xreftest!, update_trafo! + +using LinearAlgebra: dot + +using Base: fill! + +using DocStringExtensions: DocStringExtensions, SIGNATURES + +id(u) = (u, Identity) + + +dist(p1,p2) = sqrt( (p1[1] - p2[1] )^2 + (p1[2] -p2[2])^2 ) + +function iscloser(pint, p1, p2, eps) + return dist(pint,p2) < dist(p2,p1) - eps +end + +#This is the FEVectorBlock with lots of info from ExtendableFEMBase +#to execute integration along given segments +struct AugmentedFEVectorBlock{TVB, TSE, TPE, TCF, TFG} + vblock::TVB + seg_integrator::TSE + point_evaluator::TPE + cellfinder::TCF + flowgrid::TFG + + bp1::Vector{Float64} + bp2::Vector{Float64} + result::Vector{Float64} + summand::Vector{Float64} + pint::Vector{Float64} + bpint::Vector{Float64} + bary::Vector{Float64} + + +end + +function AugmentedFEVectorBlock(vel, seg_integrator, point_evaluator, cf, flowgrid) + + + + bp1 = zeros(Float64, 3) + bp2 = zeros(Float64, 3) + result = zeros(Float64, 2) + summand = zeros(Float64, 2) + pint = zeros(Float64, 2) + bpint = zeros(Float64, 3) + bary = [1 / 3, 1 / 3, 1 / 3] + + + AugmentedFEVectorBlock(vel, seg_integrator, point_evaluator, cf, flowgrid, + bp1, + bp2, + result, + summand, + pint, + bpint, + bary) + + +end + +function multiply_r(result, input, qpinfo) + x = qpinfo.x + result .= input * x[1] + return nothing +end + +function prepare_segment_integration(vel; axisymmetric=false, reconst=false, kwargs...) + flowgrid = vel.FES.xgrid + + # reference mappings not implemented for other coord types + cartesian!(flowgrid) + + if axisymmetric + if reconst + seg_integrator = SegmentIntegrator(Edge1D, [id(1)]) + else + seg_integrator = SegmentIntegrator(Edge1D, multiply_r, [id(1)]; bonus_quadorder=1) + end + else + seg_integrator = SegmentIntegrator(Edge1D, [id(1)]) + end + + initialize!(seg_integrator, [vel]) + point_evaluator = PointEvaluator([id(1)], [vel]) + + cellfinder = CellFinder(flowgrid) + + if axisymmetric + circular_symmetric!(flowgrid) + end + + return seg_integrator, point_evaluator, cellfinder, flowgrid +end + +""" +$(SIGNATURES) + +Compute [`VoronoiFVM.edgevelocities`](@ref) for a finite element flow field computed +by [`ExtendableFEM`](https://github.com/chmerdon/ExtendableFEM.jl). +""" +function VoronoiFVM.edgevelocities(grid::ExtendableGrid, vel::FEVectorBlock; kwargs...) + # construct an augmented type to gather precomputed information + # in order to pass it to the repeated integrate call in VoronoiFVM.edgevelocities + axisymmetric = grid[CoordinateSystem] <: Cylindrical2D ? true : false + seg_integrator, point_evaluator, cf, flowgrid = prepare_segment_integration(vel; axisymmetric, kwargs...) + aug_fevec_block = AugmentedFEVectorBlock(vel, seg_integrator, point_evaluator, cf, flowgrid) + velovec = VoronoiFVM.edgevelocities(grid, aug_fevec_block; axisymmetric, kwargs...) + + if axisymmetric + circular_symmetric!(flowgrid) + end + + return velovec +end + +""" +$(SIGNATURES) + +Compute [`VoronoiFVM.bfacevelocities`](@ref) for a finite element flow field computed +by [`ExtendableFEM`](https://github.com/chmerdon/ExtendableFEM.jl). +""" +function VoronoiFVM.bfacevelocities(grid::ExtendableGrid, vel::FEVectorBlock; kwargs...) + axisymmetric = grid[CoordinateSystem] <: Cylindrical2D ? true : false + seg_integrator, point_evaluator, cf, flowgrid = prepare_segment_integration(vel; axisymmetric, kwargs...) + aug_fevec_block = AugmentedFEVectorBlock(vel, seg_integrator, point_evaluator, cf, flowgrid) + + velovec = VoronoiFVM.bfacevelocities(grid, aug_fevec_block; axisymmetric, kwargs...) + + if axisymmetric + circular_symmetric!(flowgrid) + end + + return velovec +end + +# We need two explicitly type-annotated methods for a working method specialization. +# This one... +function VoronoiFVM.integrate(::Type{<:Cartesian2D}, p1, p2, hnormal, aug_vec_block::AugmentedFEVectorBlock; kwargs...) + _integrate_along_segments(p1, p2, hnormal, aug_vec_block; kwargs...) +end + +# ... and that one. +function VoronoiFVM.integrate(::Type{<:Cylindrical2D}, p1, p2, hnormal, aug_vec_block::AugmentedFEVectorBlock; kwargs...) + _integrate_along_segments(p1, p2, hnormal, aug_vec_block; kwargs...) +end + +# compute the path integral for the velocity in aug_vec_block between p1 and p2 by +# incrementally walking through each cell in the grid between p1 and p2 +# and summing up each cell's contribution +function _integrate_along_segments(p1, p2, hnormal, aug_vec_block::AugmentedFEVectorBlock{TVB, TSE, TPE, TCF, TFG}; interpolate_eps=1.0e-12, axisymmetric=false, kwargs...) where {TVB, TSE, TPE, TCF, TFG} + edge_length = dist(p1,p2) + avg_r = (p1[1] + p2[1]) / 2 + (; bp1, result, summand, bp2, pint, bpint, bary)= aug_vec_block + + + if axisymmetric && avg_r < eps() + return 0 + end + + CF = aug_vec_block.cellfinder + icell::Int = gFindLocal!(bp1, CF, p1; eps=interpolate_eps) + if edge_length ≤ interpolate_eps + point_evaluator = aug_vec_block.point_evaluator + evaluate_bary!(p2, point_evaluator, bp1, icell) + if axisymmetric + return dot(p2, hnormal) / (avg_r) + else + return dot(p2, hnormal) + end + end + + SI = aug_vec_block.seg_integrator + + + xCellFaces::Matrix{Int} = CF.xCellFaces + xFaceCells::Matrix{Int} = CF.xFaceCells + facetogo = CF.facetogo + cx = CF.cx + icell_new = icell + prevcell = icell + L2G = CF.L2G4EG[1] + L2Gb::Vector{Float64} = L2G.b + invA = CF.invA + + t = 0.0 + + p1_temp2 = @MVector zeros(2) + p1_temp3 = @MVector zeros(3) + + function calc_barycentric_coords!(bp, p) + for j = 1:2 + cx[j] = p[j] - L2Gb[j] + end + + fill!(bp, 0) + for j = 1:2, k = 1:2 + bp[k] += invA[j, k] * cx[j] + end + postprocess_xreftest!(bp, CF.xCellGeometries[icell]) + end + + while (true) + + # TODO implement proper emergency guard to avoid indefinite loops + + # first compute the barycentric coordinates of + # p1,p2 + + # update local 2 global map + L2G = CF.L2G4EG[1] + update_trafo!(L2G, icell) + L2Gb = L2G.b + + mapderiv!(invA, L2G, p1) + + calc_barycentric_coords!(bp1, p1) + calc_barycentric_coords!(bp2, p2) + + # if p1 is a node of a triangle, start with + # a cell containing p1 in the direction of (p2-p1) + if count(<=(interpolate_eps), bp1) == 2 # 10^(-13) + @. p1_temp2 = p1 + 10 * interpolate_eps * (p2 - p1) + icell_new = gFindLocal!(bp1, CF, p1_temp2; eps=10 * interpolate_eps, icellstart=icell) #!!! allocates + if icell_new == 0 + # TODO: test the following + # icell_new = gFindBruteForce!(bp1, CF, p1_temp[1:2]) + @warn "icell_new=0!" + end + if icell_new != icell + icell = icell_new + continue + end + end + + # push p1 a little towards the triangle circumcenter + # to avoid it being situated on an edge or node of the + # flowgrid + # (to avoid being stuck on an edge if (p2-p1) is + # on the edge + @. bp1 += interpolate_eps * (bary - bp1) + eval_trafo!(p1, L2G, bp1) + + (λp2min, imin) = findmin(bp2) + + # if λp2min≥0, then p2 is inside icell and we can simply add the + # integral across the line segment between p1 and p2 to the result + + # if not, then p2 is outside of icell and we try to determine + # pint which is the point where icell intersects the line segment + # [p1,p2] - since pint should be on the boundary of icell, + # at least one barycentric coordinate (stored in bpint) should + # be zero yielding an expression for the line segment parameter t. + # this is not necessarily the previous imin and we have to check + # all triangle edges for if going towards that edge actually takes us + # closer to p2 + + if λp2min ≥ -interpolate_eps + SI.integrator(summand, ((p1, p2)), (bp1, bp2), icell) #!!! allocates; probably due to .integrator being Any + result += summand + break + else + # calculate intersection point with corresponding edge + imin = 0 + t = 1.0 + interpolate_eps + pint .= p1 + closestimin = 1 + closestdist = Inf + p1_temp3 .= 0 + + while !iscloser(pint, p1, p2, interpolate_eps) || + (any(x->x<= -interpolate_eps, bpint) || any(x->x>= 1 + interpolate_eps, bpint)) || + dist(bpint,bp1) <= interpolate_eps #!!! .< allocates a temp vector + # check if pint takes us closer to p2 and if bpint is inside the cell *and* if we actually moved with pint from p1 + imin += 1 + if imin == 4 + imin = closestimin + bpint .= p1_temp3 + break + end + t = bp1[imin] / (bp1[imin] - bp2[imin]) + bpint = bp1 + t * (bp2 - bp1) + eval_trafo!(pint, L2G, bpint) + if dist(pint,p2) < closestdist && ((all(x->x>= -interpolate_eps, bpint) && all(x->x<= 1 + interpolate_eps, bpint))) + closestimin = imin + closestdist = dist(pint,p2) + p1_temp3 .= bpint + end + end + eval_trafo!(pint, L2G, bpint) + SI.integrator(summand, ((p1, pint)), (bp1, bpint), icell) + result += summand + + # proceed to next cell along edge of smallest barycentric coord + prevcell = icell + icell = xFaceCells[1, xCellFaces[facetogo[1][imin], icell]] #!!! allocates + icell = icell == prevcell ? xFaceCells[2, xCellFaces[facetogo[1][imin], icell]] : icell #!!! allocates + + p1 .= pint + end + end + + if axisymmetric + return dot(result, hnormal) / (avg_r * edge_length) + else + return dot(result, hnormal) / edge_length + end +end + +end diff --git a/src/vfvm_formfactors.jl b/src/vfvm_formfactors.jl index 9ffad92fb..3aa4f0982 100644 --- a/src/vfvm_formfactors.jl +++ b/src/vfvm_formfactors.jl @@ -326,22 +326,37 @@ end # # TODO: this should be generalized for more quadrules # -function integrate(::Type{<:Cartesian2D}, coordl, coordr, hnormal, velofunc) +""" +$(SIGNATURES) + +This is an internal function to integrate `velofunc` along the edge ``\\sigma=\\overline{\\mathtt{coordl}\\,\\mathtt{coordr}}`` +between the ``x_K`` and ``x_L`` where ``\\mathtt{hnormal}=x_K-x_L`` using [Simpson's Rule](https://en.wikipedia.org/wiki/Simpson%27s_rule). +To be precise, compute for a cartesian coordinate system: +``\\int_{\\sigma} \\mathbf{v} \\cdot \\mathbf{n} \\,\\mathrm{ds} \\lvert x_K - x_L \\rvert / \\lvert\\sigma\\rvert``. +""" +function integrate(::Type{<:Cartesian2D}, coordl, coordr, hnormal, velofunc; kwargs...) wl = 1.0 / 6.0 wm = 2.0 / 3.0 wr = 1.0 / 6.0 - coordm = 0.5 * (coordl + coordr) + coordm=( 0.5*(coordl[1]+coordr[1]), 0.5*(coordl[2]+coordr[2]) ) (vxl, vyl) = velofunc(coordl[1], coordl[2]) (vxm, vym) = velofunc(coordm[1], coordm[2]) (vxr, vyr) = velofunc(coordr[1], coordr[2]) return (wl * vxl + wm * vxm + wr * vxr) * hnormal[1] + (wl * vyl + wm * vym + wr * vyr) * hnormal[2] end -function integrate(::Type{<:Cylindrical2D}, coordl, coordr, hnormal, velofunc) +""" +$(SIGNATURES) + +This is an internal function similar to `integrate(::Type{<:Cartesian2D},...)`, but computes instead +``\\int_{\\sigma} r \\, \\mathbf{v} \\cdot \\mathbf{n} \\,\\mathrm{ds} \\lvert x_K - x_L \\rvert / \\left ( \\lvert\\sigma\\rvert r(\\mathrm{mid}(\\sigma)) \\right )`` +where ``r(\\mathrm{mid}(\\sigma))`` is the ``r``-coordinate of the mid-point of ``\\sigma``. +""" +function integrate(::Type{<:Cylindrical2D}, coordl, coordr, hnormal, velofunc; kwargs...) wl = 1.0 / 6.0 wm = 2.0 / 3.0 wr = 1.0 / 6.0 - coordm = 0.5 * (coordl + coordr) + coordm=( 0.5*(coordl[1]+coordr[1]), 0.5*(coordl[2]+coordr[2]) ) rl = coordl[1] rm = coordm[1] @@ -365,9 +380,11 @@ end """ $(SIGNATURES) -Project velocity onto grid edges, +Project velocity onto grid edges. +That is, we compute the path integrals of the given `velofunc` along the +Voronoi cell edges as provided by [`integrate`](@ref). """ -function edgevelocities(grid, velofunc) +function edgevelocities(grid, velofunc::F ; kwargs...) where F @assert dim_space(grid) < 3 cn = grid[CellNodes] @@ -386,25 +403,26 @@ function edgevelocities(grid, velofunc) velovec[iedge] = -elen * vx end else + hnormal=zeros(2) + p1 = zeros(2) + p2 = zeros(2) for iedge = 1:num_edges(grid) K = en[1, iedge] L = en[2, iedge] - p1 = @MVector zeros(2) - p2 = @MVector zeros(2) - tricircumcenter!(p1, - coord[:, cn[1, ec[1, iedge]]], - coord[:, cn[2, ec[1, iedge]]], - coord[:, cn[3, ec[1, iedge]]]) + @views tricircumcenter!(p1, + coord[:, cn[1, ec[1, iedge]]], + coord[:, cn[2, ec[1, iedge]]], + coord[:, cn[3, ec[1, iedge]]]) if ec[2, iedge] > 0 - tricircumcenter!(p2, - coord[:, cn[1, ec[2, iedge]]], - coord[:, cn[2, ec[2, iedge]]], - coord[:, cn[3, ec[2, iedge]]]) + @views tricircumcenter!(p2, + coord[:, cn[1, ec[2, iedge]]], + coord[:, cn[2, ec[2, iedge]]], + coord[:, cn[3, ec[2, iedge]]]) else - p2 .= 0.5 * (coord[:, K] + coord[:, L]) + @views @. p2 = 0.5 * (coord[:, K] + coord[:, L]) end - hnormal = coord[:, K] - coord[:, L] - velovec[iedge] = integrate(coord_system, p1, p2, hnormal, velofunc) + @views @. hnormal.= coord[:, K] - coord[:, L] + velovec[iedge] = integrate(coord_system, p1, p2, hnormal, velofunc; kwargs...) end end return velovec @@ -413,15 +431,15 @@ end """ $(SIGNATURES) -Project velocity onto boundary face normals +Similar to [`edgevelocities`](@ref), but for boundary faces. """ -function bfacevelocities(grid, velofunc) +function bfacevelocities(grid::ExtendableGrid{Tc,Ti}, velofunc::F ; kwargs...) where {Tc, Ti, F} @assert dim_space(grid) < 3 bfacenodes = grid[BFaceNodes] coord = grid[Coordinates] coord_system = grid[CoordinateSystem] bfacecells = grid[BFaceCells] - bfacenormals = grid[BFaceNormals] + bfacenormals::Matrix{Tc} = grid[BFaceNormals] bfr = grid[BFaceRegions] velovec = zeros(Float64, 2, num_bfaces(grid)) if dim_space(grid) == 1 @@ -430,12 +448,15 @@ function bfacevelocities(grid, velofunc) velovec[ibface] = vx * bfacenormals[1, ibface] end else + p1=zeros(2) + p2=zeros(2) + pm=zeros(2) for ibface = 1:num_bfaces(grid) - p1 = coord[:, bfacenodes[1, ibface]] - p2 = coord[:, bfacenodes[2, ibface]] - pm = 0.5 * (p1 + p2) - velovec[1, ibface] = integrate(coord_system, p1, pm, bfacenormals[:, ibface], velofunc) - velovec[2, ibface] = integrate(coord_system, pm, p2, bfacenormals[:, ibface], velofunc) + @views @. p1 = coord[:, bfacenodes[1, ibface]] + @views @. p2 = coord[:, bfacenodes[2, ibface]] + @views @. pm = 0.5 * (p1 + p2) + @views velovec[1, ibface] = integrate(coord_system, p1, pm, bfacenormals[:, ibface], velofunc; kwargs...) + @views velovec[2, ibface] = integrate(coord_system, pm, p2, bfacenormals[:, ibface], velofunc; kwargs...) end end return velovec diff --git a/test/Project.toml b/test/Project.toml index 7ecd63256..b098812ee 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -9,6 +9,8 @@ DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" ExampleJuggler = "3bbe58f8-ed81-4c4e-a134-03e85fcf4a1a" ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7" +ExtendableFEM = "a722555e-65e0-4074-a036-ca7ce79a4aed" +ExtendableFEMBase = "12fb9182-3d4c-4424-8fd1-727a0899810c" ExtendableGrids = "cfc395e8-590f-11e8-1f13-43a2532b2fa8" ExtendableSparse = "95c220a8-a1cf-11e9-0c77-dbfce5f500b3" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"