diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 83a49f5..c1e860e 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,3 +19,7 @@ repos: rev: v2.2.4 hooks: - id: codespell +- repo: https://github.com/fredrikekre/runic-pre-commit + rev: v1.0.0 + hooks: + - id: runic diff --git a/docs/make.jl b/docs/make.jl index 36f667f..9e4f8c1 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -5,103 +5,103 @@ using ExtendableFEM function make_all(; with_examples::Bool = true, modules = :all, run_examples::Bool = true, run_notebooks::Bool = false) - module_examples = [] + module_examples = [] - if with_examples - DocMeta.setdocmeta!(ExampleJuggler, :DocTestSetup, :(using ExampleJuggler); recursive = true) + if with_examples + DocMeta.setdocmeta!(ExampleJuggler, :DocTestSetup, :(using ExampleJuggler); recursive = true) - example_dir = joinpath(@__DIR__, "..", "examples") + example_dir = joinpath(@__DIR__, "..", "examples") - if modules === :all - modules = [ - "Example103_BurgersEquation.jl", - "Example105_NonlinearPoissonEquation.jl", - "Example106_NonlinearDiffusion.jl", - "Example108_RobinBoundaryCondition.jl", - "Example201_PoissonProblem.jl", - "Example202_MixedPoissonProblem.jl", - "Example203_PoissonProblemDG.jl", - "Example204_LaplaceEVProblem.jl", - "Example205_HeatEquation.jl", - "Example206_CoupledSubGridProblems.jl", - "Example207_AdvectionUpwindDG.jl", - "Example210_LshapeAdaptivePoissonProblem.jl", - "Example211_LshapeAdaptiveEQPoissonProblem.jl", - "Example220_ReactionConvectionDiffusion.jl", - "Example225_ObstacleProblem.jl", - "Example226_Thermoforming.jl", - "Example230_NonlinearElasticity.jl", - "Example235_StokesIteratedPenalty.jl", - "Example240_SVRTEnrichment.jl", - "Example245_NSEFlowAroundCylinder.jl", - "Example250_NSELidDrivenCavity.jl", - "Example252_NSEPlanarLatticeFlow.jl", - "Example260_AxisymmetricNavierStokesProblem.jl", - "Example265_FlowTransport.jl", - "Example270_NaturalConvectionProblem.jl", - "Example275_OptimalControlStokes.jl", - "Example280_CompressibleStokes.jl", - "Example282_IncompressibleMHD.jl", - "Example284_LevelSetMethod.jl", - "Example285_CahnHilliard.jl", - "Example290_PoroElasticity.jl", - "Example301_PoissonProblem.jl", - "Example310_DivFreeBasis.jl", - "Example330_HyperElasticity.jl", - ] - end + if modules === :all + modules = [ + "Example103_BurgersEquation.jl", + "Example105_NonlinearPoissonEquation.jl", + "Example106_NonlinearDiffusion.jl", + "Example108_RobinBoundaryCondition.jl", + "Example201_PoissonProblem.jl", + "Example202_MixedPoissonProblem.jl", + "Example203_PoissonProblemDG.jl", + "Example204_LaplaceEVProblem.jl", + "Example205_HeatEquation.jl", + "Example206_CoupledSubGridProblems.jl", + "Example207_AdvectionUpwindDG.jl", + "Example210_LshapeAdaptivePoissonProblem.jl", + "Example211_LshapeAdaptiveEQPoissonProblem.jl", + "Example220_ReactionConvectionDiffusion.jl", + "Example225_ObstacleProblem.jl", + "Example226_Thermoforming.jl", + "Example230_NonlinearElasticity.jl", + "Example235_StokesIteratedPenalty.jl", + "Example240_SVRTEnrichment.jl", + "Example245_NSEFlowAroundCylinder.jl", + "Example250_NSELidDrivenCavity.jl", + "Example252_NSEPlanarLatticeFlow.jl", + "Example260_AxisymmetricNavierStokesProblem.jl", + "Example265_FlowTransport.jl", + "Example270_NaturalConvectionProblem.jl", + "Example275_OptimalControlStokes.jl", + "Example280_CompressibleStokes.jl", + "Example282_IncompressibleMHD.jl", + "Example284_LevelSetMethod.jl", + "Example285_CahnHilliard.jl", + "Example290_PoroElasticity.jl", + "Example301_PoissonProblem.jl", + "Example310_DivFreeBasis.jl", + "Example330_HyperElasticity.jl", + ] + end - #notebooks = ["PlutoTemplate.jl" - # "Example with Graphics" => "ExamplePluto.jl"] + #notebooks = ["PlutoTemplate.jl" + # "Example with Graphics" => "ExamplePluto.jl"] - cleanexamples() + cleanexamples() - module_examples = @docmodules(example_dir, modules, Plotter = CairoMakie) - #html_examples = @docplutonotebooks(example_dir, notebooks, iframe=false) - #pluto_examples = @docplutonotebooks(example_dir, notebooks, iframe=true) - end + module_examples = @docmodules(example_dir, modules, Plotter = CairoMakie) + #html_examples = @docplutonotebooks(example_dir, notebooks, iframe=false) + #pluto_examples = @docplutonotebooks(example_dir, notebooks, iframe=true) + end - makedocs( - modules = [ExtendableFEM], - sitename = "ExtendableFEM.jl", - authors = "Christian Merdon, Jan Philipp Thiele", - format = Documenter.HTML(; repolink = "https://github.com/WIAS-PDELib/ExtendableFEM.jl", mathengine = MathJax3()), - clean = false, - checkdocs = :none, - warnonly = false, - doctest = true, - pages = [ - "Home" => "index.md", - "Index" => "package_index.md", - "Problem Description" => [ - "problemdescription.md", + makedocs( + modules = [ExtendableFEM], + sitename = "ExtendableFEM.jl", + authors = "Christian Merdon, Jan Philipp Thiele", + format = Documenter.HTML(; repolink = "https://github.com/WIAS-PDELib/ExtendableFEM.jl", mathengine = MathJax3()), + clean = false, + checkdocs = :none, + warnonly = false, + doctest = true, + pages = [ + "Home" => "index.md", + "Index" => "package_index.md", + "Problem Description" => [ + "problemdescription.md", "tensordescription.md", - "nonlinearoperator.md", - "bilinearoperator.md", - "linearoperator.md", - "interpolateboundarydata.md", - "homogeneousdata.md", - "fixdofs.md", - "combinedofs.md", - "callbackoperator.md", - "allindex.md", - ], - "Solving" => Any[ - "pdesolvers.md", - "pdesolvers_dt.md", - "parallel_assembly.md" - ], - "Postprocessing" => Any[ - "postprocessing.md", - "itemintegrators.md", - "faceinterpolator.md", - ], - #"Tutorial Notebooks" => notebooks, - "Examples" => module_examples, - ], - ) + "nonlinearoperator.md", + "bilinearoperator.md", + "linearoperator.md", + "interpolateboundarydata.md", + "homogeneousdata.md", + "fixdofs.md", + "combinedofs.md", + "callbackoperator.md", + "allindex.md", + ], + "Solving" => Any[ + "pdesolvers.md", + "pdesolvers_dt.md", + "parallel_assembly.md", + ], + "Postprocessing" => Any[ + "postprocessing.md", + "itemintegrators.md", + "faceinterpolator.md", + ], + #"Tutorial Notebooks" => notebooks, + "Examples" => module_examples, + ], + ) - cleanexamples() + return cleanexamples() end @@ -109,5 +109,5 @@ end make_all(; with_examples = false, run_examples = false, run_notebooks = false) deploydocs( - repo = "github.com/WIAS-PDELib/ExtendableFEM.jl", + repo = "github.com/WIAS-PDELib/ExtendableFEM.jl", ) diff --git a/examples/Example103_BurgersEquation.jl b/examples/Example103_BurgersEquation.jl index 3738b45..aed3ce7 100644 --- a/examples/Example103_BurgersEquation.jl +++ b/examples/Example103_BurgersEquation.jl @@ -28,84 +28,85 @@ using Test #hide ## nonlinear kernel, i.e. f(u) function f!(result, input, qpinfo) - result[1] = input[1]^2 / 2 + return result[1] = input[1]^2 / 2 end ## initial condition function uinit!(result, qpinfo) - result[1] = abs(qpinfo.x[1]) < 0.5 ? 1 : 0 + return result[1] = abs(qpinfo.x[1]) < 0.5 ? 1 : 0 end ## everything is wrapped in a main function function main(; - ν = 0.01, - h = 0.005, - T = 2, - order = 2, - τ = 0.01, - Plotter = nothing, - use_diffeq = true, - solver = Rosenbrock23(autodiff = false), - kwargs...) - - ## load mesh and exact solution - xgrid = simplexgrid(-2:h:2) - - ## generate empty PDEDescription for three unknowns (h, u) - PD = ProblemDescription("Burger's Equation") - u = Unknown("u"; name = "u") - assign_unknown!(PD, u) - assign_operator!(PD, NonlinearOperator(f!, [grad(u)], [id(u)]; bonus_quadorder = 2)) - assign_operator!(PD, BilinearOperator([grad(u)]; store = true, factor = ν)) - assign_operator!(PD, CombineDofs(u, u, [1], [num_nodes(xgrid)], [1.0]; kwargs...)) - - ## prepare solution vector and initial data - FES = FESpace{H1Pk{1, 1, order}}(xgrid) - sol = FEVector(FES; tags = PD.unknowns) - interpolate!(sol[u], uinit!) - - ## init plotter and plot u0 - plt = plot([id(u), id(u)], sol; Plotter = Plotter, title_add = " (t = 0)") - - ## generate mass matrix - M = FEMatrix(FES) - assemble!(M, BilinearOperator([id(1)]; lump = 2)) - - if (use_diffeq) - ## generate DifferentialEquations.ODEProblem - prob = ExtendableFEM.generate_ODEProblem(PD, FES, (0.0, T); init = sol, mass_matrix = M) - - ## solve ODE problem - de_sol = DifferentialEquations.solve(prob, solver, abstol = 1e-6, reltol = 1e-3, dt = τ, dtmin = 1e-6, adaptive = true) - @info "#tsteps = $(length(de_sol))" - - ## extract final solution - sol.entries .= de_sol[end] - else - ## add backward Euler time derivative - assign_operator!(PD, BilinearOperator(M, [u]; factor = 1 / τ, kwargs...)) - assign_operator!(PD, LinearOperator(M, [u], [u]; factor = 1 / τ, kwargs...)) - - ## generate solver configuration - SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, kwargs...) - - ## iterate tspan - t = 0 - for it ∈ 1:Int(floor(T / τ)) - t += τ - ExtendableFEM.solve(PD, FES, SC; time = t) - end - end - - ## plot final state - plot!(plt, [id(u)], sol; keep = 1, title_add = " (t = $T)") - - return sol, plt + ν = 0.01, + h = 0.005, + T = 2, + order = 2, + τ = 0.01, + Plotter = nothing, + use_diffeq = true, + solver = Rosenbrock23(autodiff = false), + kwargs... + ) + + ## load mesh and exact solution + xgrid = simplexgrid(-2:h:2) + + ## generate empty PDEDescription for three unknowns (h, u) + PD = ProblemDescription("Burger's Equation") + u = Unknown("u"; name = "u") + assign_unknown!(PD, u) + assign_operator!(PD, NonlinearOperator(f!, [grad(u)], [id(u)]; bonus_quadorder = 2)) + assign_operator!(PD, BilinearOperator([grad(u)]; store = true, factor = ν)) + assign_operator!(PD, CombineDofs(u, u, [1], [num_nodes(xgrid)], [1.0]; kwargs...)) + + ## prepare solution vector and initial data + FES = FESpace{H1Pk{1, 1, order}}(xgrid) + sol = FEVector(FES; tags = PD.unknowns) + interpolate!(sol[u], uinit!) + + ## init plotter and plot u0 + plt = plot([id(u), id(u)], sol; Plotter = Plotter, title_add = " (t = 0)") + + ## generate mass matrix + M = FEMatrix(FES) + assemble!(M, BilinearOperator([id(1)]; lump = 2)) + + if (use_diffeq) + ## generate DifferentialEquations.ODEProblem + prob = ExtendableFEM.generate_ODEProblem(PD, FES, (0.0, T); init = sol, mass_matrix = M) + + ## solve ODE problem + de_sol = DifferentialEquations.solve(prob, solver, abstol = 1.0e-6, reltol = 1.0e-3, dt = τ, dtmin = 1.0e-6, adaptive = true) + @info "#tsteps = $(length(de_sol))" + + ## extract final solution + sol.entries .= de_sol[end] + else + ## add backward Euler time derivative + assign_operator!(PD, BilinearOperator(M, [u]; factor = 1 / τ, kwargs...)) + assign_operator!(PD, LinearOperator(M, [u], [u]; factor = 1 / τ, kwargs...)) + + ## generate solver configuration + SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, kwargs...) + + ## iterate tspan + t = 0 + for it in 1:Int(floor(T / τ)) + t += τ + ExtendableFEM.solve(PD, FES, SC; time = t) + end + end + + ## plot final state + plot!(plt, [id(u)], sol; keep = 1, title_add = " (t = $T)") + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example103_BurgersEquation, "example103.png") #hide function runtests() #hide - sol, plt = main(; h = 0.01, τ = 0.1, T = 1, use_diffeq = false) #hide - @test maximum(sol.entries) ≈ 0.9380540612507218 #hide + sol, plt = main(; h = 0.01, τ = 0.1, T = 1, use_diffeq = false) #hide + return @test maximum(sol.entries) ≈ 0.9380540612507218 #hide end #hide end diff --git a/examples/Example105_NonlinearPoissonEquation.jl b/examples/Example105_NonlinearPoissonEquation.jl index 2414347..38c294f 100644 --- a/examples/Example105_NonlinearPoissonEquation.jl +++ b/examples/Example105_NonlinearPoissonEquation.jl @@ -32,47 +32,47 @@ using Test #hide ## rigt-hand side data function f!(result, qpinfo) - result[1] = qpinfo.x[1] < 0.5 ? -1 : 1 + return result[1] = qpinfo.x[1] < 0.5 ? -1 : 1 end ## boundary data function boundary_data!(result, qpinfo) - result[1] = qpinfo.x[1] + return result[1] = qpinfo.x[1] end ## kernel for the (nonlinear) reaction-convection-diffusion operator function nonlinear_kernel!(result, input, qpinfo) - u, ∇u, ϵ = input[1], input[2], qpinfo.params[1] - result[1] = exp(u) - exp(-u) - result[2] = ϵ * ∇u + u, ∇u, ϵ = input[1], input[2], qpinfo.params[1] + result[1] = exp(u) - exp(-u) + return result[2] = ϵ * ∇u end ## everything is wrapped in a main function -function main(; Plotter = nothing, h = 1e-2, ϵ = 1e-3, order = 2, kwargs...) +function main(; Plotter = nothing, h = 1.0e-2, ϵ = 1.0e-3, order = 2, kwargs...) - ## problem description - PD = ProblemDescription("Nonlinear Poisson Equation") - u = Unknown("u"; name = "u") - assign_unknown!(PD, u) - assign_operator!(PD, NonlinearOperator(nonlinear_kernel!, [id(u), grad(u)]; params = [ϵ], kwargs...)) - assign_operator!(PD, LinearOperator(f!, [id(u)]; store = true, kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, boundary_data!; kwargs...)) + ## problem description + PD = ProblemDescription("Nonlinear Poisson Equation") + u = Unknown("u"; name = "u") + assign_unknown!(PD, u) + assign_operator!(PD, NonlinearOperator(nonlinear_kernel!, [id(u), grad(u)]; params = [ϵ], kwargs...)) + assign_operator!(PD, LinearOperator(f!, [id(u)]; store = true, kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, boundary_data!; kwargs...)) - ## discretize: grid + FE space - xgrid = simplexgrid(0:h:1) - FES = FESpace{H1Pk{1, 1, order}}(xgrid) + ## discretize: grid + FE space + xgrid = simplexgrid(0:h:1) + FES = FESpace{H1Pk{1, 1, order}}(xgrid) - ## generate a solution vector and solve - sol = solve(PD, FES; kwargs...) + ## generate a solution vector and solve + sol = solve(PD, FES; kwargs...) - ## plot discrete and exact solution (on finer grid) - plt = plot([id(u)], sol; Plotter = Plotter) + ## plot discrete and exact solution (on finer grid) + plt = plot([id(u)], sol; Plotter = Plotter) - return sol, plt + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example105_NonlinearPoissonEquation, "example105.png") #hide function runtests() #hide - sol, plt = main(; h = 0.01, τ = 0.1, T = 1, use_diffeq = false) #hide - @test maximum(sol.entries) ≈ 0.4812118250102083 #hide + sol, plt = main(; h = 0.01, τ = 0.1, T = 1, use_diffeq = false) #hide + return @test maximum(sol.entries) ≈ 0.4812118250102083 #hide end #hide end diff --git a/examples/Example106_NonlinearDiffusion.jl b/examples/Example106_NonlinearDiffusion.jl index 38bdba6..00edeb5 100644 --- a/examples/Example106_NonlinearDiffusion.jl +++ b/examples/Example106_NonlinearDiffusion.jl @@ -29,101 +29,102 @@ using Test #hide ## Barenblatt solution ## (see Barenblatt, G. I. "On nonsteady motions of gas and fluid in porous medium." Appl. Math. and Mech.(PMM) 16.1 (1952): 67-78.) function u_exact!(result, qpinfo) - t = qpinfo.time - x = qpinfo.x[1] - m = qpinfo.params[1] - tx = t^(-1.0 / (m + 1.0)) - xx = x * tx - xx = xx * xx - xx = 1 - xx * (m - 1) / (2.0 * m * (m + 1)) - if xx < 0.0 - xx = 0.0 - end - result[1] = tx * xx^(1.0 / (m - 1.0)) + t = qpinfo.time + x = qpinfo.x[1] + m = qpinfo.params[1] + tx = t^(-1.0 / (m + 1.0)) + xx = x * tx + xx = xx * xx + xx = 1 - xx * (m - 1) / (2.0 * m * (m + 1)) + if xx < 0.0 + xx = 0.0 + end + return result[1] = tx * xx^(1.0 / (m - 1.0)) end function kernel_nonlinear!(result, input, qpinfo) - u, ∇u = input[1], input[2] - m = qpinfo.params[1] - result[1] = m * u^(m - 1) * ∇u + u, ∇u = input[1], input[2] + m = qpinfo.params[1] + return result[1] = m * u^(m - 1) * ∇u end ## everything is wrapped in a main function function main(; - m = 2, - h = 0.05, - t0 = 0.001, - T = 0.01, - order = 1, - τ = 0.0001, - Plotter = nothing, - use_diffeq = true, - use_masslumping = true, - solver = ImplicitEuler(autodiff = false), - kwargs...) - - ## load mesh and exact solution - xgrid = simplexgrid(-1:h:1) - - ## set finite element types [surface height, velocity] - FEType = H1Pk{1, 1, order} - - ## generate empty PDEDescription for three unknowns (h, u) - PD = ProblemDescription("Nonlinear Diffusion Equation") - u = Unknown("u"; name = "u") - assign_unknown!(PD, u) - assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [grad(u)], [id(u), grad(u)]; params = [m], bonus_quadorder = 2)) - - ## prepare solution vector and initial data - FES = FESpace{FEType}(xgrid) - sol = FEVector(FES; tags = PD.unknowns) - interpolate!(sol[u], u_exact!; time = t0, params = [m]) - - ## init plotter and plot u0 - plt = GridVisualizer(; Plotter = Plotter, layout = (1, 2), size = (800,400)) - scalarplot!(plt[1, 1], id(u), sol; label = "u_h", markershape = :circle, markevery = 1, title = "t = $t0") - - ## generate mass matrix (with mass lumping) - M = FEMatrix(FES) - assemble!(M, BilinearOperator([id(1)]; lump = 2 * use_masslumping)) - - if (use_diffeq) - ## generate ODE problem - prob = ExtendableFEM.generate_ODEProblem(PD, FES, (t0, T); init = sol, mass_matrix = M.entries.cscmatrix) - - ## solve ODE problem - de_sol = DifferentialEquations.solve(prob, solver, abstol = 1e-6, reltol = 1e-3, dt = τ, dtmin = 1e-8, adaptive = true) - @info "#tsteps = $(length(de_sol))" - - ## get final solution - sol.entries .= de_sol[end] - else - ## add backward Euler time derivative - assign_operator!(PD, BilinearOperator(M, [u]; factor = 1 / τ, kwargs...)) - assign_operator!(PD, LinearOperator(M, [u], [u]; factor = 1 / τ, kwargs...)) - - ## generate solver configuration - SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, kwargs...) - - ## iterate tspan - t = 0 - for it ∈ 1:Int(floor((T - t0) / τ)) - t += τ - ExtendableFEM.solve(PD, FES, SC; time = t) - end - end - - ## plot final state and exact solution for comparison - scalarplot!(plt[1, 2], id(u), sol; label = "u_h", markershape = :circle, markevery = 1) - interpolate!(sol[1], u_exact!; time = T, params = [m]) - scalarplot!(plt[1, 2], id(u), sol; clear = false, color = :green, label = "u", title = "t = $T", legend = :best) - - return sol, plt + m = 2, + h = 0.05, + t0 = 0.001, + T = 0.01, + order = 1, + τ = 0.0001, + Plotter = nothing, + use_diffeq = true, + use_masslumping = true, + solver = ImplicitEuler(autodiff = false), + kwargs... + ) + + ## load mesh and exact solution + xgrid = simplexgrid(-1:h:1) + + ## set finite element types [surface height, velocity] + FEType = H1Pk{1, 1, order} + + ## generate empty PDEDescription for three unknowns (h, u) + PD = ProblemDescription("Nonlinear Diffusion Equation") + u = Unknown("u"; name = "u") + assign_unknown!(PD, u) + assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [grad(u)], [id(u), grad(u)]; params = [m], bonus_quadorder = 2)) + + ## prepare solution vector and initial data + FES = FESpace{FEType}(xgrid) + sol = FEVector(FES; tags = PD.unknowns) + interpolate!(sol[u], u_exact!; time = t0, params = [m]) + + ## init plotter and plot u0 + plt = GridVisualizer(; Plotter = Plotter, layout = (1, 2), size = (800, 400)) + scalarplot!(plt[1, 1], id(u), sol; label = "u_h", markershape = :circle, markevery = 1, title = "t = $t0") + + ## generate mass matrix (with mass lumping) + M = FEMatrix(FES) + assemble!(M, BilinearOperator([id(1)]; lump = 2 * use_masslumping)) + + if (use_diffeq) + ## generate ODE problem + prob = ExtendableFEM.generate_ODEProblem(PD, FES, (t0, T); init = sol, mass_matrix = M.entries.cscmatrix) + + ## solve ODE problem + de_sol = DifferentialEquations.solve(prob, solver, abstol = 1.0e-6, reltol = 1.0e-3, dt = τ, dtmin = 1.0e-8, adaptive = true) + @info "#tsteps = $(length(de_sol))" + + ## get final solution + sol.entries .= de_sol[end] + else + ## add backward Euler time derivative + assign_operator!(PD, BilinearOperator(M, [u]; factor = 1 / τ, kwargs...)) + assign_operator!(PD, LinearOperator(M, [u], [u]; factor = 1 / τ, kwargs...)) + + ## generate solver configuration + SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, kwargs...) + + ## iterate tspan + t = 0 + for it in 1:Int(floor((T - t0) / τ)) + t += τ + ExtendableFEM.solve(PD, FES, SC; time = t) + end + end + + ## plot final state and exact solution for comparison + scalarplot!(plt[1, 2], id(u), sol; label = "u_h", markershape = :circle, markevery = 1) + interpolate!(sol[1], u_exact!; time = T, params = [m]) + scalarplot!(plt[1, 2], id(u), sol; clear = false, color = :green, label = "u", title = "t = $T", legend = :best) + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example106_NonlinearDiffusion, "example106.png") #hide function runtests(; T = 0.01, m = 2, kwargs...) #hide - sol, plt = main(; T = T, m = m, use_diffeq = false, kwargs...) #hide - @test maximum(sol.entries) ≈ 4.641588833612778 #hide + sol, plt = main(; T = T, m = m, use_diffeq = false, kwargs...) #hide + return @test maximum(sol.entries) ≈ 4.641588833612778 #hide end #hide end diff --git a/examples/Example108_RobinBoundaryCondition.jl b/examples/Example108_RobinBoundaryCondition.jl index d48bf4f..590d5ff 100644 --- a/examples/Example108_RobinBoundaryCondition.jl +++ b/examples/Example108_RobinBoundaryCondition.jl @@ -27,66 +27,66 @@ using Test #hide ## data and exact solution function f!(result, qpinfo) - result[1] = exp(2 * qpinfo.x[1]) + return result[1] = exp(2 * qpinfo.x[1]) end function u!(result, qpinfo) - result[1] = exp(qpinfo.x[1]) + return result[1] = exp(qpinfo.x[1]) end ## kernel for the (nonlinear) reaction-convection-diffusion operator function nonlinear_kernel!(result, input, qpinfo) - u, ∇u = input[1], input[2] - result[1] = u * ∇u + u # convection + reaction (will be multiplied with v) - result[2] = ∇u # diffusion (will be multiplied with ∇v) - return nothing + u, ∇u = input[1], input[2] + result[1] = u * ∇u + u # convection + reaction (will be multiplied with v) + result[2] = ∇u # diffusion (will be multiplied with ∇v) + return nothing end ## kernel for Robin boundary condition function robin_kernel!(result, input, qpinfo) - result[1] = 2 - input[1] # = g - u (will be multiplied with v) - return nothing + result[1] = 2 - input[1] # = g - u (will be multiplied with v) + return nothing end ## everything is wrapped in a main function -function main(; Plotter = nothing, h = 1e-1, h_fine = 1e-3, order = 2, kwargs...) - - ## problem description - PD = ProblemDescription() - u = Unknown("u"; name = "u") - assign_unknown!(PD, u) - assign_operator!(PD, NonlinearOperator(nonlinear_kernel!, [id(u), grad(u)]; kwargs...)) - assign_operator!(PD, BilinearOperator(robin_kernel!, [id(u)]; entities = ON_BFACES, regions = [1], kwargs...)) - assign_operator!(PD, LinearOperator(f!, [id(u)]; kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = [2], kwargs...)) - - ## generate coarse and fine mesh - xgrid = simplexgrid(0:h:1) - - ## choose finite element type and generate FESpace - FEType = H1Pk{1, 1, order} - FES = FESpace{FEType}(xgrid) - - ## generate a solution vector and solve - sol = solve(PD, FES; kwargs...) - - ## plot discrete and exact solution (on finer grid) - plt = GridVisualizer(Plotter = Plotter, layout = (1, 1)) - scalarplot!(plt[1, 1], id(u), sol; color = :black, label = "u_h", markershape = :circle, markersize = 10, markevery = 1) - xgrid_fine = simplexgrid(0:h_fine:1) - scalarplot!(plt[1, 1], xgrid_fine, view(nodevalues(xgrid_fine, u!), 1, :), clear = false, color = (1, 0, 0), label = "u", legend = :rb, markershape = :none) - - return sol, plt +function main(; Plotter = nothing, h = 1.0e-1, h_fine = 1.0e-3, order = 2, kwargs...) + + ## problem description + PD = ProblemDescription() + u = Unknown("u"; name = "u") + assign_unknown!(PD, u) + assign_operator!(PD, NonlinearOperator(nonlinear_kernel!, [id(u), grad(u)]; kwargs...)) + assign_operator!(PD, BilinearOperator(robin_kernel!, [id(u)]; entities = ON_BFACES, regions = [1], kwargs...)) + assign_operator!(PD, LinearOperator(f!, [id(u)]; kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = [2], kwargs...)) + + ## generate coarse and fine mesh + xgrid = simplexgrid(0:h:1) + + ## choose finite element type and generate FESpace + FEType = H1Pk{1, 1, order} + FES = FESpace{FEType}(xgrid) + + ## generate a solution vector and solve + sol = solve(PD, FES; kwargs...) + + ## plot discrete and exact solution (on finer grid) + plt = GridVisualizer(Plotter = Plotter, layout = (1, 1)) + scalarplot!(plt[1, 1], id(u), sol; color = :black, label = "u_h", markershape = :circle, markersize = 10, markevery = 1) + xgrid_fine = simplexgrid(0:h_fine:1) + scalarplot!(plt[1, 1], xgrid_fine, view(nodevalues(xgrid_fine, u!), 1, :), clear = false, color = (1, 0, 0), label = "u", legend = :rb, markershape = :none) + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example108_RobinBoundaryCondition, "example108.png") #hide function exact_error!(result, u, qpinfo) #hide - u!(result, qpinfo) #hide - result .= (result .- u).^ 2 #hide + u!(result, qpinfo) #hide + return result .= (result .- u) .^ 2 #hide end #hide function runtests(; kwargs...) #hide - sol, plt = main(; order = 2, kwargs...) #hide - L2error = ItemIntegrator(exact_error!, [id(1)]; quadorder = 4, kwargs...) #hide - error = sqrt(sum(evaluate(L2error, sol))) #hide - @test error ≈ 9.062544216508815e-6 #hide + sol, plt = main(; order = 2, kwargs...) #hide + L2error = ItemIntegrator(exact_error!, [id(1)]; quadorder = 4, kwargs...) #hide + error = sqrt(sum(evaluate(L2error, sol))) #hide + return @test error ≈ 9.062544216508815e-6 #hide end #hide end diff --git a/examples/Example201_PoissonProblem.jl b/examples/Example201_PoissonProblem.jl index ac9283f..e188a7d 100644 --- a/examples/Example201_PoissonProblem.jl +++ b/examples/Example201_PoissonProblem.jl @@ -31,38 +31,38 @@ u = Unknown("u"; name = "potential") ## define data functions function f!(fval, qpinfo) - fval[1] = qpinfo.x[1] * qpinfo.x[2] + return fval[1] = qpinfo.x[1] * qpinfo.x[2] end function main(; μ = 1.0, nrefs = 4, order = 2, Plotter = nothing, parallel = false, npart = parallel ? 8 : 1, kwargs...) - ## problem description - PD = ProblemDescription() - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator([grad(u)]; parallel = parallel, factor = μ, kwargs...)) - assign_operator!(PD, LinearOperator(f!, [id(u)]; parallel = parallel, kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4)) + ## problem description + PD = ProblemDescription() + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator([grad(u)]; parallel = parallel, factor = μ, kwargs...)) + assign_operator!(PD, LinearOperator(f!, [id(u)]; parallel = parallel, kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4)) - ## discretize - xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) - if npart > 1 - xgrid = partition(xgrid, PlainMetisPartitioning(npart=npart)) - end - FES = FESpace{H1Pk{1, 2, order}}(xgrid) + ## discretize + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) + if npart > 1 + xgrid = partition(xgrid, PlainMetisPartitioning(npart = npart)) + end + FES = FESpace{H1Pk{1, 2, order}}(xgrid) - ## solve - sol = solve(PD, FES; kwargs...) + ## solve + sol = solve(PD, FES; kwargs...) - ## plot - plt = plot([id(u), grad(u)], sol; Plotter = Plotter) + ## plot + plt = plot([id(u), grad(u)], sol; Plotter = Plotter) - return sol, plt + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example201_PoissonProblem, "example201.png") #hide function runtests() #hide - sol, plt = main(; μ = 1.0, nrefs = 2, parallel = false, order = 2, npart = 2) #hide - @test sum(sol.entries) ≈ 1.1140313632246377 #hide - sol_parallel, plt = main(; μ = 1.0, nrefs = 2, order = 2, parallel = true, npart = 2) #hide - @assert sum((sol_parallel.entries .- sol.entries).^2) ≈ 0.0 + sol, plt = main(; μ = 1.0, nrefs = 2, parallel = false, order = 2, npart = 2) #hide + @test sum(sol.entries) ≈ 1.1140313632246377 #hide + sol_parallel, plt = main(; μ = 1.0, nrefs = 2, order = 2, parallel = true, npart = 2) #hide + return @assert sum((sol_parallel.entries .- sol.entries) .^ 2) ≈ 0.0 end #hide end # module diff --git a/examples/Example202_MixedPoissonProblem.jl b/examples/Example202_MixedPoissonProblem.jl index fd41e18..2b6bec4 100644 --- a/examples/Example202_MixedPoissonProblem.jl +++ b/examples/Example202_MixedPoissonProblem.jl @@ -31,53 +31,55 @@ u = Unknown("u"; name = "potential") ## bilinearform kernel for mixed Poisson problem function blf!(result, u_ops, qpinfo) - σ, divσ, u = view(u_ops, 1:2), view(u_ops, 3), view(u_ops, 4) - μ = qpinfo.params[1] - result[1] = σ[1] / μ - result[2] = σ[2] / μ - result[3] = -u[1] - result[4] = divσ[1] - return nothing + σ, divσ, u = view(u_ops, 1:2), view(u_ops, 3), view(u_ops, 4) + μ = qpinfo.params[1] + result[1] = σ[1] / μ + result[2] = σ[2] / μ + result[3] = -u[1] + result[4] = divσ[1] + return nothing end ## right-hand side data function f!(fval, qpinfo) - fval[1] = qpinfo.x[1] * qpinfo.x[2] - return nothing + fval[1] = qpinfo.x[1] * qpinfo.x[2] + return nothing end ## boundary data function boundarydata!(result, qpinfo) - result[1] = 0 - return nothing + result[1] = 0 + return nothing end function main(; nrefs = 5, μ = 0.25, order = 0, Plotter = nothing, kwargs...) - ## problem description - PD = ProblemDescription() - assign_unknown!(PD, u) - assign_unknown!(PD, σ) - assign_operator!(PD, BilinearOperator(blf!, [id(σ), div(σ), id(u)]; params = [μ], kwargs...)) - assign_operator!(PD, LinearOperator(boundarydata!, [normalflux(σ)]; entities = ON_BFACES, regions = 1:4, kwargs...)) - assign_operator!(PD, LinearOperator(f!, [id(u)]; kwargs...)) - assign_operator!(PD, FixDofs(u; dofs = [1], vals = [0])) + ## problem description + PD = ProblemDescription() + assign_unknown!(PD, u) + assign_unknown!(PD, σ) + assign_operator!(PD, BilinearOperator(blf!, [id(σ), div(σ), id(u)]; params = [μ], kwargs...)) + assign_operator!(PD, LinearOperator(boundarydata!, [normalflux(σ)]; entities = ON_BFACES, regions = 1:4, kwargs...)) + assign_operator!(PD, LinearOperator(f!, [id(u)]; kwargs...)) + assign_operator!(PD, FixDofs(u; dofs = [1], vals = [0])) - ## discretize - xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) - FES = Dict(u => FESpace{order == 0 ? L2P0{1} : H1Pk{1,2,order}}(xgrid; broken = true), - σ => FESpace{HDIVRTk{2, order}}(xgrid)) + ## discretize + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) + FES = Dict( + u => FESpace{order == 0 ? L2P0{1} : H1Pk{1, 2, order}}(xgrid; broken = true), + σ => FESpace{HDIVRTk{2, order}}(xgrid) + ) - ## solve - sol = ExtendableFEM.solve(PD, FES; kwargs...) + ## solve + sol = ExtendableFEM.solve(PD, FES; kwargs...) - ## plot - plt = plot([id(u), id(σ)], sol; Plotter = Plotter) + ## plot + plt = plot([id(u), id(σ)], sol; Plotter = Plotter) - return sol, plt + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example202_MixedPoissonProblem, "example202.png") #hide function runtests() #hide - sol, plt = main(; μ = 0.25, order = 0, nrefs = 2) #hide - @test maximum(view(sol[1])) ≈ 0.08463539106946043 #hide + sol, plt = main(; μ = 0.25, order = 0, nrefs = 2) #hide + return @test maximum(view(sol[1])) ≈ 0.08463539106946043 #hide end #hide end # module diff --git a/examples/Example203_PoissonProblemDG.jl b/examples/Example203_PoissonProblemDG.jl index 92f89db..77471f8 100644 --- a/examples/Example203_PoissonProblemDG.jl +++ b/examples/Example203_PoissonProblemDG.jl @@ -30,106 +30,106 @@ using Test #hide ## exact data for problem by Symbolics function prepare_data(; μ = 1) - @variables x y + @variables x y - ## exact solution - u = x^3 - 3 * x * y^2 - ∇u = Symbolics.gradient(u, [x, y]) + ## exact solution + u = x^3 - 3 * x * y^2 + ∇u = Symbolics.gradient(u, [x, y]) - ## right-hand side - Δu = Symbolics.gradient(∇u[1], [x]) + Symbolics.gradient(∇u[2], [y]) - f = -μ * Δu[1] + ## right-hand side + Δu = Symbolics.gradient(∇u[1], [x]) + Symbolics.gradient(∇u[2], [y]) + f = -μ * Δu[1] - ## build functions - u_eval = build_function(u, x, y, expression = Val{false}) - ∇u_eval = build_function(∇u, x, y, expression = Val{false}) - f_eval = build_function(f, x, y, expression = Val{false}) - return f_eval, u_eval, ∇u_eval[2] + ## build functions + u_eval = build_function(u, x, y, expression = Val{false}) + ∇u_eval = build_function(∇u, x, y, expression = Val{false}) + f_eval = build_function(f, x, y, expression = Val{false}) + return f_eval, u_eval, ∇u_eval[2] end function main(; dg = true, μ = 1.0, τ = 10.0, nrefs = 4, order = 2, bonus_quadorder = 2, parallel = false, npart = parallel ? 8 : 1, Plotter = nothing, kwargs...) - ## prepare problem data - f_eval, u_eval, ∇u_eval = prepare_data(; μ = μ) - rhs!(result, qpinfo) = (result[1] = f_eval(qpinfo.x[1], qpinfo.x[2])) - exact_u!(result, qpinfo) = (result[1] = u_eval(qpinfo.x[1], qpinfo.x[2])) - exact_∇u!(result, qpinfo) = (∇u_eval(result, qpinfo.x[1], qpinfo.x[2])) - - ## problem description - PD = ProblemDescription("Poisson problem") - u = Unknown("u"; name = "potential") - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator([grad(u)]; factor = μ, parallel = parallel, kwargs...)) - assign_operator!(PD, LinearOperator(rhs!, [id(u)]; bonus_quadorder = bonus_quadorder, parallel = parallel, kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; bonus_quadorder = bonus_quadorder, regions = 1:4)) - - ## discretize - xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) - if npart > 1 - xgrid = partition(xgrid, PlainMetisPartitioning(npart=npart); edges = true) - end - FES = FESpace{order == 0 ? L2P0{1} : H1Pk{1, 2, order}}(xgrid; broken = dg) - - ## add DG terms - assign_operator!(PD, BilinearOperatorDG(dg_kernel, [jump(id(u))], [average(grad(u))]; entities = ON_FACES, factor = -μ, transposed_copy = 1, parallel = parallel, kwargs...)) - assign_operator!(PD, LinearOperatorDG(dg_kernel_bnd(exact_u!), [average(grad(u))]; entities = ON_BFACES, factor = -μ, bonus_quadorder = bonus_quadorder, parallel = parallel, kwargs...)) - assign_operator!(PD, BilinearOperatorDG(dg_kernel2, [jump(id(u))]; entities = ON_FACES, factor = μ*τ, parallel = parallel, kwargs...)) - assign_operator!(PD, LinearOperatorDG(dg_kernel2_bnd(exact_u!), [id(u)]; entities = ON_BFACES, regions = 1:4, factor = μ*τ, bonus_quadorder = bonus_quadorder, parallel = parallel, kwargs...)) - - - ## solve - sol = solve(PD, FES; kwargs...) - - ## prepare error calculation - function exact_error!(result, u, qpinfo) - exact_u!(result, qpinfo) - exact_∇u!(view(result, 2:3), qpinfo) - result .-= u - result .= result .^ 2 - end - function dgjumps!(result, u, qpinfo) - result .= u[1]^2/qpinfo.volume - end - ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(u), grad(u)]; quadorder = 2 * (order+1), params = [μ], kwargs...) - DGJumpsIntegrator = ItemIntegratorDG(dgjumps!, [jump(id(u))]; entities = ON_IFACES, kwargs...) - - ## calculate error - error = evaluate(ErrorIntegratorExact, sol) - dgjumps = sqrt(sum(evaluate(DGJumpsIntegrator, sol))) - L2error = sqrt(sum(view(error, 1, :))) - H1error = sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :))) - @info "L2 error = $L2error" - @info "H1 error = $H1error" - @info "dgjumps = $dgjumps" - - ## plot - plt = plot([id(u), grad(u)], sol; Plotter = Plotter) - - return L2error, plt + ## prepare problem data + f_eval, u_eval, ∇u_eval = prepare_data(; μ = μ) + rhs!(result, qpinfo) = (result[1] = f_eval(qpinfo.x[1], qpinfo.x[2])) + exact_u!(result, qpinfo) = (result[1] = u_eval(qpinfo.x[1], qpinfo.x[2])) + exact_∇u!(result, qpinfo) = (∇u_eval(result, qpinfo.x[1], qpinfo.x[2])) + + ## problem description + PD = ProblemDescription("Poisson problem") + u = Unknown("u"; name = "potential") + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator([grad(u)]; factor = μ, parallel = parallel, kwargs...)) + assign_operator!(PD, LinearOperator(rhs!, [id(u)]; bonus_quadorder = bonus_quadorder, parallel = parallel, kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; bonus_quadorder = bonus_quadorder, regions = 1:4)) + + ## discretize + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) + if npart > 1 + xgrid = partition(xgrid, PlainMetisPartitioning(npart = npart); edges = true) + end + FES = FESpace{order == 0 ? L2P0{1} : H1Pk{1, 2, order}}(xgrid; broken = dg) + + ## add DG terms + assign_operator!(PD, BilinearOperatorDG(dg_kernel, [jump(id(u))], [average(grad(u))]; entities = ON_FACES, factor = -μ, transposed_copy = 1, parallel = parallel, kwargs...)) + assign_operator!(PD, LinearOperatorDG(dg_kernel_bnd(exact_u!), [average(grad(u))]; entities = ON_BFACES, factor = -μ, bonus_quadorder = bonus_quadorder, parallel = parallel, kwargs...)) + assign_operator!(PD, BilinearOperatorDG(dg_kernel2, [jump(id(u))]; entities = ON_FACES, factor = μ * τ, parallel = parallel, kwargs...)) + assign_operator!(PD, LinearOperatorDG(dg_kernel2_bnd(exact_u!), [id(u)]; entities = ON_BFACES, regions = 1:4, factor = μ * τ, bonus_quadorder = bonus_quadorder, parallel = parallel, kwargs...)) + + + ## solve + sol = solve(PD, FES; kwargs...) + + ## prepare error calculation + function exact_error!(result, u, qpinfo) + exact_u!(result, qpinfo) + exact_∇u!(view(result, 2:3), qpinfo) + result .-= u + return result .= result .^ 2 + end + function dgjumps!(result, u, qpinfo) + return result .= u[1]^2 / qpinfo.volume + end + ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(u), grad(u)]; quadorder = 2 * (order + 1), params = [μ], kwargs...) + DGJumpsIntegrator = ItemIntegratorDG(dgjumps!, [jump(id(u))]; entities = ON_IFACES, kwargs...) + + ## calculate error + error = evaluate(ErrorIntegratorExact, sol) + dgjumps = sqrt(sum(evaluate(DGJumpsIntegrator, sol))) + L2error = sqrt(sum(view(error, 1, :))) + H1error = sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :))) + @info "L2 error = $L2error" + @info "H1 error = $H1error" + @info "dgjumps = $dgjumps" + + ## plot + plt = plot([id(u), grad(u)], sol; Plotter = Plotter) + + return L2error, plt end function dg_kernel(result, input, qpinfo) - result[1] = dot(input, qpinfo.normal) + return result[1] = dot(input, qpinfo.normal) end function dg_kernel_bnd(uDb! = nothing) - function closure(result, qpinfo) - uDb!(result, qpinfo) - result[1:2] = result[1] .* qpinfo.normal - end + return function closure(result, qpinfo) + uDb!(result, qpinfo) + return result[1:2] = result[1] .* qpinfo.normal + end end function dg_kernel2(result, input, qpinfo) - result .= input / qpinfo.volume + return result .= input / qpinfo.volume end function dg_kernel2_bnd(uDb! = nothing) - function closure(result, qpinfo) - uDb!(result, qpinfo) - result /= qpinfo.volume - end + return function closure(result, qpinfo) + uDb!(result, qpinfo) + return result /= qpinfo.volume + end end generateplots = ExtendableFEM.default_generateplots(Example203_PoissonProblemDG, "example203.png") #hide function runtests(; kwargs...) #hide - L2error, ~ = main(; μ = 0.25, nrefs = 2, order = 2, kwargs...) #hide - @test L2error ≈ 0.00020400470505497443 #hide + L2error, ~ = main(; μ = 0.25, nrefs = 2, order = 2, kwargs...) #hide + return @test L2error ≈ 0.00020400470505497443 #hide end #hide end # module diff --git a/examples/Example204_LaplaceEVProblem.jl b/examples/Example204_LaplaceEVProblem.jl index c374ea9..8a0b5b2 100644 --- a/examples/Example204_LaplaceEVProblem.jl +++ b/examples/Example204_LaplaceEVProblem.jl @@ -29,40 +29,40 @@ using KrylovKit function main(; which = 1:12, ncols = 3, nrefs = 4, order = 1, Plotter = nothing, kwargs...) - ## discretize - xgrid = uniform_refine(grid_lshape(Triangle2D), nrefs) - FES = FESpace{H1Pk{1, 2, order}}(xgrid) + ## discretize + xgrid = uniform_refine(grid_lshape(Triangle2D), nrefs) + FES = FESpace{H1Pk{1, 2, order}}(xgrid) - ## assemble operators - A = FEMatrix(FES) - B = FEMatrix(FES) - u = FEVector(FES; name = "u") - assemble!(A, BilinearOperator([grad(1)]; kwargs...)) - assemble!(A, BilinearOperator([id(1)]; entities = ON_BFACES, factor = 1e4, kwargs...)) - assemble!(B, BilinearOperator([id(1)]; kwargs...)) + ## assemble operators + A = FEMatrix(FES) + B = FEMatrix(FES) + u = FEVector(FES; name = "u") + assemble!(A, BilinearOperator([grad(1)]; kwargs...)) + assemble!(A, BilinearOperator([id(1)]; entities = ON_BFACES, factor = 1.0e4, kwargs...)) + assemble!(B, BilinearOperator([id(1)]; kwargs...)) - ## solver generalized eigenvalue problem iteratively with KrylovKit - λs, x, info = geneigsolve((A.entries, B.entries), maximum(which), :SR; maxiter = 4000, issymmetric = true, tol = 1e-8) - @show info - @assert info.converged >= maximum(which) + ## solver generalized eigenvalue problem iteratively with KrylovKit + λs, x, info = geneigsolve((A.entries, B.entries), maximum(which), :SR; maxiter = 4000, issymmetric = true, tol = 1.0e-8) + @show info + @assert info.converged >= maximum(which) - ## plot requested eigenvalue pairs - nEVs = length(which) - nrows = Int(ceil(nEVs / ncols)) - plt = GridVisualizer(; Plotter = Plotter, layout = (nrows, ncols), clear = true, resolution = (900, 900 / ncols * nrows)) - col, row = 0, 1 - for j in which - col += 1 - if col == ncols + 1 - col, row = 1, row + 1 - end - λ = λs[j] - @info "λ[$j] = $λ, residual = $(sum(info.residual[j]))" - u.entries .= Real.(x[j]) - scalarplot!(plt[row, col], id(1), u; Plotter = Plotter, title = "λ[$j] = $(Float16(λ))") - end + ## plot requested eigenvalue pairs + nEVs = length(which) + nrows = Int(ceil(nEVs / ncols)) + plt = GridVisualizer(; Plotter = Plotter, layout = (nrows, ncols), clear = true, resolution = (900, 900 / ncols * nrows)) + col, row = 0, 1 + for j in which + col += 1 + if col == ncols + 1 + col, row = 1, row + 1 + end + λ = λs[j] + @info "λ[$j] = $λ, residual = $(sum(info.residual[j]))" + u.entries .= Real.(x[j]) + scalarplot!(plt[row, col], id(1), u; Plotter = Plotter, title = "λ[$j] = $(Float16(λ))") + end - return u, plt + return u, plt end generateplots = ExtendableFEM.default_generateplots(Example204_LaplaceEVProblem, "example204.png") #hide diff --git a/examples/Example205_HeatEquation.jl b/examples/Example205_HeatEquation.jl index e6a06e6..5cf483b 100644 --- a/examples/Example205_HeatEquation.jl +++ b/examples/Example205_HeatEquation.jl @@ -27,68 +27,70 @@ using Test #hide ## initial state u at time t0 function initial_data!(result, qpinfo) - x = qpinfo.x - result[1] = exp(-5 * x[1]^2 - 5 * x[2]^2) + x = qpinfo.x + return result[1] = exp(-5 * x[1]^2 - 5 * x[2]^2) end -function main(; nrefs = 4, T = 2.0, τ = 1e-3, order = 2, use_diffeq = true, - solver = ImplicitEuler(autodiff = false), Plotter = nothing, kwargs...) - - ## problem description - PD = ProblemDescription("Heat Equation") - u = Unknown("u") - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator([grad(u)]; store = true, kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4)) - - ## grid - xgrid = uniform_refine(grid_unitsquare(Triangle2D; scale = [4, 4], shift = [-0.5, -0.5]), nrefs) - - ## prepare solution vector and initial data u0 - FES = FESpace{H1Pk{1, 2, order}}(xgrid) - sol = FEVector(FES; tags = PD.unknowns) - interpolate!(sol[u], initial_data!; bonus_quadorder = 5) - - ## init plotter and plot u0 - plt = plot([id(u)], sol; add = 1, Plotter = Plotter, title_add = " (t = 0)") - - if (use_diffeq) - ## generate DifferentialEquations.ODEProblem - prob = generate_ODEProblem(PD, FES, (0.0, T); init = sol, constant_matrix = true) - - ## solve ODE problem - de_sol = DifferentialEquations.solve(prob, solver, abstol = 1e-6, reltol = 1e-3, dt = τ, dtmin = 1e-6, adaptive = true) - @info "#tsteps = $(length(de_sol))" - - ## get final solution - sol.entries .= de_sol[end] - else - ## add backward Euler time derivative - M = FEMatrix(FES) - assemble!(M, BilinearOperator([id(1)])) - assign_operator!(PD, BilinearOperator(M, [u]; factor = 1 / τ, kwargs...)) - assign_operator!(PD, LinearOperator(M, [u], [u]; factor = 1 / τ, kwargs...)) - - ## generate solver configuration - SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, constant_matrix = true, kwargs...) - - ## iterate tspan - t = 0 - for it ∈ 1:Int(floor(T / τ)) - t += τ - ExtendableFEM.solve(PD, FES, SC; time = t) - end - end - - ## plot final state - plot!(plt, [id(u)], sol; keep = 1, title_add = " (t = $T)") - - return sol, plt +function main(; + nrefs = 4, T = 2.0, τ = 1.0e-3, order = 2, use_diffeq = true, + solver = ImplicitEuler(autodiff = false), Plotter = nothing, kwargs... + ) + + ## problem description + PD = ProblemDescription("Heat Equation") + u = Unknown("u") + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator([grad(u)]; store = true, kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4)) + + ## grid + xgrid = uniform_refine(grid_unitsquare(Triangle2D; scale = [4, 4], shift = [-0.5, -0.5]), nrefs) + + ## prepare solution vector and initial data u0 + FES = FESpace{H1Pk{1, 2, order}}(xgrid) + sol = FEVector(FES; tags = PD.unknowns) + interpolate!(sol[u], initial_data!; bonus_quadorder = 5) + + ## init plotter and plot u0 + plt = plot([id(u)], sol; add = 1, Plotter = Plotter, title_add = " (t = 0)") + + if (use_diffeq) + ## generate DifferentialEquations.ODEProblem + prob = generate_ODEProblem(PD, FES, (0.0, T); init = sol, constant_matrix = true) + + ## solve ODE problem + de_sol = DifferentialEquations.solve(prob, solver, abstol = 1.0e-6, reltol = 1.0e-3, dt = τ, dtmin = 1.0e-6, adaptive = true) + @info "#tsteps = $(length(de_sol))" + + ## get final solution + sol.entries .= de_sol[end] + else + ## add backward Euler time derivative + M = FEMatrix(FES) + assemble!(M, BilinearOperator([id(1)])) + assign_operator!(PD, BilinearOperator(M, [u]; factor = 1 / τ, kwargs...)) + assign_operator!(PD, LinearOperator(M, [u], [u]; factor = 1 / τ, kwargs...)) + + ## generate solver configuration + SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, constant_matrix = true, kwargs...) + + ## iterate tspan + t = 0 + for it in 1:Int(floor(T / τ)) + t += τ + ExtendableFEM.solve(PD, FES, SC; time = t) + end + end + + ## plot final state + plot!(plt, [id(u)], sol; keep = 1, title_add = " (t = $T)") + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example205_HeatEquation, "example205.png") #hide function runtests(; verbosity = -1, kwargs...) #hide - sol, plt = main(; nrefs = 2, T = 1, use_diffeq = false, kwargs...) #hide - @test maximum(sol.entries) ≈ 0.041490419236077006 #hide + sol, plt = main(; nrefs = 2, T = 1, use_diffeq = false, kwargs...) #hide + return @test maximum(sol.entries) ≈ 0.041490419236077006 #hide end #hide end # module diff --git a/examples/Example206_CoupledSubGridProblems.jl b/examples/Example206_CoupledSubGridProblems.jl index 7277ace..b8fe73d 100644 --- a/examples/Example206_CoupledSubGridProblems.jl +++ b/examples/Example206_CoupledSubGridProblems.jl @@ -36,36 +36,36 @@ using Test # function boundary_conditions!(result, qpinfo) - result[1] = 1 - qpinfo.x[1] - qpinfo.x[2] # used for both subsolutions + return result[1] = 1 - qpinfo.x[1] - qpinfo.x[2] # used for both subsolutions end function interface_condition!(result, u, qpinfo) result[1] = u[1] - u[2] - result[2] = -result[1] + return result[2] = -result[1] end function interface_condition_LM!(result, u, qpinfo) - result[1] = (u[1] - u[2]) + return result[1] = (u[1] - u[2]) end -function main(; μ = [1.0,1.0], f = [10,-10], τ = 1, use_LM = true, nref = 4, order = 2, Plotter = nothing, kwargs...) +function main(; μ = [1.0, 1.0], f = [10, -10], τ = 1, use_LM = true, nref = 4, order = 2, Plotter = nothing, kwargs...) - ## Finite element type - FEType = H1Pk{1, 2, order} + ## Finite element type + FEType = H1Pk{1, 2, order} FETypeLM = H1Pk{1, 1, order} - ## generate mesh - xgrid = grid_unitsquare(Triangle2D) + ## generate mesh + xgrid = grid_unitsquare(Triangle2D) ## define regions - xgrid[CellRegions] = Int32[1,2,2,1] + xgrid[CellRegions] = Int32[1, 2, 2, 1] ## add an interface between region 1 and 2 ## (one can use the BFace storages for that) xgrid[BFaceNodes] = Int32[xgrid[BFaceNodes] [2 5; 5 4]] - append!(xgrid[BFaceRegions], [5,5]) - xgrid[FaceRegions][xgrid[BFaceFaces][end-1:end]] .= 5 + append!(xgrid[BFaceRegions], [5, 5]) + xgrid[FaceRegions][xgrid[BFaceFaces][(end - 1):end]] .= 5 xgrid[BFaceGeometries] = VectorOfConstants{ElementGeometries, Int}(Edge1D, 6) ## refine @@ -85,40 +85,40 @@ function main(; μ = [1.0,1.0], f = [10,-10], τ = 1, use_LM = true, nref = 4, o p = Unknown("p"; name = "LM for interface condition") ## problem description - PD = ProblemDescription() - assign_unknown!(PD, u1) - assign_unknown!(PD, u2) - assign_operator!(PD, BilinearOperator([grad(u1)]; regions = [1], factor = μ[1], kwargs...)) - assign_operator!(PD, BilinearOperator([grad(u2)]; regions = [2], factor = μ[2], kwargs...)) + PD = ProblemDescription() + assign_unknown!(PD, u1) + assign_unknown!(PD, u2) + assign_operator!(PD, BilinearOperator([grad(u1)]; regions = [1], factor = μ[1], kwargs...)) + assign_operator!(PD, BilinearOperator([grad(u2)]; regions = [2], factor = μ[2], kwargs...)) assign_operator!(PD, LinearOperator([id(u1)]; regions = [1], factor = f[1])) assign_operator!(PD, LinearOperator([id(u2)]; regions = [2], factor = f[2])) if use_LM assign_unknown!(PD, p) assign_operator!(PD, BilinearOperator(interface_condition_LM!, [id(p)], [id(u1), id(u2)]; regions = [5], transposed_copy = 1, entities = ON_FACES, kwargs...)) else - assign_operator!(PD, BilinearOperator(interface_condition!, [id(u1), id(u2)]; regions = [5], factor = τ, entities = ON_FACES, kwargs...)) + assign_operator!(PD, BilinearOperator(interface_condition!, [id(u1), id(u2)]; regions = [5], factor = τ, entities = ON_FACES, kwargs...)) end - assign_operator!(PD, InterpolateBoundaryData(u1, boundary_conditions!; regions = 1:4)) - assign_operator!(PD, InterpolateBoundaryData(u2, boundary_conditions!; regions = 1:4)) + assign_operator!(PD, InterpolateBoundaryData(u1, boundary_conditions!; regions = 1:4)) + assign_operator!(PD, InterpolateBoundaryData(u2, boundary_conditions!; regions = 1:4)) sol = solve(PD, use_LM ? [FES1, FES2, FES3] : [FES1, FES2]) plt = plot([id(u1), id(u2), dofgrid(u1), dofgrid(u2), grid(u1)], sol; Plotter = Plotter) - return sol, plt + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example206_CoupledSubGridProblems, "example206.png") #hide function jump_l2norm!(result, u, qpinfo) #hide - result[1] = (u[1] - u[2])^2 #hide + return result[1] = (u[1] - u[2])^2 #hide end #hide function runtests() #hide ## test if jump at interface vanishes for large penalty #hide - sol, plt = main(; τ = 1e9, nrefs = 2, order = 2) #hide + sol, plt = main(; τ = 1.0e9, nrefs = 2, order = 2) #hide jump_integrator = ItemIntegrator(jump_l2norm!, [id(1), id(2)]; entities = ON_BFACES, regions = [5], resultdim = 1, quadorder = 4) #hide jump_error = sqrt(sum(evaluate(jump_integrator, sol))) #hide @info "||[u_1 - u_2]|| = $(jump_error)" #hide - @test jump_error < 1e-8 #hide + return @test jump_error < 1.0e-8 #hide end #hide end #module diff --git a/examples/Example207_AdvectionUpwindDG.jl b/examples/Example207_AdvectionUpwindDG.jl index 9214ad1..a2865ce 100644 --- a/examples/Example207_AdvectionUpwindDG.jl +++ b/examples/Example207_AdvectionUpwindDG.jl @@ -50,38 +50,38 @@ using Test #hide function β!(result, qpinfo) x = qpinfo.x result[1] = - x[2] - result[2] = x[1] + return result[2] = x[1] end ## exact solution function exact_u!(result, qpinfo) x = qpinfo.x r = qpinfo.params[1] - result[1] = sqrt(x[1]^2 + x[2]^2) <= r ? 1 : 0 + return result[1] = sqrt(x[1]^2 + x[2]^2) <= r ? 1 : 0 end ## integrand of the advection bilinearform function advection_kernel!(result, input, qpinfo) β!(result, qpinfo) # evaluate wind β - result .*= input[1] # multiply with u_h + return result .*= input[1] # multiply with u_h end function outflow_kernel!(xgrid) beta = zeros(Float64, 2) - function closure(result, input, qpinfo) + return function closure(result, input, qpinfo) face = qpinfo.item β!(beta, qpinfo) - result[1] = dot(beta, qpinfo.normal) * input[1] + return result[1] = dot(beta, qpinfo.normal) * input[1] end end function upwind_kernel!(xgrid) beta = zeros(Float64, 2) - function closure(result, input, qpinfo) + return function closure(result, input, qpinfo) face = qpinfo.item β!(beta, qpinfo) result[1] = dot(beta, qpinfo.normal) - if result[1] > 0 ## wind blows this -> other + return if result[1] > 0 ## wind blows this -> other result[1] *= input[1] # upwind value = this else ## wind blows this <- other result[1] *= input[2] # upwind value = other @@ -92,7 +92,7 @@ end ## prepare error calculation function exact_error!(result, u, qpinfo) exact_u!(result, qpinfo) - result[1] = (result[1] - u[1])^2 + return result[1] = (result[1] - u[1])^2 end function main(; nref = 4, order = 0, r = 0.5, dg = true, Plotter = nothing, kwargs...) @@ -100,81 +100,81 @@ function main(; nref = 4, order = 0, r = 0.5, dg = true, Plotter = nothing, kwar ## grid xgrid = make_grid(nref, r) - ## problem description - PD = ProblemDescription("advection equation") - u = Unknown("u"; name = "species") - assign_unknown!(PD, u) + ## problem description + PD = ProblemDescription("advection equation") + u = Unknown("u"; name = "species") + assign_unknown!(PD, u) - ## advection operator - assign_operator!(PD, BilinearOperator(advection_kernel!, [grad(u)], [id(u)]; factor = -1, bonus_quadorder = 1, kwargs...)) + ## advection operator + assign_operator!(PD, BilinearOperator(advection_kernel!, [grad(u)], [id(u)]; factor = -1, bonus_quadorder = 1, kwargs...)) if dg - assign_operator!(PD, BilinearOperatorDG(upwind_kernel!(xgrid), [jump(id(u))], [this(id(u)), other(id(u))]; entities = ON_IFACES, bonus_quadorder = 1, kwargs...)) + assign_operator!(PD, BilinearOperatorDG(upwind_kernel!(xgrid), [jump(id(u))], [this(id(u)), other(id(u))]; entities = ON_IFACES, bonus_quadorder = 1, kwargs...)) end ## outflow boundary (regions [3,4]) and inflow boundary (regions [5,6]) - assign_operator!(PD, BilinearOperator(outflow_kernel!(xgrid), [id(u)]; entities = ON_BFACES, regions = [3,4])) - assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; regions = [5,6], params = [r], kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1,2], kwargs...)) - - ## solve - FES = FESpace{order == 0 ? L2P0{1} : H1Pk{1, 2, order}}(xgrid; broken = dg) - sol = solve(PD, FES; kwargs...) - - ## calculate L2 error and min/max value + assign_operator!(PD, BilinearOperator(outflow_kernel!(xgrid), [id(u)]; entities = ON_BFACES, regions = [3, 4])) + assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; regions = [5, 6], params = [r], kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 2], kwargs...)) + + ## solve + FES = FESpace{order == 0 ? L2P0{1} : H1Pk{1, 2, order}}(xgrid; broken = dg) + sol = solve(PD, FES; kwargs...) + + ## calculate L2 error and min/max value ErrorIntegrator = ItemIntegrator(exact_error!, [id(u)]; quadorder = 2 * order, params = [r], kwargs...) - L2error = sqrt(sum(view(evaluate(ErrorIntegrator, sol), 1, :))) - @info "L2 error = $L2error" + L2error = sqrt(sum(view(evaluate(ErrorIntegrator, sol), 1, :))) + @info "L2 error = $L2error" @info "extrema = $(extrema(sol.entries))" - ## plot - plt = plot([grid(u), id(u)], sol; Plotter = Plotter) + ## plot + plt = plot([grid(u), id(u)], sol; Plotter = Plotter) - return sol, plt + return sol, plt end ## grid generator script using SimplexGridBuilder/Triangulate function make_grid(nref = 4, radius = 0.5) - builder = SimplexGridBuilder(Generator = Triangulate) + builder = SimplexGridBuilder(Generator = Triangulate) ## define outer boundary nodes and regions - p1 = point!(builder, 0, 0) - p12 = point!(builder, radius, 0) - p2 = point!(builder, 1, 0) - p3 = point!(builder, 1, 1) - p4 = point!(builder, 0, 1) - p41 = point!(builder, 0, radius) - - facetregion!(builder, 5) - facet!(builder, p1, p12) - facetregion!(builder, 1) - facet!(builder, p12, p2) - facetregion!(builder, 2) - facet!(builder, p2, p3) - facetregion!(builder, 3) - facet!(builder, p3, p4) - facetregion!(builder, 4) - facet!(builder, p4, p41) - facetregion!(builder, 6) - facet!(builder, p41, p1) + p1 = point!(builder, 0, 0) + p12 = point!(builder, radius, 0) + p2 = point!(builder, 1, 0) + p3 = point!(builder, 1, 1) + p4 = point!(builder, 0, 1) + p41 = point!(builder, 0, radius) + + facetregion!(builder, 5) + facet!(builder, p1, p12) + facetregion!(builder, 1) + facet!(builder, p12, p2) + facetregion!(builder, 2) + facet!(builder, p2, p3) + facetregion!(builder, 3) + facet!(builder, p3, p4) + facetregion!(builder, 4) + facet!(builder, p4, p41) + facetregion!(builder, 6) + facet!(builder, p41, p1) ## add interior interface (quarter circle) - n = 4^(nref+1) - points = [point!(builder, radius * sin(t), radius * cos(t)) for t in range(0, π/2, length = n)] - facetregion!(builder, 7) - for i ∈ 2:n-2 - facet!(builder, points[i], points[i+1]) + n = 4^(nref + 1) + points = [point!(builder, radius * sin(t), radius * cos(t)) for t in range(0, π / 2, length = n)] + facetregion!(builder, 7) + for i in 2:(n - 2) + facet!(builder, points[i], points[i + 1]) end facet!(builder, p41, points[1]) facet!(builder, points[end], p12) ## generate - simplexgrid(builder, maxvolume = 1) + return simplexgrid(builder, maxvolume = 1) end generateplots = ExtendableFEM.default_generateplots(Example207_AdvectionUpwindDG, "example207.png") #hide function runtests() #hide ## test if P0-DG solution stays within bounds #hide - sol, ~ = main(; order = 0, nrefs = 2) #hide - @test norm(extrema(sol.entries) .- (0,1)) < 1e-12 #hide + sol, ~ = main(; order = 0, nrefs = 2) #hide + return @test norm(extrema(sol.entries) .- (0, 1)) < 1.0e-12 #hide end #hide end # module diff --git a/examples/Example210_LshapeAdaptivePoissonProblem.jl b/examples/Example210_LshapeAdaptivePoissonProblem.jl index 8cb94ca..edbf378 100644 --- a/examples/Example210_LshapeAdaptivePoissonProblem.jl +++ b/examples/Example210_LshapeAdaptivePoissonProblem.jl @@ -27,158 +27,158 @@ using Test #hide ## exact solution u for the Poisson problem function u!(result, qpinfo) - x = qpinfo.x - r2 = x[1]^2 + x[2]^2 - φ = atan(x[2], x[1]) - if φ < 0 - φ += 2 * pi - end - result[1] = r2^(1 / 3) * sin(2 * φ / 3) + x = qpinfo.x + r2 = x[1]^2 + x[2]^2 + φ = atan(x[2], x[1]) + if φ < 0 + φ += 2 * pi + end + return result[1] = r2^(1 / 3) * sin(2 * φ / 3) end ## gradient of exact solution function ∇u!(result, qpinfo) - x = qpinfo.x - φ = atan(x[2], x[1]) - r2 = x[1]^2 + x[2]^2 - if φ < 0 - φ += 2 * pi - end - ∂r = 2 / 3 * r2^(-1 / 6) * sin(2 * φ / 3) - ∂φ = 2 / 3 * r2^(-1 / 6) * cos(2 * φ / 3) - result[1] = cos(φ) * ∂r - sin(φ) * ∂φ - result[2] = sin(φ) * ∂r + cos(φ) * ∂φ + x = qpinfo.x + φ = atan(x[2], x[1]) + r2 = x[1]^2 + x[2]^2 + if φ < 0 + φ += 2 * pi + end + ∂r = 2 / 3 * r2^(-1 / 6) * sin(2 * φ / 3) + ∂φ = 2 / 3 * r2^(-1 / 6) * cos(2 * φ / 3) + result[1] = cos(φ) * ∂r - sin(φ) * ∂φ + return result[2] = sin(φ) * ∂r + cos(φ) * ∂φ end ## kernel for exact error calculation function exact_error!(result, u, qpinfo) - u!(result, qpinfo) - ∇u!(view(result, 2:3), qpinfo) - result .-= u - result .= result .^ 2 + u!(result, qpinfo) + ∇u!(view(result, 2:3), qpinfo) + result .-= u + return result .= result .^ 2 end ## kernel for face interpolation of normal jumps of gradient function gradnormalflux!(result, ∇u, qpinfo) - result[1] = dot(∇u, qpinfo.normal) + return result[1] = dot(∇u, qpinfo.normal) end ## kernel for face refinement indicator function η_face!(result, gradjump, qpinfo) - result .= qpinfo.volume * gradjump .^ 2 + return result .= qpinfo.volume * gradjump .^ 2 end ## kernel for cell refinement indicator function η_cell!(result, Δu, qpinfo) - result .= qpinfo.volume * Δu .^ 2 + return result .= qpinfo.volume * Δu .^ 2 end function main(; maxdofs = 4000, θ = 0.5, μ = 1.0, nrefs = 1, order = 2, Plotter = nothing, kwargs...) - ## problem description - PD = ProblemDescription("Poisson problem") - u = Unknown("u"; name = "u") - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator([grad(u)]; factor = μ, kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 2:7, bonus_quadorder = 4, kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 8])) - - ## discretize - xgrid = uniform_refine(grid_lshape(Triangle2D), nrefs) - - ## define interpolators and item integrators for error estimation and calculation - NormalJumpProjector = FaceInterpolator(gradnormalflux!, [jump(grad(u))]; resultdim = 1, order = order, only_interior = true, kwargs...) - ErrorIntegratorFace = ItemIntegrator(η_face!, [id(1)]; quadorder = 2 * order, entities = ON_FACES, kwargs...) - ErrorIntegratorCell = ItemIntegrator(η_cell!, [Δ(1)]; quadorder = 2 * (order - 2), entities = ON_CELLS, kwargs...) - ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(1), grad(1)]; quadorder = 2 * order, kwargs...) - - NDofs = zeros(Int, 0) - ResultsL2 = zeros(Float64, 0) - ResultsH1 = zeros(Float64, 0) - Resultsη = zeros(Float64, 0) - sol = nothing - ndofs = 0 - level = 0 - while ndofs < maxdofs - level += 1 - - ## SOLVE : create a solution vector and solve the problem - println("------- LEVEL $level") - if ndofs < 1000 - println(stdout, unicode_gridplot(xgrid)) - end - @time begin - ## solve - FES = FESpace{H1Pk{1, 2, order}}(xgrid) - sol = ExtendableFEM.solve(PD, FES; u = [u], kwargs...) - ndofs = length(sol[1]) - push!(NDofs, ndofs) - println("\t ndof = $ndofs") - print("@time solver =") - end - - ## ESTIMATE : calculate local error estimator contributions - @time begin - ## calculate error estimator - Jumps4Faces = evaluate!(NormalJumpProjector, sol) - η_F = evaluate(ErrorIntegratorFace, Jumps4Faces) - - η_T = evaluate(ErrorIntegratorCell, sol) - facecells = xgrid[FaceCells] - for face ∈ 1:size(facecells, 2) - η_F[face] += η_T[facecells[1, face]] - if facecells[2, face] > 0 - η_F[face] += η_T[facecells[2, face]] - end - end - - ## calculate total estimator - push!(Resultsη, sqrt(sum(η_F))) - print("@time η eval =") - end - - ## calculate exact L2 error, H1 error - @time begin - error = evaluate(ErrorIntegratorExact, sol) - push!(ResultsL2, sqrt(sum(view(error, 1, :)))) - push!(ResultsH1, sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :)))) - print("@time e eval =") - end - - if ndofs >= maxdofs - break - end - - ## MARK+REFINE : mesh refinement - @time begin - if θ >= 1 ## uniform mesh refinement - xgrid = uniform_refine(xgrid) - else ## adaptive mesh refinement - ## refine by red-green-blue refinement (incl. closuring) - facemarker = bulk_mark(xgrid, view(η_F, :), θ; indicator_AT = ON_FACES) - xgrid = RGB_refine(xgrid, facemarker) - end - print("@time refine =") - end - println("\t η = $(Resultsη[level])\n\t e = $(ResultsH1[level])") - end - - ## plot - plt = GridVisualizer(; Plotter = Plotter, layout = (2, 2), clear = true, size = (1000, 1000)) - scalarplot!(plt[1, 1], id(u), sol; levels = 7, title = "u_h") - plot_convergencehistory!(plt[1, 2], NDofs, [ResultsL2 ResultsH1 Resultsη]; add_h_powers = [order, order + 1], X_to_h = X -> order * X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "η"]) - gridplot!(plt[2, 1], xgrid; linewidth = 1) - gridplot!(plt[2, 2], xgrid; linewidth = 1, xlimits = [-0.0005, 0.0005], ylimits = [-0.0005, 0.0005]) - - ## print convergence history - print_convergencehistory(NDofs, [ResultsL2 ResultsH1 Resultsη]; X_to_h = X -> X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "η"]) - - return sol, plt + ## problem description + PD = ProblemDescription("Poisson problem") + u = Unknown("u"; name = "u") + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator([grad(u)]; factor = μ, kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 2:7, bonus_quadorder = 4, kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 8])) + + ## discretize + xgrid = uniform_refine(grid_lshape(Triangle2D), nrefs) + + ## define interpolators and item integrators for error estimation and calculation + NormalJumpProjector = FaceInterpolator(gradnormalflux!, [jump(grad(u))]; resultdim = 1, order = order, only_interior = true, kwargs...) + ErrorIntegratorFace = ItemIntegrator(η_face!, [id(1)]; quadorder = 2 * order, entities = ON_FACES, kwargs...) + ErrorIntegratorCell = ItemIntegrator(η_cell!, [Δ(1)]; quadorder = 2 * (order - 2), entities = ON_CELLS, kwargs...) + ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(1), grad(1)]; quadorder = 2 * order, kwargs...) + + NDofs = zeros(Int, 0) + ResultsL2 = zeros(Float64, 0) + ResultsH1 = zeros(Float64, 0) + Resultsη = zeros(Float64, 0) + sol = nothing + ndofs = 0 + level = 0 + while ndofs < maxdofs + level += 1 + + ## SOLVE : create a solution vector and solve the problem + println("------- LEVEL $level") + if ndofs < 1000 + println(stdout, unicode_gridplot(xgrid)) + end + @time begin + ## solve + FES = FESpace{H1Pk{1, 2, order}}(xgrid) + sol = ExtendableFEM.solve(PD, FES; u = [u], kwargs...) + ndofs = length(sol[1]) + push!(NDofs, ndofs) + println("\t ndof = $ndofs") + print("@time solver =") + end + + ## ESTIMATE : calculate local error estimator contributions + @time begin + ## calculate error estimator + Jumps4Faces = evaluate!(NormalJumpProjector, sol) + η_F = evaluate(ErrorIntegratorFace, Jumps4Faces) + + η_T = evaluate(ErrorIntegratorCell, sol) + facecells = xgrid[FaceCells] + for face in 1:size(facecells, 2) + η_F[face] += η_T[facecells[1, face]] + if facecells[2, face] > 0 + η_F[face] += η_T[facecells[2, face]] + end + end + + ## calculate total estimator + push!(Resultsη, sqrt(sum(η_F))) + print("@time η eval =") + end + + ## calculate exact L2 error, H1 error + @time begin + error = evaluate(ErrorIntegratorExact, sol) + push!(ResultsL2, sqrt(sum(view(error, 1, :)))) + push!(ResultsH1, sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :)))) + print("@time e eval =") + end + + if ndofs >= maxdofs + break + end + + ## MARK+REFINE : mesh refinement + @time begin + if θ >= 1 ## uniform mesh refinement + xgrid = uniform_refine(xgrid) + else ## adaptive mesh refinement + ## refine by red-green-blue refinement (incl. closuring) + facemarker = bulk_mark(xgrid, view(η_F, :), θ; indicator_AT = ON_FACES) + xgrid = RGB_refine(xgrid, facemarker) + end + print("@time refine =") + end + println("\t η = $(Resultsη[level])\n\t e = $(ResultsH1[level])") + end + + ## plot + plt = GridVisualizer(; Plotter = Plotter, layout = (2, 2), clear = true, size = (1000, 1000)) + scalarplot!(plt[1, 1], id(u), sol; levels = 7, title = "u_h") + plot_convergencehistory!(plt[1, 2], NDofs, [ResultsL2 ResultsH1 Resultsη]; add_h_powers = [order, order + 1], X_to_h = X -> order * X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "η"]) + gridplot!(plt[2, 1], xgrid; linewidth = 1) + gridplot!(plt[2, 2], xgrid; linewidth = 1, xlimits = [-0.0005, 0.0005], ylimits = [-0.0005, 0.0005]) + + ## print convergence history + print_convergencehistory(NDofs, [ResultsL2 ResultsH1 Resultsη]; X_to_h = X -> X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "η"]) + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example210_LshapeAdaptivePoissonProblem, "example210.png") #hide function runtests() #hide - sol, plt = main(; maxdofs = 1000, order = 2) #hide - @test length(sol.entries) == 1007 #hide + sol, plt = main(; maxdofs = 1000, order = 2) #hide + return @test length(sol.entries) == 1007 #hide end #hide end # module diff --git a/examples/Example211_LshapeAdaptiveEQPoissonProblem.jl b/examples/Example211_LshapeAdaptiveEQPoissonProblem.jl index ca2fef5..f926432 100644 --- a/examples/Example211_LshapeAdaptiveEQPoissonProblem.jl +++ b/examples/Example211_LshapeAdaptiveEQPoissonProblem.jl @@ -43,42 +43,42 @@ using Test #hide ## exact solution u for the Poisson problem function u!(result, qpinfo) - x = qpinfo.x - r2 = x[1]^2 + x[2]^2 - φ = atan(x[2], x[1]) - if φ < 0 - φ += 2 * pi - end - result[1] = r2^(1 / 3) * sin(2 * φ / 3) + x = qpinfo.x + r2 = x[1]^2 + x[2]^2 + φ = atan(x[2], x[1]) + if φ < 0 + φ += 2 * pi + end + return result[1] = r2^(1 / 3) * sin(2 * φ / 3) end ## gradient of exact solution function ∇u!(result, qpinfo) - x = qpinfo.x - φ = atan(x[2], x[1]) - r2 = x[1]^2 + x[2]^2 - if φ < 0 - φ += 2 * pi - end - ∂r = 2 / 3 * r2^(-1 / 6) * sin(2 * φ / 3) - ∂φ = 2 / 3 * r2^(-1 / 6) * cos(2 * φ / 3) - result[1] = cos(φ) * ∂r - sin(φ) * ∂φ - result[2] = sin(φ) * ∂r + cos(φ) * ∂φ + x = qpinfo.x + φ = atan(x[2], x[1]) + r2 = x[1]^2 + x[2]^2 + if φ < 0 + φ += 2 * pi + end + ∂r = 2 / 3 * r2^(-1 / 6) * sin(2 * φ / 3) + ∂φ = 2 / 3 * r2^(-1 / 6) * cos(2 * φ / 3) + result[1] = cos(φ) * ∂r - sin(φ) * ∂φ + return result[2] = sin(φ) * ∂r + cos(φ) * ∂φ end ## kernel for exact error calculation function exact_error!(result, u, qpinfo) - u!(result, qpinfo) - ∇u!(view(result, 2:3), qpinfo) - result .-= u - result .= result .^ 2 + u!(result, qpinfo) + ∇u!(view(result, 2:3), qpinfo) + result .-= u + return result .= result .^ 2 end ## kernel for equilibration error estimator function eqestimator_kernel!(result, input, qpinfo) - σ_h, divσ_h, ∇u_h = view(input, 1:2), input[3], view(input, 4:5) - result[1] = norm(σ_h .- ∇u_h)^2 + divσ_h^2 - return nothing + σ_h, divσ_h, ∇u_h = view(input, 1:2), input[3], view(input, 4:5) + result[1] = norm(σ_h .- ∇u_h)^2 + divσ_h^2 + return nothing end ## unknowns for primal and dual problem @@ -88,278 +88,279 @@ u = Unknown("u"; name = "u") ## everything is wrapped in a main function function main(; maxdofs = 4000, μ = 1, order = 2, nlevels = 16, θ = 0.5, Plotter = nothing, kwargs...) - ## initial grid - xgrid = grid_lshape(Triangle2D) - - ## choose some finite elements for primal and dual problem (= for equilibrated fluxes) - FEType = H1Pk{1,2,order} - FETypeDual = HDIVRTk{2, order} - - ## setup Poisson problem - PD = ProblemDescription("Poisson problem") - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator([grad(u)]; factor = μ, kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 2:7, bonus_quadorder = 4, kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 8])) - - ## define error estimator : || σ_h - ∇u_h ||^2 + || div σ_h ||^2 - EQIntegrator = ItemIntegrator(eqestimator_kernel!, [id(σ), div(σ), grad(u)]; resultdim = 1, quadorder = 2 * order) - - ## setup exact error evaluations - ErrorIntegrator = ItemIntegrator(exact_error!, [id(u), grad(u)]; quadorder = 2 * order, kwargs...) - - ## refinement loop (only uniform for now) - NDofs = zeros(Int, 0) - NDofsDual = zeros(Int, 0) - ResultsL2 = zeros(Float64, 0) - ResultsH1 = zeros(Float64, 0) - Resultsη = zeros(Float64, 0) - sol = nothing - level = 0 - while (true) - level += 1 - - ## create a solution vector and solve the problem - FES = FESpace{FEType}(xgrid) - sol = solve(PD, FES) - push!(NDofs, length(view(sol[u]))) - println("\n SOLVE LEVEL $level") - println(" ndofs = $(NDofs[end])") - - ## evaluate eqilibration error estimator and append it to sol vector (for plotting etc.) - local_equilibration_estimator!(sol, FETypeDual) - η4cell = evaluate(EQIntegrator, sol) - push!(Resultsη, sqrt(sum(view(η4cell, 1, :)))) - - ## calculate L2 error, H1 error, estimator, dual L2 error and write to results - push!(NDofsDual, length(view(sol[σ]))) - error = evaluate(ErrorIntegrator, sol) - push!(ResultsL2, sqrt(sum(view(error, 1, :)))) - push!(ResultsH1, sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :)))) - println(" ESTIMATE") - println(" ndofsDual = $(NDofsDual[end])") - println(" estim H1 error = $(Resultsη[end])") - println(" exact H1 error = $(ResultsH1[end])") - println(" exact L2 error = $(ResultsL2[end])") - - if NDofs[end] >= maxdofs - break - end - - ## mesh refinement - if θ >= 1 ## uniform mesh refinement - xgrid = uniform_refine(xgrid) - else ## adaptive mesh refinement - facemarker = bulk_mark(xgrid, view(η4cell, :), θ; indicator_AT = ON_CELLS) - xgrid = RGB_refine(xgrid, facemarker) - end - end - - ## plot - plt = GridVisualizer(; Plotter = Plotter, layout = (2, 2), clear = true, resolution = (1000, 1000)) - scalarplot!(plt[1, 1], id(u), sol; levels = 11, title = "u_h") - plot_convergencehistory!(plt[1, 2], NDofs, [ResultsL2 ResultsH1 Resultsη]; add_h_powers = [order, order + 1], X_to_h = X -> order * X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "η"]) - gridplot!(plt[2, 1], xgrid; linewidth = 1) - gridplot!(plt[2, 2], xgrid; linewidth = 1, xlimits = [-0.0005, 0.0005], ylimits = [-0.0005, 0.0005]) - - ## print/plot convergence history - print_convergencehistory(NDofs, [ResultsL2 ResultsH1 Resultsη]; X_to_h = X -> X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "η"]) - - return sol, plt + ## initial grid + xgrid = grid_lshape(Triangle2D) + + ## choose some finite elements for primal and dual problem (= for equilibrated fluxes) + FEType = H1Pk{1, 2, order} + FETypeDual = HDIVRTk{2, order} + + ## setup Poisson problem + PD = ProblemDescription("Poisson problem") + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator([grad(u)]; factor = μ, kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 2:7, bonus_quadorder = 4, kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 8])) + + ## define error estimator : || σ_h - ∇u_h ||^2 + || div σ_h ||^2 + EQIntegrator = ItemIntegrator(eqestimator_kernel!, [id(σ), div(σ), grad(u)]; resultdim = 1, quadorder = 2 * order) + + ## setup exact error evaluations + ErrorIntegrator = ItemIntegrator(exact_error!, [id(u), grad(u)]; quadorder = 2 * order, kwargs...) + + ## refinement loop (only uniform for now) + NDofs = zeros(Int, 0) + NDofsDual = zeros(Int, 0) + ResultsL2 = zeros(Float64, 0) + ResultsH1 = zeros(Float64, 0) + Resultsη = zeros(Float64, 0) + sol = nothing + level = 0 + while (true) + level += 1 + + ## create a solution vector and solve the problem + FES = FESpace{FEType}(xgrid) + sol = solve(PD, FES) + push!(NDofs, length(view(sol[u]))) + println("\n SOLVE LEVEL $level") + println(" ndofs = $(NDofs[end])") + + ## evaluate eqilibration error estimator and append it to sol vector (for plotting etc.) + local_equilibration_estimator!(sol, FETypeDual) + η4cell = evaluate(EQIntegrator, sol) + push!(Resultsη, sqrt(sum(view(η4cell, 1, :)))) + + ## calculate L2 error, H1 error, estimator, dual L2 error and write to results + push!(NDofsDual, length(view(sol[σ]))) + error = evaluate(ErrorIntegrator, sol) + push!(ResultsL2, sqrt(sum(view(error, 1, :)))) + push!(ResultsH1, sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :)))) + println(" ESTIMATE") + println(" ndofsDual = $(NDofsDual[end])") + println(" estim H1 error = $(Resultsη[end])") + println(" exact H1 error = $(ResultsH1[end])") + println(" exact L2 error = $(ResultsL2[end])") + + if NDofs[end] >= maxdofs + break + end + + ## mesh refinement + if θ >= 1 ## uniform mesh refinement + xgrid = uniform_refine(xgrid) + else ## adaptive mesh refinement + facemarker = bulk_mark(xgrid, view(η4cell, :), θ; indicator_AT = ON_CELLS) + xgrid = RGB_refine(xgrid, facemarker) + end + end + + ## plot + plt = GridVisualizer(; Plotter = Plotter, layout = (2, 2), clear = true, resolution = (1000, 1000)) + scalarplot!(plt[1, 1], id(u), sol; levels = 11, title = "u_h") + plot_convergencehistory!(plt[1, 2], NDofs, [ResultsL2 ResultsH1 Resultsη]; add_h_powers = [order, order + 1], X_to_h = X -> order * X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "η"]) + gridplot!(plt[2, 1], xgrid; linewidth = 1) + gridplot!(plt[2, 2], xgrid; linewidth = 1, xlimits = [-0.0005, 0.0005], ylimits = [-0.0005, 0.0005]) + + ## print/plot convergence history + print_convergencehistory(NDofs, [ResultsL2 ResultsH1 Resultsη]; X_to_h = X -> X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "η"]) + + return sol, plt end ## this function computes the local equilibrated fluxes ## by solving local problems on (disjunct groups of) node patches function local_equilibration_estimator!(sol, FETypeDual) - ## needed grid stuff - xgrid = sol[u].FES.xgrid - xCellNodes::Array{Int32, 2} = xgrid[CellNodes] - xCellVolumes::Array{Float64, 1} = xgrid[CellVolumes] - xNodeCells::Adjacency{Int32} = atranspose(xCellNodes) - nnodes::Int = num_sources(xNodeCells) - - ## get node patch groups that can be solved in parallel - group4node = xgrid[NodePatchGroups] - - ## init equilibration space (and Lagrange multiplier space) - FESDual = FESpace{FETypeDual}(xgrid) - xItemDofs::Union{VariableTargetAdjacency{Int32}, SerialVariableTargetAdjacency{Int32}, Array{Int32, 2}} = FESDual[CellDofs] - xItemDofs_uh::Union{VariableTargetAdjacency{Int32}, SerialVariableTargetAdjacency{Int32}, Array{Int32, 2}} = sol[u].FES[CellDofs] - - ## append block in solution vector for equilibrated fluxes - append!(sol, FESDual; tag = σ) - - ## partition of unity and their gradients = P1 basis functions - POUFES = FESpace{H1P1{1}}(xgrid) - POUqf = QuadratureRule{Float64, Triangle2D}(0) - - ## quadrature formulas - qf = QuadratureRule{Float64, Triangle2D}(2 * get_polynomialorder(FETypeDual, Triangle2D)) - weights::Array{Float64, 1} = qf.w - - ## some constants - offset::Int = sol[u].offset - div_penalty::Float64 = 1e5 # divergence constraint is realized by penalisation - bnd_penalty::Float64 = 1e60 # penalty for non-involved dofs of a group - maxdofs::Int = max_num_targets_per_source(xItemDofs) - maxdofs_uh::Int = max_num_targets_per_source(xItemDofs_uh) - - ## redistribute groups for more equilibrated thread load (first groups are larger) - maxgroups = maximum(group4node) - groups = Array{Int, 1}(1:maxgroups) - for j::Int ∈ 1:floor(maxgroups / 2) - a = groups[j] - groups[j] = groups[2*j] - groups[2*j] = a - end - X = Array{Array{Float64, 1}, 1}(undef, maxgroups) - - function solve_patchgroup!(group) - ## temporary variables - graduh = zeros(Float64, 2) - coeffs_uh = zeros(Float64, maxdofs_uh) - Alocal = zeros(Float64, maxdofs, maxdofs) - blocal = zeros(Float64, maxdofs) - - ## init system - A = ExtendableSparseMatrix{Float64, Int64}(FESDual.ndofs, FESDual.ndofs) - b = zeros(Float64, FESDual.ndofs) - - ## init FEBasiEvaluators - FEE_∇φ = FEEvaluator(POUFES, Gradient, POUqf) - FEE_xref = FEEvaluator(POUFES, Identity, qf) - FEE_∇u = FEEvaluator(sol[u].FES, Gradient, qf) - FEE_div = FEEvaluator(FESDual, Divergence, qf) - FEE_id = FEEvaluator(FESDual, Identity, qf) - idvals = FEE_id.cvals - divvals = FEE_div.cvals - xref_vals = FEE_xref.cvals - ∇φvals = FEE_∇φ.cvals - - ## find dofs at boundary of current node patches - ## and in interior of cells outside of current node patch group - is_noninvolveddof = zeros(Bool, FESDual.ndofs) - outside_cell::Bool = false - for cell ∈ 1:num_cells(xgrid) - outside_cell = true - for k ∈ 1:3 - if group4node[xCellNodes[k, cell]] == group - outside_cell = false - break - end - end - if (outside_cell) # mark interior dofs of outside cell - for j ∈ 1:maxdofs - is_noninvolveddof[xItemDofs[j, cell]] = true - end - end - end - - for node ∈ 1:nnodes - if group4node[node] == group - for c ∈ 1:num_targets(xNodeCells, node) - cell = xNodeCells[c, node] - - ## find local node number of global node z - ## and evaluate (constant) gradient of nodal basis function phi_z - localnode = 1 - while xCellNodes[localnode, cell] != node - localnode += 1 - end - FEE_∇φ.citem[] = cell - update_basis!(FEE_∇φ) - - ## read coefficients for discrete flux - for j ∈ 1:maxdofs_uh - coeffs_uh[j] = sol.entries[offset + xItemDofs_uh[j, cell]] - end - - ## update other FE evaluators - FEE_∇u.citem[] = cell - FEE_div.citem[] = cell - FEE_id.citem[] = cell - update_basis!(FEE_∇u) - update_basis!(FEE_div) - update_basis!(FEE_id) - - ## assembly on this cell - for i in eachindex(weights) - weight = weights[i] * xCellVolumes[cell] - - ## evaluate grad(u_h) and nodal basis function at quadrature point - fill!(graduh, 0) - eval_febe!(graduh, FEE_∇u, coeffs_uh, i) - - ## compute residual -f*phi_z + grad(u_h) * grad(phi_z) at quadrature point i ( f = 0 in this example !!! ) - temp2 = div_penalty * sqrt(xCellVolumes[cell]) * weight - temp = temp2 * dot(graduh, view(∇φvals,:,localnode,1)) - for dof_i ∈ 1:maxdofs - ## right-hand side for best-approximation (grad(u_h)*phi) - blocal[dof_i] += dot(graduh, view(idvals,:,dof_i, i)) * xref_vals[1, localnode, i] * weight - ## mass matrix Hdiv - for dof_j ∈ dof_i:maxdofs - Alocal[dof_i, dof_j] += dot(view(idvals,:,dof_i, i), view(idvals,:,dof_j, i)) * weight - end - ## div-div matrix Hdiv * penalty (quick and dirty to avoid Lagrange multiplier) - blocal[dof_i] += temp * divvals[1,dof_i,i] - temp3 = temp2 * divvals[1,dof_i,i] - for dof_j ∈ dof_i:maxdofs - Alocal[dof_i, dof_j] += temp3 * divvals[1,dof_j,i] - end - end - end - - ## write into global A and b - for dof_i ∈ 1:maxdofs - dofi = xItemDofs[dof_i, cell] - b[dofi] += blocal[dof_i] - for dof_j ∈ 1:maxdofs - dofj = xItemDofs[dof_j, cell] - if dof_j < dof_i # use that Alocal is symmetric - _addnz(A, dofi, dofj, Alocal[dof_j, dof_i], 1) - else - _addnz(A, dofi, dofj, Alocal[dof_i, dof_j], 1) - end - end - end - - ## reset local A and b - fill!(Alocal, 0) - fill!(blocal, 0) - end - end - end - - ## penalize dofs that are not involved - for j ∈ 1:FESDual.ndofs - if is_noninvolveddof[j] - A[j, j] = bnd_penalty - b[j] = 0 - end - end - - ## solve local problem - return A \ b - end - - ## solve equilibration problems on vertex patches (in parallel) - Threads.@threads for group in groups - grouptime = @elapsed begin - @info " Starting equilibrating patch group $group on thread $(Threads.threadid())... " - X[group] = solve_patchgroup!(group) - end - @info "Finished equilibration patch group $group on thread $(Threads.threadid()) in $(grouptime)s " - end - - ## write local solutions to global vector (sequentially) - for group ∈ 1:maxgroups - view(sol[σ]) .+= X[group] - end + ## needed grid stuff + xgrid = sol[u].FES.xgrid + xCellNodes::Array{Int32, 2} = xgrid[CellNodes] + xCellVolumes::Array{Float64, 1} = xgrid[CellVolumes] + xNodeCells::Adjacency{Int32} = atranspose(xCellNodes) + nnodes::Int = num_sources(xNodeCells) + + ## get node patch groups that can be solved in parallel + group4node = xgrid[NodePatchGroups] + + ## init equilibration space (and Lagrange multiplier space) + FESDual = FESpace{FETypeDual}(xgrid) + xItemDofs::Union{VariableTargetAdjacency{Int32}, SerialVariableTargetAdjacency{Int32}, Array{Int32, 2}} = FESDual[CellDofs] + xItemDofs_uh::Union{VariableTargetAdjacency{Int32}, SerialVariableTargetAdjacency{Int32}, Array{Int32, 2}} = sol[u].FES[CellDofs] + + ## append block in solution vector for equilibrated fluxes + append!(sol, FESDual; tag = σ) + + ## partition of unity and their gradients = P1 basis functions + POUFES = FESpace{H1P1{1}}(xgrid) + POUqf = QuadratureRule{Float64, Triangle2D}(0) + + ## quadrature formulas + qf = QuadratureRule{Float64, Triangle2D}(2 * get_polynomialorder(FETypeDual, Triangle2D)) + weights::Array{Float64, 1} = qf.w + + ## some constants + offset::Int = sol[u].offset + div_penalty::Float64 = 1.0e5 # divergence constraint is realized by penalisation + bnd_penalty::Float64 = 1.0e60 # penalty for non-involved dofs of a group + maxdofs::Int = max_num_targets_per_source(xItemDofs) + maxdofs_uh::Int = max_num_targets_per_source(xItemDofs_uh) + + ## redistribute groups for more equilibrated thread load (first groups are larger) + maxgroups = maximum(group4node) + groups = Array{Int, 1}(1:maxgroups) + for j::Int in 1:floor(maxgroups / 2) + a = groups[j] + groups[j] = groups[2 * j] + groups[2 * j] = a + end + X = Array{Array{Float64, 1}, 1}(undef, maxgroups) + + function solve_patchgroup!(group) + ## temporary variables + graduh = zeros(Float64, 2) + coeffs_uh = zeros(Float64, maxdofs_uh) + Alocal = zeros(Float64, maxdofs, maxdofs) + blocal = zeros(Float64, maxdofs) + + ## init system + A = ExtendableSparseMatrix{Float64, Int64}(FESDual.ndofs, FESDual.ndofs) + b = zeros(Float64, FESDual.ndofs) + + ## init FEBasiEvaluators + FEE_∇φ = FEEvaluator(POUFES, Gradient, POUqf) + FEE_xref = FEEvaluator(POUFES, Identity, qf) + FEE_∇u = FEEvaluator(sol[u].FES, Gradient, qf) + FEE_div = FEEvaluator(FESDual, Divergence, qf) + FEE_id = FEEvaluator(FESDual, Identity, qf) + idvals = FEE_id.cvals + divvals = FEE_div.cvals + xref_vals = FEE_xref.cvals + ∇φvals = FEE_∇φ.cvals + + ## find dofs at boundary of current node patches + ## and in interior of cells outside of current node patch group + is_noninvolveddof = zeros(Bool, FESDual.ndofs) + outside_cell::Bool = false + for cell in 1:num_cells(xgrid) + outside_cell = true + for k in 1:3 + if group4node[xCellNodes[k, cell]] == group + outside_cell = false + break + end + end + if (outside_cell) # mark interior dofs of outside cell + for j in 1:maxdofs + is_noninvolveddof[xItemDofs[j, cell]] = true + end + end + end + + for node in 1:nnodes + if group4node[node] == group + for c in 1:num_targets(xNodeCells, node) + cell = xNodeCells[c, node] + + ## find local node number of global node z + ## and evaluate (constant) gradient of nodal basis function phi_z + localnode = 1 + while xCellNodes[localnode, cell] != node + localnode += 1 + end + FEE_∇φ.citem[] = cell + update_basis!(FEE_∇φ) + + ## read coefficients for discrete flux + for j in 1:maxdofs_uh + coeffs_uh[j] = sol.entries[offset + xItemDofs_uh[j, cell]] + end + + ## update other FE evaluators + FEE_∇u.citem[] = cell + FEE_div.citem[] = cell + FEE_id.citem[] = cell + update_basis!(FEE_∇u) + update_basis!(FEE_div) + update_basis!(FEE_id) + + ## assembly on this cell + for i in eachindex(weights) + weight = weights[i] * xCellVolumes[cell] + + ## evaluate grad(u_h) and nodal basis function at quadrature point + fill!(graduh, 0) + eval_febe!(graduh, FEE_∇u, coeffs_uh, i) + + ## compute residual -f*phi_z + grad(u_h) * grad(phi_z) at quadrature point i ( f = 0 in this example !!! ) + temp2 = div_penalty * sqrt(xCellVolumes[cell]) * weight + temp = temp2 * dot(graduh, view(∇φvals, :, localnode, 1)) + for dof_i in 1:maxdofs + ## right-hand side for best-approximation (grad(u_h)*phi) + blocal[dof_i] += dot(graduh, view(idvals, :, dof_i, i)) * xref_vals[1, localnode, i] * weight + ## mass matrix Hdiv + for dof_j in dof_i:maxdofs + Alocal[dof_i, dof_j] += dot(view(idvals, :, dof_i, i), view(idvals, :, dof_j, i)) * weight + end + ## div-div matrix Hdiv * penalty (quick and dirty to avoid Lagrange multiplier) + blocal[dof_i] += temp * divvals[1, dof_i, i] + temp3 = temp2 * divvals[1, dof_i, i] + for dof_j in dof_i:maxdofs + Alocal[dof_i, dof_j] += temp3 * divvals[1, dof_j, i] + end + end + end + + ## write into global A and b + for dof_i in 1:maxdofs + dofi = xItemDofs[dof_i, cell] + b[dofi] += blocal[dof_i] + for dof_j in 1:maxdofs + dofj = xItemDofs[dof_j, cell] + if dof_j < dof_i # use that Alocal is symmetric + _addnz(A, dofi, dofj, Alocal[dof_j, dof_i], 1) + else + _addnz(A, dofi, dofj, Alocal[dof_i, dof_j], 1) + end + end + end + + ## reset local A and b + fill!(Alocal, 0) + fill!(blocal, 0) + end + end + end + + ## penalize dofs that are not involved + for j in 1:FESDual.ndofs + if is_noninvolveddof[j] + A[j, j] = bnd_penalty + b[j] = 0 + end + end + + ## solve local problem + return A \ b + end + + ## solve equilibration problems on vertex patches (in parallel) + Threads.@threads for group in groups + grouptime = @elapsed begin + @info " Starting equilibrating patch group $group on thread $(Threads.threadid())... " + X[group] = solve_patchgroup!(group) + end + @info "Finished equilibration patch group $group on thread $(Threads.threadid()) in $(grouptime)s " + end + + ## write local solutions to global vector (sequentially) + for group in 1:maxgroups + view(sol[σ]) .+= X[group] + end + return end generateplots = ExtendableFEM.default_generateplots(Example211_LshapeAdaptiveEQPoissonProblem, "example211.png") #hide function runtests() #hide - sol, plt = main(; maxdofs = 1000, order = 2) #hide - @test length(sol.entries) == 8641 #hide + sol, plt = main(; maxdofs = 1000, order = 2) #hide + return @test length(sol.entries) == 8641 #hide end #hide end diff --git a/examples/Example220_ReactionConvectionDiffusion.jl b/examples/Example220_ReactionConvectionDiffusion.jl index 1a2ba3d..b14dd39 100644 --- a/examples/Example220_ReactionConvectionDiffusion.jl +++ b/examples/Example220_ReactionConvectionDiffusion.jl @@ -30,115 +30,115 @@ using Test #hide const α = 0.01 const β = [1.0, 0] -const ν = 1e-5 +const ν = 1.0e-5 function u!(result, qpinfo) - x = qpinfo.x - result[1] = x[1] * x[2] * (x[1] - 1) * (x[2] - 1) + x[1] + x = qpinfo.x + return result[1] = x[1] * x[2] * (x[1] - 1) * (x[2] - 1) + x[1] end function ∇u!(result, qpinfo) - x = qpinfo.x - result[1] = x[2] * (2 * x[1] - 1) * (x[2] - 1) + 1 - result[2] = x[1] * (2 * x[2] - 1) * (x[1] - 1) + x = qpinfo.x + result[1] = x[2] * (2 * x[1] - 1) * (x[2] - 1) + 1 + return result[2] = x[1] * (2 * x[2] - 1) * (x[1] - 1) end function Δu!(result, qpinfo) - x = qpinfo.x - result[1] = 2 * (x[2] * (x[2] - 1) + x[1] * (x[1] - 1)) + x = qpinfo.x + return result[1] = 2 * (x[2] * (x[2] - 1) + x[1] * (x[1] - 1)) end function rhs() - ∇u = zeros(Float64, 2) - Δu = zeros(Float64, 1) - u = zeros(Float64, 1) - function closure(result, qpinfo) - ∇u!(∇u, qpinfo) - u!(u, qpinfo) - Δu!(Δu, qpinfo) - result[1] = -ν * Δu[1] + α * u[1] + dot(β, ∇u) - end + ∇u = zeros(Float64, 2) + Δu = zeros(Float64, 1) + u = zeros(Float64, 1) + return function closure(result, qpinfo) + ∇u!(∇u, qpinfo) + u!(u, qpinfo) + Δu!(Δu, qpinfo) + return result[1] = -ν * Δu[1] + α * u[1] + dot(β, ∇u) + end end function kernel_DCR!(result, input, qpinfo) - u, ∇u = view(input, 1), view(input, 2:3) - result[1] = α * u[1] + dot(β, ∇u) - result[2] = ν * ∇u[1] - result[3] = ν * ∇u[2] - return nothing + u, ∇u = view(input, 1), view(input, 2:3) + result[1] = α * u[1] + dot(β, ∇u) + result[2] = ν * ∇u[1] + result[3] = ν * ∇u[2] + return nothing end ## kernel for exact error calculation function exact_error!(result, u, qpinfo) - u!(result, qpinfo) - ∇u!(view(result, 2:3), qpinfo) - result .-= u - result .= result .^ 2 + u!(result, qpinfo) + ∇u!(view(result, 2:3), qpinfo) + result .-= u + return result .= result .^ 2 end ## stab_kernel! function stab_kernel!(result, ∇u, qpinfo) - result .= ∇u .* qpinfo.volume^2 + return result .= ∇u .* qpinfo.volume^2 end ## everything is wrapped in a main function -function main(; Plotter = nothing, τ = 1e-2, nlevels = 5, order = 2, kwargs...) - - ## create problem description - PD = ProblemDescription("reaction-convection-diffusion problem") - u = Unknown("u") - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator(kernel_DCR!, [id(u), grad(u)]; bonus_quadorder = 1, kwargs...)) - assign_operator!(PD, LinearOperator(rhs(), [id(u)]; bonus_quadorder = 2, kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 1:4, kwargs...)) - - ## add a gradient jump (interior penalty) stabilisation for dominant convection - if τ > 0 - assign_operator!(PD, BilinearOperatorDG(stab_kernel!, [jump(grad(u))]; entities = ON_IFACES, factor = τ)) - end - - ## prepare error calculation - ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(1), grad(1)]; quadorder = 2 * order, kwargs...) - Results = zeros(Float64, nlevels, 4) - NDofs = zeros(Int, nlevels) - - ## refinement loop over levels - sol = nothing - xgrid = grid_unitsquare(Triangle2D) # initial grid - for level ∈ 1:nlevels - ## uniform mesh refinement - xgrid = uniform_refine(xgrid) - - ## generate FESpace and solve - FES = FESpace{H1Pk{1, 2, order}}(xgrid) - sol = solve(PD, FES) - - ## compute L2 and H1 errors and save data - NDofs[level] = length(sol.entries) - error = evaluate(ErrorIntegratorExact, sol) - Results[level, 1] = sqrt(sum(view(error, 1, :))) - Results[level, 3] = sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :))) - - ## interpolate (just for comparison) - I = FEVector(FES) - interpolate!(I[1], u!) - error = evaluate(ErrorIntegratorExact, I) - Results[level, 2] = sqrt(sum(view(error, 1, :))) - Results[level, 4] = sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :))) - end - - ## plot - plt = plot([id(u), grad(u)], sol; add = 1, ncols = 3, Plotter = Plotter) - plot_convergencehistory!(plt[1,3], NDofs, Results; add_h_powers = [order, order + 1], X_to_h = X -> X .^ (-1 / 2), legend = :lb, ylabels = ["|| u - u_h ||", "|| u - Iu ||", "|| ∇(u - u_h) ||", "|| ∇(u - Iu) ||"], limits = (1e-8, 1e-1)) - - ## print convergence history - print_convergencehistory(NDofs, Results; X_to_h = X -> X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| u - Iu ||", "|| ∇(u - u_h) ||", "|| ∇(u - Iu) ||"]) - - return Results, plt +function main(; Plotter = nothing, τ = 1.0e-2, nlevels = 5, order = 2, kwargs...) + + ## create problem description + PD = ProblemDescription("reaction-convection-diffusion problem") + u = Unknown("u") + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator(kernel_DCR!, [id(u), grad(u)]; bonus_quadorder = 1, kwargs...)) + assign_operator!(PD, LinearOperator(rhs(), [id(u)]; bonus_quadorder = 2, kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 1:4, kwargs...)) + + ## add a gradient jump (interior penalty) stabilisation for dominant convection + if τ > 0 + assign_operator!(PD, BilinearOperatorDG(stab_kernel!, [jump(grad(u))]; entities = ON_IFACES, factor = τ)) + end + + ## prepare error calculation + ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(1), grad(1)]; quadorder = 2 * order, kwargs...) + Results = zeros(Float64, nlevels, 4) + NDofs = zeros(Int, nlevels) + + ## refinement loop over levels + sol = nothing + xgrid = grid_unitsquare(Triangle2D) # initial grid + for level in 1:nlevels + ## uniform mesh refinement + xgrid = uniform_refine(xgrid) + + ## generate FESpace and solve + FES = FESpace{H1Pk{1, 2, order}}(xgrid) + sol = solve(PD, FES) + + ## compute L2 and H1 errors and save data + NDofs[level] = length(sol.entries) + error = evaluate(ErrorIntegratorExact, sol) + Results[level, 1] = sqrt(sum(view(error, 1, :))) + Results[level, 3] = sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :))) + + ## interpolate (just for comparison) + I = FEVector(FES) + interpolate!(I[1], u!) + error = evaluate(ErrorIntegratorExact, I) + Results[level, 2] = sqrt(sum(view(error, 1, :))) + Results[level, 4] = sqrt(sum(view(error, 2, :)) + sum(view(error, 3, :))) + end + + ## plot + plt = plot([id(u), grad(u)], sol; add = 1, ncols = 3, Plotter = Plotter) + plot_convergencehistory!(plt[1, 3], NDofs, Results; add_h_powers = [order, order + 1], X_to_h = X -> X .^ (-1 / 2), legend = :lb, ylabels = ["|| u - u_h ||", "|| u - Iu ||", "|| ∇(u - u_h) ||", "|| ∇(u - Iu) ||"], limits = (1.0e-8, 1.0e-1)) + + ## print convergence history + print_convergencehistory(NDofs, Results; X_to_h = X -> X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| u - Iu ||", "|| ∇(u - u_h) ||", "|| ∇(u - Iu) ||"]) + + return Results, plt end generateplots = ExtendableFEM.default_generateplots(Example220_ReactionConvectionDiffusion, "example220.png") #hide function runtests() #hide - Results, plt = main(; nlevels = 2) #hide - @test Results[end, 1] ≈ 0.0001510021661291585 #hide + Results, plt = main(; nlevels = 2) #hide + return @test Results[end, 1] ≈ 0.0001510021661291585 #hide end #hide end # module diff --git a/examples/Example225_ObstacleProblem.jl b/examples/Example225_ObstacleProblem.jl index 769baa4..453ec83 100644 --- a/examples/Example225_ObstacleProblem.jl +++ b/examples/Example225_ObstacleProblem.jl @@ -37,43 +37,43 @@ using Test #hide ## define obstacle and penalty kernel const χ! = (result, x) -> (result[1] = (cos(4 * x[1] * π) * cos(4 * x[2] * π) - 1) / 20) function obstacle_penalty_kernel!(result, input, qpinfo) - χ!(result, qpinfo.x) # eval obstacle - result[1] = min(0, input[1] - result[1]) - return nothing + χ!(result, qpinfo.x) # eval obstacle + result[1] = min(0, input[1] - result[1]) + return nothing end -function main(; Plotter = nothing, ϵ = 1e-4, nrefs = 6, order = 1, parallel = false, npart = 8, kwargs...) +function main(; Plotter = nothing, ϵ = 1.0e-4, nrefs = 6, order = 1, parallel = false, npart = 8, kwargs...) - ## choose initial mesh - xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) - if parallel - xgrid = partition(xgrid, RecursiveMetisPartitioning(npart=npart)) - end + ## choose initial mesh + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) + if parallel + xgrid = partition(xgrid, RecursiveMetisPartitioning(npart = npart)) + end - ## problem description - PD = ProblemDescription() - u = Unknown("u"; name = "potential") - assign_unknown!(PD, u) - assign_operator!(PD, NonlinearOperator(obstacle_penalty_kernel!, [id(u)]; factor = 1 / ϵ, parallel = parallel, kwargs...)) - assign_operator!(PD, BilinearOperator([grad(u)]; store = true, parallel = parallel, kwargs...)) - assign_operator!(PD, LinearOperator([id(u)]; store = true, parallel = parallel, factor = -1, kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4, kwargs...)) + ## problem description + PD = ProblemDescription() + u = Unknown("u"; name = "potential") + assign_unknown!(PD, u) + assign_operator!(PD, NonlinearOperator(obstacle_penalty_kernel!, [id(u)]; factor = 1 / ϵ, parallel = parallel, kwargs...)) + assign_operator!(PD, BilinearOperator([grad(u)]; store = true, parallel = parallel, kwargs...)) + assign_operator!(PD, LinearOperator([id(u)]; store = true, parallel = parallel, factor = -1, kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4, kwargs...)) - ## create finite element space - FES = FESpace{H1Pk{1, 2, order}}(xgrid) + ## create finite element space + FES = FESpace{H1Pk{1, 2, order}}(xgrid) - ## solve - sol = solve(PD, FES; kwargs...) + ## solve + sol = solve(PD, FES; kwargs...) - ## plot - plt = plot([id(u), grad(u)], sol; Plotter = Plotter, ncols = 3) + ## plot + plt = plot([id(u), grad(u)], sol; Plotter = Plotter, ncols = 3) - return sol, plt + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example225_ObstacleProblem, "example225.png") #hide function runtests() #hide - sol, plt = main(; μ = 1.0, nrefs = 2, order = 2) #hide - @test maximum(sol.entries) ≈ 0.0033496680638875204 #hide + sol, plt = main(; μ = 1.0, nrefs = 2, order = 2) #hide + return @test maximum(sol.entries) ≈ 0.0033496680638875204 #hide end #hide end # module diff --git a/examples/Example226_Thermoforming.jl b/examples/Example226_Thermoforming.jl index 196553c..1324da5 100644 --- a/examples/Example226_Thermoforming.jl +++ b/examples/Example226_Thermoforming.jl @@ -18,35 +18,35 @@ using LinearAlgebra using Test #hide function w(r) - if 0.1 ≤ r ≤ 0.3 - return 5.0 * r - 0.5 - elseif 0.3 < r < 0.7 - return 1.0 - elseif 0.7 <= r <= 0.9 - return 4.5 - 5.0 * r - else - return 0.0 - end + if 0.1 ≤ r ≤ 0.3 + return 5.0 * r - 0.5 + elseif 0.3 < r < 0.7 + return 1.0 + elseif 0.7 <= r <= 0.9 + return 4.5 - 5.0 * r + else + return 0.0 + end end ## initial mould function Φ0(x) - return w(x[1]) * w(x[2]) + return w(x[1]) * w(x[2]) end -function g(r,κ,s) - if r <= 0.0 - return κ - elseif r <= 0.25 * s - return κ - 8.0 * κ * r^2 / (3.0 * s^2) - elseif r <= 0.75 * s - return 7.0 / 6.0 * κ - 4.0 / 3.0 * κ * r / s - elseif r <= s - return 8.0 / 3.0 * (s - r)^2 / s^2 - else - return 0.0 - end +function g(r, κ, s) + if r <= 0.0 + return κ + elseif r <= 0.25 * s + return κ - 8.0 * κ * r^2 / (3.0 * s^2) + elseif r <= 0.75 * s + return 7.0 / 6.0 * κ - 4.0 / 3.0 * κ * r / s + elseif r <= s + return 8.0 / 3.0 * (s - r)^2 / s^2 + else + return 0.0 + end end @@ -55,92 +55,93 @@ bump(x) = (0.0 <= x <= 1.0) ? exp(-0.25 / (x - x^2)) : 0.0 ## Bump in [0,1]^2 bumpInUnitSquare(x) = begin - r = sqrt((x[1] - 0.5)^2 + (x[2] - 0.5)^2) - return bump(0.5 + r) + r = sqrt((x[1] - 0.5)^2 + (x[2] - 0.5)^2) + return bump(0.5 + r) end ## nonlinear kernel -function nonlinear_kernel!(result, input, qpinfo ) - ## results and input contain 7 variables (u,∇u,T,∇T,y) - u = view(input, 1) - ∇u = view(input, 2:3) - T = view(input, 4) - ∇T = view(input, 5:6) - y = view(input, 7) - - α = qpinfo.params[1] - k = qpinfo.params[2] - f = qpinfo.params[3] - β = qpinfo.params[4] - κ = qpinfo.params[5] - s = qpinfo.params[6] - - result[1] = α * max(0, u[1] - y[1]) - f # pattern: 1 7 - result[2:3] = ∇u # pattern: 2 / 3 - result[4] = k*T[1] - g(y[1]-u[1],κ,s) # pattern: 1 4 7 - result[5:6] = ∇T # pattern: 5 / 6 - result[7] = y[1] - Φ0(qpinfo.x) - β * bumpInUnitSquare( qpinfo.x ) * T[1] # pattern: 4 7 +function nonlinear_kernel!(result, input, qpinfo) + ## results and input contain 7 variables (u,∇u,T,∇T,y) + u = view(input, 1) + ∇u = view(input, 2:3) + T = view(input, 4) + ∇T = view(input, 5:6) + y = view(input, 7) + + α = qpinfo.params[1] + k = qpinfo.params[2] + f = qpinfo.params[3] + β = qpinfo.params[4] + κ = qpinfo.params[5] + s = qpinfo.params[6] + + result[1] = α * max(0, u[1] - y[1]) - f # pattern: 1 7 + result[2:3] = ∇u # pattern: 2 / 3 + result[4] = k * T[1] - g(y[1] - u[1], κ, s) # pattern: 1 4 7 + result[5:6] = ∇T # pattern: 5 / 6 + return result[7] = y[1] - Φ0(qpinfo.x) - β * bumpInUnitSquare(qpinfo.x) * T[1] # pattern: 4 7 end ## custom sparsity pattern for the jacobians of the nonlinear_kernel (Symbolcs cannot handle conditional jumps) ## note: jacobians are defined row-wise rows = [1, 1, 2, 3, 4, 4, 4, 5, 6, 7, 7] -cols = [1 ,7, 2, 3, 1, 4, 7, 5, 6, 4, 7] +cols = [1, 7, 2, 3, 1, 4, 7, 5, 6, 4, 7] vals = ones(Bool, length(cols)) -sparsity_pattern = sparse(rows,cols,vals) +sparsity_pattern = sparse(rows, cols, vals) function main(; - κ = 10, - s = 1, - α = 1e8, - k = 1, - β = 5.25e-3, - f = 100, - N = 32, - order = 1, - Plotter = nothing, - kwargs...) - - ## choose mesh, - h = 1/(N+1) - xgrid = simplexgrid(0:h:1,0:h:1) - - ## problem description - PD = ProblemDescription() - u = Unknown("u"; name = "membrane position") - y = Unknown("y"; name = "mould") - T = Unknown("T"; name = "temperature") - assign_unknown!(PD, u) - assign_unknown!(PD, y) - assign_unknown!(PD, T) - assign_operator!(PD, NonlinearOperator(nonlinear_kernel!, [id(u), grad(u), id(T), grad(T), id(y)]; bonus_quadorder=2, params=[α,k,f,β,κ,s], sparse_jacobians_pattern=sparsity_pattern, kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4, kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(y; regions = 1:4, kwargs...)) - - ## create finite element space - FES = FESpace{H1Pk{1, 2, order}}(xgrid) - FESs = [FES, FES, FES] - sol = FEVector(FESs; tags = [u,y,T]) - - ## initial guess for Newton - interpolate!(sol[u], (result,qpinfo) -> ( result[1] = 0.9*Φ0(qpinfo.x) ) ) - interpolate!(sol[T], (result,qpinfo) -> ( result[1] = 0.2 ) ) - interpolate!(sol[y], (result,qpinfo) -> ( result[1] = 10.0 ) ) - - ## solve - sol = solve(PD, FESs; init = sol, maxiterations=420, target_residual=1e-8, kwargs...) - - ## plot - plt = plot([id(u),id(T),id(y)], sol; Plotter = Plotter) - - return sol, plt + κ = 10, + s = 1, + α = 1.0e8, + k = 1, + β = 5.25e-3, + f = 100, + N = 32, + order = 1, + Plotter = nothing, + kwargs... + ) + + ## choose mesh, + h = 1 / (N + 1) + xgrid = simplexgrid(0:h:1, 0:h:1) + + ## problem description + PD = ProblemDescription() + u = Unknown("u"; name = "membrane position") + y = Unknown("y"; name = "mould") + T = Unknown("T"; name = "temperature") + assign_unknown!(PD, u) + assign_unknown!(PD, y) + assign_unknown!(PD, T) + assign_operator!(PD, NonlinearOperator(nonlinear_kernel!, [id(u), grad(u), id(T), grad(T), id(y)]; bonus_quadorder = 2, params = [α, k, f, β, κ, s], sparse_jacobians_pattern = sparsity_pattern, kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4, kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(y; regions = 1:4, kwargs...)) + + ## create finite element space + FES = FESpace{H1Pk{1, 2, order}}(xgrid) + FESs = [FES, FES, FES] + sol = FEVector(FESs; tags = [u, y, T]) + + ## initial guess for Newton + interpolate!(sol[u], (result, qpinfo) -> (result[1] = 0.9 * Φ0(qpinfo.x))) + interpolate!(sol[T], (result, qpinfo) -> (result[1] = 0.2)) + interpolate!(sol[y], (result, qpinfo) -> (result[1] = 10.0)) + + ## solve + sol = solve(PD, FESs; init = sol, maxiterations = 420, target_residual = 1.0e-8, kwargs...) + + ## plot + plt = plot([id(u), id(T), id(y)], sol; Plotter = Plotter) + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example226_Thermoforming, "example226.png") #hide function runtests() #hide - sol1, ~ = Example226_Thermoforming.main(; sparse_jacobians=true, N = 20) #hide - sol2, ~ = Example226_Thermoforming.main(; sparse_jacobians=false, N = 20) #hide - @test norm(sol1.entries - sol2.entries) ≈ 0 #hide + sol1, ~ = Example226_Thermoforming.main(; sparse_jacobians = true, N = 20) #hide + sol2, ~ = Example226_Thermoforming.main(; sparse_jacobians = false, N = 20) #hide + return @test norm(sol1.entries - sol2.entries) ≈ 0 #hide end #hide end # module diff --git a/examples/Example230_NonlinearElasticity.jl b/examples/Example230_NonlinearElasticity.jl index bca149c..0d0d867 100644 --- a/examples/Example230_NonlinearElasticity.jl +++ b/examples/Example230_NonlinearElasticity.jl @@ -31,141 +31,142 @@ using Test #hide ## parameter-dependent nonlinear operator uses a callable struct to reduce allocations mutable struct nonlinear_operator{T} - λ::Vector{T} - μ::Vector{T} - ϵT::Vector{T} + λ::Vector{T} + μ::Vector{T} + ϵT::Vector{T} end function strain!(result, input) - result[1] = input[1] - result[2] = input[4] - result[3] = input[2] + input[3] - - ## add nonlinear part of the strain 1/2 * (grad(u)'*grad(u)) - result[1] += 1 // 2 * (input[1]^2 + input[3]^2) - result[2] += 1 // 2 * (input[2]^2 + input[4]^2) - result[3] += input[1] * input[2] + input[3] * input[4] - return nothing + result[1] = input[1] + result[2] = input[4] + result[3] = input[2] + input[3] + + ## add nonlinear part of the strain 1/2 * (grad(u)'*grad(u)) + result[1] += 1 // 2 * (input[1]^2 + input[3]^2) + result[2] += 1 // 2 * (input[2]^2 + input[4]^2) + result[3] += input[1] * input[2] + input[3] * input[4] + return nothing end ## kernel for nonlinear operator (op::nonlinear_operator)(result, input, qpinfo) = ( - ## input = grad(u) written as a vector - ## compute strain and subtract thermal strain (all in Voigt notation) - region = qpinfo.region; - strain!(result, input); - result[1] -= op.ϵT[region]; - result[2] -= op.ϵT[region]; - - ## multiply with isotropic stress tensor - ## (stored in input[5:7] using Voigt notation) - a = op.λ[region] * (result[1] + result[2]) + 2 * op.μ[region] * result[1]; - b = op.λ[region] * (result[1] + result[2]) + 2 * op.μ[region] * result[2]; - c = 2 * op.μ[region] * result[3]; - - ## write strain into result - result[1] = a; - result[2] = c; - result[3] = c; - result[4] = b; - return nothing + ## input = grad(u) written as a vector + ## compute strain and subtract thermal strain (all in Voigt notation) + region = qpinfo.region; + strain!(result, input); + result[1] -= op.ϵT[region]; + result[2] -= op.ϵT[region]; + + ## multiply with isotropic stress tensor + ## (stored in input[5:7] using Voigt notation) + a = op.λ[region] * (result[1] + result[2]) + 2 * op.μ[region] * result[1]; + b = op.λ[region] * (result[1] + result[2]) + 2 * op.μ[region] * result[2]; + c = 2 * op.μ[region] * result[3]; + + ## write strain into result + result[1] = a; + result[2] = c; + result[3] = c; + result[4] = b; + return nothing ) const op = nonlinear_operator([0.0, 0.0], [0.0, 0.0], [0.0, 0.0]) ## everything is wrapped in a main function function main(; - ν = [0.3, 0.3], # Poisson number for each region/material - E = [2.1, 1.1], # Elasticity modulus for each region/material - ΔT = [580, 580], # temperature for each region/material - α = [1.3e-5, 2.4e-4], # thermal expansion coefficients - scale = [20, 500], # scale of the bimetal, i.e. [thickness, width] - nrefs = 0, # refinement levels - order = 2, # finite element order - periodic = false, # use periodic boundary conditions? - Plotter = nothing, - kwargs...) - - ## compute Lame' coefficients μ and λ from ν and E - ## and thermal misfit strain and assign to operator operator - @. op.μ = E / (2 * (1 + ν )) - @. op.λ = E * ν / ((1 - 2 * ν) * (1 + ν)) - @. op.ϵT = ΔT * α - - ## generate bimetal mesh - xgrid = bimetal_strip2D(; scale = scale, n = 2 * (nrefs + 1)) - println(stdout, unicode_gridplot(xgrid)) - - ## create finite element space and solution vector - FES = FESpace{H1Pk{2, 2, order}}(xgrid) - - ## problem description - PD = ProblemDescription() - u = Unknown("u"; name = "displacement") - assign_unknown!(PD, u) - assign_operator!(PD, NonlinearOperator(op, [grad(u)]; kwargs...)) - if periodic - ## periodic boundary conditions - ## 1) couple dofs left (bregion 1) and right (bregion 3) in y-direction - dofsX, dofsY, factors = get_periodic_coupling_info(FES, xgrid, 1, 3, (f1, f2) -> abs(f1[2] - f2[2]) < 1e-14; factor_components = [0, 1]) - assign_operator!(PD, CombineDofs(u, u, dofsX, dofsY, factors; kwargs...)) - ## 2) find and fix point at [0, scale[1]] - xCoordinates = xgrid[Coordinates] - closest::Int = 0 - mindist::Float64 = 1e30 - for j ∈ 1:num_nodes(xgrid) - dist = xCoordinates[1, j]^2 + (xCoordinates[2, j] - scale[1])^2 - if dist < mindist - mindist = dist - closest = j - end - end - assign_operator!(PD, FixDofs(u; dofs = [closest], vals = [0])) - else - assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1], mask = [1, 0], kwargs...)) - end - - ## solve - sol = solve(PD, FES; kwargs...) - - ## displace mesh and plot - plt = GridVisualizer(; Plotter = Plotter, layout = (3, 1), clear = true, size = (1000, 1500)) - grad_nodevals = nodevalues(grad(u), sol) - strain_nodevals = zeros(Float64, 3, num_nodes(xgrid)) - for j in 1:num_nodes(xgrid) - strain!(view(strain_nodevals, :, j), view(grad_nodevals, :, j)) - end - scalarplot!(plt[1, 1], xgrid, view(strain_nodevals, 1, :), levels = 3, colorbarticks = 7, xlimits = [-scale[2] / 2 - 10, scale[2] / 2 + 10], ylimits = [-30, scale[1] + 20], title = "ϵ(u)_xx + displacement") - scalarplot!(plt[2, 1], xgrid, view(strain_nodevals, 2, :), levels = 1, colorbarticks = 7, xlimits = [-scale[2] / 2 - 10, scale[2] / 2 + 10], ylimits = [-30, scale[1] + 20], title = "ϵ(u)_yy + displacement") - vectorplot!(plt[1, 1], xgrid, eval_func_bary(PointEvaluator([id(u)], sol)), rasterpoints = 20, clear = false) - vectorplot!(plt[2, 1], xgrid, eval_func_bary(PointEvaluator([id(u)], sol)), rasterpoints = 20, clear = false) - displace_mesh!(xgrid, sol[u]) - gridplot!(plt[3, 1], xgrid, linewidth = 1, title = "displaced mesh") - println(stdout, unicode_gridplot(xgrid)) - - return strain_nodevals, plt + ν = [0.3, 0.3], # Poisson number for each region/material + E = [2.1, 1.1], # Elasticity modulus for each region/material + ΔT = [580, 580], # temperature for each region/material + α = [1.3e-5, 2.4e-4], # thermal expansion coefficients + scale = [20, 500], # scale of the bimetal, i.e. [thickness, width] + nrefs = 0, # refinement levels + order = 2, # finite element order + periodic = false, # use periodic boundary conditions? + Plotter = nothing, + kwargs... + ) + + ## compute Lame' coefficients μ and λ from ν and E + ## and thermal misfit strain and assign to operator operator + @. op.μ = E / (2 * (1 + ν)) + @. op.λ = E * ν / ((1 - 2 * ν) * (1 + ν)) + @. op.ϵT = ΔT * α + + ## generate bimetal mesh + xgrid = bimetal_strip2D(; scale = scale, n = 2 * (nrefs + 1)) + println(stdout, unicode_gridplot(xgrid)) + + ## create finite element space and solution vector + FES = FESpace{H1Pk{2, 2, order}}(xgrid) + + ## problem description + PD = ProblemDescription() + u = Unknown("u"; name = "displacement") + assign_unknown!(PD, u) + assign_operator!(PD, NonlinearOperator(op, [grad(u)]; kwargs...)) + if periodic + ## periodic boundary conditions + ## 1) couple dofs left (bregion 1) and right (bregion 3) in y-direction + dofsX, dofsY, factors = get_periodic_coupling_info(FES, xgrid, 1, 3, (f1, f2) -> abs(f1[2] - f2[2]) < 1.0e-14; factor_components = [0, 1]) + assign_operator!(PD, CombineDofs(u, u, dofsX, dofsY, factors; kwargs...)) + ## 2) find and fix point at [0, scale[1]] + xCoordinates = xgrid[Coordinates] + closest::Int = 0 + mindist::Float64 = 1.0e30 + for j in 1:num_nodes(xgrid) + dist = xCoordinates[1, j]^2 + (xCoordinates[2, j] - scale[1])^2 + if dist < mindist + mindist = dist + closest = j + end + end + assign_operator!(PD, FixDofs(u; dofs = [closest], vals = [0])) + else + assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1], mask = [1, 0], kwargs...)) + end + + ## solve + sol = solve(PD, FES; kwargs...) + + ## displace mesh and plot + plt = GridVisualizer(; Plotter = Plotter, layout = (3, 1), clear = true, size = (1000, 1500)) + grad_nodevals = nodevalues(grad(u), sol) + strain_nodevals = zeros(Float64, 3, num_nodes(xgrid)) + for j in 1:num_nodes(xgrid) + strain!(view(strain_nodevals, :, j), view(grad_nodevals, :, j)) + end + scalarplot!(plt[1, 1], xgrid, view(strain_nodevals, 1, :), levels = 3, colorbarticks = 7, xlimits = [-scale[2] / 2 - 10, scale[2] / 2 + 10], ylimits = [-30, scale[1] + 20], title = "ϵ(u)_xx + displacement") + scalarplot!(plt[2, 1], xgrid, view(strain_nodevals, 2, :), levels = 1, colorbarticks = 7, xlimits = [-scale[2] / 2 - 10, scale[2] / 2 + 10], ylimits = [-30, scale[1] + 20], title = "ϵ(u)_yy + displacement") + vectorplot!(plt[1, 1], xgrid, eval_func_bary(PointEvaluator([id(u)], sol)), rasterpoints = 20, clear = false) + vectorplot!(plt[2, 1], xgrid, eval_func_bary(PointEvaluator([id(u)], sol)), rasterpoints = 20, clear = false) + displace_mesh!(xgrid, sol[u]) + gridplot!(plt[3, 1], xgrid, linewidth = 1, title = "displaced mesh") + println(stdout, unicode_gridplot(xgrid)) + + return strain_nodevals, plt end ## grid function bimetal_strip2D(; scale = [1, 1], n = 2, anisotropy_factor::Int = Int(ceil(scale[2] / (2 * scale[1])))) - X = linspace(-scale[2] / 2, 0, (n + 1) * anisotropy_factor) - X2 = linspace(0, scale[2] / 2, (n + 1) * anisotropy_factor) - append!(X, X2[2:end]) - Y = linspace(0, scale[1], 2 * n + 1) - xgrid = simplexgrid(X, Y) - cellmask!(xgrid, [-scale[2] / 2, 0.0], [scale[2] / 2, scale[1] / 2], 1) - cellmask!(xgrid, [-scale[2] / 2, scale[1] / 2], [scale[2] / 2, scale[1]], 2) - bfacemask!(xgrid, [-scale[2] / 2, 0.0], [-scale[2] / 2, scale[1] / 2], 1) - bfacemask!(xgrid, [-scale[2] / 2, scale[1] / 2], [-scale[2] / 2, scale[1]], 1) - bfacemask!(xgrid, [-scale[2] / 2, 0.0], [scale[2] / 2, 0.0], 2) - bfacemask!(xgrid, [-scale[2] / 2, scale[1]], [scale[2] / 2, scale[1]], 2) - bfacemask!(xgrid, [scale[2] / 2, 0.0], [scale[2] / 2, scale[1]], 3) - return xgrid + X = linspace(-scale[2] / 2, 0, (n + 1) * anisotropy_factor) + X2 = linspace(0, scale[2] / 2, (n + 1) * anisotropy_factor) + append!(X, X2[2:end]) + Y = linspace(0, scale[1], 2 * n + 1) + xgrid = simplexgrid(X, Y) + cellmask!(xgrid, [-scale[2] / 2, 0.0], [scale[2] / 2, scale[1] / 2], 1) + cellmask!(xgrid, [-scale[2] / 2, scale[1] / 2], [scale[2] / 2, scale[1]], 2) + bfacemask!(xgrid, [-scale[2] / 2, 0.0], [-scale[2] / 2, scale[1] / 2], 1) + bfacemask!(xgrid, [-scale[2] / 2, scale[1] / 2], [-scale[2] / 2, scale[1]], 1) + bfacemask!(xgrid, [-scale[2] / 2, 0.0], [scale[2] / 2, 0.0], 2) + bfacemask!(xgrid, [-scale[2] / 2, scale[1]], [scale[2] / 2, scale[1]], 2) + bfacemask!(xgrid, [scale[2] / 2, 0.0], [scale[2] / 2, scale[1]], 3) + return xgrid end generateplots = ExtendableFEM.default_generateplots(Example230_NonlinearElasticity, "example230.png") #hide function runtests() #hide - strain, plt = main(;) #hide - @test maximum(strain) ≈ 0.17289633483008537 #hide + strain, plt = main() #hide + return @test maximum(strain) ≈ 0.17289633483008537 #hide end #hide end diff --git a/examples/Example235_StokesIteratedPenalty.jl b/examples/Example235_StokesIteratedPenalty.jl index 7f40785..e377607 100644 --- a/examples/Example235_StokesIteratedPenalty.jl +++ b/examples/Example235_StokesIteratedPenalty.jl @@ -49,73 +49,73 @@ using Test #hide ## data for Hagen-Poiseuille flow function p!(result, qpinfo) - x = qpinfo.x - μ = qpinfo.params[1] - result[1] = μ * (-2 * x[1] + 1.0) + x = qpinfo.x + μ = qpinfo.params[1] + return result[1] = μ * (-2 * x[1] + 1.0) end function u!(result, qpinfo) - x = qpinfo.x - result[1] = x[2] * (1.0 - x[2]) - result[2] = 0.0 + x = qpinfo.x + result[1] = x[2] * (1.0 - x[2]) + return result[2] = 0.0 end ## kernel for div projection function div_projection!(result, input, qpinfo) - result[1] = input[1] - qpinfo.params[1] * input[2] + return result[1] = input[1] - qpinfo.params[1] * input[2] end ## everything is wrapped in a main function -function main(; Plotter = nothing, λ = 1e4, μ = 1.0, nrefs = 5, kwargs...) - - ## initial grid - xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) - - ## Bernardi--Raugel element with reconstruction operator - FETypes = (H1BR{2}, L2P0{1}) - PenaltyDivergence = Reconstruct{HDIVRT0{2}, Divergence} - - ## generate two problems - ## one for velocity, one for pressure - u = Unknown("u"; name = "velocity") - p = Unknown("p"; name = "pressure") - PDu = ProblemDescription("Stokes IPM - velocity update") - assign_unknown!(PDu, u) - assign_operator!(PDu, BilinearOperator([grad(u)]; factor = μ, store = true, kwargs...)) - assign_operator!(PDu, BilinearOperator([apply(u, PenaltyDivergence)]; store = true, factor = λ, kwargs...)) - assign_operator!(PDu, LinearOperator([div(u)], [id(p)]; factor = 1, kwargs...)) - assign_operator!(PDu, InterpolateBoundaryData(u, u!; regions = 1:4, params = [μ], bonus_quadorder = 4, kwargs...)) - - PDp = ProblemDescription("Stokes IPM - pressure update") - assign_unknown!(PDp, p) - assign_operator!(PDp, BilinearOperator([id(p)]; store = true, kwargs...)) - assign_operator!(PDp, LinearOperator(div_projection!, [id(p)], [id(p), div(u)]; params = [λ], factor = 1, kwargs...)) - - ## show and solve problem - FES = [FESpace{FETypes[1]}(xgrid), FESpace{FETypes[2]}(xgrid)] - sol = FEVector([FES[1], FES[2]]; tags = [u, p]) - SC1 = SolverConfiguration(PDu; init = sol, maxiterations = 1, target_residual = 1e-8, constant_matrix = true, kwargs...) - SC2 = SolverConfiguration(PDp; init = sol, maxiterations = 1, target_residual = 1e-8, constant_matrix = true, kwargs...) - sol, nits = iterate_until_stationarity([SC1, SC2]; init = sol, kwargs...) - @info "converged after $nits iterations" - - ## plot - plt = plot([id(u), id(p)], sol; Plotter = Plotter) - - return sol, plt +function main(; Plotter = nothing, λ = 1.0e4, μ = 1.0, nrefs = 5, kwargs...) + + ## initial grid + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) + + ## Bernardi--Raugel element with reconstruction operator + FETypes = (H1BR{2}, L2P0{1}) + PenaltyDivergence = Reconstruct{HDIVRT0{2}, Divergence} + + ## generate two problems + ## one for velocity, one for pressure + u = Unknown("u"; name = "velocity") + p = Unknown("p"; name = "pressure") + PDu = ProblemDescription("Stokes IPM - velocity update") + assign_unknown!(PDu, u) + assign_operator!(PDu, BilinearOperator([grad(u)]; factor = μ, store = true, kwargs...)) + assign_operator!(PDu, BilinearOperator([apply(u, PenaltyDivergence)]; store = true, factor = λ, kwargs...)) + assign_operator!(PDu, LinearOperator([div(u)], [id(p)]; factor = 1, kwargs...)) + assign_operator!(PDu, InterpolateBoundaryData(u, u!; regions = 1:4, params = [μ], bonus_quadorder = 4, kwargs...)) + + PDp = ProblemDescription("Stokes IPM - pressure update") + assign_unknown!(PDp, p) + assign_operator!(PDp, BilinearOperator([id(p)]; store = true, kwargs...)) + assign_operator!(PDp, LinearOperator(div_projection!, [id(p)], [id(p), div(u)]; params = [λ], factor = 1, kwargs...)) + + ## show and solve problem + FES = [FESpace{FETypes[1]}(xgrid), FESpace{FETypes[2]}(xgrid)] + sol = FEVector([FES[1], FES[2]]; tags = [u, p]) + SC1 = SolverConfiguration(PDu; init = sol, maxiterations = 1, target_residual = 1.0e-8, constant_matrix = true, kwargs...) + SC2 = SolverConfiguration(PDp; init = sol, maxiterations = 1, target_residual = 1.0e-8, constant_matrix = true, kwargs...) + sol, nits = iterate_until_stationarity([SC1, SC2]; init = sol, kwargs...) + @info "converged after $nits iterations" + + ## plot + plt = plot([id(u), id(p)], sol; Plotter = Plotter) + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example235_StokesIteratedPenalty, "example235.png") #hide function exact_error!(result, u, qpinfo) #hide - u!(result, qpinfo) #hide - p!(view(result, 3), qpinfo) #hide - result .= (result .- u).^ 2 #hide + u!(result, qpinfo) #hide + p!(view(result, 3), qpinfo) #hide + return result .= (result .- u) .^ 2 #hide end #hide function runtests(; μ = 1.0) #hide - sol, plt = main(; μ = μ) #hide - ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(1), id(2)]; quadorder = 4, params = [μ]) #hide - error = evaluate(ErrorIntegratorExact, sol) #hide - error_u = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) #hide - error_p = sqrt(sum(view(error, 3, :))) #hide - @test error_u ≈ 3.990987355891888e-5 #hide - @test error_p ≈ 0.010437891104305222 #hide + sol, plt = main(; μ = μ) #hide + ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(1), id(2)]; quadorder = 4, params = [μ]) #hide + error = evaluate(ErrorIntegratorExact, sol) #hide + error_u = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) #hide + error_p = sqrt(sum(view(error, 3, :))) #hide + @test error_u ≈ 3.990987355891888e-5 #hide + return @test error_p ≈ 0.010437891104305222 #hide end #hide end diff --git a/examples/Example240_SVRTEnrichment.jl b/examples/Example240_SVRTEnrichment.jl index fb33387..2317d0d 100644 --- a/examples/Example240_SVRTEnrichment.jl +++ b/examples/Example240_SVRTEnrichment.jl @@ -51,410 +51,414 @@ using Test #hide ## exact data for problem generated by Symbolics function prepare_data(; μ = 1) - @variables x y + @variables x y - ## stream function ξ - ξ = -sin(2 * pi * x) * cos(2 * pi * y) + ## stream function ξ + ξ = -sin(2 * pi * x) * cos(2 * pi * y) - ## velocity u = curl ξ - ∇ξ = Symbolics.gradient(ξ, [x, y]) - u = [-∇ξ[2], ∇ξ[1]] + ## velocity u = curl ξ + ∇ξ = Symbolics.gradient(ξ, [x, y]) + u = [-∇ξ[2], ∇ξ[1]] - ## pressure - p = (cos(4 * pi * x) - cos(4 * pi * y)) / 4 + ## pressure + p = (cos(4 * pi * x) - cos(4 * pi * y)) / 4 - ## gradient of velocity - ∇u = Symbolics.jacobian(u, [x, y]) - ∇u_reshaped = [∇u[1, 1], ∇u[1, 2], ∇u[2, 1], ∇u[2, 2]] + ## gradient of velocity + ∇u = Symbolics.jacobian(u, [x, y]) + ∇u_reshaped = [∇u[1, 1], ∇u[1, 2], ∇u[2, 1], ∇u[2, 2]] - ## Laplacian - Δu = [ - (Symbolics.gradient(∇u[1, 1], [x])+Symbolics.gradient(∇u[1, 2], [y]))[1], - (Symbolics.gradient(∇u[2, 1], [x])+Symbolics.gradient(∇u[2, 2], [y]))[1], - ] + ## Laplacian + Δu = [ + (Symbolics.gradient(∇u[1, 1], [x]) + Symbolics.gradient(∇u[1, 2], [y]))[1], + (Symbolics.gradient(∇u[2, 1], [x]) + Symbolics.gradient(∇u[2, 2], [y]))[1], + ] - ## right-hand side - f = -μ * Δu + Symbolics.gradient(p, [x, y]) + ## right-hand side + f = -μ * Δu + Symbolics.gradient(p, [x, y]) - ## build functions - p_eval = build_function(p, x, y, expression = Val{false}) - u_eval = build_function(u, x, y, expression = Val{false}) - ∇u_eval = build_function(∇u_reshaped, x, y, expression = Val{false}) - f_eval = build_function(f, x, y, expression = Val{false}) + ## build functions + p_eval = build_function(p, x, y, expression = Val{false}) + u_eval = build_function(u, x, y, expression = Val{false}) + ∇u_eval = build_function(∇u_reshaped, x, y, expression = Val{false}) + f_eval = build_function(f, x, y, expression = Val{false}) - return f_eval[2], u_eval[2], ∇u_eval[2], p_eval + return f_eval[2], u_eval[2], ∇u_eval[2], p_eval end ## grid generator function function get_grid2D(nref; uniform = false, barycentric = false) - if uniform || barycentric - gen_ref = 0 - else - gen_ref = nref - end - grid = simplexgrid(Triangulate; - points = [0 0; 0 1; 1 1; 1 0]', - bfaces = [1 2; 2 3; 3 4; 4 1]', - bfaceregions = [1, 2, 3, 4], - regionpoints = [0.5 0.5;]', - regionnumbers = [1], - regionvolumes = [4.0^(-gen_ref - 1)]) - if uniform - grid = uniform_refine(grid, nref) - end - if barycentric - grid = barycentric_refine(grid) - end - return grid + if uniform || barycentric + gen_ref = 0 + else + gen_ref = nref + end + grid = simplexgrid( + Triangulate; + points = [0 0; 0 1; 1 1; 1 0]', + bfaces = [1 2; 2 3; 3 4; 4 1]', + bfaceregions = [1, 2, 3, 4], + regionpoints = [0.5 0.5;]', + regionnumbers = [1], + regionvolumes = [4.0^(-gen_ref - 1)] + ) + if uniform + grid = uniform_refine(grid, nref) + end + if barycentric + grid = barycentric_refine(grid) + end + return grid end ## kernel for Stokes operator function kernel_stokes_standard!(result, u_ops, qpinfo) - ∇u, p = view(u_ops, 1:4), view(u_ops, 5) - μ = qpinfo.params[1] - 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]) - return nothing + ∇u, p = view(u_ops, 1:4), view(u_ops, 5) + μ = qpinfo.params[1] + 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]) + return nothing end function main(; nrefs = 5, μ = 1, α = 1, order = 2, Plotter = nothing, enrich = true, reduce = true, time = 0.5, bonus_quadorder = 5, kwargs...) - ## prepare problem data - f_eval, u_eval, ∇u_eval, p_eval = prepare_data(; μ = μ) - rhs!(result, qpinfo) = (f_eval(result, qpinfo.x[1], qpinfo.x[2])) - exact_p!(result, qpinfo) = (result[1] = p_eval(qpinfo.x[1], qpinfo.x[2])) - exact_u!(result, qpinfo) = (u_eval(result, qpinfo.x[1], qpinfo.x[2])) - exact_∇u!(result, qpinfo) = (∇u_eval(result, qpinfo.x[1], qpinfo.x[2])) - - ## prepare unknowns - u = Unknown("u"; name = "velocity", dim = 2) - pfull = Unknown("p"; name = "pressure (full)", dim = 1) - pE = Unknown("p⟂"; name = "pressure (enriched)", dim = 1) - p0 = Unknown("p0"; name = "pressure (reduced)", dim = 1) # only used if enrich && reduced - uR = Unknown("uR"; name = "velocity enrichment", dim = 2) # only used if enrich == true - - ## prepare plots - plt = GridVisualizer(; Plotter = Plotter, layout = (2, 2), clear = true, size = (1000, 1000)) - - ## prepare error calculations - function exact_error!(result, u, qpinfo) - exact_u!(view(result, 1:2), qpinfo) - exact_∇u!(view(result, 3:6), qpinfo) - result .-= u - result .= result .^ 2 - end - function exact_error_p!(result, p, qpinfo) - exact_p!(view(result, 1), qpinfo) - result .-= p - result .= result .^ 2 - end - ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(u), grad(u)]; quadorder = 2 * (order + 1), kwargs...) - ErrorIntegratorPressure = ItemIntegrator(exact_error_p!, [order == 1 ? id(p0) : id(pfull)]; quadorder = 2 * (order + 1), kwargs...) - L2NormIntegratorE = L2NormIntegrator([id(uR)]; quadorder = 2 * order) - function kernel_div!(result, u, qpinfo) - result .= sum(u) .^ 2 - end - DivNormIntegrator = ItemIntegrator(kernel_div!, enrich ? [div(u), div(uR)] : [div(u)]; quadorder = 2 * order) - NDofs = zeros(Int, nrefs) - Results = zeros(Float64, nrefs, 5) - - for lvl ∈ 1:nrefs - - ## grid - xgrid = get_grid2D(lvl) - - ## define and assign unknowns - PD = ProblemDescription("Stokes problem") - assign_unknown!(PD, u) - p = reduce * enrich ? p0 : pfull - assign_unknown!(PD, p) - - ################ - ### FESPACES ### - ################ - if order == 1 - FES_enrich = FESpace{HDIVRT0{2}}(xgrid) - else - FES_enrich = FESpace{HDIVRTkENRICH{2, order - 1, reduce}}(xgrid) - end - FES = Dict(u => FESpace{H1Pk{2, 2, order}}(xgrid), - pfull => FESpace{order == 1 ? L2P0{1} : H1Pk{1, 2, order - 1}}(xgrid; broken = true), - p0 => FESpace{L2P0{1}}(xgrid; broken = true), - uR => enrich ? FES_enrich : nothing) - - ###################### - ### STANDARD TERMS ### - ###################### - assign_operator!(PD, LinearOperator(rhs!, [id(u)]; bonus_quadorder = bonus_quadorder, kwargs...)) - assign_operator!(PD, BilinearOperator(kernel_stokes_standard!, [grad(u), id(p)]; params = [μ], kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; regions = 1:4, bonus_quadorder = bonus_quadorder)) - assign_operator!(PD, FixDofs(p; dofs = [1], vals = [0])) - - ################## - ### ENRICHMENT ### - ################## - if enrich - if reduce - if order == 1 - @info "... preparing condensation of RT0 dofs" - AR = FEMatrix(FES_enrich) - BR = FEMatrix(FES[p], FES_enrich) - bR = FEVector(FES_enrich) - assemble!(AR, BilinearOperator([div(1)]; lump = true, factor = α*μ, kwargs...)) - for bface in xgrid[BFaceFaces] - AR.entries[bface, bface] = 1e60 - end - assemble!(BR, BilinearOperator([id(1)], [div(1)]; factor = -1, kwargs...)) - assemble!(bR, LinearOperator(rhs!, [id(1)]; bonus_quadorder = 5, kwargs...); time = time) - ## invert AR (diagonal matrix) - AR.entries.cscmatrix.nzval .= 1 ./ AR.entries.cscmatrix.nzval - C = -BR.entries.cscmatrix * AR.entries.cscmatrix * BR.entries.cscmatrix' - c = -BR.entries.cscmatrix * AR.entries.cscmatrix * bR.entries - assign_operator!(PD, BilinearOperator(C, [p], [p]; kwargs...)) - assign_operator!(PD, LinearOperator(c, [p]; kwargs...)) - else - @info "... preparing removal of enrichment dofs" - BR = FEMatrix(FES[p], FES_enrich) - A1R = FEMatrix(FES_enrich, FES[u]) - bR = FEVector(FES_enrich) - assemble!(BR, BilinearOperator([id(1)], [div(1)]; factor = -1, kwargs...)) - assemble!(bR, LinearOperator(rhs!, [id(1)]; bonus_quadorder = 5, kwargs...); time = time) - assemble!(A1R, BilinearOperator([id(1)], [Δ(1)]; factor = -μ, kwargs...)) - F, DD_RR = div_projector(FES[u], FES_enrich) - C = F.entries.cscmatrix * A1R.entries.cscmatrix - assign_operator!(PD, BilinearOperator(C, [u], [u]; factor = 1, transposed_copy = -1, kwargs...)) - assign_operator!(PD, LinearOperator(F.entries.cscmatrix * bR.entries, [u]; kwargs...)) - end - else - assign_unknown!(PD, uR) - assign_operator!(PD, LinearOperator(rhs!, [id(uR)]; bonus_quadorder = 5, kwargs...)) - assign_operator!(PD, BilinearOperator([id(p)], [div(uR)]; transposed_copy = 1, factor = -1, kwargs...)) - if order == 1 - assign_operator!(PD, BilinearOperator([div(uR)]; lump = true, factor = μ, kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(uR; regions = 1:4)) - else - assign_operator!(PD, BilinearOperator([Δ(u)], [id(uR)]; factor = μ, transposed_copy = -1, kwargs...)) - end - end - end - - ############# - ### SOLVE ### - ############# - sol = solve(PD, FES; time = time, kwargs...) - NDofs[lvl] = length(sol.entries) - - ## move integral mean of pressure - pintegrate = ItemIntegrator([id(p)]) - pmean = sum(evaluate(pintegrate, sol)) / sum(xgrid[CellVolumes]) - view(sol[p]) .-= pmean - - ###################### - ### POSTPROCESSING ### - ###################### - if enrich && reduce - append!(sol, FES_enrich; tag = uR) - if order == 1 - ## compute enrichment part of velocity - view(sol[uR]) .= AR.entries.cscmatrix * (bR.entries - BR.entries.cscmatrix' * view(sol[p])) - else - ## compute enrichment part of velocity - view(sol[uR]) .= F.entries.cscmatrix' * view(sol[u]) - end - - ## compute higher order pressure dofs - if reduce && order > 1 - ## add blocks for higher order pressures to sol vector - VR = FES_enrich - append!(sol, VR; tag = pE) - append!(sol, FES[pfull]; tag = pfull) - sol_pE = view(sol[pE]) - sol_pfull = view(sol[pfull]) - sol_p0 = view(sol[p0]) - - res = FEVector(VR) - addblock_matmul!(res[1], A1R[1, 1], sol[u]) - celldofs_VR::VariableTargetAdjacency{Int32} = VR[CellDofs] - ndofs_VR = max_num_targets_per_source(celldofs_VR) - Ap = zeros(Float64, ndofs_VR, ndofs_VR) - bp = zeros(Float64, ndofs_VR) - xp = zeros(Float64, ndofs_VR) - for cell ∈ 1:num_cells(xgrid) - ## solve local pressure reconstruction - ## (p_h, div VR) = - (f,VR) + a_h(u_h,VR) - for dof_j ∈ 1:ndofs_VR - dof = celldofs_VR[dof_j, cell] - bp[dof_j] = -bR.entries[dof] + res.entries[dof] - for dof_k ∈ 1:ndofs_VR - dof2 = celldofs_VR[dof_k, cell] - Ap[dof_j, dof_k] = DD_RR.entries[dof, dof2] - end - end - - ## solve for coefficients of div(RT1bubbles) - xp = Ap \ bp - - ## save in block id_pk - for dof_j ∈ 1:ndofs_VR - dof = celldofs_VR[dof_j, cell] - sol_pE[dof] = xp[dof_j] - end - end - - ## interpolate into Pk basis (= same pressure basis as in full scheme) - PF = FES[pfull] - append!(sol, PF; tag = pfull) - celldofs_PF::SerialVariableTargetAdjacency{Int32} = PF[CellDofs] - ndofs_PF::Int = max_num_targets_per_source(celldofs_PF) - - ## compute local mass matrix of full pressure space - MAMA = FEMatrix(PF) - assemble!(MAMA, BilinearOperator([id(1)])) - MAMAE::ExtendableSparseMatrix{Float64, Int64} = MAMA.entries - - ## full div-pressure matrix - PFxVR = FEMatrix(PF, VR) - assemble!(PFxVR, BilinearOperator([id(1)], [div(1)])) - PFxVRE::ExtendableSparseMatrix{Float64, Int64} = PFxVR.entries - bp = zeros(Float64, ndofs_PF) - xp = zeros(Float64, ndofs_PF) - locMAMA = zeros(Float64, ndofs_PF, ndofs_PF) - for cell ∈ 1:num_cells(xgrid) - ## solve local pressure reconstruction - fill!(bp, 0) - for dof_k ∈ 1:ndofs_PF - dof2 = celldofs_PF[dof_k, cell] - for dof_j ∈ 1:ndofs_VR - dof = celldofs_VR[dof_j, cell] - bp[dof_k] += PFxVRE[dof2, dof] * sol_pE[dof] - end - for dof_j ∈ 1:ndofs_PF - dof = celldofs_PF[dof_j, cell] - locMAMA[dof_k, dof_j] = MAMAE[dof2, dof] - end - end - - ## solve for coefficients of div(RT1bubbles) - xp = locMAMA \ bp - for dof_j ∈ 1:ndofs_PF - dof = celldofs_PF[dof_j, cell] - sol_pfull[dof] = sol_p0[cell] + xp[dof_j] - end - end - elseif reduce && order == 1 - pfull = p0 - end - end - - ######################## - ### ERROR EVALUATION ### - ######################## - error = evaluate(ErrorIntegratorExact, sol) - L2errorU = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) - H1errorU = sqrt(sum(view(error, 3, :)) + sum(view(error, 4, :)) + sum(view(error, 5, :)) + sum(view(error, 6, :))) - @info "L2error(u) = $L2errorU" - @info "L2error(∇u) = $H1errorU" - evaluate!(error, ErrorIntegratorPressure, sol) - L2errorP = sqrt(sum(view(error, 1, :))) - @info "L2error(p) = $L2errorP" - Results[lvl, 4] = L2errorP - if enrich - fill!(error, 0) - evaluate!(error, L2NormIntegratorE, sol) - L2normUR = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) - @info "L2norm(uR) = $L2normUR" - end - fill!(error, 0) - evaluate!(error, DivNormIntegrator, sol) - L2normDiv = sqrt(sum(view(error, 1, :))) - @info "L2norm(div(u+uR)) = $L2normDiv" - - Results[lvl, 1] = L2errorU - Results[lvl, 2] = H1errorU - Results[lvl, 3] = L2normUR - Results[lvl, 5] = L2normDiv - - ############# - ### PLOTS ### - ############# - scalarplot!(plt[1, 1], id(u), sol; abs = true) - scalarplot!(plt[1, 2], id(pfull), sol) - if order == 1 && enrich - scalarplot!(plt[2, 2], id(uR), sol) - end - end - plot_convergencehistory!( - plt[2, 1], - NDofs, - Results[:,1:4]; - add_h_powers = [order, order + 1], - X_to_h = X -> 8 * X .^ (-1 / 2), - legend = :best, - ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "|| uR ||", "|| p - p_h ||", "|| div(u + uR) ||"], - ) - - print_convergencehistory(NDofs, Results; X_to_h = X -> X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "|| uR ||", "|| p - p_h ||", "|| div(u + uR) ||"], xlabel = "ndof") - - return Results, plt + ## prepare problem data + f_eval, u_eval, ∇u_eval, p_eval = prepare_data(; μ = μ) + rhs!(result, qpinfo) = (f_eval(result, qpinfo.x[1], qpinfo.x[2])) + exact_p!(result, qpinfo) = (result[1] = p_eval(qpinfo.x[1], qpinfo.x[2])) + exact_u!(result, qpinfo) = (u_eval(result, qpinfo.x[1], qpinfo.x[2])) + exact_∇u!(result, qpinfo) = (∇u_eval(result, qpinfo.x[1], qpinfo.x[2])) + + ## prepare unknowns + u = Unknown("u"; name = "velocity", dim = 2) + pfull = Unknown("p"; name = "pressure (full)", dim = 1) + pE = Unknown("p⟂"; name = "pressure (enriched)", dim = 1) + p0 = Unknown("p0"; name = "pressure (reduced)", dim = 1) # only used if enrich && reduced + uR = Unknown("uR"; name = "velocity enrichment", dim = 2) # only used if enrich == true + + ## prepare plots + plt = GridVisualizer(; Plotter = Plotter, layout = (2, 2), clear = true, size = (1000, 1000)) + + ## prepare error calculations + function exact_error!(result, u, qpinfo) + exact_u!(view(result, 1:2), qpinfo) + exact_∇u!(view(result, 3:6), qpinfo) + result .-= u + return result .= result .^ 2 + end + function exact_error_p!(result, p, qpinfo) + exact_p!(view(result, 1), qpinfo) + result .-= p + return result .= result .^ 2 + end + ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(u), grad(u)]; quadorder = 2 * (order + 1), kwargs...) + ErrorIntegratorPressure = ItemIntegrator(exact_error_p!, [order == 1 ? id(p0) : id(pfull)]; quadorder = 2 * (order + 1), kwargs...) + L2NormIntegratorE = L2NormIntegrator([id(uR)]; quadorder = 2 * order) + function kernel_div!(result, u, qpinfo) + return result .= sum(u) .^ 2 + end + DivNormIntegrator = ItemIntegrator(kernel_div!, enrich ? [div(u), div(uR)] : [div(u)]; quadorder = 2 * order) + NDofs = zeros(Int, nrefs) + Results = zeros(Float64, nrefs, 5) + + for lvl in 1:nrefs + + ## grid + xgrid = get_grid2D(lvl) + + ## define and assign unknowns + PD = ProblemDescription("Stokes problem") + assign_unknown!(PD, u) + p = reduce * enrich ? p0 : pfull + assign_unknown!(PD, p) + + ################ + ### FESPACES ### + ################ + if order == 1 + FES_enrich = FESpace{HDIVRT0{2}}(xgrid) + else + FES_enrich = FESpace{HDIVRTkENRICH{2, order - 1, reduce}}(xgrid) + end + FES = Dict( + u => FESpace{H1Pk{2, 2, order}}(xgrid), + pfull => FESpace{order == 1 ? L2P0{1} : H1Pk{1, 2, order - 1}}(xgrid; broken = true), + p0 => FESpace{L2P0{1}}(xgrid; broken = true), + uR => enrich ? FES_enrich : nothing + ) + + ###################### + ### STANDARD TERMS ### + ###################### + assign_operator!(PD, LinearOperator(rhs!, [id(u)]; bonus_quadorder = bonus_quadorder, kwargs...)) + assign_operator!(PD, BilinearOperator(kernel_stokes_standard!, [grad(u), id(p)]; params = [μ], kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; regions = 1:4, bonus_quadorder = bonus_quadorder)) + assign_operator!(PD, FixDofs(p; dofs = [1], vals = [0])) + + ################## + ### ENRICHMENT ### + ################## + if enrich + if reduce + if order == 1 + @info "... preparing condensation of RT0 dofs" + AR = FEMatrix(FES_enrich) + BR = FEMatrix(FES[p], FES_enrich) + bR = FEVector(FES_enrich) + assemble!(AR, BilinearOperator([div(1)]; lump = true, factor = α * μ, kwargs...)) + for bface in xgrid[BFaceFaces] + AR.entries[bface, bface] = 1.0e60 + end + assemble!(BR, BilinearOperator([id(1)], [div(1)]; factor = -1, kwargs...)) + assemble!(bR, LinearOperator(rhs!, [id(1)]; bonus_quadorder = 5, kwargs...); time = time) + ## invert AR (diagonal matrix) + AR.entries.cscmatrix.nzval .= 1 ./ AR.entries.cscmatrix.nzval + C = -BR.entries.cscmatrix * AR.entries.cscmatrix * BR.entries.cscmatrix' + c = -BR.entries.cscmatrix * AR.entries.cscmatrix * bR.entries + assign_operator!(PD, BilinearOperator(C, [p], [p]; kwargs...)) + assign_operator!(PD, LinearOperator(c, [p]; kwargs...)) + else + @info "... preparing removal of enrichment dofs" + BR = FEMatrix(FES[p], FES_enrich) + A1R = FEMatrix(FES_enrich, FES[u]) + bR = FEVector(FES_enrich) + assemble!(BR, BilinearOperator([id(1)], [div(1)]; factor = -1, kwargs...)) + assemble!(bR, LinearOperator(rhs!, [id(1)]; bonus_quadorder = 5, kwargs...); time = time) + assemble!(A1R, BilinearOperator([id(1)], [Δ(1)]; factor = -μ, kwargs...)) + F, DD_RR = div_projector(FES[u], FES_enrich) + C = F.entries.cscmatrix * A1R.entries.cscmatrix + assign_operator!(PD, BilinearOperator(C, [u], [u]; factor = 1, transposed_copy = -1, kwargs...)) + assign_operator!(PD, LinearOperator(F.entries.cscmatrix * bR.entries, [u]; kwargs...)) + end + else + assign_unknown!(PD, uR) + assign_operator!(PD, LinearOperator(rhs!, [id(uR)]; bonus_quadorder = 5, kwargs...)) + assign_operator!(PD, BilinearOperator([id(p)], [div(uR)]; transposed_copy = 1, factor = -1, kwargs...)) + if order == 1 + assign_operator!(PD, BilinearOperator([div(uR)]; lump = true, factor = μ, kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(uR; regions = 1:4)) + else + assign_operator!(PD, BilinearOperator([Δ(u)], [id(uR)]; factor = μ, transposed_copy = -1, kwargs...)) + end + end + end + + ############# + ### SOLVE ### + ############# + sol = solve(PD, FES; time = time, kwargs...) + NDofs[lvl] = length(sol.entries) + + ## move integral mean of pressure + pintegrate = ItemIntegrator([id(p)]) + pmean = sum(evaluate(pintegrate, sol)) / sum(xgrid[CellVolumes]) + view(sol[p]) .-= pmean + + ###################### + ### POSTPROCESSING ### + ###################### + if enrich && reduce + append!(sol, FES_enrich; tag = uR) + if order == 1 + ## compute enrichment part of velocity + view(sol[uR]) .= AR.entries.cscmatrix * (bR.entries - BR.entries.cscmatrix' * view(sol[p])) + else + ## compute enrichment part of velocity + view(sol[uR]) .= F.entries.cscmatrix' * view(sol[u]) + end + + ## compute higher order pressure dofs + if reduce && order > 1 + ## add blocks for higher order pressures to sol vector + VR = FES_enrich + append!(sol, VR; tag = pE) + append!(sol, FES[pfull]; tag = pfull) + sol_pE = view(sol[pE]) + sol_pfull = view(sol[pfull]) + sol_p0 = view(sol[p0]) + + res = FEVector(VR) + addblock_matmul!(res[1], A1R[1, 1], sol[u]) + celldofs_VR::VariableTargetAdjacency{Int32} = VR[CellDofs] + ndofs_VR = max_num_targets_per_source(celldofs_VR) + Ap = zeros(Float64, ndofs_VR, ndofs_VR) + bp = zeros(Float64, ndofs_VR) + xp = zeros(Float64, ndofs_VR) + for cell in 1:num_cells(xgrid) + ## solve local pressure reconstruction + ## (p_h, div VR) = - (f,VR) + a_h(u_h,VR) + for dof_j in 1:ndofs_VR + dof = celldofs_VR[dof_j, cell] + bp[dof_j] = -bR.entries[dof] + res.entries[dof] + for dof_k in 1:ndofs_VR + dof2 = celldofs_VR[dof_k, cell] + Ap[dof_j, dof_k] = DD_RR.entries[dof, dof2] + end + end + + ## solve for coefficients of div(RT1bubbles) + xp = Ap \ bp + + ## save in block id_pk + for dof_j in 1:ndofs_VR + dof = celldofs_VR[dof_j, cell] + sol_pE[dof] = xp[dof_j] + end + end + + ## interpolate into Pk basis (= same pressure basis as in full scheme) + PF = FES[pfull] + append!(sol, PF; tag = pfull) + celldofs_PF::SerialVariableTargetAdjacency{Int32} = PF[CellDofs] + ndofs_PF::Int = max_num_targets_per_source(celldofs_PF) + + ## compute local mass matrix of full pressure space + MAMA = FEMatrix(PF) + assemble!(MAMA, BilinearOperator([id(1)])) + MAMAE::ExtendableSparseMatrix{Float64, Int64} = MAMA.entries + + ## full div-pressure matrix + PFxVR = FEMatrix(PF, VR) + assemble!(PFxVR, BilinearOperator([id(1)], [div(1)])) + PFxVRE::ExtendableSparseMatrix{Float64, Int64} = PFxVR.entries + bp = zeros(Float64, ndofs_PF) + xp = zeros(Float64, ndofs_PF) + locMAMA = zeros(Float64, ndofs_PF, ndofs_PF) + for cell in 1:num_cells(xgrid) + ## solve local pressure reconstruction + fill!(bp, 0) + for dof_k in 1:ndofs_PF + dof2 = celldofs_PF[dof_k, cell] + for dof_j in 1:ndofs_VR + dof = celldofs_VR[dof_j, cell] + bp[dof_k] += PFxVRE[dof2, dof] * sol_pE[dof] + end + for dof_j in 1:ndofs_PF + dof = celldofs_PF[dof_j, cell] + locMAMA[dof_k, dof_j] = MAMAE[dof2, dof] + end + end + + ## solve for coefficients of div(RT1bubbles) + xp = locMAMA \ bp + for dof_j in 1:ndofs_PF + dof = celldofs_PF[dof_j, cell] + sol_pfull[dof] = sol_p0[cell] + xp[dof_j] + end + end + elseif reduce && order == 1 + pfull = p0 + end + end + + ######################## + ### ERROR EVALUATION ### + ######################## + error = evaluate(ErrorIntegratorExact, sol) + L2errorU = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) + H1errorU = sqrt(sum(view(error, 3, :)) + sum(view(error, 4, :)) + sum(view(error, 5, :)) + sum(view(error, 6, :))) + @info "L2error(u) = $L2errorU" + @info "L2error(∇u) = $H1errorU" + evaluate!(error, ErrorIntegratorPressure, sol) + L2errorP = sqrt(sum(view(error, 1, :))) + @info "L2error(p) = $L2errorP" + Results[lvl, 4] = L2errorP + if enrich + fill!(error, 0) + evaluate!(error, L2NormIntegratorE, sol) + L2normUR = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) + @info "L2norm(uR) = $L2normUR" + end + fill!(error, 0) + evaluate!(error, DivNormIntegrator, sol) + L2normDiv = sqrt(sum(view(error, 1, :))) + @info "L2norm(div(u+uR)) = $L2normDiv" + + Results[lvl, 1] = L2errorU + Results[lvl, 2] = H1errorU + Results[lvl, 3] = L2normUR + Results[lvl, 5] = L2normDiv + + ############# + ### PLOTS ### + ############# + scalarplot!(plt[1, 1], id(u), sol; abs = true) + scalarplot!(plt[1, 2], id(pfull), sol) + if order == 1 && enrich + scalarplot!(plt[2, 2], id(uR), sol) + end + end + plot_convergencehistory!( + plt[2, 1], + NDofs, + Results[:, 1:4]; + add_h_powers = [order, order + 1], + X_to_h = X -> 8 * X .^ (-1 / 2), + legend = :best, + ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "|| uR ||", "|| p - p_h ||", "|| div(u + uR) ||"], + ) + + print_convergencehistory(NDofs, Results; X_to_h = X -> X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "|| uR ||", "|| p - p_h ||", "|| div(u + uR) ||"], xlabel = "ndof") + + return Results, plt end function div_projector(V1, VR) - ## setup interpolation matrix - celldofs_V1 = V1[CellDofs] - celldofs_VR = VR[CellDofs] - ndofs_V1 = max_num_targets_per_source(celldofs_V1) - ndofs_VR = max_num_targets_per_source(celldofs_VR) - - DD_RR = FEMatrix(VR) - assemble!(DD_RR, BilinearOperator([div(1)])) - DD_RRE = DD_RR.entries - DD_1R = FEMatrix(V1, VR) - assemble!(DD_1R, BilinearOperator([div(1)])) - DD_1RE = DD_1R.entries - Ap = zeros(Float64, ndofs_VR, ndofs_VR) - bp = zeros(Float64, ndofs_VR) - xp = zeros(Float64, ndofs_VR) - ncells = num_sources(celldofs_V1) - F = FEMatrix(V1, VR) - FE = F.entries - for cell ∈ 1:ncells - - ## solve local pressure reconstruction for RTk part - for dof_j ∈ 1:ndofs_VR - dof = celldofs_VR[dof_j, cell] - for dof_k ∈ 1:ndofs_VR - dof2 = celldofs_VR[dof_k, cell] - Ap[dof_j, dof_k] = DD_RRE[dof, dof2] - end - end - - for dof_j ∈ 1:ndofs_V1 - dof = celldofs_V1[dof_j, cell] - for dof_k ∈ 1:ndofs_VR - dof2 = celldofs_VR[dof_k, cell] - bp[dof_k] = -DD_1RE[dof, dof2] - end - - xp = Ap \ bp - - for dof_k ∈ 1:ndofs_VR - dof2 = celldofs_VR[dof_k, cell] - FE[dof, dof2] = xp[dof_k] - end - end - end - flush!(FE) - return F, DD_RR + ## setup interpolation matrix + celldofs_V1 = V1[CellDofs] + celldofs_VR = VR[CellDofs] + ndofs_V1 = max_num_targets_per_source(celldofs_V1) + ndofs_VR = max_num_targets_per_source(celldofs_VR) + + DD_RR = FEMatrix(VR) + assemble!(DD_RR, BilinearOperator([div(1)])) + DD_RRE = DD_RR.entries + DD_1R = FEMatrix(V1, VR) + assemble!(DD_1R, BilinearOperator([div(1)])) + DD_1RE = DD_1R.entries + Ap = zeros(Float64, ndofs_VR, ndofs_VR) + bp = zeros(Float64, ndofs_VR) + xp = zeros(Float64, ndofs_VR) + ncells = num_sources(celldofs_V1) + F = FEMatrix(V1, VR) + FE = F.entries + for cell in 1:ncells + + ## solve local pressure reconstruction for RTk part + for dof_j in 1:ndofs_VR + dof = celldofs_VR[dof_j, cell] + for dof_k in 1:ndofs_VR + dof2 = celldofs_VR[dof_k, cell] + Ap[dof_j, dof_k] = DD_RRE[dof, dof2] + end + end + + for dof_j in 1:ndofs_V1 + dof = celldofs_V1[dof_j, cell] + for dof_k in 1:ndofs_VR + dof2 = celldofs_VR[dof_k, cell] + bp[dof_k] = -DD_1RE[dof, dof2] + end + + xp = Ap \ bp + + for dof_k in 1:ndofs_VR + dof2 = celldofs_VR[dof_k, cell] + FE[dof, dof2] = xp[dof_k] + end + end + end + flush!(FE) + return F, DD_RR end generateplots = ExtendableFEM.default_generateplots(Example240_SVRTEnrichment, "example240.png") #hide -function runtests(;) #hide - Results, plt = main(; nrefs = 2) #hide - @test Results[end,1] ≈ 0.09600693353585522 #hide - @test Results[end,5] < 1e-9 #hide +function runtests() #hide + Results, plt = main(; nrefs = 2) #hide + @test Results[end, 1] ≈ 0.09600693353585522 #hide + return @test Results[end, 5] < 1.0e-9 #hide end #hide end # module diff --git a/examples/Example245_NSEFlowAroundCylinder.jl b/examples/Example245_NSEFlowAroundCylinder.jl index 5e90a24..89e4748 100644 --- a/examples/Example245_NSEFlowAroundCylinder.jl +++ b/examples/Example245_NSEFlowAroundCylinder.jl @@ -38,25 +38,25 @@ const umax = 0.3 const umean = 2 // 3 * umax const L, W, H = 0.1, 2.2, 0.41 function inflow!(result, qpinfo) - x = qpinfo.x - result[1] = 4 * umax * x[2] * (H - x[2]) / (H * H) - result[2] = 0.0 + x = qpinfo.x + result[1] = 4 * umax * x[2] * (H - x[2]) / (H * H) + return result[2] = 0.0 end ## hand constructed identity matrix for kernel to avoid allocations const II = [1 0; 0 1] -## Example of a kernel using tensor_view() function to allow for an operator +## Example of a kernel using tensor_view() function to allow for an operator ## based style of writing the semilinear form. -## For comparison we also provide the kernel_nonlinear_flat! function below +## For comparison we also provide the kernel_nonlinear_flat! function below ## that uses a component-wise style of writing the semilinear form. -## +## ## the scalar product ``(\nabla v, \mu \nabla u - pI)`` will be evaluated -## so in general `a = b` corresponds to ``(a,b)``. +## so in general `a = b` corresponds to ``(a,b)``. -## Note that the order of vector entries between the kernel and the call to +## Note that the order of vector entries between the kernel and the call to ## NonlinearOperator have to match. function kernel_nonlinear!(result, u_ops, qpinfo) ## Shape values of vectorial u are starting at index 1 @@ -68,14 +68,14 @@ function kernel_nonlinear!(result, u_ops, qpinfo) ∇u = tensor_view(u_ops, 3, TDMatrix(2)) ∇v = tensor_view(result, 3, TDMatrix(2)) ## values of scalar p are starting at index 7 - ## view as 0-tensor (single value) + ## view as 0-tensor (single value) p = tensor_view(u_ops, 7, TDScalar()) q = tensor_view(result, 7, TDScalar()) ## get viscosity at current quadrature point μ = qpinfo.params[1] ## Note that all operators should be element-wise to avoid allocations ## `(v,u⋅∇u) = (v,∇u^T⋅u)` - tmul!(v,∇u,u) + tmul!(v, ∇u, u) ## `(∇v,μ∇u-pI)` ∇v .= μ .* ∇u .- p[1] .* II ## `(q,-∇⋅u)` @@ -85,154 +85,154 @@ end ## everything is wrapped in a main function -function main(; Plotter = nothing, μ = 1e-3, maxvol = 1e-3, reconstruct = true, parallel = false, npart = 8, kwargs...) - - ## load grid (see function below) - xgrid = make_grid(W, H; n = Int(ceil(sqrt(1 / maxvol))), maxvol = maxvol) - if parallel - xgrid = partition(xgrid, RecursiveMetisPartitioning(npart=npart)) - end - - ## problem description - PD = ProblemDescription() - u = Unknown("u"; name = "velocity") - p = Unknown("p"; name = "pressure") - id_u = reconstruct ? apply(u, Reconstruct{HDIVRT1{2}, Identity}) : id(u) - - assign_unknown!(PD, u) - assign_unknown!(PD, p) - assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id_u, grad(u), id(p)]; params = [μ], parallel = parallel, kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, inflow!; regions = 4)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 3, 5])) - - ## P2-bubble + reconstruction operator - FETypes = [H1P2B{2, 2}, H1P1{1}] - - ## generate FESpaces and Solution vector - FES = [FESpace{FETypes[1]}(xgrid), FESpace{FETypes[2]}(xgrid; broken = true)] - - ## solve - sol = solve(PD, FES; maxiterations = 50, target_residual = 1e-10) - - ## postprocess : compute drag/lift (see function below) - draglift = get_draglift(sol, μ; parallel = parallel, kwargs...) - pdiff = get_pressure_difference(sol) - println("[drag, lift] = $draglift") - println("p difference = $pdiff") - - ## plots via GridVisualize - plt = GridVisualizer(; Plotter = Plotter, layout = (4, 1), clear = true, size = (800, 1200)) - gridplot!(plt[1, 1], xgrid, cellcoloring = :partitions, linewidth = 1) - gridplot!(plt[2, 1], xgrid, cellcoloring = :partitions, linewidth = 1, xlimits = [0, 0.3], ylimits = [0.1, 0.3]) - scalarplot!(plt[3, 1], xgrid, nodevalues(sol[u]; abs = true)[1, :]) - vectorplot!(plt[3, 1], xgrid, eval_func_bary(PointEvaluator([id(u)], sol)), rasterpoints = 20, clear = false) - scalarplot!(plt[4, 1], xgrid, view(nodevalues(sol[p]), 1, :), levels = 11, title = "p_h") - - return [draglift[1], draglift[2], pdiff[1]], plt +function main(; Plotter = nothing, μ = 1.0e-3, maxvol = 1.0e-3, reconstruct = true, parallel = false, npart = 8, kwargs...) + + ## load grid (see function below) + xgrid = make_grid(W, H; n = Int(ceil(sqrt(1 / maxvol))), maxvol = maxvol) + if parallel + xgrid = partition(xgrid, RecursiveMetisPartitioning(npart = npart)) + end + + ## problem description + PD = ProblemDescription() + u = Unknown("u"; name = "velocity") + p = Unknown("p"; name = "pressure") + id_u = reconstruct ? apply(u, Reconstruct{HDIVRT1{2}, Identity}) : id(u) + + assign_unknown!(PD, u) + assign_unknown!(PD, p) + assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id_u, grad(u), id(p)]; params = [μ], parallel = parallel, kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, inflow!; regions = 4)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 3, 5])) + + ## P2-bubble + reconstruction operator + FETypes = [H1P2B{2, 2}, H1P1{1}] + + ## generate FESpaces and Solution vector + FES = [FESpace{FETypes[1]}(xgrid), FESpace{FETypes[2]}(xgrid; broken = true)] + + ## solve + sol = solve(PD, FES; maxiterations = 50, target_residual = 1.0e-10) + + ## postprocess : compute drag/lift (see function below) + draglift = get_draglift(sol, μ; parallel = parallel, kwargs...) + pdiff = get_pressure_difference(sol) + println("[drag, lift] = $draglift") + println("p difference = $pdiff") + + ## plots via GridVisualize + plt = GridVisualizer(; Plotter = Plotter, layout = (4, 1), clear = true, size = (800, 1200)) + gridplot!(plt[1, 1], xgrid, cellcoloring = :partitions, linewidth = 1) + gridplot!(plt[2, 1], xgrid, cellcoloring = :partitions, linewidth = 1, xlimits = [0, 0.3], ylimits = [0.1, 0.3]) + scalarplot!(plt[3, 1], xgrid, nodevalues(sol[u]; abs = true)[1, :]) + vectorplot!(plt[3, 1], xgrid, eval_func_bary(PointEvaluator([id(u)], sol)), rasterpoints = 20, clear = false) + scalarplot!(plt[4, 1], xgrid, view(nodevalues(sol[p]), 1, :), levels = 11, title = "p_h") + + return [draglift[1], draglift[2], pdiff[1]], plt end function get_pressure_difference(sol::FEVector) - xgrid = sol[2].FES.xgrid - PE = PointEvaluator([id(2)], sol) - p_left = zeros(Float64, 1) - x1 = [0.15, 0.2] - p_right = zeros(Float64, 1) - x2 = [0.25, 0.2] - evaluate!(p_left, PE, x1) - evaluate!(p_right, PE, x2) - @show p_left, p_right - return p_left - p_right + xgrid = sol[2].FES.xgrid + PE = PointEvaluator([id(2)], sol) + p_left = zeros(Float64, 1) + x1 = [0.15, 0.2] + p_right = zeros(Float64, 1) + x2 = [0.25, 0.2] + evaluate!(p_left, PE, x1) + evaluate!(p_right, PE, x2) + @show p_left, p_right + return p_left - p_right end function get_draglift(sol::FEVector, μ; parallel = false, kwargs...) - ## this function is interpolated for drag/lift test function creation - function DL_testfunction(component) - function closure(result, qpinfo) - x = qpinfo.x - fill!(result, 0) - if sqrt((x[1] - 0.2)^2 + (x[2] - 0.2)^2) <= 0.06 - result[component] = 1 - end - end - end - - ## drag lift calculation by testfunctions - function draglift_kernel(result, input, qpinfo) - ## input = [ u, grad(u), p , v , grad(v)] - ## [1:2, 3:6, 7 ,8:9, 10:13 ] - result[1] = μ * (input[3] * input[10] + input[4] * input[11] + input[5] * input[12] + input[6] * input[13]) - result[1] += (input[1] * input[3] + input[2] * input[4]) * input[8] - result[1] += (input[1] * input[5] + input[2] * input[6]) * input[9] - result[1] -= input[7] * (input[10] + input[13]) - result[1] *= -(2 / (umean^2 * L)) - return nothing - end - DLIntegrator = ItemIntegrator(draglift_kernel, [id(1), grad(1), id(2), id(3), grad(3)]; quadorder = 4, parallel = parallel, kwargs...) - - ## test for drag - TestFunction = FEVector(sol[1].FES; name = "drag/lift testfunction") - interpolate!(TestFunction[1], ON_BFACES, DL_testfunction(1)) - drag = sum(evaluate(DLIntegrator, [sol[1], sol[2], TestFunction[1]])) - - ## test for lift - interpolate!(TestFunction[1], ON_BFACES, DL_testfunction(2)) - lift = sum(evaluate(DLIntegrator, [sol[1], sol[2], TestFunction[1]])) - - return [drag, lift] + ## this function is interpolated for drag/lift test function creation + function DL_testfunction(component) + return function closure(result, qpinfo) + x = qpinfo.x + fill!(result, 0) + return if sqrt((x[1] - 0.2)^2 + (x[2] - 0.2)^2) <= 0.06 + result[component] = 1 + end + end + end + + ## drag lift calculation by testfunctions + function draglift_kernel(result, input, qpinfo) + ## input = [ u, grad(u), p , v , grad(v)] + ## [1:2, 3:6, 7 ,8:9, 10:13 ] + result[1] = μ * (input[3] * input[10] + input[4] * input[11] + input[5] * input[12] + input[6] * input[13]) + result[1] += (input[1] * input[3] + input[2] * input[4]) * input[8] + result[1] += (input[1] * input[5] + input[2] * input[6]) * input[9] + result[1] -= input[7] * (input[10] + input[13]) + result[1] *= -(2 / (umean^2 * L)) + return nothing + end + DLIntegrator = ItemIntegrator(draglift_kernel, [id(1), grad(1), id(2), id(3), grad(3)]; quadorder = 4, parallel = parallel, kwargs...) + + ## test for drag + TestFunction = FEVector(sol[1].FES; name = "drag/lift testfunction") + interpolate!(TestFunction[1], ON_BFACES, DL_testfunction(1)) + drag = sum(evaluate(DLIntegrator, [sol[1], sol[2], TestFunction[1]])) + + ## test for lift + interpolate!(TestFunction[1], ON_BFACES, DL_testfunction(2)) + lift = sum(evaluate(DLIntegrator, [sol[1], sol[2], TestFunction[1]])) + + return [drag, lift] end ## grid generator script using SimplexGridBuilder/Triangulate function make_grid(W, H; n = 20, maxvol = 0.1) - builder = SimplexGridBuilder(Generator = Triangulate) - function circlehole!(builder, center, radius; n = 20) - points = [point!(builder, center[1] + radius * sin(t), center[2] + radius * cos(t)) for t in range(0, 2π, length = n)] - for i ∈ 1:n-1 - facet!(builder, points[i], points[i+1]) - end - facet!(builder, points[end], points[1]) - holepoint!(builder, center) - end - p1 = point!(builder, 0, 0) - p2 = point!(builder, W, 0) - p3 = point!(builder, W, H) - p4 = point!(builder, 0, H) - - ## heuristic refinement around cylinder - refine_radius = 0.25 - maxrefinefactor = 1 // 20 - function unsuitable(x1, y1, x2, y2, x3, y3, area) - if area > maxvol * min(max(4 * maxrefinefactor, abs((x1 + x2 + x3) / 3 - 0.2)), 1 / maxrefinefactor) - return true - end - dist = sqrt(((x1 + x2 + x3) / 3 - 0.2)^2 + ((y1 + y2 + y3) / 3 - 0.2)^2) - 0.05 - myarea = dist < refine_radius ? maxvol * max(maxrefinefactor, 1 - (refine_radius - dist) / refine_radius) : maxvol - if area > myarea - return true - else - return false - end - end - - facetregion!(builder, 1) - facet!(builder, p1, p2) - facetregion!(builder, 2) - facet!(builder, p2, p3) - facetregion!(builder, 3) - facet!(builder, p3, p4) - facetregion!(builder, 4) - facet!(builder, p4, p1) - facetregion!(builder, 5) - circlehole!(builder, (0.2, 0.2), 0.05, n = n) - - simplexgrid(builder, maxvolume = 16 * maxvol, unsuitable = unsuitable) + builder = SimplexGridBuilder(Generator = Triangulate) + function circlehole!(builder, center, radius; n = 20) + points = [point!(builder, center[1] + radius * sin(t), center[2] + radius * cos(t)) for t in range(0, 2π, length = n)] + for i in 1:(n - 1) + facet!(builder, points[i], points[i + 1]) + end + facet!(builder, points[end], points[1]) + return holepoint!(builder, center) + end + p1 = point!(builder, 0, 0) + p2 = point!(builder, W, 0) + p3 = point!(builder, W, H) + p4 = point!(builder, 0, H) + + ## heuristic refinement around cylinder + refine_radius = 0.25 + maxrefinefactor = 1 // 20 + function unsuitable(x1, y1, x2, y2, x3, y3, area) + if area > maxvol * min(max(4 * maxrefinefactor, abs((x1 + x2 + x3) / 3 - 0.2)), 1 / maxrefinefactor) + return true + end + dist = sqrt(((x1 + x2 + x3) / 3 - 0.2)^2 + ((y1 + y2 + y3) / 3 - 0.2)^2) - 0.05 + myarea = dist < refine_radius ? maxvol * max(maxrefinefactor, 1 - (refine_radius - dist) / refine_radius) : maxvol + if area > myarea + return true + else + return false + end + end + + facetregion!(builder, 1) + facet!(builder, p1, p2) + facetregion!(builder, 2) + facet!(builder, p2, p3) + facetregion!(builder, 3) + facet!(builder, p3, p4) + facetregion!(builder, 4) + facet!(builder, p4, p1) + facetregion!(builder, 5) + circlehole!(builder, (0.2, 0.2), 0.05, n = n) + + return simplexgrid(builder, maxvolume = 16 * maxvol, unsuitable = unsuitable) end generateplots = ExtendableFEM.default_generateplots(Example245_NSEFlowAroundCylinder, "example245.png") #hide function runtests() #hide - dragliftpdiff, plt = main(; maxvol = 5e-3) #hide - @test dragliftpdiff[1] ≈ 5.484046680249255 #hide - @test dragliftpdiff[2] ≈ 0.006508486071976145 #hide - @test dragliftpdiff[3] ≈ 0.1203441600631075 #hide + dragliftpdiff, plt = main(; maxvol = 5.0e-3) #hide + @test dragliftpdiff[1] ≈ 5.484046680249255 #hide + @test dragliftpdiff[2] ≈ 0.006508486071976145 #hide + return @test dragliftpdiff[3] ≈ 0.1203441600631075 #hide end #hide end diff --git a/examples/Example250_NSELidDrivenCavity.jl b/examples/Example250_NSELidDrivenCavity.jl index 30f18f7..319f5b5 100644 --- a/examples/Example250_NSELidDrivenCavity.jl +++ b/examples/Example250_NSELidDrivenCavity.jl @@ -27,96 +27,96 @@ using LinearAlgebra using Test #hide function kernel_nonlinear!(result, u_ops, qpinfo) - u, ∇u, p = view(u_ops, 1:2), view(u_ops, 3:6), view(u_ops, 7) - μ = qpinfo.params[1] - result[1] = dot(u, view(∇u, 1:2)) - result[2] = dot(u, view(∇u, 3:4)) - result[3] = μ * ∇u[1] - p[1] - result[4] = μ * ∇u[2] - result[5] = μ * ∇u[3] - result[6] = μ * ∇u[4] - p[1] - result[7] = -(∇u[1] + ∇u[4]) - return nothing + u, ∇u, p = view(u_ops, 1:2), view(u_ops, 3:6), view(u_ops, 7) + μ = qpinfo.params[1] + result[1] = dot(u, view(∇u, 1:2)) + result[2] = dot(u, view(∇u, 3:4)) + result[3] = μ * ∇u[1] - p[1] + result[4] = μ * ∇u[2] + result[5] = μ * ∇u[3] + result[6] = μ * ∇u[4] - p[1] + result[7] = -(∇u[1] + ∇u[4]) + return nothing end function boundarydata!(result, qpinfo) - result[1] = 1 - result[2] = 0 + result[1] = 1 + return result[2] = 0 end function initialgrid_cone() - xgrid = ExtendableGrid{Float64, Int32}() - xgrid[Coordinates] = Array{Float64, 2}([-1 0; 0 -2; 1 0]') - xgrid[CellNodes] = Array{Int32, 2}([1 2 3]') - xgrid[CellGeometries] = VectorOfConstants{ElementGeometries, Int}(Triangle2D, 1) - xgrid[CellRegions] = ones(Int32, 1) - xgrid[BFaceRegions] = Array{Int32, 1}([1, 2, 3]) - xgrid[BFaceNodes] = Array{Int32, 2}([1 2; 2 3; 3 1]') - xgrid[BFaceGeometries] = VectorOfConstants{ElementGeometries, Int}(Edge1D, 3) - xgrid[CoordinateSystem] = Cartesian2D - return xgrid + xgrid = ExtendableGrid{Float64, Int32}() + xgrid[Coordinates] = Array{Float64, 2}([-1 0; 0 -2; 1 0]') + xgrid[CellNodes] = Array{Int32, 2}([1 2 3]') + xgrid[CellGeometries] = VectorOfConstants{ElementGeometries, Int}(Triangle2D, 1) + xgrid[CellRegions] = ones(Int32, 1) + xgrid[BFaceRegions] = Array{Int32, 1}([1, 2, 3]) + xgrid[BFaceNodes] = Array{Int32, 2}([1 2; 2 3; 3 1]') + xgrid[BFaceGeometries] = VectorOfConstants{ElementGeometries, Int}(Edge1D, 3) + xgrid[CoordinateSystem] = Cartesian2D + return xgrid end function main(; μ_final = 0.0005, order = 2, nrefs = 5, Plotter = nothing, kwargs...) - ## prepare parameter field - extra_params = Array{Float64, 1}([max(μ_final, 0.05)]) - - ## problem description - PD = ProblemDescription() - u = Unknown("u"; name = "velocity") - p = Unknown("p"; name = "pressure") - - assign_unknown!(PD, u) - assign_unknown!(PD, p) - assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id(u), grad(u), id(p)]; params = extra_params, kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, boundarydata!; regions = 3)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 2])) - assign_operator!(PD, FixDofs(p; dofs = [1])) - - - ## grid - xgrid = uniform_refine(initialgrid_cone(), nrefs) - - ## prepare FESpace - FES = [FESpace{H1Pk{2,2,order}}(xgrid), FESpace{H1Pk{1,2,order-1}}(xgrid)] - - ## prepare plots - plt = GridVisualizer(; Plotter = Plotter, layout = (1, 2), clear = true, size = (1600, 800)) - - ## solve by μ embedding - step = 0 - sol = nothing - SC = nothing - PE = PointEvaluator([id(1)]) - while (true) - step += 1 - @info "Step $step : solving for μ=$(extra_params[1])" - sol, SC = ExtendableFEM.solve(PD, FES, SC; return_config = true, target_residual = 1e-10, maxiterations = 20, kwargs...) - if step == 1 - initialize!(PE, sol) - end - scalarplot!(plt[1, 1], xgrid, nodevalues(sol[1]; abs = true)[1, :]; title = "velocity (μ = $(extra_params[1]))", Plotter = Plotter) - vectorplot!(plt[1, 1], xgrid, eval_func_bary(PE), rasterpoints = 20, clear = false) - streamplot!(plt[1, 2], xgrid, eval_func_bary(PE), rasterpoints = 50, density = 2, title = "streamlines") - - if extra_params[1] <= μ_final - break - else - extra_params[1] = max(μ_final, extra_params[1] / 2) - end - end - - scalarplot!(plt[1, 1], xgrid, nodevalues(sol[1]; abs = true)[1, :]; title = "velocity (μ = $(extra_params[1]))", Plotter = Plotter) - vectorplot!(plt[1, 1], xgrid, eval_func_bary(PE), rasterpoints = 20, clear = false) - streamplot!(plt[1, 2], xgrid, eval_func_bary(PE), rasterpoints = 50, density = 2, title = "streamlines") - - return sol, plt + ## prepare parameter field + extra_params = Array{Float64, 1}([max(μ_final, 0.05)]) + + ## problem description + PD = ProblemDescription() + u = Unknown("u"; name = "velocity") + p = Unknown("p"; name = "pressure") + + assign_unknown!(PD, u) + assign_unknown!(PD, p) + assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id(u), grad(u), id(p)]; params = extra_params, kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, boundarydata!; regions = 3)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 2])) + assign_operator!(PD, FixDofs(p; dofs = [1])) + + + ## grid + xgrid = uniform_refine(initialgrid_cone(), nrefs) + + ## prepare FESpace + FES = [FESpace{H1Pk{2, 2, order}}(xgrid), FESpace{H1Pk{1, 2, order - 1}}(xgrid)] + + ## prepare plots + plt = GridVisualizer(; Plotter = Plotter, layout = (1, 2), clear = true, size = (1600, 800)) + + ## solve by μ embedding + step = 0 + sol = nothing + SC = nothing + PE = PointEvaluator([id(1)]) + while (true) + step += 1 + @info "Step $step : solving for μ=$(extra_params[1])" + sol, SC = ExtendableFEM.solve(PD, FES, SC; return_config = true, target_residual = 1.0e-10, maxiterations = 20, kwargs...) + if step == 1 + initialize!(PE, sol) + end + scalarplot!(plt[1, 1], xgrid, nodevalues(sol[1]; abs = true)[1, :]; title = "velocity (μ = $(extra_params[1]))", Plotter = Plotter) + vectorplot!(plt[1, 1], xgrid, eval_func_bary(PE), rasterpoints = 20, clear = false) + streamplot!(plt[1, 2], xgrid, eval_func_bary(PE), rasterpoints = 50, density = 2, title = "streamlines") + + if extra_params[1] <= μ_final + break + else + extra_params[1] = max(μ_final, extra_params[1] / 2) + end + end + + scalarplot!(plt[1, 1], xgrid, nodevalues(sol[1]; abs = true)[1, :]; title = "velocity (μ = $(extra_params[1]))", Plotter = Plotter) + vectorplot!(plt[1, 1], xgrid, eval_func_bary(PE), rasterpoints = 20, clear = false) + streamplot!(plt[1, 2], xgrid, eval_func_bary(PE), rasterpoints = 50, density = 2, title = "streamlines") + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example250_NSELidDrivenCavity, "example250.png") #hide function runtests() #hide - sol, plt = main(; nrefs = 3, μ_final = 0.005) #hide - @test sum(view(sol[1])) ≈ 9.501630403050289 #hide + sol, plt = main(; nrefs = 3, μ_final = 0.005) #hide + return @test sum(view(sol[1])) ≈ 9.501630403050289 #hide end #hide end # module diff --git a/examples/Example252_NSEPlanarLatticeFlow.jl b/examples/Example252_NSEPlanarLatticeFlow.jl index 1cab306..d4cd565 100644 --- a/examples/Example252_NSEPlanarLatticeFlow.jl +++ b/examples/Example252_NSEPlanarLatticeFlow.jl @@ -39,92 +39,92 @@ using Test #hide ## exact velocity (and Dirichlet data) function u!(result, qpinfo) - x = qpinfo.x - result[1] = sin(2 * pi * x[1]) * sin(2 * pi * x[2]) - result[2] = cos(2 * pi * x[1]) * cos(2 * pi * x[2]) + x = qpinfo.x + result[1] = sin(2 * pi * x[1]) * sin(2 * pi * x[2]) + return result[2] = cos(2 * pi * x[1]) * cos(2 * pi * x[2]) end ## right-hand side f := -μ Δu + (u⋅∇)u + ∇p function f!(μ) - α = [0, 0] - function closure(result, qpinfo) - x = qpinfo.x - result[1] = (μ * 8 * pi^2 + α[1]) * sin(2 * pi * x[1]) * sin(2 * pi * x[2]) - result[2] = (μ * 8 * pi^2 + α[2]) * cos(2 * pi * x[1]) * cos(2 * pi * x[2]) - end + α = [0, 0] + return function closure(result, qpinfo) + x = qpinfo.x + result[1] = (μ * 8 * pi^2 + α[1]) * sin(2 * pi * x[1]) * sin(2 * pi * x[2]) + return result[2] = (μ * 8 * pi^2 + α[2]) * cos(2 * pi * x[1]) * cos(2 * pi * x[2]) + end end ## exact pressure function p!(result, qpinfo) - x = qpinfo.x - result[1] = (cos(4 * pi * x[1]) - cos(4 * pi * x[2])) / 4 + x = qpinfo.x + return result[1] = (cos(4 * pi * x[1]) - cos(4 * pi * x[2])) / 4 end function kernel_nonlinear!(result, u_ops, qpinfo) - u, ∇u, p = view(u_ops, 1:2), view(u_ops, 3:6), view(u_ops, 7) - μ = qpinfo.params[1] - result[1] = dot(u, view(∇u, 1:2)) - result[2] = dot(u, view(∇u, 3:4)) - result[3] = μ * ∇u[1] - p[1] - result[4] = μ * ∇u[2] - result[5] = μ * ∇u[3] - result[6] = μ * ∇u[4] - p[1] - result[7] = -(∇u[1] + ∇u[4]) - return nothing + u, ∇u, p = view(u_ops, 1:2), view(u_ops, 3:6), view(u_ops, 7) + μ = qpinfo.params[1] + result[1] = dot(u, view(∇u, 1:2)) + result[2] = dot(u, view(∇u, 3:4)) + result[3] = μ * ∇u[1] - p[1] + result[4] = μ * ∇u[2] + result[5] = μ * ∇u[3] + result[6] = μ * ∇u[4] - p[1] + result[7] = -(∇u[1] + ∇u[4]) + return nothing end function exact_error!(result, u, qpinfo) - u!(result, qpinfo) - p!(view(result, 3), qpinfo) - result .-= u - result .= result .^ 2 + u!(result, qpinfo) + p!(view(result, 3), qpinfo) + result .-= u + return result .= result .^ 2 end function main(; μ = 0.001, nrefs = 5, reconstruct = true, Plotter = nothing, kwargs...) - ## problem description - PD = ProblemDescription() - u = Unknown("u"; name = "velocity") - p = Unknown("p"; name = "pressure") - id_u = reconstruct ? apply(u, Reconstruct{HDIVBDM1{2}, Identity}) : id(u) + ## problem description + PD = ProblemDescription() + u = Unknown("u"; name = "velocity") + p = Unknown("p"; name = "pressure") + id_u = reconstruct ? apply(u, Reconstruct{HDIVBDM1{2}, Identity}) : id(u) - assign_unknown!(PD, u) - assign_unknown!(PD, p) - assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id_u, grad(u), id(p)]; params = [μ], kwargs...)) - assign_operator!(PD, LinearOperator(f!(μ), [id_u]; kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 1:4)) + assign_unknown!(PD, u) + assign_unknown!(PD, p) + assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id_u, grad(u), id(p)]; params = [μ], kwargs...)) + assign_operator!(PD, LinearOperator(f!(μ), [id_u]; kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 1:4)) - ## grid - xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) + ## grid + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) - ## prepare FESpace - FES = [FESpace{H1BR{2}}(xgrid), FESpace{L2P0{1}}(xgrid)] + ## prepare FESpace + FES = [FESpace{H1BR{2}}(xgrid), FESpace{L2P0{1}}(xgrid)] - ## solve - sol = solve(PD, FES; kwargs...) + ## solve + sol = solve(PD, FES; kwargs...) - ## move integral mean of pressure - pintegrate = ItemIntegrator([id(p)]) - pmean = sum(evaluate(pintegrate, sol)) / sum(xgrid[CellVolumes]) - view(sol[p]) .-= pmean + ## move integral mean of pressure + pintegrate = ItemIntegrator([id(p)]) + pmean = sum(evaluate(pintegrate, sol)) / sum(xgrid[CellVolumes]) + view(sol[p]) .-= pmean - ## error calculation - ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(u), id(p)]; quadorder = 4, params = [μ], kwargs...) - error = evaluate(ErrorIntegratorExact, sol) - L2errorU = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) - L2errorP = sqrt(sum(view(error, 3, :))) - @info "L2error(u) = $L2errorU" - @info "L2error(p) = $L2errorP" + ## error calculation + ErrorIntegratorExact = ItemIntegrator(exact_error!, [id(u), id(p)]; quadorder = 4, params = [μ], kwargs...) + error = evaluate(ErrorIntegratorExact, sol) + L2errorU = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) + L2errorP = sqrt(sum(view(error, 3, :))) + @info "L2error(u) = $L2errorU" + @info "L2error(p) = $L2errorP" - ## plot - plt = plot([id(u), id(p)], sol; Plotter = Plotter) + ## plot + plt = plot([id(u), id(p)], sol; Plotter = Plotter) - return L2errorU, plt + return L2errorU, plt end generateplots = ExtendableFEM.default_generateplots(Example252_NSEPlanarLatticeFlow, "example252.png") #hide function runtests() #hide - L2errorU, plt = main(; nrefs = 3) #hide - @test L2errorU ≈ 0.11892169556349004 #hide + L2errorU, plt = main(; nrefs = 3) #hide + return @test L2errorU ≈ 0.11892169556349004 #hide end #hide end # module diff --git a/examples/Example260_AxisymmetricNavierStokesProblem.jl b/examples/Example260_AxisymmetricNavierStokesProblem.jl index 84f6bcd..fd93d84 100644 --- a/examples/Example260_AxisymmetricNavierStokesProblem.jl +++ b/examples/Example260_AxisymmetricNavierStokesProblem.jl @@ -61,35 +61,35 @@ using Test #hide function kernel_convection!(result, input, qpinfo) u, ∇u = view(input, 1:2), view(input, 3:6) r = qpinfo.x[1] - result[1] = r*(∇u[1]*u[1] + ∇u[2]*u[2]) - result[2] = r*(∇u[3]*u[1] + ∇u[4]*u[2]) + result[1] = r * (∇u[1] * u[1] + ∇u[2] * u[2]) + result[2] = r * (∇u[3] * u[1] + ∇u[4] * u[2]) return nothing end function kernel_stokes_axisymmetric!(result, u_ops, qpinfo) - u, ∇u, p = view(u_ops,1:2), view(u_ops,3:6), view(u_ops, 7) + u, ∇u, p = view(u_ops, 1:2), view(u_ops, 3:6), view(u_ops, 7) r = qpinfo.x[1] μ = qpinfo.params[1] ## add Laplacian - result[1] = μ/r * u[1] - p[1] + 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]) + 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]) return nothing end function u!(result, qpinfo) x = qpinfo.x result[1] = x[1] - result[2] = -2*x[2] + return result[2] = -2 * x[2] end function kernel_l2div(result, u_ops, qpinfo) - u, divu = view(u_ops,1:2), view(u_ops,3) - result[1] = (qpinfo.x[1]*divu[1] + u[1])^2 + u, divu = view(u_ops, 1:2), view(u_ops, 3) + return result[1] = (qpinfo.x[1] * divu[1] + u[1])^2 end @@ -101,29 +101,31 @@ function main(; μ = 0.1, nrefs = 4, nonlinear = false, uniform = false, Plotter p = Unknown("p"; name = "pressure") assign_unknown!(PD, u) assign_unknown!(PD, p) - assign_operator!(PD, BilinearOperator(kernel_stokes_axisymmetric!, [id(u),grad(u),id(p)]; params = [μ], kwargs...))#; jacobian = kernel_jacobian!)) + assign_operator!(PD, BilinearOperator(kernel_stokes_axisymmetric!, [id(u), grad(u), id(p)]; params = [μ], kwargs...)) #; jacobian = kernel_jacobian!)) if nonlinear - assign_operator!(PD, NonlinearOperator(kernel_convection!, [id(u)], [id(u),grad(u)]; bonus_quadorder = 1, kwargs...))#; jacobian = kernel_jacobian!)) + assign_operator!(PD, NonlinearOperator(kernel_convection!, [id(u)], [id(u), grad(u)]; bonus_quadorder = 1, kwargs...)) #; jacobian = kernel_jacobian!)) end assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = [3])) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = [4], mask = (1,0,1))) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1], mask = (0,1,1))) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = [4], mask = (1, 0, 1))) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1], mask = (0, 1, 1))) ## grid if uniform xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) else - xgrid = simplexgrid(Triangulate; - points=[0 0 ; 5 0 ; 5 1 ; 0 1]', - bfaces=[1 2 ; 2 3 ; 3 4 ; 4 1 ]', - bfaceregions=[1, 2, 3, 4], - regionpoints=[0.5 0.5;]', - regionnumbers=[1], - regionvolumes=[4.0^(-nrefs-1)]) + xgrid = simplexgrid( + Triangulate; + points = [0 0 ; 5 0 ; 5 1 ; 0 1]', + bfaces = [1 2 ; 2 3 ; 3 4 ; 4 1 ]', + bfaceregions = [1, 2, 3, 4], + regionpoints = [0.5 0.5;]', + regionnumbers = [1], + regionvolumes = [4.0^(-nrefs - 1)] + ) end ## solve - FES = [FESpace{H1P2B{2,2}}(xgrid), FESpace{L2P1{1}}(xgrid)] + FES = [FESpace{H1P2B{2, 2}}(xgrid), FESpace{L2P1{1}}(xgrid)] sol = ExtendableFEM.solve(PD, FES; kwargs...) ## compute divergence in cylindrical coordinates by volume integrals @@ -134,13 +136,13 @@ function main(; μ = 0.1, nrefs = 4, nonlinear = false, uniform = false, Plotter ## compute L2error function kernel_l2error(result, u_ops, qpinfo) u!(result, qpinfo) - result .= (result - u_ops).^2 + return result .= (result - u_ops) .^ 2 end ErrorIntegratorExact = ItemIntegrator(kernel_l2error, [id(1)]; entities = ON_BFACES, regions = [3], quadorder = 4, kwargs...) error = evaluate(ErrorIntegratorExact, sol) - L2error = sqrt(sum(view(error,1,:)) + sum(view(error,2,:))) + L2error = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) @info "||u - u_h|| = $L2error" - + ## plot plt = plot([id(u)], sol; Plotter = Plotter) @@ -149,7 +151,7 @@ end generateplots = ExtendableFEM.default_generateplots(Example260_AxisymmetricNavierStokesProblem, "example260.png") #hide function runtests() #hide - errors, plt = main(; nrefs = 1) #hide - @test all(errors .<= 1e-12) #hide + errors, plt = main(; nrefs = 1) #hide + return @test all(errors .<= 1.0e-12) #hide end #hide end # module diff --git a/examples/Example265_FlowTransport.jl b/examples/Example265_FlowTransport.jl index 538c498..f5a2923 100644 --- a/examples/Example265_FlowTransport.jl +++ b/examples/Example265_FlowTransport.jl @@ -48,48 +48,52 @@ using Test #hide ## boundary data function u_inlet!(result, qpinfo) x = qpinfo.x - result[1] = 4*x[2]*(1-x[2]) - result[2] = 0 + result[1] = 4 * x[2] * (1 - x[2]) + return result[2] = 0 end function c_inlet!(result, qpinfo) - result[1] = (1-qpinfo.x[2])*qpinfo.x[2] + return result[1] = (1 - qpinfo.x[2]) * qpinfo.x[2] end function kernel_stokes_standard!(result, u_ops, qpinfo) - ∇u, p = view(u_ops,1:4), view(u_ops, 5) + ∇u, p = view(u_ops, 1:4), view(u_ops, 5) μ = qpinfo.params[1] - 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]) + result[1] = μ * ∇u[1] - p[1] + result[2] = μ * ∇u[2] + result[3] = μ * ∇u[3] + result[4] = μ * ∇u[4] - p[1] + return result[5] = -(∇u[1] + ∇u[4]) end function kernel_convection!(result, ∇T, u, qpinfo) - result[1] = ∇T[1]*u[1] + ∇T[2]*u[2] + return result[1] = ∇T[1] * u[1] + ∇T[2] * u[2] end function kernel_inlet!(result, input, qpinfo) c_inlet!(result, qpinfo) - result[1] *= -input[1] + return result[1] *= -input[1] end ## everything is wrapped in a main function function main(; nrefs = 4, Plotter = nothing, reconstruct = true, FVtransport = true, parallel = false, npart = 8, μ = 1, kwargs...) - + ## load mesh and refine - xgrid = uniform_refine(simplexgrid(Triangulate; - points = [0 0; 3 0; 3 -3; 7 -3; 7 0; 10 0; 10 1; 6 1; 6 -2; 4 -2; 4 1; 0 1]', - bfaces = [1 2; 2 3; 3 4; 4 5; 5 6; 6 7; 7 8; 8 9; 9 10; 10 11; 11 12; 12 1]', - bfaceregions = [1; 1; 1; 1; 1; 2; 3; 3; 3; 3; 3; 4], - regionpoints = [0.5 0.5;]', - regionnumbers = [1], - regionvolumes = [1.0]), nrefs) - - if parallel - xgrid = partition(xgrid, RecursiveMetisPartitioning(npart=npart)) - end + xgrid = uniform_refine( + simplexgrid( + Triangulate; + points = [0 0; 3 0; 3 -3; 7 -3; 7 0; 10 0; 10 1; 6 1; 6 -2; 4 -2; 4 1; 0 1]', + bfaces = [1 2; 2 3; 3 4; 4 5; 5 6; 6 7; 7 8; 8 9; 9 10; 10 11; 11 12; 12 1]', + bfaceregions = [1; 1; 1; 1; 1; 2; 3; 3; 3; 3; 3; 4], + regionpoints = [0.5 0.5;]', + regionnumbers = [1], + regionvolumes = [1.0] + ), nrefs + ) + + if parallel + xgrid = partition(xgrid, RecursiveMetisPartitioning(npart = npart)) + end ## define unknowns u = Unknown("u"; name = "velocity", dim = 2) @@ -102,32 +106,32 @@ function main(; nrefs = 4, Plotter = nothing, reconstruct = true, FVtransport = PD = ProblemDescription("Stokes problem") assign_unknown!(PD, u) assign_unknown!(PD, p) - assign_operator!(PD, BilinearOperator(kernel_stokes_standard!, [grad(u), id(p)]; params = [μ], parallel = parallel, kwargs...)) + assign_operator!(PD, BilinearOperator(kernel_stokes_standard!, [grad(u), id(p)]; params = [μ], parallel = parallel, kwargs...)) assign_operator!(PD, InterpolateBoundaryData(u, u_inlet!; regions = 4, parallel = parallel, kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1,3], kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = [1, 3], kwargs...)) ## add transport equation of species PDT = ProblemDescription("transport problem") assign_unknown!(PDT, T) if FVtransport ## FVM discretisation of transport equation (pure upwind convection) - τ = 1e3 + τ = 1.0e3 assign_operator!(PDT, CallbackOperator(assemble_fv_operator!(), [u]; kwargs...)) - assign_operator!(PDT, BilinearOperator([id(T)]; store = true, factor = 1/τ, parallel = parallel, kwargs...)) - assign_operator!(PDT, LinearOperator([id(T)], [id(T)]; factor = 1/τ, parallel = parallel, kwargs...)) + assign_operator!(PDT, BilinearOperator([id(T)]; store = true, factor = 1 / τ, parallel = parallel, kwargs...)) + assign_operator!(PDT, LinearOperator([id(T)], [id(T)]; factor = 1 / τ, parallel = parallel, kwargs...)) else ## FEM discretisation of transport equation (with small diffusion term) - assign_operator!(PDT, BilinearOperator([grad(T)]; factor = 1e-6, parallel = parallel, kwargs...)) + assign_operator!(PDT, BilinearOperator([grad(T)]; factor = 1.0e-6, parallel = parallel, kwargs...)) assign_operator!(PDT, BilinearOperator(kernel_convection!, [id(T)], [grad(T)], [id_u]; parallel = parallel, kwargs...)) assign_operator!(PDT, InterpolateBoundaryData(T, c_inlet!; regions = [4], kwargs...)) end - + ## generate FESpaces and a solution vector for all 3 unknowns FETypes = [H1BR{2}, L2P0{1}, FVtransport ? L2P0{1} : H1P1{1}] - FES = [FESpace{FETypes[j]}(xgrid) for j = 1 : 3] - sol = FEVector(FES; tags = [u,p,T]) + FES = [FESpace{FETypes[j]}(xgrid) for j in 1:3] + sol = FEVector(FES; tags = [u, p, T]) ## solve the two problems separately sol = solve(PD; init = sol, kwargs...) - sol = solve(PDT; init = sol, maxiterations = 20, target_residual = 1e-12, constant_matrix = true, kwargs...) + sol = solve(PDT; init = sol, maxiterations = 20, target_residual = 1.0e-12, constant_matrix = true, kwargs...) ## print minimal and maximal concentration to check max principle (should be in [0,1]) println("\n[min(c),max(c)] = [$(minimum(view(sol[T]))),$(maximum(view(sol[T])))]") @@ -140,75 +144,75 @@ end ## pure convection finite volume operator for transport function assemble_fv_operator!() - + BndFluxIntegrator = ItemIntegrator(kernel_inflow!, [normalflux(1)]; entities = ON_BFACES) FluxIntegrator = ItemIntegrator([normalflux(1)]; entities = ON_FACES) - fluxes::Matrix{Float64} = zeros(Float64,1,0) + fluxes::Matrix{Float64} = zeros(Float64, 1, 0) - function closure(A, b, args; assemble_matrix = true, assemble_rhs = true, kwargs...) + return function closure(A, b, args; assemble_matrix = true, assemble_rhs = true, kwargs...) - ## prepare grid and stash - xgrid = args[1].FES.xgrid - nfaces = size(xgrid[FaceCells],2) - if size(fluxes,2) < nfaces - fluxes = zeros(Float64, 1, nfaces) - end + ## prepare grid and stash + xgrid = args[1].FES.xgrid + nfaces = size(xgrid[FaceCells], 2) + if size(fluxes, 2) < nfaces + fluxes = zeros(Float64, 1, nfaces) + end - ## right-hand side = boundary inflow fluxes if velocity points inward - if assemble_rhs - fill!(fluxes, 0) - evaluate!(fluxes, BndFluxIntegrator, [args[1]]) - facecells = xgrid[FaceCells] - bface2face = xgrid[BFaceFaces] - for bface in 1 : lastindex(bface2face) - b[facecells[1, bface2face[bface]]] -= fluxes[bface] + ## right-hand side = boundary inflow fluxes if velocity points inward + if assemble_rhs + fill!(fluxes, 0) + evaluate!(fluxes, BndFluxIntegrator, [args[1]]) + facecells = xgrid[FaceCells] + bface2face = xgrid[BFaceFaces] + for bface in 1:lastindex(bface2face) + b[facecells[1, bface2face[bface]]] -= fluxes[bface] + end end - end - ## assemble upwind finite volume fluxes over cell faces into matrix - if assemble_matrix - ## integrate normalfux of velocity - fill!(fluxes, 0) - evaluate!(fluxes, FluxIntegrator, [args[1]]) - - cellfaces = xgrid[CellFaces] - cellfacesigns = xgrid[CellFaceSigns] - for cell = 1 : num_cells(xgrid) - nfaces4cell = num_targets(cellfaces, cell) - for cf = 1 : nfaces4cell - face = cellfaces[cf,cell] - other_cell = facecells[1,face] - if other_cell == cell - other_cell = facecells[2,face] - end - flux = fluxes[face] * cellfacesigns[cf,cell] - if (other_cell > 0) - flux *= 1 // 2 # because it will be accumulated on two cells - end - if flux > 0 # flow from cell to other_cell or out of domain - _addnz(A,cell,cell,flux,1) - if other_cell > 0 - _addnz(A,other_cell,cell,-flux,1) - ## otherwise flow goes out of domain - end - else # flow from other_cell into cell or into domain - _addnz(A,cell,cell,1e-16,1) # add zero to keep pattern for LU - if other_cell > 0 # flow comes from neighbour cell - _addnz(A,other_cell,other_cell,-flux,1) - _addnz(A,cell,other_cell,flux,1) - end - ## otherwise flow comes from outside into domain, handled in rhs side loop above + ## assemble upwind finite volume fluxes over cell faces into matrix + if assemble_matrix + ## integrate normalfux of velocity + fill!(fluxes, 0) + evaluate!(fluxes, FluxIntegrator, [args[1]]) + + cellfaces = xgrid[CellFaces] + cellfacesigns = xgrid[CellFaceSigns] + for cell in 1:num_cells(xgrid) + nfaces4cell = num_targets(cellfaces, cell) + for cf in 1:nfaces4cell + face = cellfaces[cf, cell] + other_cell = facecells[1, face] + if other_cell == cell + other_cell = facecells[2, face] + end + flux = fluxes[face] * cellfacesigns[cf, cell] + if (other_cell > 0) + flux *= 1 // 2 # because it will be accumulated on two cells + end + if flux > 0 # flow from cell to other_cell or out of domain + _addnz(A, cell, cell, flux, 1) + if other_cell > 0 + _addnz(A, other_cell, cell, -flux, 1) + ## otherwise flow goes out of domain + end + else # flow from other_cell into cell or into domain + _addnz(A, cell, cell, 1.0e-16, 1) # add zero to keep pattern for LU + if other_cell > 0 # flow comes from neighbour cell + _addnz(A, other_cell, other_cell, -flux, 1) + _addnz(A, cell, other_cell, flux, 1) + end + ## otherwise flow comes from outside into domain, handled in rhs side loop above + end end end end - end - return nothing + return nothing end end function kernel_inflow!(result, input, qpinfo) - if input[1] < 0 # if velocity points into domain + return if input[1] < 0 # if velocity points into domain c_inlet!(result, qpinfo) result[1] *= input[1] else @@ -218,8 +222,8 @@ end generateplots = ExtendableFEM.default_generateplots(Example265_FlowTransport, "example265.png") #hide function runtests() #hide - sol, plt = main(;) #hide - @test minimum(view(sol[3])) >= 0 #hide - @test maximum(view(sol[3])) <= 0.25 #hide + sol, plt = main() #hide + @test minimum(view(sol[3])) >= 0 #hide + return @test maximum(view(sol[3])) <= 0.25 #hide end #hide end # module diff --git a/examples/Example270_NaturalConvectionProblem.jl b/examples/Example270_NaturalConvectionProblem.jl index 319777c..8934003 100644 --- a/examples/Example270_NaturalConvectionProblem.jl +++ b/examples/Example270_NaturalConvectionProblem.jl @@ -57,34 +57,35 @@ using LinearAlgebra using Test #hide function kernel_nonlinear!(result, u_ops, qpinfo) - u, ∇u, p, ∇T, T = view(u_ops, 1:2), view(u_ops,3:6), view(u_ops, 7), view(u_ops, 8:9), view(u_ops, 10) + u, ∇u, p, ∇T, T = view(u_ops, 1:2), view(u_ops, 3:6), view(u_ops, 7), view(u_ops, 8:9), view(u_ops, 10) Ra, μ, ϵ = qpinfo.params[1], qpinfo.params[2], qpinfo.params[3] - result[1] = dot(u, view(∇u,1:2)) - result[2] = dot(u, view(∇u,3:4)) - Ra*T[1] - result[3] = μ*∇u[1] - p[1] - result[4] = μ*∇u[2] - result[5] = μ*∇u[3] - result[6] = μ*∇u[4] - p[1] + result[1] = dot(u, view(∇u, 1:2)) + result[2] = dot(u, view(∇u, 3:4)) - Ra * T[1] + result[3] = μ * ∇u[1] - p[1] + result[4] = μ * ∇u[2] + result[5] = μ * ∇u[3] + result[6] = μ * ∇u[4] - p[1] result[7] = -(∇u[1] + ∇u[4]) - result[8] = ϵ*∇T[1] - result[9] = ϵ*∇T[2] + result[8] = ϵ * ∇T[1] + result[9] = ϵ * ∇T[2] result[10] = dot(u, ∇T) return nothing end function T_bottom!(result, qpinfo) x = qpinfo.x - result[1] = 2*(1-cos(2*π*x[1])) + return result[1] = 2 * (1 - cos(2 * π * x[1])) end -function main(; - nrefs = 5, - μ = 1.0, - ϵ = 1.0, - Ra_final = 1.0e6, - reconstruct = true, - Plotter = nothing, - kwargs...) +function main(; + nrefs = 5, + μ = 1.0, + ϵ = 1.0, + Ra_final = 1.0e6, + reconstruct = true, + Plotter = nothing, + kwargs... + ) ## problem description PD = ProblemDescription() @@ -95,7 +96,7 @@ function main(; assign_unknown!(PD, p) assign_unknown!(PD, T) id_u = reconstruct ? apply(u, Reconstruct{HDIVBDM1{2}, Identity}) : id(u) - assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id_u,grad(u),id(p),grad(T),id(T)]; kwargs...)) + assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id_u, grad(u), id(p), grad(T), id(T)]; kwargs...)) assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:3)) assign_operator!(PD, FixDofs(p; dofs = [1], vals = [0])) assign_operator!(PD, HomogeneousBoundaryData(T; regions = 3)) @@ -105,51 +106,53 @@ function main(; xgrid = uniform_refine(reference_domain(Triangle2D), nrefs) ## FESpaces - FES = Dict(u => FESpace{H1BR{2}}(xgrid), - p => FESpace{L2P0{1}}(xgrid), - T => FESpace{H1P1{1}}(xgrid)) + FES = Dict( + u => FESpace{H1BR{2}}(xgrid), + p => FESpace{L2P0{1}}(xgrid), + T => FESpace{H1P1{1}}(xgrid) + ) ## prepare plots - plt = GridVisualizer(; Plotter = Plotter, layout = (1,3), clear = true, size = (1200,400)) + plt = GridVisualizer(; Plotter = Plotter, layout = (1, 3), clear = true, size = (1200, 400)) ## solve by Ra embedding - params = Array{Float64,1}([min(Ra_final, 4000), μ, ϵ]) + params = Array{Float64, 1}([min(Ra_final, 4000), μ, ϵ]) sol = nothing SC = nothing - step = 0 - while (true) + step = 0 + while (true) ## solve (params are given to all operators) - sol, SC = ExtendableFEM.solve(PD, FES, SC; init = sol, return_config = true, target_residual = 1e-6, params = params, kwargs...) - + sol, SC = ExtendableFEM.solve(PD, FES, SC; init = sol, return_config = true, target_residual = 1.0e-6, params = params, kwargs...) + ## plot - scalarplot!(plt[1,1], id(u), sol; levels = 0, colorbarticks = 7, abs = true) - vectorplot!(plt[1,1], id(u), sol; clear = false, title = "|u| + quiver (Ra = $(params[1]))") - scalarplot!(plt[1,2], id(T), sol; title = "T (Ra = $(params[1]))") - scalarplot!(plt[1,3], id(p), sol; title = "p (Ra = $(params[1]))") + scalarplot!(plt[1, 1], id(u), sol; levels = 0, colorbarticks = 7, abs = true) + vectorplot!(plt[1, 1], id(u), sol; clear = false, title = "|u| + quiver (Ra = $(params[1]))") + scalarplot!(plt[1, 2], id(T), sol; title = "T (Ra = $(params[1]))") + scalarplot!(plt[1, 3], id(p), sol; title = "p (Ra = $(params[1]))") ## stop if Ra_final is reached - if params[1] >= Ra_final - break - end + if params[1] >= Ra_final + break + end ## increase Ra - params[1] = min(Ra_final, params[1]*3) - step += 1 - @info "Step $step : solving for Ra=$(params[1])" - end + params[1] = min(Ra_final, params[1] * 3) + step += 1 + @info "Step $step : solving for Ra=$(params[1])" + end ## compute Nusselt number along bottom (= boundary region 1) ∇T_faces = FaceInterpolator([jump(grad(T))]; order = 0, kwargs...) - NuIntegrator = ItemIntegrator((result, input, qpinfo) -> (result[1] = -input[2]), [id(1)]; entities = ON_FACES, regions = [1]) + NuIntegrator = ItemIntegrator((result, input, qpinfo) -> (result[1] = -input[2]), [id(1)]; entities = ON_FACES, regions = [1]) Nu = sum(evaluate(NuIntegrator, evaluate!(∇T_faces, sol))) - @info "Nu = $Nu" + @info "Nu = $Nu" return Nu, plt end generateplots = ExtendableFEM.default_generateplots(Example270_NaturalConvectionProblem, "example270.png") #hide function runtests() #hide - Nu, plt = main(; nrefs = 4) #hide - @test Nu ≈ 17.641450080135293 #hide + Nu, plt = main(; nrefs = 4) #hide + return @test Nu ≈ 17.641450080135293 #hide end #hide end # module diff --git a/examples/Example275_OptimalControlStokes.jl b/examples/Example275_OptimalControlStokes.jl index 84fb925..a6c1bf4 100644 --- a/examples/Example275_OptimalControlStokes.jl +++ b/examples/Example275_OptimalControlStokes.jl @@ -53,43 +53,43 @@ using ExtendableGrids using Symbolics function prepare_data!(; ϵ = 0) - @variables x y + @variables x y - ## stream function ξ - ξ = x^4*y^4*(x-1)^4*(y-1)^4 - ∇ξ = Symbolics.gradient(ξ, [x,y]) + ## stream function ξ + ξ = x^4 * y^4 * (x - 1)^4 * (y - 1)^4 + ∇ξ = Symbolics.gradient(ξ, [x, y]) ## irrotational perturbation (to study pressure-robustness) - ϕ = cos(x)*sin(y) - ∇ϕ = Symbolics.gradient(ϕ, [x,y]) + ϕ = cos(x) * sin(y) + ∇ϕ = Symbolics.gradient(ϕ, [x, y]) ## final data = curl ξ + ϵ ∇ϕ - d = [-∇ξ[2], ∇ξ[1]] + ϵ * ∇ϕ - d_eval = build_function(d, x, y, expression = Val{false}) + d = [-∇ξ[2], ∇ξ[1]] + ϵ * ∇ϕ + d_eval = build_function(d, x, y, expression = Val{false}) return d_eval[2] end ## standard Stokes kernel function kernel_stokes_standard!(result, u_ops, qpinfo) - ∇u, p = view(u_ops,1:4), view(u_ops, 5) + ∇u, p = view(u_ops, 1:4), view(u_ops, 5) μ = qpinfo.params[1] - 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]) + result[1] = μ * ∇u[1] + p[1] + result[2] = μ * ∇u[2] + result[3] = μ * ∇u[3] + result[4] = μ * ∇u[4] + p[1] + return result[5] = (∇u[1] + ∇u[4]) end ## everything is wrapped in a main function -function main(; nrefs = 4, Plotter = nothing, reconstruct = true, μ = 1, α = 1e-6, ϵ = 0, kwargs...) - +function main(; nrefs = 4, Plotter = nothing, reconstruct = true, μ = 1, α = 1.0e-6, ϵ = 0, kwargs...) + ## prepare target data d_eval = prepare_data!(; ϵ = ϵ) data!(result, qpinfo) = (d_eval(result, qpinfo.x[1], qpinfo.x[2]);) ## load mesh and refine - xgrid = uniform_refine(grid_unitsquare(Triangle2D),nrefs) + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) ## define unknowns u = Unknown("u"; name = "velocity", dim = 2) @@ -106,17 +106,17 @@ function main(; nrefs = 4, Plotter = nothing, reconstruct = true, μ = 1, α = 1 assign_unknown!(PD, z) assign_unknown!(PD, p) assign_unknown!(PD, λ) - assign_operator!(PD, BilinearOperator(kernel_stokes_standard!, [grad(u), id(p)]; params = [μ], kwargs...)) + assign_operator!(PD, BilinearOperator(kernel_stokes_standard!, [grad(u), id(p)]; params = [μ], kwargs...)) assign_operator!(PD, BilinearOperator(kernel_stokes_standard!, [grad(z), id(λ)]; params = [μ], kwargs...)) - assign_operator!(PD, BilinearOperator([idR(z)], [idR(u)]; factor = -1/sqrt(α), transposed_copy = -1, kwargs...)) - assign_operator!(PD, LinearOperator(data!, [idR(z)]; factor = -1/sqrt(α), bonus_quadorder = 5, kwargs...)) + assign_operator!(PD, BilinearOperator([idR(z)], [idR(u)]; factor = -1 / sqrt(α), transposed_copy = -1, kwargs...)) + assign_operator!(PD, LinearOperator(data!, [idR(z)]; factor = -1 / sqrt(α), bonus_quadorder = 5, kwargs...)) assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4, kwargs...)) assign_operator!(PD, HomogeneousBoundaryData(z; regions = 1:4, kwargs...)) ## solve with Bernardi--Raugel method FETypes = [H1BR{2}, L2P0{1}] - FES = [FESpace{FETypes[j]}(xgrid) for j = 1 : 2] - sol = solve(PD, [FES[1],FES[1],FES[2],FES[2]]; kwargs...) + FES = [FESpace{FETypes[j]}(xgrid) for j in 1:2] + sol = solve(PD, [FES[1], FES[1], FES[2], FES[2]]; kwargs...) ## plot solution plt = plot([id(u), id(p), id(z), id(λ)], sol; add = 1, Plotter = Plotter) diff --git a/examples/Example280_CompressibleStokes.jl b/examples/Example280_CompressibleStokes.jl index 41a6bb8..6b3b51c 100644 --- a/examples/Example280_CompressibleStokes.jl +++ b/examples/Example280_CompressibleStokes.jl @@ -65,22 +65,23 @@ using Test #hide ## testcase = 1 : well-balanced test (stratified no-flow over mountain) ## testcase = 2 : vortex example (ϱu is div-free p7 vortex) function main(; - testcase = 1, - nrefs = 4, - M = 1, - c = 1, - ufac = 100, - pressure_stab = 0, - laplacian_in_rhs = false, # for data in example 2 - maxsteps = 5000, - target_residual = 1e-11, - Plotter = nothing, - reconstruct = true, - μ = 1, - order = 1, - kwargs...) - - ## load data for testcase + testcase = 1, + nrefs = 4, + M = 1, + c = 1, + ufac = 100, + pressure_stab = 0, + laplacian_in_rhs = false, # for data in example 2 + maxsteps = 5000, + target_residual = 1.0e-11, + Plotter = nothing, + reconstruct = true, + μ = 1, + order = 1, + kwargs... + ) + + ## load data for testcase grid_builder, kernel_gravity!, kernel_rhs!, u!, ∇u!, ϱ!, τfac = load_testcase_data(testcase; laplacian_in_rhs = laplacian_in_rhs, M = M, c = c, μ = μ, ufac = ufac) xgrid = grid_builder(nrefs) @@ -94,7 +95,7 @@ function main(; FETypes = [H1BR{2}, L2P0{1}, L2P0{1}] id_u = reconstruct ? apply(u, Reconstruct{HDIVRT0{2}, Identity}) : id(u) elseif order == 2 - FETypes = [H1P2B{2,2}, L2P1{1}, L2P1{1}] + FETypes = [H1P2B{2, 2}, L2P1{1}, L2P1{1}] id_u = reconstruct ? apply(u, Reconstruct{HDIVRT1{2}, Identity}) : id(u) end @@ -105,43 +106,43 @@ function main(; assign_operator!(PD, LinearOperator([div(u)], [id(ϱ)]; factor = c, kwargs...)) assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4, kwargs...)) if kernel_rhs! !== nothing - assign_operator!(PD, LinearOperator(kernel_rhs!, [id_u]; factor = 1, store = true, bonus_quadorder = 3*order, kwargs...)) + assign_operator!(PD, LinearOperator(kernel_rhs!, [id_u]; factor = 1, store = true, bonus_quadorder = 3 * order, kwargs...)) end - assign_operator!(PD, LinearOperator(kernel_gravity!, [id_u], [id(ϱ)]; factor = 1, bonus_quadorder = 3*order, kwargs...)) + assign_operator!(PD, LinearOperator(kernel_gravity!, [id_u], [id(ϱ)]; factor = 1, bonus_quadorder = 3 * order, kwargs...)) ## FVM for continuity equation - τ = μ / (order^2*M*sqrt(τfac)) # time step for pseudo timestepping + τ = μ / (order^2 * M * sqrt(τfac)) # time step for pseudo timestepping @info "timestep = $τ" PDT = ProblemDescription("continuity equation") - assign_unknown!(PDT, ϱ) + assign_unknown!(PDT, ϱ) if order > 1 - assign_operator!(PDT, BilinearOperator(kernel_continuity!,[grad(ϱ)],[id(ϱ)],[id(u)]; quadorder = 2*order, factor = -1, kwargs...)) + assign_operator!(PDT, BilinearOperator(kernel_continuity!, [grad(ϱ)], [id(ϱ)], [id(u)]; quadorder = 2 * order, factor = -1, kwargs...)) end if pressure_stab > 0 psf = pressure_stab #* xgrid[CellVolumes][1] - assign_operator!(PDT, BilinearOperator(stab_kernel!, [jump(id(ϱ))], [jump(id(ϱ))], [id(u)]; entities = ON_IFACES, factor = psf, kwargs...)) + assign_operator!(PDT, BilinearOperator(stab_kernel!, [jump(id(ϱ))], [jump(id(ϱ))], [id(u)]; entities = ON_IFACES, factor = psf, kwargs...)) end - assign_operator!(PDT, BilinearOperator([id(ϱ)]; quadorder = 2*(order-1), factor = 1/τ, store = true, kwargs...)) - assign_operator!(PDT, LinearOperator([id(ϱ)], [id(ϱ)]; quadorder = 2*(order-1), factor = 1/τ, kwargs...)) - assign_operator!(PDT, BilinearOperatorDG(kernel_upwind!, [jump(id(ϱ))], [this(id(ϱ)), other(id(ϱ))], [id(u)]; quadorder = order+1, entities = ON_IFACES, kwargs...)) + assign_operator!(PDT, BilinearOperator([id(ϱ)]; quadorder = 2 * (order - 1), factor = 1 / τ, store = true, kwargs...)) + assign_operator!(PDT, LinearOperator([id(ϱ)], [id(ϱ)]; quadorder = 2 * (order - 1), factor = 1 / τ, kwargs...)) + assign_operator!(PDT, BilinearOperatorDG(kernel_upwind!, [jump(id(ϱ))], [this(id(ϱ)), other(id(ϱ))], [id(u)]; quadorder = order + 1, entities = ON_IFACES, kwargs...)) ## prepare error calculation - EnergyIntegrator = ItemIntegrator(energy_kernel!, [id(u)]; resultdim = 1, quadorder = 2*(order+1), kwargs...) - ErrorIntegratorExact = ItemIntegrator(exact_error!(u!, ∇u!, ϱ!), [id(u), grad(u), id(ϱ)]; resultdim = 9, quadorder = 2*(order+1), kwargs...) + EnergyIntegrator = ItemIntegrator(energy_kernel!, [id(u)]; resultdim = 1, quadorder = 2 * (order + 1), kwargs...) + ErrorIntegratorExact = ItemIntegrator(exact_error!(u!, ∇u!, ϱ!), [id(u), grad(u), id(ϱ)]; resultdim = 9, quadorder = 2 * (order + 1), kwargs...) NDofs = zeros(Int, nrefs) Results = zeros(Float64, nrefs, 5) sol = nothing xgrid = nothing op_upwind = 0 - for lvl = 1 : nrefs + for lvl in 1:nrefs xgrid = grid_builder(lvl) @show xgrid - FES = [FESpace{FETypes[j]}(xgrid) for j = 1 : 3] - sol = FEVector(FES; tags = [u,ϱ,p]) + FES = [FESpace{FETypes[j]}(xgrid) for j in 1:3] + sol = FEVector(FES; tags = [u, ϱ, p]) ## initial guess - fill!(sol[ϱ],M) + fill!(sol[ϱ], M) interpolate!(sol[u], u!) interpolate!(sol[ϱ], ϱ!) NDofs[lvl] = length(sol.entries) @@ -153,42 +154,42 @@ function main(; ## calculate error error = evaluate(ErrorIntegratorExact, sol) - Results[lvl,1] = sqrt(sum(view(error,1,:)) + sum(view(error,2,:))) - Results[lvl,2] = sqrt(sum(view(error,3,:)) + sum(view(error,4,:)) + sum(view(error,5,:)) + sum(view(error,6,:))) - Results[lvl,3] = sqrt(sum(view(error,7,:))) - Results[lvl,4] = sqrt(sum(view(error,8,:)) + sum(view(error,9,:))) - Results[lvl,5] = nits + Results[lvl, 1] = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) + Results[lvl, 2] = sqrt(sum(view(error, 3, :)) + sum(view(error, 4, :)) + sum(view(error, 5, :)) + sum(view(error, 6, :))) + Results[lvl, 3] = sqrt(sum(view(error, 7, :))) + Results[lvl, 4] = sqrt(sum(view(error, 8, :)) + sum(view(error, 9, :))) + Results[lvl, 5] = nits ## print results - print_convergencehistory(NDofs[1:lvl], Results[1:lvl,:]; X_to_h = X -> X.^(-1/2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "|| ϱ - ϱ_h ||", "|| ϱu - ϱu_h ||","#its"], xlabel = "ndof") + print_convergencehistory(NDofs[1:lvl], Results[1:lvl, :]; X_to_h = X -> X .^ (-1 / 2), ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "|| ϱ - ϱ_h ||", "|| ϱu - ϱu_h ||", "#its"], xlabel = "ndof") end - - + + ## plot - plt = GridVisualizer(; Plotter = Plotter, layout = (2,2), clear = true, size = (1000,1000)) - scalarplot!(plt[1,1],xgrid, view(nodevalues(sol[u]; abs = true),1,:), levels = 0, colorbarticks = 7) - vectorplot!(plt[1,1],xgrid, eval_func_bary(PointEvaluator([id(u)], sol)), rasterpoints = 10, clear = false, title = "u_h (abs + quiver)") - scalarplot!(plt[2,1],xgrid, view(nodevalues(sol[ϱ]),1,:), levels = 11, title = "ϱ_h") - plot_convergencehistory!(plt[1,2], NDofs, Results[:,1:4]; add_h_powers = [order,order+1], X_to_h = X -> 0.2*X.^(-1/2), legend = :best, ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "|| ϱ - ϱ_h ||", "|| ϱu - ϱu_h ||","#its"]) - gridplot!(plt[2,2],xgrid) + plt = GridVisualizer(; Plotter = Plotter, layout = (2, 2), clear = true, size = (1000, 1000)) + scalarplot!(plt[1, 1], xgrid, view(nodevalues(sol[u]; abs = true), 1, :), levels = 0, colorbarticks = 7) + vectorplot!(plt[1, 1], xgrid, eval_func_bary(PointEvaluator([id(u)], sol)), rasterpoints = 10, clear = false, title = "u_h (abs + quiver)") + scalarplot!(plt[2, 1], xgrid, view(nodevalues(sol[ϱ]), 1, :), levels = 11, title = "ϱ_h") + plot_convergencehistory!(plt[1, 2], NDofs, Results[:, 1:4]; add_h_powers = [order, order + 1], X_to_h = X -> 0.2 * X .^ (-1 / 2), legend = :best, ylabels = ["|| u - u_h ||", "|| ∇(u - u_h) ||", "|| ϱ - ϱ_h ||", "|| ϱu - ϱu_h ||", "#its"]) + gridplot!(plt[2, 2], xgrid) return Results, plt end function stab_kernel!(result, p, u, qpinfo) - result[1] = p[1] #*abs(u[1] + u[2]) + return result[1] = p[1] #*abs(u[1] + u[2]) end ## kernel for (uϱ, ∇λ) ON_CELLS in continuity equation function kernel_continuity!(result, ϱ, u, qpinfo) - result[1] = ϱ[1] * u[1] - result[2] = ϱ[1] * u[2] + result[1] = ϱ[1] * u[1] + return result[2] = ϱ[1] * u[2] end ## kernel for (u⋅n ϱ^upw, λ) ON_IFACES in continuity equation function kernel_upwind!(result, input, u, qpinfo) flux = dot(u, qpinfo.normal) # u * n - if flux > 0 + return if flux > 0 result[1] = input[1] * flux # rho_left * flux else result[1] = input[2] * flux # rho_righ * flux @@ -196,57 +197,61 @@ function kernel_upwind!(result, input, u, qpinfo) end ## kernel for exact error calculation -function exact_error!(u!,∇u!,ϱ!) - function closure(result, u, qpinfo) - u!(view(result,1:2), qpinfo) - ∇u!(view(result,3:6), qpinfo) - ϱ!(view(result,7), qpinfo) +function exact_error!(u!, ∇u!, ϱ!) + return function closure(result, u, qpinfo) + u!(view(result, 1:2), qpinfo) + ∇u!(view(result, 3:6), qpinfo) + ϱ!(view(result, 7), qpinfo) result[8] = result[1] * result[7] result[9] = result[2] * result[7] - view(result,1:7) .-= u + view(result, 1:7) .-= u result[8] -= u[1] * u[7] result[9] -= u[2] * u[7] - result .= result.^2 + return result .= result .^ 2 end end ## kernel for gravity term in testcase 1 function standard_gravity!(result, ϱ, qpinfo) result[1] = 0 - result[2] = -ϱ[1] + return result[2] = -ϱ[1] end function energy_kernel!(result, u, qpinfo) - result[1] = dot(u,u)/2 + return result[1] = dot(u, u) / 2 end function load_testcase_data(testcase::Int = 1; laplacian_in_rhs = true, M = 1, c = 1, μ = 1, ufac = 100) if testcase == 1 - grid_builder = (nref) -> simplexgrid(Triangulate; - points = [0 0; 0.2 0; 0.3 0.2; 0.45 0.05; 0.55 0.35; 0.65 0.2; 0.7 0.3; 0.8 0; 1 0; 1 1 ; 0 1]', - bfaces = [1 2; 2 3; 3 4; 4 5; 5 6; 6 7; 7 8; 8 9; 9 10; 10 11; 11 1]', - bfaceregions = ones(Int,11), - regionpoints = [0.5 0.5;]', - regionnumbers = [1], - regionvolumes = [4.0^-(nref)/2]) + grid_builder = (nref) -> simplexgrid( + Triangulate; + points = [0 0; 0.2 0; 0.3 0.2; 0.45 0.05; 0.55 0.35; 0.65 0.2; 0.7 0.3; 0.8 0; 1 0; 1 1 ; 0 1]', + bfaces = [1 2; 2 3; 3 4; 4 5; 5 6; 6 7; 7 8; 8 9; 9 10; 10 11; 11 1]', + bfaceregions = ones(Int, 11), + regionpoints = [0.5 0.5;]', + regionnumbers = [1], + regionvolumes = [4.0^-(nref) / 2] + ) xgrid = grid_builder(3) u1!(result, qpinfo) = (fill!(result, 0);) ∇u1!(result, qpinfo) = (fill!(result, 0);) - M_exact = integrate(xgrid, ON_CELLS, (result, qpinfo) -> (result[1] = exp(-qpinfo.x[2]/c)/M;), 1; quadorder = 20) + M_exact = integrate(xgrid, ON_CELLS, (result, qpinfo) -> (result[1] = exp(-qpinfo.x[2] / c) / M;), 1; quadorder = 20) area = sum(xgrid[CellVolumes]) - ϱ1!(result, qpinfo) = (result[1] = exp(-qpinfo.x[2]/c)/(M_exact/area);) + ϱ1!(result, qpinfo) = (result[1] = exp(-qpinfo.x[2] / c) / (M_exact / area);) return grid_builder, standard_gravity!, nothing, u1!, ∇u1!, ϱ1!, 1 elseif testcase == 2 - grid_builder = (nref) -> simplexgrid(Triangulate; - points = [0 0; 1 0; 1 1 ; 0 1]', - bfaces = [1 2; 2 3; 3 4; 4 1]', - bfaceregions = ones(Int,4), - regionpoints = [0.5 0.5;]', - regionnumbers = [1], - regionvolumes = [4.0^-(nref)]) + grid_builder = (nref) -> simplexgrid( + Triangulate; + points = [0 0; 1 0; 1 1 ; 0 1]', + bfaces = [1 2; 2 3; 3 4; 4 1]', + bfaceregions = ones(Int, 4), + regionpoints = [0.5 0.5;]', + regionnumbers = [1], + regionvolumes = [4.0^-(nref)] + ) xgrid = grid_builder(3) - M_exact = integrate(xgrid, ON_CELLS, (result, qpinfo) -> (result[1] = exp(-qpinfo.x[1]^3/(3*c));), 1; quadorder = 20) + M_exact = integrate(xgrid, ON_CELLS, (result, qpinfo) -> (result[1] = exp(-qpinfo.x[1]^3 / (3 * c));), 1; quadorder = 20) ϱ_eval, g_eval, f_eval, u_eval, ∇u_eval = prepare_data!(; laplacian_in_rhs = laplacian_in_rhs, M = M_exact, c = c, μ = μ, ufac = ufac) ϱ2!(result, qpinfo) = (result[1] = ϱ_eval(qpinfo.x[1], qpinfo.x[2]);) @@ -255,11 +260,11 @@ function load_testcase_data(testcase::Int = 1; laplacian_in_rhs = true, M = 1, c function kernel_gravity!(result, input, qpinfo) g_eval(result, qpinfo.x[1], qpinfo.x[2]) - result .*= input[1] + return result .*= input[1] end function kernel_rhs!(result, qpinfo) - f_eval(result, qpinfo.x[1], qpinfo.x[2]) + return f_eval(result, qpinfo.x[1], qpinfo.x[2]) end u2!(result, qpinfo) = (u_eval(result, qpinfo.x[1], qpinfo.x[2]);) @@ -271,54 +276,54 @@ end ## exact data for testcase 2 computed by Symbolics function prepare_data!(; M = 1, c = 1, μ = 1, ufac = 100, laplacian_in_rhs = true) - @variables x y + @variables x y - ## density - ϱ = exp(-x^3/(3*c))/M + ## density + ϱ = exp(-x^3 / (3 * c)) / M - ## stream function ξ - ## sucht that ϱu = curl ξ - ξ = x^2*y^2*(x-1)^2*(y-1)^2 * ufac + ## stream function ξ + ## sucht that ϱu = curl ξ + ξ = x^2 * y^2 * (x - 1)^2 * (y - 1)^2 * ufac - ∇ξ = Symbolics.gradient(ξ, [x,y]) + ∇ξ = Symbolics.gradient(ξ, [x, y]) - ## velocity u = curl ξ / ϱ - u = [-∇ξ[2], ∇ξ[1]] ./ ϱ + ## velocity u = curl ξ / ϱ + u = [-∇ξ[2], ∇ξ[1]] ./ ϱ - ## gradient of velocity - ∇u = Symbolics.jacobian(u, [x,y]) - ∇u_reshaped = [∇u[1,1], ∇u[1,2], ∇u[2,1], ∇u[2,2]] + ## gradient of velocity + ∇u = Symbolics.jacobian(u, [x, y]) + ∇u_reshaped = [∇u[1, 1], ∇u[1, 2], ∇u[2, 1], ∇u[2, 2]] - ## Laplacian - Δu = [ - (Symbolics.gradient(∇u[1,1], [x]) + Symbolics.gradient(∇u[1,2], [y]))[1], - (Symbolics.gradient(∇u[2,1], [x]) + Symbolics.gradient(∇u[2,2], [y]))[1] - ] + ## Laplacian + Δu = [ + (Symbolics.gradient(∇u[1, 1], [x]) + Symbolics.gradient(∇u[1, 2], [y]))[1], + (Symbolics.gradient(∇u[2, 1], [x]) + Symbolics.gradient(∇u[2, 2], [y]))[1], + ] - ## gravity ϱg = - Δu + ϱ∇log(ϱ) + ## gravity ϱg = - Δu + ϱ∇log(ϱ) if laplacian_in_rhs - f = - μ*Δu - g = c * Symbolics.gradient(log(ϱ), [x,y]) - else - g = - μ*Δu/ϱ + c * Symbolics.gradient(log(ϱ), [x,y]) + f = - μ * Δu + g = c * Symbolics.gradient(log(ϱ), [x, y]) + else + g = - μ * Δu / ϱ + c * Symbolics.gradient(log(ϱ), [x, y]) f = 0 end - #Δu = Symbolics.derivative(∇u[1,1], [x]) + Symbolics.derivative(∇u[2,2], [y]) + #Δu = Symbolics.derivative(∇u[1,1], [x]) + Symbolics.derivative(∇u[2,2], [y]) - ϱ_eval = build_function(ϱ, x, y, expression = Val{false}) - u_eval = build_function(u, x, y, expression = Val{false}) - ∇u_eval = build_function(∇u_reshaped, x, y, expression = Val{false}) - g_eval = build_function(g, x, y, expression = Val{false}) - f_eval = build_function(f, x, y, expression = Val{false}) + ϱ_eval = build_function(ϱ, x, y, expression = Val{false}) + u_eval = build_function(u, x, y, expression = Val{false}) + ∇u_eval = build_function(∇u_reshaped, x, y, expression = Val{false}) + g_eval = build_function(g, x, y, expression = Val{false}) + f_eval = build_function(f, x, y, expression = Val{false}) return ϱ_eval, g_eval[2], f == 0 ? nothing : f_eval[2], u_eval[2], ∇u_eval[2] end generateplots = ExtendableFEM.default_generateplots(Example280_CompressibleStokes, "example280.png") #hide -function runtests(;) #hide - Results, plt = main(; nrefs = 2) #hide - @test Results[end,1] ≈ 6.732891488265023e-7 #hide +function runtests() #hide + Results, plt = main(; nrefs = 2) #hide + return @test Results[end, 1] ≈ 6.732891488265023e-7 #hide end #hide end diff --git a/examples/Example282_IncompressibleMHD.jl b/examples/Example282_IncompressibleMHD.jl index fa5f153..5cb7f06 100644 --- a/examples/Example282_IncompressibleMHD.jl +++ b/examples/Example282_IncompressibleMHD.jl @@ -27,89 +27,89 @@ using ExtendableGrids using LinearAlgebra function f!(result, qpinfo) - result .= 0 + return result .= 0 end function g!(result, qpinfo) - x = qpinfo.x - result[1] = sin(2*pi*x[2])*cos(pi*x[1]) - result[2] = 0 + x = qpinfo.x + result[1] = sin(2 * pi * x[2]) * cos(pi * x[1]) + return result[2] = 0 end function kernel_nonlinear!(result, u_ops, qpinfo) u, B, ∇u, ∇B, p, q = view(u_ops, 1:2), view(u_ops, 3:4), view(u_ops, 5:8), view(u_ops, 9:12), view(u_ops, 13), view(u_ops, 14) - μ = qpinfo.params[1] + μ = qpinfo.params[1] η = qpinfo.params[2] - ## viscous terms and pressures + ## viscous terms and pressures result[5] = μ * ∇u[1] - p[1] - result[6] = μ * ∇u[2] - result[7] = μ * ∇u[3] - result[8] = μ * ∇u[4] - p[1] - result[9] = η * ∇B[1] - q[1] - result[10] = η * ∇B[2] - result[11] = η * ∇B[3] - result[12] = η * ∇B[4] - q[1] - + result[6] = μ * ∇u[2] + result[7] = μ * ∇u[3] + result[8] = μ * ∇u[4] - p[1] + result[9] = η * ∇B[1] - q[1] + result[10] = η * ∇B[2] + result[11] = η * ∇B[3] + result[12] = η * ∇B[4] - q[1] + ## Lorentz force - result[1] = - dot(B, view(∇B,1:2)) - result[2] = - dot(B, view(∇B,3:4)) - BdotB = (B[1]*B[1] + B[2]*B[2])/2 + result[1] = - dot(B, view(∇B, 1:2)) + result[2] = - dot(B, view(∇B, 3:4)) + BdotB = (B[1] * B[1] + B[2] * B[2]) / 2 result[5] -= BdotB result[8] -= BdotB ## convection term for u and B - result[1] += dot(u, view(∇u,1:2)) - result[2] += dot(u, view(∇u,3:4)) - result[3] = dot(u, view(∇B,1:2)) - dot(B, view(∇u,1:2)) - result[4] = dot(u, view(∇B,3:4)) - dot(B, view(∇u,3:4)) - + result[1] += dot(u, view(∇u, 1:2)) + result[2] += dot(u, view(∇u, 3:4)) + result[3] = dot(u, view(∇B, 1:2)) - dot(B, view(∇u, 1:2)) + result[4] = dot(u, view(∇B, 3:4)) - dot(B, view(∇u, 3:4)) + ## divergence constraint - result[13] = -(∇u[1] + ∇u[4]) - result[14] = -(∇B[1] + ∇B[4]) - return nothing + result[13] = -(∇u[1] + ∇u[4]) + result[14] = -(∇B[1] + ∇B[4]) + return nothing end ## everything is wrapped in a main function -function main(; Plotter = nothing, μ = 1e-3, η = 1e-1, nrefs = 5, kwargs...) +function main(; Plotter = nothing, μ = 1.0e-3, η = 1.0e-1, nrefs = 5, kwargs...) - ## load grid (see function below) - xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) + ## load grid (see function below) + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nrefs) - ## problem description - PD = ProblemDescription() - u = Unknown("u"; name = "velocity") - B = Unknown("B"; name = "magnetic field") - p = Unknown("p"; name = "pressure") - q = Unknown("q"; name = "magnetic pressure") + ## problem description + PD = ProblemDescription() + u = Unknown("u"; name = "velocity") + B = Unknown("B"; name = "magnetic field") + p = Unknown("p"; name = "pressure") + q = Unknown("q"; name = "magnetic pressure") - assign_unknown!(PD, u) - assign_unknown!(PD, B) - assign_unknown!(PD, p) - assign_unknown!(PD, q) + assign_unknown!(PD, u) + assign_unknown!(PD, B) + assign_unknown!(PD, p) + assign_unknown!(PD, q) - assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id(u), id(B), grad(u), grad(B), id(p), id(q)]; bonus_quadorder = 2, params = [μ,η], kwargs...)) + assign_operator!(PD, NonlinearOperator(kernel_nonlinear!, [id(u), id(B), grad(u), grad(B), id(p), id(q)]; bonus_quadorder = 2, params = [μ, η], kwargs...)) assign_operator!(PD, LinearOperator(f!, [id(u)])) assign_operator!(PD, LinearOperator(g!, [id(B)])) assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4)) assign_operator!(PD, HomogeneousBoundaryData(B; regions = [1])) assign_operator!(PD, FixDofs(p; dofs = [1])) assign_operator!(PD, FixDofs(q; dofs = [1])) - - ## P2-bubble finite element method - FETypes = [H1P2{2, 2}, H1P2{2, 2}, H1P1{1}, H1P1{1}] - ## generate FESpaces and Solution vector - FES = [FESpace{FETypes[j]}(xgrid) for j = 1:4] + ## P2-bubble finite element method + FETypes = [H1P2{2, 2}, H1P2{2, 2}, H1P1{1}, H1P1{1}] + + ## generate FESpaces and Solution vector + FES = [FESpace{FETypes[j]}(xgrid) for j in 1:4] - ## solve - sol = ExtendableFEM.solve(PD, FES; target_residual = 1e-8, time = 0, kwargs...) + ## solve + sol = ExtendableFEM.solve(PD, FES; target_residual = 1.0e-8, time = 0, kwargs...) ## plot - plt = plot([id(u), id(B), id(p), id(q)], sol; Plotter = Plotter) + plt = plot([id(u), id(B), id(p), id(q)], sol; Plotter = Plotter) - return sol, plt + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example282_IncompressibleMHD, "example282.png") #hide diff --git a/examples/Example284_LevelSetMethod.jl b/examples/Example284_LevelSetMethod.jl index 62e93af..09a4180 100644 --- a/examples/Example284_LevelSetMethod.jl +++ b/examples/Example284_LevelSetMethod.jl @@ -29,83 +29,85 @@ using LinearAlgebra using DifferentialEquations function ϕ_init!(result, qpinfo) - x = qpinfo.x - ϵ = qpinfo.params[1] - result[1] = 1 / 2 * (tanh((sqrt((x[1] - 0.5)^2 + (x[2] - 0.75)^2) - 0.15) / (2 * ϵ)) + 1) + x = qpinfo.x + ϵ = qpinfo.params[1] + return result[1] = 1 / 2 * (tanh((sqrt((x[1] - 0.5)^2 + (x[2] - 0.75)^2) - 0.15) / (2 * ϵ)) + 1) end function velocity!(result, qpinfo) - result[1] = 0.5 - result[2] = 1.0 - result[1] = -2*cos(π*qpinfo.x[2])*sin(π*qpinfo.x[2]) * sin(π*qpinfo.x[1])^2 - result[2] = 2*cos(π*qpinfo.x[1])*sin(π*qpinfo.x[1]) * sin(π*qpinfo.x[2])^2 + result[1] = 0.5 + result[2] = 1.0 + result[1] = -2 * cos(π * qpinfo.x[2]) * sin(π * qpinfo.x[2]) * sin(π * qpinfo.x[1])^2 + return result[2] = 2 * cos(π * qpinfo.x[1]) * sin(π * qpinfo.x[1]) * sin(π * qpinfo.x[2])^2 end function kernel_convection!() - u = zeros(Float64, 2) - function closure(result, input, qpinfo) - velocity!(u, qpinfo) - result[1] = dot(u, input) - end + u = zeros(Float64, 2) + return function closure(result, input, qpinfo) + velocity!(u, qpinfo) + return result[1] = dot(u, input) + end end ## everything is wrapped in a main function -function main(; Plotter = nothing, ϵ = 0.05, τ = 1e-2, T = 1.0, order = 2, nref = 6, use_diffeq = false, - solver = ImplicitEuler(autodiff = false), verbosity = -1, kwargs...) - - ## initial grid and final time - xgrid = uniform_refine(grid_unitsquare(Triangle2D), nref) - - ## define main level set problem - PD = ProblemDescription("level set problem") - ϕ = Unknown("ϕ"; name = "level set function") - assign_unknown!(PD, ϕ) - assign_operator!(PD, BilinearOperator(kernel_convection!(), [id(ϕ)], [grad(ϕ)]; kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(ϕ; value = 1, regions = 1:4, kwargs...)) - - ## generate FESpace and solution vector and interpolate initial state - FES = FESpace{H1Pk{1, 2, order}}(xgrid) - sol = FEVector(FES; tags = PD.unknowns) - interpolate!(sol[ϕ], ϕ_init!; params = [ϵ]) - - ## prepare plot and plot init solution - plt = GridVisualizer(; Plotter = Plotter, layout = (1, 2), clear = true, resolution = (800, 400)) - scalarplot!(plt[1, 1], id(ϕ), sol; levels = [0.5], flimits = [-0.05, 1.05], colorbarticks = [0, 0.25, 0.5, 0.75, 1], title = "ϕ (t = 0)") - - if (use_diffeq) - ## generate DifferentialEquations.ODEProblem - prob = generate_ODEProblem(PD, FES, (0.0, T); init = sol, constant_matrix = true) - - ## solve ODE problem - de_sol = DifferentialEquations.solve(prob, solver, abstol = 1e-6, reltol = 1e-4, dt = τ, dtmin = 1e-8, adaptive = true) - @info "#tsteps = $(length(de_sol))" - - ## get final solution - sol.entries .= de_sol[end] - else - ## add backward Euler time derivative - M = FEMatrix(FES) - assemble!(M, BilinearOperator([id(1)])) - assign_operator!(PD, BilinearOperator(M, [ϕ]; factor = 1 / τ, kwargs...)) - assign_operator!(PD, LinearOperator(M, [ϕ], [ϕ]; factor = 1 / τ, kwargs...)) - - ## generate solver configuration - SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, constant_matrix = true, verbosity = verbosity, kwargs...) - - ## iterate tspan - t = 0 - for it ∈ 1:Int(floor(T / τ)) - t += τ - ExtendableFEM.solve(PD, FES, SC; time = t) - #scalarplot!(plt[1, 2], id(ϕ), sol; levels = [0.5], flimits = [-0.05, 1.05], colorbarticks = [0, 0.25, 0.5, 0.75, 1], title = "ϕ (t = $t)") - end - end - - ## plot final state - scalarplot!(plt[1, 2], id(ϕ), sol; levels = [0.5], flimits = [-0.05, 1.05], colorbarticks = [0, 0.25, 0.5, 0.75, 1], title = "ϕ (t = $T)") - - return sol, plt +function main(; + Plotter = nothing, ϵ = 0.05, τ = 1.0e-2, T = 1.0, order = 2, nref = 6, use_diffeq = false, + solver = ImplicitEuler(autodiff = false), verbosity = -1, kwargs... + ) + + ## initial grid and final time + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nref) + + ## define main level set problem + PD = ProblemDescription("level set problem") + ϕ = Unknown("ϕ"; name = "level set function") + assign_unknown!(PD, ϕ) + assign_operator!(PD, BilinearOperator(kernel_convection!(), [id(ϕ)], [grad(ϕ)]; kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(ϕ; value = 1, regions = 1:4, kwargs...)) + + ## generate FESpace and solution vector and interpolate initial state + FES = FESpace{H1Pk{1, 2, order}}(xgrid) + sol = FEVector(FES; tags = PD.unknowns) + interpolate!(sol[ϕ], ϕ_init!; params = [ϵ]) + + ## prepare plot and plot init solution + plt = GridVisualizer(; Plotter = Plotter, layout = (1, 2), clear = true, resolution = (800, 400)) + scalarplot!(plt[1, 1], id(ϕ), sol; levels = [0.5], flimits = [-0.05, 1.05], colorbarticks = [0, 0.25, 0.5, 0.75, 1], title = "ϕ (t = 0)") + + if (use_diffeq) + ## generate DifferentialEquations.ODEProblem + prob = generate_ODEProblem(PD, FES, (0.0, T); init = sol, constant_matrix = true) + + ## solve ODE problem + de_sol = DifferentialEquations.solve(prob, solver, abstol = 1.0e-6, reltol = 1.0e-4, dt = τ, dtmin = 1.0e-8, adaptive = true) + @info "#tsteps = $(length(de_sol))" + + ## get final solution + sol.entries .= de_sol[end] + else + ## add backward Euler time derivative + M = FEMatrix(FES) + assemble!(M, BilinearOperator([id(1)])) + assign_operator!(PD, BilinearOperator(M, [ϕ]; factor = 1 / τ, kwargs...)) + assign_operator!(PD, LinearOperator(M, [ϕ], [ϕ]; factor = 1 / τ, kwargs...)) + + ## generate solver configuration + SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, constant_matrix = true, verbosity = verbosity, kwargs...) + + ## iterate tspan + t = 0 + for it in 1:Int(floor(T / τ)) + t += τ + ExtendableFEM.solve(PD, FES, SC; time = t) + #scalarplot!(plt[1, 2], id(ϕ), sol; levels = [0.5], flimits = [-0.05, 1.05], colorbarticks = [0, 0.25, 0.5, 0.75, 1], title = "ϕ (t = $t)") + end + end + + ## plot final state + scalarplot!(plt[1, 2], id(ϕ), sol; levels = [0.5], flimits = [-0.05, 1.05], colorbarticks = [0, 0.25, 0.5, 0.75, 1], title = "ϕ (t = $T)") + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example284_LevelSetMethod, "example284.png") #hide diff --git a/examples/Example285_CahnHilliard.jl b/examples/Example285_CahnHilliard.jl index b604f4a..a0d01bf 100644 --- a/examples/Example285_CahnHilliard.jl +++ b/examples/Example285_CahnHilliard.jl @@ -36,94 +36,94 @@ const f = (c) -> 100 * c^2 * (1 - c)^2 const dfdc = (c) -> ForwardDiff.derivative(f, c) function c0!(result, qpinfo) - result[1] = 0.63 + 0.02 * (0.5 - rand()) + return result[1] = 0.63 + 0.02 * (0.5 - rand()) end ## everything is wrapped in a main function function main(; - order = 2, # finite element order for c and μ - nref = 4, # refinement level - M = 1.0, - λ = 1e-2, - iterations_until_next_plot = 20, - τ = 5 / 1000000, # time step (for main evolution phase) - τ_increase = 1.1, # increase factor for τ after each plot - Plotter = nothing, # Plotter (e.g. PyPlot) - kwargs..., -) - - ## initial grid and final time - xgrid = uniform_refine(grid_unitsquare(Triangle2D), nref) - - ## define unknowns - c = Unknown("c"; name = "concentration", dim = 1) - μ = Unknown("μ"; name = "chemical potential", dim = 1) - - ## define main level set problem - PD = ProblemDescription("Cahn-Hilliard equation") - assign_unknown!(PD, c) - assign_unknown!(PD, μ) - assign_operator!(PD, BilinearOperator([grad(c)], [grad(μ)]; factor = M, store = true)) - assign_operator!(PD, BilinearOperator([id(μ)]; store = true)) - assign_operator!(PD, BilinearOperator([grad(μ)], [grad(c)]; factor = -λ, store = true)) - - ## add nonlinear reaction part (= -df/dc times test function) - function kernel_dfdc!(result, input, qpinfo) - result[1] = -dfdc(input[1]) - end - assign_operator!(PD, NonlinearOperator(kernel_dfdc!, [id(μ)], [id(c)]; bonus_quadorder = 1)) - - ## generate FESpace and solution vector and interpolate initial state - FES = FESpace{H1Pk{1, 2, order}}(xgrid) - sol = FEVector([FES, FES]; tags = PD.unknowns) - interpolate!(sol[c], c0!) - - ## init plot (if order > 1, solution is upscaled to finer grid for plotting) - plt = GridVisualizer(; Plotter = Plotter, layout = (4, 3), clear = true, resolution = (900, 1200)) - if order > 1 - xgrid_upscale = uniform_refine(xgrid, order - 1) - SolutionUpscaled = FEVector(FESpace{H1P1{1}}(xgrid_upscale)) - lazy_interpolate!(SolutionUpscaled[1], sol) - else - xgrid_upscale = xgrid - SolutionUpscaled = sol - end - nodevals = nodevalues_view(SolutionUpscaled[1]) - scalarplot!(plt[1, 1], xgrid_upscale, nodevals[1]; limits = (0.61, 0.65), xlabel = "", ylabel = "", levels = 1, title = "c (t = 0)") - - ## prepare backward Euler time derivative - M = FEMatrix(FES) - b = FEVector(FES) - assemble!(M, BilinearOperator([id(1)]; factor = 1.0 / τ)) - assign_operator!(PD, BilinearOperator(M, [c]; kwargs...)) - assign_operator!(PD, LinearOperator(b, [c]; kwargs...)) - - ## generate solver configuration - SC = SolverConfiguration(PD, [FES, FES]; init = sol, maxiterations = 50, target_residual = 1e-6, kwargs...) - - ## advance in time, plot from time to time - t = 0 - for j ∈ 1:11 - ## do some timesteps until next plot - for it ∈ 1:iterations_until_next_plot - t += τ - ## update time derivative - b.entries .= M.entries * view(sol[c]) - ExtendableFEM.solve(PD, [FES, FES], SC; time = t) - end - - ## enlarge time step a little bit - τ *= τ_increase - M.entries.cscmatrix.nzval ./= τ_increase - - ## plot at current time - if order > 1 - lazy_interpolate!(SolutionUpscaled[1], sol) - end - scalarplot!(plt[1+Int(floor((j) / 3)), 1+(j)%3], xgrid_upscale, nodevals[1]; xlabel = "", ylabel = "", limits = (-0.1, 1.1), levels = 1, title = "c (t = $(Float32(t)))") - end - - return sol, plt + order = 2, # finite element order for c and μ + nref = 4, # refinement level + M = 1.0, + λ = 1.0e-2, + iterations_until_next_plot = 20, + τ = 5 / 1000000, # time step (for main evolution phase) + τ_increase = 1.1, # increase factor for τ after each plot + Plotter = nothing, # Plotter (e.g. PyPlot) + kwargs..., + ) + + ## initial grid and final time + xgrid = uniform_refine(grid_unitsquare(Triangle2D), nref) + + ## define unknowns + c = Unknown("c"; name = "concentration", dim = 1) + μ = Unknown("μ"; name = "chemical potential", dim = 1) + + ## define main level set problem + PD = ProblemDescription("Cahn-Hilliard equation") + assign_unknown!(PD, c) + assign_unknown!(PD, μ) + assign_operator!(PD, BilinearOperator([grad(c)], [grad(μ)]; factor = M, store = true)) + assign_operator!(PD, BilinearOperator([id(μ)]; store = true)) + assign_operator!(PD, BilinearOperator([grad(μ)], [grad(c)]; factor = -λ, store = true)) + + ## add nonlinear reaction part (= -df/dc times test function) + function kernel_dfdc!(result, input, qpinfo) + return result[1] = -dfdc(input[1]) + end + assign_operator!(PD, NonlinearOperator(kernel_dfdc!, [id(μ)], [id(c)]; bonus_quadorder = 1)) + + ## generate FESpace and solution vector and interpolate initial state + FES = FESpace{H1Pk{1, 2, order}}(xgrid) + sol = FEVector([FES, FES]; tags = PD.unknowns) + interpolate!(sol[c], c0!) + + ## init plot (if order > 1, solution is upscaled to finer grid for plotting) + plt = GridVisualizer(; Plotter = Plotter, layout = (4, 3), clear = true, resolution = (900, 1200)) + if order > 1 + xgrid_upscale = uniform_refine(xgrid, order - 1) + SolutionUpscaled = FEVector(FESpace{H1P1{1}}(xgrid_upscale)) + lazy_interpolate!(SolutionUpscaled[1], sol) + else + xgrid_upscale = xgrid + SolutionUpscaled = sol + end + nodevals = nodevalues_view(SolutionUpscaled[1]) + scalarplot!(plt[1, 1], xgrid_upscale, nodevals[1]; limits = (0.61, 0.65), xlabel = "", ylabel = "", levels = 1, title = "c (t = 0)") + + ## prepare backward Euler time derivative + M = FEMatrix(FES) + b = FEVector(FES) + assemble!(M, BilinearOperator([id(1)]; factor = 1.0 / τ)) + assign_operator!(PD, BilinearOperator(M, [c]; kwargs...)) + assign_operator!(PD, LinearOperator(b, [c]; kwargs...)) + + ## generate solver configuration + SC = SolverConfiguration(PD, [FES, FES]; init = sol, maxiterations = 50, target_residual = 1.0e-6, kwargs...) + + ## advance in time, plot from time to time + t = 0 + for j in 1:11 + ## do some timesteps until next plot + for it in 1:iterations_until_next_plot + t += τ + ## update time derivative + b.entries .= M.entries * view(sol[c]) + ExtendableFEM.solve(PD, [FES, FES], SC; time = t) + end + + ## enlarge time step a little bit + τ *= τ_increase + M.entries.cscmatrix.nzval ./= τ_increase + + ## plot at current time + if order > 1 + lazy_interpolate!(SolutionUpscaled[1], sol) + end + scalarplot!(plt[1 + Int(floor((j) / 3)), 1 + (j) % 3], xgrid_upscale, nodevals[1]; xlabel = "", ylabel = "", limits = (-0.1, 1.1), levels = 1, title = "c (t = $(Float32(t)))") + end + + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example285_CahnHilliard, "example285.png") #hide diff --git a/examples/Example290_PoroElasticity.jl b/examples/Example290_PoroElasticity.jl index 62f61b6..9ab2b25 100644 --- a/examples/Example290_PoroElasticity.jl +++ b/examples/Example290_PoroElasticity.jl @@ -44,175 +44,177 @@ using Test #hide ## exact data for testcase 2 computed by Symbolics function prepare_data!(; μ = 1, λ = 1, K = 1, c0 = 1, α = 1) - @variables x y t - - ## displacement and pressure - u = [exp(-t) * (sin(2 * pi * y) * (-1 + cos(2 * pi * x)) + sin(pi * x) * sin(pi * y) / (μ + λ)) - exp(-t) * (sin(2 * pi * x) * (1 - cos(2 * pi * y)) + sin(pi * x) * sin(pi * y) / (μ + λ))] - p = exp(-t) * sin(pi * x) * sin(pi * y) - - ## gradient of displacement - ∇u = Symbolics.jacobian(u, [x, y]) - ∇u_reshaped = [∇u[1, 1], ∇u[1, 2], ∇u[2, 1], ∇u[2, 2]] - - ## gradient of pressure - ∇p = [Symbolics.gradient(p, [x])[1], Symbolics.gradient(p, [y])[1]] - - ## Laplacian - Δu = [ - (Symbolics.gradient(∇u[1, 1], [x])+Symbolics.gradient(∇u[1, 2], [y]))[1], - (Symbolics.gradient(∇u[2, 1], [x])+Symbolics.gradient(∇u[2, 2], [y]))[1], - ] - Δp = Symbolics.gradient(∇p[1], [x]) + Symbolics.gradient(∇p[2], [y]) - divu = ∇u[1, 1] + ∇u[2, 2] - ∇divu = [Symbolics.gradient(divu, [x])[1], Symbolics.gradient(divu, [y])[1]] - divu_dt = Symbolics.gradient(divu, [t]) - - f = -μ * Δu .+ α * ∇p .- (μ + λ) * ∇divu - g = c0 * Symbolics.gradient(p, [t]) - K * Δp + α * divu_dt - - u_eval = build_function(u, x, y, t, expression = Val{false}) - ∇u_eval = build_function(∇u_reshaped, x, y, t, expression = Val{false}) - g_eval = build_function(g, x, y, t, expression = Val{false}) - f_eval = build_function(f, x, y, t, expression = Val{false}) - p_eval = build_function(p, x, y, t, expression = Val{false}) - ∇p_eval = build_function(∇p, x, y, t, expression = Val{false}) - - return f_eval[2], g_eval[2], u_eval[2], ∇u_eval[2], p_eval, ∇p_eval[2] + @variables x y t + + ## displacement and pressure + u = [ + exp(-t) * (sin(2 * pi * y) * (-1 + cos(2 * pi * x)) + sin(pi * x) * sin(pi * y) / (μ + λ)) + exp(-t) * (sin(2 * pi * x) * (1 - cos(2 * pi * y)) + sin(pi * x) * sin(pi * y) / (μ + λ)) + ] + p = exp(-t) * sin(pi * x) * sin(pi * y) + + ## gradient of displacement + ∇u = Symbolics.jacobian(u, [x, y]) + ∇u_reshaped = [∇u[1, 1], ∇u[1, 2], ∇u[2, 1], ∇u[2, 2]] + + ## gradient of pressure + ∇p = [Symbolics.gradient(p, [x])[1], Symbolics.gradient(p, [y])[1]] + + ## Laplacian + Δu = [ + (Symbolics.gradient(∇u[1, 1], [x]) + Symbolics.gradient(∇u[1, 2], [y]))[1], + (Symbolics.gradient(∇u[2, 1], [x]) + Symbolics.gradient(∇u[2, 2], [y]))[1], + ] + Δp = Symbolics.gradient(∇p[1], [x]) + Symbolics.gradient(∇p[2], [y]) + divu = ∇u[1, 1] + ∇u[2, 2] + ∇divu = [Symbolics.gradient(divu, [x])[1], Symbolics.gradient(divu, [y])[1]] + divu_dt = Symbolics.gradient(divu, [t]) + + f = -μ * Δu .+ α * ∇p .- (μ + λ) * ∇divu + g = c0 * Symbolics.gradient(p, [t]) - K * Δp + α * divu_dt + + u_eval = build_function(u, x, y, t, expression = Val{false}) + ∇u_eval = build_function(∇u_reshaped, x, y, t, expression = Val{false}) + g_eval = build_function(g, x, y, t, expression = Val{false}) + f_eval = build_function(f, x, y, t, expression = Val{false}) + p_eval = build_function(p, x, y, t, expression = Val{false}) + ∇p_eval = build_function(∇p, x, y, t, expression = Val{false}) + + return f_eval[2], g_eval[2], u_eval[2], ∇u_eval[2], p_eval, ∇p_eval[2] end function linear_kernel!(result, input, qpinfo) - ∇u, divu, p, w, divw = view(input, 1:4), view(input, 5), view(input, 6), view(input, 7:8), view(input, 9) - μ, λ, α, K = qpinfo.params[1], qpinfo.params[2], qpinfo.params[3], qpinfo.params[4] - result[1] = μ * ∇u[1] + (λ + μ) * divu[1] - p[1] - result[2] = μ * ∇u[2] - result[3] = μ * ∇u[3] - result[4] = μ * ∇u[4] + (λ + μ) * divu[1] - p[1] - result[5] = divu[1] - result[6] = divw[1] - result[7] = w[1] / K - result[8] = w[2] / K - result[9] = -p[1] + ∇u, divu, p, w, divw = view(input, 1:4), view(input, 5), view(input, 6), view(input, 7:8), view(input, 9) + μ, λ, α, K = qpinfo.params[1], qpinfo.params[2], qpinfo.params[3], qpinfo.params[4] + result[1] = μ * ∇u[1] + (λ + μ) * divu[1] - p[1] + result[2] = μ * ∇u[2] + result[3] = μ * ∇u[3] + result[4] = μ * ∇u[4] + (λ + μ) * divu[1] - p[1] + result[5] = divu[1] + result[6] = divw[1] + result[7] = w[1] / K + result[8] = w[2] / K + return result[9] = -p[1] end ## kernel for exact error calculation function exact_error!(u!, ∇u!, p!) - function closure(result, u, qpinfo) - u!(view(result, 1:2), qpinfo) - ∇u!(view(result, 3:6), qpinfo) - p!(view(result, 7), qpinfo) - view(result, 1:7) .-= u - result .= result .^ 2 - end + return function closure(result, u, qpinfo) + u!(view(result, 1:2), qpinfo) + ∇u!(view(result, 3:6), qpinfo) + p!(view(result, 7), qpinfo) + view(result, 1:7) .-= u + return result .= result .^ 2 + end end -function main(; α = 0.93, E = 1e5, ν = 0.4, K = 1e-7, nrefs = 6, T = 0.5, τ = 1e-2, c0 = 1, order = 1, reconstruct = true, Plotter = nothing, kwargs...) - - ## calculate Lame' parameter - μ = E / (2 * (1 + ν)) - λ = E * ν / ((1 - 2 * ν) * (1 + ν)) - - ## initial and exact state for u and p at time t0 - f_eval, g_eval, u_eval, ∇u_eval, p_eval, ∇p_eval = prepare_data!(; μ = μ, λ = λ, K = K, c0 = c0, α = α) - f!(result, qpinfo) = (f_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) - g!(result, qpinfo) = (g_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) - exact_p!(result, qpinfo) = (result[1] = p_eval(qpinfo.x[1], qpinfo.x[2], qpinfo.time)) - exact_∇p!(result, qpinfo) = (∇p_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) - exact_u!(result, qpinfo) = (u_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) - exact_∇u!(result, qpinfo) = (∇u_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) - - ## problem description - PD = ProblemDescription("Heat Equation") - u = Unknown("u"; name = "displacement") - p = Unknown("p"; name = "pressure") - w = Unknown("w"; name = "Darcy velocity") - assign_unknown!(PD, u) - assign_unknown!(PD, p) - assign_unknown!(PD, w) - - ## prepare reconstruction operator - if reconstruct - FES_Reconst = order == 1 ? HDIVBDM1{2} : HDIVBDM2{2} - divu = apply(u, Reconstruct{FES_Reconst, Divergence}) - idu = apply(u, Reconstruct{FES_Reconst, Identity}) - else - divu = div(u) - idu = id(u) - end - - ## linear operator - assign_operator!(PD, BilinearOperator(linear_kernel!, [grad(u), divu, id(p), id(w), div(w)]; params = [μ, λ, α, K], store = true, kwargs...)) - - ## right-hand side data - assign_operator!(PD, LinearOperator(f!, [idu]; kwargs...)) - assign_operator!(PD, LinearOperator(g!, [id(p)]; kwargs...)) - - ## boundary conditions - assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; regions = 1:4)) - assign_operator!(PD, InterpolateBoundaryData(p, exact_p!; regions = 1:4)) - - ## grid - xgrid = uniform_refine(grid_unitsquare(Triangle2D; scale = [4, 4], shift = [-0.5, -0.5]), nrefs) - - ## prepare solution vector - if order == 1 - FES = [FESpace{H1BR{2}}(xgrid), FESpace{L2P0{1}}(xgrid; broken = true), FESpace{HDIVRT0{2}}(xgrid)] - elseif order == 2 - FES = [FESpace{H1P2B{2, 2}}(xgrid), FESpace{H1P1{1}}(xgrid; broken = true), FESpace{HDIVRT1{2}}(xgrid)] - end - sol = FEVector(FES; tags = PD.unknowns) - - ## initial data - interpolate!(sol[u], exact_u!; bonus_quadorder = 5, time = 0) - interpolate!(sol[p], exact_p!; bonus_quadorder = 5, time = 0) - - ## init plotter and plot initial data and grid - plt = GridVisualizer(; Plotter = Plotter, layout = (3, 2), clear = true, size = (800, 1200)) - scalarplot!(plt[1, 1], id(u), sol; abs = true, title = "u_h (t = 0)") - scalarplot!(plt[2, 1], id(p), sol; title = "p_h (t = 0)") - gridplot!(plt[3, 1], xgrid; linewidth = 1) - - ## compute mass matrix - M = FEMatrix(FES) - assemble!(M, BilinearOperator([id(2)]; factor = c0)) - assemble!(M, BilinearOperator([id(2)], [div(1)]; factor = -α)) - - ## add backward Euler time derivative - assign_operator!(PD, BilinearOperator(M, [u, p, w]; factor = 1 / τ, kwargs...)) - assign_operator!(PD, LinearOperator(M, [u, p, w], [u, p, w]; factor = 1 / τ, kwargs...)) - - ## generate solver configuration - SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, verbosity = -1, constant_matrix = true, kwargs...) - - ## iterate tspan - t = 0 - for it ∈ 1:Int(floor(T / τ)) - t += τ - @info "t = $t" - ExtendableFEM.solve(PD, FES, SC; time = t) - end - - ## error calculation - ErrorIntegrator = ItemIntegrator(exact_error!(exact_u!, exact_∇u!, exact_p!), [id(u), grad(u), id(p)]; quadorder = 2 * (order + 1), kwargs...) - error = evaluate(ErrorIntegrator, sol; time = T) - L2errorU = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) - H1errorU = sqrt(sum(view(error, 3, :)) + sum(view(error, 4, :)) + sum(view(error, 5, :)) + sum(view(error, 6, :))) - L2errorP = sqrt(sum(view(error, 7, :))) - @info "|| u - u_h || = $L2errorU +function main(; α = 0.93, E = 1.0e5, ν = 0.4, K = 1.0e-7, nrefs = 6, T = 0.5, τ = 1.0e-2, c0 = 1, order = 1, reconstruct = true, Plotter = nothing, kwargs...) + + ## calculate Lame' parameter + μ = E / (2 * (1 + ν)) + λ = E * ν / ((1 - 2 * ν) * (1 + ν)) + + ## initial and exact state for u and p at time t0 + f_eval, g_eval, u_eval, ∇u_eval, p_eval, ∇p_eval = prepare_data!(; μ = μ, λ = λ, K = K, c0 = c0, α = α) + f!(result, qpinfo) = (f_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) + g!(result, qpinfo) = (g_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) + exact_p!(result, qpinfo) = (result[1] = p_eval(qpinfo.x[1], qpinfo.x[2], qpinfo.time)) + exact_∇p!(result, qpinfo) = (∇p_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) + exact_u!(result, qpinfo) = (u_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) + exact_∇u!(result, qpinfo) = (∇u_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.time)) + + ## problem description + PD = ProblemDescription("Heat Equation") + u = Unknown("u"; name = "displacement") + p = Unknown("p"; name = "pressure") + w = Unknown("w"; name = "Darcy velocity") + assign_unknown!(PD, u) + assign_unknown!(PD, p) + assign_unknown!(PD, w) + + ## prepare reconstruction operator + if reconstruct + FES_Reconst = order == 1 ? HDIVBDM1{2} : HDIVBDM2{2} + divu = apply(u, Reconstruct{FES_Reconst, Divergence}) + idu = apply(u, Reconstruct{FES_Reconst, Identity}) + else + divu = div(u) + idu = id(u) + end + + ## linear operator + assign_operator!(PD, BilinearOperator(linear_kernel!, [grad(u), divu, id(p), id(w), div(w)]; params = [μ, λ, α, K], store = true, kwargs...)) + + ## right-hand side data + assign_operator!(PD, LinearOperator(f!, [idu]; kwargs...)) + assign_operator!(PD, LinearOperator(g!, [id(p)]; kwargs...)) + + ## boundary conditions + assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; regions = 1:4)) + assign_operator!(PD, InterpolateBoundaryData(p, exact_p!; regions = 1:4)) + + ## grid + xgrid = uniform_refine(grid_unitsquare(Triangle2D; scale = [4, 4], shift = [-0.5, -0.5]), nrefs) + + ## prepare solution vector + if order == 1 + FES = [FESpace{H1BR{2}}(xgrid), FESpace{L2P0{1}}(xgrid; broken = true), FESpace{HDIVRT0{2}}(xgrid)] + elseif order == 2 + FES = [FESpace{H1P2B{2, 2}}(xgrid), FESpace{H1P1{1}}(xgrid; broken = true), FESpace{HDIVRT1{2}}(xgrid)] + end + sol = FEVector(FES; tags = PD.unknowns) + + ## initial data + interpolate!(sol[u], exact_u!; bonus_quadorder = 5, time = 0) + interpolate!(sol[p], exact_p!; bonus_quadorder = 5, time = 0) + + ## init plotter and plot initial data and grid + plt = GridVisualizer(; Plotter = Plotter, layout = (3, 2), clear = true, size = (800, 1200)) + scalarplot!(plt[1, 1], id(u), sol; abs = true, title = "u_h (t = 0)") + scalarplot!(plt[2, 1], id(p), sol; title = "p_h (t = 0)") + gridplot!(plt[3, 1], xgrid; linewidth = 1) + + ## compute mass matrix + M = FEMatrix(FES) + assemble!(M, BilinearOperator([id(2)]; factor = c0)) + assemble!(M, BilinearOperator([id(2)], [div(1)]; factor = -α)) + + ## add backward Euler time derivative + assign_operator!(PD, BilinearOperator(M, [u, p, w]; factor = 1 / τ, kwargs...)) + assign_operator!(PD, LinearOperator(M, [u, p, w], [u, p, w]; factor = 1 / τ, kwargs...)) + + ## generate solver configuration + SC = SolverConfiguration(PD, FES; init = sol, maxiterations = 1, verbosity = -1, constant_matrix = true, kwargs...) + + ## iterate tspan + t = 0 + for it in 1:Int(floor(T / τ)) + t += τ + @info "t = $t" + ExtendableFEM.solve(PD, FES, SC; time = t) + end + + ## error calculation + ErrorIntegrator = ItemIntegrator(exact_error!(exact_u!, exact_∇u!, exact_p!), [id(u), grad(u), id(p)]; quadorder = 2 * (order + 1), kwargs...) + error = evaluate(ErrorIntegrator, sol; time = T) + L2errorU = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) + H1errorU = sqrt(sum(view(error, 3, :)) + sum(view(error, 4, :)) + sum(view(error, 5, :)) + sum(view(error, 6, :))) + L2errorP = sqrt(sum(view(error, 7, :))) + @info "|| u - u_h || = $L2errorU || ∇(u - u_h) || = $H1errorU || p - p_h || = $L2errorP" - ## plot final state - scalarplot!(plt[1, 2], id(u), sol; abs = true, title = "u_h (t = $T)") - scalarplot!(plt[2, 2], id(p), sol; title = "p_h (t = $T)") - scalarplot!(plt[3, 2], id(w), sol; abs = true, title = "|w_h| (t = $T)") + ## plot final state + scalarplot!(plt[1, 2], id(u), sol; abs = true, title = "u_h (t = $T)") + scalarplot!(plt[2, 2], id(p), sol; title = "p_h (t = $T)") + scalarplot!(plt[3, 2], id(w), sol; abs = true, title = "|w_h| (t = $T)") - return L2errorU, plt + return L2errorU, plt end generateplots = ExtendableFEM.default_generateplots(Example290_PoroElasticity, "example290.png") #hide function runtests() #hide - L2errorU, plt = main(; nrefs = 4) #hide - @test L2errorU ≈ 0.18232484430836826 #hide + L2errorU, plt = main(; nrefs = 4) #hide + return @test L2errorU ≈ 0.18232484430836826 #hide end #hide end # module diff --git a/examples/Example301_PoissonProblem.jl b/examples/Example301_PoissonProblem.jl index 87e7c8b..0529cb5 100644 --- a/examples/Example301_PoissonProblem.jl +++ b/examples/Example301_PoissonProblem.jl @@ -24,35 +24,35 @@ using ExtendableGrids using Test #hide function f!(fval, qpinfo) - fval[1] = qpinfo.x[1] * qpinfo.x[2] * qpinfo.x[3] + return fval[1] = qpinfo.x[1] * qpinfo.x[2] * qpinfo.x[3] end function main(; μ = 1.0, nrefs = 3, Plotter = nothing, kwargs...) - ## problem description - PD = ProblemDescription() - u = Unknown("u"; name = "potential") - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator([grad(u)]; factor = μ, kwargs...)) - assign_operator!(PD, LinearOperator(f!, [id(u)]; kwargs...)) - assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4)) + ## problem description + PD = ProblemDescription() + u = Unknown("u"; name = "potential") + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator([grad(u)]; factor = μ, kwargs...)) + assign_operator!(PD, LinearOperator(f!, [id(u)]; kwargs...)) + assign_operator!(PD, HomogeneousBoundaryData(u; regions = 1:4)) - ## discretize - xgrid = uniform_refine(grid_unitcube(Tetrahedron3D), nrefs) - FES = FESpace{H1P2{1, 3}}(xgrid) + ## discretize + xgrid = uniform_refine(grid_unitcube(Tetrahedron3D), nrefs) + FES = FESpace{H1P2{1, 3}}(xgrid) - ## solve - sol = solve(PD, FES; kwargs...) + ## solve + sol = solve(PD, FES; kwargs...) - ## plot - plt = plot([id(u)], sol; Plotter = Plotter) + ## plot + plt = plot([id(u)], sol; Plotter = Plotter) - return sol, plt + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example301_PoissonProblem, "example301.png") #hide function runtests() #hide - sol, plt = main(;) #hide - @test sum(sol.entries) ≈ 21.874305144549524 #hide + sol, plt = main() #hide + return @test sum(sol.entries) ≈ 21.874305144549524 #hide end #hide end # module diff --git a/examples/Example310_DivFreeBasis.jl b/examples/Example310_DivFreeBasis.jl index b30f1c8..91daf91 100644 --- a/examples/Example310_DivFreeBasis.jl +++ b/examples/Example310_DivFreeBasis.jl @@ -40,163 +40,164 @@ using Test #hide ## exact data for problem generated by symbolics function prepare_data() - @variables x y z + @variables x y z - ## stream function ξ - ξ = [x*y*z,x*y*z,x*y*z] + ## stream function ξ + ξ = [x * y * z, x * y * z, x * y * z] - ## velocity u = curl ξ - ∇ξ = Symbolics.jacobian(ξ, [x, y, z]) - u = [∇ξ[3,2] - ∇ξ[2,3], ∇ξ[1,3] - ∇ξ[3,1], ∇ξ[2,1] - ∇ξ[1,2]] + ## velocity u = curl ξ + ∇ξ = Symbolics.jacobian(ξ, [x, y, z]) + u = [∇ξ[3, 2] - ∇ξ[2, 3], ∇ξ[1, 3] - ∇ξ[3, 1], ∇ξ[2, 1] - ∇ξ[1, 2]] - ## build function - u_eval = build_function(u, x, y, z, expression = Val{false}) + ## build function + u_eval = build_function(u, x, y, z, expression = Val{false}) - return u_eval[2] + return u_eval[2] end function main(; - nrefs = 4, ## number of refinement levels - bonus_quadorder = 2, ## additional quadrature order for data evaluations - divfree_basis = true, ## if true uses curl(N0), if false uses mixed FEM RT0xP0 - Plotter = nothing, ## Plotter (e.g. PyPlot) - kwargs...) - - ## prepare problem data - u_eval = prepare_data() - exact_u!(result, qpinfo) = (u_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.x[3])) - - ## prepare plots - plt = GridVisualizer(; Plotter = Plotter, layout = (2, 2), clear = true, size = (800, 800)) - - ## prepare error calculation - function exact_error!(result, u, qpinfo) - exact_u!(view(result, 1:3), qpinfo) - result .-= u - result .= result .^ 2 - end - ErrorIntegratorExact = ItemIntegrator(exact_error!, [divfree_basis ? curl3(1) : id(1)]; bonus_quadorder = 2 + bonus_quadorder, kwargs...) - NDofs = zeros(Int, nrefs) - L2error = zeros(Float64, nrefs) - - sol = nothing - for lvl ∈ 1:nrefs - ## grid - xgrid = uniform_refine(grid_unitcube(Tetrahedron3D), lvl) - - if divfree_basis - - ## use Nedelec FESpace and determine linear independent basis - FES = FESpace{HCURLN0{3}}(xgrid) - - @time begin - ## get subset of edges, spanning the node graph - spanning_tree = get_spanning_edge_subset(xgrid) - - ## get all other edges = linear independent degrees of freedom - subset = setdiff(1:num_edges(xgrid), spanning_tree) - end - NDofs[lvl] = length(subset) - - ## assemble full Nedelec curl-curl problem... - u = Unknown("u"; name = "curl potential of velocity") - PD = ProblemDescription("curl-curl formulation") - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator([curl3(u)])) - assign_operator!(PD, LinearOperator(exact_u!, [curl3(u)]; bonus_quadorder = bonus_quadorder)) - - ## ...and solve with subset - sol = solve(PD, FES; restrict_dofs = [subset[:]]) - else - ## use RT0 functions + side constraint for divergence - FES = [FESpace{HDIVRT0{3}}(xgrid), FESpace{L2P0{1}}(xgrid)] - NDofs[lvl] = FES[1].ndofs + FES[2].ndofs - - u = Unknown("u"; name = "velocity") - p = Unknown("u"; name = "pressure") - PD = ProblemDescription("mixed formulation") - assign_unknown!(PD, u) - assign_unknown!(PD, p) - assign_operator!(PD, BilinearOperator([id(u)])) - assign_operator!(PD, BilinearOperator([div(u)], [id(p)]; transposed_copy = 1)) - assign_operator!(PD, LinearOperator(exact_u!, [id(u)]; bonus_quadorder = bonus_quadorder)) - sol = solve(PD, FES) - end - - ## evaluate error - error = evaluate(ErrorIntegratorExact, sol) - L2error[lvl] = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) - if divfree_basis - @info "|| u - curl(ϕ_h) || = $(L2error[lvl])" - else - @info "|| u - u_h || = $(L2error[lvl])" - end - end - - ## plot - if divfree_basis - scalarplot!(plt[1, 1], curl3(1), sol; abs = true) - else - scalarplot!(plt[1, 1], id(1), sol; abs = true) - end - - ## print convergence history as table - print_convergencehistory(NDofs, L2error; X_to_h = X -> X .^ (-1 / 3), ylabels = ["|| u - u_h ||"], xlabel = "ndof") - - return L2error, plt + nrefs = 4, ## number of refinement levels + bonus_quadorder = 2, ## additional quadrature order for data evaluations + divfree_basis = true, ## if true uses curl(N0), if false uses mixed FEM RT0xP0 + Plotter = nothing, ## Plotter (e.g. PyPlot) + kwargs... + ) + + ## prepare problem data + u_eval = prepare_data() + exact_u!(result, qpinfo) = (u_eval(result, qpinfo.x[1], qpinfo.x[2], qpinfo.x[3])) + + ## prepare plots + plt = GridVisualizer(; Plotter = Plotter, layout = (2, 2), clear = true, size = (800, 800)) + + ## prepare error calculation + function exact_error!(result, u, qpinfo) + exact_u!(view(result, 1:3), qpinfo) + result .-= u + return result .= result .^ 2 + end + ErrorIntegratorExact = ItemIntegrator(exact_error!, [divfree_basis ? curl3(1) : id(1)]; bonus_quadorder = 2 + bonus_quadorder, kwargs...) + NDofs = zeros(Int, nrefs) + L2error = zeros(Float64, nrefs) + + sol = nothing + for lvl in 1:nrefs + ## grid + xgrid = uniform_refine(grid_unitcube(Tetrahedron3D), lvl) + + if divfree_basis + + ## use Nedelec FESpace and determine linear independent basis + FES = FESpace{HCURLN0{3}}(xgrid) + + @time begin + ## get subset of edges, spanning the node graph + spanning_tree = get_spanning_edge_subset(xgrid) + + ## get all other edges = linear independent degrees of freedom + subset = setdiff(1:num_edges(xgrid), spanning_tree) + end + NDofs[lvl] = length(subset) + + ## assemble full Nedelec curl-curl problem... + u = Unknown("u"; name = "curl potential of velocity") + PD = ProblemDescription("curl-curl formulation") + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator([curl3(u)])) + assign_operator!(PD, LinearOperator(exact_u!, [curl3(u)]; bonus_quadorder = bonus_quadorder)) + + ## ...and solve with subset + sol = solve(PD, FES; restrict_dofs = [subset[:]]) + else + ## use RT0 functions + side constraint for divergence + FES = [FESpace{HDIVRT0{3}}(xgrid), FESpace{L2P0{1}}(xgrid)] + NDofs[lvl] = FES[1].ndofs + FES[2].ndofs + + u = Unknown("u"; name = "velocity") + p = Unknown("u"; name = "pressure") + PD = ProblemDescription("mixed formulation") + assign_unknown!(PD, u) + assign_unknown!(PD, p) + assign_operator!(PD, BilinearOperator([id(u)])) + assign_operator!(PD, BilinearOperator([div(u)], [id(p)]; transposed_copy = 1)) + assign_operator!(PD, LinearOperator(exact_u!, [id(u)]; bonus_quadorder = bonus_quadorder)) + sol = solve(PD, FES) + end + + ## evaluate error + error = evaluate(ErrorIntegratorExact, sol) + L2error[lvl] = sqrt(sum(view(error, 1, :)) + sum(view(error, 2, :))) + if divfree_basis + @info "|| u - curl(ϕ_h) || = $(L2error[lvl])" + else + @info "|| u - u_h || = $(L2error[lvl])" + end + end + + ## plot + if divfree_basis + scalarplot!(plt[1, 1], curl3(1), sol; abs = true) + else + scalarplot!(plt[1, 1], id(1), sol; abs = true) + end + + ## print convergence history as table + print_convergencehistory(NDofs, L2error; X_to_h = X -> X .^ (-1 / 3), ylabels = ["|| u - u_h ||"], xlabel = "ndof") + + return L2error, plt end ## finds a minimal subset (of dimension #nodes - 1) of edges, such that all nodes are connected function get_spanning_edge_subset(xgrid) - nnodes = num_nodes(xgrid) - edgenodes = xgrid[EdgeNodes] - bedgenodes = xgrid[BEdgeNodes] - bedgeedges = xgrid[BEdgeEdges] - - ## boolean arrays to memorize which nodes are visited - ## and which edges belong to the spanning tree - visited = zeros(Bool, nnodes) - markededges = zeros(Bool, num_edges(xgrid)) - - function find_spanning_tree(edgenodes, remap) - nodeedges = atranspose(edgenodes) - function recursive(node) - visited[node] = true - nneighbors = num_targets(nodeedges, node) - for e = 1 : nneighbors - edge = nodeedges[e, node] - for k = 1 : 2 - node2 = edgenodes[k, edge] - if !visited[node2] - ## mark edge - markededges[remap[edge]] = true - recursive(node2) - end - end - end - return nothing - end - recursive(edgenodes[1]) - end - - ## find spanning tree for Neumann boundary - ## local bedges >> global edge numbers - find_spanning_tree(bedgenodes, bedgeedges) - - ## find spanning tree for remaining part - other_nodes = setdiff(1:nnodes, unique(view(bedgenodes,:))) - if length(other_nodes) > 0 - find_spanning_tree(edgenodes, 1 : num_edges(xgrid)) - end - - ## return all marked edges - return findall(==(true), markededges) + nnodes = num_nodes(xgrid) + edgenodes = xgrid[EdgeNodes] + bedgenodes = xgrid[BEdgeNodes] + bedgeedges = xgrid[BEdgeEdges] + + ## boolean arrays to memorize which nodes are visited + ## and which edges belong to the spanning tree + visited = zeros(Bool, nnodes) + markededges = zeros(Bool, num_edges(xgrid)) + + function find_spanning_tree(edgenodes, remap) + nodeedges = atranspose(edgenodes) + function recursive(node) + visited[node] = true + nneighbors = num_targets(nodeedges, node) + for e in 1:nneighbors + edge = nodeedges[e, node] + for k in 1:2 + node2 = edgenodes[k, edge] + if !visited[node2] + ## mark edge + markededges[remap[edge]] = true + recursive(node2) + end + end + end + return nothing + end + return recursive(edgenodes[1]) + end + + ## find spanning tree for Neumann boundary + ## local bedges >> global edge numbers + find_spanning_tree(bedgenodes, bedgeedges) + + ## find spanning tree for remaining part + other_nodes = setdiff(1:nnodes, unique(view(bedgenodes, :))) + if length(other_nodes) > 0 + find_spanning_tree(edgenodes, 1:num_edges(xgrid)) + end + + ## return all marked edges + return findall(==(true), markededges) end generateplots = ExtendableFEM.default_generateplots(Example310_DivFreeBasis, "example310.png") #hide function runtests() #hide - L2error, plt = main(; nrefs = 2) #hide - @test L2error[2] ≈ 0.06821145277709957 #hide + L2error, plt = main(; nrefs = 2) #hide + return @test L2error[2] ≈ 0.06821145277709957 #hide end #hide end # module diff --git a/examples/Example330_HyperElasticity.jl b/examples/Example330_HyperElasticity.jl index 218e85f..d2b441a 100644 --- a/examples/Example330_HyperElasticity.jl +++ b/examples/Example330_HyperElasticity.jl @@ -34,15 +34,15 @@ using TetGen ## inhomogeneous boundary conditions for bregion 1 function bnd_1!(result, qpinfo) x, y, z = qpinfo.x[1], qpinfo.x[2], qpinfo.x[3] - angle = pi/3 + angle = pi / 3 result[1] = 0.0 - result[2] = (0.5+(y-0.5)*cos(angle) - (z-0.5)*sin(angle)-y)/2.0 - result[3] = (0.5+(y-0.5)*sin(angle) + (z-0.5)*cos(angle)-x)/2.0 + result[2] = (0.5 + (y - 0.5) * cos(angle) - (z - 0.5) * sin(angle) - y) / 2.0 + return result[3] = (0.5 + (y - 0.5) * sin(angle) + (z - 0.5) * cos(angle) - x) / 2.0 end ## kernel for body and traction forces function apply_force!(result, qpinfo) - result .= qpinfo.params[1] + return result .= qpinfo.params[1] end ## energy functional (only nonlinear part, without exterior forces) @@ -51,47 +51,48 @@ function W!(result, F, qpinfo) F[5] += 1 F[9] += 1 μ, λ = qpinfo.params[1], qpinfo.params[2] - detF = -(F[3]*(F[5]*F[7] - F[4]*F[8]) + F[2]*((-F[6])*F[7] + F[4]*F[9]) + F[1]*(F[6]*F[8] - F[5]*F[9])) - result[1] = μ/2 * (dot(F,F) - 3 - 2*log(detF)) + λ/2 * (log(detF))^2 + detF = -(F[3] * (F[5] * F[7] - F[4] * F[8]) + F[2] * ((-F[6]) * F[7] + F[4] * F[9]) + F[1] * (F[6] * F[8] - F[5] * F[9])) + return result[1] = μ / 2 * (dot(F, F) - 3 - 2 * log(detF)) + λ / 2 * (log(detF))^2 end ## derivative of energy functional (by ForwardDiff) function nonlinkernel_DW!() - Dresult = nothing - cfg = nothing - result_dummy = nothing - W(qpinfo) = (a,b) -> W!(a,b,qpinfo) - - function closure(result, input, qpinfo) - if Dresult === nothing - ## first initialization of DResult when type of input = F is known - result_dummy = zeros(eltype(input), 1) - Dresult = DiffResults.JacobianResult(result_dummy, input) - cfg = ForwardDiff.JacobianConfig(W(qpinfo), result_dummy, input, ForwardDiff.Chunk{length(input)}()) - end - Dresult = ForwardDiff.vector_mode_jacobian!(Dresult, W(qpinfo), result_dummy, input, cfg) - copyto!(result, DiffResults.jacobian(Dresult)) + Dresult = nothing + cfg = nothing + result_dummy = nothing + W(qpinfo) = (a, b) -> W!(a, b, qpinfo) + + return function closure(result, input, qpinfo) + if Dresult === nothing + ## first initialization of DResult when type of input = F is known + result_dummy = zeros(eltype(input), 1) + Dresult = DiffResults.JacobianResult(result_dummy, input) + cfg = ForwardDiff.JacobianConfig(W(qpinfo), result_dummy, input, ForwardDiff.Chunk{length(input)}()) + end + Dresult = ForwardDiff.vector_mode_jacobian!(Dresult, W(qpinfo), result_dummy, input, cfg) + copyto!(result, DiffResults.jacobian(Dresult)) return nothing - end + end end -function main(; - maxvolume = 0.001, # parameter for grid generator - E = 10, # Young modulus - ν = 0.3, # Poisson ratio - order = 3, # finite element order - B = [0,-0.5,0], # body force - T = [0.1,0,0], # traction force - Plotter = nothing, - kwargs...) +function main(; + maxvolume = 0.001, # parameter for grid generator + E = 10, # Young modulus + ν = 0.3, # Poisson ratio + order = 3, # finite element order + B = [0, -0.5, 0], # body force + T = [0.1, 0, 0], # traction force + Plotter = nothing, + kwargs... + ) ## compute Lame parameters - μ = E / (2 * (1 + ν)) - λ = E * ν / ((1+ν)*(1 - 2 * ν)) + μ = E / (2 * (1 + ν)) + λ = E * ν / ((1 + ν) * (1 - 2 * ν)) - ## define unknowns - u = Unknown("u"; name = "displacement") + ## define unknowns + u = Unknown("u"; name = "displacement") ## define problem PD = ProblemDescription("Hyperelasticity problem") @@ -111,9 +112,9 @@ function main(; ## displace mesh and plot final result displace_mesh!(xgrid, sol[u]) - plt = plot([grid(u), id(u)], sol; Plotter = Plotter, do_vector_plots = false) + plt = plot([grid(u), id(u)], sol; Plotter = Plotter, do_vector_plots = false) - return sol, plt + return sol, plt end generateplots = ExtendableFEM.default_generateplots(Example330_HyperElasticity, "example330.png") #hide @@ -143,7 +144,7 @@ function tetrahedralization_of_cube(; maxvolume = 0.1) facetregion!(builder, 6) facet!(builder, p4, p1, p5, p8) - simplexgrid(builder; maxvolume = maxvolume) + return simplexgrid(builder; maxvolume = maxvolume) end end # module diff --git a/src/ExtendableFEM.jl b/src/ExtendableFEM.jl index cfaf427..ec4d347 100644 --- a/src/ExtendableFEM.jl +++ b/src/ExtendableFEM.jl @@ -1,7 +1,7 @@ """ ExtendableFEM -$(read(joinpath(@__DIR__,"..","README.md"),String)) +$(read(joinpath(@__DIR__, "..", "README.md"), String)) """ module ExtendableFEM @@ -9,73 +9,73 @@ using CommonSolve: CommonSolve using DiffResults: DiffResults using DocStringExtensions: DocStringExtensions, TYPEDFIELDS using ExtendableFEMBase: ExtendableFEMBase, AbstractFiniteElement, - AbstractFunctionOperator, AbstractH1FiniteElement, - AbstractHdivFiniteElement, BEdgeDofs, BFaceDofs, - CellDofs, Curl2D, Curl3D, CurlScalar, - DefaultName4Operator, Divergence, Dofmap4AssemblyType, - EdgeDofs, EffAT4AssemblyType, FEEvaluator, FEMatrix, - FEMatrixBlock, FESpace, FEVector, FEVectorBlock, - FaceDofs, Gradient, H1BR, H1BUBBLE, H1CR, H1MINI, - H1P1, H1P1TEB, H1P2, H1P2B, H1P3, H1Pk, H1Q1, H1Q2, - HCURLN0, HCURLN1, HDIVBDM1, HDIVBDM2, HDIVRT0, - HDIVRT1, HDIVRTk, HDIVRTkENRICH, Hessian, Identity, - L2P0, L2P1, Laplacian, Length4Operator, - NeededDerivative4Operator, NormalFlux, - ParentDofmap4Dofmap, PointEvaluator, QPInfos, - QuadratureRule, Reconstruct, SegmentIntegrator, - StandardFunctionOperator, TangentFlux, - TangentialGradient, VertexRule, _addnz, add!, - addblock!, addblock_matmul!, displace_mesh, - displace_mesh!, eval_func, eval_func_bary, evaluate!, - evaluate_bary!, fill!, get_AT, get_FEType, - get_ncomponents, get_ndofs, get_polynomialorder, - initialize!, integrate, integrate!, - integrate_segment!, lazy_interpolate!, nodevalues, - nodevalues!, nodevalues_subset!, nodevalues_view, - norms, unicode_gridplot, unicode_scalarplot, - update_basis! + AbstractFunctionOperator, AbstractH1FiniteElement, + AbstractHdivFiniteElement, BEdgeDofs, BFaceDofs, + CellDofs, Curl2D, Curl3D, CurlScalar, + DefaultName4Operator, Divergence, Dofmap4AssemblyType, + EdgeDofs, EffAT4AssemblyType, FEEvaluator, FEMatrix, + FEMatrixBlock, FESpace, FEVector, FEVectorBlock, + FaceDofs, Gradient, H1BR, H1BUBBLE, H1CR, H1MINI, + H1P1, H1P1TEB, H1P2, H1P2B, H1P3, H1Pk, H1Q1, H1Q2, + HCURLN0, HCURLN1, HDIVBDM1, HDIVBDM2, HDIVRT0, + HDIVRT1, HDIVRTk, HDIVRTkENRICH, Hessian, Identity, + L2P0, L2P1, Laplacian, Length4Operator, + NeededDerivative4Operator, NormalFlux, + ParentDofmap4Dofmap, PointEvaluator, QPInfos, + QuadratureRule, Reconstruct, SegmentIntegrator, + StandardFunctionOperator, TangentFlux, + TangentialGradient, VertexRule, _addnz, add!, + addblock!, addblock_matmul!, displace_mesh, + displace_mesh!, eval_func, eval_func_bary, evaluate!, + evaluate_bary!, fill!, get_AT, get_FEType, + get_ncomponents, get_ndofs, get_polynomialorder, + initialize!, integrate, integrate!, + integrate_segment!, lazy_interpolate!, nodevalues, + nodevalues!, nodevalues_subset!, nodevalues_view, + norms, unicode_gridplot, unicode_scalarplot, + update_basis! using ExtendableGrids: ExtendableGrids, AT_NODES, AbstractElementGeometry, - Adjacency, AssemblyType, BEdgeNodes, BFaceFaces, - BFaceNodes, BFaceRegions, CellAssemblyGroups, - CellFaceOrientations, CellFaces, CellGeometries, - CellNodes, CellRegions, Coordinates, EdgeNodes, - ElementGeometries, ExtendableGrid, FaceCells, FaceEdges, - FaceNodes, FaceNormals, FaceRegions, FaceVolumes, - PColorPartitions, PartitionCells, PartitionEdges, - GridComponentAssemblyGroups4AssemblyType, - GridComponentGeometries4AssemblyType, - GridComponentRegions4AssemblyType, - GridComponentVolumes4AssemblyType, L2GTransformer, - ON_BEDGES, ON_BFACES, ON_CELLS, ON_EDGES, ON_FACES, - ON_IFACES, SerialVariableTargetAdjacency, - UniqueBFaceGeometries, UniqueCellGeometries, - UniqueFaceGeometries, append!, dim_element, eval_trafo!, - facetype_of_cellface, interpolate!, - max_num_targets_per_source, num_cells, num_faces, - num_nodes, num_sources, num_targets, simplexgrid, - num_pcolors, num_partitions, num_partitions_per_color, - unique, update_trafo!, xrefFACE2xrefCELL, - xrefFACE2xrefOFACE + Adjacency, AssemblyType, BEdgeNodes, BFaceFaces, + BFaceNodes, BFaceRegions, CellAssemblyGroups, + CellFaceOrientations, CellFaces, CellGeometries, + CellNodes, CellRegions, Coordinates, EdgeNodes, + ElementGeometries, ExtendableGrid, FaceCells, FaceEdges, + FaceNodes, FaceNormals, FaceRegions, FaceVolumes, + PColorPartitions, PartitionCells, PartitionEdges, + GridComponentAssemblyGroups4AssemblyType, + GridComponentGeometries4AssemblyType, + GridComponentRegions4AssemblyType, + GridComponentVolumes4AssemblyType, L2GTransformer, + ON_BEDGES, ON_BFACES, ON_CELLS, ON_EDGES, ON_FACES, + ON_IFACES, SerialVariableTargetAdjacency, + UniqueBFaceGeometries, UniqueCellGeometries, + UniqueFaceGeometries, append!, dim_element, eval_trafo!, + facetype_of_cellface, interpolate!, + max_num_targets_per_source, num_cells, num_faces, + num_nodes, num_sources, num_targets, simplexgrid, + num_pcolors, num_partitions, num_partitions_per_color, + unique, update_trafo!, xrefFACE2xrefCELL, + xrefFACE2xrefOFACE using ExtendableSparse: ExtendableSparse, ExtendableSparseMatrix, flush!, - MTExtendableSparseMatrixCSC, - rawupdateindex! + MTExtendableSparseMatrixCSC, + rawupdateindex! using ForwardDiff: ForwardDiff using GridVisualize: GridVisualize, GridVisualizer, gridplot!, reveal, save, - scalarplot!, vectorplot! + scalarplot!, vectorplot! using LinearAlgebra: LinearAlgebra, copyto!, isposdef, mul!, norm using LinearSolve: LinearSolve, LinearProblem, UMFPACKFactorization, deleteat!, - init, solve + init, solve using Printf: Printf, @printf using SparseArrays: SparseArrays, AbstractSparseArray, SparseMatrixCSC, nnz, - nzrange, rowvals, sparse + nzrange, rowvals, sparse using SparseDiffTools: SparseDiffTools, ForwardColorJacCache, - forwarddiff_color_jacobian!, matrix_colors + forwarddiff_color_jacobian!, matrix_colors using Symbolics: Symbolics using SciMLBase: SciMLBase using UnicodePlots: UnicodePlots if !isdefined(Base, :get_extension) - using Requires + using Requires end ## reexport stuff from ExtendableFEMBase and ExtendableGrids diff --git a/src/common_operators/bilinear_operator.jl b/src/common_operators/bilinear_operator.jl index b6b8505..7ea91bf 100644 --- a/src/common_operators/bilinear_operator.jl +++ b/src/common_operators/bilinear_operator.jl @@ -1,76 +1,75 @@ - mutable struct BilinearOperatorFromMatrix{UT <: Union{Unknown, Integer}, MT} <: AbstractOperator - u_test::Array{UT, 1} - u_ansatz::Array{UT, 1} - u_args::Array{UT, 1} - A::MT - parameters::Dict{Symbol, Any} + u_test::Array{UT, 1} + u_ansatz::Array{UT, 1} + u_args::Array{UT, 1} + A::MT + parameters::Dict{Symbol, Any} end # informs solver when operator needs reassembly function depends_nonlinearly_on(O::BilinearOperatorFromMatrix) - return O.u_args + return O.u_args end # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::BilinearOperatorFromMatrix) - return [O.u_ansatz, O.u_test] + return [O.u_ansatz, O.u_test] end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::BilinearOperatorFromMatrix) - return O.parameters[:time_dependent] + return O.parameters[:time_dependent] end function Base.show(io::IO, O::BilinearOperatorFromMatrix) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($([ansatz_function(dependencies[1][j]) for j = 1 : length(dependencies[1])]), $([test_function(dependencies[2][j]) for j = 1 : length(dependencies[2])]))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($([ansatz_function(dependencies[1][j]) for j in 1:length(dependencies[1])]), $([test_function(dependencies[2][j]) for j in 1:length(dependencies[2])]))") + return nothing end mutable struct BilinearOperator{Tv <: Real, UT <: Union{Unknown, Integer}, KFT, MT} <: AbstractOperator - u_test::Array{UT, 1} - ops_test::Array{DataType, 1} - u_ansatz::Array{UT, 1} - ops_ansatz::Array{DataType, 1} - u_args::Array{UT, 1} - ops_args::Array{DataType, 1} - kernel::KFT - BE_test_vals::Array{Array{Array{Tv, 3}, 1}} - BE_ansatz_vals::Array{Array{Array{Tv, 3}, 1}} - BE_args_vals::Array{Array{Array{Tv, 3}, 1}} - FES_test::Any #::Array{FESpace,1} - FES_ansatz::Any #::Array{FESpace,1} - FES_args::Any #::Array{FESpace,1} - BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} - BE_ansatz::Any #::Union{Nothing, Array{FEEvaluator,1}} - BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} - QP_infos::Any #::Array{QPInfosT,1} - L2G::Any - QF::Any - assembler::Any - storage::MT - parameters::Dict{Symbol, Any} + u_test::Array{UT, 1} + ops_test::Array{DataType, 1} + u_ansatz::Array{UT, 1} + ops_ansatz::Array{DataType, 1} + u_args::Array{UT, 1} + ops_args::Array{DataType, 1} + kernel::KFT + BE_test_vals::Array{Array{Array{Tv, 3}, 1}} + BE_ansatz_vals::Array{Array{Array{Tv, 3}, 1}} + BE_args_vals::Array{Array{Array{Tv, 3}, 1}} + FES_test::Any #::Array{FESpace,1} + FES_ansatz::Any #::Array{FESpace,1} + FES_args::Any #::Array{FESpace,1} + BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} + BE_ansatz::Any #::Union{Nothing, Array{FEEvaluator,1}} + BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} + QP_infos::Any #::Array{QPInfosT,1} + L2G::Any + QF::Any + assembler::Any + storage::MT + parameters::Dict{Symbol, Any} end default_blfop_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), - :name => ("BilinearOperator", "name for operator used in printouts"), - :transposed_copy => (0, "assemble a transposed copy of that operator into the transposed matrix block(s), 0 = no, 1 = symmetric, -1 = skew-symmetric"), - :factor => (1, "factor that should be multiplied during assembly"), - :lump => (0, "diagonal lumping (=0 no lumping, =1 only keep diagonal entry, =2 accumulate full row to diagonal)"), - :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), - :entry_tolerance => (0, "threshold to add entry to sparse matrix"), - :use_sparsity_pattern => ("auto", "read sparsity pattern of jacobian of kernel to find out which components couple"), - :parallel_groups => (false, "assemble operator in parallel using CellAssemblyGroups (assembles separated matrices that are added together sequantially)"), - :parallel => (false, "assemble operator in parallel using colors/partitions information (assembles into full matrix directly)"), - :time_dependent => (false, "operator is time-dependent ?"), - :store => (false, "store matrix separately (and copy from there when reassembly is triggered)"), - :quadorder => ("auto", "quadrature order"), - :bonus_quadorder => (0, "additional quadrature order added to quadorder"), - :verbosity => (0, "verbosity level"), - :regions => ([], "subset of regions where operator should be assembly only"), + :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), + :name => ("BilinearOperator", "name for operator used in printouts"), + :transposed_copy => (0, "assemble a transposed copy of that operator into the transposed matrix block(s), 0 = no, 1 = symmetric, -1 = skew-symmetric"), + :factor => (1, "factor that should be multiplied during assembly"), + :lump => (0, "diagonal lumping (=0 no lumping, =1 only keep diagonal entry, =2 accumulate full row to diagonal)"), + :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), + :entry_tolerance => (0, "threshold to add entry to sparse matrix"), + :use_sparsity_pattern => ("auto", "read sparsity pattern of jacobian of kernel to find out which components couple"), + :parallel_groups => (false, "assemble operator in parallel using CellAssemblyGroups (assembles separated matrices that are added together sequantially)"), + :parallel => (false, "assemble operator in parallel using colors/partitions information (assembles into full matrix directly)"), + :time_dependent => (false, "operator is time-dependent ?"), + :store => (false, "store matrix separately (and copy from there when reassembly is triggered)"), + :quadorder => ("auto", "quadrature order"), + :bonus_quadorder => (0, "additional quadrature order added to quadorder"), + :verbosity => (0, "verbosity level"), + :regions => ([], "subset of regions where operator should be assembly only"), ) getFEStest(FVB::FEVectorBlock) = FVB.FES @@ -80,23 +79,23 @@ getFESansatz(FMB::FEMatrixBlock) = FMB.FESY # informs solver when operator needs reassembly function depends_nonlinearly_on(O::BilinearOperator) - return unique(O.u_args) + return unique(O.u_args) end # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::BilinearOperator) - return [unique(O.u_ansatz), unique(O.u_test)] + return [unique(O.u_ansatz), unique(O.u_test)] end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::BilinearOperator) - return O.parameters[:time_dependent] + return O.parameters[:time_dependent] end function Base.show(io::IO, O::BilinearOperator) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($([ansatz_function(dependencies[1][j]) for j = 1 : length(dependencies[1])]), $([test_function(dependencies[2][j]) for j = 1 : length(dependencies[2])]); entities = $(O.parameters[:entities]))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($([ansatz_function(dependencies[1][j]) for j in 1:length(dependencies[1])]), $([test_function(dependencies[2][j]) for j in 1:length(dependencies[2])]); entities = $(O.parameters[:entities]))") + return nothing end @@ -115,99 +114,98 @@ specify where to put the (blocks of the) matrix in the system. """ function BilinearOperator(A::AbstractMatrix, u_test::Vector{<:Union{Unknown, Int}}, u_ansatz = u_test, u_args = []; kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfop_kwargs()) - _update_params!(parameters, kwargs) - return BilinearOperatorFromMatrix{typeof(u_test[1]), typeof(A)}(u_test, u_ansatz, u_args, A, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfop_kwargs()) + _update_params!(parameters, kwargs) + return BilinearOperatorFromMatrix{typeof(u_test[1]), typeof(A)}(u_test, u_ansatz, u_args, A, parameters) end function BilinearOperator(kernel, u_test, ops_test, u_ansatz = u_test, ops_ansatz = ops_test; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfop_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_ansatz) == length(ops_ansatz) - @assert length(u_test) == length(ops_test) - if parameters[:store] - if parameters[:parallel] - storage = MTExtendableSparseMatrixCSC{Float64, Int}(0, 0, 1) - else - storage = ExtendableSparseMatrix{Float64, Int}(0, 0) - end - else - storage = nothing - end - return BilinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( - u_test, - ops_test, - u_ansatz, - ops_ansatz, - [], - [], - kernel, - [[zeros(Tv, 0, 0, 0)]], - [[zeros(Tv, 0, 0, 0)]], - [[zeros(Tv, 0, 0, 0)]], - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - storage, - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfop_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_ansatz) == length(ops_ansatz) + @assert length(u_test) == length(ops_test) + if parameters[:store] + if parameters[:parallel] + storage = MTExtendableSparseMatrixCSC{Float64, Int}(0, 0, 1) + else + storage = ExtendableSparseMatrix{Float64, Int}(0, 0) + end + else + storage = nothing + end + return BilinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( + u_test, + ops_test, + u_ansatz, + ops_ansatz, + [], + [], + kernel, + [[zeros(Tv, 0, 0, 0)]], + [[zeros(Tv, 0, 0, 0)]], + [[zeros(Tv, 0, 0, 0)]], + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + storage, + parameters, + ) end function BilinearOperator(kernel, u_test, ops_test, u_ansatz, ops_ansatz, u_args, ops_args; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfop_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_args) == length(ops_args) - @assert length(u_ansatz) == length(ops_ansatz) - @assert length(u_test) == length(ops_test) - if parameters[:store] - storage = ExtendableSparseMatrix{Float64, Int}(0, 0) - else - storage = nothing - end - return BilinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( - u_test, - ops_test, - u_ansatz, - ops_ansatz, - u_args, - ops_args, - kernel, - [[zeros(Tv, 0, 0, 0)]], - [[zeros(Tv, 0, 0, 0)]], - [[zeros(Tv, 0, 0, 0)]], - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - storage, - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfop_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_args) == length(ops_args) + @assert length(u_ansatz) == length(ops_ansatz) + @assert length(u_test) == length(ops_test) + if parameters[:store] + storage = ExtendableSparseMatrix{Float64, Int}(0, 0) + else + storage = nothing + end + return BilinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( + u_test, + ops_test, + u_ansatz, + ops_ansatz, + u_args, + ops_args, + kernel, + [[zeros(Tv, 0, 0, 0)]], + [[zeros(Tv, 0, 0, 0)]], + [[zeros(Tv, 0, 0, 0)]], + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + storage, + parameters, + ) end function BilinearOperator(kernel, oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_ansatz::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1} = oa_test; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_ansatz = [oa[1] for oa in oa_ansatz] - ops_test = [oa[2] for oa in oa_test] - ops_ansatz = [oa[2] for oa in oa_ansatz] - return BilinearOperator(kernel, u_test, ops_test, u_ansatz, ops_ansatz; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_ansatz = [oa[1] for oa in oa_ansatz] + ops_test = [oa[2] for oa in oa_test] + ops_ansatz = [oa[2] for oa in oa_ansatz] + return BilinearOperator(kernel, u_test, ops_test, u_ansatz, ops_ansatz; kwargs...) end - """ ```` function BilinearOperator( @@ -239,11 +237,11 @@ $(_myprint(default_blfop_kwargs())) """ function BilinearOperator(oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_ansatz::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1} = oa_test; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_ansatz = [oa[1] for oa in oa_ansatz] - ops_test = [oa[2] for oa in oa_test] - ops_ansatz = [oa[2] for oa in oa_ansatz] - return BilinearOperator(ExtendableFEMBase.standard_kernel, u_test, ops_test, u_ansatz, ops_ansatz; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_ansatz = [oa[1] for oa in oa_ansatz] + ops_test = [oa[2] for oa in oa_test] + ops_ansatz = [oa[2] for oa in oa_ansatz] + return BilinearOperator(ExtendableFEMBase.standard_kernel, u_test, ops_test, u_ansatz, ops_ansatz; kwargs...) end @@ -279,794 +277,794 @@ $(_myprint(default_blfop_kwargs())) """ function BilinearOperator(kernel, oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_ansatz::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_ansatz = [oa[1] for oa in oa_ansatz] - u_args = [oa[1] for oa in oa_args] - ops_test = [oa[2] for oa in oa_test] - ops_ansatz = [oa[2] for oa in oa_ansatz] - ops_args = [oa[2] for oa in oa_args] - return BilinearOperator(kernel, u_test, ops_test, u_ansatz, ops_ansatz, u_args, ops_args; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_ansatz = [oa[1] for oa in oa_ansatz] + u_args = [oa[1] for oa in oa_args] + ops_test = [oa[2] for oa in oa_test] + ops_ansatz = [oa[2] for oa in oa_ansatz] + ops_args = [oa[2] for oa in oa_args] + return BilinearOperator(kernel, u_test, ops_test, u_ansatz, ops_ansatz, u_args, ops_args; kwargs...) end function build_assembler!(A::AbstractMatrix, O::BilinearOperator{Tv}, FE_test, FE_ansatz, FE_args::Array{<:FEVectorBlock, 1}; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_test = [getFEStest(FE_test[j]) for j ∈ 1:length(FE_test)] - FES_ansatz = [getFESansatz(FE_ansatz[j]) for j ∈ 1:length(FE_ansatz)] - FES_args = [FE_args[j].FES for j ∈ 1:length(FE_args)] - if (O.FES_test != FES_test) || (O.FES_args != FES_args) - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_test, FES_ansatz) - - ## prepare assembly - AT = O.parameters[:entities] - Ti = typeof(xgrid).parameters[2] - if xgrid == FES_test[1].dofgrid - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) - else - gridAT = AT - end - - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pc = xgrid[PartitionCells] - itemassemblygroups = [pc[j]:pc[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - # assuming here that all cells of one partition have the same geometry - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - has_normals = true - if AT <: ON_FACES - itemnormals = xgrid[FaceNormals] - elseif AT <: ON_BFACES - itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] - else - has_normals = false - end - FETypes_test = [eltype(F) for F in FES_test] - FETypes_ansatz = [eltype(F) for F in FES_ansatz] - FETypes_args = [eltype(F) for F in FES_args] - EGs = [itemgeometries[itemassemblygroups[j][1]] for j ∈ 1:length(itemassemblygroups)] - - ## prepare assembly - nargs = length(FES_args) - ntest = length(FES_test) - nansatz = length(FES_ansatz) - O.QF = [] - O.BE_test = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.BE_ansatz = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.BE_args = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.BE_ansatz_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_ansatz = maximum([get_polynomialorder(FETypes_ansatz[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_ansatz[j]) for j ∈ 1:nansatz]) - polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j ∈ 1:ntest]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_ansatz + polyorder_test + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) - - ## L2G map for EG - push!(O.L2G, L2GTransformer(EG, xgrid, gridAT)) - - ## FE basis evaluator for EG - push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:ntest]) - push!(O.BE_ansatz, [FEEvaluator(FES_ansatz[j], O.ops_ansatz[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:nansatz]) - push!(O.BE_args, [FEEvaluator(FES_args[j], O.ops_args[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:nargs]) - push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) - push!(O.BE_ansatz_vals, [BE.cvals for BE in O.BE_ansatz[end]]) - push!(O.BE_args_vals, [BE.cvals for BE in O.BE_args[end]]) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[O.regions] = true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j ∈ 1:ntest] - op_lengths_ansatz = [size(O.BE_ansatz[1][j].cvals, 1) for j ∈ 1:nansatz] - op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j ∈ 1:nargs] - - op_offsets_test = [0] - op_offsets_ansatz = [0] - op_offsets_args = [0] - append!(op_offsets_test, cumsum(op_lengths_test)) - append!(op_offsets_ansatz, cumsum(op_lengths_ansatz)) - append!(op_offsets_args, cumsum(op_lengths_args)) - offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] - offsets_ansatz = [FE_ansatz[j].offsetY for j in 1:length(FES_ansatz)] - - ## prepare sparsity pattern - use_sparsity_pattern = O.parameters[:use_sparsity_pattern] - if use_sparsity_pattern == "auto" - use_sparsity_pattern = false - end - coupling_matrix::Matrix{Bool} = ones(Bool, nansatz, ntest) - if use_sparsity_pattern - kernel_params = (result, input) -> (O.kernel(result, input, O.QP_infos[1])) - sparsity_pattern = SparseMatrixCSC{Float64, Int}(Symbolics.jacobian_sparsity(kernel_params, zeros(Tv, op_offsets_test[end]), zeros(Tv, op_offsets_ansatz[end]))) - - ## find out which test and ansatz functions couple - for id ∈ 1:nansatz - for idt ∈ 1:ntest - couple = false - for j ∈ 1:op_lengths_ansatz[id] - for k ∈ 1:op_lengths_test[idt] - if sparsity_pattern[k+op_offsets_test[idt], j+op_offsets_ansatz[id]] > 0 - couple = true - end - end - end - coupling_matrix[id, idt] = couple - end - end - end - couples_with::Vector{Vector{Int}} = [findall(==(true), view(coupling_matrix, j, :)) for j ∈ 1:nansatz] - - ## prepare parallel assembly_allocations - if O.parameters[:parallel_groups] - Aj = Array{typeof(A), 1}(undef, length(EGs)) - for j ∈ 1:length(EGs) - Aj[j] = copy(A) - end - end - - FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j ∈ 1:ntest] - FEATs_ansatz = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_ansatz[j]), AT) for j ∈ 1:nansatz] - FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j ∈ 1:nargs] - itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j ∈ 1:ntest] - itemdofs_ansatz::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_ansatz[j], xgrid, FEATs_ansatz[j]) for j ∈ 1:nansatz] - itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j ∈ 1:nargs] - factor = O.parameters[:factor] - transposed_copy = O.parameters[:transposed_copy] - entry_tol = O.parameters[:entry_tolerance] - lump = O.parameters[:lump] - - ## Assembly loop for fixed geometry - function assembly_loop( - A::AbstractSparseArray{T}, - sol::Array{<:FEVectorBlock, 1}, - items, - EG::ElementGeometries, - QF::QuadratureRule, - BE_test::Array{<:FEEvaluator, 1}, - BE_ansatz::Array{<:FEEvaluator, 1}, - BE_args::Array{<:FEEvaluator, 1}, - BE_test_vals::Array{Array{Tv, 3}, 1}, - BE_ansatz_vals::Array{Array{Tv, 3}, 1}, - BE_args_vals::Array{Array{Tv, 3}, 1}, - L2G::L2GTransformer, - QPinfos::QPInfos, - part = 1, - ) where {T} - - input_ansatz = zeros(T, op_offsets_ansatz[end]) - input_args = zeros(T, op_offsets_args[end]) - result_kernel = zeros(T, op_offsets_test[end]) - - ndofs_test::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_test] - ndofs_ansatz::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_ansatz] - ndofs_args::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_args] - - Aloc = Matrix{Matrix{T}}(undef, ntest, nansatz) - for j ∈ 1:ntest, k ∈ 1:nansatz - Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_ansatz[k]) - end - weights, xref = QF.w, QF.xref - nweights = length(weights) - - for item::Int in items - if itemregions[item] > 0 - if !(visit_region[itemregions[item]]) || AT == ON_IFACES - continue - end - end - QPinfos.region = itemregions[item] - QPinfos.item = item - if has_normals - QPinfos.normal .= view(itemnormals, :, item) - end - QPinfos.volume = itemvolumes[item] - - ## update FE basis evaluators - for j ∈ 1:ntest - BE_test[j].citem[] = item - update_basis!(BE_test[j]) - end - for j ∈ 1:nansatz - BE_ansatz[j].citem[] = item - update_basis!(BE_ansatz[j]) - end - for j ∈ 1:nargs - BE_args[j].citem[] = item - update_basis!(BE_args[j]) - end - update_trafo!(L2G, item) - - ## evaluate arguments - for qp ∈ 1:nweights - fill!(input_args, 0) - for id ∈ 1:nargs - for j ∈ 1:ndofs_args[id] - dof_j = itemdofs_args[id][j, item] - for d ∈ 1:op_lengths_args[id] - input_args[d+op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][d, j, qp] - end - end - end - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # update matrix - for id ∈ 1:nansatz - for j ∈ 1:ndofs_ansatz[id] - # evaluat kernel for ansatz basis function - fill!(input_ansatz, 0) - for d ∈ 1:op_lengths_ansatz[id] - input_ansatz[d+op_offsets_ansatz[id]] += BE_ansatz_vals[id][d, j, qp] - end - - # evaluate kernel - O.kernel(result_kernel, input_ansatz, input_args, QPinfos) - result_kernel .*= factor * weights[qp] - - # multiply test function operator evaluation - if lump == 1 - for d ∈ 1:op_lengths_test[id] - Aloc[id, id][j, j] += result_kernel[d+op_offsets_test[id]] * BE_test_vals[id][d, j, qp] - end - elseif lump == 2 - for k ∈ 1:ndofs_test[id] - for d ∈ 1:op_lengths_test[id] - Aloc[id, id][j, j] += result_kernel[d+op_offsets_test[id]] * BE_test_vals[id][d, k, qp] - end - end - else - for idt in couples_with[id] - for k ∈ 1:ndofs_test[idt] - for d ∈ 1:op_lengths_test[idt] - Aloc[idt, id][k, j] += result_kernel[d+op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] - end - end - end - end - end - end - end - - ## add local matrices to global matrix - for id ∈ 1:nansatz, idt ∈ 1:ntest - Aloc[idt, id] .*= itemvolumes[item] - for j ∈ 1:ndofs_test[idt] - dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] - for k ∈ 1:ndofs_ansatz[id] - dof_k = itemdofs_ansatz[id][k, item] + offsets_ansatz[id] - if abs(Aloc[idt, id][j, k]) > entry_tol - rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) - end - end - end - end - if transposed_copy != 0 - for id ∈ 1:nansatz, idt ∈ 1:ntest - Aloc[idt, id] .*= transposed_copy - for j ∈ 1:ndofs_test[idt] - dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] - for k ∈ 1:ndofs_ansatz[id] - dof_k = itemdofs_ansatz[id][k, item] + offsets_ansatz[id] - if abs(Aloc[idt, id][j, k]) > entry_tol - rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_k, dof_j, part) - end - end - end - end - end - - for id ∈ 1:nansatz, idt ∈ 1:ntest - fill!(Aloc[idt, id], 0) - end - end - return - end - O.FES_test = FES_test - O.FES_ansatz = FES_ansatz - O.FES_args = FES_args - - function assembler(A, b, sol; kwargs...) - time = @elapsed begin - if O.parameters[:parallel] - pcp = xgrid[PColorPartitions] - ncolors = length(pcp) - 1 - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - for color in 1:ncolors - Threads.@threads for part in pcp[color]:pcp[color+1]-1 - assembly_loop( - A, - sol, - itemassemblygroups[part], - EGs[part], - O.QF[part], - O.BE_test[part], - O.BE_ansatz[part], - O.BE_args[part], - O.BE_test_vals[part], - O.BE_ansatz_vals[part], - O.BE_args_vals[part], - O.L2G[part], - O.QP_infos[part], - part; - kwargs..., - ) - end - end - elseif O.parameters[:parallel_groups] - Threads.@threads for j ∈ 1:length(EGs) - fill!(Aj[j].cscmatrix.nzval, 0) - assembly_loop(Aj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_args[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - flush!(Aj[j]) - end - for j ∈ 1:length(EGs) - add!(A, Aj[j]) - end - else - for j ∈ 1:length(EGs) - assembly_loop(A, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_args[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - end - flush!(A) - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end - end - O.assembler = assembler - else - ## update the time - for j ∈ 1:length(O.QP_infos) - O.QP_infos[j].time = time - end - end + ## check if FES is the same as last time + FES_test = [getFEStest(FE_test[j]) for j in 1:length(FE_test)] + FES_ansatz = [getFESansatz(FE_ansatz[j]) for j in 1:length(FE_ansatz)] + FES_args = [FE_args[j].FES for j in 1:length(FE_args)] + return if (O.FES_test != FES_test) || (O.FES_args != FES_args) + + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_test, FES_ansatz) + + ## prepare assembly + AT = O.parameters[:entities] + Ti = typeof(xgrid).parameters[2] + if xgrid == FES_test[1].dofgrid + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) + else + gridAT = AT + end + + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pc = xgrid[PartitionCells] + itemassemblygroups = [pc[j]:(pc[j + 1] - 1) for j in 1:num_partitions(xgrid)] + # assuming here that all cells of one partition have the same geometry + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + has_normals = true + if AT <: ON_FACES + itemnormals = xgrid[FaceNormals] + elseif AT <: ON_BFACES + itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] + else + has_normals = false + end + FETypes_test = [eltype(F) for F in FES_test] + FETypes_ansatz = [eltype(F) for F in FES_ansatz] + FETypes_args = [eltype(F) for F in FES_args] + EGs = [itemgeometries[itemassemblygroups[j][1]] for j in 1:length(itemassemblygroups)] + + ## prepare assembly + nargs = length(FES_args) + ntest = length(FES_test) + nansatz = length(FES_ansatz) + O.QF = [] + O.BE_test = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.BE_ansatz = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.BE_args = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.BE_ansatz_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_ansatz = maximum([get_polynomialorder(FETypes_ansatz[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_ansatz[j]) for j in 1:nansatz]) + polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j in 1:ntest]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_ansatz + polyorder_test + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) + + ## L2G map for EG + push!(O.L2G, L2GTransformer(EG, xgrid, gridAT)) + + ## FE basis evaluator for EG + push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:ntest]) + push!(O.BE_ansatz, [FEEvaluator(FES_ansatz[j], O.ops_ansatz[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:nansatz]) + push!(O.BE_args, [FEEvaluator(FES_args[j], O.ops_args[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:nargs]) + push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) + push!(O.BE_ansatz_vals, [BE.cvals for BE in O.BE_ansatz[end]]) + push!(O.BE_args_vals, [BE.cvals for BE in O.BE_args[end]]) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[O.regions] = true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j in 1:ntest] + op_lengths_ansatz = [size(O.BE_ansatz[1][j].cvals, 1) for j in 1:nansatz] + op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j in 1:nargs] + + op_offsets_test = [0] + op_offsets_ansatz = [0] + op_offsets_args = [0] + append!(op_offsets_test, cumsum(op_lengths_test)) + append!(op_offsets_ansatz, cumsum(op_lengths_ansatz)) + append!(op_offsets_args, cumsum(op_lengths_args)) + offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] + offsets_ansatz = [FE_ansatz[j].offsetY for j in 1:length(FES_ansatz)] + + ## prepare sparsity pattern + use_sparsity_pattern = O.parameters[:use_sparsity_pattern] + if use_sparsity_pattern == "auto" + use_sparsity_pattern = false + end + coupling_matrix::Matrix{Bool} = ones(Bool, nansatz, ntest) + if use_sparsity_pattern + kernel_params = (result, input) -> (O.kernel(result, input, O.QP_infos[1])) + sparsity_pattern = SparseMatrixCSC{Float64, Int}(Symbolics.jacobian_sparsity(kernel_params, zeros(Tv, op_offsets_test[end]), zeros(Tv, op_offsets_ansatz[end]))) + + ## find out which test and ansatz functions couple + for id in 1:nansatz + for idt in 1:ntest + couple = false + for j in 1:op_lengths_ansatz[id] + for k in 1:op_lengths_test[idt] + if sparsity_pattern[k + op_offsets_test[idt], j + op_offsets_ansatz[id]] > 0 + couple = true + end + end + end + coupling_matrix[id, idt] = couple + end + end + end + couples_with::Vector{Vector{Int}} = [findall(==(true), view(coupling_matrix, j, :)) for j in 1:nansatz] + + ## prepare parallel assembly_allocations + if O.parameters[:parallel_groups] + Aj = Array{typeof(A), 1}(undef, length(EGs)) + for j in 1:length(EGs) + Aj[j] = copy(A) + end + end + + FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j in 1:ntest] + FEATs_ansatz = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_ansatz[j]), AT) for j in 1:nansatz] + FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j in 1:nargs] + itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j in 1:ntest] + itemdofs_ansatz::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_ansatz[j], xgrid, FEATs_ansatz[j]) for j in 1:nansatz] + itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j in 1:nargs] + factor = O.parameters[:factor] + transposed_copy = O.parameters[:transposed_copy] + entry_tol = O.parameters[:entry_tolerance] + lump = O.parameters[:lump] + + ## Assembly loop for fixed geometry + function assembly_loop( + A::AbstractSparseArray{T}, + sol::Array{<:FEVectorBlock, 1}, + items, + EG::ElementGeometries, + QF::QuadratureRule, + BE_test::Array{<:FEEvaluator, 1}, + BE_ansatz::Array{<:FEEvaluator, 1}, + BE_args::Array{<:FEEvaluator, 1}, + BE_test_vals::Array{Array{Tv, 3}, 1}, + BE_ansatz_vals::Array{Array{Tv, 3}, 1}, + BE_args_vals::Array{Array{Tv, 3}, 1}, + L2G::L2GTransformer, + QPinfos::QPInfos, + part = 1, + ) where {T} + + input_ansatz = zeros(T, op_offsets_ansatz[end]) + input_args = zeros(T, op_offsets_args[end]) + result_kernel = zeros(T, op_offsets_test[end]) + + ndofs_test::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_test] + ndofs_ansatz::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_ansatz] + ndofs_args::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_args] + + Aloc = Matrix{Matrix{T}}(undef, ntest, nansatz) + for j in 1:ntest, k in 1:nansatz + Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_ansatz[k]) + end + weights, xref = QF.w, QF.xref + nweights = length(weights) + + for item::Int in items + if itemregions[item] > 0 + if !(visit_region[itemregions[item]]) || AT == ON_IFACES + continue + end + end + QPinfos.region = itemregions[item] + QPinfos.item = item + if has_normals + QPinfos.normal .= view(itemnormals, :, item) + end + QPinfos.volume = itemvolumes[item] + + ## update FE basis evaluators + for j in 1:ntest + BE_test[j].citem[] = item + update_basis!(BE_test[j]) + end + for j in 1:nansatz + BE_ansatz[j].citem[] = item + update_basis!(BE_ansatz[j]) + end + for j in 1:nargs + BE_args[j].citem[] = item + update_basis!(BE_args[j]) + end + update_trafo!(L2G, item) + + ## evaluate arguments + for qp in 1:nweights + fill!(input_args, 0) + for id in 1:nargs + for j in 1:ndofs_args[id] + dof_j = itemdofs_args[id][j, item] + for d in 1:op_lengths_args[id] + input_args[d + op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][d, j, qp] + end + end + end + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # update matrix + for id in 1:nansatz + for j in 1:ndofs_ansatz[id] + # evaluat kernel for ansatz basis function + fill!(input_ansatz, 0) + for d in 1:op_lengths_ansatz[id] + input_ansatz[d + op_offsets_ansatz[id]] += BE_ansatz_vals[id][d, j, qp] + end + + # evaluate kernel + O.kernel(result_kernel, input_ansatz, input_args, QPinfos) + result_kernel .*= factor * weights[qp] + + # multiply test function operator evaluation + if lump == 1 + for d in 1:op_lengths_test[id] + Aloc[id, id][j, j] += result_kernel[d + op_offsets_test[id]] * BE_test_vals[id][d, j, qp] + end + elseif lump == 2 + for k in 1:ndofs_test[id] + for d in 1:op_lengths_test[id] + Aloc[id, id][j, j] += result_kernel[d + op_offsets_test[id]] * BE_test_vals[id][d, k, qp] + end + end + else + for idt in couples_with[id] + for k in 1:ndofs_test[idt] + for d in 1:op_lengths_test[idt] + Aloc[idt, id][k, j] += result_kernel[d + op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] + end + end + end + end + end + end + end + + ## add local matrices to global matrix + for id in 1:nansatz, idt in 1:ntest + Aloc[idt, id] .*= itemvolumes[item] + for j in 1:ndofs_test[idt] + dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] + for k in 1:ndofs_ansatz[id] + dof_k = itemdofs_ansatz[id][k, item] + offsets_ansatz[id] + if abs(Aloc[idt, id][j, k]) > entry_tol + rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) + end + end + end + end + if transposed_copy != 0 + for id in 1:nansatz, idt in 1:ntest + Aloc[idt, id] .*= transposed_copy + for j in 1:ndofs_test[idt] + dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] + for k in 1:ndofs_ansatz[id] + dof_k = itemdofs_ansatz[id][k, item] + offsets_ansatz[id] + if abs(Aloc[idt, id][j, k]) > entry_tol + rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_k, dof_j, part) + end + end + end + end + end + + for id in 1:nansatz, idt in 1:ntest + fill!(Aloc[idt, id], 0) + end + end + return + end + O.FES_test = FES_test + O.FES_ansatz = FES_ansatz + O.FES_args = FES_args + + function assembler(A, b, sol; kwargs...) + time = @elapsed begin + if O.parameters[:parallel] + pcp = xgrid[PColorPartitions] + ncolors = length(pcp) - 1 + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + for color in 1:ncolors + Threads.@threads for part in pcp[color]:(pcp[color + 1] - 1) + assembly_loop( + A, + sol, + itemassemblygroups[part], + EGs[part], + O.QF[part], + O.BE_test[part], + O.BE_ansatz[part], + O.BE_args[part], + O.BE_test_vals[part], + O.BE_ansatz_vals[part], + O.BE_args_vals[part], + O.L2G[part], + O.QP_infos[part], + part; + kwargs..., + ) + end + end + elseif O.parameters[:parallel_groups] + Threads.@threads for j in 1:length(EGs) + fill!(Aj[j].cscmatrix.nzval, 0) + assembly_loop(Aj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_args[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + flush!(Aj[j]) + end + for j in 1:length(EGs) + add!(A, Aj[j]) + end + else + for j in 1:length(EGs) + assembly_loop(A, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_args[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + end + flush!(A) + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end + end + O.assembler = assembler + else + ## update the time + for j in 1:length(O.QP_infos) + O.QP_infos[j].time = time + end + end end function build_assembler!(A, O::BilinearOperator{Tv}, FE_test, FE_ansatz; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_test = [getFEStest(FE_test[j]) for j ∈ 1:length(FE_test)] - FES_ansatz = [getFESansatz(FE_ansatz[j]) for j ∈ 1:length(FE_ansatz)] - - if (O.FES_test != FES_test) || (O.FES_ansatz != FES_ansatz) - - ntest = length(FES_test) - nansatz = length(FES_ansatz) - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - if O.parameters[:verbosity] > 1 - @info "...... TEST : $([(get_FEType(FES_test[j]), O.ops_test[j]) for j = 1 : ntest])" - @info "...... ANSATZ : $([(get_FEType(FES_ansatz[j]), O.ops_ansatz[j]) for j = 1 : nansatz])" - end - end - - ## determine grid - xgrid = determine_assembly_grid(FES_test, FES_ansatz) - - ## prepare assembly - AT = O.parameters[:entities] - Ti = typeof(xgrid).parameters[2] - if xgrid == FES_test[1].dofgrid - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) - else - gridAT = AT - end - - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pc = xgrid[PartitionCells] - itemassemblygroups = [pc[j]:pc[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - # assuming here that all cells of one partition have the same geometry - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - EGs = [itemgeometries[itemassemblygroups[j][1]] for j ∈ 1:length(itemassemblygroups)] - - has_normals = true - if gridAT <: ON_FACES - itemnormals = xgrid[FaceNormals] - elseif gridAT <: ON_BFACES - itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] - else - has_normals = false - end - FETypes_test = [eltype(F) for F in FES_test] - FETypes_ansatz = [eltype(F) for F in FES_ansatz] - - ## prepare assembly - O.QF = [] - O.BE_test = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.BE_ansatz = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.BE_ansatz_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_ansatz = maximum([get_polynomialorder(FETypes_ansatz[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_ansatz[j]) for j ∈ 1:nansatz]) - polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j ∈ 1:ntest]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_ansatz + polyorder_test + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) - - ## L2G map for EG - push!(O.L2G, L2GTransformer(EG, xgrid, gridAT)) - - ## FE basis evaluator for EG - push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:ntest]) - push!(O.BE_ansatz, [FEEvaluator(FES_ansatz[j], O.ops_ansatz[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:nansatz]) - push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) - push!(O.BE_ansatz_vals, [BE.cvals for BE in O.BE_ansatz[end]]) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[regions] .= true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j ∈ 1:ntest] - op_lengths_ansatz = [size(O.BE_ansatz[1][j].cvals, 1) for j ∈ 1:nansatz] - - op_offsets_test = [0] - op_offsets_ansatz = [0] - append!(op_offsets_test, cumsum(op_lengths_test)) - append!(op_offsets_ansatz, cumsum(op_lengths_ansatz)) - offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] - offsets_ansatz = [FE_ansatz[j].offsetY for j in 1:length(FES_ansatz)] - - ## prepare sparsity pattern - use_sparsity_pattern = O.parameters[:use_sparsity_pattern] - if use_sparsity_pattern == "auto" - use_sparsity_pattern = ntest > 1 - end - coupling_matrix::Matrix{Bool} = ones(Bool, nansatz, ntest) - if use_sparsity_pattern - kernel_params = (result, input) -> (O.kernel(result, input, O.QP_infos[1])) - sparsity_pattern = SparseMatrixCSC{Float64, Int}(Symbolics.jacobian_sparsity(kernel_params, zeros(Tv, op_offsets_test[end]), zeros(Tv, op_offsets_ansatz[end]))) - - ## find out which test and ansatz functions couple - for id ∈ 1:nansatz - for idt ∈ 1:ntest - couple = false - for j ∈ 1:op_lengths_ansatz[id] - for k ∈ 1:op_lengths_test[idt] - if sparsity_pattern[k+op_offsets_test[idt], j+op_offsets_ansatz[id]] > 0 - couple = true - end - end - end - coupling_matrix[id, idt] = couple - end - end - end - couples_with::Vector{Vector{Int}} = [findall(==(true), view(coupling_matrix, j, :)) for j ∈ 1:nansatz] - - ## prepare parallel assembly - if O.parameters[:parallel_groups] - Aj = Array{typeof(A), 1}(undef, length(EGs)) - for j ∈ 1:length(EGs) - Aj[j] = deepcopy(A) - end - end - - FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j ∈ 1:ntest] - FEATs_ansatz = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_ansatz[j]), AT) for j ∈ 1:nansatz] - - itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j ∈ 1:ntest] - itemdofs_ansatz::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_ansatz[j], xgrid, FEATs_ansatz[j]) for j ∈ 1:nansatz] - factor = O.parameters[:factor] - transposed_copy = O.parameters[:transposed_copy] - entry_tol = O.parameters[:entry_tolerance] - lump = O.parameters[:lump] - - ## Assembly loop for fixed geometry - function assembly_loop( - A::AbstractSparseArray{T}, - items, - EG::ElementGeometries, - QF::QuadratureRule, - BE_test::Array{<:FEEvaluator, 1}, - BE_ansatz::Array{<:FEEvaluator, 1}, - BE_test_vals::Array{Array{Tv, 3}, 1}, - BE_ansatz_vals::Array{Array{Tv, 3}, 1}, - L2G::L2GTransformer, - QPinfos::QPInfos, - part = 1, - ) where {T} - - input_ansatz = zeros(T, op_offsets_ansatz[end]) - result_kernel = zeros(T, op_offsets_test[end]) - - ndofs_test::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_test] - ndofs_ansatz::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_ansatz] - - Aloc = Matrix{Matrix{T}}(undef, ntest, nansatz) - for j ∈ 1:ntest, k ∈ 1:nansatz - Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_ansatz[k]) - end - weights, xref = QF.w, QF.xref - nweights = length(weights) - - for item::Int in items - if itemregions[item] > 0 - if !(visit_region[itemregions[item]]) || AT == ON_IFACES - continue - end - else - if length(regions) > 0 - continue - end - end - QPinfos.region = itemregions[item] - QPinfos.item = item - if has_normals - QPinfos.normal .= view(itemnormals, :, item) - end - QPinfos.volume = itemvolumes[item] - - ## update FE basis evaluators - for j ∈ 1:ntest - BE_test[j].citem[] = item - update_basis!(BE_test[j]) - end - for j ∈ 1:nansatz - BE_ansatz[j].citem[] = item - update_basis!(BE_ansatz[j]) - end - update_trafo!(L2G, item) - - ## evaluate arguments - for qp ∈ 1:nweights - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # update matrix - for id ∈ 1:nansatz - for j ∈ 1:ndofs_ansatz[id] - # evaluat kernel for ansatz basis function - fill!(input_ansatz, 0) - for d ∈ 1:op_lengths_ansatz[id] - input_ansatz[d+op_offsets_ansatz[id]] = BE_ansatz_vals[id][d, j, qp] - end - - # evaluate kernel - O.kernel(result_kernel, input_ansatz, QPinfos) - result_kernel .*= factor * weights[qp] - - # multiply test function operator evaluation - if lump == 1 - for d ∈ 1:op_lengths_test[id] - Aloc[id, id][j, j] += result_kernel[d+op_offsets_test[id]] * BE_test_vals[id][d, j, qp] - end - elseif lump == 2 - for k ∈ 1:ndofs_test[id] - for d ∈ 1:op_lengths_test[id] - Aloc[id, id][j, j] += result_kernel[d+op_offsets_test[id]] * BE_test_vals[id][d, k, qp] - end - end - else - for idt in couples_with[id] - for k ∈ 1:ndofs_test[idt] - for d ∈ 1:op_lengths_test[idt] - Aloc[idt, id][k, j] += result_kernel[d+op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] - end - end - end - end - end - end - end - - ## add local matrices to global matrix - for id ∈ 1:nansatz, idt in couples_with[id] - Aloc[idt, id] .*= itemvolumes[item] - for j ∈ 1:ndofs_test[idt] - dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] - for k ∈ 1:ndofs_ansatz[id] - dof_k = itemdofs_ansatz[id][k, item] + offsets_ansatz[id] - if abs(Aloc[idt, id][j, k]) > entry_tol - rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) - end - end - end - end - if transposed_copy != 0 - for id ∈ 1:nansatz, idt in couples_with[id] - Aloc[idt, id] .*= transposed_copy - for j ∈ 1:ndofs_test[idt] - dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] - for k ∈ 1:ndofs_ansatz[id] - dof_k = itemdofs_ansatz[id][k, item] + offsets_ansatz[id] - if abs(Aloc[idt, id][j, k]) > entry_tol - rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_k, dof_j, part) - end - end - end - end - end - - for id ∈ 1:nansatz, idt ∈ 1:ntest - fill!(Aloc[idt, id], 0) - end - end - return - end - O.FES_test = FES_test - O.FES_ansatz = FES_ansatz - - function assembler(A, b; kwargs...) - time = @elapsed begin - if O.parameters[:store] && size(A) == size(O.storage) - add!(A, O.storage) - else - if O.parameters[:store] - if O.parameters[:parallel] - S = MTExtendableSparseMatrixCSC{Float64, Int}(size(A, 1), size(A, 2), length(EGs)) - else - S = ExtendableSparseMatrix{Float64, Int}(size(A, 1), size(A, 2)) - end - else - S = A - end - if O.parameters[:parallel] - pcp = xgrid[PColorPartitions] - ncolors = length(pcp) - 1 - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - for color in 1:ncolors - Threads.@threads for part in pcp[color]:pcp[color+1]-1 - assembly_loop(S, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_ansatz[part], O.BE_test_vals[part], O.BE_ansatz_vals[part], O.L2G[part], O.QP_infos[part], part; kwargs...) - end - end - elseif O.parameters[:parallel_groups] - Threads.@threads for j ∈ 1:length(EGs) - fill!(Aj[j].cscmatrix.nzval, 0) - assembly_loop(Aj[j], itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.L2G[j], O.QP_infos[j], j; kwargs...) - end - for j ∈ 1:length(EGs) - add!(S, Aj[j]) - end - else - for j ∈ 1:length(EGs) - assembly_loop(S, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - flush!(S) - if O.parameters[:store] - add!(A, S) - O.storage = S - end - end - end - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end - end - O.assembler = assembler - else - ## update the time - for j ∈ 1:length(O.QP_infos) - O.QP_infos[j].time = time - end - end + ## check if FES is the same as last time + FES_test = [getFEStest(FE_test[j]) for j in 1:length(FE_test)] + FES_ansatz = [getFESansatz(FE_ansatz[j]) for j in 1:length(FE_ansatz)] + + return if (O.FES_test != FES_test) || (O.FES_ansatz != FES_ansatz) + + ntest = length(FES_test) + nansatz = length(FES_ansatz) + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + if O.parameters[:verbosity] > 1 + @info "...... TEST : $([(get_FEType(FES_test[j]), O.ops_test[j]) for j in 1:ntest])" + @info "...... ANSATZ : $([(get_FEType(FES_ansatz[j]), O.ops_ansatz[j]) for j in 1:nansatz])" + end + end + + ## determine grid + xgrid = determine_assembly_grid(FES_test, FES_ansatz) + + ## prepare assembly + AT = O.parameters[:entities] + Ti = typeof(xgrid).parameters[2] + if xgrid == FES_test[1].dofgrid + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) + else + gridAT = AT + end + + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pc = xgrid[PartitionCells] + itemassemblygroups = [pc[j]:(pc[j + 1] - 1) for j in 1:num_partitions(xgrid)] + # assuming here that all cells of one partition have the same geometry + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + EGs = [itemgeometries[itemassemblygroups[j][1]] for j in 1:length(itemassemblygroups)] + + has_normals = true + if gridAT <: ON_FACES + itemnormals = xgrid[FaceNormals] + elseif gridAT <: ON_BFACES + itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] + else + has_normals = false + end + FETypes_test = [eltype(F) for F in FES_test] + FETypes_ansatz = [eltype(F) for F in FES_ansatz] + + ## prepare assembly + O.QF = [] + O.BE_test = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.BE_ansatz = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.BE_ansatz_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_ansatz = maximum([get_polynomialorder(FETypes_ansatz[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_ansatz[j]) for j in 1:nansatz]) + polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j in 1:ntest]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_ansatz + polyorder_test + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) + + ## L2G map for EG + push!(O.L2G, L2GTransformer(EG, xgrid, gridAT)) + + ## FE basis evaluator for EG + push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:ntest]) + push!(O.BE_ansatz, [FEEvaluator(FES_ansatz[j], O.ops_ansatz[j], O.QF[end]; AT = AT, L2G = O.L2G[end]) for j in 1:nansatz]) + push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) + push!(O.BE_ansatz_vals, [BE.cvals for BE in O.BE_ansatz[end]]) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[regions] .= true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j in 1:ntest] + op_lengths_ansatz = [size(O.BE_ansatz[1][j].cvals, 1) for j in 1:nansatz] + + op_offsets_test = [0] + op_offsets_ansatz = [0] + append!(op_offsets_test, cumsum(op_lengths_test)) + append!(op_offsets_ansatz, cumsum(op_lengths_ansatz)) + offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] + offsets_ansatz = [FE_ansatz[j].offsetY for j in 1:length(FES_ansatz)] + + ## prepare sparsity pattern + use_sparsity_pattern = O.parameters[:use_sparsity_pattern] + if use_sparsity_pattern == "auto" + use_sparsity_pattern = ntest > 1 + end + coupling_matrix::Matrix{Bool} = ones(Bool, nansatz, ntest) + if use_sparsity_pattern + kernel_params = (result, input) -> (O.kernel(result, input, O.QP_infos[1])) + sparsity_pattern = SparseMatrixCSC{Float64, Int}(Symbolics.jacobian_sparsity(kernel_params, zeros(Tv, op_offsets_test[end]), zeros(Tv, op_offsets_ansatz[end]))) + + ## find out which test and ansatz functions couple + for id in 1:nansatz + for idt in 1:ntest + couple = false + for j in 1:op_lengths_ansatz[id] + for k in 1:op_lengths_test[idt] + if sparsity_pattern[k + op_offsets_test[idt], j + op_offsets_ansatz[id]] > 0 + couple = true + end + end + end + coupling_matrix[id, idt] = couple + end + end + end + couples_with::Vector{Vector{Int}} = [findall(==(true), view(coupling_matrix, j, :)) for j in 1:nansatz] + + ## prepare parallel assembly + if O.parameters[:parallel_groups] + Aj = Array{typeof(A), 1}(undef, length(EGs)) + for j in 1:length(EGs) + Aj[j] = deepcopy(A) + end + end + + FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j in 1:ntest] + FEATs_ansatz = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_ansatz[j]), AT) for j in 1:nansatz] + + itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j in 1:ntest] + itemdofs_ansatz::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_ansatz[j], xgrid, FEATs_ansatz[j]) for j in 1:nansatz] + factor = O.parameters[:factor] + transposed_copy = O.parameters[:transposed_copy] + entry_tol = O.parameters[:entry_tolerance] + lump = O.parameters[:lump] + + ## Assembly loop for fixed geometry + function assembly_loop( + A::AbstractSparseArray{T}, + items, + EG::ElementGeometries, + QF::QuadratureRule, + BE_test::Array{<:FEEvaluator, 1}, + BE_ansatz::Array{<:FEEvaluator, 1}, + BE_test_vals::Array{Array{Tv, 3}, 1}, + BE_ansatz_vals::Array{Array{Tv, 3}, 1}, + L2G::L2GTransformer, + QPinfos::QPInfos, + part = 1, + ) where {T} + + input_ansatz = zeros(T, op_offsets_ansatz[end]) + result_kernel = zeros(T, op_offsets_test[end]) + + ndofs_test::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_test] + ndofs_ansatz::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_ansatz] + + Aloc = Matrix{Matrix{T}}(undef, ntest, nansatz) + for j in 1:ntest, k in 1:nansatz + Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_ansatz[k]) + end + weights, xref = QF.w, QF.xref + nweights = length(weights) + + for item::Int in items + if itemregions[item] > 0 + if !(visit_region[itemregions[item]]) || AT == ON_IFACES + continue + end + else + if length(regions) > 0 + continue + end + end + QPinfos.region = itemregions[item] + QPinfos.item = item + if has_normals + QPinfos.normal .= view(itemnormals, :, item) + end + QPinfos.volume = itemvolumes[item] + + ## update FE basis evaluators + for j in 1:ntest + BE_test[j].citem[] = item + update_basis!(BE_test[j]) + end + for j in 1:nansatz + BE_ansatz[j].citem[] = item + update_basis!(BE_ansatz[j]) + end + update_trafo!(L2G, item) + + ## evaluate arguments + for qp in 1:nweights + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # update matrix + for id in 1:nansatz + for j in 1:ndofs_ansatz[id] + # evaluat kernel for ansatz basis function + fill!(input_ansatz, 0) + for d in 1:op_lengths_ansatz[id] + input_ansatz[d + op_offsets_ansatz[id]] = BE_ansatz_vals[id][d, j, qp] + end + + # evaluate kernel + O.kernel(result_kernel, input_ansatz, QPinfos) + result_kernel .*= factor * weights[qp] + + # multiply test function operator evaluation + if lump == 1 + for d in 1:op_lengths_test[id] + Aloc[id, id][j, j] += result_kernel[d + op_offsets_test[id]] * BE_test_vals[id][d, j, qp] + end + elseif lump == 2 + for k in 1:ndofs_test[id] + for d in 1:op_lengths_test[id] + Aloc[id, id][j, j] += result_kernel[d + op_offsets_test[id]] * BE_test_vals[id][d, k, qp] + end + end + else + for idt in couples_with[id] + for k in 1:ndofs_test[idt] + for d in 1:op_lengths_test[idt] + Aloc[idt, id][k, j] += result_kernel[d + op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] + end + end + end + end + end + end + end + + ## add local matrices to global matrix + for id in 1:nansatz, idt in couples_with[id] + Aloc[idt, id] .*= itemvolumes[item] + for j in 1:ndofs_test[idt] + dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] + for k in 1:ndofs_ansatz[id] + dof_k = itemdofs_ansatz[id][k, item] + offsets_ansatz[id] + if abs(Aloc[idt, id][j, k]) > entry_tol + rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) + end + end + end + end + if transposed_copy != 0 + for id in 1:nansatz, idt in couples_with[id] + Aloc[idt, id] .*= transposed_copy + for j in 1:ndofs_test[idt] + dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] + for k in 1:ndofs_ansatz[id] + dof_k = itemdofs_ansatz[id][k, item] + offsets_ansatz[id] + if abs(Aloc[idt, id][j, k]) > entry_tol + rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_k, dof_j, part) + end + end + end + end + end + + for id in 1:nansatz, idt in 1:ntest + fill!(Aloc[idt, id], 0) + end + end + return + end + O.FES_test = FES_test + O.FES_ansatz = FES_ansatz + + function assembler(A, b; kwargs...) + time = @elapsed begin + if O.parameters[:store] && size(A) == size(O.storage) + add!(A, O.storage) + else + if O.parameters[:store] + if O.parameters[:parallel] + S = MTExtendableSparseMatrixCSC{Float64, Int}(size(A, 1), size(A, 2), length(EGs)) + else + S = ExtendableSparseMatrix{Float64, Int}(size(A, 1), size(A, 2)) + end + else + S = A + end + if O.parameters[:parallel] + pcp = xgrid[PColorPartitions] + ncolors = length(pcp) - 1 + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + for color in 1:ncolors + Threads.@threads for part in pcp[color]:(pcp[color + 1] - 1) + assembly_loop(S, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_ansatz[part], O.BE_test_vals[part], O.BE_ansatz_vals[part], O.L2G[part], O.QP_infos[part], part; kwargs...) + end + end + elseif O.parameters[:parallel_groups] + Threads.@threads for j in 1:length(EGs) + fill!(Aj[j].cscmatrix.nzval, 0) + assembly_loop(Aj[j], itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.L2G[j], O.QP_infos[j], j; kwargs...) + end + for j in 1:length(EGs) + add!(S, Aj[j]) + end + else + for j in 1:length(EGs) + assembly_loop(S, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + flush!(S) + if O.parameters[:store] + add!(A, S) + O.storage = S + end + end + end + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end + end + O.assembler = assembler + else + ## update the time + for j in 1:length(O.QP_infos) + O.QP_infos[j].time = time + end + end end function assemble!(A, b, sol, O::BilinearOperator{Tv, UT}, SC::SolverConfiguration; assemble_matrix = true, assemblr_rhs = true, kwargs...) where {Tv, UT} - if !assemble_matrix - return nothing - end - if UT <: Integer - ind_test = O.u_test - ind_ansatz = O.u_ansatz - ind_args = O.u_args - elseif UT <: Unknown - ind_test = [get_unknown_id(SC, u) for u in O.u_test] - ind_ansatz = [get_unknown_id(SC, u) for u in O.u_ansatz] - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] #[get_unknown_id(SC, u) for u in O.u_args] - end - if length(O.u_args) > 0 - build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz], [sol[j] for j in ind_args]; kwargs...) - O.assembler(A.entries, b.entries, [sol[j] for j in ind_args]) - else - build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz]; kwargs...) - O.assembler(A.entries, b.entries) - end + if !assemble_matrix + return nothing + end + if UT <: Integer + ind_test = O.u_test + ind_ansatz = O.u_ansatz + ind_args = O.u_args + elseif UT <: Unknown + ind_test = [get_unknown_id(SC, u) for u in O.u_test] + ind_ansatz = [get_unknown_id(SC, u) for u in O.u_ansatz] + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] #[get_unknown_id(SC, u) for u in O.u_args] + end + return if length(O.u_args) > 0 + build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz], [sol[j] for j in ind_args]; kwargs...) + O.assembler(A.entries, b.entries, [sol[j] for j in ind_args]) + else + build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz]; kwargs...) + O.assembler(A.entries, b.entries) + end end function assemble!(A::FEMatrix, O::BilinearOperator{Tv, UT}, sol = nothing; assemble_matrix = true, kwargs...) where {Tv, UT} - if !assemble_matrix - return nothing - end - ind_test = O.u_test - ind_ansatz = O.u_ansatz - ind_args = O.u_args - if length(O.u_args) > 0 - build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz], [sol[j] for j in ind_args]; kwargs...) - O.assembler(A.entries, nothing, [sol[j] for j in ind_args]) - else - build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz]; kwargs...) - O.assembler(A.entries, nothing) - end + if !assemble_matrix + return nothing + end + ind_test = O.u_test + ind_ansatz = O.u_ansatz + ind_args = O.u_args + return if length(O.u_args) > 0 + build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz], [sol[j] for j in ind_args]; kwargs...) + O.assembler(A.entries, nothing, [sol[j] for j in ind_args]) + else + build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz]; kwargs...) + O.assembler(A.entries, nothing) + end end function assemble!(A, b, sol, O::BilinearOperatorFromMatrix{UT, MT}, SC::SolverConfiguration; assemble_matrix = true, kwargs...) where {UT, MT} - if !assemble_matrix - return nothing - end - if UT <: Integer - ind_test = O.u_test - ind_ansatz = O.u_ansatz - elseif UT <: Unknown - ind_test = [get_unknown_id(SC, u) for u in O.u_test] - ind_ansatz = [get_unknown_id(SC, u) for u in O.u_ansatz] - end - if MT <: FEMatrix - for (j, ij) in enumerate(ind_test), (k, ik) in enumerate(ind_ansatz) - addblock!(A[j, k], O.A[ij, ik]; factor = O.parameters[:factor]) - if O.parameters[:transposed_copy] != 0 - addblock!(A[k, j], O.A[ij, ik]; transpose = true, factor = O.parameters[:factor] * O.parameters[:transposed_copy]) - end - end - else - @assert length(ind_test) == 1 - @assert length(ind_ansatz) == 1 - addblock!(A[ind_test[1], ind_ansatz[1]], O.A; factor = O.parameters[:factor]) - if O.parameters[:transposed_copy] != 0 - addblock!(A[ind_ansatz[1], ind_test[1]], O.A; transpose = true, factor = O.parameters[:factor] * O.parameters[:transposed_copy]) - end - end + if !assemble_matrix + return nothing + end + if UT <: Integer + ind_test = O.u_test + ind_ansatz = O.u_ansatz + elseif UT <: Unknown + ind_test = [get_unknown_id(SC, u) for u in O.u_test] + ind_ansatz = [get_unknown_id(SC, u) for u in O.u_ansatz] + end + return if MT <: FEMatrix + for (j, ij) in enumerate(ind_test), (k, ik) in enumerate(ind_ansatz) + addblock!(A[j, k], O.A[ij, ik]; factor = O.parameters[:factor]) + if O.parameters[:transposed_copy] != 0 + addblock!(A[k, j], O.A[ij, ik]; transpose = true, factor = O.parameters[:factor] * O.parameters[:transposed_copy]) + end + end + else + @assert length(ind_test) == 1 + @assert length(ind_ansatz) == 1 + addblock!(A[ind_test[1], ind_ansatz[1]], O.A; factor = O.parameters[:factor]) + if O.parameters[:transposed_copy] != 0 + addblock!(A[ind_ansatz[1], ind_test[1]], O.A; transpose = true, factor = O.parameters[:factor] * O.parameters[:transposed_copy]) + end + end end diff --git a/src/common_operators/bilinear_operator_dg.jl b/src/common_operators/bilinear_operator_dg.jl index 20a6498..8416b13 100644 --- a/src/common_operators/bilinear_operator_dg.jl +++ b/src/common_operators/bilinear_operator_dg.jl @@ -1,154 +1,152 @@ - mutable struct BilinearOperatorDG{Tv <: Real, UT <: Union{Unknown, Integer}, KFT, MT} <: AbstractOperator - u_test::Array{UT, 1} - ops_test::Array{DataType, 1} - u_ansatz::Array{UT, 1} - ops_ansatz::Array{DataType, 1} - u_args::Array{UT, 1} - ops_args::Array{DataType, 1} - kernel::KFT - BE_test_vals::Array{Vector{Matrix{Array{Tv, 3}}}} - BE_ansatz_vals::Array{Vector{Matrix{Array{Tv, 3}}}} - BE_args_vals::Array{Vector{Matrix{Array{Tv, 3}}}} - FES_test::Any #::Array{FESpace,1} - FES_ansatz::Any #::Array{FESpace,1} - FES_args::Any #::Array{FESpace,1} - BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} - BE_ansatz::Any #::Union{Nothing, Array{FEEvaluator,1}} - BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} - QP_infos::Any #::Array{QPInfosT,1} - L2G::Any - QF::Any - assembler::Any - storage::MT - parameters::Dict{Symbol, Any} + u_test::Array{UT, 1} + ops_test::Array{DataType, 1} + u_ansatz::Array{UT, 1} + ops_ansatz::Array{DataType, 1} + u_args::Array{UT, 1} + ops_args::Array{DataType, 1} + kernel::KFT + BE_test_vals::Array{Vector{Matrix{Array{Tv, 3}}}} + BE_ansatz_vals::Array{Vector{Matrix{Array{Tv, 3}}}} + BE_args_vals::Array{Vector{Matrix{Array{Tv, 3}}}} + FES_test::Any #::Array{FESpace,1} + FES_ansatz::Any #::Array{FESpace,1} + FES_args::Any #::Array{FESpace,1} + BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} + BE_ansatz::Any #::Union{Nothing, Array{FEEvaluator,1}} + BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} + QP_infos::Any #::Array{QPInfosT,1} + L2G::Any + QF::Any + assembler::Any + storage::MT + parameters::Dict{Symbol, Any} end default_blfopdg_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :entities => (ON_FACES, "assemble operator on these grid entities (default = ON_FACES)"), - :name => ("BilinearOperatorDG", "name for operator used in printouts"), - :transposed_copy => (0, "assemble a transposed copy of that operator into the transposed matrix block(s), 0 = no, 1 = symmetric, -1 = skew-symmetric"), - :factor => (1, "factor that should be multiplied during assembly"), - :lump => (false, "lump the operator (= only assemble the diagonal)"), - :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), - :entry_tolerance => (0, "threshold to add entry to sparse matrix"), - :use_sparsity_pattern => ("auto", "read sparsity pattern of jacobian of kernel to find out which components couple"), - :parallel_groups => (false, "assemble operator in parallel using CellAssemblyGroups (assembles separated matrices that are added together sequantially)"), - :parallel => (false, "assemble operator in parallel using colors/partitions information (assembles into full matrix directly)"), - :time_dependent => (false, "operator is time-dependent ?"), - :callback! => (nothing, "function with interface (A, b, sol) that is called in each assembly step"), - :store => (false, "store matrix separately (and copy from there when reassembly is triggered)"), - :quadorder => ("auto", "quadrature order"), - :bonus_quadorder => (0, "additional quadrature order added to quadorder"), - :verbosity => (0, "verbosity level"), - :regions => ([], "subset of regions where operator should be assembly only"), + :entities => (ON_FACES, "assemble operator on these grid entities (default = ON_FACES)"), + :name => ("BilinearOperatorDG", "name for operator used in printouts"), + :transposed_copy => (0, "assemble a transposed copy of that operator into the transposed matrix block(s), 0 = no, 1 = symmetric, -1 = skew-symmetric"), + :factor => (1, "factor that should be multiplied during assembly"), + :lump => (false, "lump the operator (= only assemble the diagonal)"), + :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), + :entry_tolerance => (0, "threshold to add entry to sparse matrix"), + :use_sparsity_pattern => ("auto", "read sparsity pattern of jacobian of kernel to find out which components couple"), + :parallel_groups => (false, "assemble operator in parallel using CellAssemblyGroups (assembles separated matrices that are added together sequantially)"), + :parallel => (false, "assemble operator in parallel using colors/partitions information (assembles into full matrix directly)"), + :time_dependent => (false, "operator is time-dependent ?"), + :callback! => (nothing, "function with interface (A, b, sol) that is called in each assembly step"), + :store => (false, "store matrix separately (and copy from there when reassembly is triggered)"), + :quadorder => ("auto", "quadrature order"), + :bonus_quadorder => (0, "additional quadrature order added to quadorder"), + :verbosity => (0, "verbosity level"), + :regions => ([], "subset of regions where operator should be assembly only"), ) # informs solver when operator needs reassembly function depends_nonlinearly_on(O::BilinearOperatorDG) - return unique(O.u_args) + return unique(O.u_args) end # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::BilinearOperatorDG) - return [unique(O.u_ansatz), unique(O.u_test)] + return [unique(O.u_ansatz), unique(O.u_test)] end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::BilinearOperatorDG) - return O.parameters[:time_dependent] + return O.parameters[:time_dependent] end function Base.show(io::IO, O::BilinearOperatorDG) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($([ansatz_function(dependencies[1][j]) for j = 1 : length(dependencies[1])]), $([test_function(dependencies[2][j]) for j = 1 : length(dependencies[2])]); entities = $(O.parameters[:entities])))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($([ansatz_function(dependencies[1][j]) for j in 1:length(dependencies[1])]), $([test_function(dependencies[2][j]) for j in 1:length(dependencies[2])]); entities = $(O.parameters[:entities])))") + return nothing end function BilinearOperatorDG(kernel, u_test, ops_test, u_ansatz = u_test, ops_ansatz = ops_test; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfopdg_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_ansatz) == length(ops_ansatz) - @assert length(u_test) == length(ops_test) - if parameters[:store] - storage = ExtendableSparseMatrix{Float64, Int}(0, 0) - else - storage = nothing - end - return BilinearOperatorDG{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( - u_test, - ops_test, - u_ansatz, - ops_ansatz, - [], - [], - kernel, - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - storage, - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfopdg_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_ansatz) == length(ops_ansatz) + @assert length(u_test) == length(ops_test) + if parameters[:store] + storage = ExtendableSparseMatrix{Float64, Int}(0, 0) + else + storage = nothing + end + return BilinearOperatorDG{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( + u_test, + ops_test, + u_ansatz, + ops_ansatz, + [], + [], + kernel, + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + storage, + parameters, + ) end function BilinearOperatorDG(kernel, u_test, ops_test, u_ansatz, ops_ansatz, u_args, ops_args; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfopdg_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_args) == length(ops_args) - @assert length(u_ansatz) == length(ops_ansatz) - @assert length(u_test) == length(ops_test) - if parameters[:store] - storage = ExtendableSparseMatrix{Float64, Int}(0, 0) - else - storage = nothing - end - return BilinearOperatorDG{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( - u_test, - ops_test, - u_ansatz, - ops_ansatz, - u_args, - ops_args, - kernel, - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - storage, - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_blfopdg_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_args) == length(ops_args) + @assert length(u_ansatz) == length(ops_ansatz) + @assert length(u_test) == length(ops_test) + if parameters[:store] + storage = ExtendableSparseMatrix{Float64, Int}(0, 0) + else + storage = nothing + end + return BilinearOperatorDG{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( + u_test, + ops_test, + u_ansatz, + ops_ansatz, + u_args, + ops_args, + kernel, + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + storage, + parameters, + ) end function BilinearOperatorDG(kernel, oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_ansatz::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1} = oa_test; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_ansatz = [oa[1] for oa in oa_ansatz] - ops_test = [oa[2] for oa in oa_test] - ops_ansatz = [oa[2] for oa in oa_ansatz] - return BilinearOperatorDG(kernel, u_test, ops_test, u_ansatz, ops_ansatz; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_ansatz = [oa[1] for oa in oa_ansatz] + ops_test = [oa[2] for oa in oa_test] + ops_ansatz = [oa[2] for oa in oa_ansatz] + return BilinearOperatorDG(kernel, u_test, ops_test, u_ansatz, ops_ansatz; kwargs...) end - """ ```` function BilinearOperatorDG( @@ -180,11 +178,11 @@ $(_myprint(default_blfopdg_kwargs())) """ function BilinearOperatorDG(oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_ansatz::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1} = oa_test; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_ansatz = [oa[1] for oa in oa_ansatz] - ops_test = [oa[2] for oa in oa_test] - ops_ansatz = [oa[2] for oa in oa_ansatz] - return BilinearOperatorDG(ExtendableFEMBase.standard_kernel, u_test, ops_test, u_ansatz, ops_ansatz; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_ansatz = [oa[1] for oa in oa_ansatz] + ops_test = [oa[2] for oa in oa_test] + ops_ansatz = [oa[2] for oa in oa_ansatz] + return BilinearOperatorDG(ExtendableFEMBase.standard_kernel, u_test, ops_test, u_ansatz, ops_ansatz; kwargs...) end @@ -218,816 +216,816 @@ $(_myprint(default_blfop_kwargs())) """ function BilinearOperatorDG(kernel, oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_ansatz::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_ansatz = [oa[1] for oa in oa_ansatz] - u_args = [oa[1] for oa in oa_args] - ops_test = [oa[2] for oa in oa_test] - ops_ansatz = [oa[2] for oa in oa_ansatz] - ops_args = [oa[2] for oa in oa_args] - return BilinearOperatorDG(kernel, u_test, ops_test, u_ansatz, ops_ansatz, u_args, ops_args; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_ansatz = [oa[1] for oa in oa_ansatz] + u_args = [oa[1] for oa in oa_args] + ops_test = [oa[2] for oa in oa_test] + ops_ansatz = [oa[2] for oa in oa_ansatz] + ops_args = [oa[2] for oa in oa_args] + return BilinearOperatorDG(kernel, u_test, ops_test, u_ansatz, ops_ansatz, u_args, ops_args; kwargs...) end function build_assembler!(A, O::BilinearOperatorDG{Tv}, FE_test, FE_ansatz, FE_args::Array{<:FEVectorBlock, 1}; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_test = [getFEStest(FE_test[j]) for j ∈ 1:length(FE_test)] - FES_ansatz = [getFESansatz(FE_ansatz[j]) for j ∈ 1:length(FE_ansatz)] - FES_args = [FE_args[j].FES for j ∈ 1:length(FE_args)] - if (O.FES_test != FES_test) || (O.FES_args != FES_args) || (O.FES_ansatz != FES_ansatz) - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_test, FES_ansatz, FES_args) - - ## prepare assembly - AT = O.parameters[:entities] - @assert AT <: ON_FACES "only works for entities <: ON_FACES" - Ti = typeof(xgrid).parameters[2] - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) - - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if num_pcolors(xgrid) > 1 - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pe = xgrid[PartitionEdges] - itemassemblygroups = [pe[j]:pe[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - - FETypes_test = [eltype(F) for F in FES_test] - FETypes_ansatz = [eltype(F) for F in FES_ansatz] - FETypes_args = [eltype(F) for F in FES_args] - - EGs = num_pcolors(xgrid) > 1 ? [xgrid[UniqueCellGeometries][1] for j ∈ 1:num_partitions(xgrid)] : xgrid[UniqueCellGeometries] - - coeffs_ops_test = Array{Array{Float64, 1}, 1}([]) - coeffs_ops_ansatz = Array{Array{Float64, 1}, 1}([]) - coeffs_ops_args = Array{Array{Float64, 1}, 1}([]) - for op in O.ops_test - push!(coeffs_ops_test, coeffs(op)) - end - for op in O.ops_ansatz - push!(coeffs_ops_ansatz, coeffs(op)) - end - for op in O.ops_args - push!(coeffs_ops_args, coeffs(op)) - end - - ## prepare assembly - nargs = length(FES_args) - ntest = length(FES_test) - nansatz = length(FES_ansatz) - O.QF = [] - O.BE_test = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) - O.BE_ansatz = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) - O.BE_args = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) - O.BE_test_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) - O.BE_ansatz_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) - O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_ansatz = maximum([get_polynomialorder(FETypes_ansatz[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_ansatz[j]) for j ∈ 1:nansatz]) - polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j ∈ 1:ntest]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_ansatz + polyorder_test + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - - ## generate DG operator - push!(O.BE_test, [generate_DG_operators(StandardFunctionOperator(O.ops_test[j]), FES_test[j], quadorder, EG) for j ∈ 1:ntest]) - push!(O.BE_ansatz, [generate_DG_operators(StandardFunctionOperator(O.ops_ansatz[j]), FES_ansatz[j], quadorder, EG) for j ∈ 1:nansatz]) - push!(O.BE_args, [generate_DG_operators(StandardFunctionOperator(O.ops_args[j]), FES_args[j], quadorder, EG) for j ∈ 1:nargs]) - push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) - - ## L2G map for EG - EGface = facetype_of_cellface(EG, 1) - push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) - - ## FE basis evaluator for EG - push!(O.BE_test_vals, [[O.BE_test[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_test[end][k])] for k ∈ 1:ntest]) - push!(O.BE_ansatz_vals, [[O.BE_ansatz[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_ansatz[end][k])] for k ∈ 1:nansatz]) - push!(O.BE_args_vals, [[O.BE_args[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_args[end][k])] for k ∈ 1:nargs]) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[O.regions] = true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_test = [size(O.BE_test[1][j][1, 1].cvals, 1) for j ∈ 1:ntest] - op_lengths_ansatz = [size(O.BE_ansatz[1][j][1, 1].cvals, 1) for j ∈ 1:nansatz] - op_lengths_args = [size(O.BE_args[1][j][1, 1].cvals, 1) for j ∈ 1:nargs] - - op_offsets_test = [0] - op_offsets_ansatz = [0] - op_offsets_args = [0] - append!(op_offsets_test, cumsum(op_lengths_test)) - append!(op_offsets_ansatz, cumsum(op_lengths_ansatz)) - append!(op_offsets_args, cumsum(op_lengths_args)) - offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] - offsets_ansatz = [FE_ansatz[j].offsetY for j in 1:length(FES_ansatz)] - offsets_args = [FE_args[j].offset for j in 1:length(FES_args)] - - ## prepare sparsity pattern - use_sparsity_pattern = O.parameters[:use_sparsity_pattern] - if use_sparsity_pattern == "auto" - use_sparsity_pattern = false - end - coupling_matrix::Matrix{Bool} = ones(Bool, nansatz, ntest) - if use_sparsity_pattern - kernel_params = (result, input) -> (O.kernel(result, input, O.QP_infos[1])) - sparsity_pattern = SparseMatrixCSC{Float64, Int}(Symbolics.jacobian_sparsity(kernel_params, zeros(Tv, op_offsets_test[end]), zeros(Tv, op_offsets_ansatz[end]))) - - ## find out which test and ansatz functions couple - for id ∈ 1:nansatz - for idt ∈ 1:ntest - couple = false - for j ∈ 1:op_lengths_ansatz[id] - for k ∈ 1:op_lengths_test[idt] - if sparsity_pattern[k+op_offsets_test[idt], j+op_offsets_ansatz[id]] > 0 - couple = true - end - end - end - coupling_matrix[id, idt] = couple - end - end - end - couples_with::Vector{Vector{Int}} = [findall(==(true), view(coupling_matrix, j, :)) for j ∈ 1:nansatz] - - ## prepare parallel assembly - if O.parameters[:parallel_groups] - Aj = Array{typeof(A), 1}(undef, length(EGs)) - for j ∈ 1:length(EGs) - Aj[j] = copy(A) - end - end - - FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), ON_CELLS) for j ∈ 1:ntest] - FEATs_ansatz = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_ansatz[j]), ON_CELLS) for j ∈ 1:nansatz] - FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), ON_CELLS) for j ∈ 1:nargs] - itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j ∈ 1:ntest] - itemdofs_ansatz::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_ansatz[j], xgrid, FEATs_ansatz[j]) for j ∈ 1:nansatz] - itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j ∈ 1:nargs] - factor = O.parameters[:factor] - transposed_copy = O.parameters[:transposed_copy] - entry_tol = O.parameters[:entry_tolerance] - lump = O.parameters[:lump] - - ## Assembly loop for fixed geometry - function assembly_loop( - A::AbstractSparseArray{T}, - sol::Array{<:FEVectorBlock, 1}, - items, - EG::ElementGeometries, - QF::QuadratureRule, - BE_test::Vector{Matrix{<:FEEvaluator}}, - BE_ansatz::Vector{Matrix{<:FEEvaluator}}, - BE_args::Vector{Matrix{<:FEEvaluator}}, - BE_test_vals::Vector{Matrix{Array{Tv, 3}}}, - BE_ansatz_vals::Vector{Matrix{Array{Tv, 3}}}, - BE_args_vals::Vector{Matrix{Array{Tv, 3}}}, - L2G::L2GTransformer, - QPinfos::QPInfos, - part = 1, - ) where {T} - - input_ansatz = zeros(T, op_offsets_ansatz[end]) - result_kernel = zeros(T, op_offsets_test[end]) - itemorientations = xgrid[CellFaceOrientations] - itemcells = xgrid[FaceCells] - itemnormals = xgrid[FaceNormals] - cellitems = xgrid[CellFaces] - - ndofs_test::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_test] - ndofs_ansatz::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_ansatz] - ndofs_args::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_args] - - Aloc = Matrix{Matrix{T}}(undef, ntest, nansatz) - for j ∈ 1:ntest, k ∈ 1:nansatz - Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_ansatz[k]) - end - weights, xref = QF.w, QF.xref - nweights = length(weights) - cell1::Int = 0 - cell2::Int = 0 - orientation1::Int = 0 - orientation2::Int = 0 - itempos1::Int = 0 - itempos2::Int = 0 - - input_args = [zeros(T, op_offsets_args[end]) for j ∈ 1:nweights] - - for item::Int in items - QPinfos.region = itemregions[item] - QPinfos.item = item - QPinfos.normal .= view(itemnormals, :, item) - QPinfos.volume = itemvolumes[item] - update_trafo!(L2G, item) - - boundary_face = itemcells[2, item] == 0 - if AT <: ON_IFACES - if boundary_face - continue - end - end - - ## evaluate arguments at all quadrature points - for qp ∈ 1:nweights - fill!(input_args[qp], 0) - for c1 ∈ 1:2 - cell1 = itemcells[c1, item] - if (cell1 > 0) - itempos1 = 1 - while !(cellitems[itempos1, cell1] == item) - itempos1 += 1 - end - orientation1 = itemorientations[itempos1, cell1] - - for j ∈ 1:nargs - BE_args[j][itempos1, orientation1].citem[] = cell1 - update_basis!(BE_args[j][itempos1, orientation1]) - end - - for id ∈ 1:nargs - for j ∈ 1:ndofs_args[id] - dof_j = itemdofs_args[id][j, cell1] - for d ∈ 1:op_lengths_args[id] - input_args[qp][d+op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][itempos1, orientation1][d, j, qp] * coeffs_ops_args[id][c1] - end - end - end - end - end - end - - for c1 ∈ 1:2, c2 ∈ 1:2 - cell1 = itemcells[c1, item] # current cell of test function - cell2 = itemcells[c2, item] # current cell of ansatz function - if (cell1 > 0) && (cell2 > 0) - QPinfos.cell = cell2 - itempos1 = 1 - while !(cellitems[itempos1, cell1] == item) - itempos1 += 1 - end - itempos2 = 1 - while !(cellitems[itempos2, cell2] == item) - itempos2 += 1 - end - orientation1 = itemorientations[itempos1, cell1] - orientation2 = itemorientations[itempos2, cell2] - - ## update FE basis evaluators - for j ∈ 1:ntest - BE_test[j][itempos1, orientation1].citem[] = cell1 - update_basis!(BE_test[j][itempos1, orientation1]) - end - for j ∈ 1:nansatz - BE_ansatz[j][itempos2, orientation2].citem[] = cell2 - update_basis!(BE_ansatz[j][itempos2, orientation2]) - end - - ## evaluate arguments - for qp ∈ 1:nweights - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # update matrix - for id ∈ 1:nansatz - coeff_ansatz = boundary_face ? 1 : coeffs_ops_ansatz[id][c2] - for j ∈ 1:ndofs_ansatz[id] - # evaluate kernel for ansatz basis function on cell 2 - fill!(input_ansatz, 0) - for d ∈ 1:op_lengths_ansatz[id] - input_ansatz[d+op_offsets_ansatz[id]] = BE_ansatz_vals[id][itempos2, orientation2][d, j, qp] * coeff_ansatz - end - - # evaluate kernel - O.kernel(result_kernel, input_ansatz, input_args[qp], QPinfos) - result_kernel .*= factor * weights[qp] - - # multiply test function operator evaluation on cell 1 - for idt in couples_with[id] - coeff_test = boundary_face ? 1 : coeffs_ops_test[idt][c1] - for k ∈ 1:ndofs_test[idt] - for d ∈ 1:op_lengths_test[idt] - Aloc[idt, id][k, j] += result_kernel[d+op_offsets_test[idt]] * BE_test_vals[idt][itempos1, orientation1][d, k, qp] * coeff_test - end - end - end - end - end - end - - - ## add local matrices to global matrix - for id ∈ 1:nansatz, idt ∈ 1:ntest - Aloc[idt, id] .*= itemvolumes[item] - for j ∈ 1:ndofs_test[idt] - dof_j = itemdofs_test[idt][j, cell1] + offsets_test[idt] - for k ∈ 1:ndofs_ansatz[id] - dof_k = itemdofs_ansatz[id][k, cell2] + offsets_ansatz[id] - if abs(Aloc[idt, id][j, k]) > entry_tol - rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) - end - end - end - end - if transposed_copy != 0 - for id ∈ 1:nansatz, idt ∈ 1:ntest - Aloc[idt, id] .*= transposed_copy - for j ∈ 1:ndofs_test[idt] - dof_j = itemdofs_test[idt][j, cell1] + offsets_test[idt] - for k ∈ 1:ndofs_ansatz[id] - dof_k = itemdofs_ansatz[id][k, cell2] + offsets_ansatz[id] - if abs(Aloc[idt, id][j, k]) > entry_tol - rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_k, dof_j, part) - end - end - end - end - end - - for id ∈ 1:nansatz, idt ∈ 1:ntest - fill!(Aloc[idt, id], 0) - end - end - end - end - return - end - O.FES_test = FES_test - O.FES_ansatz = FES_ansatz - O.FES_args = FES_args - - function assembler(A, b, sol; kwargs...) - time = @elapsed begin - if O.parameters[:parallel] - pcp = xgrid[PColorPartitions] - ncolors = length(pcp) - 1 - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - for color in 1:ncolors - Threads.@threads for part in pcp[color]:pcp[color+1]-1 - assembly_loop( - A, - itemassemblygroups[part], - EGs[part], - O.QF[part], - O.BE_test[part], - O.BE_ansatz[part], - O.BE_args[part], - O.BE_test_vals[part], - O.BE_ansatz_vals[part], - O.BE_args_vals[part], - O.L2G[part], - O.QP_infos[part], - part; - kwargs..., - ) - end - end - elseif O.parameters[:parallel_groups] - Threads.@threads for j ∈ 1:length(EGs) - fill!(Aj[j].cscmatrix.nzval, 0) - assembly_loop(Aj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_args[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - flush!(Aj[j]) - end - for j ∈ 1:length(EGs) - A.cscmatrix += Aj[j].cscmatrix - end - else - for j ∈ 1:length(EGs) - assembly_loop(A, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_args[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - flush!(A) - end - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end - end - O.assembler = assembler - end + ## check if FES is the same as last time + FES_test = [getFEStest(FE_test[j]) for j in 1:length(FE_test)] + FES_ansatz = [getFESansatz(FE_ansatz[j]) for j in 1:length(FE_ansatz)] + FES_args = [FE_args[j].FES for j in 1:length(FE_args)] + return if (O.FES_test != FES_test) || (O.FES_args != FES_args) || (O.FES_ansatz != FES_ansatz) + + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_test, FES_ansatz, FES_args) + + ## prepare assembly + AT = O.parameters[:entities] + @assert AT <: ON_FACES "only works for entities <: ON_FACES" + Ti = typeof(xgrid).parameters[2] + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) + + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if num_pcolors(xgrid) > 1 + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pe = xgrid[PartitionEdges] + itemassemblygroups = [pe[j]:(pe[j + 1] - 1) for j in 1:num_partitions(xgrid)] + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + + FETypes_test = [eltype(F) for F in FES_test] + FETypes_ansatz = [eltype(F) for F in FES_ansatz] + FETypes_args = [eltype(F) for F in FES_args] + + EGs = num_pcolors(xgrid) > 1 ? [xgrid[UniqueCellGeometries][1] for j in 1:num_partitions(xgrid)] : xgrid[UniqueCellGeometries] + + coeffs_ops_test = Array{Array{Float64, 1}, 1}([]) + coeffs_ops_ansatz = Array{Array{Float64, 1}, 1}([]) + coeffs_ops_args = Array{Array{Float64, 1}, 1}([]) + for op in O.ops_test + push!(coeffs_ops_test, coeffs(op)) + end + for op in O.ops_ansatz + push!(coeffs_ops_ansatz, coeffs(op)) + end + for op in O.ops_args + push!(coeffs_ops_args, coeffs(op)) + end + + ## prepare assembly + nargs = length(FES_args) + ntest = length(FES_test) + nansatz = length(FES_ansatz) + O.QF = [] + O.BE_test = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) + O.BE_ansatz = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) + O.BE_args = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) + O.BE_test_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) + O.BE_ansatz_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) + O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_ansatz = maximum([get_polynomialorder(FETypes_ansatz[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_ansatz[j]) for j in 1:nansatz]) + polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j in 1:ntest]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_ansatz + polyorder_test + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + + ## generate DG operator + push!(O.BE_test, [generate_DG_operators(StandardFunctionOperator(O.ops_test[j]), FES_test[j], quadorder, EG) for j in 1:ntest]) + push!(O.BE_ansatz, [generate_DG_operators(StandardFunctionOperator(O.ops_ansatz[j]), FES_ansatz[j], quadorder, EG) for j in 1:nansatz]) + push!(O.BE_args, [generate_DG_operators(StandardFunctionOperator(O.ops_args[j]), FES_args[j], quadorder, EG) for j in 1:nargs]) + push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) + + ## L2G map for EG + EGface = facetype_of_cellface(EG, 1) + push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) + + ## FE basis evaluator for EG + push!(O.BE_test_vals, [[O.BE_test[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_test[end][k])] for k in 1:ntest]) + push!(O.BE_ansatz_vals, [[O.BE_ansatz[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_ansatz[end][k])] for k in 1:nansatz]) + push!(O.BE_args_vals, [[O.BE_args[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_args[end][k])] for k in 1:nargs]) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[O.regions] = true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_test = [size(O.BE_test[1][j][1, 1].cvals, 1) for j in 1:ntest] + op_lengths_ansatz = [size(O.BE_ansatz[1][j][1, 1].cvals, 1) for j in 1:nansatz] + op_lengths_args = [size(O.BE_args[1][j][1, 1].cvals, 1) for j in 1:nargs] + + op_offsets_test = [0] + op_offsets_ansatz = [0] + op_offsets_args = [0] + append!(op_offsets_test, cumsum(op_lengths_test)) + append!(op_offsets_ansatz, cumsum(op_lengths_ansatz)) + append!(op_offsets_args, cumsum(op_lengths_args)) + offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] + offsets_ansatz = [FE_ansatz[j].offsetY for j in 1:length(FES_ansatz)] + offsets_args = [FE_args[j].offset for j in 1:length(FES_args)] + + ## prepare sparsity pattern + use_sparsity_pattern = O.parameters[:use_sparsity_pattern] + if use_sparsity_pattern == "auto" + use_sparsity_pattern = false + end + coupling_matrix::Matrix{Bool} = ones(Bool, nansatz, ntest) + if use_sparsity_pattern + kernel_params = (result, input) -> (O.kernel(result, input, O.QP_infos[1])) + sparsity_pattern = SparseMatrixCSC{Float64, Int}(Symbolics.jacobian_sparsity(kernel_params, zeros(Tv, op_offsets_test[end]), zeros(Tv, op_offsets_ansatz[end]))) + + ## find out which test and ansatz functions couple + for id in 1:nansatz + for idt in 1:ntest + couple = false + for j in 1:op_lengths_ansatz[id] + for k in 1:op_lengths_test[idt] + if sparsity_pattern[k + op_offsets_test[idt], j + op_offsets_ansatz[id]] > 0 + couple = true + end + end + end + coupling_matrix[id, idt] = couple + end + end + end + couples_with::Vector{Vector{Int}} = [findall(==(true), view(coupling_matrix, j, :)) for j in 1:nansatz] + + ## prepare parallel assembly + if O.parameters[:parallel_groups] + Aj = Array{typeof(A), 1}(undef, length(EGs)) + for j in 1:length(EGs) + Aj[j] = copy(A) + end + end + + FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), ON_CELLS) for j in 1:ntest] + FEATs_ansatz = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_ansatz[j]), ON_CELLS) for j in 1:nansatz] + FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), ON_CELLS) for j in 1:nargs] + itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j in 1:ntest] + itemdofs_ansatz::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_ansatz[j], xgrid, FEATs_ansatz[j]) for j in 1:nansatz] + itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j in 1:nargs] + factor = O.parameters[:factor] + transposed_copy = O.parameters[:transposed_copy] + entry_tol = O.parameters[:entry_tolerance] + lump = O.parameters[:lump] + + ## Assembly loop for fixed geometry + function assembly_loop( + A::AbstractSparseArray{T}, + sol::Array{<:FEVectorBlock, 1}, + items, + EG::ElementGeometries, + QF::QuadratureRule, + BE_test::Vector{Matrix{<:FEEvaluator}}, + BE_ansatz::Vector{Matrix{<:FEEvaluator}}, + BE_args::Vector{Matrix{<:FEEvaluator}}, + BE_test_vals::Vector{Matrix{Array{Tv, 3}}}, + BE_ansatz_vals::Vector{Matrix{Array{Tv, 3}}}, + BE_args_vals::Vector{Matrix{Array{Tv, 3}}}, + L2G::L2GTransformer, + QPinfos::QPInfos, + part = 1, + ) where {T} + + input_ansatz = zeros(T, op_offsets_ansatz[end]) + result_kernel = zeros(T, op_offsets_test[end]) + itemorientations = xgrid[CellFaceOrientations] + itemcells = xgrid[FaceCells] + itemnormals = xgrid[FaceNormals] + cellitems = xgrid[CellFaces] + + ndofs_test::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_test] + ndofs_ansatz::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_ansatz] + ndofs_args::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_args] + + Aloc = Matrix{Matrix{T}}(undef, ntest, nansatz) + for j in 1:ntest, k in 1:nansatz + Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_ansatz[k]) + end + weights, xref = QF.w, QF.xref + nweights = length(weights) + cell1::Int = 0 + cell2::Int = 0 + orientation1::Int = 0 + orientation2::Int = 0 + itempos1::Int = 0 + itempos2::Int = 0 + + input_args = [zeros(T, op_offsets_args[end]) for j in 1:nweights] + + for item::Int in items + QPinfos.region = itemregions[item] + QPinfos.item = item + QPinfos.normal .= view(itemnormals, :, item) + QPinfos.volume = itemvolumes[item] + update_trafo!(L2G, item) + + boundary_face = itemcells[2, item] == 0 + if AT <: ON_IFACES + if boundary_face + continue + end + end + + ## evaluate arguments at all quadrature points + for qp in 1:nweights + fill!(input_args[qp], 0) + for c1 in 1:2 + cell1 = itemcells[c1, item] + if (cell1 > 0) + itempos1 = 1 + while !(cellitems[itempos1, cell1] == item) + itempos1 += 1 + end + orientation1 = itemorientations[itempos1, cell1] + + for j in 1:nargs + BE_args[j][itempos1, orientation1].citem[] = cell1 + update_basis!(BE_args[j][itempos1, orientation1]) + end + + for id in 1:nargs + for j in 1:ndofs_args[id] + dof_j = itemdofs_args[id][j, cell1] + for d in 1:op_lengths_args[id] + input_args[qp][d + op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][itempos1, orientation1][d, j, qp] * coeffs_ops_args[id][c1] + end + end + end + end + end + end + + for c1 in 1:2, c2 in 1:2 + cell1 = itemcells[c1, item] # current cell of test function + cell2 = itemcells[c2, item] # current cell of ansatz function + if (cell1 > 0) && (cell2 > 0) + QPinfos.cell = cell2 + itempos1 = 1 + while !(cellitems[itempos1, cell1] == item) + itempos1 += 1 + end + itempos2 = 1 + while !(cellitems[itempos2, cell2] == item) + itempos2 += 1 + end + orientation1 = itemorientations[itempos1, cell1] + orientation2 = itemorientations[itempos2, cell2] + + ## update FE basis evaluators + for j in 1:ntest + BE_test[j][itempos1, orientation1].citem[] = cell1 + update_basis!(BE_test[j][itempos1, orientation1]) + end + for j in 1:nansatz + BE_ansatz[j][itempos2, orientation2].citem[] = cell2 + update_basis!(BE_ansatz[j][itempos2, orientation2]) + end + + ## evaluate arguments + for qp in 1:nweights + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # update matrix + for id in 1:nansatz + coeff_ansatz = boundary_face ? 1 : coeffs_ops_ansatz[id][c2] + for j in 1:ndofs_ansatz[id] + # evaluate kernel for ansatz basis function on cell 2 + fill!(input_ansatz, 0) + for d in 1:op_lengths_ansatz[id] + input_ansatz[d + op_offsets_ansatz[id]] = BE_ansatz_vals[id][itempos2, orientation2][d, j, qp] * coeff_ansatz + end + + # evaluate kernel + O.kernel(result_kernel, input_ansatz, input_args[qp], QPinfos) + result_kernel .*= factor * weights[qp] + + # multiply test function operator evaluation on cell 1 + for idt in couples_with[id] + coeff_test = boundary_face ? 1 : coeffs_ops_test[idt][c1] + for k in 1:ndofs_test[idt] + for d in 1:op_lengths_test[idt] + Aloc[idt, id][k, j] += result_kernel[d + op_offsets_test[idt]] * BE_test_vals[idt][itempos1, orientation1][d, k, qp] * coeff_test + end + end + end + end + end + end + + + ## add local matrices to global matrix + for id in 1:nansatz, idt in 1:ntest + Aloc[idt, id] .*= itemvolumes[item] + for j in 1:ndofs_test[idt] + dof_j = itemdofs_test[idt][j, cell1] + offsets_test[idt] + for k in 1:ndofs_ansatz[id] + dof_k = itemdofs_ansatz[id][k, cell2] + offsets_ansatz[id] + if abs(Aloc[idt, id][j, k]) > entry_tol + rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) + end + end + end + end + if transposed_copy != 0 + for id in 1:nansatz, idt in 1:ntest + Aloc[idt, id] .*= transposed_copy + for j in 1:ndofs_test[idt] + dof_j = itemdofs_test[idt][j, cell1] + offsets_test[idt] + for k in 1:ndofs_ansatz[id] + dof_k = itemdofs_ansatz[id][k, cell2] + offsets_ansatz[id] + if abs(Aloc[idt, id][j, k]) > entry_tol + rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_k, dof_j, part) + end + end + end + end + end + + for id in 1:nansatz, idt in 1:ntest + fill!(Aloc[idt, id], 0) + end + end + end + end + return + end + O.FES_test = FES_test + O.FES_ansatz = FES_ansatz + O.FES_args = FES_args + + function assembler(A, b, sol; kwargs...) + time = @elapsed begin + if O.parameters[:parallel] + pcp = xgrid[PColorPartitions] + ncolors = length(pcp) - 1 + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + for color in 1:ncolors + Threads.@threads for part in pcp[color]:(pcp[color + 1] - 1) + assembly_loop( + A, + itemassemblygroups[part], + EGs[part], + O.QF[part], + O.BE_test[part], + O.BE_ansatz[part], + O.BE_args[part], + O.BE_test_vals[part], + O.BE_ansatz_vals[part], + O.BE_args_vals[part], + O.L2G[part], + O.QP_infos[part], + part; + kwargs..., + ) + end + end + elseif O.parameters[:parallel_groups] + Threads.@threads for j in 1:length(EGs) + fill!(Aj[j].cscmatrix.nzval, 0) + assembly_loop(Aj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_args[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + flush!(Aj[j]) + end + for j in 1:length(EGs) + A.cscmatrix += Aj[j].cscmatrix + end + else + for j in 1:length(EGs) + assembly_loop(A, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_args[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + flush!(A) + end + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end + end + O.assembler = assembler + end end function build_assembler!(A, O::BilinearOperatorDG{Tv}, FE_test, FE_ansatz; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_test = [getFEStest(FE_test[j]) for j ∈ 1:length(FE_test)] - FES_ansatz = [getFESansatz(FE_ansatz[j]) for j ∈ 1:length(FE_ansatz)] - - if (O.FES_test != FES_test) || (O.FES_ansatz != FES_ansatz) - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_test, FES_ansatz) - - ## prepare assembly - AT = O.parameters[:entities] - @assert AT <: ON_FACES || AT <: ON_BFACES "only works for entities <: ON_FACES or ON_BFACES" - Ti = typeof(xgrid).parameters[2] - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) - - - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if num_pcolors(xgrid) > 1 - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pe = xgrid[PartitionEdges] - itemassemblygroups = [pe[j]:pe[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - - FETypes_test = [eltype(F) for F in FES_test] - FETypes_ansatz = [eltype(F) for F in FES_ansatz] - EGs = num_pcolors(xgrid) > 1 ? [xgrid[UniqueCellGeometries][1] for j ∈ 1:num_partitions(xgrid)] : xgrid[UniqueCellGeometries] - - coeffs_ops_test = Array{Array{Float64, 1}, 1}([]) - coeffs_ops_ansatz = Array{Array{Float64, 1}, 1}([]) - for op in O.ops_test - push!(coeffs_ops_test, coeffs(op)) - end - for op in O.ops_ansatz - push!(coeffs_ops_ansatz, coeffs(op)) - end - - ## prepare assembly - ntest = length(FES_test) - nansatz = length(FES_ansatz) - O.QF = [] - O.BE_test = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) - O.BE_ansatz = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) - O.BE_test_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) - O.BE_ansatz_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_ansatz = maximum([get_polynomialorder(FETypes_ansatz[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_ansatz[j]) for j ∈ 1:nansatz]) - polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j ∈ 1:ntest]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_ansatz + polyorder_test + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - - ## generate DG operator - push!(O.BE_test, [generate_DG_operators(StandardFunctionOperator(O.ops_test[j]), FES_test[j], quadorder, EG) for j ∈ 1:ntest]) - push!(O.BE_ansatz, [generate_DG_operators(StandardFunctionOperator(O.ops_ansatz[j]), FES_ansatz[j], quadorder, EG) for j ∈ 1:nansatz]) - push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) - - ## L2G map for EG - EGface = facetype_of_cellface(EG, 1) - push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) - - ## FE basis evaluator for EG - push!(O.BE_test_vals, [[O.BE_test[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_test[end][k])] for k ∈ 1:ntest]) - push!(O.BE_ansatz_vals, [[O.BE_ansatz[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_ansatz[end][k])] for k ∈ 1:nansatz]) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[regions] .= true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_test = [size(O.BE_test[1][j][1, 1].cvals, 1) for j ∈ 1:ntest] - op_lengths_ansatz = [size(O.BE_ansatz[1][j][1, 1].cvals, 1) for j ∈ 1:nansatz] - - op_offsets_test = [0] - op_offsets_ansatz = [0] - append!(op_offsets_test, cumsum(op_lengths_test)) - append!(op_offsets_ansatz, cumsum(op_lengths_ansatz)) - offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] - offsets_ansatz = [FE_ansatz[j].offsetY for j in 1:length(FES_ansatz)] - - ## prepare sparsity pattern - use_sparsity_pattern = O.parameters[:use_sparsity_pattern] - if use_sparsity_pattern == "auto" - use_sparsity_pattern = ntest > 1 - end - coupling_matrix::Matrix{Bool} = ones(Bool, nansatz, ntest) - if use_sparsity_pattern - kernel_params = (result, input) -> (O.kernel(result, input, O.QP_infos[1])) - sparsity_pattern = SparseMatrixCSC{Float64, Int}(Symbolics.jacobian_sparsity(kernel_params, zeros(Tv, op_offsets_test[end]), zeros(Tv, op_offsets_ansatz[end]))) - - ## find out which test and ansatz functions couple - for id ∈ 1:nansatz - for idt ∈ 1:ntest - couple = false - for j ∈ 1:op_lengths_ansatz[id] - for k ∈ 1:op_lengths_test[idt] - if sparsity_pattern[k+op_offsets_test[id], j+op_offsets_ansatz[idt]] > 0 - couple = true - end - end - end - coupling_matrix[id, idt] = couple - end - end - end - couples_with::Vector{Vector{Int}} = [findall(==(true), view(coupling_matrix, j, :)) for j ∈ 1:nansatz] - - ## prepare parallel assembly - if O.parameters[:parallel_groups] - Aj = Array{typeof(A), 1}(undef, length(EGs)) - for j ∈ 1:length(EGs) - Aj[j] = copy(A) - end - end - - FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), ON_CELLS) for j ∈ 1:ntest] - FEATs_ansatz = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_ansatz[j]), ON_CELLS) for j ∈ 1:nansatz] - itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j ∈ 1:ntest] - itemdofs_ansatz::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_ansatz[j], xgrid, FEATs_ansatz[j]) for j ∈ 1:nansatz] - factor = O.parameters[:factor] - transposed_copy = O.parameters[:transposed_copy] - entry_tol = O.parameters[:entry_tolerance] - lump = O.parameters[:lump] - - ## Assembly loop for fixed geometry - function assembly_loop( - A::AbstractSparseArray{T}, - items, - EG::ElementGeometries, - QF::QuadratureRule, - BE_test::Vector{Matrix{<:FEEvaluator}}, - BE_ansatz::Vector{Matrix{<:FEEvaluator}}, - BE_test_vals::Vector{Matrix{Array{Tv, 3}}}, - BE_ansatz_vals::Vector{Matrix{Array{Tv, 3}}}, - L2G::L2GTransformer, - QPinfos::QPInfos, - part = 1, - ) where {T} - - input_ansatz = zeros(T, op_offsets_ansatz[end]) - result_kernel = zeros(T, op_offsets_test[end]) - itemorientations = xgrid[CellFaceOrientations] - itemcells = xgrid[FaceCells] - itemnormals = xgrid[FaceNormals] - cellitems = xgrid[CellFaces] - - #ndofs_test::Array{Int,1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_test] - #ndofs_ansatz::Array{Int,1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_ansatz] - ndofs_test::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_test] - ndofs_ansatz::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_ansatz] - - Aloc = Matrix{Matrix{T}}(undef, ntest, nansatz) - for j ∈ 1:ntest, k ∈ 1:nansatz - Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_ansatz[k]) - end - weights, xref = QF.w, QF.xref - nweights = length(weights) - cell1::Int = 0 - cell2::Int = 0 - orientation1::Int = 0 - orientation2::Int = 0 - itempos1::Int = 0 - itempos2::Int = 0 - - ## loop over faces - ## got into neighbouring cells and evaluate each operator according to - ## facepos and orientation - for item::Int in items - - QPinfos.region = itemregions[item] - QPinfos.item = item - QPinfos.normal .= view(itemnormals, :, item) - QPinfos.volume = itemvolumes[item] - update_trafo!(L2G, item) - - boundary_face = itemcells[2, item] == 0 - if AT <: ON_IFACES - if boundary_face - continue - end - end - - for c1 ∈ 1:2, c2 ∈ 1:2 - cell1 = itemcells[c1, item] # current cell of test function - cell2 = itemcells[c2, item] # current cell of ansatz function - if (cell1 > 0) && (cell2 > 0) - QPinfos.cell = cell2 # give cell of input for kernel - itempos1 = 1 - while !(cellitems[itempos1, cell1] == item) - itempos1 += 1 - end - itempos2 = 1 - while !(cellitems[itempos2, cell2] == item) - itempos2 += 1 - end - orientation1 = itemorientations[itempos1, cell1] - orientation2 = itemorientations[itempos2, cell2] - - ## update FE basis evaluators - for j ∈ 1:ntest - BE_test[j][itempos1, orientation1].citem[] = cell1 - update_basis!(BE_test[j][itempos1, orientation1]) - end - for j ∈ 1:nansatz - BE_ansatz[j][itempos2, orientation2].citem[] = cell2 - update_basis!(BE_ansatz[j][itempos2, orientation2]) - end - - ## evaluate arguments - for qp ∈ 1:nweights - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # update matrix - for id ∈ 1:nansatz - if coeffs_ops_ansatz[id][c2] != 0 - coeff_ansatz = boundary_face ? 1 : coeffs_ops_ansatz[id][c2] - for j ∈ 1:ndofs_ansatz[id] - # evaluate kernel for ansatz basis function on cell 2 - fill!(input_ansatz, 0) - for d ∈ 1:op_lengths_ansatz[id] - input_ansatz[d+op_offsets_ansatz[id]] = BE_ansatz_vals[id][itempos2, orientation2][d, j, qp] * coeff_ansatz - end - - # evaluate kernel - O.kernel(result_kernel, input_ansatz, QPinfos) - result_kernel .*= factor * weights[qp] - - # multiply test function operator evaluation on cell 1 - for idt in couples_with[id] - coeff_test = boundary_face ? 1 : coeffs_ops_test[idt][c1] - for k ∈ 1:ndofs_test[idt] - for d ∈ 1:op_lengths_test[idt] - Aloc[idt, id][k, j] += result_kernel[d+op_offsets_test[idt]] * BE_test_vals[idt][itempos1, orientation1][d, k, qp] * coeff_test - end - end - end - end - end - end - end - - - ## add local matrices to global matrix - for id ∈ 1:nansatz, idt ∈ 1:ntest - Aloc[idt, id] .*= itemvolumes[item] - for j ∈ 1:ndofs_test[idt] - dof_j = itemdofs_test[idt][j, cell1] + offsets_test[idt] - for k ∈ 1:ndofs_ansatz[id] - dof_k = itemdofs_ansatz[id][k, cell2] + offsets_ansatz[id] - if abs(Aloc[idt, id][j, k]) > entry_tol - rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) - end - end - end - end - if transposed_copy != 0 - for id ∈ 1:nansatz, idt ∈ 1:ntest - Aloc[idt, id] .*= transposed_copy - for j ∈ 1:ndofs_test[idt] - dof_j = itemdofs_test[idt][j, cell1] + offsets_test[idt] - for k ∈ 1:ndofs_ansatz[id] - dof_k = itemdofs_ansatz[id][k, cell2] + offsets_ansatz[id] - if abs(Aloc[idt, id][j, k]) > entry_tol - rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_k, dof_j, part) - end - end - end - end - end - - for id ∈ 1:nansatz, idt ∈ 1:ntest - fill!(Aloc[idt, id], 0) - end - end - end - end - return - end - O.FES_test = FES_test - O.FES_ansatz = FES_ansatz - - function assembler(A, b; kwargs...) - if O.parameters[:store] && size(A) == size(O.storage) - A.cscmatrix += O.storage.cscmatrix - else - if O.parameters[:store] - S = ExtendableSparseMatrix{Float64, Int}(size(A, 1), size(A, 2)) - else - S = A - end - time = @elapsed begin - if O.parameters[:parallel] - pcp = xgrid[PColorPartitions] - ncolors = length(pcp) - 1 - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - for color in 1:ncolors - Threads.@threads for part in pcp[color]:pcp[color+1]-1 - assembly_loop(S, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_ansatz[part], O.BE_test_vals[part], O.BE_ansatz_vals[part], O.L2G[part], O.QP_infos[part], part; kwargs...) - end - end - elseif O.parameters[:parallel_groups] - Threads.@threads for j ∈ 1:length(EGs) - fill!(Aj[j].cscmatrix.nzval, 0) - assembly_loop(Aj[j], itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - flush!(Aj[j]) - end - for j ∈ 1:length(EGs) - S.cscmatrix += Aj[j].cscmatrix - end - else - for j ∈ 1:length(EGs) - assembly_loop(S, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - flush!(S) - end - if O.parameters[:callback!] !== nothing - S = O.parameters[:callback!](S, b, sol) - end - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end - if O.parameters[:store] - A.cscmatrix += S.cscmatrix - O.storage = S - end - end - end - O.assembler = assembler - end + ## check if FES is the same as last time + FES_test = [getFEStest(FE_test[j]) for j in 1:length(FE_test)] + FES_ansatz = [getFESansatz(FE_ansatz[j]) for j in 1:length(FE_ansatz)] + + return if (O.FES_test != FES_test) || (O.FES_ansatz != FES_ansatz) + + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_test, FES_ansatz) + + ## prepare assembly + AT = O.parameters[:entities] + @assert AT <: ON_FACES || AT <: ON_BFACES "only works for entities <: ON_FACES or ON_BFACES" + Ti = typeof(xgrid).parameters[2] + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) + + + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if num_pcolors(xgrid) > 1 + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pe = xgrid[PartitionEdges] + itemassemblygroups = [pe[j]:(pe[j + 1] - 1) for j in 1:num_partitions(xgrid)] + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + + FETypes_test = [eltype(F) for F in FES_test] + FETypes_ansatz = [eltype(F) for F in FES_ansatz] + EGs = num_pcolors(xgrid) > 1 ? [xgrid[UniqueCellGeometries][1] for j in 1:num_partitions(xgrid)] : xgrid[UniqueCellGeometries] + + coeffs_ops_test = Array{Array{Float64, 1}, 1}([]) + coeffs_ops_ansatz = Array{Array{Float64, 1}, 1}([]) + for op in O.ops_test + push!(coeffs_ops_test, coeffs(op)) + end + for op in O.ops_ansatz + push!(coeffs_ops_ansatz, coeffs(op)) + end + + ## prepare assembly + ntest = length(FES_test) + nansatz = length(FES_ansatz) + O.QF = [] + O.BE_test = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) + O.BE_ansatz = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) + O.BE_test_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) + O.BE_ansatz_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_ansatz = maximum([get_polynomialorder(FETypes_ansatz[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_ansatz[j]) for j in 1:nansatz]) + polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j in 1:ntest]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_ansatz + polyorder_test + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + + ## generate DG operator + push!(O.BE_test, [generate_DG_operators(StandardFunctionOperator(O.ops_test[j]), FES_test[j], quadorder, EG) for j in 1:ntest]) + push!(O.BE_ansatz, [generate_DG_operators(StandardFunctionOperator(O.ops_ansatz[j]), FES_ansatz[j], quadorder, EG) for j in 1:nansatz]) + push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) + + ## L2G map for EG + EGface = facetype_of_cellface(EG, 1) + push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) + + ## FE basis evaluator for EG + push!(O.BE_test_vals, [[O.BE_test[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_test[end][k])] for k in 1:ntest]) + push!(O.BE_ansatz_vals, [[O.BE_ansatz[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_ansatz[end][k])] for k in 1:nansatz]) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[regions] .= true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_test = [size(O.BE_test[1][j][1, 1].cvals, 1) for j in 1:ntest] + op_lengths_ansatz = [size(O.BE_ansatz[1][j][1, 1].cvals, 1) for j in 1:nansatz] + + op_offsets_test = [0] + op_offsets_ansatz = [0] + append!(op_offsets_test, cumsum(op_lengths_test)) + append!(op_offsets_ansatz, cumsum(op_lengths_ansatz)) + offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] + offsets_ansatz = [FE_ansatz[j].offsetY for j in 1:length(FES_ansatz)] + + ## prepare sparsity pattern + use_sparsity_pattern = O.parameters[:use_sparsity_pattern] + if use_sparsity_pattern == "auto" + use_sparsity_pattern = ntest > 1 + end + coupling_matrix::Matrix{Bool} = ones(Bool, nansatz, ntest) + if use_sparsity_pattern + kernel_params = (result, input) -> (O.kernel(result, input, O.QP_infos[1])) + sparsity_pattern = SparseMatrixCSC{Float64, Int}(Symbolics.jacobian_sparsity(kernel_params, zeros(Tv, op_offsets_test[end]), zeros(Tv, op_offsets_ansatz[end]))) + + ## find out which test and ansatz functions couple + for id in 1:nansatz + for idt in 1:ntest + couple = false + for j in 1:op_lengths_ansatz[id] + for k in 1:op_lengths_test[idt] + if sparsity_pattern[k + op_offsets_test[id], j + op_offsets_ansatz[idt]] > 0 + couple = true + end + end + end + coupling_matrix[id, idt] = couple + end + end + end + couples_with::Vector{Vector{Int}} = [findall(==(true), view(coupling_matrix, j, :)) for j in 1:nansatz] + + ## prepare parallel assembly + if O.parameters[:parallel_groups] + Aj = Array{typeof(A), 1}(undef, length(EGs)) + for j in 1:length(EGs) + Aj[j] = copy(A) + end + end + + FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), ON_CELLS) for j in 1:ntest] + FEATs_ansatz = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_ansatz[j]), ON_CELLS) for j in 1:nansatz] + itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j in 1:ntest] + itemdofs_ansatz::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_ansatz[j], xgrid, FEATs_ansatz[j]) for j in 1:nansatz] + factor = O.parameters[:factor] + transposed_copy = O.parameters[:transposed_copy] + entry_tol = O.parameters[:entry_tolerance] + lump = O.parameters[:lump] + + ## Assembly loop for fixed geometry + function assembly_loop( + A::AbstractSparseArray{T}, + items, + EG::ElementGeometries, + QF::QuadratureRule, + BE_test::Vector{Matrix{<:FEEvaluator}}, + BE_ansatz::Vector{Matrix{<:FEEvaluator}}, + BE_test_vals::Vector{Matrix{Array{Tv, 3}}}, + BE_ansatz_vals::Vector{Matrix{Array{Tv, 3}}}, + L2G::L2GTransformer, + QPinfos::QPInfos, + part = 1, + ) where {T} + + input_ansatz = zeros(T, op_offsets_ansatz[end]) + result_kernel = zeros(T, op_offsets_test[end]) + itemorientations = xgrid[CellFaceOrientations] + itemcells = xgrid[FaceCells] + itemnormals = xgrid[FaceNormals] + cellitems = xgrid[CellFaces] + + #ndofs_test::Array{Int,1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_test] + #ndofs_ansatz::Array{Int,1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_ansatz] + ndofs_test::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_test] + ndofs_ansatz::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_ansatz] + + Aloc = Matrix{Matrix{T}}(undef, ntest, nansatz) + for j in 1:ntest, k in 1:nansatz + Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_ansatz[k]) + end + weights, xref = QF.w, QF.xref + nweights = length(weights) + cell1::Int = 0 + cell2::Int = 0 + orientation1::Int = 0 + orientation2::Int = 0 + itempos1::Int = 0 + itempos2::Int = 0 + + ## loop over faces + ## got into neighbouring cells and evaluate each operator according to + ## facepos and orientation + for item::Int in items + + QPinfos.region = itemregions[item] + QPinfos.item = item + QPinfos.normal .= view(itemnormals, :, item) + QPinfos.volume = itemvolumes[item] + update_trafo!(L2G, item) + + boundary_face = itemcells[2, item] == 0 + if AT <: ON_IFACES + if boundary_face + continue + end + end + + for c1 in 1:2, c2 in 1:2 + cell1 = itemcells[c1, item] # current cell of test function + cell2 = itemcells[c2, item] # current cell of ansatz function + if (cell1 > 0) && (cell2 > 0) + QPinfos.cell = cell2 # give cell of input for kernel + itempos1 = 1 + while !(cellitems[itempos1, cell1] == item) + itempos1 += 1 + end + itempos2 = 1 + while !(cellitems[itempos2, cell2] == item) + itempos2 += 1 + end + orientation1 = itemorientations[itempos1, cell1] + orientation2 = itemorientations[itempos2, cell2] + + ## update FE basis evaluators + for j in 1:ntest + BE_test[j][itempos1, orientation1].citem[] = cell1 + update_basis!(BE_test[j][itempos1, orientation1]) + end + for j in 1:nansatz + BE_ansatz[j][itempos2, orientation2].citem[] = cell2 + update_basis!(BE_ansatz[j][itempos2, orientation2]) + end + + ## evaluate arguments + for qp in 1:nweights + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # update matrix + for id in 1:nansatz + if coeffs_ops_ansatz[id][c2] != 0 + coeff_ansatz = boundary_face ? 1 : coeffs_ops_ansatz[id][c2] + for j in 1:ndofs_ansatz[id] + # evaluate kernel for ansatz basis function on cell 2 + fill!(input_ansatz, 0) + for d in 1:op_lengths_ansatz[id] + input_ansatz[d + op_offsets_ansatz[id]] = BE_ansatz_vals[id][itempos2, orientation2][d, j, qp] * coeff_ansatz + end + + # evaluate kernel + O.kernel(result_kernel, input_ansatz, QPinfos) + result_kernel .*= factor * weights[qp] + + # multiply test function operator evaluation on cell 1 + for idt in couples_with[id] + coeff_test = boundary_face ? 1 : coeffs_ops_test[idt][c1] + for k in 1:ndofs_test[idt] + for d in 1:op_lengths_test[idt] + Aloc[idt, id][k, j] += result_kernel[d + op_offsets_test[idt]] * BE_test_vals[idt][itempos1, orientation1][d, k, qp] * coeff_test + end + end + end + end + end + end + end + + + ## add local matrices to global matrix + for id in 1:nansatz, idt in 1:ntest + Aloc[idt, id] .*= itemvolumes[item] + for j in 1:ndofs_test[idt] + dof_j = itemdofs_test[idt][j, cell1] + offsets_test[idt] + for k in 1:ndofs_ansatz[id] + dof_k = itemdofs_ansatz[id][k, cell2] + offsets_ansatz[id] + if abs(Aloc[idt, id][j, k]) > entry_tol + rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) + end + end + end + end + if transposed_copy != 0 + for id in 1:nansatz, idt in 1:ntest + Aloc[idt, id] .*= transposed_copy + for j in 1:ndofs_test[idt] + dof_j = itemdofs_test[idt][j, cell1] + offsets_test[idt] + for k in 1:ndofs_ansatz[id] + dof_k = itemdofs_ansatz[id][k, cell2] + offsets_ansatz[id] + if abs(Aloc[idt, id][j, k]) > entry_tol + rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_k, dof_j, part) + end + end + end + end + end + + for id in 1:nansatz, idt in 1:ntest + fill!(Aloc[idt, id], 0) + end + end + end + end + return + end + O.FES_test = FES_test + O.FES_ansatz = FES_ansatz + + function assembler(A, b; kwargs...) + return if O.parameters[:store] && size(A) == size(O.storage) + A.cscmatrix += O.storage.cscmatrix + else + if O.parameters[:store] + S = ExtendableSparseMatrix{Float64, Int}(size(A, 1), size(A, 2)) + else + S = A + end + time = @elapsed begin + if O.parameters[:parallel] + pcp = xgrid[PColorPartitions] + ncolors = length(pcp) - 1 + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + for color in 1:ncolors + Threads.@threads for part in pcp[color]:(pcp[color + 1] - 1) + assembly_loop(S, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_ansatz[part], O.BE_test_vals[part], O.BE_ansatz_vals[part], O.L2G[part], O.QP_infos[part], part; kwargs...) + end + end + elseif O.parameters[:parallel_groups] + Threads.@threads for j in 1:length(EGs) + fill!(Aj[j].cscmatrix.nzval, 0) + assembly_loop(Aj[j], itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + flush!(Aj[j]) + end + for j in 1:length(EGs) + S.cscmatrix += Aj[j].cscmatrix + end + else + for j in 1:length(EGs) + assembly_loop(S, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_ansatz[j], O.BE_test_vals[j], O.BE_ansatz_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + flush!(S) + end + if O.parameters[:callback!] !== nothing + S = O.parameters[:callback!](S, b, sol) + end + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end + if O.parameters[:store] + A.cscmatrix += S.cscmatrix + O.storage = S + end + end + end + O.assembler = assembler + end end function assemble!(A, b, sol, O::BilinearOperatorDG{Tv, UT}, SC::SolverConfiguration; assemble_matrix = true, kwargs...) where {Tv, UT} - if !assemble_matrix - return nothing - end - if UT <: Integer - ind_test = O.u_test - ind_ansatz = O.u_ansatz - ind_args = O.u_args - elseif UT <: Unknown - ind_test = [get_unknown_id(SC, u) for u in O.u_test] - ind_ansatz = [get_unknown_id(SC, u) for u in O.u_ansatz] - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] #[get_unknown_id(SC, u) for u in O.u_args] - end - if length(O.u_args) > 0 - build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz], [sol[j] for j in ind_args]; kwargs...) - O.assembler(A.entries, b.entries, [sol[j] for j in ind_args]) - else - build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz]; kwargs...) - O.assembler(A.entries, b.entries) - end + if !assemble_matrix + return nothing + end + if UT <: Integer + ind_test = O.u_test + ind_ansatz = O.u_ansatz + ind_args = O.u_args + elseif UT <: Unknown + ind_test = [get_unknown_id(SC, u) for u in O.u_test] + ind_ansatz = [get_unknown_id(SC, u) for u in O.u_ansatz] + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] #[get_unknown_id(SC, u) for u in O.u_args] + end + return if length(O.u_args) > 0 + build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz], [sol[j] for j in ind_args]; kwargs...) + O.assembler(A.entries, b.entries, [sol[j] for j in ind_args]) + else + build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz]; kwargs...) + O.assembler(A.entries, b.entries) + end end function assemble!(A::FEMatrix, O::BilinearOperatorDG{Tv, UT}, sol = nothing; assemble_matrix = true, kwargs...) where {Tv, UT} - if !assemble_matrix - return nothing - end - ind_test = O.u_test - ind_ansatz = O.u_ansatz - ind_args = O.u_args - if length(O.u_args) > 0 - build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz], [sol[j] for j in ind_args]; kwargs...) - O.assembler(A.entries, [sol[j] for j in ind_args]) - else - build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz]; kwargs...) - O.assembler(A.entries, nothing) - end + if !assemble_matrix + return nothing + end + ind_test = O.u_test + ind_ansatz = O.u_ansatz + ind_args = O.u_args + return if length(O.u_args) > 0 + build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz], [sol[j] for j in ind_args]; kwargs...) + O.assembler(A.entries, [sol[j] for j in ind_args]) + else + build_assembler!(A.entries, O, [A[j, j] for j in ind_test], [A[j, j] for j in ind_ansatz]; kwargs...) + O.assembler(A.entries, nothing) + end end diff --git a/src/common_operators/callback_operator.jl b/src/common_operators/callback_operator.jl index 7beb49a..ed6cac0 100644 --- a/src/common_operators/callback_operator.jl +++ b/src/common_operators/callback_operator.jl @@ -1,41 +1,40 @@ - mutable struct CallbackOperator{UT <: Union{Unknown, Integer}, FT, MT, bT} <: AbstractOperator - callback::FT - u_args::Array{UT, 1} - storage_A::MT - storage_b::bT - parameters::Dict{Symbol, Any} + callback::FT + u_args::Array{UT, 1} + storage_A::MT + storage_b::bT + parameters::Dict{Symbol, Any} end default_cbop_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :name => ("CallbackOperator", "name for operator used in printouts"), - :time_dependent => (false, "operator is time-dependent ?"), - :linearized_dependencies => (:auto, "[u_ansatz, u_test] when linearized"), - :modifies_matrix => (true, "callback function modifies the matrix?"), - :modifies_rhs => (true, "callback function modifies the rhs?"), - :store => (false, "store matrix and rhs separately (and copy from there when reassembly is triggered)"), - :verbosity => (0, "verbosity level"), + :name => ("CallbackOperator", "name for operator used in printouts"), + :time_dependent => (false, "operator is time-dependent ?"), + :linearized_dependencies => (:auto, "[u_ansatz, u_test] when linearized"), + :modifies_matrix => (true, "callback function modifies the matrix?"), + :modifies_rhs => (true, "callback function modifies the rhs?"), + :store => (false, "store matrix and rhs separately (and copy from there when reassembly is triggered)"), + :verbosity => (0, "verbosity level"), ) # informs solver when operator needs reassembly function depends_nonlinearly_on(O::CallbackOperator) - return O.u_args + return O.u_args end # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::CallbackOperator) - return O.parameters[:linearized_dependencies] + return O.parameters[:linearized_dependencies] end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::CallbackOperator) - return O.parameters[:time_dependent] + return O.parameters[:time_dependent] end function Base.show(io::IO, O::CallbackOperator) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($([ansatz_function(dependencies[1][j]) for j = 1 : length(dependencies[1])]), $([test_function(dependencies[2][j]) for j = 1 : length(dependencies[2])]))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($([ansatz_function(dependencies[1][j]) for j in 1:length(dependencies[1])]), $([test_function(dependencies[2][j]) for j in 1:length(dependencies[2])]))") + return nothing end @@ -61,54 +60,54 @@ $(_myprint(default_cbop_kwargs())) """ function CallbackOperator(callback, u_args = []; kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_cbop_kwargs()) - _update_params!(parameters, kwargs) - if parameters[:linearized_dependencies] == :auto - parameters[:linearized_dependencies] = [u_args, u_args] - end - if parameters[:store] && parameters[:modifies_matrix] - storage_A = ExtendableSparseMatrix{Float64, Int}(0, 0) - else - storage_A = nothing - end - if parameters[:store] && parameters[:modifies_rhs] - storage_b = Vector{Float64}(0) - else - storage_b = nothing - end - return CallbackOperator{eltype(u_args), typeof(callback), typeof(storage_A), typeof(storage_b)}(callback, u_args, storage_A, storage_b, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_cbop_kwargs()) + _update_params!(parameters, kwargs) + if parameters[:linearized_dependencies] == :auto + parameters[:linearized_dependencies] = [u_args, u_args] + end + if parameters[:store] && parameters[:modifies_matrix] + storage_A = ExtendableSparseMatrix{Float64, Int}(0, 0) + else + storage_A = nothing + end + if parameters[:store] && parameters[:modifies_rhs] + storage_b = Vector{Float64}(0) + else + storage_b = nothing + end + return CallbackOperator{eltype(u_args), typeof(callback), typeof(storage_A), typeof(storage_b)}(callback, u_args, storage_A, storage_b, parameters) end function assemble!(A, b, sol, O::CallbackOperator{UT}, SC::SolverConfiguration; time = 0, assemble_matrix = true, assemble_rhs = true, kwargs...) where {UT} - if O.parameters[:store] && size(A) == size(O.storage) - add!(A, O.storage_A) - add!(b, O.storage_b) - else - if UT <: Integer - ind_args = O.u_args - elseif UT <: Unknown - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] - end - if O.parameters[:store] - if O.parameters[:modifies_matrix] - O.storage_A = ExtendableSparseMatrix{Float64, Int}(size(A, 1), size(A, 2)) - end - if O.parameters[:modifies_rhs] - O.storage_b = Vector(Float64)(length(b)) - end - end - if O.parameters[:store] - O.callback(O.storage_A, O.storage_b, [sol[j] for j in ind_args]; time = time, assemble_matrix = assemble_matrix, assemble_rhs = assemble_rhs, kwargs...) - if O.parameters[:modifies_matrix] - flush!(O.storage_A) - add!(A, O.storage_A) - end - if O.parameters[:modifies_rhs] - add!(b, O.storage_b) - end - else - O.callback(A.entries, b.entries, [sol[j] for j in ind_args]; time = time, assemble_matrix = assemble_matrix, assemble_rhs = assemble_rhs, kwargs...) - flush!(A.entries) - end - end + return if O.parameters[:store] && size(A) == size(O.storage) + add!(A, O.storage_A) + add!(b, O.storage_b) + else + if UT <: Integer + ind_args = O.u_args + elseif UT <: Unknown + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] + end + if O.parameters[:store] + if O.parameters[:modifies_matrix] + O.storage_A = ExtendableSparseMatrix{Float64, Int}(size(A, 1), size(A, 2)) + end + if O.parameters[:modifies_rhs] + O.storage_b = Vector(Float64)(length(b)) + end + end + if O.parameters[:store] + O.callback(O.storage_A, O.storage_b, [sol[j] for j in ind_args]; time = time, assemble_matrix = assemble_matrix, assemble_rhs = assemble_rhs, kwargs...) + if O.parameters[:modifies_matrix] + flush!(O.storage_A) + add!(A, O.storage_A) + end + if O.parameters[:modifies_rhs] + add!(b, O.storage_b) + end + else + O.callback(A.entries, b.entries, [sol[j] for j in ind_args]; time = time, assemble_matrix = assemble_matrix, assemble_rhs = assemble_rhs, kwargs...) + flush!(A.entries) + end + end end diff --git a/src/common_operators/combinedofs.jl b/src/common_operators/combinedofs.jl index b5cea7e..c547734 100644 --- a/src/common_operators/combinedofs.jl +++ b/src/common_operators/combinedofs.jl @@ -1,33 +1,32 @@ - ########################################### ### COMBINE DOFS (e.g. for periodicity) ### ########################################### mutable struct CombineDofs{XT, UT, YT, FT} <: AbstractOperator - uX::UT # component nr for dofsX - uY::UT # component nr for dofsY - dofsX::XT # dofsX that should be the same as dofsY in Y component - dofsY::YT - factors::FT - FESX::Any - FESY::Any - assembler::Any - parameters::Dict{Symbol, Any} + uX::UT # component nr for dofsX + uY::UT # component nr for dofsY + dofsX::XT # dofsX that should be the same as dofsY in Y component + dofsY::YT + factors::FT + FESX::Any + FESY::Any + assembler::Any + parameters::Dict{Symbol, Any} end default_combop_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :penalty => (1e30, "penalty for fixed degrees of freedom"), - :verbosity => (0, "verbosity level"), + :penalty => (1.0e30, "penalty for fixed degrees of freedom"), + :verbosity => (0, "verbosity level"), ) # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::CombineDofs) - return [O.uX, O.uY] + return [O.uX, O.uY] end function fixed_dofs(O::CombineDofs) - ## assembles operator to full matrix A and b - return O.dofsY + ## assembles operator to full matrix A and b + return O.dofsY end @@ -45,73 +44,73 @@ $(_myprint(default_combop_kwargs())) """ function CombineDofs(uX, uY, dofsX, dofsY, factors = ones(Int, length(X)); kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_combop_kwargs()) - _update_params!(parameters, kwargs) - @assert length(dofsX) == length(dofsY) - return CombineDofs(uX, uY, dofsX, dofsY, factors, nothing, nothing, nothing, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_combop_kwargs()) + _update_params!(parameters, kwargs) + @assert length(dofsX) == length(dofsY) + return CombineDofs(uX, uY, dofsX, dofsY, factors, nothing, nothing, nothing, parameters) end function apply_penalties!(A, b, sol, O::CombineDofs{Tv, UT}, SC::SolverConfiguration; assemble_matrix = true, assemble_rhs = true, kwargs...) where {Tv, UT} - if UT <: Integer - ind = [O.ux, O.uY] - elseif UT <: Unknown - ind = [get_unknown_id(SC, O.uX), get_unknown_id(SC, O.uY)] - end - build_assembler!(O, [sol[j] for j in ind]) - O.assembler(A.entries, b.entries, assemble_matrix, assemble_rhs) + if UT <: Integer + ind = [O.ux, O.uY] + elseif UT <: Unknown + ind = [get_unknown_id(SC, O.uX), get_unknown_id(SC, O.uY)] + end + build_assembler!(O, [sol[j] for j in ind]) + return O.assembler(A.entries, b.entries, assemble_matrix, assemble_rhs) end function build_assembler!(O::CombineDofs{Tv}, FE::Array{<:FEVectorBlock, 1}; time = 0.0) where {Tv} - ## check if FES is the same as last time - FESX, FESY = FE[1].FES, FE[2].FES - if (O.FESX != FESX) || (O.FESY != FESY) - dofsX = O.dofsX - dofsY = O.dofsY - offsetX = FE[1].offset - offsetY = FE[2].offset - factors = O.factors - if O.parameters[:verbosity] > 0 - @info ".... combining $(length(dofsX)) dofs" - end - function assemble(A::AbstractSparseArray{T}, b::AbstractVector{T}, assemble_matrix::Bool, assemble_rhs::Bool, kwargs...) where {T} - if assemble_matrix - targetrow::Int = 0 - sourcerow::Int = 0 - targetcol::Int = 0 - sourcecol::Int = 0 - val::Float64 = 0 - ncols::Int = size(A, 2) - for gdof in eachindex(dofsX) - # copy source row (for dofY) to target row (for dofX) - targetrow = dofsX[gdof] + offsetX - sourcerow = offsetY + dofsY[gdof] - for sourcecol ∈ 1:ncols - targetcol = sourcecol - offsetY + offsetX - val = A[sourcerow, sourcecol] - _addnz(A, targetrow, targetcol, factors[gdof] * val, 1) - A[sourcerow, sourcecol] = 0 - end + ## check if FES is the same as last time + FESX, FESY = FE[1].FES, FE[2].FES + return if (O.FESX != FESX) || (O.FESY != FESY) + dofsX = O.dofsX + dofsY = O.dofsY + offsetX = FE[1].offset + offsetY = FE[2].offset + factors = O.factors + if O.parameters[:verbosity] > 0 + @info ".... combining $(length(dofsX)) dofs" + end + function assemble(A::AbstractSparseArray{T}, b::AbstractVector{T}, assemble_matrix::Bool, assemble_rhs::Bool, kwargs...) where {T} + if assemble_matrix + targetrow::Int = 0 + sourcerow::Int = 0 + targetcol::Int = 0 + sourcecol::Int = 0 + val::Float64 = 0 + ncols::Int = size(A, 2) + for gdof in eachindex(dofsX) + # copy source row (for dofY) to target row (for dofX) + targetrow = dofsX[gdof] + offsetX + sourcerow = offsetY + dofsY[gdof] + for sourcecol in 1:ncols + targetcol = sourcecol - offsetY + offsetX + val = A[sourcerow, sourcecol] + _addnz(A, targetrow, targetcol, factors[gdof] * val, 1) + A[sourcerow, sourcecol] = 0 + end - # replace source row (of dofY) with equation for coupling the two dofs - sourcecol = dofsY[gdof] + offsetY - targetcol = dofsX[gdof] + offsetX - sourcerow = offsetY + dofsY[gdof] - _addnz(A, sourcerow, targetcol, 1, 1) - _addnz(A, sourcerow, sourcecol, -factors[gdof], 1) - end - flush!(A) - end - if assemble_rhs - for gdof ∈ 1:length(dofsX) - sourcerow = offsetY + dofsY[gdof] - targetrow = offsetX + dofsX[gdof] - b[targetrow] += b[sourcerow] - b[sourcerow] = 0 - end - end - end - O.assembler = assemble - O.FESX = FESX - O.FESY = FESY - end + # replace source row (of dofY) with equation for coupling the two dofs + sourcecol = dofsY[gdof] + offsetY + targetcol = dofsX[gdof] + offsetX + sourcerow = offsetY + dofsY[gdof] + _addnz(A, sourcerow, targetcol, 1, 1) + _addnz(A, sourcerow, sourcecol, -factors[gdof], 1) + end + flush!(A) + end + return if assemble_rhs + for gdof in 1:length(dofsX) + sourcerow = offsetY + dofsY[gdof] + targetrow = offsetX + dofsX[gdof] + b[targetrow] += b[sourcerow] + b[sourcerow] = 0 + end + end + end + O.assembler = assemble + O.FESX = FESX + O.FESY = FESY + end end diff --git a/src/common_operators/discface_interpolator.jl b/src/common_operators/discface_interpolator.jl index caf4113..ba02025 100644 --- a/src/common_operators/discface_interpolator.jl +++ b/src/common_operators/discface_interpolator.jl @@ -1,28 +1,28 @@ mutable struct FaceInterpolator{Tv, Ti, UT <: Union{Unknown, Integer}, MT, KFT} - u_args::Array{UT, 1} - ops_args::Array{DataType, 1} - coeffs_ops::Array{Array{Ti, 1}, 1} - kernel::KFT - FES_args::Any - FES_target::Any - BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} - QP_infos::Any #::Array{QPInfosT,1} - QF::Any - L2G::Any - assembler::Any - value::Any - A::MT - parameters::Dict{Symbol, Any} + u_args::Array{UT, 1} + ops_args::Array{DataType, 1} + coeffs_ops::Array{Array{Ti, 1}, 1} + kernel::KFT + FES_args::Any + FES_target::Any + BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} + QP_infos::Any #::Array{QPInfosT,1} + QF::Any + L2G::Any + assembler::Any + value::Any + A::MT + parameters::Dict{Symbol, Any} end default_interp_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :order => ("auto", "interpolation order (default: match order of applied finite element space)"), - :name => ("Projector", "name for operator used in printouts"), - :parallel_groups => (true, "assemble operator in parallel using CellAssemblyGroups"), - :only_interior => (false, "only interior faces, interpolation of boundary faces will be zero"), - :resultdim => (0, "dimension of result field (default = length of arguments)"), - :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), - :verbosity => (0, "verbosity level"), + :order => ("auto", "interpolation order (default: match order of applied finite element space)"), + :name => ("Projector", "name for operator used in printouts"), + :parallel_groups => (true, "assemble operator in parallel using CellAssemblyGroups"), + :only_interior => (false, "only interior faces, interpolation of boundary faces will be zero"), + :resultdim => (0, "dimension of result field (default = length of arguments)"), + :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), + :verbosity => (0, "verbosity level"), ) """ @@ -45,233 +45,234 @@ Keyword arguments: $(_myprint(default_interp_kwargs())) """ function FaceInterpolator(kernel, u_args, ops_args; Tv = Float64, Ti = Int, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_interp_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_args) == length(ops_args) - coeffs_ops = Array{Array{Ti, 1}, 1}([]) - for op in ops_args - @assert is_discontinuous(op) "all operators must be of type DiscontinuousFunctionOperator ($(typeof(op)) is not)" - push!(coeffs_ops, coeffs(op)) - end - return FaceInterpolator{Tv, Ti, typeof(u_args[1]), ExtendableSparseMatrix{Tv, Int64}, typeof(kernel)}( - u_args, - ops_args, - coeffs_ops, - kernel, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - ExtendableSparseMatrix{Tv, Int64}(0, 0), - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_interp_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_args) == length(ops_args) + coeffs_ops = Array{Array{Ti, 1}, 1}([]) + for op in ops_args + @assert is_discontinuous(op) "all operators must be of type DiscontinuousFunctionOperator ($(typeof(op)) is not)" + push!(coeffs_ops, coeffs(op)) + end + return FaceInterpolator{Tv, Ti, typeof(u_args[1]), ExtendableSparseMatrix{Tv, Int64}, typeof(kernel)}( + u_args, + ops_args, + coeffs_ops, + kernel, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + ExtendableSparseMatrix{Tv, Int64}(0, 0), + parameters, + ) end function FaceInterpolator(kernel, oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_args = [oa[1] for oa in oa_args] - ops_args = [oa[2] for oa in oa_args] - return FaceInterpolator(kernel, u_args, ops_args; kwargs...) + u_args = [oa[1] for oa in oa_args] + ops_args = [oa[2] for oa in oa_args] + return FaceInterpolator(kernel, u_args, ops_args; kwargs...) end function FaceInterpolator(oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_args = [oa[1] for oa in oa_args] - ops_args = [oa[2] for oa in oa_args] - return FaceInterpolator(ExtendableFEMBase.standard_kernel, u_args, ops_args; kwargs...) + u_args = [oa[1] for oa in oa_args] + ops_args = [oa[2] for oa in oa_args] + return FaceInterpolator(ExtendableFEMBase.standard_kernel, u_args, ops_args; kwargs...) end function build_assembler!(O::FaceInterpolator{Tv}, FE_args::Array{<:FEVectorBlock, 1}; time = 0.0, kwargs...) where {Tv} - FES_args = [FE_args[j].FES for j ∈ 1:length(FE_args)] - - if (O.FES_args != FES_args) - if O.parameters[:verbosity] > 0 - @info ".... building assembler for $(O.parameters[:name])" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_args) - - ## prepare assembly - AT = ON_CELLS - Ti = typeof(xgrid).parameters[2] - itemassemblygroups = xgrid[CellAssemblyGroups] - itemgeometries = xgrid[CellGeometries] - itemregions = xgrid[CellRegions] - itemfaces = xgrid[CellFaces] - facevolumes = xgrid[FaceVolumes] - facenormals = xgrid[FaceNormals] - facecells = xgrid[FaceCells] - FETypes_args = [eltype(F) for F in FES_args] - EGs = [itemgeometries[itemassemblygroups[1, j]] for j ∈ 1:num_sources(itemassemblygroups)] - - @assert length(xgrid[UniqueFaceGeometries]) == 1 "currently only grids with single face geometry type are allowed" - ## prepare assembly - nargs = length(FES_args) - O.QF = [] - O.BE_args = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - faceEG = xgrid[UniqueFaceGeometries][1] - qf_offsets::Array{Int, 1} = [0] - for EG in EGs - ## quadrature formula for face - if O.parameters[:order] == "auto" - polyorder = maximum([get_polynomialorder(FE, faceEG) for FE in FETypes_args]) - minderiv = minimum([ExtendableFEMBase.NeededDerivative4Operator(op) for op in O.ops_args]) - O.parameters[:order] = polyorder - minderiv - end - qf = VertexRule(faceEG, O.parameters[:order]) - - ## generate qf that integrates along full cell boundary - nfaces = num_faces(EG) - xref_face_to_cell = xrefFACE2xrefCELL(EG) - w_cellboundary = Array{eltype(qf.w), 1}([]) - xref_cellboundary = Array{eltype(qf.xref), 1}([]) - for f ∈ 1:nfaces - append!(w_cellboundary, qf.w) - push!(qf_offsets, qf_offsets[end] + length(qf.w)) - for i ∈ 1:length(qf.xref) - push!(xref_cellboundary, xref_face_to_cell[f](qf.xref[i])) - end - end - qf_cellboundary = ExtendableFEMBase.SQuadratureRule{eltype(qf.xref[1]), EG, dim_element(EG), length(qf.xref) * nfaces}("cell boundary rule", xref_cellboundary, w_cellboundary) - push!(O.QF, qf_cellboundary) - - ## FE basis evaluator for EG - push!(O.BE_args, [FEEvaluator(FES_args[j], StandardFunctionOperator(O.ops_args[j]), O.QF[end]; AT = AT) for j in 1:nargs]) - - ## L2G map for EG - push!(O.L2G, L2GTransformer(EG, xgrid, ON_CELLS)) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) - end - - - ## prepare operator infos - op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j ∈ 1:nargs] - op_offsets_args = [0] - append!(op_offsets_args, cumsum(op_lengths_args)) - resultdim::Int = O.parameters[:resultdim] - if resultdim == 0 - resultdim = op_offsets_args[end] - O.parameters[:resultdim] = resultdim - end - - ## prepare target FE - only_interior = O.parameters[:only_interior] - if O.parameters[:order] <= 0 - FEType_target = L2P0{resultdim} - else - FEType_target = H1Pk{resultdim, dim_element(faceEG), O.parameters[:order]} - end - FES_target = FESpace{FEType_target, ON_FACES}(xgrid; broken = true) - b = FEVector(FES_target) - coffsets = ExtendableFEMBase.get_local_coffsets(FEType_target, ON_CELLS, faceEG) - #A = FEMatrix(FES_target, FES_target) - - FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j ∈ 1:nargs] - itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j ∈ 1:nargs] - facedofs_target::Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}} = FES_target[CellDofs] - - O.FES_args = FES_args - O.value = b - - ## Assembly loop for fixed geometry - function assembly_loop(b::AbstractVector{T}, sol::Array{<:FEVectorBlock, 1}, items, EG::ElementGeometries, QF::QuadratureRule, BE_args::Array{<:FEEvaluator, 1}, L2G::L2GTransformer, QPinfos::QPInfos) where {T} - - ## prepare parameters - nfaces = num_faces(EG) - result_kernel = zeros(Tv, resultdim) - input_args = zeros(Tv, op_offsets_args[end]) - ndofs_args::Array{Int, 1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_args] - weights, xref = QF.w, QF.xref - left_or_right::Int = 0 - if O.parameters[:order] <= 0 - left_or_right_dofs = [[1], [1]] - else - left_or_right_dofs = [1:qf_offsets[2], [2, 1]] - append!(left_or_right_dofs[2], qf_offsets[2]:-1:3) - end - coeffs_ops::Array{Array{Int, 1}, 1} = O.coeffs_ops - qp::Int = 0 - - - fill!(b, 0) - - for item::Int in items - QPinfos.region = itemregions[item] - - ## update FE basis evaluators on cell - for j ∈ 1:nargs - BE_args[j].citem[] = item - update_basis!(BE_args[j]) - end - update_trafo!(L2G, item) - - for localface::Int ∈ 1:nfaces - face = itemfaces[localface, item] - if only_interior && facecells[2, face] == 0 - ## skip boundary face - continue - end - QPinfos.item = face - QPinfos.volume = facevolumes[face] - QPinfos.normal .= view(facenormals, :, face) - - left_or_right = facecells[1, face] == item ? 1 : 2 - - ## evaluate arguments - for k ∈ 1:qf_offsets[2] - qp = left_or_right_dofs[left_or_right][k] - fill!(input_args, 0) - for id ∈ 1:nargs - for j ∈ 1:ndofs_args[id] - dof_j = itemdofs_args[id][j, item] - for d ∈ 1:op_lengths_args[id] - input_args[d+op_offsets_args[id]] += sol[id][dof_j] * BE_args[id].cvals[d, j, qp+qf_offsets[localface]] * coeffs_ops[id][left_or_right] - end - end - end - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp+qf_offsets[localface]]) - - # evaluate kernel - O.kernel(result_kernel, input_args, QPinfos) - #@show QPinfos.x, result_kernel, left_or_right - - # write into coefficients of target FE - for d ∈ 1:resultdim - bdof = facedofs_target[k+coffsets[d], face] - b[bdof] += result_kernel[d] - end - end - end - end - end - - function assembler(b, sol; kwargs...) - time = @elapsed begin - for j ∈ 1:length(EGs) - assembly_loop(b.entries, sol, view(itemassemblygroups, :, j), EGs[j], O.QF[j], O.BE_args[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - if O.parameters[:verbosity] > 1 - @info ".... assembly of $(O.parameters[:name]) took $time s" - end - end - O.assembler = assembler - else - ## update the time - for j ∈ 1:length(O.QP_infos) - O.QP_infos[j].time = time - end - end + FES_args = [FE_args[j].FES for j in 1:length(FE_args)] + + return if (O.FES_args != FES_args) + if O.parameters[:verbosity] > 0 + @info ".... building assembler for $(O.parameters[:name])" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_args) + + ## prepare assembly + AT = ON_CELLS + Ti = typeof(xgrid).parameters[2] + itemassemblygroups = xgrid[CellAssemblyGroups] + itemgeometries = xgrid[CellGeometries] + itemregions = xgrid[CellRegions] + itemfaces = xgrid[CellFaces] + facevolumes = xgrid[FaceVolumes] + facenormals = xgrid[FaceNormals] + facecells = xgrid[FaceCells] + FETypes_args = [eltype(F) for F in FES_args] + EGs = [itemgeometries[itemassemblygroups[1, j]] for j in 1:num_sources(itemassemblygroups)] + + @assert length(xgrid[UniqueFaceGeometries]) == 1 "currently only grids with single face geometry type are allowed" + ## prepare assembly + nargs = length(FES_args) + O.QF = [] + O.BE_args = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + faceEG = xgrid[UniqueFaceGeometries][1] + qf_offsets::Array{Int, 1} = [0] + for EG in EGs + ## quadrature formula for face + if O.parameters[:order] == "auto" + polyorder = maximum([get_polynomialorder(FE, faceEG) for FE in FETypes_args]) + minderiv = minimum([ExtendableFEMBase.NeededDerivative4Operator(op) for op in O.ops_args]) + O.parameters[:order] = polyorder - minderiv + end + qf = VertexRule(faceEG, O.parameters[:order]) + + ## generate qf that integrates along full cell boundary + nfaces = num_faces(EG) + xref_face_to_cell = xrefFACE2xrefCELL(EG) + w_cellboundary = Array{eltype(qf.w), 1}([]) + xref_cellboundary = Array{eltype(qf.xref), 1}([]) + for f in 1:nfaces + append!(w_cellboundary, qf.w) + push!(qf_offsets, qf_offsets[end] + length(qf.w)) + for i in 1:length(qf.xref) + push!(xref_cellboundary, xref_face_to_cell[f](qf.xref[i])) + end + end + qf_cellboundary = ExtendableFEMBase.SQuadratureRule{eltype(qf.xref[1]), EG, dim_element(EG), length(qf.xref) * nfaces}("cell boundary rule", xref_cellboundary, w_cellboundary) + push!(O.QF, qf_cellboundary) + + ## FE basis evaluator for EG + push!(O.BE_args, [FEEvaluator(FES_args[j], StandardFunctionOperator(O.ops_args[j]), O.QF[end]; AT = AT) for j in 1:nargs]) + + ## L2G map for EG + push!(O.L2G, L2GTransformer(EG, xgrid, ON_CELLS)) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) + end + + + ## prepare operator infos + op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j in 1:nargs] + op_offsets_args = [0] + append!(op_offsets_args, cumsum(op_lengths_args)) + resultdim::Int = O.parameters[:resultdim] + if resultdim == 0 + resultdim = op_offsets_args[end] + O.parameters[:resultdim] = resultdim + end + + ## prepare target FE + only_interior = O.parameters[:only_interior] + if O.parameters[:order] <= 0 + FEType_target = L2P0{resultdim} + else + FEType_target = H1Pk{resultdim, dim_element(faceEG), O.parameters[:order]} + end + FES_target = FESpace{FEType_target, ON_FACES}(xgrid; broken = true) + b = FEVector(FES_target) + coffsets = ExtendableFEMBase.get_local_coffsets(FEType_target, ON_CELLS, faceEG) + #A = FEMatrix(FES_target, FES_target) + + FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j in 1:nargs] + itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j in 1:nargs] + facedofs_target::Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}} = FES_target[CellDofs] + + O.FES_args = FES_args + O.value = b + + ## Assembly loop for fixed geometry + function assembly_loop(b::AbstractVector{T}, sol::Array{<:FEVectorBlock, 1}, items, EG::ElementGeometries, QF::QuadratureRule, BE_args::Array{<:FEEvaluator, 1}, L2G::L2GTransformer, QPinfos::QPInfos) where {T} + + ## prepare parameters + nfaces = num_faces(EG) + result_kernel = zeros(Tv, resultdim) + input_args = zeros(Tv, op_offsets_args[end]) + ndofs_args::Array{Int, 1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_args] + weights, xref = QF.w, QF.xref + left_or_right::Int = 0 + if O.parameters[:order] <= 0 + left_or_right_dofs = [[1], [1]] + else + left_or_right_dofs = [1:qf_offsets[2], [2, 1]] + append!(left_or_right_dofs[2], qf_offsets[2]:-1:3) + end + coeffs_ops::Array{Array{Int, 1}, 1} = O.coeffs_ops + qp::Int = 0 + + + fill!(b, 0) + + for item::Int in items + QPinfos.region = itemregions[item] + + ## update FE basis evaluators on cell + for j in 1:nargs + BE_args[j].citem[] = item + update_basis!(BE_args[j]) + end + update_trafo!(L2G, item) + + for localface::Int in 1:nfaces + face = itemfaces[localface, item] + if only_interior && facecells[2, face] == 0 + ## skip boundary face + continue + end + QPinfos.item = face + QPinfos.volume = facevolumes[face] + QPinfos.normal .= view(facenormals, :, face) + + left_or_right = facecells[1, face] == item ? 1 : 2 + + ## evaluate arguments + for k in 1:qf_offsets[2] + qp = left_or_right_dofs[left_or_right][k] + fill!(input_args, 0) + for id in 1:nargs + for j in 1:ndofs_args[id] + dof_j = itemdofs_args[id][j, item] + for d in 1:op_lengths_args[id] + input_args[d + op_offsets_args[id]] += sol[id][dof_j] * BE_args[id].cvals[d, j, qp + qf_offsets[localface]] * coeffs_ops[id][left_or_right] + end + end + end + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp + qf_offsets[localface]]) + + # evaluate kernel + O.kernel(result_kernel, input_args, QPinfos) + #@show QPinfos.x, result_kernel, left_or_right + + # write into coefficients of target FE + for d in 1:resultdim + bdof = facedofs_target[k + coffsets[d], face] + b[bdof] += result_kernel[d] + end + end + end + end + return + end + + function assembler(b, sol; kwargs...) + time = @elapsed begin + for j in 1:length(EGs) + assembly_loop(b.entries, sol, view(itemassemblygroups, :, j), EGs[j], O.QF[j], O.BE_args[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + return if O.parameters[:verbosity] > 1 + @info ".... assembly of $(O.parameters[:name]) took $time s" + end + end + O.assembler = assembler + else + ## update the time + for j in 1:length(O.QP_infos) + O.QP_infos[j].time = time + end + end end @@ -284,12 +285,12 @@ Evaluates the FaceInterpolator using the blocks of sol as arguments and returns with the results. """ function ExtendableFEMBase.evaluate!(O::FaceInterpolator{Tv, Ti, UT}, sol; kwargs...) where {Tv, Ti, UT} - if UT <: Integer - ind_args = O.u_args - else - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] - end - build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) - O.assembler(O.value, [sol[j] for j in ind_args]) - return O.value + if UT <: Integer + ind_args = O.u_args + else + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] + end + build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) + O.assembler(O.value, [sol[j] for j in ind_args]) + return O.value end diff --git a/src/common_operators/fixdofs_operator.jl b/src/common_operators/fixdofs_operator.jl index a871e75..6feac70 100644 --- a/src/common_operators/fixdofs_operator.jl +++ b/src/common_operators/fixdofs_operator.jl @@ -1,35 +1,34 @@ - ################## ### FIXED DOFS ### ################## mutable struct FixDofs{UT, AT, VT} <: AbstractOperator - u::UT - dofs::AT - offset::Int - vals::VT - assembler::Any - parameters::Dict{Symbol, Any} + u::UT + dofs::AT + offset::Int + vals::VT + assembler::Any + parameters::Dict{Symbol, Any} end fixed_dofs(O::FixDofs) = O.dofs .+ O.offset fixed_vals(O::FixDofs) = O.vals default_fixdofs_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :penalty => (1e30, "penalty for fixed degrees of freedom"), - :name => ("FixDofs", "name for operator used in printouts"), - :verbosity => (0, "verbosity level"), + :penalty => (1.0e30, "penalty for fixed degrees of freedom"), + :name => ("FixDofs", "name for operator used in printouts"), + :verbosity => (0, "verbosity level"), ) # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::FixDofs) - return O.u + return O.u end function Base.show(io::IO, O::FixDofs) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($(ansatz_function(dependencies)), ndofs = $(length(O.dofs)))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($(ansatz_function(dependencies)), ndofs = $(length(O.dofs)))") + return nothing end """ @@ -45,40 +44,40 @@ $(_myprint(default_fixdofs_kwargs())) """ function FixDofs(u; dofs = [], vals = zeros(Float64, length(dofs)), kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_fixdofs_kwargs()) - _update_params!(parameters, kwargs) - @assert length(dofs) == length(vals) - return FixDofs{typeof(u), typeof(dofs), typeof(vals)}(u, dofs, 0, vals, nothing, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_fixdofs_kwargs()) + _update_params!(parameters, kwargs) + @assert length(dofs) == length(vals) + return FixDofs{typeof(u), typeof(dofs), typeof(vals)}(u, dofs, 0, vals, nothing, parameters) end function apply_penalties!(A, b, sol, O::FixDofs{UT}, SC::SolverConfiguration; assemble_matrix = true, assemble_rhs = true, kwargs...) where {UT} - if UT <: Integer - ind = O.u - elseif UT <: Unknown - ind = get_unknown_id(SC, O.u) - end - offset = sol[ind].offset - dofs = O.dofs - vals = O.vals - penalty = O.parameters[:penalty] - if assemble_matrix - AE = A.entries - for j ∈ 1:length(dofs) - dof = dofs[j] + offset - AE[dof, dof] = penalty - end - end - if assemble_rhs - BE = b.entries - for j ∈ 1:length(dofs) - dof = dofs[j] + offset - BE[dof] = penalty * vals[j] - end - end - SE = sol.entries - for j ∈ 1:length(dofs) - dof = dofs[j] + offset - SE[dof] = vals[j] - end - O.offset = offset + if UT <: Integer + ind = O.u + elseif UT <: Unknown + ind = get_unknown_id(SC, O.u) + end + offset = sol[ind].offset + dofs = O.dofs + vals = O.vals + penalty = O.parameters[:penalty] + if assemble_matrix + AE = A.entries + for j in 1:length(dofs) + dof = dofs[j] + offset + AE[dof, dof] = penalty + end + end + if assemble_rhs + BE = b.entries + for j in 1:length(dofs) + dof = dofs[j] + offset + BE[dof] = penalty * vals[j] + end + end + SE = sol.entries + for j in 1:length(dofs) + dof = dofs[j] + offset + SE[dof] = vals[j] + end + return O.offset = offset end diff --git a/src/common_operators/homogeneousdata_operator.jl b/src/common_operators/homogeneousdata_operator.jl index 1e540d2..e26124e 100644 --- a/src/common_operators/homogeneousdata_operator.jl +++ b/src/common_operators/homogeneousdata_operator.jl @@ -1,9 +1,9 @@ mutable struct HomogeneousData{UT, AT} <: AbstractOperator - u::UT - bdofs::Array{Int, 1} - FES::Any - assembler::Any - parameters::Dict{Symbol, Any} + u::UT + bdofs::Array{Int, 1} + FES::Any + assembler::Any + parameters::Dict{Symbol, Any} end fixed_dofs(O::HomogeneousData) = O.bdofs @@ -11,23 +11,23 @@ fixed_vals(O::HomogeneousData) = O.parameters[:value] default_homdata_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :penalty => (1e30, "penalty for fixed degrees of freedom"), - :name => ("HomogeneousData", "name for operator used in printouts"), - :value => (0, "constant value of the data"), - :mask => ([], "array of zeros/ones to set which components should be set by the operator (only works with componentwise dofs, add a 1 or 0 to mask additional dofs)"), - :regions => ([], "subset of regions where operator should be assembly only"), - :verbosity => (0, "verbosity level"), + :penalty => (1.0e30, "penalty for fixed degrees of freedom"), + :name => ("HomogeneousData", "name for operator used in printouts"), + :value => (0, "constant value of the data"), + :mask => ([], "array of zeros/ones to set which components should be set by the operator (only works with componentwise dofs, add a 1 or 0 to mask additional dofs)"), + :regions => ([], "subset of regions where operator should be assembly only"), + :verbosity => (0, "verbosity level"), ) # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::HomogeneousData) - return O.u + return O.u end function Base.show(io::IO, O::HomogeneousData) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($(ansatz_function(dependencies)), regions = $(O.parameters[:regions]))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($(ansatz_function(dependencies)), regions = $(O.parameters[:regions]))") + return nothing end @@ -44,9 +44,9 @@ $(_myprint(default_homdata_kwargs())) """ function HomogeneousData(u; entities = ON_CELLS, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_homdata_kwargs()) - _update_params!(parameters, kwargs) - return HomogeneousData{typeof(u), entities}(u, zeros(Int, 0), nothing, nothing, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_homdata_kwargs()) + _update_params!(parameters, kwargs) + return HomogeneousData{typeof(u), entities}(u, zeros(Int, 0), nothing, nothing, parameters) end """ @@ -62,132 +62,132 @@ $(_myprint(default_homdata_kwargs())) """ function HomogeneousBoundaryData(u; entities = ON_BFACES, kwargs...) - return HomogeneousData(u; entities = entities, kwargs...) + return HomogeneousData(u; entities = entities, kwargs...) end function assemble!(O::HomogeneousData{UT, AT}, FES = O.FES; offset = 0, kwargs...) where {UT, AT} - if O.FES !== FES - regions = O.parameters[:regions] - xgrid = FES.dofgrid - if AT <: ON_BFACES - itemdofs = FES[ExtendableFEMBase.BFaceDofs] - itemregions = xgrid[BFaceRegions] - uniquegeometries = xgrid[UniqueBFaceGeometries] - elseif AT <: ON_CELLS - itemdofs = FES[ExtendableFEMBase.CellDofs] - itemregions = xgrid[CellRegions] - uniquegeometries = xgrid[UniqueCellGeometries] - elseif AT <: ON_FACES - itemdofs = FES[ExtendableFEMBase.FaceDofs] - itemregions = xgrid[FaceRegions] - uniquegeometries = xgrid[UniqueFaceGeometries] - end - nitems = num_sources(itemdofs) - ndofs4item = max_num_targets_per_source(itemdofs) - mask = O.parameters[:mask] - bdofs = [] - if any(mask .== 0) - # only some components are Dirichlet - FEType = get_FEType(FES) - ncomponents = get_ncomponents(FEType) - @assert ncomponents <= length(mask) "mask needs to have an entry for each component" - @assert FEType <: AbstractH1FiniteElement "masks are only allowed for H1FiniteElements" - @assert length(uniquegeometries) == 1 "masks only work for single geometries for $AT" - EG = uniquegeometries[1] - coffsets = ExtendableFEMBase.get_local_coffsets(FEType, AT, EG) - ndofs = get_ndofs(AT, FEType, EG) - dofmask = [] - if ndofs > coffsets[end] && length(mask) == length(coffsets) - 1 - @warn "$FEType has additional dofs not associated to single components, add a 0 to the mask if these dofs also should be removed" - end - for j ∈ 1:length(mask) - if j == length(coffsets) - if mask[end] == 1 - for dof ∈ coffsets[end]+1:ndofs - push!(dofmask, dof) - end - end - elseif mask[j] == 1 - for dof ∈ coffsets[j]+1:coffsets[j+1] - push!(dofmask, dof) - end - end - end - for item ∈ 1:nitems - if itemregions[item] in regions - for dof in dofmask - append!(bdofs, itemdofs[dof, item] + offset) - end - end - end - else - for item ∈ 1:nitems - if itemregions[item] in regions - ndofs4item = num_targets(itemdofs, item) - for k ∈ 1:ndofs4item - dof = itemdofs[k, item] - push!(bdofs, dof + offset) - end - end - end - end - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : penalizing $(length(bdofs)) dofs of '$(O.u.name)' ($AT, regions = $(O.parameters[:regions]))" - end - O.bdofs = bdofs - O.FES = FES - end + return if O.FES !== FES + regions = O.parameters[:regions] + xgrid = FES.dofgrid + if AT <: ON_BFACES + itemdofs = FES[ExtendableFEMBase.BFaceDofs] + itemregions = xgrid[BFaceRegions] + uniquegeometries = xgrid[UniqueBFaceGeometries] + elseif AT <: ON_CELLS + itemdofs = FES[ExtendableFEMBase.CellDofs] + itemregions = xgrid[CellRegions] + uniquegeometries = xgrid[UniqueCellGeometries] + elseif AT <: ON_FACES + itemdofs = FES[ExtendableFEMBase.FaceDofs] + itemregions = xgrid[FaceRegions] + uniquegeometries = xgrid[UniqueFaceGeometries] + end + nitems = num_sources(itemdofs) + ndofs4item = max_num_targets_per_source(itemdofs) + mask = O.parameters[:mask] + bdofs = [] + if any(mask .== 0) + # only some components are Dirichlet + FEType = get_FEType(FES) + ncomponents = get_ncomponents(FEType) + @assert ncomponents <= length(mask) "mask needs to have an entry for each component" + @assert FEType <: AbstractH1FiniteElement "masks are only allowed for H1FiniteElements" + @assert length(uniquegeometries) == 1 "masks only work for single geometries for $AT" + EG = uniquegeometries[1] + coffsets = ExtendableFEMBase.get_local_coffsets(FEType, AT, EG) + ndofs = get_ndofs(AT, FEType, EG) + dofmask = [] + if ndofs > coffsets[end] && length(mask) == length(coffsets) - 1 + @warn "$FEType has additional dofs not associated to single components, add a 0 to the mask if these dofs also should be removed" + end + for j in 1:length(mask) + if j == length(coffsets) + if mask[end] == 1 + for dof in (coffsets[end] + 1):ndofs + push!(dofmask, dof) + end + end + elseif mask[j] == 1 + for dof in (coffsets[j] + 1):coffsets[j + 1] + push!(dofmask, dof) + end + end + end + for item in 1:nitems + if itemregions[item] in regions + for dof in dofmask + append!(bdofs, itemdofs[dof, item] + offset) + end + end + end + else + for item in 1:nitems + if itemregions[item] in regions + ndofs4item = num_targets(itemdofs, item) + for k in 1:ndofs4item + dof = itemdofs[k, item] + push!(bdofs, dof + offset) + end + end + end + end + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : penalizing $(length(bdofs)) dofs of '$(O.u.name)' ($AT, regions = $(O.parameters[:regions]))" + end + O.bdofs = bdofs + O.FES = FES + end end function assemble!(A, b, sol, O::HomogeneousData{UT, AT}, SC::SolverConfiguration; kwargs...) where {UT, AT} - if UT <: Integer - ind = O.u - elseif UT <: Unknown - ind = get_unknown_id(SC, O.u) - end - FES = sol[ind].FES - assemble!(O, FES; offset = SC.offsets[ind], kwargs...) + if UT <: Integer + ind = O.u + elseif UT <: Unknown + ind = get_unknown_id(SC, O.u) + end + FES = sol[ind].FES + return assemble!(O, FES; offset = SC.offsets[ind], kwargs...) end function apply!(U::FEVectorBlock, O::HomogeneousData; offset = 0, kwargs...) - bdofs = O.bdofs - value = O.parameters[:value] - Uentries = U.entries - Uentries[bdofs] .= value + bdofs = O.bdofs + value = O.parameters[:value] + Uentries = U.entries + return Uentries[bdofs] .= value end function apply_penalties!(A, b, sol, O::HomogeneousData{UT}, SC::SolverConfiguration; assemble_matrix = true, assemble_rhs = true, assemble_sol = true, kwargs...) where {UT} - time = @elapsed begin - if UT <: Integer - ind = O.u - ind_sol = ind - elseif UT <: Unknown - ind = get_unknown_id(SC, O.u) - ind_sol = findfirst(==(O.u), sol.tags) - end - bdofs = O.bdofs - penalty = O.parameters[:penalty] - value = O.parameters[:value] - - if assemble_matrix - penalty = O.parameters[:penalty] - AE = A.entries - for dof in bdofs - AE[dof, dof] = penalty - end - flush!(AE) - end - if assemble_rhs - BE = b.entries - BE[bdofs] .= value * penalty - end - if assemble_sol - SE = sol.entries - SE[bdofs] .= value - end - end - if O.parameters[:verbosity] > 1 - @info "$(O.parameters[:name]) : applying penalties took $time s" - end + time = @elapsed begin + if UT <: Integer + ind = O.u + ind_sol = ind + elseif UT <: Unknown + ind = get_unknown_id(SC, O.u) + ind_sol = findfirst(==(O.u), sol.tags) + end + bdofs = O.bdofs + penalty = O.parameters[:penalty] + value = O.parameters[:value] + + if assemble_matrix + penalty = O.parameters[:penalty] + AE = A.entries + for dof in bdofs + AE[dof, dof] = penalty + end + flush!(AE) + end + if assemble_rhs + BE = b.entries + BE[bdofs] .= value * penalty + end + if assemble_sol + SE = sol.entries + SE[bdofs] .= value + end + end + return if O.parameters[:verbosity] > 1 + @info "$(O.parameters[:name]) : applying penalties took $time s" + end end diff --git a/src/common_operators/interpolateboundarydata_operator.jl b/src/common_operators/interpolateboundarydata_operator.jl index 103f9a1..f31e8a8 100644 --- a/src/common_operators/interpolateboundarydata_operator.jl +++ b/src/common_operators/interpolateboundarydata_operator.jl @@ -1,12 +1,12 @@ mutable struct InterpolateBoundaryData{UT, DFT} <: AbstractOperator - u::UT - data::DFT - bdofs::Array{Int, 1} - bfaces::Array{Int, 1} - FES::Any - bddata::Any - assembler::Any - parameters::Dict{Symbol, Any} + u::UT + data::DFT + bdofs::Array{Int, 1} + bfaces::Array{Int, 1} + FES::Any + bddata::Any + assembler::Any + parameters::Dict{Symbol, Any} end """ @@ -28,29 +28,29 @@ returns the currently assembled values for the fixed degrees of freedom of O fixed_vals(O::InterpolateBoundaryData) = view(O.bddata.entries, O.bdofs) default_bndop_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :penalty => (1e30, "penalty for fixed degrees of freedom"), - :name => ("BoundaryData", "name for operator used in printouts"), - :bonus_quadorder => (0, "additional quadrature order added to the quadorder chosen by the interpolator"), - :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), - :regions => ([], "subset of regions where operator should be assembly only"), - :plot => (false, "plot unicode plot of boundary data into terminal when assembled"), - :verbosity => (0, "verbosity level"), + :penalty => (1.0e30, "penalty for fixed degrees of freedom"), + :name => ("BoundaryData", "name for operator used in printouts"), + :bonus_quadorder => (0, "additional quadrature order added to the quadorder chosen by the interpolator"), + :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), + :regions => ([], "subset of regions where operator should be assembly only"), + :plot => (false, "plot unicode plot of boundary data into terminal when assembled"), + :verbosity => (0, "verbosity level"), ) # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::InterpolateBoundaryData) - return O.u + return O.u end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::InterpolateBoundaryData) - return O.parameters[:time_dependent] + return O.parameters[:time_dependent] end function Base.show(io::IO, O::InterpolateBoundaryData) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($(ansatz_function(dependencies)))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($(ansatz_function(dependencies)))") + return nothing end """ @@ -72,9 +72,9 @@ $(_myprint(default_bndop_kwargs())) """ function InterpolateBoundaryData(u, data = nothing; kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_bndop_kwargs()) - _update_params!(parameters, kwargs) - return InterpolateBoundaryData{typeof(u), typeof(data)}(u, data, zeros(Int, 0), zeros(Int, 0), nothing, nothing, nothing, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_bndop_kwargs()) + _update_params!(parameters, kwargs) + return InterpolateBoundaryData{typeof(u), typeof(data)}(u, data, zeros(Int, 0), zeros(Int, 0), nothing, nothing, nothing, parameters) end @@ -86,80 +86,80 @@ assemble!(A, b, sol, O::InterpolateBoundaryData{UT}, SC::SolverConfiguration; kw assembles the correct boundary values for O by interpolating the boundary data with the current kwargs (where e.g. time and params might have changed). """ -function assemble!(A, b, sol, O::InterpolateBoundaryData{UT}, SC::SolverConfiguration; kwargs...) where UT - if UT <: Integer - ind = O.u - ind_sol = ind - elseif UT <: Unknown - ind = get_unknown_id(SC, O.u) - ind_sol = findfirst(==(O.u), sol.tags) - end - assemble!(O, b[ind].FES; ind = ind, offset = SC.offsets[ind], kwargs...) +function assemble!(A, b, sol, O::InterpolateBoundaryData{UT}, SC::SolverConfiguration; kwargs...) where {UT} + if UT <: Integer + ind = O.u + ind_sol = ind + elseif UT <: Unknown + ind = get_unknown_id(SC, O.u) + ind_sol = findfirst(==(O.u), sol.tags) + end + return assemble!(O, b[ind].FES; ind = ind, offset = SC.offsets[ind], kwargs...) end function assemble!(O::InterpolateBoundaryData, FES = O.FES; time = 0, offset = 0, kwargs...) - regions = O.parameters[:regions] - bdofs::Array{Int, 1} = O.bdofs - bfaces::Array{Int, 1} = O.bfaces - if O.FES !== FES - bddata = FEVector(FES) - xgrid = FES.dofgrid - Ti = eltype(xgrid[CellNodes]) - bfacedofs::Adjacency{Ti} = FES[BFaceDofs] - bfacefaces = xgrid[BFaceFaces] - bfaceregions = xgrid[BFaceRegions] - nbfaces = num_sources(bfacedofs) - ndofs4bface = max_num_targets_per_source(bfacedofs) - bdofs = [] - bfaces = [] - for bface ∈ 1:nbfaces - if bfaceregions[bface] in regions - for k ∈ 1:ndofs4bface - dof = bfacedofs[k, bface] + offset - push!(bdofs, dof) - end - push!(bfaces, bfacefaces[bface]) - end - end - unique!(bdofs) - O.bdofs = bdofs - O.bddata = bddata - O.bfaces = bfaces - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : penalizing $(length(O.bdofs)) dofs of '$(O.u.name)' (ON_BFACES, regions = $(O.parameters[:regions]))" - end - end - time = @elapsed begin - bddata = O.bddata - data = O.data - bfaces = O.bfaces - if FES.broken - FEType = eltype(FES) - xgrid = FES.dofgrid - FESc = FESpace{FEType}(xgrid) - Targetc = FEVector(FESc) - interpolate!(Targetc[1], FESc, ON_FACES, data; items = bfaces, time = time, params = O.parameters[:params], bonus_quadorder = O.parameters[:bonus_quadorder]) - bfacedofs = FES[BFaceDofs] - bfacedofs_c = FESc[BFaceDofs] - dof::Int = 0 - dofc::Int = 0 - for bface ∈ 1:nbfaces - for k ∈ 1:num_targets(bfacedofs, bface) - dof = bfacedofs[k, bface] - dofc = bfacedofs_c[k, bface] - bddata.entries[dof] = Targetc.entries[dofc] - end - end - else - interpolate!(bddata[1], ON_BFACES, data; time = time, params = O.parameters[:params], bonus_quadorder = O.parameters[:bonus_quadorder]) - end - if O.parameters[:plot] - println(stdout, unicode_scalarplot(bddata[1]; title = "boundary data for $(O.u)")) - end - end - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end + regions = O.parameters[:regions] + bdofs::Array{Int, 1} = O.bdofs + bfaces::Array{Int, 1} = O.bfaces + if O.FES !== FES + bddata = FEVector(FES) + xgrid = FES.dofgrid + Ti = eltype(xgrid[CellNodes]) + bfacedofs::Adjacency{Ti} = FES[BFaceDofs] + bfacefaces = xgrid[BFaceFaces] + bfaceregions = xgrid[BFaceRegions] + nbfaces = num_sources(bfacedofs) + ndofs4bface = max_num_targets_per_source(bfacedofs) + bdofs = [] + bfaces = [] + for bface in 1:nbfaces + if bfaceregions[bface] in regions + for k in 1:ndofs4bface + dof = bfacedofs[k, bface] + offset + push!(bdofs, dof) + end + push!(bfaces, bfacefaces[bface]) + end + end + unique!(bdofs) + O.bdofs = bdofs + O.bddata = bddata + O.bfaces = bfaces + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : penalizing $(length(O.bdofs)) dofs of '$(O.u.name)' (ON_BFACES, regions = $(O.parameters[:regions]))" + end + end + time = @elapsed begin + bddata = O.bddata + data = O.data + bfaces = O.bfaces + if FES.broken + FEType = eltype(FES) + xgrid = FES.dofgrid + FESc = FESpace{FEType}(xgrid) + Targetc = FEVector(FESc) + interpolate!(Targetc[1], FESc, ON_FACES, data; items = bfaces, time = time, params = O.parameters[:params], bonus_quadorder = O.parameters[:bonus_quadorder]) + bfacedofs = FES[BFaceDofs] + bfacedofs_c = FESc[BFaceDofs] + dof::Int = 0 + dofc::Int = 0 + for bface in 1:nbfaces + for k in 1:num_targets(bfacedofs, bface) + dof = bfacedofs[k, bface] + dofc = bfacedofs_c[k, bface] + bddata.entries[dof] = Targetc.entries[dofc] + end + end + else + interpolate!(bddata[1], ON_BFACES, data; time = time, params = O.parameters[:params], bonus_quadorder = O.parameters[:bonus_quadorder]) + end + if O.parameters[:plot] + println(stdout, unicode_scalarplot(bddata[1]; title = "boundary data for $(O.u)")) + end + end + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end end @@ -172,11 +172,12 @@ applies the boundary data of O to U, i.e., sets the boundary dofs to the correct that have been computed during the last assemble! call. """ function apply!(U::FEVectorBlock, O::InterpolateBoundaryData; offset = 0, kwargs...) - bddata = O.bddata - bdofs = O.bdofs - for dof in bdofs - U[dof-offset] = bddata.entries[dof-offset] - end + bddata = O.bddata + bdofs = O.bdofs + for dof in bdofs + U[dof - offset] = bddata.entries[dof - offset] + end + return end """ @@ -188,30 +189,30 @@ modifies the linear system A|b such that the boundary dofs are penalized and att correct values from the last assemble! call of O. Also applies the correct values to sol. """ function apply_penalties!(A, b, sol, O::InterpolateBoundaryData{UT}, SC::SolverConfiguration; kwargs...) where {UT} - time = @elapsed begin - if UT <: Integer - ind = O.u - ind_sol = ind - elseif UT <: Unknown - ind = get_unknown_id(SC, O.u) - ind_sol = findfirst(==(O.u), sol.tags) - end - offset = SC.offsets[ind] - bddata = O.bddata - bdofs = O.bdofs - penalty = O.parameters[:penalty] - AE = A.entries - BE = b.entries - for dof in bdofs - AE[dof, dof] = penalty - end - flush!(AE) - for dof in bdofs - BE[dof] = penalty * bddata.entries[dof-offset] - end - apply!(sol[ind_sol], O; offset = offset) - end - if O.parameters[:verbosity] > 1 - @info "$(O.parameters[:name]) : applying penalties took $time s" - end + time = @elapsed begin + if UT <: Integer + ind = O.u + ind_sol = ind + elseif UT <: Unknown + ind = get_unknown_id(SC, O.u) + ind_sol = findfirst(==(O.u), sol.tags) + end + offset = SC.offsets[ind] + bddata = O.bddata + bdofs = O.bdofs + penalty = O.parameters[:penalty] + AE = A.entries + BE = b.entries + for dof in bdofs + AE[dof, dof] = penalty + end + flush!(AE) + for dof in bdofs + BE[dof] = penalty * bddata.entries[dof - offset] + end + apply!(sol[ind_sol], O; offset = offset) + end + return if O.parameters[:verbosity] > 1 + @info "$(O.parameters[:name]) : applying penalties took $time s" + end end diff --git a/src/common_operators/item_integrator.jl b/src/common_operators/item_integrator.jl index e1f53b7..215471c 100644 --- a/src/common_operators/item_integrator.jl +++ b/src/common_operators/item_integrator.jl @@ -1,39 +1,39 @@ mutable struct ItemIntegrator{Tv <: Real, UT <: Union{Unknown, Integer}, KFT <: Function} - u_args::Array{UT, 1} - ops_args::Array{DataType, 1} - kernel::KFT - FES_args::Any #::Array{FESpace,1} - BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} - QP_infos::Any #::Array{QPInfosT,1} - L2G::Any - QF::Any - assembler::Any - parameters::Dict{Symbol, Any} + u_args::Array{UT, 1} + ops_args::Array{DataType, 1} + kernel::KFT + FES_args::Any #::Array{FESpace,1} + BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} + QP_infos::Any #::Array{QPInfosT,1} + L2G::Any + QF::Any + assembler::Any + parameters::Dict{Symbol, Any} end default_iiop_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), - :name => ("ItemIntegrator", "name for operator used in printouts"), - :resultdim => (0, "dimension of result field (default = length of arguments)"), - :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), - :factor => (1, "factor that should be multiplied during assembly"), - :piecewise => (true, "returns piecewise integrations, otherwise a global integration"), - :parallel => (false, "assemble operator in parallel using partitions information"), - :quadorder => ("auto", "quadrature order"), - :bonus_quadorder => (0, "additional quadrature order added to quadorder"), - :verbosity => (0, "verbosity level"), - :regions => ([], "subset of regions where the item integrator should be evaluated"), + :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), + :name => ("ItemIntegrator", "name for operator used in printouts"), + :resultdim => (0, "dimension of result field (default = length of arguments)"), + :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), + :factor => (1, "factor that should be multiplied during assembly"), + :piecewise => (true, "returns piecewise integrations, otherwise a global integration"), + :parallel => (false, "assemble operator in parallel using partitions information"), + :quadorder => ("auto", "quadrature order"), + :bonus_quadorder => (0, "additional quadrature order added to quadorder"), + :verbosity => (0, "verbosity level"), + :regions => ([], "subset of regions where the item integrator should be evaluated"), ) function l2norm_kernel(result, input, qpinfo) - result .= input .^ 2 + return result .= input .^ 2 end function ItemIntegrator(kernel, u_args, ops_args; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_iiop_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_args) == length(ops_args) - return ItemIntegrator{Tv, typeof(u_args[1]), typeof(kernel)}(u_args, ops_args, kernel, nothing, nothing, nothing, nothing, nothing, nothing, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_iiop_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_args) == length(ops_args) + return ItemIntegrator{Tv, typeof(u_args[1]), typeof(kernel)}(u_args, ops_args, kernel, nothing, nothing, nothing, nothing, nothing, nothing, parameters) end """ @@ -64,15 +64,15 @@ Keyword arguments: $(_myprint(default_iiop_kwargs())) """ function ItemIntegrator(kernel, oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_args = [oa[1] for oa in oa_args] - ops_args = [oa[2] for oa in oa_args] - return ItemIntegrator(kernel, u_args, ops_args; kwargs...) + u_args = [oa[1] for oa in oa_args] + ops_args = [oa[2] for oa in oa_args] + return ItemIntegrator(kernel, u_args, ops_args; kwargs...) end function ItemIntegrator(oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_args = [oa[1] for oa in oa_args] - ops_args = [oa[2] for oa in oa_args] - return ItemIntegrator(ExtendableFEMBase.standard_kernel, u_args, ops_args; kwargs...) + u_args = [oa[1] for oa in oa_args] + ops_args = [oa[2] for oa in oa_args] + return ItemIntegrator(ExtendableFEMBase.standard_kernel, u_args, ops_args; kwargs...) end @@ -90,194 +90,194 @@ Keyword arguments: $(_myprint(default_iiop_kwargs())) """ function L2NormIntegrator(oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_args = [oa[1] for oa in oa_args] - ops_args = [oa[2] for oa in oa_args] - return ItemIntegrator(l2norm_kernel, u_args, ops_args; kwargs...) + u_args = [oa[1] for oa in oa_args] + ops_args = [oa[2] for oa in oa_args] + return ItemIntegrator(l2norm_kernel, u_args, ops_args; kwargs...) end function build_assembler!(O::ItemIntegrator{Tv}, FE_args::Array{<:FEVectorBlock, 1}; time = 0.0) where {Tv} - ## check if FES is the same as last time - FES_args = [FE_args[j].FES for j ∈ 1:length(FE_args)] - if (O.FES_args != FES_args) - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_args) - AT = O.parameters[:entities] - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[1]), AT) - - ## prepare assembly - Ti = typeof(xgrid).parameters[2] - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pc = xgrid[PartitionCells] - itemassemblygroups = [pc[j]:pc[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - # assuming here that all cells of one partition have the same geometry - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - Ti = typeof(xgrid).parameters[2] - has_normals = true - if gridAT <: ON_FACES - itemnormals = xgrid[FaceNormals] - elseif gridAT <: ON_BFACES - itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] - else - has_normals = false - end - FETypes_args = [eltype(F) for F in FES_args] - EGs = [itemgeometries[itemassemblygroups[j][1]] for j ∈ 1:length(itemassemblygroups)] - - ## prepare assembly - nargs = length(FES_args) - O.QF = [] - O.BE_args = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_args = maximum([get_polynomialorder(FETypes_args[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_args[j]) for j ∈ 1:nargs]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_args + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) - - ## FE basis evaluator for EG - push!(O.BE_args, [FEEvaluator(FES_args[j], O.ops_args[j], O.QF[end]; AT = AT) for j in 1:nargs]) - - ## L2G map for EG - push!(O.L2G, L2GTransformer(EG, xgrid, gridAT)) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[regions] .= true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j ∈ 1:nargs] - piecewise = O.parameters[:piecewise] - - op_offsets_args = [0] - append!(op_offsets_args, cumsum(op_lengths_args)) - resultdim::Int = O.parameters[:resultdim] - if resultdim == 0 - resultdim = op_offsets_args[end] - O.parameters[:resultdim] = resultdim - end - - FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j ∈ 1:nargs] - itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j ∈ 1:nargs] - factor = O.parameters[:factor] - - ## Assembly loop for fixed geometry - function assembly_loop(b, sol::Array{<:FEVectorBlock, 1}, items, EG::ElementGeometries, QF::QuadratureRule, BE_args::Array{<:FEEvaluator, 1}, L2G::L2GTransformer, QPinfos::QPInfos) - - ## prepare parameters - result_kernel = zeros(Tv, resultdim) - input_args = zeros(Tv, op_offsets_args[end]) - ndofs_args::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_args] - weights, xref = QF.w, QF.xref - nweights = length(weights) - - for item::Int in items - if itemregions[item] > 0 - if !(visit_region[itemregions[item]]) - continue - end - end - - QPinfos.region = itemregions[item] - QPinfos.item = item - if has_normals - QPinfos.normal .= view(itemnormals, :, item) - end - QPinfos.volume = itemvolumes[item] - - ## update FE basis evaluators - for j ∈ 1:nargs - BE_args[j].citem[] = item - update_basis!(BE_args[j]) - end - update_trafo!(L2G, item) - - ## evaluate arguments - for qp ∈ 1:nweights - fill!(input_args, 0) - for id ∈ 1:nargs - for j ∈ 1:ndofs_args[id] - dof_j = itemdofs_args[id][j, item] - for d ∈ 1:op_lengths_args[id] - input_args[d+op_offsets_args[id]] += sol[id][dof_j] * BE_args[id].cvals[d, j, qp] - end - end - end - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # evaluate kernel - O.kernel(result_kernel, input_args, QPinfos) - result_kernel .*= factor * weights[qp] * itemvolumes[item] - - # integrate over item - if piecewise - b[1:resultdim, item] .+= result_kernel - else - b .+= result_kernel - end - end - end - return - end - O.FES_args = FES_args - - function assembler(b, sol; kwargs...) - time_assembly = @elapsed begin - if O.parameters[:parallel] && O.parameters[:piecewise] - pcp = xgrid[PColorPartitions] - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - Threads.@threads for j ∈ 1:length(EGs) - assembly_loop(b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_args[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - else - for j ∈ 1:length(EGs) - assembly_loop(b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_args[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - end - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time_assembly s" - end - end - O.assembler = assembler - else - ## update the time - for j ∈ 1:length(O.QP_infos) - O.QP_infos[j].time = time - end - end + ## check if FES is the same as last time + FES_args = [FE_args[j].FES for j in 1:length(FE_args)] + return if (O.FES_args != FES_args) + + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_args) + AT = O.parameters[:entities] + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[1]), AT) + + ## prepare assembly + Ti = typeof(xgrid).parameters[2] + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pc = xgrid[PartitionCells] + itemassemblygroups = [pc[j]:(pc[j + 1] - 1) for j in 1:num_partitions(xgrid)] + # assuming here that all cells of one partition have the same geometry + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + Ti = typeof(xgrid).parameters[2] + has_normals = true + if gridAT <: ON_FACES + itemnormals = xgrid[FaceNormals] + elseif gridAT <: ON_BFACES + itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] + else + has_normals = false + end + FETypes_args = [eltype(F) for F in FES_args] + EGs = [itemgeometries[itemassemblygroups[j][1]] for j in 1:length(itemassemblygroups)] + + ## prepare assembly + nargs = length(FES_args) + O.QF = [] + O.BE_args = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_args = maximum([get_polynomialorder(FETypes_args[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_args[j]) for j in 1:nargs]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_args + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) + + ## FE basis evaluator for EG + push!(O.BE_args, [FEEvaluator(FES_args[j], O.ops_args[j], O.QF[end]; AT = AT) for j in 1:nargs]) + + ## L2G map for EG + push!(O.L2G, L2GTransformer(EG, xgrid, gridAT)) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[regions] .= true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j in 1:nargs] + piecewise = O.parameters[:piecewise] + + op_offsets_args = [0] + append!(op_offsets_args, cumsum(op_lengths_args)) + resultdim::Int = O.parameters[:resultdim] + if resultdim == 0 + resultdim = op_offsets_args[end] + O.parameters[:resultdim] = resultdim + end + + FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j in 1:nargs] + itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j in 1:nargs] + factor = O.parameters[:factor] + + ## Assembly loop for fixed geometry + function assembly_loop(b, sol::Array{<:FEVectorBlock, 1}, items, EG::ElementGeometries, QF::QuadratureRule, BE_args::Array{<:FEEvaluator, 1}, L2G::L2GTransformer, QPinfos::QPInfos) + + ## prepare parameters + result_kernel = zeros(Tv, resultdim) + input_args = zeros(Tv, op_offsets_args[end]) + ndofs_args::Array{Int, 1} = [size(BE.cvals, 2) for BE in BE_args] + weights, xref = QF.w, QF.xref + nweights = length(weights) + + for item::Int in items + if itemregions[item] > 0 + if !(visit_region[itemregions[item]]) + continue + end + end + + QPinfos.region = itemregions[item] + QPinfos.item = item + if has_normals + QPinfos.normal .= view(itemnormals, :, item) + end + QPinfos.volume = itemvolumes[item] + + ## update FE basis evaluators + for j in 1:nargs + BE_args[j].citem[] = item + update_basis!(BE_args[j]) + end + update_trafo!(L2G, item) + + ## evaluate arguments + for qp in 1:nweights + fill!(input_args, 0) + for id in 1:nargs + for j in 1:ndofs_args[id] + dof_j = itemdofs_args[id][j, item] + for d in 1:op_lengths_args[id] + input_args[d + op_offsets_args[id]] += sol[id][dof_j] * BE_args[id].cvals[d, j, qp] + end + end + end + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # evaluate kernel + O.kernel(result_kernel, input_args, QPinfos) + result_kernel .*= factor * weights[qp] * itemvolumes[item] + + # integrate over item + if piecewise + b[1:resultdim, item] .+= result_kernel + else + b .+= result_kernel + end + end + end + return + end + O.FES_args = FES_args + + function assembler(b, sol; kwargs...) + time_assembly = @elapsed begin + if O.parameters[:parallel] && O.parameters[:piecewise] + pcp = xgrid[PColorPartitions] + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + Threads.@threads for j in 1:length(EGs) + assembly_loop(b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_args[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + else + for j in 1:length(EGs) + assembly_loop(b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_args[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + end + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time_assembly s" + end + end + O.assembler = assembler + else + ## update the time + for j in 1:length(O.QP_infos) + O.QP_infos[j].time = time + end + end end @@ -294,9 +294,9 @@ function evaluate( Evaluates the ItemIntegrator for the specified solution into the matrix b. """ function ExtendableFEMBase.evaluate!(b, O::ItemIntegrator, sol::FEVector; kwargs...) - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] - build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) - O.assembler(b, [sol[j] for j in ind_args]) + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] + build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) + return O.assembler(b, [sol[j] for j in ind_args]) end """ @@ -312,9 +312,9 @@ function evaluate( Evaluates the ItemIntegrator for the specified solution into the matrix b. """ function ExtendableFEMBase.evaluate!(b, O::ItemIntegrator, sol::Array{<:FEVectorBlock, 1}; kwargs...) - ind_args = O.u_args - build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) - O.assembler(b, [sol[j] for j in ind_args]) + ind_args = O.u_args + build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) + return O.assembler(b, [sol[j] for j in ind_args]) end """ @@ -329,30 +329,30 @@ function evaluate( Evaluates the ItemIntegrator for the specified solution and returns an matrix of size resultdim x num_items. """ function evaluate(O::ItemIntegrator{Tv, UT}, sol; kwargs...) where {Tv, UT} - if UT <: Integer - ind_args = O.u_args - elseif UT <: Unknown - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] - end - build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) - grid = sol[ind_args[1]].FES.xgrid - AT = O.parameters[:entities] - if AT <: ON_CELLS - nitems = num_cells(grid) - elseif AT <: ON_FACES - nitems = size(grid[FaceNodes], 2) - elseif AT <: ON_EDGES - nitems = size(grid[EdgeNodes], 2) - elseif AT <: ON_BFACES - nitems = size(grid[BFaceNodes], 2) - elseif AT <: ON_BEDGES - nitems = size(grid[BEdgeNodes], 2) - end - if O.parameters[:piecewise] - b = zeros(eltype(sol[1].entries), O.parameters[:resultdim], nitems) - else - b = zeros(eltype(sol[1].entries), O.parameters[:resultdim]) - end - O.assembler(b, [sol[j] for j in ind_args]) - return b + if UT <: Integer + ind_args = O.u_args + elseif UT <: Unknown + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] + end + build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) + grid = sol[ind_args[1]].FES.xgrid + AT = O.parameters[:entities] + if AT <: ON_CELLS + nitems = num_cells(grid) + elseif AT <: ON_FACES + nitems = size(grid[FaceNodes], 2) + elseif AT <: ON_EDGES + nitems = size(grid[EdgeNodes], 2) + elseif AT <: ON_BFACES + nitems = size(grid[BFaceNodes], 2) + elseif AT <: ON_BEDGES + nitems = size(grid[BEdgeNodes], 2) + end + if O.parameters[:piecewise] + b = zeros(eltype(sol[1].entries), O.parameters[:resultdim], nitems) + else + b = zeros(eltype(sol[1].entries), O.parameters[:resultdim]) + end + O.assembler(b, [sol[j] for j in ind_args]) + return b end diff --git a/src/common_operators/item_integrator_dg.jl b/src/common_operators/item_integrator_dg.jl index 442292d..baac04e 100644 --- a/src/common_operators/item_integrator_dg.jl +++ b/src/common_operators/item_integrator_dg.jl @@ -1,64 +1,63 @@ - mutable struct ItemIntegratorDG{Tv <: Real, UT <: Union{Unknown, Integer}, KFT} <: AbstractOperator - u_args::Array{UT, 1} - ops_args::Array{DataType, 1} - kernel::KFT - BE_args_vals::Array{Vector{Matrix{Array{Tv, 3}}}} - FES_args::Any #::Array{FESpace,1} - BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} - QP_infos::Any #::Array{QPInfosT,1} - L2G::Any - QF::Any - assembler::Any - parameters::Dict{Symbol, Any} + u_args::Array{UT, 1} + ops_args::Array{DataType, 1} + kernel::KFT + BE_args_vals::Array{Vector{Matrix{Array{Tv, 3}}}} + FES_args::Any #::Array{FESpace,1} + BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} + QP_infos::Any #::Array{QPInfosT,1} + L2G::Any + QF::Any + assembler::Any + parameters::Dict{Symbol, Any} end default_iiopdg_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :entities => (ON_FACES, "assemble operator on these grid entities (default = ON_CELLS)"), - :name => ("ItemIntegratorDG", "name for operator used in printouts"), - :resultdim => (0, "dimension of result field (default = length of arguments)"), - :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), - :factor => (1, "factor that should be multiplied during assembly"), - :piecewise => (true, "returns piecewise integrations, otherwise a global integration"), - :quadorder => ("auto", "quadrature order"), - :bonus_quadorder => (0, "additional quadrature order added to quadorder"), - :verbosity => (0, "verbosity level"), - :regions => ([], "subset of regions where the item integrator should be evaluated"), + :entities => (ON_FACES, "assemble operator on these grid entities (default = ON_CELLS)"), + :name => ("ItemIntegratorDG", "name for operator used in printouts"), + :resultdim => (0, "dimension of result field (default = length of arguments)"), + :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), + :factor => (1, "factor that should be multiplied during assembly"), + :piecewise => (true, "returns piecewise integrations, otherwise a global integration"), + :quadorder => ("auto", "quadrature order"), + :bonus_quadorder => (0, "additional quadrature order added to quadorder"), + :verbosity => (0, "verbosity level"), + :regions => ([], "subset of regions where the item integrator should be evaluated"), ) # informs solver when operator needs reassembly function depends_nonlinearly_on(O::ItemIntegratorDG) - return unique(O.u_args) + return unique(O.u_args) end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::ItemIntegratorDG) - return O.parameters[:time_dependent] + return O.parameters[:time_dependent] end function Base.show(io::IO, O::ItemIntegratorDG) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($([test_function(dependencies[1][j]) for j = 1 : length(dependencies[1])]))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($([test_function(dependencies[1][j]) for j in 1:length(dependencies[1])]))") + return nothing end function ItemIntegratorDG(kernel, u_args, ops_args; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_iiopdg_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_args) == length(ops_args) - return ItemIntegratorDG{Tv, typeof(u_args[1]), typeof(kernel)}( - u_args, - ops_args, - kernel, - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - nothing, - nothing, - nothing, - nothing, - nothing, - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_iiopdg_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_args) == length(ops_args) + return ItemIntegratorDG{Tv, typeof(u_args[1]), typeof(kernel)}( + u_args, + ops_args, + kernel, + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + nothing, + nothing, + nothing, + nothing, + nothing, + parameters, + ) end """ @@ -92,214 +91,214 @@ $(_myprint(default_iiopdg_kwargs())) """ function ItemIntegratorDG(kernel, oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_args = [oa[1] for oa in oa_args] - ops_args = [oa[2] for oa in oa_args] - return ItemIntegratorDG(kernel, u_args, ops_args; kwargs...) + u_args = [oa[1] for oa in oa_args] + ops_args = [oa[2] for oa in oa_args] + return ItemIntegratorDG(kernel, u_args, ops_args; kwargs...) end function build_assembler!(O::ItemIntegratorDG{Tv}, FE_args::Array{<:FEVectorBlock, 1}; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_args = [FE_args[j].FES for j ∈ 1:length(FE_args)] - if O.FES_args != FES_args - - if O.parameters[:verbosity] > 0 - @info ".... building assembler for $(O.parameters[:name])" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_args) - - ## prepare assembly - AT = O.parameters[:entities] - @assert AT <: ON_FACES || AT <: ON_BFACES "only works for entities <: ON_FACES or ON_BFACES" - if AT <: ON_BFACES - AT = ON_FACES - bfaces = xgrid[BFaceFaces] - itemassemblygroups = zeros(Int, length(bfaces), 1) - itemassemblygroups[:] .= bfaces - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[1]), AT) - else - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[1]), AT) - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - end - Ti = typeof(xgrid).parameters[2] - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - FETypes_args = [eltype(F) for F in FES_args] - EGs = xgrid[UniqueCellGeometries] - - coeffs_ops_args = Array{Array{Float64, 1}, 1}([]) - for op in O.ops_args - push!(coeffs_ops_args, coeffs(op)) - end - - ## prepare assembly - nargs = length(FES_args) - O.QF = [] - O.BE_args = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) - O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_args = maximum([get_polynomialorder(FETypes_args[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_args[j]) for j ∈ 1:nargs]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_args + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - - ## generate DG operator - push!(O.BE_args, [generate_DG_operators(StandardFunctionOperator(O.ops_args[j]), FES_args[j], quadorder, EG) for j ∈ 1:nargs]) - push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) - - ## L2G map for EG - EGface = facetype_of_cellface(EG, 1) - push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) - - ## FE basis evaluator for EG - push!(O.BE_args_vals, [[O.BE_args[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_args[end][k])] for k ∈ 1:nargs]) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[regions] .= true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_args = [size(O.BE_args[1][j][1, 1].cvals, 1) for j ∈ 1:nargs] - piecewise = O.parameters[:piecewise] - - op_offsets_args = [0] - append!(op_offsets_args, cumsum(op_lengths_args)) - offsets_args = [FE_args[j].offset for j in 1:length(FES_args)] - resultdim::Int = O.parameters[:resultdim] - if resultdim == 0 - resultdim = op_offsets_args[end] - O.parameters[:resultdim] = resultdim - end - - FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), ON_CELLS) for j ∈ 1:nargs] - itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j ∈ 1:nargs] - factor = O.parameters[:factor] - - ## Assembly loop for fixed geometry - function assembly_loop( - b::AbstractMatrix{T}, - sol::Array{<:FEVectorBlock, 1}, - items, - EG::ElementGeometries, - QF::QuadratureRule, - BE_args::Vector{Matrix{<:FEEvaluator}}, - L2G::L2GTransformer, - QPinfos::QPInfos, - ) where {T} - - input_args = zeros(T, op_offsets_args[end]) - result_kernel = zeros(Tv, resultdim) - itemorientations = xgrid[CellFaceOrientations] - itemcells = xgrid[FaceCells] - itemnormals = xgrid[FaceNormals] - cellitems = xgrid[CellFaces] - - ndofs_args::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_args] - - weights, xref = QF.w, QF.xref - nweights = length(weights) - cell1::Int = 0 - orientation1::Int = 0 - itempos1::Int = 0 - - for item::Int in items - QPinfos.region = itemregions[item] - QPinfos.item = item - QPinfos.normal .= view(itemnormals, :, item) - QPinfos.volume = itemvolumes[item] - update_trafo!(L2G, item) - - boundary_face = itemcells[2, item] == 0 - if AT <: ON_IFACES - if boundary_face - continue - end - end - - for qp ∈ 1:nweights - ## evaluate arguments at quadrature points - fill!(input_args, 0) - for c1 ∈ 1:2 - cell1 = itemcells[c1, item] - if (cell1 > 0) - itempos1 = 1 - while !(cellitems[itempos1, cell1] == item) - itempos1 += 1 - end - orientation1 = itemorientations[itempos1, cell1] - - for j ∈ 1:nargs - BE_args[j][itempos1, orientation1].citem[] = cell1 - update_basis!(BE_args[j][itempos1, orientation1]) - end - - for id ∈ 1:nargs - for j ∈ 1:ndofs_args[id] - dof_j = itemdofs_args[id][j, cell1] - for d ∈ 1:op_lengths_args[id] - input_args[d+op_offsets_args[id]] += sol[id][dof_j] * BE_args[id][itempos1, orientation1].cvals[d, j, qp] * coeffs_ops_args[id][c1] - end - end - end - end - end - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # evaluate kernel - O.kernel(result_kernel, input_args, QPinfos) - result_kernel .*= factor * weights[qp] * itemvolumes[item] - - # integrate over item - if piecewise - b[1:resultdim, item] .+= result_kernel - else - b .+= result_kernel - end - end - - end - return - end - O.FES_args = FES_args - - function assembler(b, sol; kwargs...) - time_assembly = @elapsed begin - for j ∈ 1:length(EGs) - assembly_loop(b, sol, view(itemassemblygroups, :, j), EGs[j], O.QF[j], O.BE_args[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - if O.parameters[:verbosity] > 1 - @info ".... assembly of $(O.parameters[:name]) took $time_assembly s" - end - end - O.assembler = assembler - else - ## update the time - for j ∈ 1:length(O.QP_infos) - O.QP_infos[j].time = time - end - end + ## check if FES is the same as last time + FES_args = [FE_args[j].FES for j in 1:length(FE_args)] + return if O.FES_args != FES_args + + if O.parameters[:verbosity] > 0 + @info ".... building assembler for $(O.parameters[:name])" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_args) + + ## prepare assembly + AT = O.parameters[:entities] + @assert AT <: ON_FACES || AT <: ON_BFACES "only works for entities <: ON_FACES or ON_BFACES" + if AT <: ON_BFACES + AT = ON_FACES + bfaces = xgrid[BFaceFaces] + itemassemblygroups = zeros(Int, length(bfaces), 1) + itemassemblygroups[:] .= bfaces + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[1]), AT) + else + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[1]), AT) + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + end + Ti = typeof(xgrid).parameters[2] + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + FETypes_args = [eltype(F) for F in FES_args] + EGs = xgrid[UniqueCellGeometries] + + coeffs_ops_args = Array{Array{Float64, 1}, 1}([]) + for op in O.ops_args + push!(coeffs_ops_args, coeffs(op)) + end + + ## prepare assembly + nargs = length(FES_args) + O.QF = [] + O.BE_args = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) + O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_args = maximum([get_polynomialorder(FETypes_args[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_args[j]) for j in 1:nargs]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_args + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + + ## generate DG operator + push!(O.BE_args, [generate_DG_operators(StandardFunctionOperator(O.ops_args[j]), FES_args[j], quadorder, EG) for j in 1:nargs]) + push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) + + ## L2G map for EG + EGface = facetype_of_cellface(EG, 1) + push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) + + ## FE basis evaluator for EG + push!(O.BE_args_vals, [[O.BE_args[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_args[end][k])] for k in 1:nargs]) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[regions] .= true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_args = [size(O.BE_args[1][j][1, 1].cvals, 1) for j in 1:nargs] + piecewise = O.parameters[:piecewise] + + op_offsets_args = [0] + append!(op_offsets_args, cumsum(op_lengths_args)) + offsets_args = [FE_args[j].offset for j in 1:length(FES_args)] + resultdim::Int = O.parameters[:resultdim] + if resultdim == 0 + resultdim = op_offsets_args[end] + O.parameters[:resultdim] = resultdim + end + + FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), ON_CELLS) for j in 1:nargs] + itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j in 1:nargs] + factor = O.parameters[:factor] + + ## Assembly loop for fixed geometry + function assembly_loop( + b::AbstractMatrix{T}, + sol::Array{<:FEVectorBlock, 1}, + items, + EG::ElementGeometries, + QF::QuadratureRule, + BE_args::Vector{Matrix{<:FEEvaluator}}, + L2G::L2GTransformer, + QPinfos::QPInfos, + ) where {T} + + input_args = zeros(T, op_offsets_args[end]) + result_kernel = zeros(Tv, resultdim) + itemorientations = xgrid[CellFaceOrientations] + itemcells = xgrid[FaceCells] + itemnormals = xgrid[FaceNormals] + cellitems = xgrid[CellFaces] + + ndofs_args::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_args] + + weights, xref = QF.w, QF.xref + nweights = length(weights) + cell1::Int = 0 + orientation1::Int = 0 + itempos1::Int = 0 + + for item::Int in items + QPinfos.region = itemregions[item] + QPinfos.item = item + QPinfos.normal .= view(itemnormals, :, item) + QPinfos.volume = itemvolumes[item] + update_trafo!(L2G, item) + + boundary_face = itemcells[2, item] == 0 + if AT <: ON_IFACES + if boundary_face + continue + end + end + + for qp in 1:nweights + ## evaluate arguments at quadrature points + fill!(input_args, 0) + for c1 in 1:2 + cell1 = itemcells[c1, item] + if (cell1 > 0) + itempos1 = 1 + while !(cellitems[itempos1, cell1] == item) + itempos1 += 1 + end + orientation1 = itemorientations[itempos1, cell1] + + for j in 1:nargs + BE_args[j][itempos1, orientation1].citem[] = cell1 + update_basis!(BE_args[j][itempos1, orientation1]) + end + + for id in 1:nargs + for j in 1:ndofs_args[id] + dof_j = itemdofs_args[id][j, cell1] + for d in 1:op_lengths_args[id] + input_args[d + op_offsets_args[id]] += sol[id][dof_j] * BE_args[id][itempos1, orientation1].cvals[d, j, qp] * coeffs_ops_args[id][c1] + end + end + end + end + end + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # evaluate kernel + O.kernel(result_kernel, input_args, QPinfos) + result_kernel .*= factor * weights[qp] * itemvolumes[item] + + # integrate over item + if piecewise + b[1:resultdim, item] .+= result_kernel + else + b .+= result_kernel + end + end + + end + return + end + O.FES_args = FES_args + + function assembler(b, sol; kwargs...) + time_assembly = @elapsed begin + for j in 1:length(EGs) + assembly_loop(b, sol, view(itemassemblygroups, :, j), EGs[j], O.QF[j], O.BE_args[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + return if O.parameters[:verbosity] > 1 + @info ".... assembly of $(O.parameters[:name]) took $time_assembly s" + end + end + O.assembler = assembler + else + ## update the time + for j in 1:length(O.QP_infos) + O.QP_infos[j].time = time + end + end end """ @@ -315,9 +314,9 @@ function evaluate( Evaluates the ItemIntegratorDG for the specified solution into the matrix b. """ function ExtendableFEMBase.evaluate!(b, O::ItemIntegratorDG, sol::FEVector; kwargs...) - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] - build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) - O.assembler(b, [sol[j] for j in ind_args]) + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] + build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) + return O.assembler(b, [sol[j] for j in ind_args]) end """ @@ -333,9 +332,9 @@ function evaluate( Evaluates the ItemIntegratorDG for the specified solution into the matrix b. """ function ExtendableFEMBase.evaluate!(b, O::ItemIntegratorDG, sol::Array{<:FEVectorBlock, 1}; kwargs...) - ind_args = O.u_args - build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) - O.assembler(b, [sol[j] for j in ind_args]) + ind_args = O.u_args + build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) + return O.assembler(b, [sol[j] for j in ind_args]) end """ @@ -350,30 +349,30 @@ function evaluate( Evaluates the ItemIntegratorDG for the specified solution and returns an matrix of size resultdim x num_items. """ function evaluate(O::ItemIntegratorDG{Tv, UT}, sol; kwargs...) where {Tv, UT} - if UT <: Integer - ind_args = O.u_args - elseif UT <: Unknown - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] - end - build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) - grid = sol[ind_args[1]].FES.xgrid - AT = O.parameters[:entities] - if AT <: ON_CELLS - nitems = num_cells(grid) - elseif AT <: ON_FACES - nitems = size(grid[FaceNodes], 2) - elseif AT <: ON_EDGES - nitems = size(grid[EdgeNodes], 2) - elseif AT <: ON_BFACES - nitems = size(grid[BFaceNodes], 2) - elseif AT <: ON_BEDGES - nitems = size(grid[BEdgeNodes], 2) - end - if O.parameters[:piecewise] - b = zeros(eltype(sol[1].entries), O.parameters[:resultdim], nitems) - else - b = zeros(eltype(sol[1].entries), O.parameters[:resultdim]) - end - O.assembler(b, [sol[j] for j in ind_args]) - return b + if UT <: Integer + ind_args = O.u_args + elseif UT <: Unknown + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] + end + build_assembler!(O, [sol[j] for j in ind_args]; kwargs...) + grid = sol[ind_args[1]].FES.xgrid + AT = O.parameters[:entities] + if AT <: ON_CELLS + nitems = num_cells(grid) + elseif AT <: ON_FACES + nitems = size(grid[FaceNodes], 2) + elseif AT <: ON_EDGES + nitems = size(grid[EdgeNodes], 2) + elseif AT <: ON_BFACES + nitems = size(grid[BFaceNodes], 2) + elseif AT <: ON_BEDGES + nitems = size(grid[BEdgeNodes], 2) + end + if O.parameters[:piecewise] + b = zeros(eltype(sol[1].entries), O.parameters[:resultdim], nitems) + else + b = zeros(eltype(sol[1].entries), O.parameters[:resultdim]) + end + O.assembler(b, [sol[j] for j in ind_args]) + return b end diff --git a/src/common_operators/linear_operator.jl b/src/common_operators/linear_operator.jl index 61d2a06..ffe8b18 100644 --- a/src/common_operators/linear_operator.jl +++ b/src/common_operators/linear_operator.jl @@ -1,118 +1,118 @@ mutable struct LinearOperatorFromVector{UT <: Union{Unknown, Integer}, bT} <: AbstractOperator - u_test::Array{UT, 1} - b::bT - parameters::Dict{Symbol, Any} + u_test::Array{UT, 1} + b::bT + parameters::Dict{Symbol, Any} end mutable struct LinearOperatorFromMatrix{UT <: Union{Unknown, Integer}, MT} <: AbstractOperator - u_test::Array{UT, 1} - u_args::Array{UT, 1} - A::MT - parameters::Dict{Symbol, Any} + u_test::Array{UT, 1} + u_args::Array{UT, 1} + A::MT + parameters::Dict{Symbol, Any} end mutable struct LinearOperator{Tv <: Real, UT <: Union{Unknown, Integer}, KFT, ST} <: AbstractOperator - u_test::Array{UT, 1} - ops_test::Array{DataType, 1} - u_args::Array{UT, 1} - ops_args::Array{DataType, 1} - kernel::KFT - BE_test_vals::Array{Array{Array{Tv, 3}, 1}} - BE_args_vals::Array{Array{Array{Tv, 3}, 1}} - FES_test::Any #::Array{FESpace,1} - FES_args::Any #::Array{FESpace,1} - BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} - BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} - QP_infos::Any #::Array{QPInfosT,1} - L2G::Any - QF::Any - assembler::Any - storage::ST - parameters::Dict{Symbol, Any} + u_test::Array{UT, 1} + ops_test::Array{DataType, 1} + u_args::Array{UT, 1} + ops_args::Array{DataType, 1} + kernel::KFT + BE_test_vals::Array{Array{Array{Tv, 3}, 1}} + BE_args_vals::Array{Array{Array{Tv, 3}, 1}} + FES_test::Any #::Array{FESpace,1} + FES_args::Any #::Array{FESpace,1} + BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} + BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} + QP_infos::Any #::Array{QPInfosT,1} + L2G::Any + QF::Any + assembler::Any + storage::ST + parameters::Dict{Symbol, Any} end default_linop_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), - :name => ("LinearOperator", "name for operator used in printouts"), - :parallel_groups => (false, "assemble operator in parallel using CellAssemblyGroups"), - :parallel => (false, "assemble operator in parallel using colors/partitions information"), - :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), - :factor => (1, "factor that should be multiplied during assembly"), - :store => (false, "store matrix separately (and copy from there when reassembly is triggered)"), - :quadorder => ("auto", "quadrature order"), - :bonus_quadorder => (0, "additional quadrature order added to quadorder"), - :time_dependent => (false, "operator is time-dependent ?"), - :verbosity => (0, "verbosity level"), - :regions => ([], "subset of regions where operator should be assembly only"), + :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), + :name => ("LinearOperator", "name for operator used in printouts"), + :parallel_groups => (false, "assemble operator in parallel using CellAssemblyGroups"), + :parallel => (false, "assemble operator in parallel using colors/partitions information"), + :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), + :factor => (1, "factor that should be multiplied during assembly"), + :store => (false, "store matrix separately (and copy from there when reassembly is triggered)"), + :quadorder => ("auto", "quadrature order"), + :bonus_quadorder => (0, "additional quadrature order added to quadorder"), + :time_dependent => (false, "operator is time-dependent ?"), + :verbosity => (0, "verbosity level"), + :regions => ([], "subset of regions where operator should be assembly only"), ) # informs solver when operator needs reassembly function depends_nonlinearly_on(O::LinearOperator) - return unique(O.u_args) + return unique(O.u_args) end # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::LinearOperator) - return [unique(O.u_test)] + return [unique(O.u_test)] end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::LinearOperator) - return O.parameters[:time_dependent] + return O.parameters[:time_dependent] end function Base.show(io::IO, O::LinearOperator) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($([test_function(dependencies[1][j]) for j = 1 : length(dependencies[1])]))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($([test_function(dependencies[1][j]) for j in 1:length(dependencies[1])]))") + return nothing end function Base.show(io::IO, O::Union{LinearOperatorFromVector, LinearOperatorFromMatrix}) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])") + return nothing end function LinearOperator(kernel, u_test, ops_test, u_args, ops_args; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_linop_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_args) == length(ops_args) - @assert length(u_test) == length(ops_test) - if parameters[:store] - storage = zeros(Tv, 0) - else - storage = nothing - end - return LinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( - u_test, - ops_test, - u_args, - ops_args, - kernel, - [[zeros(Tv, 0, 0, 0)]], - [[zeros(Tv, 0, 0, 0)]], - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - storage, - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_linop_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_args) == length(ops_args) + @assert length(u_test) == length(ops_test) + if parameters[:store] + storage = zeros(Tv, 0) + else + storage = nothing + end + return LinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( + u_test, + ops_test, + u_args, + ops_args, + kernel, + [[zeros(Tv, 0, 0, 0)]], + [[zeros(Tv, 0, 0, 0)]], + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + storage, + parameters, + ) end function LinearOperator(kernel, u_test, ops_test::Array{DataType, 1}; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_linop_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_test) == length(ops_test) - if parameters[:store] - storage = zeros(Tv, 0) - else - storage = nothing - end - return LinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}(u_test, ops_test, [], [], kernel, [[zeros(Tv, 0, 0, 0)]], [[zeros(Tv, 0, 0, 0)]], nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, storage, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_linop_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_test) == length(ops_test) + if parameters[:store] + storage = zeros(Tv, 0) + else + storage = nothing + end + return LinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}(u_test, ops_test, [], [], kernel, [[zeros(Tv, 0, 0, 0)]], [[zeros(Tv, 0, 0, 0)]], nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, storage, parameters) end """ @@ -145,15 +145,15 @@ $(_myprint(default_linop_kwargs())) """ function LinearOperator(kernel, oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_test = [oa[1] for oa in oa_test] - ops_test = [oa[2] for oa in oa_test] - return LinearOperator(kernel, u_test, ops_test; kwargs...) + u_test = [oa[1] for oa in oa_test] + ops_test = [oa[2] for oa in oa_test] + return LinearOperator(kernel, u_test, ops_test; kwargs...) end function LinearOperator(oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_test = [oa[1] for oa in oa_test] - ops_test = [oa[2] for oa in oa_test] - return LinearOperator(ExtendableFEMBase.constant_one_kernel, u_test, ops_test; kwargs...) + u_test = [oa[1] for oa in oa_test] + ops_test = [oa[2] for oa in oa_test] + return LinearOperator(ExtendableFEMBase.constant_one_kernel, u_test, ops_test; kwargs...) end @@ -170,9 +170,9 @@ multiple blocks. The argument u_test specifies where to put the (blocks of the) """ function LinearOperator(b, u_test; kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_linop_kwargs()) - _update_params!(parameters, kwargs) - return LinearOperatorFromVector{typeof(u_test[1]), typeof(b)}(u_test, b, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_linop_kwargs()) + _update_params!(parameters, kwargs) + return LinearOperatorFromVector{typeof(u_test[1]), typeof(b)}(u_test, b, parameters) end """ @@ -191,9 +191,9 @@ should be multiplied with the matrix and u_test specifies where to put the """ function LinearOperator(A::AbstractMatrix, u_test::Array{<:Union{Unknown, Int}, 1}, u_args::Array{<:Union{Unknown, Int}, 1}; kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_linop_kwargs()) - _update_params!(parameters, kwargs) - return LinearOperatorFromMatrix{typeof(u_test[1]), typeof(A)}(u_test, u_args, A, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_linop_kwargs()) + _update_params!(parameters, kwargs) + return LinearOperatorFromMatrix{typeof(u_test[1]), typeof(A)}(u_test, u_args, A, parameters) end @@ -225,552 +225,551 @@ $(_myprint(default_linop_kwargs())) """ function LinearOperator(kernel, oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_args = [oa[1] for oa in oa_args] - ops_test = [oa[2] for oa in oa_test] - ops_args = [oa[2] for oa in oa_args] - return LinearOperator(kernel, u_test, ops_test, u_args, ops_args; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_args = [oa[1] for oa in oa_args] + ops_test = [oa[2] for oa in oa_test] + ops_args = [oa[2] for oa in oa_args] + return LinearOperator(kernel, u_test, ops_test, u_args, ops_args; kwargs...) end function LinearOperator(oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_args = [oa[1] for oa in oa_args] - ops_test = [oa[2] for oa in oa_test] - ops_args = [oa[2] for oa in oa_args] - return LinearOperator(ExtendableFEMBase.standard_kernel, u_test, ops_test, u_args, ops_args; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_args = [oa[1] for oa in oa_args] + ops_test = [oa[2] for oa in oa_test] + ops_args = [oa[2] for oa in oa_args] + return LinearOperator(ExtendableFEMBase.standard_kernel, u_test, ops_test, u_args, ops_args; kwargs...) end function build_assembler!(b, O::LinearOperator{Tv}, FE_test, FE_args; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_test = [FE_test[j].FES for j ∈ 1:length(FE_test)] - FES_args = [FE_args[j].FES for j ∈ 1:length(FE_args)] - if (O.FES_test != FES_test) || (O.FES_args != FES_args) - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_test, FES_args) - AT = O.parameters[:entities] - if xgrid == FES_test[1].dofgrid - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) - else - gridAT = AT - end - - ## prepare assembly - Ti = typeof(xgrid).parameters[2] - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pc = xgrid[PartitionCells] - itemassemblygroups = [pc[j]:pc[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - # assuming here that all cells of one partition have the same geometry - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - Ti = typeof(xgrid).parameters[2] - has_normals = true - if AT <: ON_FACES - itemnormals = xgrid[FaceNormals] - elseif AT <: ON_BFACES - itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] - else - has_normals = false - end - FETypes_test = [eltype(F) for F in FES_test] - FETypes_args = [eltype(F) for F in FES_args] - EGs = [itemgeometries[itemassemblygroups[j][1]] for j ∈ 1:length(itemassemblygroups)] - - ## prepare assembly - nargs = length(FES_args) - ntest = length(FES_test) - O.QF = [] - O.BE_test = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.BE_args = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_args = maximum([get_polynomialorder(FETypes_args[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_args[j]) for j ∈ 1:nargs]) - polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j ∈ 1:ntest]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_args + polyorder_test + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) - - ## FE basis evaluator for EG - push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]; AT = AT) for j in 1:ntest]) - push!(O.BE_args, [FEEvaluator(FES_args[j], O.ops_args[j], O.QF[end]; AT = AT) for j in 1:nargs]) - push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) - push!(O.BE_args_vals, [BE.cvals for BE in O.BE_args[end]]) - - ## L2G map for EG - push!(O.L2G, L2GTransformer(EG, xgrid, ON_CELLS)) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[regions] .= true - else - visit_region .= true - end - - ## prepare parameters - - ## prepare operator infos - op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j ∈ 1:ntest] - op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j ∈ 1:nargs] - - op_offsets_test = [0] - op_offsets_args = [0] - append!(op_offsets_test, cumsum(op_lengths_test)) - append!(op_offsets_args, cumsum(op_lengths_args)) - - ## prepare parallel assembly - if O.parameters[:parallel_groups] - bj = Array{typeof(b), 1}(undef, length(EGs)) - for j ∈ 1:length(EGs) - bj[j] = copy(b) - end - end - - FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j ∈ 1:ntest] - FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j ∈ 1:nargs] - itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j ∈ 1:ntest] - itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j ∈ 1:nargs] - factor = O.parameters[:factor] - - ## Assembly loop for fixed geometry - function assembly_loop( - b::AbstractVector{T}, - sol::Array{<:FEVectorBlock, 1}, - items, - EG::ElementGeometries, - QF::QuadratureRule, - BE_test::Array{<:FEEvaluator, 1}, - BE_args::Array{<:FEEvaluator, 1}, - BE_test_vals::Array{Array{Tv, 3}, 1}, - BE_args_vals::Array{Array{Tv, 3}, 1}, - L2G::L2GTransformer, - QPinfos::QPInfos, - ) where {T} - - ## prepare parameters - input_args = zeros(Tv, op_offsets_args[end]) - result_kernel = zeros(Tv, op_offsets_test[end]) - offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] - - ndofs_test::Array{Int, 1} = [get_ndofs(AT, FE, EG) for FE in FETypes_test] - ndofs_args::Array{Int, 1} = [get_ndofs(AT, FE, EG) for FE in FETypes_args] - weights, xref = QF.w, QF.xref - nweights = length(weights) - - for item::Int in items - if itemregions[item] > 0 - if !(visit_region[itemregions[item]]) || AT == ON_IFACES - continue - end - end - QPinfos.region = itemregions[item] - QPinfos.item = item - if has_normals - QPinfos.normal .= view(itemnormals, :, item) - end - QPinfos.volume = itemvolumes[item] - - ## update FE basis evaluators - for j ∈ 1:ntest - BE_test[j].citem[] = item - update_basis!(BE_test[j]) - end - for j ∈ 1:nargs - BE_args[j].citem[] = item - update_basis!(BE_args[j]) - end - update_trafo!(L2G, item) - - ## evaluate arguments - for qp ∈ 1:nweights - fill!(input_args, 0) - for id ∈ 1:nargs - for j ∈ 1:ndofs_args[id] - dof_j = itemdofs_args[id][j, item] - for d ∈ 1:op_lengths_args[id] - input_args[d+op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][d, j, qp] - end - end - end - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # evaluate kernel - O.kernel(result_kernel, input_args, QPinfos) - result_kernel .*= factor * weights[qp] * itemvolumes[item] - - # multiply test function operator evaluation - for idt ∈ 1:ntest - for k ∈ 1:ndofs_test[idt] - dof = itemdofs_test[idt][k, item] + offsets_test[idt] - for d ∈ 1:op_lengths_test[idt] - b[dof] += result_kernel[d+op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] - end - end - end - end - end - return - end - O.FES_test = FES_test - O.FES_args = FES_args - - function assembler(b, sol; kwargs...) - time = @elapsed begin - if O.parameters[:parallel] - pcp = xgrid[PColorPartitions] - ncolors = length(pcp) - 1 - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - for color in 1:ncolors - Threads.@threads for part in pcp[color]:pcp[color+1]-1 - assembly_loop(b, sol, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_args[part], O.BE_test_vals[part], O.BE_args_vals[part], O.L2G[part], O.QP_infos[part]; kwargs...) - end - end - elseif O.parameters[:parallel_groups] - Threads.@threads for j ∈ 1:length(EGs) - fill!(bj[j], 0) - assembly_loop(bj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - for j ∈ 1:length(EGs) - b .+= bj[j] - end - else - for j ∈ 1:length(EGs) - assembly_loop(b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - end - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end - end - O.assembler = assembler - else - ## update the time - for j ∈ 1:length(O.QP_infos) - O.QP_infos[j].time = time - end - end + ## check if FES is the same as last time + FES_test = [FE_test[j].FES for j in 1:length(FE_test)] + FES_args = [FE_args[j].FES for j in 1:length(FE_args)] + return if (O.FES_test != FES_test) || (O.FES_args != FES_args) + + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_test, FES_args) + AT = O.parameters[:entities] + if xgrid == FES_test[1].dofgrid + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) + else + gridAT = AT + end + + ## prepare assembly + Ti = typeof(xgrid).parameters[2] + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pc = xgrid[PartitionCells] + itemassemblygroups = [pc[j]:(pc[j + 1] - 1) for j in 1:num_partitions(xgrid)] + # assuming here that all cells of one partition have the same geometry + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + Ti = typeof(xgrid).parameters[2] + has_normals = true + if AT <: ON_FACES + itemnormals = xgrid[FaceNormals] + elseif AT <: ON_BFACES + itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] + else + has_normals = false + end + FETypes_test = [eltype(F) for F in FES_test] + FETypes_args = [eltype(F) for F in FES_args] + EGs = [itemgeometries[itemassemblygroups[j][1]] for j in 1:length(itemassemblygroups)] + + ## prepare assembly + nargs = length(FES_args) + ntest = length(FES_test) + O.QF = [] + O.BE_test = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.BE_args = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_args = maximum([get_polynomialorder(FETypes_args[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_args[j]) for j in 1:nargs]) + polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j in 1:ntest]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_args + polyorder_test + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) + + ## FE basis evaluator for EG + push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]; AT = AT) for j in 1:ntest]) + push!(O.BE_args, [FEEvaluator(FES_args[j], O.ops_args[j], O.QF[end]; AT = AT) for j in 1:nargs]) + push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) + push!(O.BE_args_vals, [BE.cvals for BE in O.BE_args[end]]) + + ## L2G map for EG + push!(O.L2G, L2GTransformer(EG, xgrid, ON_CELLS)) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[regions] .= true + else + visit_region .= true + end + + ## prepare parameters + + ## prepare operator infos + op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j in 1:ntest] + op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j in 1:nargs] + + op_offsets_test = [0] + op_offsets_args = [0] + append!(op_offsets_test, cumsum(op_lengths_test)) + append!(op_offsets_args, cumsum(op_lengths_args)) + + ## prepare parallel assembly + if O.parameters[:parallel_groups] + bj = Array{typeof(b), 1}(undef, length(EGs)) + for j in 1:length(EGs) + bj[j] = copy(b) + end + end + + FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j in 1:ntest] + FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j in 1:nargs] + itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j in 1:ntest] + itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j in 1:nargs] + factor = O.parameters[:factor] + + ## Assembly loop for fixed geometry + function assembly_loop( + b::AbstractVector{T}, + sol::Array{<:FEVectorBlock, 1}, + items, + EG::ElementGeometries, + QF::QuadratureRule, + BE_test::Array{<:FEEvaluator, 1}, + BE_args::Array{<:FEEvaluator, 1}, + BE_test_vals::Array{Array{Tv, 3}, 1}, + BE_args_vals::Array{Array{Tv, 3}, 1}, + L2G::L2GTransformer, + QPinfos::QPInfos, + ) where {T} + + ## prepare parameters + input_args = zeros(Tv, op_offsets_args[end]) + result_kernel = zeros(Tv, op_offsets_test[end]) + offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] + + ndofs_test::Array{Int, 1} = [get_ndofs(AT, FE, EG) for FE in FETypes_test] + ndofs_args::Array{Int, 1} = [get_ndofs(AT, FE, EG) for FE in FETypes_args] + weights, xref = QF.w, QF.xref + nweights = length(weights) + + for item::Int in items + if itemregions[item] > 0 + if !(visit_region[itemregions[item]]) || AT == ON_IFACES + continue + end + end + QPinfos.region = itemregions[item] + QPinfos.item = item + if has_normals + QPinfos.normal .= view(itemnormals, :, item) + end + QPinfos.volume = itemvolumes[item] + + ## update FE basis evaluators + for j in 1:ntest + BE_test[j].citem[] = item + update_basis!(BE_test[j]) + end + for j in 1:nargs + BE_args[j].citem[] = item + update_basis!(BE_args[j]) + end + update_trafo!(L2G, item) + + ## evaluate arguments + for qp in 1:nweights + fill!(input_args, 0) + for id in 1:nargs + for j in 1:ndofs_args[id] + dof_j = itemdofs_args[id][j, item] + for d in 1:op_lengths_args[id] + input_args[d + op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][d, j, qp] + end + end + end + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # evaluate kernel + O.kernel(result_kernel, input_args, QPinfos) + result_kernel .*= factor * weights[qp] * itemvolumes[item] + + # multiply test function operator evaluation + for idt in 1:ntest + for k in 1:ndofs_test[idt] + dof = itemdofs_test[idt][k, item] + offsets_test[idt] + for d in 1:op_lengths_test[idt] + b[dof] += result_kernel[d + op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] + end + end + end + end + end + return + end + O.FES_test = FES_test + O.FES_args = FES_args + + function assembler(b, sol; kwargs...) + time = @elapsed begin + if O.parameters[:parallel] + pcp = xgrid[PColorPartitions] + ncolors = length(pcp) - 1 + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + for color in 1:ncolors + Threads.@threads for part in pcp[color]:(pcp[color + 1] - 1) + assembly_loop(b, sol, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_args[part], O.BE_test_vals[part], O.BE_args_vals[part], O.L2G[part], O.QP_infos[part]; kwargs...) + end + end + elseif O.parameters[:parallel_groups] + Threads.@threads for j in 1:length(EGs) + fill!(bj[j], 0) + assembly_loop(bj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + for j in 1:length(EGs) + b .+= bj[j] + end + else + for j in 1:length(EGs) + assembly_loop(b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + end + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end + end + O.assembler = assembler + else + ## update the time + for j in 1:length(O.QP_infos) + O.QP_infos[j].time = time + end + end end function build_assembler!(b, O::LinearOperator{Tv}, FE_test::Array{<:FEVectorBlock, 1}; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_test = [FE_test[j].FES for j ∈ 1:length(FE_test)] - if (O.FES_test != FES_test) - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - end - - ## determine grid - AT = O.parameters[:entities] - xgrid = determine_assembly_grid(FES_test) - if xgrid == FES_test[1].dofgrid - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) - else - gridAT = AT - end - - ## prepare assembly - Ti = typeof(xgrid).parameters[2] - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pc = xgrid[PartitionCells] - itemassemblygroups = [pc[j]:pc[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - # assuming here that all cells of one partition have the same geometry - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - has_normals = true - if AT <: ON_FACES - itemnormals = xgrid[FaceNormals] - elseif AT <: ON_BFACES - itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] - else - has_normals = false - end - FETypes_test = [eltype(F) for F in FES_test] - EGs = [itemgeometries[itemassemblygroups[j][1]] for j ∈ 1:length(itemassemblygroups)] - - ## prepare assembly - ntest = length(FES_test) - O.QF = [] - O.BE_test = Array{Array{<:FEEvaluator, 1}, 1}([]) - O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j ∈ 1:ntest]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_test + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) - - ## FE basis evaluator for EG - push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]; AT = AT) for j in 1:ntest]) - push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) - - ## L2G map for EG - push!(O.L2G, L2GTransformer(EG, xgrid, AT)) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[regions] .= true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j ∈ 1:ntest] - - op_offsets_test = [0] - append!(op_offsets_test, cumsum(op_lengths_test)) - - ## prepare parallel assembly - if O.parameters[:parallel_groups] - bj = Array{typeof(b), 1}(undef, length(EGs)) - for j ∈ 1:length(EGs) - bj[j] = copy(b) - end - end - - FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j ∈ 1:ntest] - itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j ∈ 1:ntest] - factor = O.parameters[:factor] - - ## Assembly loop for fixed geometry - function assembly_loop(b::AbstractVector{T}, items, EG::ElementGeometries, QF::QuadratureRule, BE_test::Array{<:FEEvaluator, 1}, BE_test_vals::Array{Array{Tv, 3}, 1}, L2G::L2GTransformer, QPinfos::QPInfos) where {T} - - ## prepare parameters - result_kernel = zeros(Tv, op_offsets_test[end]) - offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] - - ndofs_test::Array{Int, 1} = [get_ndofs(AT, FE, EG) for FE in FETypes_test] - weights, xref = QF.w, QF.xref - nweights = length(weights) - - for item::Int in items - if itemregions[item] > 0 - if !(visit_region[itemregions[item]]) || AT == ON_IFACES - continue - end - else - if length(regions) > 0 - continue - end - end - QPinfos.region = itemregions[item] - QPinfos.item = item - if has_normals - QPinfos.normal .= view(itemnormals, :, item) - end - QPinfos.volume = itemvolumes[item] - - ## update FE basis evaluators - for j ∈ 1:ntest - BE_test[j].citem[] = item - update_basis!(BE_test[j]) - end - update_trafo!(L2G, item) - - ## evaluate arguments - for qp ∈ 1:nweights - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # evaluate kernel - O.kernel(result_kernel, QPinfos) - result_kernel .*= factor * weights[qp] * itemvolumes[item] - - # multiply test function operator evaluation - for idt ∈ 1:ntest - for k ∈ 1:ndofs_test[idt] - dof = itemdofs_test[idt][k, item] + offsets_test[idt] - for d ∈ 1:op_lengths_test[idt] - b[dof] += result_kernel[d+op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] - end - end - end - end - end - return - end - O.FES_test = FES_test - - function assembler(b; kwargs...) - time = @elapsed begin - if O.parameters[:store] && size(b) == size(O.storage) - b .+= O.storage - else - if O.parameters[:store] - s = zeros(eltype(b), length(b)) - else - s = b - end - if O.parameters[:parallel] - pcp = xgrid[PColorPartitions] - ncolors = length(pcp) - 1 - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - for color in 1:ncolors - Threads.@threads for part in pcp[color]:pcp[color+1]-1 - assembly_loop(s, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_test_vals[part], O.L2G[part], O.QP_infos[part]; kwargs...) - end - end - elseif O.parameters[:parallel_groups] - Threads.@threads for j ∈ 1:length(EGs) - fill!(bj[j], 0) - assembly_loop(bj[j], itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_test_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - for j ∈ 1:length(EGs) - s .+= bj[j] - end - else - for j ∈ 1:length(EGs) - assembly_loop(s, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_test_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - if O.parameters[:store] - b .+= s - O.storage = s - end - end - end - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end - end - O.assembler = assembler - else - ## update the time - for j ∈ 1:length(O.QP_infos) - O.QP_infos[j].time = time - end - end + ## check if FES is the same as last time + FES_test = [FE_test[j].FES for j in 1:length(FE_test)] + return if (O.FES_test != FES_test) + + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + end + + ## determine grid + AT = O.parameters[:entities] + xgrid = determine_assembly_grid(FES_test) + if xgrid == FES_test[1].dofgrid + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) + else + gridAT = AT + end + + ## prepare assembly + Ti = typeof(xgrid).parameters[2] + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pc = xgrid[PartitionCells] + itemassemblygroups = [pc[j]:(pc[j + 1] - 1) for j in 1:num_partitions(xgrid)] + # assuming here that all cells of one partition have the same geometry + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + has_normals = true + if AT <: ON_FACES + itemnormals = xgrid[FaceNormals] + elseif AT <: ON_BFACES + itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] + else + has_normals = false + end + FETypes_test = [eltype(F) for F in FES_test] + EGs = [itemgeometries[itemassemblygroups[j][1]] for j in 1:length(itemassemblygroups)] + + ## prepare assembly + ntest = length(FES_test) + O.QF = [] + O.BE_test = Array{Array{<:FEEvaluator, 1}, 1}([]) + O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j in 1:ntest]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_test + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) + + ## FE basis evaluator for EG + push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]; AT = AT) for j in 1:ntest]) + push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) + + ## L2G map for EG + push!(O.L2G, L2GTransformer(EG, xgrid, AT)) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[regions] .= true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j in 1:ntest] + + op_offsets_test = [0] + append!(op_offsets_test, cumsum(op_lengths_test)) + + ## prepare parallel assembly + if O.parameters[:parallel_groups] + bj = Array{typeof(b), 1}(undef, length(EGs)) + for j in 1:length(EGs) + bj[j] = copy(b) + end + end + + FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j in 1:ntest] + itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j in 1:ntest] + factor = O.parameters[:factor] + + ## Assembly loop for fixed geometry + function assembly_loop(b::AbstractVector{T}, items, EG::ElementGeometries, QF::QuadratureRule, BE_test::Array{<:FEEvaluator, 1}, BE_test_vals::Array{Array{Tv, 3}, 1}, L2G::L2GTransformer, QPinfos::QPInfos) where {T} + + ## prepare parameters + result_kernel = zeros(Tv, op_offsets_test[end]) + offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] + + ndofs_test::Array{Int, 1} = [get_ndofs(AT, FE, EG) for FE in FETypes_test] + weights, xref = QF.w, QF.xref + nweights = length(weights) + + for item::Int in items + if itemregions[item] > 0 + if !(visit_region[itemregions[item]]) || AT == ON_IFACES + continue + end + else + if length(regions) > 0 + continue + end + end + QPinfos.region = itemregions[item] + QPinfos.item = item + if has_normals + QPinfos.normal .= view(itemnormals, :, item) + end + QPinfos.volume = itemvolumes[item] + + ## update FE basis evaluators + for j in 1:ntest + BE_test[j].citem[] = item + update_basis!(BE_test[j]) + end + update_trafo!(L2G, item) + + ## evaluate arguments + for qp in 1:nweights + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # evaluate kernel + O.kernel(result_kernel, QPinfos) + result_kernel .*= factor * weights[qp] * itemvolumes[item] + + # multiply test function operator evaluation + for idt in 1:ntest + for k in 1:ndofs_test[idt] + dof = itemdofs_test[idt][k, item] + offsets_test[idt] + for d in 1:op_lengths_test[idt] + b[dof] += result_kernel[d + op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] + end + end + end + end + end + return + end + O.FES_test = FES_test + + function assembler(b; kwargs...) + time = @elapsed begin + if O.parameters[:store] && size(b) == size(O.storage) + b .+= O.storage + else + if O.parameters[:store] + s = zeros(eltype(b), length(b)) + else + s = b + end + if O.parameters[:parallel] + pcp = xgrid[PColorPartitions] + ncolors = length(pcp) - 1 + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + for color in 1:ncolors + Threads.@threads for part in pcp[color]:(pcp[color + 1] - 1) + assembly_loop(s, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_test_vals[part], O.L2G[part], O.QP_infos[part]; kwargs...) + end + end + elseif O.parameters[:parallel_groups] + Threads.@threads for j in 1:length(EGs) + fill!(bj[j], 0) + assembly_loop(bj[j], itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_test_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + for j in 1:length(EGs) + s .+= bj[j] + end + else + for j in 1:length(EGs) + assembly_loop(s, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_test_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + if O.parameters[:store] + b .+= s + O.storage = s + end + end + end + + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end + end + O.assembler = assembler + else + ## update the time + for j in 1:length(O.QP_infos) + O.QP_infos[j].time = time + end + end end function assemble!(A, b, sol, O::LinearOperator{Tv, UT}, SC::SolverConfiguration; assemble_rhs = true, kwargs...) where {Tv, UT} - if !assemble_rhs - return - end - if UT <: Integer - ind_test = O.u_test - ind_args = O.u_args - elseif UT <: Unknown - ind_test = [get_unknown_id(SC, u) for u in O.u_test] - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] # [get_unknown_id(SC, u) for u in O.u_args] - end - if length(O.u_args) > 0 - build_assembler!(b.entries, O, [b[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) - O.assembler(b.entries, [sol[j] for j in ind_args]) - else - build_assembler!(b.entries, O, [b[j] for j in ind_test]; kwargs...) - O.assembler(b.entries) - end + if !assemble_rhs + return + end + if UT <: Integer + ind_test = O.u_test + ind_args = O.u_args + elseif UT <: Unknown + ind_test = [get_unknown_id(SC, u) for u in O.u_test] + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] # [get_unknown_id(SC, u) for u in O.u_args] + end + return if length(O.u_args) > 0 + build_assembler!(b.entries, O, [b[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) + O.assembler(b.entries, [sol[j] for j in ind_args]) + else + build_assembler!(b.entries, O, [b[j] for j in ind_test]; kwargs...) + O.assembler(b.entries) + end end function assemble!(b::FEVector, O::LinearOperator{Tv, UT}, sol = nothing; assemble_rhs = true, kwargs...) where {Tv, UT} - if !assemble_rhs - return - end - ind_test = O.u_test - ind_args = O.u_args - if length(O.u_args) > 0 - build_assembler!(b.entries, O, [b[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) - O.assembler(b.entries, [sol[j] for j in ind_args]) - else - build_assembler!(b.entries, O, [b[j] for j in ind_test]; kwargs...) - O.assembler(b.entries) - end + if !assemble_rhs + return + end + ind_test = O.u_test + ind_args = O.u_args + return if length(O.u_args) > 0 + build_assembler!(b.entries, O, [b[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) + O.assembler(b.entries, [sol[j] for j in ind_args]) + else + build_assembler!(b.entries, O, [b[j] for j in ind_test]; kwargs...) + O.assembler(b.entries) + end end function assemble!(A, b, sol, O::LinearOperatorFromVector{UT, bT}, SC::SolverConfiguration; assemble_rhs = true, kwargs...) where {UT, bT} - if !assemble_rhs - return - end - if UT <: Integer - ind_test = O.u_test - elseif UT <: Unknown - ind_test = [get_unknown_id(SC, u) for u in O.u_test] - end - if bT <: FEVector - for (j, ij) in enumerate(ind_test) - addblock!(b[j], O.b[ij]; factor = O.parameters[:factor]) - end - else - @assert length(ind_test) == 1 - addblock!(b[ind_test[1]], O.b; factor = O.parameters[:factor]) - end + if !assemble_rhs + return + end + if UT <: Integer + ind_test = O.u_test + elseif UT <: Unknown + ind_test = [get_unknown_id(SC, u) for u in O.u_test] + end + return if bT <: FEVector + for (j, ij) in enumerate(ind_test) + addblock!(b[j], O.b[ij]; factor = O.parameters[:factor]) + end + else + @assert length(ind_test) == 1 + addblock!(b[ind_test[1]], O.b; factor = O.parameters[:factor]) + end end - function assemble!(A, b, sol, O::LinearOperatorFromMatrix{UT, MT}, SC::SolverConfiguration; assemble_rhs = true, kwargs...) where {UT, MT} - if !assemble_rhs - return - end - if UT <: Integer - ind_test = O.u_test - ind_args = O.u_args - elseif UT <: Unknown - ind_test = [get_unknown_id(SC, u) for u in O.u_test] - ind_args = [get_unknown_id(SC, u) for u in O.u_args] - end - if MT <: FEMatrix - for (j, ij) in enumerate(ind_test), k in ind_args - addblock_matmul!(b[j], O.A[ij, k], sol[k]; factor = O.parameters[:factor]) - end - else - @assert length(ind_test) == 1 && length(ind_args) == 1 - addblock_matmul!(b[ind_test[1]], O.A, sol[ind_args[1]]; factor = O.parameters[:factor]) - end + if !assemble_rhs + return + end + if UT <: Integer + ind_test = O.u_test + ind_args = O.u_args + elseif UT <: Unknown + ind_test = [get_unknown_id(SC, u) for u in O.u_test] + ind_args = [get_unknown_id(SC, u) for u in O.u_args] + end + return if MT <: FEMatrix + for (j, ij) in enumerate(ind_test), k in ind_args + addblock_matmul!(b[j], O.A[ij, k], sol[k]; factor = O.parameters[:factor]) + end + else + @assert length(ind_test) == 1 && length(ind_args) == 1 + addblock_matmul!(b[ind_test[1]], O.A, sol[ind_args[1]]; factor = O.parameters[:factor]) + end end diff --git a/src/common_operators/linear_operator_dg.jl b/src/common_operators/linear_operator_dg.jl index be5d12c..c122c02 100644 --- a/src/common_operators/linear_operator_dg.jl +++ b/src/common_operators/linear_operator_dg.jl @@ -1,131 +1,129 @@ - mutable struct LinearOperatorDG{Tv <: Real, UT <: Union{Unknown, Integer}, KFT, MT} <: AbstractOperator - u_test::Array{UT, 1} - ops_test::Array{DataType, 1} - u_args::Array{UT, 1} - ops_args::Array{DataType, 1} - kernel::KFT - BE_test_vals::Array{Vector{Matrix{Array{Tv, 3}}}} - BE_args_vals::Array{Vector{Matrix{Array{Tv, 3}}}} - FES_test::Any #::Array{FESpace,1} - FES_args::Any #::Array{FESpace,1} - BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} - BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} - QP_infos::Any #::Array{QPInfosT,1} - L2G::Any - QF::Any - assembler::Any - storage::MT - parameters::Dict{Symbol, Any} + u_test::Array{UT, 1} + ops_test::Array{DataType, 1} + u_args::Array{UT, 1} + ops_args::Array{DataType, 1} + kernel::KFT + BE_test_vals::Array{Vector{Matrix{Array{Tv, 3}}}} + BE_args_vals::Array{Vector{Matrix{Array{Tv, 3}}}} + FES_test::Any #::Array{FESpace,1} + FES_args::Any #::Array{FESpace,1} + BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} + BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} + QP_infos::Any #::Array{QPInfosT,1} + L2G::Any + QF::Any + assembler::Any + storage::MT + parameters::Dict{Symbol, Any} end default_lfopdg_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :entities => (ON_FACES, "assemble operator on these grid entities (default = ON_FACES)"), - :name => ("LinearOperatorDG", "name for operator used in printouts"), - :factor => (1, "factor that should be multiplied during assembly"), - :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), - :entry_tolerance => (0, "threshold to add entry to sparse matrix"), - :parallel_groups => (false, "assemble operator in parallel using FaceAssemblyGroups"), - :parallel => (false, "assemble operator in parallel using colors/partitions information"), - :time_dependent => (false, "operator is time-dependent ?"), - :store => (false, "store matrix separately (and copy from there when reassembly is triggered)"), - :quadorder => ("auto", "quadrature order"), - :bonus_quadorder => (0, "additional quadrature order added to quadorder"), - :verbosity => (0, "verbosity level"), - :regions => ([], "subset of regions where operator should be assembly only"), + :entities => (ON_FACES, "assemble operator on these grid entities (default = ON_FACES)"), + :name => ("LinearOperatorDG", "name for operator used in printouts"), + :factor => (1, "factor that should be multiplied during assembly"), + :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), + :entry_tolerance => (0, "threshold to add entry to sparse matrix"), + :parallel_groups => (false, "assemble operator in parallel using FaceAssemblyGroups"), + :parallel => (false, "assemble operator in parallel using colors/partitions information"), + :time_dependent => (false, "operator is time-dependent ?"), + :store => (false, "store matrix separately (and copy from there when reassembly is triggered)"), + :quadorder => ("auto", "quadrature order"), + :bonus_quadorder => (0, "additional quadrature order added to quadorder"), + :verbosity => (0, "verbosity level"), + :regions => ([], "subset of regions where operator should be assembly only"), ) # informs solver when operator needs reassembly function depends_nonlinearly_on(O::LinearOperatorDG) - return unique(O.u_args) + return unique(O.u_args) end # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::LinearOperatorDG) - return [unique(O.u_test)] + return [unique(O.u_test)] end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::LinearOperatorDG) - return O.parameters[:time_dependent] + return O.parameters[:time_dependent] end function Base.show(io::IO, O::LinearOperatorDG) - dependencies = dependencies_when_linearized(O) - print(io, "$(O.parameters[:name])($([test_function(dependencies[1][j]) for j = 1 : length(dependencies[1])]))") - return nothing + dependencies = dependencies_when_linearized(O) + print(io, "$(O.parameters[:name])($([test_function(dependencies[1][j]) for j in 1:length(dependencies[1])]))") + return nothing end function LinearOperatorDG(kernel, u_test, ops_test; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_lfopdg_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_test) == length(ops_test) - if parameters[:store] - storage = ExtendableSparseMatrix{Float64, Int}(0, 0) - else - storage = nothing - end - return LinearOperatorDG{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( - u_test, - ops_test, - [], - [], - kernel, - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - storage, - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_lfopdg_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_test) == length(ops_test) + if parameters[:store] + storage = ExtendableSparseMatrix{Float64, Int}(0, 0) + else + storage = nothing + end + return LinearOperatorDG{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( + u_test, + ops_test, + [], + [], + kernel, + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + storage, + parameters, + ) end function LinearOperatorDG(kernel, u_test, ops_test, u_args, ops_args; Tv = Float64, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_lfopdg_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_args) == length(ops_args) - @assert length(u_test) == length(ops_test) - if parameters[:store] - storage = ExtendableSparseMatrix{Float64, Int}(0, 0) - else - storage = nothing - end - return LinearOperatorDG{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( - u_test, - ops_test, - u_args, - ops_args, - kernel, - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - storage, - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_lfopdg_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_args) == length(ops_args) + @assert length(u_test) == length(ops_test) + if parameters[:store] + storage = ExtendableSparseMatrix{Float64, Int}(0, 0) + else + storage = nothing + end + return LinearOperatorDG{Tv, typeof(u_test[1]), typeof(kernel), typeof(storage)}( + u_test, + ops_test, + u_args, + ops_args, + kernel, + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + Array{Vector{Matrix{Array{Tv, 3}}}}(undef, 0), + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + storage, + parameters, + ) end function LinearOperatorDG(kernel, oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_test = [oa[1] for oa in oa_test] - ops_test = [oa[2] for oa in oa_test] - return LinearOperatorDG(kernel, u_test, ops_test; kwargs...) + u_test = [oa[1] for oa in oa_test] + ops_test = [oa[2] for oa in oa_test] + return LinearOperatorDG(kernel, u_test, ops_test; kwargs...) end - """ ```` function LinearOperatorDG( @@ -151,9 +149,9 @@ $(_myprint(default_lfopdg_kwargs())) """ function LinearOperatorDG(oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_test = [oa[1] for oa in oa_test] - ops_test = [oa[2] for oa in oa_test] - return LinearOperatorDG(ExtendableFEMBase.standard_kernel, u_test, ops_test; kwargs...) + u_test = [oa[1] for oa in oa_test] + ops_test = [oa[2] for oa in oa_test] + return LinearOperatorDG(ExtendableFEMBase.standard_kernel, u_test, ops_test; kwargs...) end @@ -185,615 +183,614 @@ $(_myprint(default_lfopdg_kwargs())) """ function LinearOperatorDG(kernel, oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_args = [oa[1] for oa in oa_args] - ops_test = [oa[2] for oa in oa_test] - ops_args = [oa[2] for oa in oa_args] - return LinearOperatorDG(kernel, u_test, ops_test, u_args, ops_args; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_args = [oa[1] for oa in oa_args] + ops_test = [oa[2] for oa in oa_test] + ops_args = [oa[2] for oa in oa_args] + return LinearOperatorDG(kernel, u_test, ops_test, u_args, ops_args; kwargs...) end function build_assembler!(b, O::LinearOperatorDG{Tv}, FE_test, FE_args::Array{<:FEVectorBlock, 1}; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_test = [getFEStest(FE_test[j]) for j ∈ 1:length(FE_test)] - FES_args = [FE_args[j].FES for j ∈ 1:length(FE_args)] - if (O.FES_test != FES_test) || (O.FES_args != FES_args) - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_test, FES_args) - - ## prepare assembly - AT = O.parameters[:entities] - @assert AT <: ON_FACES || AT <: ON_BFACES "only works for entities <: ON_FACES or ON_BFACES" - on_bfaces = false - if AT <: ON_BFACES - AT = ON_FACES - on_bfaces = true - end - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) - Ti = typeof(xgrid).parameters[2] - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if on_bfaces - itemassemblygroups = [xgrid[BFaceFaces]] - EGs = [xgrid[UniqueCellGeometries][1]] - if O.parameters[:parallel] - @warn "parallel assembly on boundary faces not supported yet" - O.parameters[:parallel] = false - end - else - if num_pcolors(xgrid) > 1 - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pe = xgrid[PartitionEdges] - itemassemblygroups = [pe[j]:pe[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - EGs = num_pcolors(xgrid) > 1 ? [xgrid[UniqueCellGeometries][1] for j ∈ 1:num_partitions(xgrid)] : xgrid[UniqueCellGeometries] - end - FETypes_test = [eltype(F) for F in FES_test] - - coeffs_ops_test = Array{Array{Float64, 1}, 1}([]) - coeffs_ops_args = Array{Array{Float64, 1}, 1}([]) - for op in O.ops_test - push!(coeffs_ops_test, coeffs(op)) - end - for op in O.ops_args - push!(coeffs_ops_args, coeffs(op)) - end - - ## prepare assembly - nargs = length(FES_args) - ntest = length(FES_test) - O.QF = [] - O.BE_test = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) - O.BE_args = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) - O.BE_test_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) - O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j ∈ 1:ntest]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_test + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - - ## generate DG operator - push!(O.BE_test, [generate_DG_operators(StandardFunctionOperator(O.ops_test[j]), FES_test[j], quadorder, EG) for j ∈ 1:ntest]) - push!(O.BE_args, [generate_DG_operators(StandardFunctionOperator(O.ops_args[j]), FES_args[j], quadorder, EG) for j ∈ 1:nargs]) - push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) - - ## L2G map for EG - EGface = facetype_of_cellface(EG, 1) - push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) - - ## FE basis evaluator for EG - push!(O.BE_test_vals, [[O.BE_test[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_test[end][k])] for k ∈ 1:ntest]) - push!(O.BE_args_vals, [[O.BE_args[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_args[end][k])] for k ∈ 1:nargs]) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[regions] .= true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_test = [size(O.BE_test[1][j][1, 1].cvals, 1) for j ∈ 1:ntest] - op_lengths_args = [size(O.BE_args[1][j][1, 1].cvals, 1) for j ∈ 1:nargs] - - op_offsets_test = [0] - op_offsets_args = [0] - append!(op_offsets_test, cumsum(op_lengths_test)) - append!(op_offsets_args, cumsum(op_lengths_args)) - offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] - offsets_args = [FE_args[j].offset for j in 1:length(FES_args)] - - ## prepare parallel assembly - if O.parameters[:parallel_groups] - bj = Array{typeof(b), 1}(undef, length(EGs)) - for j ∈ 1:length(EGs) - bj[j] = copy(b) - end - end - - FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), ON_CELLS) for j ∈ 1:ntest] - FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), ON_CELLS) for j ∈ 1:nargs] - itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j ∈ 1:ntest] - itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j ∈ 1:nargs] - factor = O.parameters[:factor] - - ## Assembly loop for fixed geometry - function assembly_loop( - b::AbstractVector{T}, - sol::Array{<:FEVectorBlock, 1}, - items, - EG::ElementGeometries, - QF::QuadratureRule, - BE_test::Vector{Matrix{<:FEEvaluator}}, - BE_args::Vector{Matrix{<:FEEvaluator}}, - BE_test_vals::Vector{Matrix{Array{Tv, 3}}}, - BE_args_vals::Vector{Matrix{Array{Tv, 3}}}, - L2G::L2GTransformer, - QPinfos::QPInfos, - ) where {T} - - input_args = zeros(T, op_offsets_args[end]) - result_kernel = zeros(T, op_offsets_test[end]) - itemorientations = xgrid[CellFaceOrientations] - itemcells = xgrid[FaceCells] - itemnormals = xgrid[FaceNormals] - cellitems = xgrid[CellFaces] - - ndofs_test::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_test] - ndofs_args::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_args] - - weights, xref = QF.w, QF.xref - nweights = length(weights) - cell1::Int = 0 - orientation1::Int = 0 - itempos1::Int = 0 - - input_args = [zeros(T, op_offsets_args[end]) for j ∈ 1:nweights] - - for item::Int in items - if itemregions[item] > 0 - if !(visit_region[itemregions[item]]) || AT == ON_IFACES - continue - end - end - QPinfos.region = itemregions[item] - QPinfos.item = item - QPinfos.normal .= view(itemnormals, :, item) - QPinfos.volume = itemvolumes[item] - update_trafo!(L2G, item) - - boundary_face = itemcells[2, item] == 0 - if AT <: ON_IFACES - if boundary_face - continue - end - end - - ## evaluate arguments at all quadrature points - for qp ∈ 1:nweights - fill!(input_args[qp], 0) - for c1 ∈ 1:2 - cell1 = itemcells[c1, item] - if (cell1 > 0) - itempos1 = 1 - while !(cellitems[itempos1, cell1] == item) - itempos1 += 1 - end - orientation1 = itemorientations[itempos1, cell1] - - for j ∈ 1:nargs - BE_args[j][itempos1, orientation1].citem[] = cell1 - update_basis!(BE_args[j][itempos1, orientation1]) - end - - for id ∈ 1:nargs - for j ∈ 1:ndofs_args[id] - dof_j = itemdofs_args[id][j, cell1] - for d ∈ 1:op_lengths_args[id] - input_args[qp][d+op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][itempos1, orientation1][d, j, qp] * coeffs_ops_args[id][c1] - end - end - end - end - end - end - - for c1 ∈ 1:2 - cell1 = itemcells[c1, item] # current cell of test function - if (cell1 > 0) - QPinfos.cell = cell1 - itempos1 = 1 - while !(cellitems[itempos1, cell1] == item) - itempos1 += 1 - end - orientation1 = itemorientations[itempos1, cell1] - - ## update FE basis evaluators - for j ∈ 1:ntest - BE_test[j][itempos1, orientation1].citem[] = cell1 - update_basis!(BE_test[j][itempos1, orientation1]) - end - - ## evaluate arguments - for qp ∈ 1:nweights - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # evaluate kernel - O.kernel(result_kernel, input_args[qp], QPinfos) - result_kernel .*= factor * weights[qp] * itemvolumes[item] - - # multiply test function operator evaluation on cell 1 - for idt ∈ 1:ntest - coeff_test = boundary_face ? 1 : coeffs_ops_test[idt][c1] - for k ∈ 1:ndofs_test[idt] - dof = itemdofs_test[idt][k, cell1] + offsets_test[idt] - for d ∈ 1:op_lengths_test[idt] - b[dof] += result_kernel[d+op_offsets_test[idt]] * BE_test_vals[idt][itempos1, orientation1][d, k, qp] * coeff_test - end - end - end - end - end - end - end - return - end - O.FES_test = FES_test - O.FES_args = FES_args - - function assembler(b, sol; kwargs...) - if O.parameters[:store] && size(b) == size(O.storage) - b .+= O.storage - else - if O.parameters[:store] - s = zeros(eltype(b), length(b)) - else - s = b - end - time = @elapsed begin - if O.parameters[:parallel] - pcp = xgrid[PColorPartitions] - ncolors = length(pcp) - 1 - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - for color in 1:ncolors - Threads.@threads for part in pcp[color]:pcp[color+1]-1 - assembly_loop(b, sol, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_args[part], O.BE_test_vals[part], O.BE_args_vals[part], O.L2G[part], O.QP_infos[part]; kwargs...) - end - end - elseif O.parameters[:parallel_groups] - Threads.@threads for j ∈ 1:length(EGs) - fill!(bj[j], 0) - assembly_loop(bj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - for j ∈ 1:length(EGs) - s .+= bj[j] - end - else - for j ∈ 1:length(EGs) - assembly_loop(b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - if O.parameters[:store] - b .+= s - O.storage = s - end - end - end - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end - end - O.assembler = assembler - else - ## update the time - for j ∈ 1:length(O.QP_infos) - O.QP_infos[j].time = time - end - end + ## check if FES is the same as last time + FES_test = [getFEStest(FE_test[j]) for j in 1:length(FE_test)] + FES_args = [FE_args[j].FES for j in 1:length(FE_args)] + return if (O.FES_test != FES_test) || (O.FES_args != FES_args) + + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_test, FES_args) + + ## prepare assembly + AT = O.parameters[:entities] + @assert AT <: ON_FACES || AT <: ON_BFACES "only works for entities <: ON_FACES or ON_BFACES" + on_bfaces = false + if AT <: ON_BFACES + AT = ON_FACES + on_bfaces = true + end + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) + Ti = typeof(xgrid).parameters[2] + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if on_bfaces + itemassemblygroups = [xgrid[BFaceFaces]] + EGs = [xgrid[UniqueCellGeometries][1]] + if O.parameters[:parallel] + @warn "parallel assembly on boundary faces not supported yet" + O.parameters[:parallel] = false + end + else + if num_pcolors(xgrid) > 1 + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pe = xgrid[PartitionEdges] + itemassemblygroups = [pe[j]:(pe[j + 1] - 1) for j in 1:num_partitions(xgrid)] + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + EGs = num_pcolors(xgrid) > 1 ? [xgrid[UniqueCellGeometries][1] for j in 1:num_partitions(xgrid)] : xgrid[UniqueCellGeometries] + end + FETypes_test = [eltype(F) for F in FES_test] + + coeffs_ops_test = Array{Array{Float64, 1}, 1}([]) + coeffs_ops_args = Array{Array{Float64, 1}, 1}([]) + for op in O.ops_test + push!(coeffs_ops_test, coeffs(op)) + end + for op in O.ops_args + push!(coeffs_ops_args, coeffs(op)) + end + + ## prepare assembly + nargs = length(FES_args) + ntest = length(FES_test) + O.QF = [] + O.BE_test = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) + O.BE_args = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) + O.BE_test_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) + O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j in 1:ntest]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_test + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + + ## generate DG operator + push!(O.BE_test, [generate_DG_operators(StandardFunctionOperator(O.ops_test[j]), FES_test[j], quadorder, EG) for j in 1:ntest]) + push!(O.BE_args, [generate_DG_operators(StandardFunctionOperator(O.ops_args[j]), FES_args[j], quadorder, EG) for j in 1:nargs]) + push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) + + ## L2G map for EG + EGface = facetype_of_cellface(EG, 1) + push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) + + ## FE basis evaluator for EG + push!(O.BE_test_vals, [[O.BE_test[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_test[end][k])] for k in 1:ntest]) + push!(O.BE_args_vals, [[O.BE_args[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_args[end][k])] for k in 1:nargs]) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[regions] .= true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_test = [size(O.BE_test[1][j][1, 1].cvals, 1) for j in 1:ntest] + op_lengths_args = [size(O.BE_args[1][j][1, 1].cvals, 1) for j in 1:nargs] + + op_offsets_test = [0] + op_offsets_args = [0] + append!(op_offsets_test, cumsum(op_lengths_test)) + append!(op_offsets_args, cumsum(op_lengths_args)) + offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] + offsets_args = [FE_args[j].offset for j in 1:length(FES_args)] + + ## prepare parallel assembly + if O.parameters[:parallel_groups] + bj = Array{typeof(b), 1}(undef, length(EGs)) + for j in 1:length(EGs) + bj[j] = copy(b) + end + end + + FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), ON_CELLS) for j in 1:ntest] + FEATs_args = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_args[j]), ON_CELLS) for j in 1:nargs] + itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j in 1:ntest] + itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j in 1:nargs] + factor = O.parameters[:factor] + + ## Assembly loop for fixed geometry + function assembly_loop( + b::AbstractVector{T}, + sol::Array{<:FEVectorBlock, 1}, + items, + EG::ElementGeometries, + QF::QuadratureRule, + BE_test::Vector{Matrix{<:FEEvaluator}}, + BE_args::Vector{Matrix{<:FEEvaluator}}, + BE_test_vals::Vector{Matrix{Array{Tv, 3}}}, + BE_args_vals::Vector{Matrix{Array{Tv, 3}}}, + L2G::L2GTransformer, + QPinfos::QPInfos, + ) where {T} + + input_args = zeros(T, op_offsets_args[end]) + result_kernel = zeros(T, op_offsets_test[end]) + itemorientations = xgrid[CellFaceOrientations] + itemcells = xgrid[FaceCells] + itemnormals = xgrid[FaceNormals] + cellitems = xgrid[CellFaces] + + ndofs_test::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_test] + ndofs_args::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_args] + + weights, xref = QF.w, QF.xref + nweights = length(weights) + cell1::Int = 0 + orientation1::Int = 0 + itempos1::Int = 0 + + input_args = [zeros(T, op_offsets_args[end]) for j in 1:nweights] + + for item::Int in items + if itemregions[item] > 0 + if !(visit_region[itemregions[item]]) || AT == ON_IFACES + continue + end + end + QPinfos.region = itemregions[item] + QPinfos.item = item + QPinfos.normal .= view(itemnormals, :, item) + QPinfos.volume = itemvolumes[item] + update_trafo!(L2G, item) + + boundary_face = itemcells[2, item] == 0 + if AT <: ON_IFACES + if boundary_face + continue + end + end + + ## evaluate arguments at all quadrature points + for qp in 1:nweights + fill!(input_args[qp], 0) + for c1 in 1:2 + cell1 = itemcells[c1, item] + if (cell1 > 0) + itempos1 = 1 + while !(cellitems[itempos1, cell1] == item) + itempos1 += 1 + end + orientation1 = itemorientations[itempos1, cell1] + + for j in 1:nargs + BE_args[j][itempos1, orientation1].citem[] = cell1 + update_basis!(BE_args[j][itempos1, orientation1]) + end + + for id in 1:nargs + for j in 1:ndofs_args[id] + dof_j = itemdofs_args[id][j, cell1] + for d in 1:op_lengths_args[id] + input_args[qp][d + op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][itempos1, orientation1][d, j, qp] * coeffs_ops_args[id][c1] + end + end + end + end + end + end + + for c1 in 1:2 + cell1 = itemcells[c1, item] # current cell of test function + if (cell1 > 0) + QPinfos.cell = cell1 + itempos1 = 1 + while !(cellitems[itempos1, cell1] == item) + itempos1 += 1 + end + orientation1 = itemorientations[itempos1, cell1] + + ## update FE basis evaluators + for j in 1:ntest + BE_test[j][itempos1, orientation1].citem[] = cell1 + update_basis!(BE_test[j][itempos1, orientation1]) + end + + ## evaluate arguments + for qp in 1:nweights + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # evaluate kernel + O.kernel(result_kernel, input_args[qp], QPinfos) + result_kernel .*= factor * weights[qp] * itemvolumes[item] + + # multiply test function operator evaluation on cell 1 + for idt in 1:ntest + coeff_test = boundary_face ? 1 : coeffs_ops_test[idt][c1] + for k in 1:ndofs_test[idt] + dof = itemdofs_test[idt][k, cell1] + offsets_test[idt] + for d in 1:op_lengths_test[idt] + b[dof] += result_kernel[d + op_offsets_test[idt]] * BE_test_vals[idt][itempos1, orientation1][d, k, qp] * coeff_test + end + end + end + end + end + end + end + return + end + O.FES_test = FES_test + O.FES_args = FES_args + + function assembler(b, sol; kwargs...) + if O.parameters[:store] && size(b) == size(O.storage) + b .+= O.storage + else + if O.parameters[:store] + s = zeros(eltype(b), length(b)) + else + s = b + end + time = @elapsed begin + if O.parameters[:parallel] + pcp = xgrid[PColorPartitions] + ncolors = length(pcp) - 1 + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + for color in 1:ncolors + Threads.@threads for part in pcp[color]:(pcp[color + 1] - 1) + assembly_loop(b, sol, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_args[part], O.BE_test_vals[part], O.BE_args_vals[part], O.L2G[part], O.QP_infos[part]; kwargs...) + end + end + elseif O.parameters[:parallel_groups] + Threads.@threads for j in 1:length(EGs) + fill!(bj[j], 0) + assembly_loop(bj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + for j in 1:length(EGs) + s .+= bj[j] + end + else + for j in 1:length(EGs) + assembly_loop(b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + if O.parameters[:store] + b .+= s + O.storage = s + end + end + end + + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end + end + O.assembler = assembler + else + ## update the time + for j in 1:length(O.QP_infos) + O.QP_infos[j].time = time + end + end end function build_assembler!(b, O::LinearOperatorDG{Tv}, FE_test; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_test = [getFEStest(FE_test[j]) for j ∈ 1:length(FE_test)] - - if (O.FES_test != FES_test) - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_test) - - ## prepare assembly - AT = O.parameters[:entities] - @assert AT <: ON_FACES || AT <: ON_BFACES "only works for entities <: ON_FACES or ON_BFACES" - on_bfaces = false - if AT <: ON_BFACES - AT = ON_FACES - on_bfaces = true - end - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) - Ti = typeof(xgrid).parameters[2] - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if on_bfaces - itemassemblygroups = [xgrid[BFaceFaces]] - EGs = [xgrid[UniqueCellGeometries][1]] - if O.parameters[:parallel] - @warn "parallel assembly on boundary faces not supported yet" - O.parameters[:parallel] = false - end - else - if num_pcolors(xgrid) > 1 - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pe = xgrid[PartitionEdges] - itemassemblygroups = [pe[j]:pe[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - EGs = num_pcolors(xgrid) > 1 ? [xgrid[UniqueCellGeometries][1] for j ∈ 1:num_partitions(xgrid)] : xgrid[UniqueCellGeometries] - end - FETypes_test = [eltype(F) for F in FES_test] - - - - coeffs_ops_test = Array{Array{Float64, 1}, 1}([]) - for op in O.ops_test - push!(coeffs_ops_test, coeffs(op)) - end - - ## prepare assembly - ntest = length(FES_test) - O.QF = [] - O.BE_test = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) - O.BE_test_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) - O.QP_infos = Array{QPInfos, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j ∈ 1:ntest]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_test + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - - ## generate DG operator - push!(O.BE_test, [generate_DG_operators(StandardFunctionOperator(O.ops_test[j]), FES_test[j], quadorder, EG) for j ∈ 1:ntest]) - push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) - - ## L2G map for EG - EGface = facetype_of_cellface(EG, 1) - push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) - - ## FE basis evaluator for EG - push!(O.BE_test_vals, [[O.BE_test[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_test[end][k])] for k ∈ 1:ntest]) - - ## parameter structure - push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[regions] .= true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_test = [size(O.BE_test[1][j][1, 1].cvals, 1) for j ∈ 1:ntest] - - op_offsets_test = [0] - append!(op_offsets_test, cumsum(op_lengths_test)) - offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] - - - ## prepare parallel assembly - if O.parameters[:parallel_groups] - bj = Array{typeof(b), 1}(undef, length(EGs)) - for j ∈ 1:length(EGs) - bj[j] = copy(b) - end - end - - FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), ON_CELLS) for j ∈ 1:ntest] - itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j ∈ 1:ntest] - factor = O.parameters[:factor] - - ## Assembly loop for fixed geometry - function assembly_loop( - b::AbstractVector{T}, - items, - EG::ElementGeometries, - QF::QuadratureRule, - BE_test::Vector{Matrix{<:FEEvaluator}}, - BE_test_vals::Vector{Matrix{Array{Tv, 3}}}, - L2G::L2GTransformer, - QPinfos::QPInfos, - ) where {T} - - result_kernel = zeros(T, op_offsets_test[end]) - itemorientations = xgrid[CellFaceOrientations] - itemcells = xgrid[FaceCells] - itemnormals = xgrid[FaceNormals] - cellitems = xgrid[CellFaces] - - #ndofs_test::Array{Int,1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_test] - ndofs_test::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_test] - - weights, xref = QF.w, QF.xref - nweights = length(weights) - cell1::Int = 0 - orientation1::Int = 0 - itempos1::Int = 0 - - ## loop over faces - ## got into neighbouring cells and evaluate each operator according to - ## facepos and orientation - for item::Int in items - if itemregions[item] > 0 - if !(visit_region[itemregions[item]]) || AT == ON_IFACES - continue - end - end - - QPinfos.region = itemregions[item] - QPinfos.item = item - QPinfos.normal .= view(itemnormals, :, item) - QPinfos.volume = itemvolumes[item] - update_trafo!(L2G, item) - - boundary_face = itemcells[2, item] == 0 - if AT <: ON_IFACES - if boundary_face - continue - end - end - for c1 ∈ 1:2 - cell1 = itemcells[c1, item] # current cell of test function - if (cell1 > 0) - QPinfos.cell = cell1 # give cell of input for kernel - itempos1 = 1 - while !(cellitems[itempos1, cell1] == item) - itempos1 += 1 - end - orientation1 = itemorientations[itempos1, cell1] - - ## update FE basis evaluators - for j ∈ 1:ntest - BE_test[j][itempos1, orientation1].citem[] = cell1 - update_basis!(BE_test[j][itempos1, orientation1]) - end - - ## evaluate arguments - for qp ∈ 1:nweights - - ## get global x for quadrature point - eval_trafo!(QPinfos.x, L2G, xref[qp]) - - # evaluate kernel - O.kernel(result_kernel, QPinfos) - result_kernel .*= factor * weights[qp] * itemvolumes[item] - - # multiply test function operator evaluation on cell 1 - for idt ∈ 1:ntest - coeff_test = boundary_face ? 1 : coeffs_ops_test[idt][c1] - for k ∈ 1:ndofs_test[idt] - dof = itemdofs_test[idt][k, cell1] + offsets_test[idt] - for d ∈ 1:op_lengths_test[idt] - b[dof] += result_kernel[d+op_offsets_test[idt]] * BE_test_vals[idt][itempos1, orientation1][d, k, qp] * coeff_test - end - end - end - end - end - end - end - return - end - O.FES_test = FES_test - - function assembler(b; kwargs...) - if O.parameters[:store] && size(b) == size(O.storage) - b .+= O.storage - else - if O.parameters[:store] - s = zeros(eltype(b), length(b)) - else - s = b - end - time = @elapsed begin - if O.parameters[:parallel] - pcp = xgrid[PColorPartitions] - ncolors = length(pcp) - 1 - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - for color in 1:ncolors - Threads.@threads for part in pcp[color]:pcp[color+1]-1 - assembly_loop(s, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_test_vals[part], O.L2G[part], O.QP_infos[part]; kwargs...) - end - end - elseif O.parameters[:parallel_groups] - Threads.@threads for j ∈ 1:length(EGs) - fill!(bj[j], 0) - assembly_loop(bj[j], itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_test_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - for j ∈ 1:length(EGs) - s .+= bj[j] - end - else - for j ∈ 1:length(EGs) - assembly_loop(s, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_test_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) - end - end - if O.parameters[:store] - b .+= s - O.storage = s - end - end - end - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end - end - O.assembler = assembler - else - ## update the time - for j ∈ 1:length(O.QP_infos) - O.QP_infos[j].time = time - end - end + ## check if FES is the same as last time + FES_test = [getFEStest(FE_test[j]) for j in 1:length(FE_test)] + + return if (O.FES_test != FES_test) + + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_test) + + ## prepare assembly + AT = O.parameters[:entities] + @assert AT <: ON_FACES || AT <: ON_BFACES "only works for entities <: ON_FACES or ON_BFACES" + on_bfaces = false + if AT <: ON_BFACES + AT = ON_FACES + on_bfaces = true + end + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) + Ti = typeof(xgrid).parameters[2] + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if on_bfaces + itemassemblygroups = [xgrid[BFaceFaces]] + EGs = [xgrid[UniqueCellGeometries][1]] + if O.parameters[:parallel] + @warn "parallel assembly on boundary faces not supported yet" + O.parameters[:parallel] = false + end + else + if num_pcolors(xgrid) > 1 + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pe = xgrid[PartitionEdges] + itemassemblygroups = [pe[j]:(pe[j + 1] - 1) for j in 1:num_partitions(xgrid)] + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + EGs = num_pcolors(xgrid) > 1 ? [xgrid[UniqueCellGeometries][1] for j in 1:num_partitions(xgrid)] : xgrid[UniqueCellGeometries] + end + FETypes_test = [eltype(F) for F in FES_test] + + + coeffs_ops_test = Array{Array{Float64, 1}, 1}([]) + for op in O.ops_test + push!(coeffs_ops_test, coeffs(op)) + end + + ## prepare assembly + ntest = length(FES_test) + O.QF = [] + O.BE_test = Array{Vector{Matrix{<:FEEvaluator}}, 1}(undef, 0) + O.BE_test_vals = Array{Vector{Matrix{Array{Tv, 3}}}, 1}(undef, 0) + O.QP_infos = Array{QPInfos, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j in 1:ntest]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_test + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + + ## generate DG operator + push!(O.BE_test, [generate_DG_operators(StandardFunctionOperator(O.ops_test[j]), FES_test[j], quadorder, EG) for j in 1:ntest]) + push!(O.QF, generate_DG_master_quadrule(quadorder, EG)) + + ## L2G map for EG + EGface = facetype_of_cellface(EG, 1) + push!(O.L2G, L2GTransformer(EGface, xgrid, gridAT)) + + ## FE basis evaluator for EG + push!(O.BE_test_vals, [[O.BE_test[end][k][j[1], j[2]].cvals for j in CartesianIndices(O.BE_test[end][k])] for k in 1:ntest]) + + ## parameter structure + push!(O.QP_infos, QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params])) + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[regions] .= true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_test = [size(O.BE_test[1][j][1, 1].cvals, 1) for j in 1:ntest] + + op_offsets_test = [0] + append!(op_offsets_test, cumsum(op_lengths_test)) + offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] + + + ## prepare parallel assembly + if O.parameters[:parallel_groups] + bj = Array{typeof(b), 1}(undef, length(EGs)) + for j in 1:length(EGs) + bj[j] = copy(b) + end + end + + FEATs_test = [ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[j]), ON_CELLS) for j in 1:ntest] + itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j in 1:ntest] + factor = O.parameters[:factor] + + ## Assembly loop for fixed geometry + function assembly_loop( + b::AbstractVector{T}, + items, + EG::ElementGeometries, + QF::QuadratureRule, + BE_test::Vector{Matrix{<:FEEvaluator}}, + BE_test_vals::Vector{Matrix{Array{Tv, 3}}}, + L2G::L2GTransformer, + QPinfos::QPInfos, + ) where {T} + + result_kernel = zeros(T, op_offsets_test[end]) + itemorientations = xgrid[CellFaceOrientations] + itemcells = xgrid[FaceCells] + itemnormals = xgrid[FaceNormals] + cellitems = xgrid[CellFaces] + + #ndofs_test::Array{Int,1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_test] + ndofs_test::Array{Int, 1} = [size(BE[1, 1].cvals, 2) for BE in BE_test] + + weights, xref = QF.w, QF.xref + nweights = length(weights) + cell1::Int = 0 + orientation1::Int = 0 + itempos1::Int = 0 + + ## loop over faces + ## got into neighbouring cells and evaluate each operator according to + ## facepos and orientation + for item::Int in items + if itemregions[item] > 0 + if !(visit_region[itemregions[item]]) || AT == ON_IFACES + continue + end + end + + QPinfos.region = itemregions[item] + QPinfos.item = item + QPinfos.normal .= view(itemnormals, :, item) + QPinfos.volume = itemvolumes[item] + update_trafo!(L2G, item) + + boundary_face = itemcells[2, item] == 0 + if AT <: ON_IFACES + if boundary_face + continue + end + end + for c1 in 1:2 + cell1 = itemcells[c1, item] # current cell of test function + if (cell1 > 0) + QPinfos.cell = cell1 # give cell of input for kernel + itempos1 = 1 + while !(cellitems[itempos1, cell1] == item) + itempos1 += 1 + end + orientation1 = itemorientations[itempos1, cell1] + + ## update FE basis evaluators + for j in 1:ntest + BE_test[j][itempos1, orientation1].citem[] = cell1 + update_basis!(BE_test[j][itempos1, orientation1]) + end + + ## evaluate arguments + for qp in 1:nweights + + ## get global x for quadrature point + eval_trafo!(QPinfos.x, L2G, xref[qp]) + + # evaluate kernel + O.kernel(result_kernel, QPinfos) + result_kernel .*= factor * weights[qp] * itemvolumes[item] + + # multiply test function operator evaluation on cell 1 + for idt in 1:ntest + coeff_test = boundary_face ? 1 : coeffs_ops_test[idt][c1] + for k in 1:ndofs_test[idt] + dof = itemdofs_test[idt][k, cell1] + offsets_test[idt] + for d in 1:op_lengths_test[idt] + b[dof] += result_kernel[d + op_offsets_test[idt]] * BE_test_vals[idt][itempos1, orientation1][d, k, qp] * coeff_test + end + end + end + end + end + end + end + return + end + O.FES_test = FES_test + + function assembler(b; kwargs...) + if O.parameters[:store] && size(b) == size(O.storage) + b .+= O.storage + else + if O.parameters[:store] + s = zeros(eltype(b), length(b)) + else + s = b + end + time = @elapsed begin + if O.parameters[:parallel] + pcp = xgrid[PColorPartitions] + ncolors = length(pcp) - 1 + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + for color in 1:ncolors + Threads.@threads for part in pcp[color]:(pcp[color + 1] - 1) + assembly_loop(s, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_test_vals[part], O.L2G[part], O.QP_infos[part]; kwargs...) + end + end + elseif O.parameters[:parallel_groups] + Threads.@threads for j in 1:length(EGs) + fill!(bj[j], 0) + assembly_loop(bj[j], itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_test_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + for j in 1:length(EGs) + s .+= bj[j] + end + else + for j in 1:length(EGs) + assembly_loop(s, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_test_vals[j], O.L2G[j], O.QP_infos[j]; kwargs...) + end + end + if O.parameters[:store] + b .+= s + O.storage = s + end + end + end + + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end + end + O.assembler = assembler + else + ## update the time + for j in 1:length(O.QP_infos) + O.QP_infos[j].time = time + end + end end function assemble!(A, b, sol, O::LinearOperatorDG{Tv, UT}, SC::SolverConfiguration; assemble_rhs = true, kwargs...) where {Tv, UT} - if !assemble_rhs - return nothing - end - if UT <: Integer - ind_test = O.u_test - ind_args = O.u_args - elseif UT <: Unknown - ind_test = [get_unknown_id(SC, u) for u in O.u_test] - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] #[get_unknown_id(SC, u) for u in O.u_args] - end - if length(O.u_args) > 0 - build_assembler!(b.entries, O, [b[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) - O.assembler(b.entries, [sol[j] for j in ind_args]) - else - build_assembler!(b.entries, O, [b[j] for j in ind_test]; kwargs...) - O.assembler(b.entries) - end + if !assemble_rhs + return nothing + end + if UT <: Integer + ind_test = O.u_test + ind_args = O.u_args + elseif UT <: Unknown + ind_test = [get_unknown_id(SC, u) for u in O.u_test] + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] #[get_unknown_id(SC, u) for u in O.u_args] + end + return if length(O.u_args) > 0 + build_assembler!(b.entries, O, [b[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) + O.assembler(b.entries, [sol[j] for j in ind_args]) + else + build_assembler!(b.entries, O, [b[j] for j in ind_test]; kwargs...) + O.assembler(b.entries) + end end function assemble!(b::FEVector, O::LinearOperatorDG{Tv, UT}, sol = nothing; assemble_rhs = true, kwargs...) where {Tv, UT} - if !assemble_rhs - return nothing - end - ind_test = O.u_test - ind_args = O.u_args - if length(O.u_args) > 0 - build_assembler!(b.entries, O, [b[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) - O.assembler(b.entries, [sol[j] for j in ind_args]) - else - build_assembler!(b.entries, O, [b[j] for j in ind_test]; kwargs...) - O.assembler(b.entries) - end + if !assemble_rhs + return nothing + end + ind_test = O.u_test + ind_args = O.u_args + return if length(O.u_args) > 0 + build_assembler!(b.entries, O, [b[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) + O.assembler(b.entries, [sol[j] for j in ind_args]) + else + build_assembler!(b.entries, O, [b[j] for j in ind_test]; kwargs...) + O.assembler(b.entries) + end end diff --git a/src/common_operators/nonlinear_operator.jl b/src/common_operators/nonlinear_operator.jl index d5d5f79..e6cead8 100644 --- a/src/common_operators/nonlinear_operator.jl +++ b/src/common_operators/nonlinear_operator.jl @@ -1,102 +1,101 @@ - struct KernelEvaluator{Tv <: Real, QPInfosT <: QPInfos, JT, VT, DRT, CFGT, KPT <: Function} - input_args::Array{Tv, 1} - params::QPInfosT - result_kernel::Array{Tv, 1} - jac::JT - value::VT - Dresult::DRT - cfg::CFGT - kernel::KPT + input_args::Array{Tv, 1} + params::QPInfosT + result_kernel::Array{Tv, 1} + jac::JT + value::VT + Dresult::DRT + cfg::CFGT + kernel::KPT end mutable struct NonlinearOperator{Tv <: Real, UT <: Union{Unknown, Integer}, KFT, JFT} <: AbstractOperator - u_test::Array{UT, 1} - ops_test::Array{DataType, 1} - u_args::Array{UT, 1} - ops_args::Array{DataType, 1} - kernel::KFT - jacobian::JFT - BE_test_vals::Array{Array{Array{Tv, 3}, 1}} - BE_args_vals::Array{Array{Array{Tv, 3}, 1}} - FES_test::Any #::Array{FESpace,1} - FES_args::Any #::Array{FESpace,1} - BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} - BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} - L2G::Any - QF::Any - assembler::Any - parameters::Dict{Symbol, Any} + u_test::Array{UT, 1} + ops_test::Array{DataType, 1} + u_args::Array{UT, 1} + ops_args::Array{DataType, 1} + kernel::KFT + jacobian::JFT + BE_test_vals::Array{Array{Array{Tv, 3}, 1}} + BE_args_vals::Array{Array{Array{Tv, 3}, 1}} + FES_test::Any #::Array{FESpace,1} + FES_args::Any #::Array{FESpace,1} + BE_test::Any #::Union{Nothing, Array{FEEvaluator,1}} + BE_args::Any #::Union{Nothing, Array{FEEvaluator,1}} + L2G::Any + QF::Any + assembler::Any + parameters::Dict{Symbol, Any} end default_nlop_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :bonus_quadorder => (0, "additional quadrature order added to quadorder"), - :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), - :entry_tolerance => (0, "threshold to add entry to sparse matrix"), - :extra_inputsize => (0, "additional entries in input vector (e.g. for type-stable storage for intermediate results)"), - :factor => (1, "factor that should be multiplied during assembly"), - :name => ("NonlinearOperator", "name for operator used in printouts"), - :parallel_groups => (false, "assemble operator in parallel using CellAssemblyGroups (assembles separated matrices that are added together sequantially)"), - :parallel => (false, "assemble operator in parallel using colors/partitions information (assembles into full matrix directly)"), - :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), - :quadorder => ("auto", "quadrature order"), - :regions => ([], "subset of regions where operator should be assembly only"), - :sparse_jacobians => (true, "use sparse jacobians"), - :sparse_jacobians_pattern => (nothing, "user provided sparsity pattern for the sparse jacobians (in case automatic detection fails)"), - :time_dependent => (false, "operator is time-dependent ?"), - :verbosity => (0, "verbosity level"), + :bonus_quadorder => (0, "additional quadrature order added to quadorder"), + :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), + :entry_tolerance => (0, "threshold to add entry to sparse matrix"), + :extra_inputsize => (0, "additional entries in input vector (e.g. for type-stable storage for intermediate results)"), + :factor => (1, "factor that should be multiplied during assembly"), + :name => ("NonlinearOperator", "name for operator used in printouts"), + :parallel_groups => (false, "assemble operator in parallel using CellAssemblyGroups (assembles separated matrices that are added together sequantially)"), + :parallel => (false, "assemble operator in parallel using colors/partitions information (assembles into full matrix directly)"), + :params => (nothing, "array of parameters that should be made available in qpinfo argument of kernel function"), + :quadorder => ("auto", "quadrature order"), + :regions => ([], "subset of regions where operator should be assembly only"), + :sparse_jacobians => (true, "use sparse jacobians"), + :sparse_jacobians_pattern => (nothing, "user provided sparsity pattern for the sparse jacobians (in case automatic detection fails)"), + :time_dependent => (false, "operator is time-dependent ?"), + :verbosity => (0, "verbosity level"), ) # informs solver when operator needs reassembly function depends_nonlinearly_on(O::NonlinearOperator) - return unique(O.u_args) + return unique(O.u_args) end # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::NonlinearOperator) - return [unique(O.u_test), unique(O.u_args)] + return [unique(O.u_test), unique(O.u_args)] end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::NonlinearOperator) - return O.parameters[:time_dependent] + return O.parameters[:time_dependent] end function Base.show(io::IO, O::NonlinearOperator) - nl_dependencies = depends_nonlinearly_on(O) - dependencies = dependencies_when_linearized(O) - print( - io, - "$(O.parameters[:name])($([ansatz_function(nl_dependencies[j]) for j = 1 : length(nl_dependencies)]); $([ansatz_function(dependencies[1][j]) for j = 1 : length(dependencies[1])]), $([test_function(dependencies[2][j]) for j = 1 : length(dependencies[2])]))", - ) - return nothing + nl_dependencies = depends_nonlinearly_on(O) + dependencies = dependencies_when_linearized(O) + print( + io, + "$(O.parameters[:name])($([ansatz_function(nl_dependencies[j]) for j in 1:length(nl_dependencies)]); $([ansatz_function(dependencies[1][j]) for j in 1:length(dependencies[1])]), $([test_function(dependencies[2][j]) for j in 1:length(dependencies[2])]))", + ) + return nothing end function NonlinearOperator(kernel, u_test, ops_test, u_args = u_test, ops_args = ops_test; Tv = Float64, jacobian = nothing, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_nlop_kwargs()) - _update_params!(parameters, kwargs) - @assert length(u_args) == length(ops_args) - @assert length(u_test) == length(ops_test) - return NonlinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(jacobian)}( - u_test, - ops_test, - u_args, - ops_args, - kernel, - jacobian, - [[zeros(Tv, 0, 0, 0)]], - [[zeros(Tv, 0, 0, 0)]], - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - nothing, - parameters, - ) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_nlop_kwargs()) + _update_params!(parameters, kwargs) + @assert length(u_args) == length(ops_args) + @assert length(u_test) == length(ops_test) + return NonlinearOperator{Tv, typeof(u_test[1]), typeof(kernel), typeof(jacobian)}( + u_test, + ops_test, + u_args, + ops_args, + kernel, + jacobian, + [[zeros(Tv, 0, 0, 0)]], + [[zeros(Tv, 0, 0, 0)]], + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + parameters, + ) end @@ -131,386 +130,387 @@ $(_myprint(default_nlop_kwargs())) """ function NonlinearOperator(kernel, oa_test::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1}, oa_args::Array{<:Tuple{Union{Unknown, Int}, DataType}, 1} = oa_test; kwargs...) - u_test = [oa[1] for oa in oa_test] - u_args = [oa[1] for oa in oa_args] - ops_test = [oa[2] for oa in oa_test] - ops_args = [oa[2] for oa in oa_args] - return NonlinearOperator(kernel, u_test, ops_test, u_args, ops_args; kwargs...) + u_test = [oa[1] for oa in oa_test] + u_args = [oa[1] for oa in oa_args] + ops_test = [oa[2] for oa in oa_test] + ops_args = [oa[2] for oa in oa_args] + return NonlinearOperator(kernel, u_test, ops_test, u_args, ops_args; kwargs...) end function build_assembler!(A::AbstractMatrix, b::AbstractVector, O::NonlinearOperator{Tv}, FE_test::Array{<:FEVectorBlock, 1}, FE_args::Array{<:FEVectorBlock, 1}; time = 0.0, kwargs...) where {Tv} - ## check if FES is the same as last time - FES_test = [FE_test[j].FES for j ∈ 1:length(FE_test)] - FES_args = [FE_args[j].FES for j ∈ 1:length(FE_args)] - _update_params!(O.parameters, kwargs) - if (O.FES_test != FES_test) || (O.FES_args != FES_args) - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : building assembler" - end - - ## determine grid - xgrid = determine_assembly_grid(FES_test, FES_args) - - ## prepare assembly - AT = O.parameters[:entities] - Ti = typeof(xgrid).parameters[2] - if xgrid == FES_test[1].dofgrid - gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) - else - gridAT = AT - end - Ti = typeof(xgrid).parameters[2] - itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] - itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] - itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] - if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS - maxnpartitions = maximum(num_partitions_per_color(xgrid)) - pc = xgrid[PartitionCells] - itemassemblygroups = [pc[j]:pc[j+1]-1 for j ∈ 1:num_partitions(xgrid)] - # assuming here that all cells of one partition have the same geometry - else - itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] - itemassemblygroups = [view(itemassemblygroups, :, j) for j ∈ 1:num_sources(itemassemblygroups)] - end - has_normals = true - if AT <: ON_FACES - itemnormals = xgrid[FaceNormals] - elseif AT <: ON_BFACES - itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] - else - has_normals = false - end - FETypes_test = [eltype(F) for F in FES_test] - FETypes_args = [eltype(F) for F in FES_args] - EGs = [itemgeometries[itemassemblygroups[j][1]] for j ∈ 1:length(itemassemblygroups)] - - ## prepare assembly - nargs = length(FES_args) - ntest = length(FES_test) - O.QF = [] - O.BE_test = Array{Array{<:FEEvaluator{Tv}, 1}, 1}([]) - O.BE_args = Array{Array{<:FEEvaluator{Tv}, 1}, 1}([]) - O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) - O.L2G = [] - for EG in EGs - ## quadrature formula for EG - polyorder_args = maximum([get_polynomialorder(FETypes_args[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_args[j]) for j ∈ 1:nargs]) - polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j ∈ 1:ntest]) - if O.parameters[:quadorder] == "auto" - quadorder = polyorder_args + polyorder_test + O.parameters[:bonus_quadorder] - else - quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] - end - if O.parameters[:verbosity] > 1 - @info "...... integrating on $EG with quadrature order $quadorder" - end - push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) - - ## FE basis evaluator for EG - push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]) for j in 1:ntest]) - push!(O.BE_args, [FEEvaluator(FES_args[j], O.ops_args[j], O.QF[end]) for j in 1:nargs]) - push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) - push!(O.BE_args_vals, [BE.cvals for BE in O.BE_args[end]]) - - ## L2G map for EG - push!(O.L2G, L2GTransformer(EG, xgrid, ON_CELLS)) - - end - - ## prepare regions - regions = O.parameters[:regions] - visit_region = zeros(Bool, maximum(itemregions)) - if length(regions) > 0 - visit_region[regions] .= true - else - visit_region .= true - end - - ## prepare operator infos - op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j ∈ 1:ntest] - op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j ∈ 1:nargs] - - op_offsets_test = [0] - op_offsets_args = [0] - append!(op_offsets_test, cumsum(op_lengths_test)) - append!(op_offsets_args, cumsum(op_lengths_args)) - offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] - offsets_args = [FE_args[j].offset for j in 1:length(FES_args)] - - Kj = Array{KernelEvaluator, 1}([]) - - sparse_jacobians = O.parameters[:sparse_jacobians] - sparsity_pattern = O.parameters[:sparse_jacobians_pattern] - use_autodiff = O.jacobian === nothing - for EG in EGs - ## prepare parameters - QPj = QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params]) - kernel_params = (result, input) -> (O.kernel(result, input, QPj)) - if sparse_jacobians - input_args = zeros(Tv, op_offsets_args[end] + O.parameters[:extra_inputsize]) - result_kernel = zeros(Tv, op_offsets_test[end]) - if sparsity_pattern === nothing - sparsity_pattern = Symbolics.jacobian_sparsity(kernel_params, result_kernel, input_args) - end - jac = Float64.(sparse(sparsity_pattern)) - value = zeros(Tv, op_offsets_test[end]) - colors = matrix_colors(jac) - Dresult = nothing - cfg = ForwardColorJacCache(kernel_params, input_args, nothing; - dx = nothing, - colorvec = colors, - sparsity = sparsity_pattern) - else - input_args = zeros(Tv, op_offsets_args[end] + O.parameters[:extra_inputsize]) - result_kernel = zeros(Tv, op_offsets_test[end]) - Dresult = DiffResults.JacobianResult(result_kernel, input_args) - jac = DiffResults.jacobian(Dresult) - value = DiffResults.value(Dresult) - cfg = ForwardDiff.JacobianConfig(kernel_params, result_kernel, input_args, ForwardDiff.Chunk{op_offsets_args[end]}()) - end - push!(Kj, KernelEvaluator(input_args, QPj, result_kernel, jac, value, Dresult, cfg, kernel_params)) - end - - ## prepare parallel assembly - if O.parameters[:parallel_groups] - Aj = Array{typeof(A), 1}(undef, length(EGs)) - bj = Array{typeof(b), 1}(undef, length(EGs)) - for j ∈ 1:length(EGs) - Aj[j] = copy(A) - bj[j] = copy(b) - end - end - - FEATs_test = [EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j ∈ 1:ntest] - FEATs_args = [EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j ∈ 1:nargs] - itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j ∈ 1:ntest] - itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j ∈ 1:nargs] - factor = O.parameters[:factor] - entry_tol = O.parameters[:entry_tolerance] - - ## Assembly loop for fixed geometry - function assembly_loop( - A::AbstractSparseArray{T}, - b::AbstractVector{T}, - sol::Array{<:FEVectorBlock{T, Tv, Ti}, 1}, - items, - EG::ElementGeometries, - QF::QuadratureRule, - BE_test::Array{<:FEEvaluator, 1}, - BE_args::Array{<:FEEvaluator, 1}, - BE_test_vals::Array{Array{Tv, 3}, 1}, - BE_args_vals::Array{Array{Tv, 3}, 1}, - L2G::L2GTransformer, - K::KernelEvaluator, - part = 1; - time = 0.0, - ) where {T, Tv, Ti} - - ## extract kernel properties - params = K.params - input_args = K.input_args - result_kernel = K.result_kernel - cfg = K.cfg - Dresult = K.Dresult - jac = K.jac - value = K.value - kernel_params = K.kernel - params.time = time - - ndofs_test::Array{Int, 1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_test] - ndofs_args::Array{Int, 1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_args] - Aloc = Matrix{Matrix{T}}(undef, ntest, nargs) - for j ∈ 1:ntest, k ∈ 1:nargs - Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_args[k]) - end - weights, xref = QF.w, QF.xref - nweights = length(weights) - tempV = zeros(T, op_offsets_test[end]) - dof_j::Int, dof_k::Int = 0, 0 - for item::Int in items - if itemregions[item] > 0 - if !(visit_region[itemregions[item]]) - continue - end - end - params.region = itemregions[item] - params.item = item - if has_normals - params.normal .= view(itemnormals, :, item) - end - params.volume = itemvolumes[item] - - ## update FE basis evaluators - for j ∈ 1:ntest - BE_test[j].citem[] = item - update_basis!(BE_test[j]) - end - for j ∈ 1:nargs - BE_args[j].citem[] = item - update_basis!(BE_args[j]) - end - update_trafo!(L2G, item) - - ## evaluate arguments - for qp ∈ 1:nweights - fill!(input_args, 0) - for id ∈ 1:nargs - for j ∈ 1:ndofs_args[id] - dof_j = itemdofs_args[id][j, item] - for d ∈ 1:op_lengths_args[id] - input_args[d+op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][d, j, qp] - end - end - end - - ## evaluate jacobian - ## get global x for quadrature point - eval_trafo!(params.x, L2G, xref[qp]) - if use_autodiff - if sparse_jacobians - forwarddiff_color_jacobian!(jac, kernel_params, input_args, cfg) - kernel_params(value, input_args) - else - ForwardDiff.chunk_mode_jacobian!(Dresult, kernel_params, result_kernel, input_args, cfg) - end - else - O.jacobian(jac, input_args, params) - O.kernel(value, input_args, params) - end - - # update matrix - for id ∈ 1:nargs - for j ∈ 1:ndofs_args[id] - # multiply ansatz function with local jacobian - fill!(tempV, 0) - if sparse_jacobians - rows = rowvals(jac) - jac_vals = jac.nzval - for col ∈ 1:op_lengths_args[id] - for r in nzrange(jac, col + op_offsets_args[id]) - tempV[rows[r]] += jac_vals[r] * BE_args_vals[id][col, j, qp] - end - end - else - for d ∈ 1:op_lengths_args[id] - for k ∈ 1:op_offsets_test[end] - tempV[k] += jac[k, d+op_offsets_args[id]] * BE_args_vals[id][d, j, qp] - end - end - end - - # multiply test function operator evaluation - for idt ∈ 1:ntest - for k ∈ 1:ndofs_test[idt] - for d ∈ 1:op_lengths_test[idt] - Aloc[idt, id][k, j] += tempV[d+op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] * weights[qp] - end - end - end - end - end - - # update rhs - mul!(tempV, jac, input_args) - tempV .-= value - tempV .*= factor * weights[qp] * itemvolumes[item] - for idt ∈ 1:ntest - for j ∈ 1:ndofs_test[idt] - dof = itemdofs_test[idt][j, item] + offsets_test[idt] - for d ∈ 1:op_lengths_test[idt] - b[dof] += tempV[d+op_offsets_test[idt]] * BE_test_vals[idt][d, j, qp] - end - end - end - end - - ## add local matrices to global matrix - for id ∈ 1:nargs, idt ∈ 1:ntest - Aloc[idt, id] .*= factor * itemvolumes[item] - for j ∈ 1:ndofs_test[idt] - dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] - for k ∈ 1:ndofs_args[id] - dof_k = itemdofs_args[id][k, item] + offsets_args[id] - if abs(Aloc[idt, id][j, k]) > entry_tol - rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) - end - end - end - end - - for id ∈ 1:nargs, idt ∈ 1:ntest - fill!(Aloc[idt, id], 0) - end - end - return - end - O.FES_test = FES_test - O.FES_args = FES_args - - function assembler(A, b, sol; kwargs...) - time = @elapsed begin - if O.parameters[:parallel] - pcp = xgrid[PColorPartitions] - ncolors = length(pcp) - 1 - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" - end - for color in 1:ncolors - Threads.@threads for part in pcp[color]:pcp[color+1]-1 - assembly_loop(A, b, sol, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_args[part], O.BE_test_vals[part], O.BE_args_vals[part], O.L2G[part], Kj[part], part; kwargs...) - end - end - elseif O.parameters[:parallel_groups] - Threads.@threads for j ∈ 1:length(EGs) - fill!(bj[j], 0) - fill!(Aj[j].cscmatrix.nzval, 0) - assembly_loop(Aj[j], bj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], Kj[j]; kwargs...) - end - for j ∈ 1:length(EGs) - add!(A, Aj[j]) - b .+= bj[j] - end - else - for j ∈ 1:length(EGs) - assembly_loop(A, b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], Kj[j]; kwargs...) - end - end - flush!(A) - end - - if O.parameters[:verbosity] > 0 - @info "$(O.parameters[:name]) : assembly took $time s" - end - end - O.assembler = assembler - end + ## check if FES is the same as last time + FES_test = [FE_test[j].FES for j in 1:length(FE_test)] + FES_args = [FE_args[j].FES for j in 1:length(FE_args)] + _update_params!(O.parameters, kwargs) + return if (O.FES_test != FES_test) || (O.FES_args != FES_args) + + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : building assembler" + end + + ## determine grid + xgrid = determine_assembly_grid(FES_test, FES_args) + + ## prepare assembly + AT = O.parameters[:entities] + Ti = typeof(xgrid).parameters[2] + if xgrid == FES_test[1].dofgrid + gridAT = ExtendableFEMBase.EffAT4AssemblyType(get_AT(FES_test[1]), AT) + else + gridAT = AT + end + Ti = typeof(xgrid).parameters[2] + itemgeometries = xgrid[GridComponentGeometries4AssemblyType(gridAT)] + itemvolumes = xgrid[GridComponentVolumes4AssemblyType(gridAT)] + itemregions = xgrid[GridComponentRegions4AssemblyType(gridAT)] + if num_pcolors(xgrid) > 1 && gridAT == ON_CELLS + maxnpartitions = maximum(num_partitions_per_color(xgrid)) + pc = xgrid[PartitionCells] + itemassemblygroups = [pc[j]:(pc[j + 1] - 1) for j in 1:num_partitions(xgrid)] + # assuming here that all cells of one partition have the same geometry + else + itemassemblygroups = xgrid[GridComponentAssemblyGroups4AssemblyType(gridAT)] + itemassemblygroups = [view(itemassemblygroups, :, j) for j in 1:num_sources(itemassemblygroups)] + end + has_normals = true + if AT <: ON_FACES + itemnormals = xgrid[FaceNormals] + elseif AT <: ON_BFACES + itemnormals = xgrid[FaceNormals][:, xgrid[BFaceFaces]] + else + has_normals = false + end + FETypes_test = [eltype(F) for F in FES_test] + FETypes_args = [eltype(F) for F in FES_args] + EGs = [itemgeometries[itemassemblygroups[j][1]] for j in 1:length(itemassemblygroups)] + + ## prepare assembly + nargs = length(FES_args) + ntest = length(FES_test) + O.QF = [] + O.BE_test = Array{Array{<:FEEvaluator{Tv}, 1}, 1}([]) + O.BE_args = Array{Array{<:FEEvaluator{Tv}, 1}, 1}([]) + O.BE_test_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.BE_args_vals = Array{Array{Array{Tv, 3}, 1}, 1}([]) + O.L2G = [] + for EG in EGs + ## quadrature formula for EG + polyorder_args = maximum([get_polynomialorder(FETypes_args[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_args[j]) for j in 1:nargs]) + polyorder_test = maximum([get_polynomialorder(FETypes_test[j], EG) - ExtendableFEMBase.NeededDerivative4Operator(O.ops_test[j]) for j in 1:ntest]) + if O.parameters[:quadorder] == "auto" + quadorder = polyorder_args + polyorder_test + O.parameters[:bonus_quadorder] + else + quadorder = O.parameters[:quadorder] + O.parameters[:bonus_quadorder] + end + if O.parameters[:verbosity] > 1 + @info "...... integrating on $EG with quadrature order $quadorder" + end + push!(O.QF, QuadratureRule{Tv, EG}(quadorder)) + + ## FE basis evaluator for EG + push!(O.BE_test, [FEEvaluator(FES_test[j], O.ops_test[j], O.QF[end]) for j in 1:ntest]) + push!(O.BE_args, [FEEvaluator(FES_args[j], O.ops_args[j], O.QF[end]) for j in 1:nargs]) + push!(O.BE_test_vals, [BE.cvals for BE in O.BE_test[end]]) + push!(O.BE_args_vals, [BE.cvals for BE in O.BE_args[end]]) + + ## L2G map for EG + push!(O.L2G, L2GTransformer(EG, xgrid, ON_CELLS)) + + end + + ## prepare regions + regions = O.parameters[:regions] + visit_region = zeros(Bool, maximum(itemregions)) + if length(regions) > 0 + visit_region[regions] .= true + else + visit_region .= true + end + + ## prepare operator infos + op_lengths_test = [size(O.BE_test[1][j].cvals, 1) for j in 1:ntest] + op_lengths_args = [size(O.BE_args[1][j].cvals, 1) for j in 1:nargs] + + op_offsets_test = [0] + op_offsets_args = [0] + append!(op_offsets_test, cumsum(op_lengths_test)) + append!(op_offsets_args, cumsum(op_lengths_args)) + offsets_test = [FE_test[j].offset for j in 1:length(FES_test)] + offsets_args = [FE_args[j].offset for j in 1:length(FES_args)] + + Kj = Array{KernelEvaluator, 1}([]) + + sparse_jacobians = O.parameters[:sparse_jacobians] + sparsity_pattern = O.parameters[:sparse_jacobians_pattern] + use_autodiff = O.jacobian === nothing + for EG in EGs + ## prepare parameters + QPj = QPInfos(xgrid; time = time, x = ones(Tv, size(xgrid[Coordinates], 1)), params = O.parameters[:params]) + kernel_params = (result, input) -> (O.kernel(result, input, QPj)) + if sparse_jacobians + input_args = zeros(Tv, op_offsets_args[end] + O.parameters[:extra_inputsize]) + result_kernel = zeros(Tv, op_offsets_test[end]) + if sparsity_pattern === nothing + sparsity_pattern = Symbolics.jacobian_sparsity(kernel_params, result_kernel, input_args) + end + jac = Float64.(sparse(sparsity_pattern)) + value = zeros(Tv, op_offsets_test[end]) + colors = matrix_colors(jac) + Dresult = nothing + cfg = ForwardColorJacCache( + kernel_params, input_args, nothing; + dx = nothing, + colorvec = colors, + sparsity = sparsity_pattern + ) + else + input_args = zeros(Tv, op_offsets_args[end] + O.parameters[:extra_inputsize]) + result_kernel = zeros(Tv, op_offsets_test[end]) + Dresult = DiffResults.JacobianResult(result_kernel, input_args) + jac = DiffResults.jacobian(Dresult) + value = DiffResults.value(Dresult) + cfg = ForwardDiff.JacobianConfig(kernel_params, result_kernel, input_args, ForwardDiff.Chunk{op_offsets_args[end]}()) + end + push!(Kj, KernelEvaluator(input_args, QPj, result_kernel, jac, value, Dresult, cfg, kernel_params)) + end + + ## prepare parallel assembly + if O.parameters[:parallel_groups] + Aj = Array{typeof(A), 1}(undef, length(EGs)) + bj = Array{typeof(b), 1}(undef, length(EGs)) + for j in 1:length(EGs) + Aj[j] = copy(A) + bj[j] = copy(b) + end + end + + FEATs_test = [EffAT4AssemblyType(get_AT(FES_test[j]), AT) for j in 1:ntest] + FEATs_args = [EffAT4AssemblyType(get_AT(FES_args[j]), AT) for j in 1:nargs] + itemdofs_test::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_test[j], xgrid, FEATs_test[j]) for j in 1:ntest] + itemdofs_args::Array{Union{Adjacency{Ti}, SerialVariableTargetAdjacency{Ti}}, 1} = [get_dofmap(FES_args[j], xgrid, FEATs_args[j]) for j in 1:nargs] + factor = O.parameters[:factor] + entry_tol = O.parameters[:entry_tolerance] + + ## Assembly loop for fixed geometry + function assembly_loop( + A::AbstractSparseArray{T}, + b::AbstractVector{T}, + sol::Array{<:FEVectorBlock{T, Tv, Ti}, 1}, + items, + EG::ElementGeometries, + QF::QuadratureRule, + BE_test::Array{<:FEEvaluator, 1}, + BE_args::Array{<:FEEvaluator, 1}, + BE_test_vals::Array{Array{Tv, 3}, 1}, + BE_args_vals::Array{Array{Tv, 3}, 1}, + L2G::L2GTransformer, + K::KernelEvaluator, + part = 1; + time = 0.0, + ) where {T, Tv, Ti} + + ## extract kernel properties + params = K.params + input_args = K.input_args + result_kernel = K.result_kernel + cfg = K.cfg + Dresult = K.Dresult + jac = K.jac + value = K.value + kernel_params = K.kernel + params.time = time + + ndofs_test::Array{Int, 1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_test] + ndofs_args::Array{Int, 1} = [get_ndofs(ON_CELLS, FE, EG) for FE in FETypes_args] + Aloc = Matrix{Matrix{T}}(undef, ntest, nargs) + for j in 1:ntest, k in 1:nargs + Aloc[j, k] = zeros(T, ndofs_test[j], ndofs_args[k]) + end + weights, xref = QF.w, QF.xref + nweights = length(weights) + tempV = zeros(T, op_offsets_test[end]) + dof_j::Int, dof_k::Int = 0, 0 + for item::Int in items + if itemregions[item] > 0 + if !(visit_region[itemregions[item]]) + continue + end + end + params.region = itemregions[item] + params.item = item + if has_normals + params.normal .= view(itemnormals, :, item) + end + params.volume = itemvolumes[item] + + ## update FE basis evaluators + for j in 1:ntest + BE_test[j].citem[] = item + update_basis!(BE_test[j]) + end + for j in 1:nargs + BE_args[j].citem[] = item + update_basis!(BE_args[j]) + end + update_trafo!(L2G, item) + + ## evaluate arguments + for qp in 1:nweights + fill!(input_args, 0) + for id in 1:nargs + for j in 1:ndofs_args[id] + dof_j = itemdofs_args[id][j, item] + for d in 1:op_lengths_args[id] + input_args[d + op_offsets_args[id]] += sol[id][dof_j] * BE_args_vals[id][d, j, qp] + end + end + end + + ## evaluate jacobian + ## get global x for quadrature point + eval_trafo!(params.x, L2G, xref[qp]) + if use_autodiff + if sparse_jacobians + forwarddiff_color_jacobian!(jac, kernel_params, input_args, cfg) + kernel_params(value, input_args) + else + ForwardDiff.chunk_mode_jacobian!(Dresult, kernel_params, result_kernel, input_args, cfg) + end + else + O.jacobian(jac, input_args, params) + O.kernel(value, input_args, params) + end + + # update matrix + for id in 1:nargs + for j in 1:ndofs_args[id] + # multiply ansatz function with local jacobian + fill!(tempV, 0) + if sparse_jacobians + rows = rowvals(jac) + jac_vals = jac.nzval + for col in 1:op_lengths_args[id] + for r in nzrange(jac, col + op_offsets_args[id]) + tempV[rows[r]] += jac_vals[r] * BE_args_vals[id][col, j, qp] + end + end + else + for d in 1:op_lengths_args[id] + for k in 1:op_offsets_test[end] + tempV[k] += jac[k, d + op_offsets_args[id]] * BE_args_vals[id][d, j, qp] + end + end + end + + # multiply test function operator evaluation + for idt in 1:ntest + for k in 1:ndofs_test[idt] + for d in 1:op_lengths_test[idt] + Aloc[idt, id][k, j] += tempV[d + op_offsets_test[idt]] * BE_test_vals[idt][d, k, qp] * weights[qp] + end + end + end + end + end + + # update rhs + mul!(tempV, jac, input_args) + tempV .-= value + tempV .*= factor * weights[qp] * itemvolumes[item] + for idt in 1:ntest + for j in 1:ndofs_test[idt] + dof = itemdofs_test[idt][j, item] + offsets_test[idt] + for d in 1:op_lengths_test[idt] + b[dof] += tempV[d + op_offsets_test[idt]] * BE_test_vals[idt][d, j, qp] + end + end + end + end + + ## add local matrices to global matrix + for id in 1:nargs, idt in 1:ntest + Aloc[idt, id] .*= factor * itemvolumes[item] + for j in 1:ndofs_test[idt] + dof_j = itemdofs_test[idt][j, item] + offsets_test[idt] + for k in 1:ndofs_args[id] + dof_k = itemdofs_args[id][k, item] + offsets_args[id] + if abs(Aloc[idt, id][j, k]) > entry_tol + rawupdateindex!(A, +, Aloc[idt, id][j, k], dof_j, dof_k, part) + end + end + end + end + + for id in 1:nargs, idt in 1:ntest + fill!(Aloc[idt, id], 0) + end + end + return + end + O.FES_test = FES_test + O.FES_args = FES_args + + function assembler(A, b, sol; kwargs...) + time = @elapsed begin + if O.parameters[:parallel] + pcp = xgrid[PColorPartitions] + ncolors = length(pcp) - 1 + if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembling in parallel with $ncolors colors, $(length(EGs)) partitions and $(Threads.nthreads()) threads" + end + for color in 1:ncolors + Threads.@threads for part in pcp[color]:(pcp[color + 1] - 1) + assembly_loop(A, b, sol, itemassemblygroups[part], EGs[part], O.QF[part], O.BE_test[part], O.BE_args[part], O.BE_test_vals[part], O.BE_args_vals[part], O.L2G[part], Kj[part], part; kwargs...) + end + end + elseif O.parameters[:parallel_groups] + Threads.@threads for j in 1:length(EGs) + fill!(bj[j], 0) + fill!(Aj[j].cscmatrix.nzval, 0) + assembly_loop(Aj[j], bj[j], sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], Kj[j]; kwargs...) + end + for j in 1:length(EGs) + add!(A, Aj[j]) + b .+= bj[j] + end + else + for j in 1:length(EGs) + assembly_loop(A, b, sol, itemassemblygroups[j], EGs[j], O.QF[j], O.BE_test[j], O.BE_args[j], O.BE_test_vals[j], O.BE_args_vals[j], O.L2G[j], Kj[j]; kwargs...) + end + end + flush!(A) + end + + return if O.parameters[:verbosity] > 0 + @info "$(O.parameters[:name]) : assembly took $time s" + end + end + O.assembler = assembler + end end function assemble!(A, b, sol, O::NonlinearOperator{Tv, UT}, SC::SolverConfiguration; assemble_matrix = true, assemble_rhs = true, time = 0.0, kwargs...) where {Tv, UT} - if assemble_matrix * assemble_rhs == false - return nothing - end - if UT <: Integer - ind_test = O.u_test - ind_args = O.u_args - elseif UT <: Unknown - ind_test = [get_unknown_id(SC, u) for u in O.u_test] - ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] #[get_unknown_id(SC, u) for u in O.u_args] - end - build_assembler!(A.entries, b.entries, O, [sol[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) - O.assembler(A.entries, b.entries, [sol[j] for j in ind_args]; time = time) + if assemble_matrix * assemble_rhs == false + return nothing + end + if UT <: Integer + ind_test = O.u_test + ind_args = O.u_args + elseif UT <: Unknown + ind_test = [get_unknown_id(SC, u) for u in O.u_test] + ind_args = [findfirst(==(u), sol.tags) for u in O.u_args] #[get_unknown_id(SC, u) for u in O.u_args] + end + build_assembler!(A.entries, b.entries, O, [sol[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) + return O.assembler(A.entries, b.entries, [sol[j] for j in ind_args]; time = time) end - function assemble!(A, b, O::NonlinearOperator{Tv, UT}, sol; assemble_matrix = true, assemble_rhs = true, time = 0.0, kwargs...) where {Tv, UT} - if assemble_matrix * assemble_rhs == false - return nothing - end - ind_test = O.u_test - ind_args = O.u_args - build_assembler!(A.entries, b.entries, O, [sol[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) - O.assembler(A.entries, b.entries, [sol[j] for j in ind_args]; time = time) + if assemble_matrix * assemble_rhs == false + return nothing + end + ind_test = O.u_test + ind_args = O.u_args + build_assembler!(A.entries, b.entries, O, [sol[j] for j in ind_test], [sol[j] for j in ind_args]; kwargs...) + return O.assembler(A.entries, b.entries, [sol[j] for j in ind_args]; time = time) end diff --git a/src/common_operators/reduction_operator.jl b/src/common_operators/reduction_operator.jl index 4b18fe9..651520c 100644 --- a/src/common_operators/reduction_operator.jl +++ b/src/common_operators/reduction_operator.jl @@ -1,91 +1,91 @@ abstract type AbstractReductionOperator end mutable struct FixbyInterpolation{UT <: Union{Unknown, Integer}, MT} <: AbstractReductionOperator - u_in::UT - u_out::UT - A::MT - parameters::Dict{Symbol, Any} + u_in::UT + u_out::UT + A::MT + parameters::Dict{Symbol, Any} end default_redop_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), - :name => ("FixbyInterpolation", "name for operator used in printouts"), - :factor => (1, "factor that should be multiplied during assembly"), - :also_remove => ([], "other unknowns besides u_out that are removed by the reduction step"), - :verbosity => (0, "verbosity level"), - :regions => ([], "subset of regions where operator should be assembly only"), + :entities => (ON_CELLS, "assemble operator on these grid entities (default = ON_CELLS)"), + :name => ("FixbyInterpolation", "name for operator used in printouts"), + :factor => (1, "factor that should be multiplied during assembly"), + :also_remove => ([], "other unknowns besides u_out that are removed by the reduction step"), + :verbosity => (0, "verbosity level"), + :regions => ([], "subset of regions where operator should be assembly only"), ) # informs solver when operator needs reassembly function depends_nonlinearly_on(O::FixbyInterpolation, u::Unknown) - return [] + return [] end # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::FixbyInterpolation) - return [O.u_in] + return [O.u_in] end function FixbyInterpolation(u_out, A, u_in; kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_redop_kwargs()) - _update_params!(parameters, kwargs) - return FixbyInterpolation{typeof(u_out), typeof(A)}(u_in, u_out, A, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_redop_kwargs()) + _update_params!(parameters, kwargs) + return FixbyInterpolation{typeof(u_out), typeof(A)}(u_in, u_out, A, parameters) end function apply!(LP, O::FixbyInterpolation{UT}, SC; kwargs...) where {UT} - A_full = SC.A - I = O.A - b_full = SC.b - sol = SC.sol - if UT <: Integer - ind_in = O.u_in - ind_out = O.u_out - ind_also = O.parameters[:also_remove] - elseif UT <: Unknown - ind_in = get_unknown_id(SC, O.u_in) - ind_out = get_unknown_id(SC, O.u_out) - ind_also = get_unknown_id(SC, O.parameters[:also_remove]) - end + A_full = SC.A + I = O.A + b_full = SC.b + sol = SC.sol + if UT <: Integer + ind_in = O.u_in + ind_out = O.u_out + ind_also = O.parameters[:also_remove] + elseif UT <: Unknown + ind_in = get_unknown_id(SC, O.u_in) + ind_out = get_unknown_id(SC, O.u_out) + ind_also = get_unknown_id(SC, O.parameters[:also_remove]) + end - remaining = deleteat!(SC.unknowns_reduced, ind_out) - remaining = deleteat!(SC.unknowns_reduced, ind_also) - #remap = union(1:ind_out-1, ind_out+1:length(SC.unknowns_reduced)) - remap = [get_unknown_id(SC, u) for u in remaining] - @show ind_in, ind_out, remaining, remap - FES = [sol[j].FES for j ∈ 1:length(sol)] - FES_reduced = [sol[j].FES for j in remap] - b = FEVector{eltype(SC.b.entries)}(FES_reduced) - A = FEMatrix{eltype(SC.b.entries)}(FES_reduced) + remaining = deleteat!(SC.unknowns_reduced, ind_out) + remaining = deleteat!(SC.unknowns_reduced, ind_also) + #remap = union(1:ind_out-1, ind_out+1:length(SC.unknowns_reduced)) + remap = [get_unknown_id(SC, u) for u in remaining] + @show ind_in, ind_out, remaining, remap + FES = [sol[j].FES for j in 1:length(sol)] + FES_reduced = [sol[j].FES for j in remap] + b = FEVector{eltype(SC.b.entries)}(FES_reduced) + A = FEMatrix{eltype(SC.b.entries)}(FES_reduced) - T = ExtendableSparseMatrix{eltype(SC.b.entries), Int}(length(sol.entries), length(b.entries)) + T = ExtendableSparseMatrix{eltype(SC.b.entries), Int}(length(sol.entries), length(b.entries)) - for j ∈ 1:length(remap) - offset_x = b_full[remap[j]].offset - offset_y = b[j].offset - for k ∈ 1:FES_reduced[j].ndofs - T[offset_x+k, offset_y+k] = 1 - end - end + for j in 1:length(remap) + offset_x = b_full[remap[j]].offset + offset_y = b[j].offset + for k in 1:FES_reduced[j].ndofs + T[offset_x + k, offset_y + k] = 1 + end + end - offset_x = b_full[ind_out].offset - offset_y = b_full[ind_in].offset - @show size(I), size(T), [FE.ndofs for FE in FES] + offset_x = b_full[ind_out].offset + offset_y = b_full[ind_in].offset + @show size(I), size(T), [FE.ndofs for FE in FES] - for j ∈ 1:FES[ind_out].ndofs, k ∈ 1:FES[ind_in].ndofs - T[offset_x+j, offset_y+k] = I[k, j] - end - flush!(T) + for j in 1:FES[ind_out].ndofs, k in 1:FES[ind_in].ndofs + T[offset_x + j, offset_y + k] = I[k, j] + end + flush!(T) - A.entries.cscmatrix += T.cscmatrix' * A_full.entries * T.cscmatrix - @info ".... spy plot of full system matrix:\n$(UnicodePlots.spy(sparse(A_full.entries.cscmatrix)))" - @info ".... spy plot of trafo matrix:\n$(UnicodePlots.spy(sparse(T.cscmatrix)))" - @info ".... spy plot of reduced system matrix:\n$(UnicodePlots.spy(sparse(A.entries.cscmatrix)))" - b.entries .+= T.cscmatrix' * b_full.entries + A.entries.cscmatrix += T.cscmatrix' * A_full.entries * T.cscmatrix + @info ".... spy plot of full system matrix:\n$(UnicodePlots.spy(sparse(A_full.entries.cscmatrix)))" + @info ".... spy plot of trafo matrix:\n$(UnicodePlots.spy(sparse(T.cscmatrix)))" + @info ".... spy plot of reduced system matrix:\n$(UnicodePlots.spy(sparse(A.entries.cscmatrix)))" + b.entries .+= T.cscmatrix' * b_full.entries - ## remap reduced matrix + ## remap reduced matrix - ## return reduced linear problem - LP_reduced = LinearProblem(A.entries.cscmatrix, b.entries) - return LP_reduced, A, b + ## return reduced linear problem + LP_reduced = LinearProblem(A.entries.cscmatrix, b.entries) + return LP_reduced, A, b end diff --git a/src/diffeq_interface.jl b/src/diffeq_interface.jl index b6d1506..75e297b 100644 --- a/src/diffeq_interface.jl +++ b/src/diffeq_interface.jl @@ -22,136 +22,136 @@ $(ExtendableFEM._myprint(ExtendableFEM.default_diffeq_kwargs())) """ function generate_ODEProblem(PD::ProblemDescription, FES, tspan; unknowns = PD.unknowns, kwargs...) - if typeof(FES) <: FESpace - FES = [FES] - end - SC = SolverConfiguration(PD, unknowns, FES, ExtendableFEM.default_diffeq_kwargs(); kwargs...) - if SC.parameters[:verbosity] > 0 - @info ".... init solver configuration\n" - end - - return generate_ODEProblem(SC, tspan; mass_matrix = nothing, kwargs...) + if typeof(FES) <: FESpace + FES = [FES] + end + SC = SolverConfiguration(PD, unknowns, FES, ExtendableFEM.default_diffeq_kwargs(); kwargs...) + if SC.parameters[:verbosity] > 0 + @info ".... init solver configuration\n" + end + + return generate_ODEProblem(SC, tspan; mass_matrix = nothing, kwargs...) end function generate_ODEProblem(SC::SolverConfiguration, tspan; mass_matrix = nothing, kwargs...) - ## update kwargs - ExtendableFEM._update_params!(SC.parameters, kwargs) - - ## generate default mass matrix if needed - if mass_matrix === nothing - if SC.parameters[:verbosity] > 0 - @info ".... generating mass matrix\n" - end - FES = [SC.sol[u].FES for u in SC.unknowns] - M = FEMatrix(FES) - assemble!(M, BilinearOperator([id(j) for j ∈ 1:length(SC.unknowns)])) - mass_matrix = M.entries.cscmatrix - elseif typeof(mass_matrix) <: ExtendableFEMBase.FEMatrix - flush!(mass_matrix.entries) - mass_matrix = mass_matrix.entries.cscmatrix - elseif typeof(mass_matrix) <: ExtendableSparseMatrix - flush!(mass_matrix) - mass_matrix = mass_matrix.cscmatrix - end - - ## generate ODE problem - f = SciMLBase.ODEFunction(eval_rhs!, jac = eval_jacobian!, jac_prototype = jac_prototype(SC), mass_matrix = mass_matrix) - prob = SciMLBase.ODEProblem(f, SC.sol.entries, tspan, SC) - return prob + ## update kwargs + ExtendableFEM._update_params!(SC.parameters, kwargs) + + ## generate default mass matrix if needed + if mass_matrix === nothing + if SC.parameters[:verbosity] > 0 + @info ".... generating mass matrix\n" + end + FES = [SC.sol[u].FES for u in SC.unknowns] + M = FEMatrix(FES) + assemble!(M, BilinearOperator([id(j) for j in 1:length(SC.unknowns)])) + mass_matrix = M.entries.cscmatrix + elseif typeof(mass_matrix) <: ExtendableFEMBase.FEMatrix + flush!(mass_matrix.entries) + mass_matrix = mass_matrix.entries.cscmatrix + elseif typeof(mass_matrix) <: ExtendableSparseMatrix + flush!(mass_matrix) + mass_matrix = mass_matrix.cscmatrix + end + + ## generate ODE problem + f = SciMLBase.ODEFunction(eval_rhs!, jac = eval_jacobian!, jac_prototype = jac_prototype(SC), mass_matrix = mass_matrix) + prob = SciMLBase.ODEProblem(f, SC.sol.entries, tspan, SC) + return prob end function diffeq_assembly!(sys, ctime) - # unpack - PD = sys.PD - A = sys.A - b = sys.b - sol = sys.sol - - if sys.parameters[:verbosity] > 0 - @info "DiffEQ-extension: t = $ctime" - end - - ## assemble operators - if !sys.parameters[:constant_rhs] - fill!(b.entries, 0) - end - if !sys.parameters[:constant_matrix] - fill!(A.entries.cscmatrix.nzval, 0) - end - if sys.parameters[:initialized] - for op in PD.operators - ExtendableFEM.assemble!(A, b, sol, op, sys; assemble_matrix = !sys.parameters[:constant_matrix], assemble_rhs = !sys.parameters[:constant_rhs], time = ctime) - end - else - for op in PD.operators - ExtendableFEM.assemble!(A, b, sol, op, sys; time = ctime) - end - end - flush!(A.entries) - - for op in PD.operators - ExtendableFEM.apply_penalties!(A, b, sol, op, sys; time = ctime) - end - flush!(A.entries) - - ## set initialize flag - sys.parameters[:initialized] = true + # unpack + PD = sys.PD + A = sys.A + b = sys.b + sol = sys.sol + + if sys.parameters[:verbosity] > 0 + @info "DiffEQ-extension: t = $ctime" + end + + ## assemble operators + if !sys.parameters[:constant_rhs] + fill!(b.entries, 0) + end + if !sys.parameters[:constant_matrix] + fill!(A.entries.cscmatrix.nzval, 0) + end + if sys.parameters[:initialized] + for op in PD.operators + ExtendableFEM.assemble!(A, b, sol, op, sys; assemble_matrix = !sys.parameters[:constant_matrix], assemble_rhs = !sys.parameters[:constant_rhs], time = ctime) + end + else + for op in PD.operators + ExtendableFEM.assemble!(A, b, sol, op, sys; time = ctime) + end + end + flush!(A.entries) + + for op in PD.operators + ExtendableFEM.apply_penalties!(A, b, sol, op, sys; time = ctime) + end + flush!(A.entries) + + ## set initialize flag + return sys.parameters[:initialized] = true end """ Provides the rhs function for DifferentialEquations.jl/ODEProblem. """ function eval_rhs!(du, x, sys, ctime) - # (re)assemble system - if sys.parameters[:verbosity] > 0 - "DiffEQ-extension: evaluating ODE rhs @time = $ctime" - end - - A = sys.A - b = sys.b - sol = sys.sol - - if norm(sol.entries .- x) > sys.parameters[:sametol] || sys.parameters[:initialized] == false - sol.entries .= x - diffeq_assembly!(sys, ctime) - end - - ## calculate residual res = A*u - b - fill!(du, 0) - mul!(du, A.entries.cscmatrix, x) - du .= b.entries - du - - nothing + # (re)assemble system + if sys.parameters[:verbosity] > 0 + "DiffEQ-extension: evaluating ODE rhs @time = $ctime" + end + + A = sys.A + b = sys.b + sol = sys.sol + + if norm(sol.entries .- x) > sys.parameters[:sametol] || sys.parameters[:initialized] == false + sol.entries .= x + diffeq_assembly!(sys, ctime) + end + + ## calculate residual res = A*u - b + fill!(du, 0) + mul!(du, A.entries.cscmatrix, x) + du .= b.entries - du + + return nothing end """ Provides the jacobi matrix calculation function for DifferentialEquations.jl/ODEProblem. """ function eval_jacobian!(J, u, SC, ctime) - if SC.parameters[:verbosity] > 0 - @info "DiffEQ-extension: evaluating jacobian @time = $ctime" - end - - ## reassemble if necessary - sol = SC.sol - if norm(sol.entries .- u) > SC.parameters[:sametol] || SC.parameters[:initialized] == false - sol.entries .= u - diffeq_assembly!(SC, ctime) - end - - ## extract jacobian = system matrix - A = SC.A - J .= -A.entries.cscmatrix - - nothing + if SC.parameters[:verbosity] > 0 + @info "DiffEQ-extension: evaluating jacobian @time = $ctime" + end + + ## reassemble if necessary + sol = SC.sol + if norm(sol.entries .- u) > SC.parameters[:sametol] || SC.parameters[:initialized] == false + sol.entries .= u + diffeq_assembly!(SC, ctime) + end + + ## extract jacobian = system matrix + A = SC.A + J .= -A.entries.cscmatrix + + return nothing end """ Provides the system matrix as prototype for the jacobian. """ function jac_prototype(sys) - ExtendableSparse.flush!(sys.A[1].entries) - sys.A[1].entries.cscmatrix + ExtendableSparse.flush!(sys.A[1].entries) + return sys.A[1].entries.cscmatrix end diff --git a/src/helper_functions.jl b/src/helper_functions.jl index dc456ec..5e6c581 100644 --- a/src/helper_functions.jl +++ b/src/helper_functions.jl @@ -1,5 +1,3 @@ - - """ ```` function get_periodic_coupling_info(FES, xgrid, b1, b2, is_opposite::Function; factor_vectordofs = "auto") @@ -12,202 +10,203 @@ This is automatically done for all Hdiv-conforming elements and (for the normal- """ function get_periodic_coupling_info( - FES::FESpace, - xgrid::ExtendableGrid, - b1, - b2, - is_opposite::Function; - factor_vectordofs = "auto", - factor_components = "auto") + FES::FESpace, + xgrid::ExtendableGrid, + b1, + b2, + is_opposite::Function; + factor_vectordofs = "auto", + factor_components = "auto" + ) - FEType = eltype(FES) - ncomponents = get_ncomponents(FEType) - if factor_vectordofs == "auto" - if FEType <: AbstractHdivFiniteElement || FEType <: H1BR - factor_vectordofs = -1 - else - factor_vectordofs = 1 - end - end - if factor_components == "auto" - factor_components = ones(Int, ncomponents) - end + FEType = eltype(FES) + ncomponents = get_ncomponents(FEType) + if factor_vectordofs == "auto" + if FEType <: AbstractHdivFiniteElement || FEType <: H1BR + factor_vectordofs = -1 + else + factor_vectordofs = 1 + end + end + if factor_components == "auto" + factor_components = ones(Int, ncomponents) + end - @assert FEType <: AbstractH1FiniteElement "not yet working for non H1-conforming elements" - xBFaceRegions = xgrid[BFaceRegions] - xBFaceNodes = xgrid[BFaceNodes] - xBFaceFaces = xgrid[BFaceFaces] - xCoordinates = xgrid[Coordinates] - nbfaces = size(xBFaceNodes, 2) - nnodes = num_nodes(xgrid) - nnodes4bface = size(xBFaceNodes, 1) - EG = xgrid[UniqueBFaceGeometries][1] - xdim = size(xCoordinates, 1) - nedges4bface = xdim == 3 ? num_faces(EG) : 0 - xBFaceMidPoints = zeros(Float64, xdim, nbfaces) - for bface ∈ 1:nbfaces, j ∈ 1:xdim, bn ∈ 1:nnodes4bface - xBFaceMidPoints[j, bface] += xCoordinates[j, xBFaceNodes[bn, bface]] / nnodes4bface - end - if xdim == 3 - xEdgeMidPoint = zeros(Float64, xdim) - xEdgeMidPoint2 = zeros(Float64, xdim) - xEdgeNodes = xgrid[EdgeNodes] - xFaceEdges = xgrid[FaceEdges] - if FEType <: H1P1 - nedgedofs = 0 - elseif FEType <: H1P2 - nedgedofs = 1 - elseif FEType <: H1P3 - nedgedofs = 2 - else - @warn "get_periodic_coupling_info not yet working for non H1-conforming elements" - end - end - @assert FEType <: AbstractH1FiniteElement "get_periodic_coupling_info not yet working for non H1-conforming elements" - xBFaceDofs = FES[BFaceDofs] - dofsX, dofsY, factors = Int[], Int[], Int[] - counterface = 0 - nfb = 0 - partners = zeros(Int, xdim) - coffsets = ExtendableFEMBase.get_local_coffsets(FEType, ON_BFACES, EG) - nedgedofs = 0 + @assert FEType <: AbstractH1FiniteElement "not yet working for non H1-conforming elements" + xBFaceRegions = xgrid[BFaceRegions] + xBFaceNodes = xgrid[BFaceNodes] + xBFaceFaces = xgrid[BFaceFaces] + xCoordinates = xgrid[Coordinates] + nbfaces = size(xBFaceNodes, 2) + nnodes = num_nodes(xgrid) + nnodes4bface = size(xBFaceNodes, 1) + EG = xgrid[UniqueBFaceGeometries][1] + xdim = size(xCoordinates, 1) + nedges4bface = xdim == 3 ? num_faces(EG) : 0 + xBFaceMidPoints = zeros(Float64, xdim, nbfaces) + for bface in 1:nbfaces, j in 1:xdim, bn in 1:nnodes4bface + xBFaceMidPoints[j, bface] += xCoordinates[j, xBFaceNodes[bn, bface]] / nnodes4bface + end + if xdim == 3 + xEdgeMidPoint = zeros(Float64, xdim) + xEdgeMidPoint2 = zeros(Float64, xdim) + xEdgeNodes = xgrid[EdgeNodes] + xFaceEdges = xgrid[FaceEdges] + if FEType <: H1P1 + nedgedofs = 0 + elseif FEType <: H1P2 + nedgedofs = 1 + elseif FEType <: H1P3 + nedgedofs = 2 + else + @warn "get_periodic_coupling_info not yet working for non H1-conforming elements" + end + end + @assert FEType <: AbstractH1FiniteElement "get_periodic_coupling_info not yet working for non H1-conforming elements" + xBFaceDofs = FES[BFaceDofs] + dofsX, dofsY, factors = Int[], Int[], Int[] + counterface = 0 + nfb = 0 + partners = zeros(Int, xdim) + coffsets = ExtendableFEMBase.get_local_coffsets(FEType, ON_BFACES, EG) + nedgedofs = 0 - for bface ∈ 1:nbfaces - counterface = 0 - nfb = num_targets(xBFaceDofs, bface) - if xBFaceRegions[bface] == b1 - for bface2 ∈ 1:nbfaces - if xBFaceRegions[bface2] == b2 - if is_opposite(view(xBFaceMidPoints, :, bface), view(xBFaceMidPoints, :, bface2)) - counterface = bface2 - break - end - end - end - end - if counterface > 0 + for bface in 1:nbfaces + counterface = 0 + nfb = num_targets(xBFaceDofs, bface) + if xBFaceRegions[bface] == b1 + for bface2 in 1:nbfaces + if xBFaceRegions[bface2] == b2 + if is_opposite(view(xBFaceMidPoints, :, bface), view(xBFaceMidPoints, :, bface2)) + counterface = bface2 + break + end + end + end + end + if counterface > 0 - # couple first two node dofs in opposite order due to orientation - for c ∈ 1:ncomponents - if factor_components[c] == 0 - continue - end - nfbc = coffsets[c+1] - coffsets[c] # total dof count for this component + # couple first two node dofs in opposite order due to orientation + for c in 1:ncomponents + if factor_components[c] == 0 + continue + end + nfbc = coffsets[c + 1] - coffsets[c] # total dof count for this component - # couple nodes - for nb ∈ 1:nnodes4bface - ## find node partner on other side that evaluates true in is_ooposite function - for nc ∈ 1:nnodes4bface - if is_opposite(view(xCoordinates, :, xBFaceDofs[nb, bface]), view(xCoordinates, :, xBFaceDofs[nc, counterface])) - partners[nb] = nc - break - end - end - ## couple node dofs (to be skipped for e.g. Hdiv, Hcurl elements) - push!(dofsX, xBFaceDofs[coffsets[c]+nb, bface]) - push!(dofsY, xBFaceDofs[coffsets[c]+partners[nb], counterface]) - end - # @info "matching face $bface (nodes = $(xBFaceNodes[:,bface]), dofs = $(xBFaceDofs[:,bface])) with face $counterface (nodes = $(xBFaceNodes[:,counterface]), dofs = $(xBFaceDofs[:,counterface])) with partner node order $partners" + # couple nodes + for nb in 1:nnodes4bface + ## find node partner on other side that evaluates true in is_ooposite function + for nc in 1:nnodes4bface + if is_opposite(view(xCoordinates, :, xBFaceDofs[nb, bface]), view(xCoordinates, :, xBFaceDofs[nc, counterface])) + partners[nb] = nc + break + end + end + ## couple node dofs (to be skipped for e.g. Hdiv, Hcurl elements) + push!(dofsX, xBFaceDofs[coffsets[c] + nb, bface]) + push!(dofsY, xBFaceDofs[coffsets[c] + partners[nb], counterface]) + end + # @info "matching face $bface (nodes = $(xBFaceNodes[:,bface]), dofs = $(xBFaceDofs[:,bface])) with face $counterface (nodes = $(xBFaceNodes[:,counterface]), dofs = $(xBFaceDofs[:,counterface])) with partner node order $partners" - ## couple edges - if nedges4bface > 0 && FEType <: H1P2 || FEType <: H1P3 - # todo: for H1P3 edge orientation place a role !!! - for nb ∈ 1:nedges4bface - fill!(xEdgeMidPoint, 0) - for j ∈ 1:xdim, k ∈ 1:2 - xEdgeMidPoint[j] += xCoordinates[j, xEdgeNodes[k, xFaceEdges[nb, xBFaceFaces[bface]]]] / 2 - end - ## find edge partner on other side that evaluates true at edge midpoint in is_opposite function - for nc ∈ 1:nnodes4bface - fill!(xEdgeMidPoint2, 0) - for j ∈ 1:xdim, k ∈ 1:2 - xEdgeMidPoint2[j] += xCoordinates[j, xEdgeNodes[k, xFaceEdges[nc, xBFaceFaces[counterface]]]] / 2 - end - if is_opposite(xEdgeMidPoint, xEdgeMidPoint2) - partners[nb] = nc - break - end - end + ## couple edges + if nedges4bface > 0 && FEType <: H1P2 || FEType <: H1P3 + # todo: for H1P3 edge orientation place a role !!! + for nb in 1:nedges4bface + fill!(xEdgeMidPoint, 0) + for j in 1:xdim, k in 1:2 + xEdgeMidPoint[j] += xCoordinates[j, xEdgeNodes[k, xFaceEdges[nb, xBFaceFaces[bface]]]] / 2 + end + ## find edge partner on other side that evaluates true at edge midpoint in is_opposite function + for nc in 1:nnodes4bface + fill!(xEdgeMidPoint2, 0) + for j in 1:xdim, k in 1:2 + xEdgeMidPoint2[j] += xCoordinates[j, xEdgeNodes[k, xFaceEdges[nc, xBFaceFaces[counterface]]]] / 2 + end + if is_opposite(xEdgeMidPoint, xEdgeMidPoint2) + partners[nb] = nc + break + end + end - ## couple edge dofs (local orientation information is needed for more than one dof on each edge !!! ) - for k ∈ 1:nedgedofs - push!(dofsX, xBFaceDofs[coffsets[c]+nnodes4bface+nb+(k-1)*nedgedofs, bface]) - push!(dofsY, xBFaceDofs[coffsets[c]+nnodes4bface+partners[nb]+(k-1)*nedgedofs, counterface]) - end - end - end + ## couple edge dofs (local orientation information is needed for more than one dof on each edge !!! ) + for k in 1:nedgedofs + push!(dofsX, xBFaceDofs[coffsets[c] + nnodes4bface + nb + (k - 1) * nedgedofs, bface]) + push!(dofsY, xBFaceDofs[coffsets[c] + nnodes4bface + partners[nb] + (k - 1) * nedgedofs, counterface]) + end + end + end - ## couple face dofs (interior dofs of bface) - for nb ∈ 1:nfbc-nnodes4bface-nedges4bface*nedgedofs - push!(dofsX, xBFaceDofs[coffsets[c]+nnodes4bface+nedges4bface*nedgedofs+nb, bface]) - push!(dofsY, xBFaceDofs[coffsets[c]+nnodes4bface+nfbc-nnodes4bface-nedges4bface*nedgedofs+1-nb, counterface]) # couple face dofs in opposite order due to orientation (works in 2D at least) - end - append!(factors, ones(nfbc) * factor_components[c]) - end + ## couple face dofs (interior dofs of bface) + for nb in 1:(nfbc - nnodes4bface - nedges4bface * nedgedofs) + push!(dofsX, xBFaceDofs[coffsets[c] + nnodes4bface + nedges4bface * nedgedofs + nb, bface]) + push!(dofsY, xBFaceDofs[coffsets[c] + nnodes4bface + nfbc - nnodes4bface - nedges4bface * nedgedofs + 1 - nb, counterface]) # couple face dofs in opposite order due to orientation (works in 2D at least) + end + append!(factors, ones(nfbc) * factor_components[c]) + end - ## couple remaining dofs (should be vector dofs) - for dof ∈ coffsets[end]+1:nfb - push!(dofsX, xBFaceDofs[dof, bface]) - push!(dofsY, xBFaceDofs[nfb-coffsets[end]+dof-1, counterface]) # couple face dofs in opposite order due to orientation (works in 2D at least, e.g. for Bernardi--Raugel) - push!(factors, factor_vectordofs) - end - end - end + ## couple remaining dofs (should be vector dofs) + for dof in (coffsets[end] + 1):nfb + push!(dofsX, xBFaceDofs[dof, bface]) + push!(dofsY, xBFaceDofs[nfb - coffsets[end] + dof - 1, counterface]) # couple face dofs in opposite order due to orientation (works in 2D at least, e.g. for Bernardi--Raugel) + push!(factors, factor_vectordofs) + end + end + end - return dofsX, dofsY, factors + return dofsX, dofsY, factors end ## determines a common assembly grid for the given arrays of finite element spaces function determine_assembly_grid(FES_test, FES_ansatz = [], FES_args = []) - xgrid = FES_test[1].xgrid - dofgrid = FES_test[1].dofgrid - all_same_xgrid = true - all_same_dofgrid = true - for j ∈ 2:length(FES_test) - if xgrid !== FES_test[j].xgrid - all_same_xgrid = false - end - if dofgrid !== FES_test[j].dofgrid - all_same_dofgrid = false - end - end - for j ∈ 1:length(FES_ansatz) - if xgrid !== FES_ansatz[j].xgrid - all_same_xgrid = false - end - if dofgrid !== FES_ansatz[j].dofgrid - all_same_dofgrid = false - end - end - for j ∈ 1:length(FES_args) - if xgrid !== FES_args[j].xgrid - all_same_xgrid = false - end - if dofgrid !== FES_args[j].dofgrid - all_same_dofgrid = false - end - end - if all_same_dofgrid - return dofgrid - elseif all_same_xgrid - return xgrid - else - @warn "detected non-matching grids for involved finite element spaces, trying assembly on grid of first testfunction argument" - return xgrid - end - return xgrid + xgrid = FES_test[1].xgrid + dofgrid = FES_test[1].dofgrid + all_same_xgrid = true + all_same_dofgrid = true + for j in 2:length(FES_test) + if xgrid !== FES_test[j].xgrid + all_same_xgrid = false + end + if dofgrid !== FES_test[j].dofgrid + all_same_dofgrid = false + end + end + for j in 1:length(FES_ansatz) + if xgrid !== FES_ansatz[j].xgrid + all_same_xgrid = false + end + if dofgrid !== FES_ansatz[j].dofgrid + all_same_dofgrid = false + end + end + for j in 1:length(FES_args) + if xgrid !== FES_args[j].xgrid + all_same_xgrid = false + end + if dofgrid !== FES_args[j].dofgrid + all_same_dofgrid = false + end + end + if all_same_dofgrid + return dofgrid + elseif all_same_xgrid + return xgrid + else + @warn "detected non-matching grids for involved finite element spaces, trying assembly on grid of first testfunction argument" + return xgrid + end + return xgrid end ## gets the dofmap for the FESpace FES fr the assemblygrid xgrid and the assembly type AT function get_dofmap(FES, xgrid, AT) - DM = Dofmap4AssemblyType(AT) - if FES.dofgrid !== xgrid && FES.xgrid !== xgrid - @warn "warning assembly grid does neither match FES dofgrid or parent grid!" - return FES[DM] - end - FES[DM] - return FES.dofgrid === xgrid ? FES[DM] : FES[ParentDofmap4Dofmap(DM)] + DM = Dofmap4AssemblyType(AT) + if FES.dofgrid !== xgrid && FES.xgrid !== xgrid + @warn "warning assembly grid does neither match FES dofgrid or parent grid!" + return FES[DM] + end + FES[DM] + return FES.dofgrid === xgrid ? FES[DM] : FES[ParentDofmap4Dofmap(DM)] end @@ -216,11 +215,11 @@ end # function tensor_view(input, i, rank, dim) # ```` -# Returns a view of input[i] and following entries +# Returns a view of input[i] and following entries # reshaped as a tensor of rank `rank`. -# The parameter `dim` specifies the size of a tensor in each direction, +# The parameter `dim` specifies the size of a tensor in each direction, # e.g. a 1-Tensor (Vector) of length(dim) or a dim x dim 2-Tensor (matrix). -# As an example `tensor_view(v,5,2,5)` returns a view of `v(5:29)` +# As an example `tensor_view(v,5,2,5)` returns a view of `v(5:29)` # as a 5x5 matrix. # """ @@ -251,12 +250,13 @@ aliased with either `A` or `x`. """ function tmul!(y, A, x, α = 1.0, β = 0.0) - for i in eachindex(y) - y[i] *= β - for j in eachindex(x) - y[i] += α * A[j, i] * x[j] - end - end + for i in eachindex(y) + y[i] *= β + for j in eachindex(x) + y[i] += α * A[j, i] * x[j] + end + end + return end """ @@ -268,10 +268,11 @@ Overload of the generic function for types supported by `LinearAlgebra.BLAS.gemv!` to avoid slow run times for large inputs. """ function tmul!( - y::AbstractVector{T}, - A::AbstractMatrix{T}, - x::AbstractVector{T}, - α = 1.0, - β = 0.0) where {T <: AbstractFloat} - LinearAlgebra.BLAS.gemv!('T', α, A, x, β, y) + y::AbstractVector{T}, + A::AbstractMatrix{T}, + x::AbstractVector{T}, + α = 1.0, + β = 0.0 + ) where {T <: AbstractFloat} + return LinearAlgebra.BLAS.gemv!('T', α, A, x, β, y) end diff --git a/src/io.jl b/src/io.jl index fb2fe67..c862dbd 100644 --- a/src/io.jl +++ b/src/io.jl @@ -2,38 +2,38 @@ # Print default dict for solver parameters into docstrings # function _myprint(dict::Dict{Symbol, Tuple{Any, String}}) - lines_out = IOBuffer() - for k in sort!(collect(keys(dict))) - v = dict[k] - if typeof(v[1]) <: String - println(lines_out, " - `$(k)`: $(v[2]). Default: ''$(v[1])''\n") - else - println(lines_out, " - `$(k)`: $(v[2]). Default: $(v[1])\n") - end - end - String(take!(lines_out)) + lines_out = IOBuffer() + for k in sort!(collect(keys(dict))) + v = dict[k] + if typeof(v[1]) <: String + println(lines_out, " - `$(k)`: $(v[2]). Default: ''$(v[1])''\n") + else + println(lines_out, " - `$(k)`: $(v[2]). Default: $(v[1])\n") + end + end + return String(take!(lines_out)) end # # Update solver params from dict # function _update_params!(parameters, kwargs) - for (k, v) in kwargs - parameters[Symbol(k)] = v - end - return nothing + for (k, v) in kwargs + parameters[Symbol(k)] = v + end + return nothing end function center_string(S::String, L::Int = 8) - if length(S) > L - S = S[1:L] - end - while length(S) < L - 1 - S = " " * S * " " - end - if length(S) < L - S = " " * S - end - return S + if length(S) > L + S = S[1:L] + end + while length(S) < L - 1 + S = " " * S * " " + end + if length(S) < L + S = " " * S + end + return S end @@ -46,56 +46,56 @@ Prints a convergence history based on arrays X vs. Y. """ function print_convergencehistory(X, Y; X_to_h = X -> X, ylabels = [], xlabel = "ndofs", latex_mode = false, separator = latex_mode ? "&" : "|", order_seperator = latex_mode ? "&" : "") - xlabel = center_string(xlabel, 12) - if latex_mode - tabular_argument = "c" - for j ∈ 1:size(Y, 2) - tabular_argument *= "|cc" - end - @printf("\\begin{tabular}{%s}", tabular_argument) - end - @printf("\n%s%s", xlabel, separator) - for j ∈ 1:size(Y, 2) - if length(ylabels) < j - push!(ylabels, "DATA $j") - end - if j == size(Y, 2) - @printf("%s %s order %s", center_string(ylabels[j], 18), order_seperator, latex_mode ? "" : separator) - else - @printf("%s %s order %s", center_string(ylabels[j], 18), order_seperator, separator) - end - end - @printf("\n") - if latex_mode - @printf("\\\\\\hline") - else - @printf("============|") - for j ∈ 1:size(Y, 2) - @printf("==========================|") - end - end - @printf("\n") - order = 0 - for j ∈ 1:length(X) - @printf(" %7d %s", X[j], separator) - for k ∈ 1:size(Y, 2) - if j > 1 - order = -log(Y[j-1, k] / Y[j, k]) / (log(X_to_h(X[j]) / X_to_h(X[j-1]))) - end - if k == size(Y, 2) - @printf(" %.3e %s %.2f %s", Y[j, k], order_seperator, order, latex_mode ? "" : separator) - else - @printf(" %.3e %s %.2f %s", Y[j, k], order_seperator, order, separator) - end - end - if latex_mode - @printf("\\\\") - end - @printf("\n") - end - if latex_mode - @printf("\\end{tabular}") - end + xlabel = center_string(xlabel, 12) + if latex_mode + tabular_argument = "c" + for j in 1:size(Y, 2) + tabular_argument *= "|cc" + end + @printf("\\begin{tabular}{%s}", tabular_argument) + end + @printf("\n%s%s", xlabel, separator) + for j in 1:size(Y, 2) + if length(ylabels) < j + push!(ylabels, "DATA $j") + end + if j == size(Y, 2) + @printf("%s %s order %s", center_string(ylabels[j], 18), order_seperator, latex_mode ? "" : separator) + else + @printf("%s %s order %s", center_string(ylabels[j], 18), order_seperator, separator) + end + end + @printf("\n") + if latex_mode + @printf("\\\\\\hline") + else + @printf("============|") + for j in 1:size(Y, 2) + @printf("==========================|") + end + end + @printf("\n") + order = 0 + for j in 1:length(X) + @printf(" %7d %s", X[j], separator) + for k in 1:size(Y, 2) + if j > 1 + order = -log(Y[j - 1, k] / Y[j, k]) / (log(X_to_h(X[j]) / X_to_h(X[j - 1]))) + end + if k == size(Y, 2) + @printf(" %.3e %s %.2f %s", Y[j, k], order_seperator, order, latex_mode ? "" : separator) + else + @printf(" %.3e %s %.2f %s", Y[j, k], order_seperator, order, separator) + end + end + if latex_mode + @printf("\\\\") + end + @printf("\n") + end + return if latex_mode + @printf("\\end{tabular}") + end end @@ -108,29 +108,30 @@ Prints a table with data X vs. Y """ function print_table(X, Y; ylabels = [], xlabel = "ndofs") - xlabel = center_string(xlabel, 12) - @printf("\n%s|", xlabel) - for j ∈ 1:size(Y, 2) - if length(ylabels) < j - push!(ylabels, "DATA $j") - end - @printf(" %s |", center_string(ylabels[j], 20)) - end - @printf("\n") - @printf("============|") - for j ∈ 1:size(Y, 2) - @printf("======================|") - end - @printf("\n") - for j ∈ 1:length(X) - if eltype(X) <: Int - @printf(" %7d |", X[j]) - else - @printf(" %.2e |", X[j]) - end - for k ∈ 1:size(Y, 2) - @printf(" %.8e |", Y[j, k]) - end - @printf("\n") - end + xlabel = center_string(xlabel, 12) + @printf("\n%s|", xlabel) + for j in 1:size(Y, 2) + if length(ylabels) < j + push!(ylabels, "DATA $j") + end + @printf(" %s |", center_string(ylabels[j], 20)) + end + @printf("\n") + @printf("============|") + for j in 1:size(Y, 2) + @printf("======================|") + end + @printf("\n") + for j in 1:length(X) + if eltype(X) <: Int + @printf(" %7d |", X[j]) + else + @printf(" %.2e |", X[j]) + end + for k in 1:size(Y, 2) + @printf(" %.8e |", Y[j, k]) + end + @printf("\n") + end + return end diff --git a/src/jump_operators.jl b/src/jump_operators.jl index 2f3b879..19b5b51 100644 --- a/src/jump_operators.jl +++ b/src/jump_operators.jl @@ -1,4 +1,3 @@ - """ DiscontinuousFunctionOperator @@ -72,44 +71,46 @@ ExtendableFEMBase.DefaultName4Operator(::Type{Right{O}}) where {O} = DefaultName #Base.size(SCV::DuplicateCValView,i) = (i == 2) ? 2 * size(SCV.cvals,i) : size(SCV.cvals,i) struct FEEvaluatorDisc{T, TvG, TiG, FEType, FEBType, O <: DiscontinuousFunctionOperator} <: FEEvaluator{T, TvG, TiG} - citem::Base.RefValue{Int} # current item - FE::FESpace{TvG, TiG, FEType} # link to full FE (e.g. for coefficients) - FEB::FEBType # first FEBasisEvaluator - coeffs::Array{T, 1} - cvals::Array{T, 3} + citem::Base.RefValue{Int} # current item + FE::FESpace{TvG, TiG, FEType} # link to full FE (e.g. for coefficients) + FEB::FEBType # first FEBasisEvaluator + coeffs::Array{T, 1} + cvals::Array{T, 3} end function FEEvaluator( - FE::FESpace{TvG, TiG, FEType, FEAPT}, - operator::Type{<:DiscontinuousFunctionOperator}, - qrule::QuadratureRule{TvR, EG}; - T = Float64, - kwargs...) where {TvG, TiG, TvR, FEType <: AbstractFiniteElement, EG <: AbstractElementGeometry, FEAPT <: AssemblyType} - - FEB = FEEvaluator(FE, StandardFunctionOperator(operator), qrule; T = T, kwargs...) - ndofs = size(FEB.cvals, 2) - cvals = reshape(repeat(FEB.cvals, 2), (size(FEB.cvals, 1), 2 * ndofs, size(FEB.cvals, 3))) - return FEEvaluatorDisc{T, TvG, TiG, FEType, typeof(FEB), operator}(FEB.citem, FE, FEB, coeffs(operator), cvals) + FE::FESpace{TvG, TiG, FEType, FEAPT}, + operator::Type{<:DiscontinuousFunctionOperator}, + qrule::QuadratureRule{TvR, EG}; + T = Float64, + kwargs... + ) where {TvG, TiG, TvR, FEType <: AbstractFiniteElement, EG <: AbstractElementGeometry, FEAPT <: AssemblyType} + + FEB = FEEvaluator(FE, StandardFunctionOperator(operator), qrule; T = T, kwargs...) + ndofs = size(FEB.cvals, 2) + cvals = reshape(repeat(FEB.cvals, 2), (size(FEB.cvals, 1), 2 * ndofs, size(FEB.cvals, 3))) + return FEEvaluatorDisc{T, TvG, TiG, FEType, typeof(FEB), operator}(FEB.citem, FE, FEB, coeffs(operator), cvals) end function ExtendableFEMBase.update_basis!(FEBE::FEEvaluatorDisc) - ExtendableFEMBase.update_basis!(FEBE.FEB) - cvals_std = FEBE.FEB.cvals - cvals = FEBE.cvals - coeffs = FEBE.coeffs - for d ∈ 1:size(cvals_std, 1), j ∈ size(cvals_std, 2), qp ∈ size(cvals_std, 3) - cvals[d, j, qp] = coeffs[1] * cvals_std[d, j, qp] - cvals[d, j+size(cvals_std, 2), qp] = coeffs[2] * cvals_std[d, j, qp] - end + ExtendableFEMBase.update_basis!(FEBE.FEB) + cvals_std = FEBE.FEB.cvals + cvals = FEBE.cvals + coeffs = FEBE.coeffs + for d in 1:size(cvals_std, 1), j in size(cvals_std, 2), qp in size(cvals_std, 3) + cvals[d, j, qp] = coeffs[1] * cvals_std[d, j, qp] + cvals[d, j + size(cvals_std, 2), qp] = coeffs[2] * cvals_std[d, j, qp] + end + return end function ExtendableFEMBase.update_basis!(FEBE::FEEvaluatorDisc, item) - if FEBE.citem[] == item - else - FEBE.citem[] = item - update_basis!(FEBE.FEB, item) - end + return if FEBE.citem[] == item + else + FEBE.citem[] = item + update_basis!(FEBE.FEB, item) + end end @@ -117,36 +118,36 @@ end function generate_DG_master_quadrule(quadorder, EG; T = Float64) - EGface = facetype_of_cellface(EG, 1) - nfaces4cell = num_faces(EG) - for j ∈ 1:nfaces4cell - @assert facetype_of_cellface(EG, j) == EGface "all faces of cell must have the same face geometry!" - end + EGface = facetype_of_cellface(EG, 1) + nfaces4cell = num_faces(EG) + for j in 1:nfaces4cell + @assert facetype_of_cellface(EG, j) == EGface "all faces of cell must have the same face geometry!" + end - return QuadratureRule{T, EGface}(quadorder) + return QuadratureRule{T, EGface}(quadorder) end function generate_DG_operators(operator, FE, quadorder, EG; T = Float64) - ## prototype quadrature rule on face geometry - qf4face = generate_DG_master_quadrule(quadorder, EG; T = T) - - EGface = facetype_of_cellface(EG, 1) - nfaces4cell = num_faces(EG) - - # generate new quadrature rules on cell - # where quadrature points of face are mapped to quadrature points of cells - xrefFACE2CELL = xrefFACE2xrefCELL(EG) - xrefFACE2OFACE = xrefFACE2xrefOFACE(EGface) - norientations = length(xrefFACE2OFACE) - basisevaler4EG = Array{FEEvaluator, 2}(undef, nfaces4cell, norientations) - xrefdim = length(qf4face.xref) - qf4cell = ExtendableFEMBase.SQuadratureRule{T, EG, xrefdim, length(qf4face.xref)}(qf4face.name * " (shape faces)", Array{Array{T, 1}, 1}(undef, length(qf4face.xref)), qf4face.w) - for f ∈ 1:nfaces4cell, orientation ∈ 1:norientations - ## modify quadrature rule for this local face and local orientation - for i ∈ 1:length(qf4face.xref) - qf4cell.xref[i] = xrefFACE2CELL[f](xrefFACE2OFACE[orientation](qf4face.xref[i])) - end - basisevaler4EG[f, orientation] = FEEvaluator(FE, operator, deepcopy(qf4cell); T = T, AT = ON_CELLS) - end - return basisevaler4EG + ## prototype quadrature rule on face geometry + qf4face = generate_DG_master_quadrule(quadorder, EG; T = T) + + EGface = facetype_of_cellface(EG, 1) + nfaces4cell = num_faces(EG) + + # generate new quadrature rules on cell + # where quadrature points of face are mapped to quadrature points of cells + xrefFACE2CELL = xrefFACE2xrefCELL(EG) + xrefFACE2OFACE = xrefFACE2xrefOFACE(EGface) + norientations = length(xrefFACE2OFACE) + basisevaler4EG = Array{FEEvaluator, 2}(undef, nfaces4cell, norientations) + xrefdim = length(qf4face.xref) + qf4cell = ExtendableFEMBase.SQuadratureRule{T, EG, xrefdim, length(qf4face.xref)}(qf4face.name * " (shape faces)", Array{Array{T, 1}, 1}(undef, length(qf4face.xref)), qf4face.w) + for f in 1:nfaces4cell, orientation in 1:norientations + ## modify quadrature rule for this local face and local orientation + for i in 1:length(qf4face.xref) + qf4cell.xref[i] = xrefFACE2CELL[f](xrefFACE2OFACE[orientation](qf4face.xref[i])) + end + basisevaler4EG[f, orientation] = FEEvaluator(FE, operator, deepcopy(qf4cell); T = T, AT = ON_CELLS) + end + return basisevaler4EG end diff --git a/src/operators.jl b/src/operators.jl index bb6943d..a12aa05 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -7,35 +7,35 @@ abstract type AbstractOperator end # informs solver when operator needs reassembly function depends_nonlinearly_on(O::AbstractOperator) - return [] + return [] end # informs solver in which blocks the operator assembles to function dependencies_when_linearized(O::AbstractOperator) - return nothing # Array{Symbol,1} (linear forms) or Array{Array{Symbol,1},1} (bilinearform) + return nothing # Array{Symbol,1} (linear forms) or Array{Array{Symbol,1},1} (bilinearform) end function Base.show(io::IO, O::AbstractOperator) - print(io, "AbstractOperator") - return nothing # Array{Symbol,1} (linear forms) or Array{Array{Symbol,1},1} (bilinearform) + print(io, "AbstractOperator") + return nothing # Array{Symbol,1} (linear forms) or Array{Array{Symbol,1},1} (bilinearform) end # informs solver when operator needs reassembly in a time dependent setting function is_timedependent(O::AbstractOperator) - return false + return false end function fixed_dofs(O::AbstractOperator) - ## assembles operator to full matrix A and b - return [] + ## assembles operator to full matrix A and b + return [] end function assemble!(A, b, sol, O::AbstractOperator, SC; kwargs...) - ## assembles operator to full matrix A and b - return nothing + ## assembles operator to full matrix A and b + return nothing end function apply_penalties!(A, b, sol, O::AbstractOperator, SC; kwargs...) - ## applies penalties to full matrix A and b and also sets values in sol - return nothing + ## applies penalties to full matrix A and b and also sets values in sol + return nothing end diff --git a/src/plots.jl b/src/plots.jl index aafd04d..de12a87 100644 --- a/src/plots.jl +++ b/src/plots.jl @@ -1,14 +1,14 @@ function GridVisualize.scalarplot!(p, op::Tuple{Unknown, DataType}, sol; abs = false, component = 1, title = String(op[1].identifier), kwargs...) - GridVisualize.scalarplot!(p, sol[op[1]].FES.dofgrid, view(nodevalues(sol[op[1]], op[2]; abs = abs), component, :); title = title, kwargs...) + return GridVisualize.scalarplot!(p, sol[op[1]].FES.dofgrid, view(nodevalues(sol[op[1]], op[2]; abs = abs), component, :); title = title, kwargs...) end function GridVisualize.scalarplot!(p, op::Tuple{Int, DataType}, sol; abs = false, component = 1, title = sol[op[1]].name, kwargs...) - GridVisualize.scalarplot!(p, sol[op[1]].FES.dofgrid, view(nodevalues(sol[op[1]], op[2]; abs = abs), component, :); title = title, kwargs...) + return GridVisualize.scalarplot!(p, sol[op[1]].FES.dofgrid, view(nodevalues(sol[op[1]], op[2]; abs = abs), component, :); title = title, kwargs...) end function GridVisualize.vectorplot!(p, op::Tuple{Unknown, DataType}, sol; title = String(op[1].identifier), kwargs...) - GridVisualize.vectorplot!(p, sol[op[1]].FES.dofgrid, eval_func_bary(PointEvaluator([op], sol)); title = title, kwargs...) + return GridVisualize.vectorplot!(p, sol[op[1]].FES.dofgrid, eval_func_bary(PointEvaluator([op], sol)); title = title, kwargs...) end function GridVisualize.vectorplot!(p, op::Tuple{Int, DataType}, sol; title = sol[op[1]].name, kwargs...) - GridVisualize.vectorplot!(p, sol[op[1]].FES.dofgrid, eval_func_bary(PointEvaluator([op], sol)); title = title, kwargs...) + return GridVisualize.vectorplot!(p, sol[op[1]].FES.dofgrid, eval_func_bary(PointEvaluator([op], sol)); title = title, kwargs...) end """ @@ -20,55 +20,55 @@ Plots the operator evaluations ops of blocks in sol into the GridVisualizer. """ function plot!(p::GridVisualizer, ops, sol; rasterpoints = 10, keep = [], ncols = size(p.subplots, 2), do_abs = true, do_vector_plots = true, title_add = "", kwargs...) - col, row, id = 0, 1, 0 - for op in ops - col += 1 - id += 1 - if col == ncols + 1 - col, row = 1, row + 1 - end - while id in keep - col += 1 - id += 1 - if col == ncols + 1 - col, row = 1, row + 1 - end - end - if op[2] == "grid" - gridplot!(p[row, col], sol[op[1]].FES.xgrid; kwargs...) - elseif op[2] == "dofgrid" - gridplot!(p[row, col], sol[op[1]].FES.dofgrid; kwargs...) - else - ncomponents = get_ncomponents(sol[op[1]]) - edim = size(sol[op[1]].FES.xgrid[Coordinates], 1) - resultdim = Length4Operator(op[2], edim, ncomponents) - if typeof(op[1]) <: Unknown - title = op[2] == Identity ? String(op[1].identifier) : "$(op[2])(" * String(op[1].identifier) * ")" - else - title = op[2] == Identity ? "$(sol[op[1]].name)" : "$(op[2])($(sol[op[1]].name))" - end - if resultdim == 1 - GridVisualize.scalarplot!(p[row, col], sol[op[1]].FES.dofgrid, view(nodevalues(sol[op[1]], op[2]; abs = false), 1, :), title = title * title_add; kwargs...) - elseif do_abs == true - GridVisualize.scalarplot!(p[row, col], sol[op[1]].FES.dofgrid, view(nodevalues(sol[op[1]], op[2]; abs = true), 1, :), title = "|" * title * "|" * title_add; kwargs...) - else - nv = nodevalues(sol[op[1]], op[2]; abs = false) - for k ∈ 1:resultdim - if k > 1 - col += 1 - if col == ncols + 1 - col, row = 1, row + 1 - end - end - GridVisualize.scalarplot!(p[row, col], sol[op[1]].FES.dofgrid, view(nv, k, :), title = title * " (component $k)" * title_add, kwargs...) - end - end - if resultdim > 1 && do_vector_plots && do_abs == true && edim > 1 - GridVisualize.vectorplot!(p[row, col], sol[op[1]].FES.dofgrid, eval_func_bary(PointEvaluator([op], sol)); rasterpoints = rasterpoints, title = "|" * title * "|" * " + quiver" * title_add, clear = false, kwargs...) - end - end - end - return p + col, row, id = 0, 1, 0 + for op in ops + col += 1 + id += 1 + if col == ncols + 1 + col, row = 1, row + 1 + end + while id in keep + col += 1 + id += 1 + if col == ncols + 1 + col, row = 1, row + 1 + end + end + if op[2] == "grid" + gridplot!(p[row, col], sol[op[1]].FES.xgrid; kwargs...) + elseif op[2] == "dofgrid" + gridplot!(p[row, col], sol[op[1]].FES.dofgrid; kwargs...) + else + ncomponents = get_ncomponents(sol[op[1]]) + edim = size(sol[op[1]].FES.xgrid[Coordinates], 1) + resultdim = Length4Operator(op[2], edim, ncomponents) + if typeof(op[1]) <: Unknown + title = op[2] == Identity ? String(op[1].identifier) : "$(op[2])(" * String(op[1].identifier) * ")" + else + title = op[2] == Identity ? "$(sol[op[1]].name)" : "$(op[2])($(sol[op[1]].name))" + end + if resultdim == 1 + GridVisualize.scalarplot!(p[row, col], sol[op[1]].FES.dofgrid, view(nodevalues(sol[op[1]], op[2]; abs = false), 1, :), title = title * title_add; kwargs...) + elseif do_abs == true + GridVisualize.scalarplot!(p[row, col], sol[op[1]].FES.dofgrid, view(nodevalues(sol[op[1]], op[2]; abs = true), 1, :), title = "|" * title * "|" * title_add; kwargs...) + else + nv = nodevalues(sol[op[1]], op[2]; abs = false) + for k in 1:resultdim + if k > 1 + col += 1 + if col == ncols + 1 + col, row = 1, row + 1 + end + end + GridVisualize.scalarplot!(p[row, col], sol[op[1]].FES.dofgrid, view(nv, k, :), title = title * " (component $k)" * title_add, kwargs...) + end + end + if resultdim > 1 && do_vector_plots && do_abs == true && edim > 1 + GridVisualize.vectorplot!(p[row, col], sol[op[1]].FES.dofgrid, eval_func_bary(PointEvaluator([op], sol)); rasterpoints = rasterpoints, title = "|" * title * "|" * " + quiver" * title_add, clear = false, kwargs...) + end + end + end + return p end @@ -81,23 +81,23 @@ Plots the operator evaluations ops of blocks in sol with the specified Plotter m """ function plot(ops, sol; add = 0, Plotter = nothing, ncols = min(2, length(ops) + add), do_abs = true, width = (length(ops) + add) == 1 ? 400 : 800, height = 0, kwargs...) - nplots = length(ops) + add - for op in ops - ncomponents = get_ncomponents(sol[op[1]]) - edim = size(sol[op[1]].FES.xgrid[Coordinates], 1) - if !(op[2] in ["grid", "dofgrid"]) - resultdim = Length4Operator(op[2], edim, ncomponents) - if resultdim > 1 && do_abs == false - nplots += resultdim - 1 - end - end - end - nrows = Int(ceil(nplots / ncols)) - if height == 0 - height = width / ncols * nrows - end - p = GridVisualizer(; Plotter = Plotter, layout = (nrows, ncols), clear = true, resolution = (width, height)) - plot!(p, ops, sol; do_abs = do_abs, kwargs...) + nplots = length(ops) + add + for op in ops + ncomponents = get_ncomponents(sol[op[1]]) + edim = size(sol[op[1]].FES.xgrid[Coordinates], 1) + if !(op[2] in ["grid", "dofgrid"]) + resultdim = Length4Operator(op[2], edim, ncomponents) + if resultdim > 1 && do_abs == false + nplots += resultdim - 1 + end + end + end + nrows = Int(ceil(nplots / ncols)) + if height == 0 + height = width / ncols * nrows + end + p = GridVisualizer(; Plotter = Plotter, layout = (nrows, ncols), clear = true, resolution = (width, height)) + return plot!(p, ops, sol; do_abs = do_abs, kwargs...) end """ @@ -110,13 +110,14 @@ Plots all blocks of sol into stdout """ function plot_unicode(sol; kwargs...) - for u ∈ 1:length(sol) - println(stdout, unicode_scalarplot(sol[u]; title = sol[u].name, kwargs...)) - end + for u in 1:length(sol) + println(stdout, unicode_scalarplot(sol[u]; title = sol[u].name, kwargs...)) + end + return end function GridVisualize.vectorplot!(p, xgrid, op::Tuple{Union{Unknown, Int}, DataType}, sol; title = sol[op[1]].name, kwargs...) - GridVisualize.vectorplot!(p, xgrid, eval_func(PointEvaluator([op], sol)); title = title, kwargs...) + return GridVisualize.vectorplot!(p, xgrid, eval_func(PointEvaluator([op], sol)); title = title, kwargs...) end @@ -144,58 +145,59 @@ Plots a convergence history based on arrays X vs. Y into the GridVisualizer. """ function plot_convergencehistory!( - target, - X, - Y; - add_h_powers = [], - X_to_h = X -> X, - colors = [:blue, :green, :red, :magenta, :lightblue], - title = "convergence history", - legend = :best, - ylabel = "", - ylabels = [], - xlabel = "ndofs", - markershape = :circle, - markevery = 1, - clear = true, - args..., -) - for j ∈ 1:size(Y, 2) - Xk = [] - Yk = [] - for k ∈ 1:length(X) - if Y[k, j] > 0 - push!(Xk, X[k]) - push!(Yk, Y[k, j]) - end - end - if length(ylabels) >= j - label = ylabels[j] - else - label = "Data $j" - end - scalarplot!( - target, - simplexgrid(Xk), - Yk; - xlabel = xlabel, - ylabel = ylabel, - color = length(colors) >= j ? colors[j] : :black, - clear = j == 1 ? clear : false, - markershape = markershape, - markevery = markevery, - xscale = :log, - yscale = :log, - label = label, - legend = legend, - title = title, - args..., - ) - end - for p in add_h_powers - label = "h^$p" - scalarplot!(target, simplexgrid(X), X_to_h(X) .^ p; linestyle = :dot, xlabel = xlabel, ylabel = ylabel, color = :gray, clear = false, markershape = :none, xscale = :log, yscale = :log, label = label, legend = legend, title = title, args...) - end + target, + X, + Y; + add_h_powers = [], + X_to_h = X -> X, + colors = [:blue, :green, :red, :magenta, :lightblue], + title = "convergence history", + legend = :best, + ylabel = "", + ylabels = [], + xlabel = "ndofs", + markershape = :circle, + markevery = 1, + clear = true, + args..., + ) + for j in 1:size(Y, 2) + Xk = [] + Yk = [] + for k in 1:length(X) + if Y[k, j] > 0 + push!(Xk, X[k]) + push!(Yk, Y[k, j]) + end + end + if length(ylabels) >= j + label = ylabels[j] + else + label = "Data $j" + end + scalarplot!( + target, + simplexgrid(Xk), + Yk; + xlabel = xlabel, + ylabel = ylabel, + color = length(colors) >= j ? colors[j] : :black, + clear = j == 1 ? clear : false, + markershape = markershape, + markevery = markevery, + xscale = :log, + yscale = :log, + label = label, + legend = legend, + title = title, + args..., + ) + end + for p in add_h_powers + label = "h^$p" + scalarplot!(target, simplexgrid(X), X_to_h(X) .^ p; linestyle = :dot, xlabel = xlabel, ylabel = ylabel, color = :gray, clear = false, markershape = :none, xscale = :log, yscale = :log, label = label, legend = legend, title = title, args...) + end + return end @@ -208,20 +210,20 @@ Plots a convergence history based on arrays X vs. Y into the GridVisualizer with """ function plot_convergencehistory(X, Y; Plotter = nothing, size = (800, 600), add_h_powers = [], X_to_h = X -> X, colors = [:blue, :green, :red, :magenta, :lightblue], legend = :best, ylabel = "", ylabels = [], xlabel = "ndofs", clear = true, args...) - p = GridVisualizer(; Plotter = Plotter, layout = (1, 1), clear = true, size = size) - plot_convergencehistory!(p[1, 1], X, Y; add_h_powers = add_h_powers, X_to_h = X_to_h, colors = colors, legend = legend, ylabel = ylabel, ylabels = ylabels, xlabel = xlabel, clear = clear, args...) + p = GridVisualizer(; Plotter = Plotter, layout = (1, 1), clear = true, size = size) + return plot_convergencehistory!(p[1, 1], X, Y; add_h_powers = add_h_powers, X_to_h = X_to_h, colors = colors, legend = legend, ylabel = ylabel, ylabels = ylabels, xlabel = xlabel, clear = clear, args...) end function ExtendableFEMBase.nodevalues(op, sol; kwargs...) - return nodevalues(sol[op[1]], op[2]; kwargs...) + return nodevalues(sol[op[1]], op[2]; kwargs...) end ## default function for generateplots for ExampleJuggler.jl function default_generateplots(example_module, filename; kwargs...) - function closure(dir = pwd(); Plotter = nothing, kwargs...) - ~, plt = example_module.main(; Plotter = Plotter, kwargs...) - scene = GridVisualize.reveal(plt) - GridVisualize.save(joinpath(dir, filename), scene; Plotter = Plotter) - end + return function closure(dir = pwd(); Plotter = nothing, kwargs...) + ~, plt = example_module.main(; Plotter = Plotter, kwargs...) + scene = GridVisualize.reveal(plt) + return GridVisualize.save(joinpath(dir, filename), scene; Plotter = Plotter) + end end diff --git a/src/problemdescription.jl b/src/problemdescription.jl index c5e0375..4c5b282 100644 --- a/src/problemdescription.jl +++ b/src/problemdescription.jl @@ -8,19 +8,19 @@ Structure holding data for a problem description with the following fields: $(TYPEDFIELDS) """ mutable struct ProblemDescription - """ - The name of the problem used for printout messages. Default: "My Problem" - """ - name::String - """ - A vector of Unknowns that are involved in the problem. - """ - unknowns::Array{Unknown, 1} - """ - A vector of operators that are involved in the problem. - """ - operators::Array{AbstractOperator, 1} - #reduction_operators::Array{AbstractReductionOperator,1} + """ + The name of the problem used for printout messages. Default: "My Problem" + """ + name::String + """ + A vector of Unknowns that are involved in the problem. + """ + unknowns::Array{Unknown, 1} + """ + A vector of operators that are involved in the problem. + """ + operators::Array{AbstractOperator, 1} + #reduction_operators::Array{AbstractReductionOperator,1} end """ @@ -32,7 +32,7 @@ Generates an empty ProblemDescription with the given name. """ function ProblemDescription(name = "My problem") - return ProblemDescription(name, Array{Unknown, 1}(undef, 0), Array{AbstractOperator, 1}(undef, 0)) + return ProblemDescription(name, Array{Unknown, 1}(undef, 0), Array{AbstractOperator, 1}(undef, 0)) end @@ -46,13 +46,13 @@ and returns its position in the unknowns array of the ProblemDescription. """ function assign_unknown!(PD::ProblemDescription, u::Unknown) - if u in PD.unknowns - @warn "This unknown was already assigned to the problem description! Ignoring this call." - return find(==(u), PD.unknowns) - else - push!(PD.unknowns, u) - return length(PD.unknowns) - end + if u in PD.unknowns + @warn "This unknown was already assigned to the problem description! Ignoring this call." + return find(==(u), PD.unknowns) + else + push!(PD.unknowns, u) + return length(PD.unknowns) + end end @@ -66,8 +66,8 @@ and returns its position in the operators array of the ProblemDescription. """ function assign_operator!(PD::ProblemDescription, o::AbstractOperator) - push!(PD.operators, o) - return length(PD.operators) + push!(PD.operators, o) + return length(PD.operators) end @@ -82,8 +82,8 @@ Nothing is returned (as the new operator gets position j). """ function replace_operator!(PD::ProblemDescription, j, o::AbstractOperator) - PD.operators[j] = o - return nothing + PD.operators[j] = o + return nothing end #function assign_reduction!(PD::ProblemDescription, u::AbstractReductionOperator) @@ -91,20 +91,21 @@ end #end function Base.show(io::IO, PD::ProblemDescription) - println(io, "\nPDE-DESCRIPTION") - println(io, " • name = $(PD.name)") - println(io, "\n <<>>") - for u in PD.unknowns - print(io, " • $u") - end - - println(io, "\n <<>>") - for o in PD.operators - println(io, " • $(o)") - end - - #println(io, " reductions =") - #for o in PD.reduction_operators - # println(io, " • $o") - #end + println(io, "\nPDE-DESCRIPTION") + println(io, " • name = $(PD.name)") + println(io, "\n <<>>") + for u in PD.unknowns + print(io, " • $u") + end + + println(io, "\n <<>>") + for o in PD.operators + println(io, " • $(o)") + end + + #println(io, " reductions =") + #for o in PD.reduction_operators + # println(io, " • $o") + #end + return end diff --git a/src/solver_config.jl b/src/solver_config.jl index 7eba383..7ee165a 100644 --- a/src/solver_config.jl +++ b/src/solver_config.jl @@ -1,31 +1,31 @@ default_statistics() = Dict{Symbol, Vector{Real}}( - :assembly_times => [], - :solver_times => [], - :assembly_allocations => [], - :solver_allocations => [], - :linear_residuals => [], - :nonlinear_residuals => [], - :matrix_nnz => [], - :total_times => [], - :total_allocations => [], + :assembly_times => [], + :solver_times => [], + :assembly_allocations => [], + :solver_allocations => [], + :linear_residuals => [], + :nonlinear_residuals => [], + :matrix_nnz => [], + :total_times => [], + :total_allocations => [], ) mutable struct SolverConfiguration{AT <: AbstractMatrix, bT, xT} - PD::ProblemDescription - A::AT## stores system matrix - b::bT## stores right-hand side - sol::xT## stores solution - tempsol::xT## temporary solution - res::xT - freedofs::Vector{Int}## stores indices of free dofs - LP::LinearProblem - statistics::Dict{Symbol, Vector{Real}} - linsolver::Any - unknown_ids_in_sol::Array{Int, 1} - unknowns::Array{Unknown, 1} - unknowns_reduced::Array{Unknown, 1} - offsets::Array{Int, 1} ## offset for each unknown that is solved - parameters::Dict{Symbol, Any} # dictionary with user parameters + PD::ProblemDescription + A::AT ## stores system matrix + b::bT ## stores right-hand side + sol::xT ## stores solution + tempsol::xT ## temporary solution + res::xT + freedofs::Vector{Int} ## stores indices of free dofs + LP::LinearProblem + statistics::Dict{Symbol, Vector{Real}} + linsolver::Any + unknown_ids_in_sol::Array{Int, 1} + unknowns::Array{Unknown, 1} + unknowns_reduced::Array{Unknown, 1} + offsets::Array{Int, 1} ## offset for each unknown that is solved + parameters::Dict{Symbol, Any} # dictionary with user parameters end """ @@ -43,40 +43,41 @@ residual(S::SolverConfiguration) = S.statistics[:nonlinear_residuals][end] # Default context information with help info. # default_solver_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :target_residual => (1e-10, "stop if the absolute (nonlinear) residual is smaller than this number"), - :damping => (0, "amount of damping, value should be between in (0,1)"), - :abstol => (1e-11, "abstol for linear solver (if iterative)"), - :reltol => (1e-11, "reltol for linear solver (if iterative)"), - :time => (0.0, "current time to be used in all time-dependent operators"), - :init => (nothing, "initial solution (also used to save the new solution)"), - :spy => (false, "show unicode spy plot of system matrix during solve"), - :symmetrize => (false, "make system matrix symmetric (replace by (A+A')/2)"), - :symmetrize_structure => (false, "make the system sparse matrix structurally symmetric (e.g. if [j,k] is also [k,j] must be set, all diagonal entries must be set)"), - :restrict_dofs => ([], "array of dofs for each unknown that should be solved (default: all dofs)"), - :check_matrix => (false, "check matrix for symmetry and positive definiteness and largest/smallest eigenvalues"), - :verbosity => (0, "verbosity level"), - :show_config => (false, "show configuration at the beginning of solve"), - :show_matrix => (false, "show system matrix after assembly"), - :return_config => (false, "solver returns solver configuration (including A and b of last iteration)"), - :is_linear => ("auto", "linear problem (avoid reassembly of nonlinear operators to check residual)"), - :inactive => (Array{Unknown, 1}([]), "inactive unknowns (are made available in assembly, but not updated in solve)"), - :maxiterations => (10, "maximal number of nonlinear iterations/linear solves"), - :constant_matrix => (false, "matrix is constant (skips reassembly and refactorization in solver)"), - :constant_rhs => (false, "right-hand side is constant (skips reassembly)"), - :method_linear => (UMFPACKFactorization(), "any solver or custom LinearSolveFunction compatible with LinearSolve.jl (default = UMFPACKFactorization())"), - :precon_linear => (nothing, "function that computes preconditioner for method_linear in case an iterative solver is chosen"), - :initialized => (false, "linear system in solver configuration is already assembled (turns true after first solve)"), - :plot => (false, "plot all solved unknowns with a (very rough but fast) unicode plot"), + :target_residual => (1.0e-10, "stop if the absolute (nonlinear) residual is smaller than this number"), + :damping => (0, "amount of damping, value should be between in (0,1)"), + :abstol => (1.0e-11, "abstol for linear solver (if iterative)"), + :reltol => (1.0e-11, "reltol for linear solver (if iterative)"), + :time => (0.0, "current time to be used in all time-dependent operators"), + :init => (nothing, "initial solution (also used to save the new solution)"), + :spy => (false, "show unicode spy plot of system matrix during solve"), + :symmetrize => (false, "make system matrix symmetric (replace by (A+A')/2)"), + :symmetrize_structure => (false, "make the system sparse matrix structurally symmetric (e.g. if [j,k] is also [k,j] must be set, all diagonal entries must be set)"), + :restrict_dofs => ([], "array of dofs for each unknown that should be solved (default: all dofs)"), + :check_matrix => (false, "check matrix for symmetry and positive definiteness and largest/smallest eigenvalues"), + :verbosity => (0, "verbosity level"), + :show_config => (false, "show configuration at the beginning of solve"), + :show_matrix => (false, "show system matrix after assembly"), + :return_config => (false, "solver returns solver configuration (including A and b of last iteration)"), + :is_linear => ("auto", "linear problem (avoid reassembly of nonlinear operators to check residual)"), + :inactive => (Array{Unknown, 1}([]), "inactive unknowns (are made available in assembly, but not updated in solve)"), + :maxiterations => (10, "maximal number of nonlinear iterations/linear solves"), + :constant_matrix => (false, "matrix is constant (skips reassembly and refactorization in solver)"), + :constant_rhs => (false, "right-hand side is constant (skips reassembly)"), + :method_linear => (UMFPACKFactorization(), "any solver or custom LinearSolveFunction compatible with LinearSolve.jl (default = UMFPACKFactorization())"), + :precon_linear => (nothing, "function that computes preconditioner for method_linear in case an iterative solver is chosen"), + :initialized => (false, "linear system in solver configuration is already assembled (turns true after first solve)"), + :plot => (false, "plot all solved unknowns with a (very rough but fast) unicode plot"), ) function Base.show(io::IO, PD::SolverConfiguration) - println(io, "\nSOLVER-CONFIGURATION") - for item in PD.parameters - print(item.first) - print(" : ") - println(item.second) - end + println(io, "\nSOLVER-CONFIGURATION") + for item in PD.parameters + print(item.first) + print(" : ") + println(item.second) + end + return end @@ -100,74 +101,74 @@ $(_myprint(default_solver_kwargs())) """ function SolverConfiguration(Problem::ProblemDescription; init = nothing, unknowns = Problem.unknowns, kwargs...) - ## try to guess FES from init - if typeof(init) <: FEVector - FES = [init[u].FES for u in unknowns] - end - SolverConfiguration(Problem, unknowns, FES; kwargs...) + ## try to guess FES from init + if typeof(init) <: FEVector + FES = [init[u].FES for u in unknowns] + end + return SolverConfiguration(Problem, unknowns, FES; kwargs...) end function SolverConfiguration(Problem::ProblemDescription, FES; unknowns = Problem.unknowns, kwargs...) - SolverConfiguration(Problem, unknowns, FES; kwargs...) + return SolverConfiguration(Problem, unknowns, FES; kwargs...) end function SolverConfiguration(Problem::ProblemDescription, unknowns::Array{Unknown, 1}, FES, default_kwargs = default_solver_kwargs(); TvM = Float64, TiM = Int, bT = Float64, kwargs...) - if typeof(FES) <: FESpace - FES = [FES] - end - @assert length(unknowns) <= length(FES) "length of unknowns and FE spaces must coincide" - ## check if unknowns are part of Problem description - for u in unknowns - @assert u in Problem.unknowns "unknown $u is not part of the given ProblemDescription" - end - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_kwargs) - _update_params!(parameters, kwargs) - ## compute offsets - offsets = [0] - for FE in FES - push!(offsets, FE.ndofs + offsets[end]) - end - - ## storage for full system - FES_active = FES[1:length(unknowns)] - A = FEMatrix{TvM, TiM}(FES_active; tags = unknowns, npartitions = num_partitions(FES[1].xgrid)) - b = FEVector{bT}(FES_active; tags = unknowns) - res = copy(b) - - ## initialize solution vector - if parameters[:init] === nothing - names = [u.name for u in unknowns] - append!(names, ["N.N." for j ∈ length(unknowns)+1:length(FES)]) - x = FEVector{bT}(FES; name = names, tags = unknowns) - unknown_ids_in_sol = 1:length(unknowns) - else - x = parameters[:init] - unknown_ids_in_sol = [findfirst(==(u), x.tags) for u in unknowns] - end - - ## adjustments for using freedofs - if haskey(parameters, :restrict_dofs) - if length(parameters[:restrict_dofs]) > 0 - freedofs = Vector{Int}(parameters[:restrict_dofs][1]) - for j ∈ 2:length(parameters[:restrict_dofs]) - parameters[:restrict_dofs][j] .+= FES[j-1].ndofs - append!(freedofs, parameters[:restrict_dofs][j]) - end - x_temp = copy(b) - else - freedofs = [] - x_temp = x - end - else - freedofs = [] - x_temp = x - end - - ## construct linear problem - if length(freedofs) > 0 - LP = LinearProblem(A.entries.cscmatrix[freedofs, freedofs], b.entries[freedofs]) - else - LP = LinearProblem(A.entries.cscmatrix, b.entries) - end - return SolverConfiguration{typeof(A), typeof(b), typeof(x)}(Problem, A, b, x, x_temp, res, freedofs, LP, default_statistics(), nothing, unknown_ids_in_sol, unknowns, copy(unknowns), offsets, parameters) + if typeof(FES) <: FESpace + FES = [FES] + end + @assert length(unknowns) <= length(FES) "length of unknowns and FE spaces must coincide" + ## check if unknowns are part of Problem description + for u in unknowns + @assert u in Problem.unknowns "unknown $u is not part of the given ProblemDescription" + end + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_kwargs) + _update_params!(parameters, kwargs) + ## compute offsets + offsets = [0] + for FE in FES + push!(offsets, FE.ndofs + offsets[end]) + end + + ## storage for full system + FES_active = FES[1:length(unknowns)] + A = FEMatrix{TvM, TiM}(FES_active; tags = unknowns, npartitions = num_partitions(FES[1].xgrid)) + b = FEVector{bT}(FES_active; tags = unknowns) + res = copy(b) + + ## initialize solution vector + if parameters[:init] === nothing + names = [u.name for u in unknowns] + append!(names, ["N.N." for j in (length(unknowns) + 1):length(FES)]) + x = FEVector{bT}(FES; name = names, tags = unknowns) + unknown_ids_in_sol = 1:length(unknowns) + else + x = parameters[:init] + unknown_ids_in_sol = [findfirst(==(u), x.tags) for u in unknowns] + end + + ## adjustments for using freedofs + if haskey(parameters, :restrict_dofs) + if length(parameters[:restrict_dofs]) > 0 + freedofs = Vector{Int}(parameters[:restrict_dofs][1]) + for j in 2:length(parameters[:restrict_dofs]) + parameters[:restrict_dofs][j] .+= FES[j - 1].ndofs + append!(freedofs, parameters[:restrict_dofs][j]) + end + x_temp = copy(b) + else + freedofs = [] + x_temp = x + end + else + freedofs = [] + x_temp = x + end + + ## construct linear problem + if length(freedofs) > 0 + LP = LinearProblem(A.entries.cscmatrix[freedofs, freedofs], b.entries[freedofs]) + else + LP = LinearProblem(A.entries.cscmatrix, b.entries) + end + return SolverConfiguration{typeof(A), typeof(b), typeof(x)}(Problem, A, b, x, x_temp, res, freedofs, LP, default_statistics(), nothing, unknown_ids_in_sol, unknowns, copy(unknowns), offsets, parameters) end diff --git a/src/solvers.jl b/src/solvers.jl index 23850c6..07981d7 100644 --- a/src/solvers.jl +++ b/src/solvers.jl @@ -7,7 +7,7 @@ returns the id of the unknown u in SC """ function get_unknown_id(SC::SolverConfiguration, u::Unknown) - return findfirst(==(u), SC.unknowns) + return findfirst(==(u), SC.unknowns) end @@ -36,405 +36,403 @@ either solved directly in one step or via a fixed-point iteration. """ function CommonSolve.solve(PD::ProblemDescription, FES::Dict{<:Unknown}, SC = nothing; unknowns = PD.unknowns, kwargs...) - return solve(PD, [FES[u] for u in unknowns], SC; unknowns = unknowns, kwargs...) + return solve(PD, [FES[u] for u in unknowns], SC; unknowns = unknowns, kwargs...) end function CommonSolve.solve(PD::ProblemDescription, SC = nothing; init = nothing, unknowns = PD.unknowns, kwargs...) - if init === nothing - @error "need to know initial FEVector or finite element spaces for unknowns of problem" - end - return solve(PD, [init[u].FES for u in unknowns], SC; init = init, unknowns = unknowns, kwargs...) + if init === nothing + @error "need to know initial FEVector or finite element spaces for unknowns of problem" + end + return solve(PD, [init[u].FES for u in unknowns], SC; init = init, unknowns = unknowns, kwargs...) end -function symmetrize_structure!(A::ExtendableSparseMatrix{Tv, Ti}; diagval = 1e-16) where {Tv, Ti} - cscmat = A.cscmatrix - rows::Array{Ti, 1} = rowvals(cscmat) - nzvals::Array{Tv, 1} = cscmat.nzval - for col ∈ 1:size(cscmat, 2) - for r in nzrange(cscmat, col) - A[col, rows[r]] += 1e-16 * nzvals[r] - end - if A[col, col] == 0 && diagval !== 0 - A[col, col] = diagval - end - end - flush!(A) +function symmetrize_structure!(A::ExtendableSparseMatrix{Tv, Ti}; diagval = 1.0e-16) where {Tv, Ti} + cscmat = A.cscmatrix + rows::Array{Ti, 1} = rowvals(cscmat) + nzvals::Array{Tv, 1} = cscmat.nzval + for col in 1:size(cscmat, 2) + for r in nzrange(cscmat, col) + A[col, rows[r]] += 1.0e-16 * nzvals[r] + end + if A[col, col] == 0 && diagval !== 0 + A[col, col] = diagval + end + end + return flush!(A) end function CommonSolve.solve(PD::ProblemDescription, FES::Union{<:FESpace, Vector{<:FESpace}}, SC = nothing; unknowns = PD.unknowns, kwargs...) - if typeof(FES) <: FESpace - FES = [FES] - end - if typeof(SC) <: SolverConfiguration - _update_params!(SC.parameters, kwargs) - if SC.parameters[:verbosity] > 0 - @info ".... reusing given solver configuration\n" - end - time = 0 - allocs = 0 - else - time = @elapsed begin - allocs = @allocated begin - SC = SolverConfiguration(PD, unknowns, FES; kwargs...) - if SC.parameters[:verbosity] > 0 - @info ".... init solver configuration\n" - end - end - end - end - - A = SC.A - b = SC.b - sol = SC.sol - soltemp = SC.tempsol - residual = SC.res - method_linear = SC.parameters[:method_linear] - precon_linear = SC.parameters[:precon_linear] - stats = SC.statistics - for (key, value) in stats - stats[key] = [] - end - - ## unpack solver parameters - maxits = SC.parameters[:maxiterations] - @assert maxits > -1 - nltol = SC.parameters[:target_residual] - is_linear = SC.parameters[:is_linear] - damping = SC.parameters[:damping] - freedofs = SC.freedofs - - - if SC.parameters[:verbosity] > -1 - if length(freedofs) > 0 - @info "SOLVING $(PD.name) @ time = $(SC.parameters[:time]) + if typeof(FES) <: FESpace + FES = [FES] + end + if typeof(SC) <: SolverConfiguration + _update_params!(SC.parameters, kwargs) + if SC.parameters[:verbosity] > 0 + @info ".... reusing given solver configuration\n" + end + time = 0 + allocs = 0 + else + time = @elapsed begin + allocs = @allocated begin + SC = SolverConfiguration(PD, unknowns, FES; kwargs...) + if SC.parameters[:verbosity] > 0 + @info ".... init solver configuration\n" + end + end + end + end + + A = SC.A + b = SC.b + sol = SC.sol + soltemp = SC.tempsol + residual = SC.res + method_linear = SC.parameters[:method_linear] + precon_linear = SC.parameters[:precon_linear] + stats = SC.statistics + for (key, value) in stats + stats[key] = [] + end + + ## unpack solver parameters + maxits = SC.parameters[:maxiterations] + @assert maxits > -1 + nltol = SC.parameters[:target_residual] + is_linear = SC.parameters[:is_linear] + damping = SC.parameters[:damping] + freedofs = SC.freedofs + + + if SC.parameters[:verbosity] > -1 + if length(freedofs) > 0 + @info "SOLVING $(PD.name) @ time = $(SC.parameters[:time]) unknowns = $([u.name for u in unknowns]) - fetypes = $(["$(get_FEType(FES[j]))" for j = 1 : length(unknowns)]) - ndofs = $([FES[j].ndofs for j = 1 : length(unknowns)]) (restricted to $(length.(SC.parameters[:restrict_dofs])))" - else - @info "SOLVING $(PD.name) @ time = $(SC.parameters[:time]) + fetypes = $(["$(get_FEType(FES[j]))" for j in 1:length(unknowns)]) + ndofs = $([FES[j].ndofs for j in 1:length(unknowns)]) (restricted to $(length.(SC.parameters[:restrict_dofs])))" + else + @info "SOLVING $(PD.name) @ time = $(SC.parameters[:time]) unknowns = $([u.name for u in unknowns]) - fetypes = $(["$(get_FEType(FES[j]))" for j = 1 : length(unknowns)]) - ndofs = $([FES[j].ndofs for j = 1 : length(unknowns)])" - end - end - - if SC.parameters[:verbosity] > 0 || SC.parameters[:show_config] - @info "\n$(SC)" - end - - ## check if problem is (non)linear - nonlinear = false - for op in PD.operators - nl_dependencies = depends_nonlinearly_on(op) - for u in unknowns - if u in nl_dependencies - nonlinear = true - break - end - end - end - if SC.parameters[:verbosity] > -1 - @info " nonlinear = $(nonlinear ? "true" : "false")\n" - end - if is_linear == "auto" - is_linear = !nonlinear - end - if is_linear && nonlinear - @warn "problem seems nonlinear, but user set is_linear = true (results may be wrong)!!" - end - if is_linear - maxits = 0 - end - - alloc_factor = 1024^2 - - if SC.parameters[:verbosity] > -1 - @printf " #IT\t------- RESIDUALS -------\t---- DURATION (s) ----\t\t---- ALLOCATIONS (MiB) ----\n" - @printf " \tNONLINEAR\tLINEAR\t\tASSEMB\tSOLVE\tTOTAL\t\tASSEMB\tSOLVE\tTOTAL\n" - @printf " INI\t\t\t\t\t\t\t%.2f\t\t\t\t%.2f\n" time allocs / alloc_factor - end - time_final = time - allocs_final = allocs - nlres = 1.1e30 - linres = 1.1e30 - linsolve = SC.linsolver - reduced = false - - for j ∈ 1:maxits+1 - allocs_assembly = 0 - time_assembly = 0 - time_total = 0 - if is_linear && j == 2 - nlres = linres - else - time_total += @elapsed begin - - ## assemble operators - if !SC.parameters[:constant_rhs] - fill!(b.entries, 0) - end - if !SC.parameters[:constant_matrix] - fill!(A.entries.cscmatrix.nzval, 0) - end - if SC.parameters[:initialized] - time_assembly += @elapsed for op in PD.operators - allocs_assembly += @allocated assemble!(A, b, sol, op, SC; time = SC.parameters[:time], assemble_matrix = !SC.parameters[:constant_matrix], assemble_rhs = !SC.parameters[:constant_rhs], kwargs...) - end - else - time_assembly += @elapsed for op in PD.operators - allocs_assembly += @allocated assemble!(A, b, sol, op, SC; time = SC.parameters[:time], kwargs...) - end - end - flush!(A.entries) - - ## penalize fixed dofs - time_assembly += @elapsed for op in PD.operators - allocs_assembly += @allocated apply_penalties!(A, b, sol, op, SC; kwargs...) - end - flush!(A.entries) - # end - - # ## remove inactive dofs - # for u_off in SC.parameters[:inactive] - # j = get_unknown_id(SC, u_off) - # if j > 0 - # fill!(A[j,j],0) - # FES = sol[j].FES - # for dof in 1:FES.ndofs - # A[j,j][dof, dof] = 1e60 - # b[j][dof] = 1e60*sol[j][dof] - # end - # else - # @warn "inactive unknown $(u_off) not part of unknowns, skipping this one..." - # end - # end - - ## reduction steps - # time_assembly += @elapsed begin - # if length(PD.reduction_operators) > 0 && j == 1 - # LP_reduced = SC.LP - # reduced = true - # for op in PD.reduction_operators - # allocs_assembly += @allocated LP_reduced, A, b = apply!(LP_reduced, op, SC; kwargs...) - # end - # residual = copy(b) - # end - # end - - ## show spy - if SC.parameters[:symmetrize] - A.entries.cscmatrix = (A.entries.cscmatrix + A.entries.cscmatrix') / 2 - elseif SC.parameters[:symmetrize_structure] - symmetrize_structure!(A.entries) - end - if SC.parameters[:show_matrix] - @show A - elseif SC.parameters[:spy] - @info ".... spy plot of system matrix:\n$(A.entries.cscmatrix))" - end - if SC.parameters[:check_matrix] - #λ, ϕ = Arpack.eigs(A.entries.cscmatrix; nev = 5, which = :SM, ritzvec = false) - #@info ".... 5 :SM eigs = $(λ)" - #λ, ϕ = Arpack.eigs(A.entries.cscmatrix; nev = 5, which = :LM, ritzvec = false) - #@info ".... 5 :LM eigs = $(λ)" - @info ".... ||A - A'|| = $(norm(A.entries.cscmatrix - A.entries.cscmatrix', Inf))" - @info ".... isposdef = $(isposdef(A.entries.cscmatrix))" - end - - ## init solver - if linsolve === nothing - if SC.parameters[:verbosity] > 0 - @info ".... initializing linear solver ($(method_linear))\n" - end - abstol = SC.parameters[:abstol] - reltol = SC.parameters[:reltol] - LP = reduced ? LP_reduced : SC.LP - if precon_linear !== nothing - linsolve = init(LP, method_linear; Pl = precon_linear(A.entries.cscmatrix), abstol = abstol, reltol = reltol) - else - linsolve = init(LP, method_linear; abstol = abstol, reltol = reltol) - end - SC.linsolver = linsolve - end - - ## compute nonlinear residual - if !is_linear - fill!(residual.entries, 0) - for j ∈ 1:length(b), k ∈ 1:length(b) - addblock_matmul!(residual[j], A[j, k], sol[unknowns[k]]) - end - residual.entries .-= b.entries - #res = A.entries * sol.entries - b.entries - for op in PD.operators - residual.entries[fixed_dofs(op)] .= 0 - end - for u_off in SC.parameters[:inactive] - j = get_unknown_id(SC, u_off) - if j > 0 - fill!(residual[j], 0) - end - end - if length(freedofs) > 0 - nlres = norm(residual.entries[freedofs]) - else - nlres = norm(residual.entries) - end - if SC.parameters[:verbosity] > 0 && length(residual) > 1 - @info "sub-residuals = $(norms(residual))" - end - end - end - time_final += time_assembly - allocs_final += allocs_assembly - end - push!(stats[:assembly_allocations], allocs_assembly) - push!(stats[:assembly_times], time_assembly) - if !is_linear - push!(stats[:nonlinear_residuals], nlres) - end - if nlres < nltol - if SC.parameters[:verbosity] > -1 - @printf " END\t" - @printf "%.3e\t" nlres - @printf "\t\t%.2f\t\t%.2f\t" time_assembly time_total - @printf "\t%.2f\t\t%.2f\n" allocs_assembly / alloc_factor allocs_assembly / alloc_factor - @printf "\tconverged" - @printf "\t\t\t\tSUM -->\t%.2f" time_final - @printf "\t\t\tSUM -->\t%.2f\n\n" allocs_final / alloc_factor - end - break - elseif isnan(nlres) - if SC.parameters[:verbosity] > -1 - @printf " END\t" - @printf "%.3e\t" nlres - @printf "\t\t\t%.2f\t\t%.2f\t" time_assembly time_total - @printf "\t%.2f\t\t%.2f\n" allocs_assembly / alloc_factor allocs_assembly / alloc_factor - @printf "\tdid not converge" - @printf "\t\t\tSUM -->\t%.2f" time_final - @printf "\t\t\tSUM -->\t%.2f\n\n" allocs_final / alloc_factor - end - break - elseif (j == maxits + 1) && !(is_linear) - if SC.parameters[:verbosity] > -1 - @printf " END\t" - @printf "\t\t%.3e\t" linres - @printf "\t\t%.2f\t\t%.2f\t" time_assembly time_total - @printf "\t%.2f\t\t%.2f\n" allocs_assembly / alloc_factor allocs_assembly / alloc_factor - @printf "\tmaxiterations reached" - @printf "\t\t\tSUM -->\t%.2f" time_final - @printf "\t\t\tSUM -->\t%.2f\n\n" allocs_final / alloc_factor - end - break - else - if SC.parameters[:verbosity] > -1 - if is_linear - @printf " END\t" - else - @printf "%4d\t" j - end - if !(is_linear) - @printf "%.3e\t" nlres - else - @printf "---------\t" - end - end - end - - time_solve = @elapsed begin - allocs_solve = @allocated begin - if !SC.parameters[:constant_matrix] || !SC.parameters[:initialized] - if length(freedofs) > 0 - linsolve.A = A.entries.cscmatrix[freedofs, freedofs] - else - linsolve.A = A.entries.cscmatrix - end - end - if !SC.parameters[:constant_rhs] || !SC.parameters[:initialized] - if length(freedofs) > 0 - linsolve.b = b.entries[freedofs] - else - linsolve.b = b.entries - end - end - SC.parameters[:initialized] = true - - ## solve - push!(stats[:matrix_nnz], nnz(linsolve.A)) - x = LinearSolve.solve!(linsolve) - - ## check linear residual with full matrix - if length(freedofs) > 0 - soltemp.entries[freedofs] .= x.u - residual.entries .= A.entries.cscmatrix * soltemp.entries - else - residual.entries .= A.entries.cscmatrix * x.u - end - residual.entries .-= b.entries - for op in PD.operators - for dof in fixed_dofs(op) - if dof <= length(residual.entries) - residual.entries[dof] = 0 - end - end - end - linres = norm(residual.entries) - push!(stats[:linear_residuals], linres) - if is_linear - push!(stats[:nonlinear_residuals], linres) - end - - ## update solution (incl. damping etc.) - offset = 0 - if length(freedofs) > 0 - sol.entries[freedofs] .= x.u - else - for u in unknowns - ndofs_u = length(view(sol[u])) - if damping > 0 - view(sol[u]) .= damping * view(sol[u]) + (1 - damping) * view(x.u, offset+1:offset+ndofs_u) - else - view(sol[u]) .= view(x.u, offset+1:offset+ndofs_u) - end - offset += ndofs_u - end - end - end - end - time_total += time_solve - time_final += time_solve - allocs_final += allocs_solve - push!(stats[:solver_allocations], allocs_solve) - push!(stats[:solver_times], time_solve) - push!(stats[:total_times], time_total) - push!(stats[:total_allocations], (allocs_assembly + allocs_solve)) - if SC.parameters[:verbosity] > -1 - @printf "%.3e\t" linres - @printf "%.2f\t%.2f\t%.2f\t" time_assembly time_solve time_total - @printf "\t%.2f\t%.2f\t%.2f\n" allocs_assembly / alloc_factor allocs_solve / alloc_factor (allocs_assembly + allocs_solve) / alloc_factor - if is_linear - @printf "\tfinished" - @printf "\t\t\t\tSUM -->\t%.2f" time_final - @printf "\t\t\tSUM -->\t%.2f\n\n" allocs_final / alloc_factor - end - end - end - - if SC.parameters[:plot] - for u in unknowns - println(stdout, unicode_scalarplot(sol[u]; title = u.name, kwargs...)) - end - end - - if SC.parameters[:return_config] - return sol, SC - else - return sol - end + fetypes = $(["$(get_FEType(FES[j]))" for j in 1:length(unknowns)]) + ndofs = $([FES[j].ndofs for j in 1:length(unknowns)])" + end + end + + if SC.parameters[:verbosity] > 0 || SC.parameters[:show_config] + @info "\n$(SC)" + end + + ## check if problem is (non)linear + nonlinear = false + for op in PD.operators + nl_dependencies = depends_nonlinearly_on(op) + for u in unknowns + if u in nl_dependencies + nonlinear = true + break + end + end + end + if SC.parameters[:verbosity] > -1 + @info " nonlinear = $(nonlinear ? "true" : "false")\n" + end + if is_linear == "auto" + is_linear = !nonlinear + end + if is_linear && nonlinear + @warn "problem seems nonlinear, but user set is_linear = true (results may be wrong)!!" + end + if is_linear + maxits = 0 + end + + alloc_factor = 1024^2 + + if SC.parameters[:verbosity] > -1 + @printf " #IT\t------- RESIDUALS -------\t---- DURATION (s) ----\t\t---- ALLOCATIONS (MiB) ----\n" + @printf " \tNONLINEAR\tLINEAR\t\tASSEMB\tSOLVE\tTOTAL\t\tASSEMB\tSOLVE\tTOTAL\n" + @printf " INI\t\t\t\t\t\t\t%.2f\t\t\t\t%.2f\n" time allocs / alloc_factor + end + time_final = time + allocs_final = allocs + nlres = 1.1e30 + linres = 1.1e30 + linsolve = SC.linsolver + reduced = false + + for j in 1:(maxits + 1) + allocs_assembly = 0 + time_assembly = 0 + time_total = 0 + if is_linear && j == 2 + nlres = linres + else + time_total += @elapsed begin + + ## assemble operators + if !SC.parameters[:constant_rhs] + fill!(b.entries, 0) + end + if !SC.parameters[:constant_matrix] + fill!(A.entries.cscmatrix.nzval, 0) + end + if SC.parameters[:initialized] + time_assembly += @elapsed for op in PD.operators + allocs_assembly += @allocated assemble!(A, b, sol, op, SC; time = SC.parameters[:time], assemble_matrix = !SC.parameters[:constant_matrix], assemble_rhs = !SC.parameters[:constant_rhs], kwargs...) + end + else + time_assembly += @elapsed for op in PD.operators + allocs_assembly += @allocated assemble!(A, b, sol, op, SC; time = SC.parameters[:time], kwargs...) + end + end + flush!(A.entries) + + ## penalize fixed dofs + time_assembly += @elapsed for op in PD.operators + allocs_assembly += @allocated apply_penalties!(A, b, sol, op, SC; kwargs...) + end + flush!(A.entries) + # end + + # ## remove inactive dofs + # for u_off in SC.parameters[:inactive] + # j = get_unknown_id(SC, u_off) + # if j > 0 + # fill!(A[j,j],0) + # FES = sol[j].FES + # for dof in 1:FES.ndofs + # A[j,j][dof, dof] = 1e60 + # b[j][dof] = 1e60*sol[j][dof] + # end + # else + # @warn "inactive unknown $(u_off) not part of unknowns, skipping this one..." + # end + # end + + ## reduction steps + # time_assembly += @elapsed begin + # if length(PD.reduction_operators) > 0 && j == 1 + # LP_reduced = SC.LP + # reduced = true + # for op in PD.reduction_operators + # allocs_assembly += @allocated LP_reduced, A, b = apply!(LP_reduced, op, SC; kwargs...) + # end + # residual = copy(b) + # end + # end + + ## show spy + if SC.parameters[:symmetrize] + A.entries.cscmatrix = (A.entries.cscmatrix + A.entries.cscmatrix') / 2 + elseif SC.parameters[:symmetrize_structure] + symmetrize_structure!(A.entries) + end + if SC.parameters[:show_matrix] + @show A + elseif SC.parameters[:spy] + @info ".... spy plot of system matrix:\n$(A.entries.cscmatrix))" + end + if SC.parameters[:check_matrix] + #λ, ϕ = Arpack.eigs(A.entries.cscmatrix; nev = 5, which = :SM, ritzvec = false) + #@info ".... 5 :SM eigs = $(λ)" + #λ, ϕ = Arpack.eigs(A.entries.cscmatrix; nev = 5, which = :LM, ritzvec = false) + #@info ".... 5 :LM eigs = $(λ)" + @info ".... ||A - A'|| = $(norm(A.entries.cscmatrix - A.entries.cscmatrix', Inf))" + @info ".... isposdef = $(isposdef(A.entries.cscmatrix))" + end + + ## init solver + if linsolve === nothing + if SC.parameters[:verbosity] > 0 + @info ".... initializing linear solver ($(method_linear))\n" + end + abstol = SC.parameters[:abstol] + reltol = SC.parameters[:reltol] + LP = reduced ? LP_reduced : SC.LP + if precon_linear !== nothing + linsolve = init(LP, method_linear; Pl = precon_linear(A.entries.cscmatrix), abstol = abstol, reltol = reltol) + else + linsolve = init(LP, method_linear; abstol = abstol, reltol = reltol) + end + SC.linsolver = linsolve + end + + ## compute nonlinear residual + if !is_linear + fill!(residual.entries, 0) + for j in 1:length(b), k in 1:length(b) + addblock_matmul!(residual[j], A[j, k], sol[unknowns[k]]) + end + residual.entries .-= b.entries + #res = A.entries * sol.entries - b.entries + for op in PD.operators + residual.entries[fixed_dofs(op)] .= 0 + end + for u_off in SC.parameters[:inactive] + j = get_unknown_id(SC, u_off) + if j > 0 + fill!(residual[j], 0) + end + end + if length(freedofs) > 0 + nlres = norm(residual.entries[freedofs]) + else + nlres = norm(residual.entries) + end + if SC.parameters[:verbosity] > 0 && length(residual) > 1 + @info "sub-residuals = $(norms(residual))" + end + end + end + time_final += time_assembly + allocs_final += allocs_assembly + end + push!(stats[:assembly_allocations], allocs_assembly) + push!(stats[:assembly_times], time_assembly) + if !is_linear + push!(stats[:nonlinear_residuals], nlres) + end + if nlres < nltol + if SC.parameters[:verbosity] > -1 + @printf " END\t" + @printf "%.3e\t" nlres + @printf "\t\t%.2f\t\t%.2f\t" time_assembly time_total + @printf "\t%.2f\t\t%.2f\n" allocs_assembly / alloc_factor allocs_assembly / alloc_factor + @printf "\tconverged" + @printf "\t\t\t\tSUM -->\t%.2f" time_final + @printf "\t\t\tSUM -->\t%.2f\n\n" allocs_final / alloc_factor + end + break + elseif isnan(nlres) + if SC.parameters[:verbosity] > -1 + @printf " END\t" + @printf "%.3e\t" nlres + @printf "\t\t\t%.2f\t\t%.2f\t" time_assembly time_total + @printf "\t%.2f\t\t%.2f\n" allocs_assembly / alloc_factor allocs_assembly / alloc_factor + @printf "\tdid not converge" + @printf "\t\t\tSUM -->\t%.2f" time_final + @printf "\t\t\tSUM -->\t%.2f\n\n" allocs_final / alloc_factor + end + break + elseif (j == maxits + 1) && !(is_linear) + if SC.parameters[:verbosity] > -1 + @printf " END\t" + @printf "\t\t%.3e\t" linres + @printf "\t\t%.2f\t\t%.2f\t" time_assembly time_total + @printf "\t%.2f\t\t%.2f\n" allocs_assembly / alloc_factor allocs_assembly / alloc_factor + @printf "\tmaxiterations reached" + @printf "\t\t\tSUM -->\t%.2f" time_final + @printf "\t\t\tSUM -->\t%.2f\n\n" allocs_final / alloc_factor + end + break + else + if SC.parameters[:verbosity] > -1 + if is_linear + @printf " END\t" + else + @printf "%4d\t" j + end + if !(is_linear) + @printf "%.3e\t" nlres + else + @printf "---------\t" + end + end + end + + time_solve = @elapsed begin + allocs_solve = @allocated begin + if !SC.parameters[:constant_matrix] || !SC.parameters[:initialized] + if length(freedofs) > 0 + linsolve.A = A.entries.cscmatrix[freedofs, freedofs] + else + linsolve.A = A.entries.cscmatrix + end + end + if !SC.parameters[:constant_rhs] || !SC.parameters[:initialized] + if length(freedofs) > 0 + linsolve.b = b.entries[freedofs] + else + linsolve.b = b.entries + end + end + SC.parameters[:initialized] = true + + ## solve + push!(stats[:matrix_nnz], nnz(linsolve.A)) + x = LinearSolve.solve!(linsolve) + + ## check linear residual with full matrix + if length(freedofs) > 0 + soltemp.entries[freedofs] .= x.u + residual.entries .= A.entries.cscmatrix * soltemp.entries + else + residual.entries .= A.entries.cscmatrix * x.u + end + residual.entries .-= b.entries + for op in PD.operators + for dof in fixed_dofs(op) + if dof <= length(residual.entries) + residual.entries[dof] = 0 + end + end + end + linres = norm(residual.entries) + push!(stats[:linear_residuals], linres) + if is_linear + push!(stats[:nonlinear_residuals], linres) + end + + ## update solution (incl. damping etc.) + offset = 0 + if length(freedofs) > 0 + sol.entries[freedofs] .= x.u + else + for u in unknowns + ndofs_u = length(view(sol[u])) + if damping > 0 + view(sol[u]) .= damping * view(sol[u]) + (1 - damping) * view(x.u, (offset + 1):(offset + ndofs_u)) + else + view(sol[u]) .= view(x.u, (offset + 1):(offset + ndofs_u)) + end + offset += ndofs_u + end + end + end + end + time_total += time_solve + time_final += time_solve + allocs_final += allocs_solve + push!(stats[:solver_allocations], allocs_solve) + push!(stats[:solver_times], time_solve) + push!(stats[:total_times], time_total) + push!(stats[:total_allocations], (allocs_assembly + allocs_solve)) + if SC.parameters[:verbosity] > -1 + @printf "%.3e\t" linres + @printf "%.2f\t%.2f\t%.2f\t" time_assembly time_solve time_total + @printf "\t%.2f\t%.2f\t%.2f\n" allocs_assembly / alloc_factor allocs_solve / alloc_factor (allocs_assembly + allocs_solve) / alloc_factor + if is_linear + @printf "\tfinished" + @printf "\t\t\t\tSUM -->\t%.2f" time_final + @printf "\t\t\tSUM -->\t%.2f\n\n" allocs_final / alloc_factor + end + end + end + + if SC.parameters[:plot] + for u in unknowns + println(stdout, unicode_scalarplot(sol[u]; title = u.name, kwargs...)) + end + end + + if SC.parameters[:return_config] + return sol, SC + else + return sol + end end - - """ ```` function iterate_until_stationarity( @@ -464,236 +462,237 @@ with the usual keyword arguments. """ function iterate_until_stationarity( - SCs::Array{<:SolverConfiguration, 1}, - FES = nothing; - maxsteps = 1000, - energy_integrator = nothing, - init = nothing, - unknowns = [SC.PD.unknowns for SC in SCs], - kwargs...) - - PDs::Array{ProblemDescription, 1} = [SC.PD for SC in SCs] - nPDs = length(PDs) - - ## find FESpaces and generate solution vector - if FES === nothing - @assert init !== nothing "need init vector or FES (as a Vector{Vector{<:FESpace}})" - @info ".... taking FESpaces from init vector \n" - all_unknowns = init.tags - for p ∈ 1:nPDs, u in unknowns[p] - @assert u in all_unknowns "did not found unknown $u in init vector (tags missing?)" - end - FES = [[init[u].FES for u in unknowns[j]] for j ∈ 1:nPDs] - sol = copy(init) - sol.tags .= init.tags - else - all_unknowns = [] - for p ∈ 1:nPDs, u in unknowns[p] - if !(u in all_unknowns) - push!(u, all_unknowns) - end - end - sol = FEVector(FES; tags = all_unknowns) - end - - @info "SOLVING iteratively $([PD.name for PD in PDs]) + SCs::Array{<:SolverConfiguration, 1}, + FES = nothing; + maxsteps = 1000, + energy_integrator = nothing, + init = nothing, + unknowns = [SC.PD.unknowns for SC in SCs], + kwargs... + ) + + PDs::Array{ProblemDescription, 1} = [SC.PD for SC in SCs] + nPDs = length(PDs) + + ## find FESpaces and generate solution vector + if FES === nothing + @assert init !== nothing "need init vector or FES (as a Vector{Vector{<:FESpace}})" + @info ".... taking FESpaces from init vector \n" + all_unknowns = init.tags + for p in 1:nPDs, u in unknowns[p] + @assert u in all_unknowns "did not found unknown $u in init vector (tags missing?)" + end + FES = [[init[u].FES for u in unknowns[j]] for j in 1:nPDs] + sol = copy(init) + sol.tags .= init.tags + else + all_unknowns = [] + for p in 1:nPDs, u in unknowns[p] + if !(u in all_unknowns) + push!(u, all_unknowns) + end + end + sol = FEVector(FES; tags = all_unknowns) + end + + @info "SOLVING iteratively $([PD.name for PD in PDs]) unknowns = $([[uj.name for uj in u] for u in unknowns])" - # fetypes = $(["$(get_FEType(FES[j]))" for j = 1 : length(unknowns)]) - # ndofs = $([FES[j].ndofs for j = 1 : length(unknowns)]) - - As = [SC.A for SC in SCs] - bs = [SC.b for SC in SCs] - residuals = [SC.res for SC in SCs] - - ## unpack solver parameters - is_linear = zeros(Bool, nPDs) - - ## check if problems are (non)linear - nonlinear = zeros(Bool, nPDs) - for (j, PD) in enumerate(PDs) - for op in PD.operators - nl_dependencies = depends_nonlinearly_on(op) - for u in unknowns - if u in nl_dependencies - nonlinear[j] = true - break - end - end - end - if SCs[j].parameters[:verbosity] > 0 - @info "nonlinear = $(nonlinear[j] ? "true" : "false")\n" - end - if SCs[j].parameters[:is_linear] == "auto" - is_linear[j] = !nonlinear[j] - end - if is_linear[j] && nonlinear[j] - @warn "problem $(PD.name) seems nonlinear, but user set is_linear = true (results may be wrong)!!" - end - end - maxits = [is_linear[j] ? 1 : maxits[j] for j ∈ 1:nPDs] - - alloc_factor = 1024^2 - - time_final = 0 - allocs_final = 0 - nlres = 1.1e30 - linres = 1.1e30 - converged = zeros(Bool, nPDs) - it::Int = 0 - while (it < maxsteps) && (any(converged .== false)) - it += 1 - @printf "%5d\t" it - copyto!(init.entries, sol.entries) - allocs_assembly = 0 - time_assembly = 0 - time_total = 0 - for p ∈ 1:nPDs - b = bs[p] - A = As[p] - PD = PDs[p] - SC = SCs[p] - residual = residuals[p] - maxits = SC.parameters[:maxiterations] - nltol = SC.parameters[:target_residual] - damping = SC.parameters[:damping] - for j ∈ 1:1 - time_total += @elapsed begin - - ## assemble operators - if !SC.parameters[:constant_rhs] - fill!(b.entries, 0) - end - if !SC.parameters[:constant_matrix] - fill!(A.entries.cscmatrix.nzval, 0) - end - if SC.parameters[:initialized] - time_assembly += @elapsed for op in PD.operators - allocs_assembly += @allocated assemble!(A, b, sol, op, SC; time = SC.parameters[:time], assemble_matrix = !SC.parameters[:constant_matrix], assemble_rhs = !SC.parameters[:constant_rhs], kwargs...) - end - else - time_assembly += @elapsed for op in PD.operators - allocs_assembly += @allocated assemble!(A, b, sol, op, SC; time = SC.parameters[:time], kwargs...) - end - end - flush!(A.entries) - - ## penalize fixed dofs - time_assembly += @elapsed for op in PD.operators - allocs_assembly += @allocated apply_penalties!(A, b, sol, op, SC; kwargs...) - end - flush!(A.entries) - - if SC.parameters[:verbosity] > 0 - @printf " assembly time | allocs = %.2f s | %.2f MiB\n" time allocs / alloc_factor - end - - ## show spy - if SC.parameters[:show_matrix] - @show A - elseif SC.parameters[:spy] - @info ".... spy plot of system matrix:\n$(UnicodePlots.spy(sparse(A.entries.cscmatrix)))" - end - - ## init solver - linsolve = SC.linsolver - if linsolve === nothing - method_linear = SC.parameters[:method_linear] - precon_linear = SC.parameters[:precon_linear] - if SC.parameters[:verbosity] > 0 - @info ".... initializing linear solver ($(method_linear))\n" - end - abstol = SC.parameters[:abstol] - reltol = SC.parameters[:reltol] - LP = SC.LP - if precon_linear !== nothing - linsolve = LinearSolve.init(LP, method_linear; Pl = precon_linear(linsolve.A), abstol = abstol, reltol = reltol) - else - linsolve = LinearSolve.init(LP, method_linear; abstol = abstol, reltol = reltol) - end - SC.linsolver = linsolve - end - - ## compute nonlinear residual - fill!(residual.entries, 0) - for j ∈ 1:length(b), k ∈ 1:length(b) - addblock_matmul!(residual[j], A[j, k], sol[unknowns[p][k]]) - end - residual.entries .-= b.entries - #res = A.entries * sol.entries - b.entries - for op in PD.operators - residual.entries[fixed_dofs(op)] .= 0 - end - for u_off in SC.parameters[:inactive] - j = get_unknown_id(SC, u_off) - if j > 0 - fill!(residual[j], 0) - end - end - nlres = norm(residual.entries) - @printf "\tres[%d] = %.2e" p nlres - end - time_final += time_assembly - allocs_final += allocs_assembly - - if nlres < nltol - converged[p] = true - else - converged[p] = false - end - - time_solve = @elapsed begin - allocs_solve = @allocated begin - if !SC.parameters[:constant_matrix] || !SC.parameters[:initialized] - linsolve.A = A.entries.cscmatrix - end - if !SC.parameters[:constant_rhs] || !SC.parameters[:initialized] - linsolve.b = b.entries - end - SC.parameters[:initialized] = true - - - ## solve - x = LinearSolve.solve!(linsolve) - - fill!(residual.entries, 0) - mul!(residual.entries, A.entries.cscmatrix, x.u) - residual.entries .-= b.entries - for op in PD.operators - for dof in fixed_dofs(op) - if dof <= length(residual.entries) - residual.entries[dof] = 0 - end - end - end - #@info residual.entries, norms(residual) - linres = norm(residual.entries) - offset = 0 - for u in unknowns[p] - ndofs_u = length(view(sol[u])) - if damping > 0 - view(sol[u]) .= damping * view(sol[u]) + (1 - damping) * view(x.u, offset+1:offset+ndofs_u) - else - view(sol[u]) .= view(x.u, offset+1:offset+ndofs_u) - end - offset += ndofs_u - end - end - end - time_total += time_solve - time_final += time_solve - allocs_final += allocs_solve - if SC.parameters[:verbosity] > -1 - @printf " (%.3e)" linres - end - end # nonlinear iterations subproblem - end - - if energy_integrator !== nothing - error = evaluate(energy_integrator, sol) - @printf " energy = %.3e" sum([sum(view(error, j, :)) for j ∈ 1:size(error, 1)]) - end - @printf "\n" - end - - return sol, it + # fetypes = $(["$(get_FEType(FES[j]))" for j = 1 : length(unknowns)]) + # ndofs = $([FES[j].ndofs for j = 1 : length(unknowns)]) + + As = [SC.A for SC in SCs] + bs = [SC.b for SC in SCs] + residuals = [SC.res for SC in SCs] + + ## unpack solver parameters + is_linear = zeros(Bool, nPDs) + + ## check if problems are (non)linear + nonlinear = zeros(Bool, nPDs) + for (j, PD) in enumerate(PDs) + for op in PD.operators + nl_dependencies = depends_nonlinearly_on(op) + for u in unknowns + if u in nl_dependencies + nonlinear[j] = true + break + end + end + end + if SCs[j].parameters[:verbosity] > 0 + @info "nonlinear = $(nonlinear[j] ? "true" : "false")\n" + end + if SCs[j].parameters[:is_linear] == "auto" + is_linear[j] = !nonlinear[j] + end + if is_linear[j] && nonlinear[j] + @warn "problem $(PD.name) seems nonlinear, but user set is_linear = true (results may be wrong)!!" + end + end + maxits = [is_linear[j] ? 1 : maxits[j] for j in 1:nPDs] + + alloc_factor = 1024^2 + + time_final = 0 + allocs_final = 0 + nlres = 1.1e30 + linres = 1.1e30 + converged = zeros(Bool, nPDs) + it::Int = 0 + while (it < maxsteps) && (any(converged .== false)) + it += 1 + @printf "%5d\t" it + copyto!(init.entries, sol.entries) + allocs_assembly = 0 + time_assembly = 0 + time_total = 0 + for p in 1:nPDs + b = bs[p] + A = As[p] + PD = PDs[p] + SC = SCs[p] + residual = residuals[p] + maxits = SC.parameters[:maxiterations] + nltol = SC.parameters[:target_residual] + damping = SC.parameters[:damping] + for j in 1:1 + time_total += @elapsed begin + + ## assemble operators + if !SC.parameters[:constant_rhs] + fill!(b.entries, 0) + end + if !SC.parameters[:constant_matrix] + fill!(A.entries.cscmatrix.nzval, 0) + end + if SC.parameters[:initialized] + time_assembly += @elapsed for op in PD.operators + allocs_assembly += @allocated assemble!(A, b, sol, op, SC; time = SC.parameters[:time], assemble_matrix = !SC.parameters[:constant_matrix], assemble_rhs = !SC.parameters[:constant_rhs], kwargs...) + end + else + time_assembly += @elapsed for op in PD.operators + allocs_assembly += @allocated assemble!(A, b, sol, op, SC; time = SC.parameters[:time], kwargs...) + end + end + flush!(A.entries) + + ## penalize fixed dofs + time_assembly += @elapsed for op in PD.operators + allocs_assembly += @allocated apply_penalties!(A, b, sol, op, SC; kwargs...) + end + flush!(A.entries) + + if SC.parameters[:verbosity] > 0 + @printf " assembly time | allocs = %.2f s | %.2f MiB\n" time allocs / alloc_factor + end + + ## show spy + if SC.parameters[:show_matrix] + @show A + elseif SC.parameters[:spy] + @info ".... spy plot of system matrix:\n$(UnicodePlots.spy(sparse(A.entries.cscmatrix)))" + end + + ## init solver + linsolve = SC.linsolver + if linsolve === nothing + method_linear = SC.parameters[:method_linear] + precon_linear = SC.parameters[:precon_linear] + if SC.parameters[:verbosity] > 0 + @info ".... initializing linear solver ($(method_linear))\n" + end + abstol = SC.parameters[:abstol] + reltol = SC.parameters[:reltol] + LP = SC.LP + if precon_linear !== nothing + linsolve = LinearSolve.init(LP, method_linear; Pl = precon_linear(linsolve.A), abstol = abstol, reltol = reltol) + else + linsolve = LinearSolve.init(LP, method_linear; abstol = abstol, reltol = reltol) + end + SC.linsolver = linsolve + end + + ## compute nonlinear residual + fill!(residual.entries, 0) + for j in 1:length(b), k in 1:length(b) + addblock_matmul!(residual[j], A[j, k], sol[unknowns[p][k]]) + end + residual.entries .-= b.entries + #res = A.entries * sol.entries - b.entries + for op in PD.operators + residual.entries[fixed_dofs(op)] .= 0 + end + for u_off in SC.parameters[:inactive] + j = get_unknown_id(SC, u_off) + if j > 0 + fill!(residual[j], 0) + end + end + nlres = norm(residual.entries) + @printf "\tres[%d] = %.2e" p nlres + end + time_final += time_assembly + allocs_final += allocs_assembly + + if nlres < nltol + converged[p] = true + else + converged[p] = false + end + + time_solve = @elapsed begin + allocs_solve = @allocated begin + if !SC.parameters[:constant_matrix] || !SC.parameters[:initialized] + linsolve.A = A.entries.cscmatrix + end + if !SC.parameters[:constant_rhs] || !SC.parameters[:initialized] + linsolve.b = b.entries + end + SC.parameters[:initialized] = true + + + ## solve + x = LinearSolve.solve!(linsolve) + + fill!(residual.entries, 0) + mul!(residual.entries, A.entries.cscmatrix, x.u) + residual.entries .-= b.entries + for op in PD.operators + for dof in fixed_dofs(op) + if dof <= length(residual.entries) + residual.entries[dof] = 0 + end + end + end + #@info residual.entries, norms(residual) + linres = norm(residual.entries) + offset = 0 + for u in unknowns[p] + ndofs_u = length(view(sol[u])) + if damping > 0 + view(sol[u]) .= damping * view(sol[u]) + (1 - damping) * view(x.u, (offset + 1):(offset + ndofs_u)) + else + view(sol[u]) .= view(x.u, (offset + 1):(offset + ndofs_u)) + end + offset += ndofs_u + end + end + end + time_total += time_solve + time_final += time_solve + allocs_final += allocs_solve + if SC.parameters[:verbosity] > -1 + @printf " (%.3e)" linres + end + end # nonlinear iterations subproblem + end + + if energy_integrator !== nothing + error = evaluate(energy_integrator, sol) + @printf " energy = %.3e" sum([sum(view(error, j, :)) for j in 1:size(error, 1)]) + end + @printf "\n" + end + + return sol, it end diff --git a/src/solvers_diffeq.jl b/src/solvers_diffeq.jl index df5cf2f..dd4345a 100644 --- a/src/solvers_diffeq.jl +++ b/src/solvers_diffeq.jl @@ -1,10 +1,10 @@ default_diffeq_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :sametol => (1e-15, "tolerance to identify two solution vectors to be identical (and to skip reassemblies called by DifferentialEquations.jl)"), - :constant_matrix => (false, "matrix is constant (skips reassembly and refactorization in solver)"), - :constant_rhs => (false, "right-hand side is constant (skips reassembly)"), - :init => (nothing, "initial solution (otherwise starts with a zero vector)"), - :initialized => (false, "linear system in solver configuration is already assembled (turns true after first assembly)"), - :verbosity => (0, "verbosity level"), + :sametol => (1.0e-15, "tolerance to identify two solution vectors to be identical (and to skip reassemblies called by DifferentialEquations.jl)"), + :constant_matrix => (false, "matrix is constant (skips reassembly and refactorization in solver)"), + :constant_rhs => (false, "right-hand side is constant (skips reassembly)"), + :init => (nothing, "initial solution (otherwise starts with a zero vector)"), + :initialized => (false, "linear system in solver configuration is already assembled (turns true after first assembly)"), + :verbosity => (0, "verbosity level"), ) """ diff --git a/src/tensors.jl b/src/tensors.jl index 767e1aa..57cdbb4 100644 --- a/src/tensors.jl +++ b/src/tensors.jl @@ -1,4 +1,3 @@ - """ TensorDescription{R,D} @@ -30,8 +29,8 @@ that will likely produce allocations and slow assembly times if used in a kernel function. """ function tensor_view(input, i::Int, ::TensorDescription{rank, dim}) where {rank, dim} - @warn "tensor_view for rank > 4 is a general implementation that needs allocations!" - return reshape(view(input, i:(i+(dim^rank)-1)), ntuple(i -> dim, rank)) + @warn "tensor_view for rank > 4 is a general implementation that needs allocations!" + return reshape(view(input, i:(i + (dim^rank) - 1)), ntuple(i -> dim, rank)) end @@ -95,8 +94,8 @@ function tensor_view(input,i::Int,::TensorDescription{0,dim}) Returns a view of `input[i]` reshaped as a vector of length 1. """ -function tensor_view(input, i::Int, ::TensorDescription{0, dim}) where dim - return view(input, i:i) +function tensor_view(input, i::Int, ::TensorDescription{0, dim}) where {dim} + return view(input, i:i) end @@ -107,8 +106,8 @@ function tensor_view(input,i::Int,::TensorDescription{1,dim}) Returns a view of `input[i:i+dim-1]` reshaped as a vector of length dim. """ -function tensor_view(input, i::Int, ::TensorDescription{1, dim}) where dim - return view(input, i:i+dim-1) +function tensor_view(input, i::Int, ::TensorDescription{1, dim}) where {dim} + return view(input, i:(i + dim - 1)) end """ @@ -118,8 +117,8 @@ function tensor_view(input,i::Int,::TensorDescription{2,dim}) Returns a view of `input[i:i+dim^2-1]` reshaped as a `(dim,dim)` matrix. """ -function tensor_view(input, i::Int, ::TensorDescription{2, dim}) where dim - return reshape(view(input, i:(i+(dim*dim)-1)), (dim, dim)) +function tensor_view(input, i::Int, ::TensorDescription{2, dim}) where {dim} + return reshape(view(input, i:(i + (dim * dim) - 1)), (dim, dim)) end """ @@ -129,8 +128,8 @@ function tensor_view(input,i::Int,::TensorDescription{3,dim}) Returns a view of `input[i:i+dim^3-1]` reshaped as a `(dim,dim,dim)` 3-tensor. """ -function tensor_view(input, i::Int, ::TensorDescription{3, dim}) where dim - return reshape(view(input, i:(i+(dim^3)-1)), (dim, dim, dim)) +function tensor_view(input, i::Int, ::TensorDescription{3, dim}) where {dim} + return reshape(view(input, i:(i + (dim^3) - 1)), (dim, dim, dim)) end """ @@ -140,6 +139,6 @@ function tensor_view(input,i::Int,::TensorDescription{4,dim}) Returns a view of `input[i:i+dim^4-1]` reshaped as `(dim,dim,dim,dim)` 4-tensor. """ -function tensor_view(input, i::Int, ::TensorDescription{4, dim}) where dim - return reshape(view(input, i:(i+(dim^4)-1)), (dim, dim, dim, dim)) +function tensor_view(input, i::Int, ::TensorDescription{4, dim}) where {dim} + return reshape(view(input, i:(i + (dim^4) - 1)), (dim, dim, dim, dim)) end diff --git a/src/unknowns.jl b/src/unknowns.jl index bb6eef9..5bdb404 100644 --- a/src/unknowns.jl +++ b/src/unknowns.jl @@ -8,26 +8,26 @@ Structure holding information for an unknown with the following fields: $(TYPEDFIELDS) """ mutable struct Unknown{IT} - """ - The name of the unknown used for printout messages. - """ - name::String - """ - The identifier of the unknown used for assignments to operators. - """ - identifier::IT - """ - Further properties of the unknown can be stored in a Dict, see constructor. - """ - parameters::Dict{Symbol, Any} + """ + The name of the unknown used for printout messages. + """ + name::String + """ + The identifier of the unknown used for assignments to operators. + """ + identifier::IT + """ + Further properties of the unknown can be stored in a Dict, see constructor. + """ + parameters::Dict{Symbol, Any} end default_unknown_kwargs() = Dict{Symbol, Tuple{Any, String}}( - :dimension => (nothing, "dimension of the unknown"), - :symbol_ansatz => (nothing, "symbol for ansatz functions of this unknown in printouts"), - :symbol_test => (nothing, "symbol for test functions of this unknown in printouts"), - :algebraic_constraint => (nothing, "is this unknown an algebraic constraint?"), + :dimension => (nothing, "dimension of the unknown"), + :symbol_ansatz => (nothing, "symbol for ansatz functions of this unknown in printouts"), + :symbol_test => (nothing, "symbol for test functions of this unknown in printouts"), + :algebraic_constraint => (nothing, "is this unknown an algebraic constraint?"), ) test_function(u::Unknown) = u.parameters[:symbol_test] === nothing ? "X($(u.identifier))" : u.parameters[:symbol_test] @@ -52,13 +52,13 @@ $(_myprint(default_unknown_kwargs())) """ function Unknown(u::String; identifier = Symbol(u), name = u, kwargs...) - parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_unknown_kwargs()) - _update_params!(parameters, kwargs) - return Unknown(name, identifier, parameters) + parameters = Dict{Symbol, Any}(k => v[1] for (k, v) in default_unknown_kwargs()) + _update_params!(parameters, kwargs) + return Unknown(name, identifier, parameters) end function Base.show(io::IO, u::Unknown) - print(io, "$(u.identifier) ($(u.name))") + return print(io, "$(u.identifier) ($(u.name))") end ## remapping of all function operators diff --git a/test/runtests.jl b/test/runtests.jl index 5d06f2e..5501038 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,76 +15,76 @@ include("test_timedependence.jl") include("test_nonlinear_operator.jl") function run_examples() - ExampleJuggler.verbose!(true) + ExampleJuggler.verbose!(true) - example_dir = joinpath(@__DIR__, "..", "examples") + example_dir = joinpath(@__DIR__, "..", "examples") - modules = [ - "Example103_BurgersEquation.jl", - "Example105_NonlinearPoissonEquation.jl", - "Example106_NonlinearDiffusion.jl", - "Example108_RobinBoundaryCondition.jl", - "Example201_PoissonProblem.jl", - "Example202_MixedPoissonProblem.jl", - "Example203_PoissonProblemDG.jl", - #"Example204_LaplaceEVProblem.jl", - "Example205_HeatEquation.jl", - "Example207_AdvectionUpwindDG.jl", - "Example210_LshapeAdaptivePoissonProblem.jl", - "Example211_LshapeAdaptiveEQPoissonProblem.jl", - "Example220_ReactionConvectionDiffusion.jl", - "Example225_ObstacleProblem.jl", - "Example226_Thermoforming.jl", - "Example230_NonlinearElasticity.jl", - "Example235_StokesIteratedPenalty.jl", - "Example240_SVRTEnrichment.jl", - "Example245_NSEFlowAroundCylinder.jl", - "Example250_NSELidDrivenCavity.jl", - "Example252_NSEPlanarLatticeFlow.jl", - "Example260_AxisymmetricNavierStokesProblem.jl", - "Example265_FlowTransport.jl", - "Example270_NaturalConvectionProblem.jl", - "Example275_OptimalControlStokes.jl", - "Example280_CompressibleStokes.jl", - #"Example284_LevelSetMethod.jl", - #"Example285_CahnHilliard.jl", - "Example290_PoroElasticity.jl", - "Example301_PoissonProblem.jl", - "Example310_DivFreeBasis.jl", - ] + modules = [ + "Example103_BurgersEquation.jl", + "Example105_NonlinearPoissonEquation.jl", + "Example106_NonlinearDiffusion.jl", + "Example108_RobinBoundaryCondition.jl", + "Example201_PoissonProblem.jl", + "Example202_MixedPoissonProblem.jl", + "Example203_PoissonProblemDG.jl", + #"Example204_LaplaceEVProblem.jl", + "Example205_HeatEquation.jl", + "Example207_AdvectionUpwindDG.jl", + "Example210_LshapeAdaptivePoissonProblem.jl", + "Example211_LshapeAdaptiveEQPoissonProblem.jl", + "Example220_ReactionConvectionDiffusion.jl", + "Example225_ObstacleProblem.jl", + "Example226_Thermoforming.jl", + "Example230_NonlinearElasticity.jl", + "Example235_StokesIteratedPenalty.jl", + "Example240_SVRTEnrichment.jl", + "Example245_NSEFlowAroundCylinder.jl", + "Example250_NSELidDrivenCavity.jl", + "Example252_NSEPlanarLatticeFlow.jl", + "Example260_AxisymmetricNavierStokesProblem.jl", + "Example265_FlowTransport.jl", + "Example270_NaturalConvectionProblem.jl", + "Example275_OptimalControlStokes.jl", + "Example280_CompressibleStokes.jl", + #"Example284_LevelSetMethod.jl", + #"Example285_CahnHilliard.jl", + "Example290_PoroElasticity.jl", + "Example301_PoissonProblem.jl", + "Example310_DivFreeBasis.jl", + ] - @testset "module examples" begin - @testmodules(example_dir, modules) - end + return @testset "module examples" begin + @testmodules(example_dir, modules) + end end function run_all_tests() - @testset "Aqua.jl" begin - Aqua.test_all( - ExtendableFEM; - ambiguities = false, - piracies = false, - ) - Aqua.test_ambiguities(ExtendableFEM) - end + @testset "Aqua.jl" begin + Aqua.test_all( + ExtendableFEM; + ambiguities = false, + piracies = false, + ) + Aqua.test_ambiguities(ExtendableFEM) + end - @testset "ExplicitImports" begin - @test ExplicitImports.check_no_implicit_imports(ExtendableFEM) === nothing - @test ExplicitImports.check_no_stale_explicit_imports(ExtendableFEM) === nothing - end + @testset "ExplicitImports" begin + @test ExplicitImports.check_no_implicit_imports(ExtendableFEM) === nothing + @test ExplicitImports.check_no_stale_explicit_imports(ExtendableFEM) === nothing + end - if isdefined(Docs, :undocumented_names) # >=1.11 - @testset "UndocumentedNames" begin - @test isempty(Docs.undocumented_names(ExtendableFEM)) - end - end + if isdefined(Docs, :undocumented_names) # >=1.11 + @testset "UndocumentedNames" begin + @test isempty(Docs.undocumented_names(ExtendableFEM)) + end + end - run_boundary_operator_tests() - run_dgblf_tests() - run_nonlinear_operator_tests() - run_itemintegrator_tests() - run_dt_tests() + run_boundary_operator_tests() + run_dgblf_tests() + run_nonlinear_operator_tests() + run_itemintegrator_tests() + return run_dt_tests() end run_all_tests() diff --git a/test/test_boundary_operator.jl b/test/test_boundary_operator.jl index 9049cf6..b926fe5 100644 --- a/test/test_boundary_operator.jl +++ b/test/test_boundary_operator.jl @@ -1,45 +1,46 @@ function run_boundary_operator_tests() - @testset "Boundary Operator" begin - println("\n") - println("==========================") - println("Testing Boundary Operator") - println("==========================") + return @testset "Boundary Operator" begin + println("\n") + println("==========================") + println("Testing Boundary Operator") + println("==========================") - for broken in [false, true] - TestInterpolateBoundary(H1P1{2}, 1, broken) - TestInterpolateBoundary(H1P2{2, 2}, 2, broken) - TestInterpolateBoundary(H1P3{2, 2}, 3, broken) - TestInterpolateBoundary(HDIVRT0{2}, 0, broken) - TestInterpolateBoundary(HDIVRT1{2}, 1, broken) - TestInterpolateBoundary(HDIVBDM1{2}, 1, broken) - TestInterpolateBoundary(HDIVBDM2{2}, 2, broken) - end - end + for broken in [false, true] + TestInterpolateBoundary(H1P1{2}, 1, broken) + TestInterpolateBoundary(H1P2{2, 2}, 2, broken) + TestInterpolateBoundary(H1P3{2, 2}, 3, broken) + TestInterpolateBoundary(HDIVRT0{2}, 0, broken) + TestInterpolateBoundary(HDIVRT1{2}, 1, broken) + TestInterpolateBoundary(HDIVBDM1{2}, 1, broken) + TestInterpolateBoundary(HDIVBDM2{2}, 2, broken) + end + end end function TestInterpolateBoundary(FEType, order = get_polynomialorder(FEType, Triangle2D), broken = false) - ## tests if boundary data for polynomial in ansatz space matches its interpolation - ## which should be reliable (since verified by tests in ExtendableFEMBase) - ncomponents = get_ncomponents(FEType) - function u!(result, qpinfo) - for j ∈ 1:ncomponents - result[j] = qpinfo.x[1]^order + j * qpinfo.x[2]^order - end - end + ## tests if boundary data for polynomial in ansatz space matches its interpolation + ## which should be reliable (since verified by tests in ExtendableFEMBase) + ncomponents = get_ncomponents(FEType) + function u!(result, qpinfo) + for j in 1:ncomponents + result[j] = qpinfo.x[1]^order + j * qpinfo.x[2]^order + end + return + end - xgrid = reference_domain(Triangle2D) #uniform_refine(grid_unitsquare(Triangle2D),0) - FES = FESpace{FEType}(xgrid; broken = broken) + xgrid = reference_domain(Triangle2D) #uniform_refine(grid_unitsquare(Triangle2D),0) + FES = FESpace{FEType}(xgrid; broken = broken) - ## first test: interpolate a smooth function from the discrete space and check if its interpolation has no jumps - uh = FEVector(FES) - interpolate!(uh[1], u!; bonus_quadorder = order) - uh2 = FEVector(FES) - boundary_operator = InterpolateBoundaryData(1, u!; bonus_quadorder = order, regions = 1:4) - assemble!(boundary_operator, FES) - boundary_dofs = fixed_dofs(boundary_operator) - apply!(uh2[1], boundary_operator) - error = norm(uh.entries[boundary_dofs] - uh2.entries[boundary_dofs]) - @info "Error for boundary operator for $FEType (broken = $broken) = $error" - @test error < 1e-15 + ## first test: interpolate a smooth function from the discrete space and check if its interpolation has no jumps + uh = FEVector(FES) + interpolate!(uh[1], u!; bonus_quadorder = order) + uh2 = FEVector(FES) + boundary_operator = InterpolateBoundaryData(1, u!; bonus_quadorder = order, regions = 1:4) + assemble!(boundary_operator, FES) + boundary_dofs = fixed_dofs(boundary_operator) + apply!(uh2[1], boundary_operator) + error = norm(uh.entries[boundary_dofs] - uh2.entries[boundary_dofs]) + @info "Error for boundary operator for $FEType (broken = $broken) = $error" + return @test error < 1.0e-15 end diff --git a/test/test_dgblf.jl b/test/test_dgblf.jl index 1a7f466..2db8eeb 100644 --- a/test/test_dgblf.jl +++ b/test/test_dgblf.jl @@ -1,91 +1,92 @@ function run_dgblf_tests() - @testset "BilinearOperatorDG" begin - println("\n") - println("==========================") - println("Testing BilinearOperatorDG") - println("==========================") - - @test TestParallelAssemblyDGBLF() < 5e-10 # windows/macOS-14 tests on github need greater tolerance - - for operator in [jump(grad(1)), jump(id(1))] - TestDGBLF(H1Pk{1, 2, 1}, 1, operator) - TestDGBLF(H1Pk{1, 2, 2}, 2, operator) - TestDGBLF(H1Pk{1, 2, 3}, 3, operator) - TestDGBLF(HDIVBDM1{2}, 1, operator) - TestDGBLF(HDIVRT1{2}, 1, operator) - TestDGBLF(HDIVBDM2{2}, 2, operator) - end - end + return @testset "BilinearOperatorDG" begin + println("\n") + println("==========================") + println("Testing BilinearOperatorDG") + println("==========================") + + @test TestParallelAssemblyDGBLF() < 5.0e-10 # windows/macOS-14 tests on github need greater tolerance + + for operator in [jump(grad(1)), jump(id(1))] + TestDGBLF(H1Pk{1, 2, 1}, 1, operator) + TestDGBLF(H1Pk{1, 2, 2}, 2, operator) + TestDGBLF(H1Pk{1, 2, 3}, 3, operator) + TestDGBLF(HDIVBDM1{2}, 1, operator) + TestDGBLF(HDIVRT1{2}, 1, operator) + TestDGBLF(HDIVBDM2{2}, 2, operator) + end + end end ## stab_kernel! function stab_kernel!(result, input, qpinfo) - result .= input / qpinfo.volume + return result .= input / qpinfo.volume end -function TestDGBLF(FEType = H1Pk{1, 2, 3}, order = get_polynomialorder(FEType, Triangle2D), operator = jump(grad(1)), tol = 1e-12) - ## tests if jumps of polynomial in ansatz space is zero - ncomponents = get_ncomponents(FEType) - function u!(result, qpinfo) - for j ∈ 1:ncomponents - result[j] = qpinfo.x[1]^order + j * qpinfo.x[2]^order - end - end - - xgrid = uniform_refine(grid_unitsquare(Triangle2D), 2) - FES = FESpace{FEType}(xgrid) - - ## first test: interpolate a smooth function from the discrete space and check if its interpolation has no jumps - uh = FEVector(FES) - interpolate!(uh[1], u!) - A = FEMatrix(FES, FES) - - dgblf = BilinearOperatorDG(stab_kernel!, [operator]; entities = ON_IFACES, quadorder = 2 * order, factor = 1e-2) - assemble!(A, dgblf) - - error = uh.entries' * A.entries * uh.entries - @info "DG jump test for FEType=$FEType with order = $order yields error = $error" - @test error < tol - - ## second test: solve best-approximatio problem for the same problem with jump penalization and see if the correct function is found - PD = ProblemDescription() - u = Unknown("u") - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator([id(u)])) - assign_operator!(PD, BilinearOperator(A, [u], [u])) - assign_operator!(PD, LinearOperator(u!, [id(u)]; bonus_quadorder = order)) - assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 1:4, bonus_quadorder = order)) - - uh = solve(PD, FES) - error = uh.entries' * A.entries * uh.entries - @info "DG jump test 2 for FEType=$FEType with order = $order yields error = $error" - @test error < tol - - - return error +function TestDGBLF(FEType = H1Pk{1, 2, 3}, order = get_polynomialorder(FEType, Triangle2D), operator = jump(grad(1)), tol = 1.0e-12) + ## tests if jumps of polynomial in ansatz space is zero + ncomponents = get_ncomponents(FEType) + function u!(result, qpinfo) + for j in 1:ncomponents + result[j] = qpinfo.x[1]^order + j * qpinfo.x[2]^order + end + return + end + + xgrid = uniform_refine(grid_unitsquare(Triangle2D), 2) + FES = FESpace{FEType}(xgrid) + + ## first test: interpolate a smooth function from the discrete space and check if its interpolation has no jumps + uh = FEVector(FES) + interpolate!(uh[1], u!) + A = FEMatrix(FES, FES) + + dgblf = BilinearOperatorDG(stab_kernel!, [operator]; entities = ON_IFACES, quadorder = 2 * order, factor = 1.0e-2) + assemble!(A, dgblf) + + error = uh.entries' * A.entries * uh.entries + @info "DG jump test for FEType=$FEType with order = $order yields error = $error" + @test error < tol + + ## second test: solve best-approximatio problem for the same problem with jump penalization and see if the correct function is found + PD = ProblemDescription() + u = Unknown("u") + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator([id(u)])) + assign_operator!(PD, BilinearOperator(A, [u], [u])) + assign_operator!(PD, LinearOperator(u!, [id(u)]; bonus_quadorder = order)) + assign_operator!(PD, InterpolateBoundaryData(u, u!; regions = 1:4, bonus_quadorder = order)) + + uh = solve(PD, FES) + error = uh.entries' * A.entries * uh.entries + @info "DG jump test 2 for FEType=$FEType with order = $order yields error = $error" + @test error < tol + + + return error end function TestParallelAssemblyDGBLF(FEType = H1Pk{1, 2, 3}, order = get_polynomialorder(FEType, Triangle2D), operator = jump(grad(1)), verbosity = 1) - dgblf_seq = BilinearOperatorDG(stab_kernel!, [operator]; entities = ON_IFACES, quadorder = 2 * order, factor = 1e-2, parallel = false, verbosity = verbosity) - dgblf_par = BilinearOperatorDG(stab_kernel!, [operator]; entities = ON_IFACES, quadorder = 2 * order, factor = 1e-2, parallel = true, verbosity = verbosity) - - ## sequential assembly - xgrid = uniform_refine(grid_unitsquare(Triangle2D), 4) - FES = FESpace{FEType}(xgrid) - Aseq = FEMatrix(FES, FES) - assemble!(Aseq, dgblf_seq) - - ## parallel assembly on same grid - xgrid = partition(xgrid, PlainMetisPartitioning(npart = 20), edges = true) - FES = FESpace{FEType}(xgrid) - Apar = FEMatrix(FES, FES; npartitions = num_partitions(xgrid)) - assemble!(Apar, dgblf_par) - - ## compare the two matrices - ## since partitioning changes dof enumeration only norms are compared - nor = abs(norm(Apar.entries.cscmatrix) - norm(Aseq.entries.cscmatrix)) - @info "difference between norms of sequantially and parallel assembled DG matrix = $nor" - return nor + dgblf_seq = BilinearOperatorDG(stab_kernel!, [operator]; entities = ON_IFACES, quadorder = 2 * order, factor = 1.0e-2, parallel = false, verbosity = verbosity) + dgblf_par = BilinearOperatorDG(stab_kernel!, [operator]; entities = ON_IFACES, quadorder = 2 * order, factor = 1.0e-2, parallel = true, verbosity = verbosity) + + ## sequential assembly + xgrid = uniform_refine(grid_unitsquare(Triangle2D), 4) + FES = FESpace{FEType}(xgrid) + Aseq = FEMatrix(FES, FES) + assemble!(Aseq, dgblf_seq) + + ## parallel assembly on same grid + xgrid = partition(xgrid, PlainMetisPartitioning(npart = 20), edges = true) + FES = FESpace{FEType}(xgrid) + Apar = FEMatrix(FES, FES; npartitions = num_partitions(xgrid)) + assemble!(Apar, dgblf_par) + + ## compare the two matrices + ## since partitioning changes dof enumeration only norms are compared + nor = abs(norm(Apar.entries.cscmatrix) - norm(Aseq.entries.cscmatrix)) + @info "difference between norms of sequantially and parallel assembled DG matrix = $nor" + return nor end diff --git a/test/test_itemintegrator.jl b/test/test_itemintegrator.jl index a184f24..0de2d62 100644 --- a/test/test_itemintegrator.jl +++ b/test/test_itemintegrator.jl @@ -1,33 +1,33 @@ function run_itemintegrator_tests() - @testset "ItemIntegrator" begin - println("\n") - println("======================") - println("Testing ItemIntegrator") - println("======================") + return @testset "ItemIntegrator" begin + println("\n") + println("======================") + println("Testing ItemIntegrator") + println("======================") - @test TestInterpolationErrorIntegration(3) < 1e-15 - end + @test TestInterpolationErrorIntegration(3) < 1.0e-15 + end end function TestInterpolationErrorIntegration(order = 3) - function u!(result, qpinfo) - x = qpinfo.x - result[1] = x[1]^2 + x[2] - end - function exact_error!(result, u, qpinfo) - u!(result, qpinfo) - result .-= u - result .= result .^ 2 - end - ErrorIntegrator = ItemIntegrator(exact_error!, [id(1)]; quadorder = 2 * order) + function u!(result, qpinfo) + x = qpinfo.x + return result[1] = x[1]^2 + x[2] + end + function exact_error!(result, u, qpinfo) + u!(result, qpinfo) + result .-= u + return result .= result .^ 2 + end + ErrorIntegrator = ItemIntegrator(exact_error!, [id(1)]; quadorder = 2 * order) - FEType = H1Pk{1, 2, order} - xgrid = grid_unitsquare(Triangle2D) - FES = FESpace{FEType}(xgrid) - uh = FEVector(FES) - interpolate!(uh[1], u!) + FEType = H1Pk{1, 2, order} + xgrid = grid_unitsquare(Triangle2D) + FES = FESpace{FEType}(xgrid) + uh = FEVector(FES) + interpolate!(uh[1], u!) - return maximum(evaluate(ErrorIntegrator, uh)[:]) + return maximum(evaluate(ErrorIntegrator, uh)[:]) end diff --git a/test/test_nonlinear_operator.jl b/test/test_nonlinear_operator.jl index 1105826..6e52220 100644 --- a/test/test_nonlinear_operator.jl +++ b/test/test_nonlinear_operator.jl @@ -1,82 +1,82 @@ function run_nonlinear_operator_tests() - @testset "Nonlinear Operator" begin - println("\n") - println("==========================") - println("Testing Nonlinear Operator") - println("==========================") + return @testset "Nonlinear Operator" begin + println("\n") + println("==========================") + println("Testing Nonlinear Operator") + println("==========================") - @test TestLinearNonlinearOperator() < 1e-14 - @test TestParallelAssemblyNonlinearOperator() < 2e-13 - end + @test TestLinearNonlinearOperator() < 1.0e-14 + @test TestParallelAssemblyNonlinearOperator() < 2.0e-13 + end end ## generate a nonlinear operator with a linear kernel ## and compare its jacobian with matrix from BilinearOperator ## for that same kernel, they should be identical function kernel!(result, u_ops, qpinfo) - u, ∇u, p = view(u_ops, 1:2), view(u_ops, 3:6), view(u_ops, 7) - μ = qpinfo.params[1] - α = qpinfo.params[2] - result[1] = ∇u[1] + α * u[1] - result[2] = ∇u[3] + α * u[2] - result[3] = μ * ∇u[1] - p[1] - result[4] = μ * ∇u[2] - result[5] = μ * ∇u[3] - result[6] = μ * ∇u[4] - p[1] - result[7] = -(∇u[1] + ∇u[4]) + u, ∇u, p = view(u_ops, 1:2), view(u_ops, 3:6), view(u_ops, 7) + μ = qpinfo.params[1] + α = qpinfo.params[2] + result[1] = ∇u[1] + α * u[1] + result[2] = ∇u[3] + α * u[2] + result[3] = μ * ∇u[1] - p[1] + result[4] = μ * ∇u[2] + result[5] = μ * ∇u[3] + result[6] = μ * ∇u[4] - p[1] + return result[7] = -(∇u[1] + ∇u[4]) end function TestLinearNonlinearOperator(; μ = 0.1, α = 2, sparse = true) - nlop = NonlinearOperator(kernel!, [id(1), grad(1), id(2)]; params = [μ, α], sparse_jacobians = sparse) - blop = BilinearOperator(kernel!, [id(1), grad(1), id(2)]; params = [μ, α], use_sparsity_pattern = sparse) + nlop = NonlinearOperator(kernel!, [id(1), grad(1), id(2)]; params = [μ, α], sparse_jacobians = sparse) + blop = BilinearOperator(kernel!, [id(1), grad(1), id(2)]; params = [μ, α], use_sparsity_pattern = sparse) - xgrid = uniform_refine(grid_unitsquare(Triangle2D), 2) - FES = [FESpace{H1P2{2, 2}}(xgrid), FESpace{H1P1{1}}(xgrid)] - u = FEVector(FES) - interpolate!(u[1], (result, qpinfo) -> (result[1] = qpinfo.x[1]^2; result[2] = sum(qpinfo.x))) - interpolate!(u[2], (result, qpinfo) -> (result[1] = qpinfo.x[2]^2)) - bnonlin = FEVector(FES) - Anonlin = FEMatrix(FES, FES) - Alin = FEMatrix(FES, FES) - assemble!(Anonlin, bnonlin, nlop, u) - assemble!(Alin, blop) + xgrid = uniform_refine(grid_unitsquare(Triangle2D), 2) + FES = [FESpace{H1P2{2, 2}}(xgrid), FESpace{H1P1{1}}(xgrid)] + u = FEVector(FES) + interpolate!(u[1], (result, qpinfo) -> (result[1] = qpinfo.x[1]^2; result[2] = sum(qpinfo.x))) + interpolate!(u[2], (result, qpinfo) -> (result[1] = qpinfo.x[2]^2)) + bnonlin = FEVector(FES) + Anonlin = FEMatrix(FES, FES) + Alin = FEMatrix(FES, FES) + assemble!(Anonlin, bnonlin, nlop, u) + assemble!(Alin, blop) - nor = norm(Anonlin.entries.cscmatrix - Alin.entries.cscmatrix) - @info "norm between jacobian of (linear) nonlinear operator and matrix from (same) bilinear operator = $nor" - return nor + nor = norm(Anonlin.entries.cscmatrix - Alin.entries.cscmatrix) + @info "norm between jacobian of (linear) nonlinear operator and matrix from (same) bilinear operator = $nor" + return nor end function TestParallelAssemblyNonlinearOperator(; μ = 0.1, α = 2, sparse = true, verbosity = 1) - nlop_seq = NonlinearOperator(kernel!, [id(1), grad(1), id(2)]; params = [μ, α], sparse_jacobians = sparse, parallel = false, verbosity = verbosity) - nlop_par = NonlinearOperator(kernel!, [id(1), grad(1), id(2)]; params = [μ, α], sparse_jacobians = sparse, parallel = true, verbosity = verbosity) + nlop_seq = NonlinearOperator(kernel!, [id(1), grad(1), id(2)]; params = [μ, α], sparse_jacobians = sparse, parallel = false, verbosity = verbosity) + nlop_par = NonlinearOperator(kernel!, [id(1), grad(1), id(2)]; params = [μ, α], sparse_jacobians = sparse, parallel = true, verbosity = verbosity) - ## sequential assembly - xgrid = uniform_refine(grid_unitsquare(Triangle2D), 4) - FES = [FESpace{H1P2{2, 2}}(xgrid), FESpace{H1P1{1}}(xgrid)] - u = FEVector(FES) - interpolate!(u[1], (result, qpinfo) -> (result[1] = qpinfo.x[1]^2; result[2] = sum(qpinfo.x))) - interpolate!(u[2], (result, qpinfo) -> (result[1] = qpinfo.x[2]^2)) - bseq = FEVector(FES) - Aseq = FEMatrix(FES, FES) - assemble!(Aseq, bseq, nlop_seq, u) + ## sequential assembly + xgrid = uniform_refine(grid_unitsquare(Triangle2D), 4) + FES = [FESpace{H1P2{2, 2}}(xgrid), FESpace{H1P1{1}}(xgrid)] + u = FEVector(FES) + interpolate!(u[1], (result, qpinfo) -> (result[1] = qpinfo.x[1]^2; result[2] = sum(qpinfo.x))) + interpolate!(u[2], (result, qpinfo) -> (result[1] = qpinfo.x[2]^2)) + bseq = FEVector(FES) + Aseq = FEMatrix(FES, FES) + assemble!(Aseq, bseq, nlop_seq, u) - ## parallel assembly on same grid - xgrid = partition(xgrid, PlainMetisPartitioning(npart = 20)) - FES = [FESpace{H1P2{2, 2}}(xgrid), FESpace{H1P1{1}}(xgrid)] - u = FEVector(FES) - interpolate!(u[1], (result, qpinfo) -> (result[1] = qpinfo.x[1]^2; result[2] = sum(qpinfo.x))) - interpolate!(u[2], (result, qpinfo) -> (result[1] = qpinfo.x[2]^2)) - bpar = FEVector(FES) - Apar = FEMatrix(FES, FES; npartitions = num_partitions(xgrid)) - assemble!(Apar, bpar, nlop_par, u) + ## parallel assembly on same grid + xgrid = partition(xgrid, PlainMetisPartitioning(npart = 20)) + FES = [FESpace{H1P2{2, 2}}(xgrid), FESpace{H1P1{1}}(xgrid)] + u = FEVector(FES) + interpolate!(u[1], (result, qpinfo) -> (result[1] = qpinfo.x[1]^2; result[2] = sum(qpinfo.x))) + interpolate!(u[2], (result, qpinfo) -> (result[1] = qpinfo.x[2]^2)) + bpar = FEVector(FES) + Apar = FEMatrix(FES, FES; npartitions = num_partitions(xgrid)) + assemble!(Apar, bpar, nlop_par, u) - ## compare the two matrices - ## since partitioning changes dof enumeration only norms are compared - nor = abs(norm(Apar.entries.cscmatrix) - norm(Aseq.entries.cscmatrix)) + abs(norm(bpar.entries) - norm(bseq.entries)) - @info "difference between norms of sequantially and parallel assembled jacobians and rhs = $nor" - return nor + ## compare the two matrices + ## since partitioning changes dof enumeration only norms are compared + nor = abs(norm(Apar.entries.cscmatrix) - norm(Aseq.entries.cscmatrix)) + abs(norm(bpar.entries) - norm(bseq.entries)) + @info "difference between norms of sequantially and parallel assembled jacobians and rhs = $nor" + return nor end diff --git a/test/test_timedependence.jl b/test/test_timedependence.jl index 1b14d0f..3c96e2f 100644 --- a/test/test_timedependence.jl +++ b/test/test_timedependence.jl @@ -1,72 +1,72 @@ function run_dt_tests() - @testset "TimeDependence" begin - println("\n") - println("=========================") - println("Testing Time-Dependencies") - println("=========================") + return @testset "TimeDependence" begin + println("\n") + println("=========================") + println("Testing Time-Dependencies") + println("=========================") - @test test_heatequation() < 1e-14 - end + @test test_heatequation() < 1.0e-14 + end end ## Tests heat equation problem with known exact solution ## that is quadratic in space and linear in time with f = 0 function test_heatequation(; nrefs = 2, T = 2.0, τ = 0.5, order = 2, kwargs...) - ## initial state u and exact data - function exact_u!(result, qpinfo) - x = qpinfo.x - t = qpinfo.time - result[1] = t + (x[1]^2 + x[2]^2) / 4 - end + ## initial state u and exact data + function exact_u!(result, qpinfo) + x = qpinfo.x + t = qpinfo.time + return result[1] = t + (x[1]^2 + x[2]^2) / 4 + end - ## kernel for exact error calculation - function exact_error!(result, u, qpinfo) - exact_u!(result, qpinfo) - result .-= u - result .= result .^ 2 - end + ## kernel for exact error calculation + function exact_error!(result, u, qpinfo) + exact_u!(result, qpinfo) + result .-= u + return result .= result .^ 2 + end - ## problem description - PD = ProblemDescription("Heat Equation") - u = Unknown("u"; name = "temperature") - assign_unknown!(PD, u) - assign_operator!(PD, BilinearOperator([grad(u)]; store = true, kwargs...)) - assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; regions = 1:4)) + ## problem description + PD = ProblemDescription("Heat Equation") + u = Unknown("u"; name = "temperature") + assign_unknown!(PD, u) + assign_operator!(PD, BilinearOperator([grad(u)]; store = true, kwargs...)) + assign_operator!(PD, InterpolateBoundaryData(u, exact_u!; regions = 1:4)) - ## grid - xgrid = uniform_refine(grid_unitsquare(Triangle2D; scale = [4, 4], shift = [-0.5, -0.5]), nrefs) + ## grid + xgrid = uniform_refine(grid_unitsquare(Triangle2D; scale = [4, 4], shift = [-0.5, -0.5]), nrefs) - ## prepare solution vector and initial data u0 - FES = FESpace{H1Pk{1, 2, order}}(xgrid) - sol = FEVector(FES; tags = PD.unknowns) - interpolate!(sol[u], exact_u!; bonus_quadorder = 2) - SC = SolverConfiguration(PD, [FES]; init = sol, constant_matrix = true, kwargs...) + ## prepare solution vector and initial data u0 + FES = FESpace{H1Pk{1, 2, order}}(xgrid) + sol = FEVector(FES; tags = PD.unknowns) + interpolate!(sol[u], exact_u!; bonus_quadorder = 2) + SC = SolverConfiguration(PD, [FES]; init = sol, constant_matrix = true, kwargs...) - ## compute initial error - ErrorIntegrator = ItemIntegrator(exact_error!, [id(u)]; quadorder = 2 * order, kwargs...) - error = evaluate(ErrorIntegrator, sol; time = 0) - @info "||u-u_h||(t = 0) = $(sqrt(sum(view(error, 1, :))))" + ## compute initial error + ErrorIntegrator = ItemIntegrator(exact_error!, [id(u)]; quadorder = 2 * order, kwargs...) + error = evaluate(ErrorIntegrator, sol; time = 0) + @info "||u-u_h||(t = 0) = $(sqrt(sum(view(error, 1, :))))" - ## generate mass matrix - M = FEMatrix(FES) - assemble!(M, BilinearOperator([id(1)])) + ## generate mass matrix + M = FEMatrix(FES) + assemble!(M, BilinearOperator([id(1)])) - ## add backward Euler time derivative - assign_operator!(PD, BilinearOperator(M, [u]; factor = 1 / τ, kwargs...)) - assign_operator!(PD, LinearOperator(M, [u], [u]; factor = 1 / τ, kwargs...)) + ## add backward Euler time derivative + assign_operator!(PD, BilinearOperator(M, [u]; factor = 1 / τ, kwargs...)) + assign_operator!(PD, LinearOperator(M, [u], [u]; factor = 1 / τ, kwargs...)) - ## iterate tspan - t = 0 - for it ∈ 1:Int(floor(T / τ)) - t += τ - ExtendableFEM.solve(PD, [FES], SC; time = t) - end + ## iterate tspan + t = 0 + for it in 1:Int(floor(T / τ)) + t += τ + ExtendableFEM.solve(PD, [FES], SC; time = t) + end - ## compute error - ErrorIntegrator = ItemIntegrator(exact_error!, [id(u)]; quadorder = 0, kwargs...) - error = sqrt(sum(evaluate(ErrorIntegrator, sol; time = T))) - @info "error heat equation test = $error" - return error + ## compute error + ErrorIntegrator = ItemIntegrator(exact_error!, [id(u)]; quadorder = 0, kwargs...) + error = sqrt(sum(evaluate(ErrorIntegrator, sol; time = T))) + @info "error heat equation test = $error" + return error end