diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..d60f0707 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "monthly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f316fd1c..c015fee3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,31 +8,22 @@ jobs: fail-fast: false matrix: version: - - '1.8' + - '1.10' os: - ubuntu-latest arch: - x64 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test- - ${{ runner.os }}- + - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v5 with: file: lcov.info drivers: @@ -42,27 +33,18 @@ jobs: fail-fast: false matrix: version: - - '1.8' + - '1.10' os: - ubuntu-latest arch: - x64 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test- - ${{ runner.os }}- + - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - run: | julia --color=yes --project=. --check-bounds=yes --depwarn=error -e ' @@ -77,27 +59,18 @@ jobs: fail-fast: false matrix: version: - - '1.8' + - '1.10' os: - ubuntu-latest arch: - x64 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test- - ${{ runner.os }}- + - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - run: | julia --color=yes --project=. --check-bounds=yes --depwarn=error -e ' @@ -109,10 +82,10 @@ jobs: name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: - version: '1.8' + version: '1.10' - run: | julia --project=docs -e ' using Pkg diff --git a/.github/workflows/ci_x86.yml b/.github/workflows/ci_x86.yml index 03ca2ab1..c3b5744c 100644 --- a/.github/workflows/ci_x86.yml +++ b/.github/workflows/ci_x86.yml @@ -14,26 +14,17 @@ jobs: fail-fast: false matrix: version: - - '1.8' + - '1.10' os: - ubuntu-latest arch: - x86 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test- - ${{ runner.os }}- + - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 diff --git a/.gitignore b/.gitignore index 97dd38a8..1c9b9a66 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ /docs/build/ /docs/site/ Manifest.toml +LocalPreferences.toml .vscode/ *.swp diff --git a/NEWS.md b/NEWS.md index 53030e64..72fd3937 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,12 +4,44 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.9.8] - 2025-7-10 + +### Added + +- Added missing update_trian! method for MultiField. Since PR[#112](https://github.com/gridap/GridapEmbedded.jl/pull/112). + +### Fixed + +- Fixed failed precompilation due to Gridap v0.19.2. Since PR[#112](https://github.com/gridap/GridapEmbedded.jl/pull/112). + +## [0.9.7] - 2025-6-11 + +### Added + +- Added support for Gridap v0.19. Since PR[#110](https://github.com/gridap/GridapEmbedded.jl/pull/110). + +## [0.9.6] - 2025-04-19 + +### Added + +- Added support for distributed level-set geometries. Since PR[#99](https://github.com/gridap/GridapEmbedded.jl/pull/99). +- Refactored the distributed code to allow for ghosted/unghosted geometries and triangulations. Since PR[#100](https://github.com/gridap/GridapEmbedded.jl/pull/100). +- Implemented geometrical derivatives. Since PR[#109](https://github.com/gridap/GridapEmbedded.jl/pull/109). +- Added a proper documentation. Since PR[#109](https://github.com/gridap/GridapEmbedded.jl/pull/109). + +### Changed + +- Swapped `LightGraphs.jl` dependency to `Graphs.jl`, due to the former being deprecated. Since PR[#108](https://github.com/gridap/GridapEmbedded.jl/pull/108). + +## [0.9.5] - 2024-10-18 ### Added - Adding `compute_redistribute_weights` and `compute_adaptive_flags` functions for load balancing and adaptive mesh refinement, respectively. Since PR [#95](https://github.com/gridap/GridapEmbedded.jl/pull/95). +### Changed + +- Updated to Algoim v0.2.2, which runs on Julia 1.11. Since PR [#97](https://github.com/gridap/GridapEmbedded.jl/pull/97). ## [0.9.4] - 2024-07-09 diff --git a/Project.toml b/Project.toml index 5ccc3783..b5e91a35 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "GridapEmbedded" uuid = "8838a6a3-0006-4405-b874-385995508d5d" authors = ["Francesc Verdugo ", "Eric Neiva ", "Pere Antoni Martorell ", "Santiago Badia "] -version = "0.9.4" +version = "0.9.8" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" @@ -9,9 +9,10 @@ Algoim = "0eb9048c-21de-4c7a-bfac-056de1940b74" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" CxxWrap = "1f15a43c-97ca-5a2a-ae31-89f07a497df4" FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" Gridap = "56d4f2e9-7ea1-5844-9cf6-b9c51ca7ce8e" GridapDistributed = "f9701e48-63b3-45aa-9a63-9bc6c271f355" -LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195" MiniQhull = "978d7f02-9e05-4691-894f-ae31a51d76ca" @@ -22,20 +23,24 @@ algoimWrapper_jll = "3c43aa7b-5398-51f3-8d75-8f051e6faa4d" [compat] AbstractTrees = "0.3.3, 0.4" -Algoim = "0.2" +Algoim = "0.2.2" Combinatorics = "1" -CxxWrap = "0.14" +CxxWrap = "0.16" FillArrays = "0.10, 0.11, 0.12, 0.13, 1" -Gridap = "0.17, 0.18" +FiniteDiff = "2.27.0" +ForwardDiff = "0.10.38, 1" +Graphs = "1.12.0" +Gridap = "0.18.12, 0.19" GridapDistributed = "0.3, 0.4" -LightGraphs = "1.3.3" MPI = "0.20" MiniQhull = "0.1.0, 0.2, 0.3, 0.4" PartitionedArrays = "0.3.4" julia = "1.3" [extras] +FiniteDiff = "6a86dc24-6348-571c-b903-95158fe2bd41" +MPIPreferences = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Test", "FiniteDiff"] diff --git a/README.md b/README.md index 77d0b637..d7b3c765 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Embedded finite element methods, level set surface descriptions and constructive solid geometry. +[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://gridap.github.io/GridapEmbedded.jl/stable) [![Build Status](https://github.com/gridap/GridapEmbedded.jl/workflows/CI/badge.svg?branch=master)](https://github.com/gridap/GridapEmbedded.jl/actions?query=workflow%3ACI) [![Codecov](https://codecov.io/gh/gridap/GridapEmbedded.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/gridap/GridapEmbedded.jl) @@ -40,4 +41,3 @@ julia> BimaterialLinElastCutFEM.main(n=4,outputfile="results3") ``` - diff --git a/bulk_ghost_penalty_canvas.jl b/bulk_ghost_penalty_canvas.jl index 3ef8553a..b2dd0659 100644 --- a/bulk_ghost_penalty_canvas.jl +++ b/bulk_ghost_penalty_canvas.jl @@ -1,595 +1,656 @@ using Gridap using GridapEmbedded -using FillArrays -using LinearAlgebra -include("BulkGhostPenaltyAssembleMaps.jl") +include("./src/BGP/BGP.jl") + +# Problem selection +problem = 1 # 0 = Manufactured solution (L2-like projection), 1 = Darcy problem # Manufactured solution -order = 1 -uex(x) = VectorValue(x[1],x[2]) -pex(x) = x[1]^order + x[2]^order +order = 0 +uex(x) = -VectorValue(2*x[1],2*x[2]) +pex(x) = (x[1]^2 + x[2]^2) +divuex(x) = -4.0 # Select geometry -R = 0.2 -geom = disk(R, x0=Point(0.5,0.5)) - -# Setup background model -n=20 -partition = (n,n) -box = get_metadata(geom) -bgmodel = CartesianDiscreteModel((0,1,0,1),partition) -dp = box.pmax - box.pmin -h = dp[1]/n - -# Cut the background model with the mesh -cutdisk = cut(bgmodel,geom) - -# Compute mapping among background model -# cut cells and interior cells -strategy = AggregateAllCutCells() -aggregates = aggregate(strategy,cutdisk) - -""" - Creates an array of arrays with as many entries - as aggregates. For each aggregate, the array - contains the global cell IDs of that cells in the background - model that belong to the same aggregate - - TO-DO: with efficiency in mind we may want to store this - array of arrays as a Gridap.Arrays.Table. -""" -function setup_aggregate_to_cells(aggregates) - - size_aggregates=Dict{Int,Int}() - for (i,agg) in enumerate(aggregates) - if agg>0 - if !haskey(size_aggregates,agg) - size_aggregates[agg]=1 - else - size_aggregates[agg]+=1 - end - end - end - - touched=Dict{Int,Int}() - aggregate_to_cells=Vector{Vector{Int}}() - current_aggregate=1 - for (i,agg) in enumerate(aggregates) - if agg>0 - if (size_aggregates[agg]>1) - if !haskey(touched,agg) - push!(aggregate_to_cells,[i]) - touched[agg]=current_aggregate - current_aggregate+=1 - else - push!(aggregate_to_cells[touched[agg]],i) - end - end - end - end - aggregate_to_cells -end - -function setup_aggregates_bounding_box_model(bgmodel, aggregate_to_cells) - g=get_grid(bgmodel) - cell_coords=get_cell_coordinates(g) - D=num_dims(bgmodel) - xmin=Vector{Float64}(undef,D) - xmax=Vector{Float64}(undef,D) - - # Compute coordinates of the nodes defining the bounding boxes - bounding_box_node_coords= - Vector{Point{D,Float64}}(undef,length(aggregate_to_cells)*2^D) - ptr = [ (((i-1)*2^D)+1) for i in 1:length(aggregate_to_cells)+1 ] - data = collect(1:length(bounding_box_node_coords)) - bounding_box_node_ids = Gridap.Arrays.Table(data,ptr) - for (agg,cells) in enumerate(aggregate_to_cells) - p=first(cell_coords[cells[1]]) - for i in 1:D - xmin[i]=p[i] - xmax[i]=p[i] - end - for cell in cells - for p in cell_coords[cell] - for i in 1:D - xmin[i]=min(xmin[i],p[i]) - xmax[i]=max(xmax[i],p[i]) - end - end - end - bounds = [(xmin[i], xmax[i]) for i in 1:D] - point_iterator = Iterators.product(bounds...) - bounding_box_node_coords[bounding_box_node_ids[agg]] = - reshape([Point(p...) for p in point_iterator],2^D) - end - - # Set up the discrete model of bounding boxes - HEX_AXIS=1 - polytope=Polytope(Fill(HEX_AXIS,D)...) - scalar_reffe=ReferenceFE(polytope,lagrangian,Float64,1) - cell_types=fill(1,length(bounding_box_node_ids)) - cell_reffes=[scalar_reffe] - grid = Gridap.Geometry.UnstructuredGrid(bounding_box_node_coords, - bounding_box_node_ids, - cell_reffes, - cell_types, - Gridap.Geometry.Oriented()) - Gridap.Geometry.UnstructuredDiscreteModel(grid) -end +nint = 3 # number of (uncut) interior elements along single direction +# cut length +ε = 0.2/2.0 # not so small cut +# ε = 0.2e-2/2.0 # smaller cut +# ε = 0.2e-6/2.0 # smallest cut +pmin = Point(0.0,0.0) +pmax = Point(1.0,1.0) +function setup_geometry(nint, ε, pmin, pmax) + nbg = nint + 2 + 2 # number of elements in the background mesh (2 from cut, 2 dummy) + dp = pmax - pmin + h = dp[1]/nint + bgpmin = pmin - Point(2*h,2*h) + bgpmax = pmax + Point(2*h,2*h) + bgdp = bgpmax - bgpmin + partition = (nbg,nbg) + bgmodel = CartesianDiscreteModel(bgpmin,bgpmax,partition) + hbg = bgdp[1]/nbg + @assert abs(hbg - h) < 10e-10 + @assert abs(ε/h) < 0.5 + + # The following is based on square (part of GridapEmbedded): + e1 = VectorValue(1,0) + e2 = VectorValue(0,1) + x0 = pmin + Point(0.5*dp[1],0.5*dp[2]) + L1 = dp[1] + 2*ε + L2 = dp[2] + 2*ε + plane1 = plane(x0=x0-0.5*L2*e2,v=-e2,name="bottom") + plane2 = plane(x0=x0+0.5*L1*e1,v= e1,name="right") + plane3 = plane(x0=x0+0.5*L2*e2,v= e2,name="top") + plane4 = plane(x0=x0-0.5*L1*e1,v=-e1,name="left") + + geo12 = intersect(plane1,plane2) + geo34 = intersect(plane3,plane4) + + square = intersect(geo12,geo34) + cutgeo = cut(bgmodel, square) + + bgmodel, cutgeo, h +end +bgmodel, cutgeo, h= setup_geometry(nint, ε, pmin, pmax) +# Setup aggregates +strategy = AggregateAllCutCells() +aggregates= aggregate(strategy,cutgeo) aggregate_to_cells=setup_aggregate_to_cells(aggregates) aggregates_bounding_box_model= setup_aggregates_bounding_box_model(bgmodel,aggregate_to_cells) -colors = color_aggregates(aggregates,bgmodel) -writevtk(Triangulation(bgmodel),"trian",celldata=["cellin"=>aggregates,"color"=>colors]) -writevtk(aggregates_bounding_box_model, "bb_model") -writevtk(bgmodel, "bg_model") - -""" - Changes the domain of a trial/test basis defined on - the reference space of bounding boxes to the reference - space of the agg cells - - TO-DO: in the future, for system of PDEs (MultiField) we should - also take care of blocks (BlockMap) -""" -function change_domain_bb_to_agg_cells(basis_bb, - ref_agg_cell_to_ref_bb_map, - Ωagg_cells, - agg_cells_to_aggregate) - @assert num_cells(Ωagg_cells)==length(ref_agg_cell_to_ref_bb_map) - @assert Gridap.CellData.DomainStyle(basis_bb)==ReferenceDomain() - bb_basis_style = Gridap.FESpaces.BasisStyle(basis_bb) - bb_basis_array = Gridap.CellData.get_data(basis_bb) - if (bb_basis_style==Gridap.FESpaces.TrialBasis()) - # Remove transpose map; we will add it later - @assert isa(bb_basis_array,Gridap.Arrays.LazyArray) - @assert isa(bb_basis_array.maps,Fill) - @assert isa(bb_basis_array.maps.value,typeof(transpose)) - bb_basis_array=bb_basis_array.args[1] - end - - bb_basis_array_to_Ωagg_cells_array = lazy_map(Reindex(bb_basis_array),agg_cells_to_aggregate) - bb_basis_array_to_Ωagg_cells_array = lazy_map(Broadcasting(∘), - bb_basis_array_to_Ωagg_cells_array, - ref_agg_cell_to_ref_bb_map) - if (bb_basis_style==Gridap.FESpaces.TrialBasis()) - # Add transpose - bb_basis_array_to_Ωagg_cells_array=lazy_map(transpose, bb_basis_array_to_Ωagg_cells_array) - end - - Gridap.CellData.GenericCellField(bb_basis_array_to_Ωagg_cells_array, - Ωagg_cells, - ReferenceDomain()) +# Triangulations +Ωbg = Triangulation(bgmodel) +Ωact = Triangulation(cutgeo,ACTIVE) + +# Physical domain +Ω = Triangulation(cutgeo,PHYSICAL) +degree=2*2*(order+1) +dΩ = Measure(Ω,degree) + +# Setup agg cells and triangulation +agg_cells =flatten(aggregate_to_cells) #[CLEAN]: replaced setup_agg_cells +Ωbg_agg_cells=view(Ωbg,agg_cells) + +# Pressure space +if problem==0 + Q = FESpace(Ωact, ReferenceFE(lagrangian,Float64,order), conformity=:L2) +elseif problem==1 + # Set up zero-mean pressure space, with fixed interior dof + Qnzm = FESpace(Ωact, ReferenceFE(lagrangian,Float64,order), conformity=:L2) + int_cells = restrict_cells(cutgeo,IN) # global (background mesh') cell identifiers of interior cells + nonagg_int_cell = int_cells[findfirst(!in(agg_cells),int_cells)] # cell identifier (background mesh) of first interior cell not in aggregate + local_id = findfirst(isequal(nonagg_int_cell),Ωact.tface_to_mface) # local (active mesh') cell id of interior cell not in aggregate + dof_to_fix = get_cell_dof_ids(Qnzm)[local_id][1] + spaceWithConstantFixed = Gridap.FESpaces.FESpaceWithConstantFixed(Qnzm,true,Int64(dof_to_fix)) + Qzm_vol_i = assemble_vector(v->∫(v)*dΩ,Qnzm) + Qzm_vol = sum(Qzm_vol_i) + Q = Gridap.FESpaces.ZeroMeanFESpace(spaceWithConstantFixed,Qzm_vol_i,Qzm_vol) end - -# Set up objects required to compute both LHS and RHS of the L2 projection - # Set up global spaces -Ωhact = Triangulation(cutdisk,ACTIVE) - -V = FESpace(Ωhact, ReferenceFE(raviart_thomas,Float64,order),conformity=:HDiv) -Q = FESpace(Ωhact, ReferenceFE(lagrangian,Float64,order), conformity=:L2) +V = FESpace(Ωact, ReferenceFE(raviart_thomas,Float64,order),conformity=:HDiv) U = TrialFESpace(V) P = TrialFESpace(Q) Y = MultiFieldFESpace([V, Q]) X = MultiFieldFESpace([U, P]) - -# Generate an array with the global IDs of the cells -# that belong to an aggregrate. From now on, we will -# use the terminology "agg_cells" to refer to those -# cells of the background model that belong to an aggregate -# (i.e., they can be either cut or interior cells) -agg_cells=Vector{Int}() -for cells in aggregate_to_cells - append!(agg_cells,cells) -end +dx = get_trial_fe_basis(X) +dy = get_fe_basis(Y) +du,dp = dx +dv,dq = dy # ref_agg_cell_to_agg_cell_map: \hat{K} -> K -Ω=Triangulation(bgmodel) -Ωagg_cells=view(Ω,agg_cells) -ref_agg_cell_to_agg_cell_map=get_cell_map(Ωagg_cells) - -# Generate an array that given the local ID of an "agg_cell" -# returns the ID of the aggregate to which it belongs -# (i.e., flattened version of aggregate_to_cells) -agg_cells_to_aggregate=Vector{Int}() -for (i,cells) in enumerate(aggregate_to_cells) - for _ in cells - push!(agg_cells_to_aggregate,i) - end -end - -# ref_agg_cell_to_ref_bb_map: \hat{K} -> K -> bb -> \hat{bb} -bb_to_ref_bb=lazy_map(Gridap.Fields.inverse_map,get_cell_map(aggregates_bounding_box_model)) -bb_to_ref_bb_agg_cells=lazy_map(Reindex(bb_to_ref_bb),agg_cells_to_aggregate) -ref_agg_cell_to_ref_bb_map= - lazy_map(Broadcasting(∘),bb_to_ref_bb_agg_cells,ref_agg_cell_to_agg_cell_map) - -# Compute LHS of L2 projection -degree=2*(order+1) -dΩagg_cells = Measure(Ωagg_cells,degree) -reffe =ReferenceFE(lagrangian,Float64,order) # Here we MUST use a Q space (not a P space!) -Qbb=FESpace(aggregates_bounding_box_model,reffe,conformity=:L2) # We need a DG space to represent the L2 projection +ref_agg_cell_to_agg_cell_map=get_cell_map(Ωbg_agg_cells) +agg_cells_to_aggregate =setup_cells_to_aggregate(aggregate_to_cells) +ref_agg_cell_to_ref_bb_map =setup_ref_agg_cell_to_ref_bb_map(aggregates_bounding_box_model, + agg_cells_to_aggregate,ref_agg_cell_to_agg_cell_map) + +# Spaces on bounding boxes +reffeₚ_bb =ReferenceFE(lagrangian,Float64,order) +Qbb=FESpace(aggregates_bounding_box_model,reffeₚ_bb,conformity=:L2) # We need a DG space to represent the L2 projection Pbb=TrialFESpace(Qbb) pbb=get_trial_fe_basis(Pbb) qbb=get_fe_basis(Qbb) - -reffe=ReferenceFE(raviart_thomas,Float64,order) -Vbb=FESpace(aggregates_bounding_box_model,reffe,conformity=:L2) +reffeᵤ_bb=ReferenceFE(raviart_thomas,Float64,order) +Vbb=FESpace(aggregates_bounding_box_model,reffeᵤ_bb,conformity=:L2) Ubb=TrialFESpace(Vbb) ubb=get_trial_fe_basis(Ubb) vbb=get_fe_basis(Vbb) +# Numerical integration (Measures) +dΩbg_agg_cells = Measure(Ωbg_agg_cells,degree) -aggregate_to_local_cells=copy(aggregate_to_cells) -current_local_cell=1 -for (i,cells) in enumerate(aggregate_to_local_cells) - for j in 1:length(cells) - cells[j]=current_local_cell - current_local_cell+=1 - end -end - -function set_up_bulk_ghost_penalty_lhs(aggregates_bounding_box_model, - agg_cells_to_aggregate, - ref_agg_cell_to_ref_bb_map, - dΩagg_cells, - ubb, - vbb) - Ωagg_cells=dΩagg_cells.quad.trian - - # Change domain of vbb (test) from Ωbb to Ωagg_cells - vbb_Ωagg_cells=change_domain_bb_to_agg_cells(vbb, - ref_agg_cell_to_ref_bb_map, - Ωagg_cells, - agg_cells_to_aggregate) - - # Change domain of ubb (trial) from Ωbb to Ωagg_cells - ubb_Ωagg_cells=change_domain_bb_to_agg_cells(ubb, - ref_agg_cell_to_ref_bb_map, - Ωagg_cells, - agg_cells_to_aggregate) - - # Compute contributions to LHS of L2 projection - agg_cells_to_lhs_contribs=get_array(∫(vbb_Ωagg_cells⋅ubb_Ωagg_cells)dΩagg_cells) - - # Finally assemble LHS contributions - ass_lhs_map=BulkGhostPenaltyAssembleLhsMap(agg_cells_to_lhs_contribs) - lazy_map(ass_lhs_map,aggregate_to_local_cells) -end - -p_lhs=set_up_bulk_ghost_penalty_lhs(aggregates_bounding_box_model, - agg_cells_to_aggregate, - ref_agg_cell_to_ref_bb_map, - dΩagg_cells, - pbb, - qbb) - -u_lhs=set_up_bulk_ghost_penalty_lhs(aggregates_bounding_box_model, - agg_cells_to_aggregate, - ref_agg_cell_to_ref_bb_map, - dΩagg_cells, - ubb, - vbb) - -# Compute contributions to the RHS of the L2 projection -dx = get_trial_fe_basis(X) -dy = get_fe_basis(Y) -du,dp = dx -dv,dq = dy - -function _restrict_to_block(cell_dof_ids::Gridap.Arrays.LazyArray{<:Fill{<:Gridap.Fields.BlockMap}}, blockid) - map=cell_dof_ids.maps.value - @assert length(map.size)==1 - @assert blockid >= 1 - @assert blockid <= map.size[1] - cell_dof_ids.args[blockid] -end - -Ωagg_cell_dof_ids = get_cell_dof_ids(X,Ωagg_cells) -U_Ωagg_cell_dof_ids = _restrict_to_block(Ωagg_cell_dof_ids, 1) -P_Ωagg_cell_dof_ids = _restrict_to_block(Ωagg_cell_dof_ids, 2) - - -### BEGIN TESTING CODE -# This code is just for testing purposes, so I have commented it out -# It allows to evaluate the LHS of the L2 projection corresponding to a -# particular FE function, instead of a basis -# uhex=interpolate(uex,Ustd) -# agg_cells_lhs_contribs_uhex=get_array(∫(vbb_Ωagg_cells*uhex)dΩagg_cells) -# ass_lhs_map_uhex=AssembleLhsMap(agg_cells_lhs_contribs_uhex) -# lhs_uhex=lazy_map(ass_lhs_map_uhex,aggregate_to_local_cells) -### END TESTING CODE - -function compute_agg_cells_local_dof_ids(agg_cells_dof_ids, aggregate_to_agg_cells) - agg_cells_local_dof_ids=copy(agg_cells_dof_ids) - current_cell=1 - for agg_cells in aggregate_to_agg_cells - g2l=Dict{Int32,Int32}() - current_local_dof=1 - for (i,_) in enumerate(agg_cells) - current_cell_dof_ids=agg_cells_dof_ids[current_cell] - for (j, dof) in enumerate(current_cell_dof_ids) - if !(dof in keys(g2l)) - g2l[dof]=current_local_dof - agg_cells_local_dof_ids[current_cell][j]=current_local_dof - current_local_dof+=1 - else - agg_cells_local_dof_ids[current_cell][j]=g2l[dof] - end - end - current_cell+=1 - println(agg_cells_local_dof_ids) - end - end - agg_cells_local_dof_ids -end +# Selecting relevant global dofs ids of aggregate cells (from background mesh) +Ωbg_agg_cell_dof_ids = get_cell_dof_ids(X,Ωbg_agg_cells) +U_Ωbg_agg_cell_dof_ids = _restrict_to_block(Ωbg_agg_cell_dof_ids, 1) +P_Ωbg_agg_cell_dof_ids = _restrict_to_block(Ωbg_agg_cell_dof_ids, 2) +# Computing local (per aggregate) dof ids +aggregate_to_local_cells=setup_aggregate_to_local_cells(aggregate_to_cells) U_agg_cells_local_dof_ids= - compute_agg_cells_local_dof_ids(U_Ωagg_cell_dof_ids, aggregate_to_local_cells) + compute_agg_cells_local_dof_ids(U_Ωbg_agg_cell_dof_ids, aggregate_to_local_cells) P_agg_cells_local_dof_ids= - compute_agg_cells_local_dof_ids(P_Ωagg_cell_dof_ids, aggregate_to_local_cells) - - -function set_up_h_U(aggregates_bounding_box_model, - agg_cells_to_aggregate, - Ωagg_cells) - degree = 0 # We are integrating a constant function - # Thus, degree=0 is enough for exact integration - Ωbb = Triangulation(aggregates_bounding_box_model) - dΩbb = Measure(Ωbb, degree) - h_U_array = get_array(∫(1.0)dΩbb) - h_U_array = lazy_map(Reindex(h_U_array), agg_cells_to_aggregate) - CellField(h_U_array, Ωagg_cells) -end - - -function compute_aggregate_dof_ids(agg_cells_dof_ids, aggregate_to_agg_cells) - aggregate_dof_ids=Vector{Vector{Int}}(undef, length(aggregate_to_agg_cells)) - current_aggregate=1 - current_cell=1 - for agg_cells in aggregate_to_agg_cells - current_aggregate_dof_ids=Int[] - for (i,_) in enumerate(agg_cells) - current_cell_dof_ids=agg_cells_dof_ids[current_cell] - for (j, dof) in enumerate(current_cell_dof_ids) - if !(dof in current_aggregate_dof_ids) - push!(current_aggregate_dof_ids, dof) - end - end - current_cell+=1 - end - aggregate_dof_ids[current_aggregate]=current_aggregate_dof_ids - current_aggregate+=1 - end - aggregate_dof_ids -end - -function _get_single_field_fe_basis(a::Gridap.MultiField.MultiFieldFEBasisComponent) - a.single_field -end -function _get_single_field_fe_basis(a) - a -end -function _is_multifield_fe_basis_component(a::Gridap.MultiField.MultiFieldFEBasisComponent) - true -end -function _is_multifield_fe_basis_component(a) - false -end -function _nfields(a::Gridap.MultiField.MultiFieldFEBasisComponent) - a.nfields -end -function _fieldid(a::Gridap.MultiField.MultiFieldFEBasisComponent) - a.fieldid -end + compute_agg_cells_local_dof_ids(P_Ωbg_agg_cell_dof_ids, aggregate_to_local_cells) -""" - dv, du: Test and trial basis functions. They may be components of a MultiFieldCellField - - # Compute and assemble the bulk penalty stabilization term - # ∫( (dv-dv_l2_proj_agg_cells)*(du-du_l2_proj_agg_cells))*dΩ_agg_cells (long version) - # ∫( (dv)*(du-du_l2_proj_agg_cells))*dΩ_agg_cells (simplified, equivalent version) -""" -function interior_bulk_penalty_stabilization_collect_cell_matrix(agg_cells_to_aggregate, - ref_agg_cell_to_ref_bb_map, - dΩagg_cells, - dv, # Test basis - du, # Trial basis (to project) - dvbb, # Bounding box space test basis - lhs, - Ωagg_cell_dof_ids, - agg_cells_local_dof_ids, - agg_cells_to_aggregate_dof_ids, - h_U, - γ) - - - Ωagg_cells=dΩagg_cells.quad.trian - - # Change domain of vbb (test) from Ωbb to Ωagg_cells - dvbb_Ωagg_cells=change_domain_bb_to_agg_cells(dvbb, - ref_agg_cell_to_ref_bb_map, - Ωagg_cells, - agg_cells_to_aggregate) - - du_single_field=_get_single_field_fe_basis(du) - agg_cells_rhs_contribs=get_array(∫(dvbb_Ωagg_cells⋅du_single_field)dΩagg_cells) - ass_rhs_map=BulkGhostPenaltyAssembleRhsMap(agg_cells_local_dof_ids,agg_cells_rhs_contribs) - rhs=lazy_map(ass_rhs_map,aggregate_to_local_cells) - - # TO-DO: optimize using our own optimized version Gridap.Fields.Map - # of backslash that re-uses storage for lu factors among cells, etc. - dv_l2_proj_bb_dofs=lazy_map(\,lhs,rhs) - - # Generate bb-wise array of fields. For each aggregate's bounding box, - # it provides the l2 projection of all basis functions in du - # restricted to the cells included in the bounding box of the aggregate - dv_l2_proj_bb_array=lazy_map(Gridap.Fields.linear_combination, - dv_l2_proj_bb_dofs, - Gridap.CellData.get_data(dvbb)) - - # # Change domain of dv_l2_proj_bb_array from bb to agg_cells - dv_l2_proj_bb_array_agg_cells=lazy_map(Broadcasting(∘), - lazy_map(Reindex(dv_l2_proj_bb_array),agg_cells_to_aggregate), - ref_agg_cell_to_ref_bb_map) - - du_l2_proj_bb_array_agg_cells=lazy_map(transpose, dv_l2_proj_bb_array_agg_cells) - - if (_is_multifield_fe_basis_component(du)) - @assert _is_multifield_fe_basis_component(dv) - @assert _nfields(du)==_nfields(dv) - nfields=_nfields(du) - fieldid=_fieldid(du) - du_l2_proj_bb_array_agg_cells=lazy_map( - Gridap.Fields.BlockMap((1,nfields),fieldid), - du_l2_proj_bb_array_agg_cells) - end - - du_l2_proj_agg_cells = Gridap.CellData.GenericCellField(du_l2_proj_bb_array_agg_cells, - dΩagg_cells.quad.trian, - ReferenceDomain()) - - # Manually set up the arrays that collect_cell_matrix would return automatically - w = [] - r = [] - c = [] - - dv_du_mat_contribs=get_array(∫(γ*(1.0/h_U*h_U)*dv⋅du)*dΩagg_cells) - if (_is_multifield_fe_basis_component(dv)) - nfields=_nfields(dv) - fieldid=_fieldid(dv) - Ωagg_cell_dof_ids=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),Ωagg_cell_dof_ids) - end - push!(w, dv_du_mat_contribs) - push!(r, Ωagg_cell_dof_ids) - push!(c, Ωagg_cell_dof_ids) - - # In the MultiField case, I have had to add this change domain - # call before setting up the term right below. Otherwise, we get an error - # when trying to multiply the fields. Not sure why this is happening - dvΩagg_cells=Gridap.CellData.change_domain(dv,Ωagg_cells,ReferenceDomain()) - - proj_dv_du_mat_contribs=get_array(∫(γ*(1.0/h_U*h_U)*(-1.0)*dvΩagg_cells⋅(du_l2_proj_agg_cells))*dΩagg_cells) - - if (_is_multifield_fe_basis_component(du)) - nfields=_nfields(du) - fieldid=_fieldid(du) - agg_cells_to_aggregate_dof_ids= - lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),agg_cells_to_aggregate_dof_ids) - end - - push!(w, proj_dv_du_mat_contribs) - push!(r, Ωagg_cell_dof_ids) - push!(c, agg_cells_to_aggregate_dof_ids) - - w, r, c -end - -U_aggregate_dof_ids=compute_aggregate_dof_ids(U_Ωagg_cell_dof_ids,aggregate_to_cells) +# Compute global dofs ids per aggregate and reindex these +U_aggregate_dof_ids=compute_aggregate_dof_ids(U_Ωbg_agg_cell_dof_ids,aggregate_to_cells) U_agg_cells_to_aggregate_dof_ids=lazy_map(Reindex(U_aggregate_dof_ids),agg_cells_to_aggregate) - -P_aggregate_dof_ids=compute_aggregate_dof_ids(P_Ωagg_cell_dof_ids,aggregate_to_cells) +P_aggregate_dof_ids=compute_aggregate_dof_ids(P_Ωbg_agg_cell_dof_ids,aggregate_to_cells) P_agg_cells_to_aggregate_dof_ids=lazy_map(Reindex(P_aggregate_dof_ids),agg_cells_to_aggregate) +# parameters γ = 10.0 # Interior bulk-penalty stabilization parameter - # (@amartinhuertas no idea what a reasonable value is) - -h_U = set_up_h_U(aggregates_bounding_box_model, agg_cells_to_aggregate, Ωagg_cells) - -# Manually set up the arrays that collect_cell_matrix would return automatically -wp,rp,cp=interior_bulk_penalty_stabilization_collect_cell_matrix(agg_cells_to_aggregate, - ref_agg_cell_to_ref_bb_map, - dΩagg_cells, - dq, # Test basis - dp, # Trial basis (to project) - qbb, # Bounding box space test basis - p_lhs, - P_Ωagg_cell_dof_ids, - P_agg_cells_local_dof_ids, - P_agg_cells_to_aggregate_dof_ids, - h_U, - γ) - -wu,ru,cu=interior_bulk_penalty_stabilization_collect_cell_matrix(agg_cells_to_aggregate, - ref_agg_cell_to_ref_bb_map, - dΩagg_cells, - dv, # Test basis - du, # Trial basis (to project) - vbb, # Bounding box space test basis - u_lhs, - U_Ωagg_cell_dof_ids, - U_agg_cells_local_dof_ids, - U_agg_cells_to_aggregate_dof_ids, - h_U, - γ) - - -# Set up global projection matrix -Ωcut = Triangulation(cutdisk,PHYSICAL) -dΩcut = Measure(Ωcut,degree) -a((u,p),(v,q))=∫(v⋅u+q*p)dΩcut -wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) -assem=SparseMatrixAssembler(X,Y) -Anostab=assemble_matrix(assem, wrc) -cond(Array(Anostab)) +########################################### +### STABILIZATION ON Ωagg\Troot ### +########################################### + +# Setup cut cells and triangulation +aggregate_to_cut_cells = restrict_aggregate_to_cells(cutgeo,aggregate_to_cells,GridapEmbedded.Interfaces.CUT) +cut_cells = flatten(aggregate_to_cut_cells) +#TO-DO: look into why cut_cells = restrict_cells(cutgeo,GridapEmbedded.Interfaces.CUT) can not be used instead of the above +Ωbg_cut_cells = view(Ωbg,cut_cells) +dΩbg_cut_cells = Measure(Ωbg_cut_cells,degree) + +# Selecting relevant global dofs ids of cut cells (from background mesh) +Ωbg_cut_cell_dof_ids = get_cell_dof_ids(X,Ωbg_cut_cells) +U_Ωbg_cut_cell_dof_ids = _restrict_to_block(Ωbg_cut_cell_dof_ids, 1) +P_Ωbg_cut_cell_dof_ids = _restrict_to_block(Ωbg_cut_cell_dof_ids, 2) + +# Compute global dofs ids per aggregate and reindex these +cut_cells_to_aggregate = setup_cells_to_aggregate(aggregate_to_cut_cells) +U_cut_cells_to_aggregate_dof_ids=lazy_map(Reindex(U_aggregate_dof_ids),cut_cells_to_aggregate) +P_cut_cells_to_aggregate_dof_ids=lazy_map(Reindex(P_aggregate_dof_ids),cut_cells_to_aggregate) + +########################################### +### Setup projections +########################################### + +du_proj_Vbb, dv_proj_Vbb = setup_L2_proj_in_bb_space( + dΩbg_agg_cells, # measure of aggregated cells in background domain + ref_agg_cell_to_ref_bb_map, # map + agg_cells_to_aggregate, # + aggregate_to_local_cells, # + du, # Trial basis (to project) + dv, # Test basis + ubb, # Trial basis of bounding box space Vbb + vbb, # Test basis of bounding box space Vbb + identity, # operation to be applied to u and v + U_agg_cells_local_dof_ids) # aggregates local dof ids for space U + +dp_proj_Qbb, dq_proj_Qbb = setup_L2_proj_in_bb_space( + dΩbg_agg_cells, # measure of aggregated cells in background domain + ref_agg_cell_to_ref_bb_map, # map + agg_cells_to_aggregate, # + aggregate_to_local_cells, # + dp, # Trial basis (to project) + dq, # Test basis + pbb, # Trial basis of bounding box space Qbb + qbb, # Test basis of bounding box space Qbb + identity, # operation to be applied to u and v + P_agg_cells_local_dof_ids) # aggregates local dof ids for space P + +div_du_proj_Qbb, div_dv_proj_Qbb = setup_L2_proj_in_bb_space( + dΩbg_agg_cells, # measure of aggregated cells in background domain + ref_agg_cell_to_ref_bb_map, # map + agg_cells_to_aggregate, # + aggregate_to_local_cells, # + du, # Trial basis (to project) + dv, # Test basis + pbb, # Trial basis of bounding box space Vbb + qbb, # Test basis of bounding box space Vbb + divergence, # operation to be applied to u and v + U_agg_cells_local_dof_ids) # aggregates local dof ids for space U + +# ########################################### +# ### BlockMap PREP STEPS #TODO MOVE INTO collect_cell_matrix_on_D? +# ########################################### +if (_is_multifield_fe_basis_component(dv)) + nfields=_nfields(dv) + fieldid=_fieldid(dv) + U_Ωbg_cut_cell_dof_ids=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),U_Ωbg_cut_cell_dof_ids) +end -# Add the bulk penalty stabilization term to wrc -push!(wrc[1], wp...) -push!(wrc[2], rp...) -push!(wrc[3], cp...) +if (_is_multifield_fe_basis_component(du)) + nfields=_nfields(du) + fieldid=_fieldid(du) + U_cut_cells_to_aggregate_dof_ids= + lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),U_cut_cells_to_aggregate_dof_ids) +end -push!(wrc[1], wu...) -push!(wrc[2], ru...) -push!(wrc[3], cu...) +if (_is_multifield_fe_basis_component(dq)) + nfields=_nfields(dq) + fieldid=_fieldid(dq) + P_Ωbg_cut_cell_dof_ids=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),P_Ωbg_cut_cell_dof_ids) +end -Awithstab=assemble_matrix(assem, wrc) -cond(Array(Awithstab)) +if (_is_multifield_fe_basis_component(dp)) + nfields=_nfields(dp) + fieldid=_fieldid(dp) + P_cut_cells_to_aggregate_dof_ids= + lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),P_cut_cells_to_aggregate_dof_ids) +end -# Set up rhs global projection -l((v,q))=∫(v⋅uex+q*pex)dΩcut +########################################## +### Setup stabilization terms (DIFF) ### +########################################## +w_u_diff, r_u_diff, c_u_diff = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + du, + dv, + du_proj_Vbb, + dv_proj_Vbb, + U_Ωbg_cut_cell_dof_ids, + U_Ωbg_cut_cell_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + identity, + identity) + +w_p_diff, r_p_diff, c_p_diff = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + dp, + dq, + dp_proj_Qbb, + dq_proj_Qbb, + P_Ωbg_cut_cell_dof_ids, + P_Ωbg_cut_cell_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + identity, + identity) + +w_divu_diff, r_divu_diff, c_divu_diff = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + du, + dv, + div_du_proj_Qbb, + div_dv_proj_Qbb, + U_Ωbg_cut_cell_dof_ids, + U_Ωbg_cut_cell_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + divergence, + divergence) + +w_divuq_diff, r_divuq_diff, c_divuq_diff = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + du, + dq, + div_du_proj_Qbb, + dq_proj_Qbb, + U_Ωbg_cut_cell_dof_ids, + P_Ωbg_cut_cell_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + divergence, + identity) + +w_pdivv_diff, r_pdivv_diff, c_pdivv_diff = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + dp, + dv, + dp_proj_Qbb, + div_dv_proj_Qbb, + P_Ωbg_cut_cell_dof_ids, + U_Ωbg_cut_cell_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + identity, + divergence) + +########################################## +### Setup stabilization terms (FULL) ### +########################################## +w_u_full, r_u_full, c_u_full = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + du, + dv, + du_proj_Vbb, + U_Ωbg_cut_cell_dof_ids, + U_Ωbg_cut_cell_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + identity, + identity) + +w_p_full, r_p_full, c_p_full = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + dp, + dq, + dp_proj_Qbb, + P_Ωbg_cut_cell_dof_ids, + P_Ωbg_cut_cell_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + identity, + identity) + +w_divu_full, r_divu_full, c_divu_full = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + du, + dv, + div_du_proj_Qbb, + U_Ωbg_cut_cell_dof_ids, + U_Ωbg_cut_cell_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + divergence, + divergence) + +w_divuq_full, r_divuq_full, c_divuq_full = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + du, + dq, + div_du_proj_Qbb, + U_Ωbg_cut_cell_dof_ids, + P_Ωbg_cut_cell_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + divergence, + identity) + +w_pdivv_full, r_pdivv_full, c_pdivv_full = bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dΩbg_cut_cells, + Ωbg_agg_cells, + γ, + dp, + dv, + dp_proj_Qbb, + P_Ωbg_cut_cell_dof_ids, + U_Ωbg_cut_cell_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + identity, + divergence) + +########################################## +### Setup rhs stabilization ### +########################################## +rhs_func = divuex +rhs_func_proj_Qbb = setup_L2_proj_in_bb_space(dΩbg_agg_cells, + ref_agg_cell_to_ref_bb_map, + agg_cells_to_aggregate, + aggregate_to_local_cells, + rhs_func, + pbb, + qbb) + +w_rhsq_diff, r_rhsq_diff = bulk_ghost_penalty_stabilization_collect_cell_vector_on_D(dΩbg_cut_cells, + γ, + dq, + dq_proj_Qbb, + P_Ωbg_cut_cell_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + identity, + rhs_func, + rhs_func_proj_Qbb) + +w_rhsq_full, r_rhsq_full = bulk_ghost_penalty_stabilization_collect_cell_vector_on_D(dΩbg_cut_cells, + γ, + dq, + P_Ωbg_cut_cell_dof_ids, + identity, + rhs_func, + rhs_func_proj_Qbb) + +## WEAK FORM +if problem==0 + a((u,p),(v,q))=∫(v⋅u+q*p+(∇⋅u)*(∇⋅v))dΩ + l((v,q))=∫(v⋅uex+q*pex+(∇⋅v)*divuex)dΩ +elseif problem==1 + ΓN = EmbeddedBoundary(cutgeo) + dΓN = Measure(ΓN,degree) + nN = get_normal_vector(ΓN) + s = -1 + β₀ = 1e3 + β = β₀*h.^(s) + m =1 + uNeu = uex + g = divuex + a((u,p), (v,q)) = ∫(v⋅u - (∇⋅v)*p - (∇⋅u)*q)dΩ + ∫(β*(u⋅nN)*(v⋅nN) + (v⋅nN)*p + m*(u⋅nN)*q)dΓN + l((v,q)) = ∫(- q*g)dΩ + ∫(β*(v⋅nN)*(uNeu⋅nN) + m*q*(uNeu⋅nN))dΓN +else + print("Problem not implemented. Try again with problem=0 (L2-like projection test) or problem=1 (Darcy test)") +end +assem=SparseMatrixAssembler(X,Y) b = assemble_vector(l, Y) -global_l2_proj_dofs = Awithstab\b -xh = FEFunction(X, global_l2_proj_dofs) -uh,ph = xh +# RHS +vec_rhsq_diff=Gridap.FESpaces.collect_cell_vector(Y,l(dy)) +push!(vec_rhsq_diff[1],w_rhsq_diff...) +push!(vec_rhsq_diff[2],r_rhsq_diff...) +b_rhsq_diff = assemble_vector(assem, vec_rhsq_diff) + +vec_rhsq_full=Gridap.FESpaces.collect_cell_vector(Y,l(dy)) +push!(vec_rhsq_full[1],w_rhsq_full...) +push!(vec_rhsq_full[2],r_rhsq_full...) +b_rhsq_full = assemble_vector(assem, vec_rhsq_full) + +function compute_quantities(problem,A,b,dΩ) + cond_A = cond(Array(A)) + norm_A = norm(A) + sol_x = A\b + xh = FEFunction(X, sol_x) + uh,ph = xh + if problem==1 + area = sum(∫(1.0)dΩ) + mean_p = sum(∫(pex)dΩ)/area # mean presure exact sol + ph = ph + mean_p + end + euh = uex-uh + eph = pex-ph + edivuh = divuex-(∇⋅uh) + norm_euh = sum(∫(euh⋅euh)*dΩ) + norm_eph = sum(∫(eph*eph)*dΩ) + norm_edivuh = sum(∫(edivuh⋅edivuh)*dΩ) + return round(cond_A,sigdigits=3), round(norm_A,sigdigits=3), round(norm_euh,sigdigits=3), round(norm_eph,sigdigits=3), round(norm_edivuh,sigdigits=3) +end + +####### SOLVING TEST PROBLEMS (DIFF) -euh = uex-uh -@assert sum(∫(euh⋅euh)*dΩcut) < 1.0e-12 +# NO STAB DIFF +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +A = assemble_matrix(assem, wrc) +res_nostab = compute_quantities(problem,A,b,dΩ) + +# ONLY U DIFF +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_u_diff...) +push!(wrc[2], r_u_diff...) +push!(wrc[3], c_u_diff...) +A = assemble_matrix(assem, wrc) +res_stab_u_diff = compute_quantities(problem,A,b,dΩ) -eph = pex-ph -@assert sum(∫(eph*eph)*dΩcut) < 1.0e-12 +# ONLY P DIFF +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_p_diff...) +push!(wrc[2], r_p_diff...) +push!(wrc[3], c_p_diff...) +A = assemble_matrix(assem, wrc) +res_stab_p_diff = compute_quantities(problem,A,b,dΩ) +# ONLY DIVU DIFF +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_divu_diff...) +push!(wrc[2], r_divu_diff...) +push!(wrc[3], c_divu_diff...) +A = assemble_matrix(assem, wrc) +res_stab_divu_diff = compute_quantities(problem,A,b,dΩ) -# Piece of code to generate the divergence of the -# velocity space basis in the pressure space in the -# same format as returned by dv and du +# ONLY DIVUQ DIFF +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_divuq_diff...) +push!(wrc[2], r_divuq_diff...) +push!(wrc[3], c_divuq_diff...) +A = assemble_matrix(assem, wrc) +res_stab_divuq_diff = compute_quantities(problem,A,b,dΩ) +res_stab_divuq_diff_rhs = compute_quantities(problem,A,b_rhsq_diff,dΩ) + +# ONLY PDIVV DIFF +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_pdivv_diff...) +push!(wrc[2], r_pdivv_diff...) +push!(wrc[3], c_pdivv_diff...) +A = assemble_matrix(assem, wrc) +res_stab_pdivv_diff = compute_quantities(problem,A,b,dΩ) -div_dv=∇⋅(_get_single_field_fe_basis(dv)) -pdofs=Gridap.FESpaces.get_fe_dof_basis(P) -div_dv_pdofs_values=pdofs(div_dv) -div_dv_in_pressure_space_cell_array=lazy_map(Gridap.Fields.linear_combination, - div_dv_pdofs_values, - Gridap.CellData.get_data(_get_single_field_fe_basis(dq))) +####### SOLVING TEST PROBLEMS (FULL) -div_dv_in_pressure_space= - Gridap.FESpaces.SingleFieldFEBasis(div_dv_in_pressure_space_cell_array, - get_triangulation(P), - Gridap.FESpaces.TestBasis(), - Gridap.CellData.ReferenceDomain()) +# ONLY U FULL +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_u_full...) +push!(wrc[2], r_u_full...) +push!(wrc[3], c_u_full...) +A = assemble_matrix(assem, wrc) +res_stab_u_full = compute_quantities(problem,A,b,dΩ) -div_dv_in_pressure_space= - Gridap.MultiField.MultiFieldFEBasisComponent(div_dv_in_pressure_space,1,2) +# ONLY P FULL +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_p_full...) +push!(wrc[2], r_p_full...) +push!(wrc[3], c_p_full...) +A = assemble_matrix(assem, wrc) +res_stab_p_full = compute_quantities(problem,A,b,dΩ) -div_du_in_pressure_space_cell_array=lazy_map(transpose,div_dv_in_pressure_space_cell_array) +# ONLY DIVU FULL +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_divu_full...) +push!(wrc[2], r_divu_full...) +push!(wrc[3], c_divu_full...) +A = assemble_matrix(assem, wrc) +res_stab_divu_full = compute_quantities(problem,A,b,dΩ) -div_du_in_pressure_space=Gridap.FESpaces.SingleFieldFEBasis(div_du_in_pressure_space_cell_array, - get_triangulation(P), - Gridap.FESpaces.TrialBasis(), - Gridap.CellData.ReferenceDomain()) -div_du_in_pressure_space=Gridap.MultiField.MultiFieldFEBasisComponent(div_du_in_pressure_space,1,2) \ No newline at end of file +# ONLY DIVUQ FULL +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_divuq_full...) +push!(wrc[2], r_divuq_full...) +push!(wrc[3], c_divuq_full...) +A = assemble_matrix(assem, wrc) +res_stab_divuq_full = compute_quantities(problem,A,b,dΩ) +res_stab_divuq_full_rhs = compute_quantities(problem,A,b_rhsq_full,dΩ) + +# ONLY PDIVV FULL +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_pdivv_full...) +push!(wrc[2], r_pdivv_full...) +push!(wrc[3], c_pdivv_full...) +A = assemble_matrix(assem, wrc) +res_stab_pdivv_full = compute_quantities(problem,A,b,dΩ) + +## GENERATE EXCEL SHEET RESULTS`` +print("results") +res_nostab +res_stab_u_full +res_stab_u_diff +res_stab_p_full +res_stab_p_diff +res_stab_divu_full +res_stab_divu_diff +res_stab_pdivv_full # THIS IS OK. +res_stab_divuq_full_rhs # ERRORS SEEM TO BE OF SAME ORDER NOW. + +# Alternatives for res_stab_divuq_full_rhs +res_stab_divuq_full #THIS SEEMS BETTER +res_stab_divuq_diff #not too relevant +res_stab_divuq_full_rhs # seems incorrect +res_stab_divuq_diff_rhs # seems incorrect + +# Lastly compare these: +res_stab_pdivv_full +res_stab_pdivv_diff + +### +# UFULL + PFULL + DIVUFULL +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_u_full...) +push!(wrc[2], r_u_full...) +push!(wrc[3], c_u_full...) +push!(wrc[1], w_p_full...) +push!(wrc[2], r_p_full...) +push!(wrc[3], c_p_full...) +push!(wrc[1], w_divu_full...) +push!(wrc[2], r_divu_full...) +push!(wrc[3], c_divu_full...) +A = assemble_matrix(assem, wrc) +res_stab_updivu_full = compute_quantities(problem,A,b,dΩ) #ok + +# UDIFF+ PDIFF + DIVUDIFF +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_u_diff...) +push!(wrc[2], r_u_diff...) +push!(wrc[3], c_u_diff...) +push!(wrc[1], w_p_diff...) +push!(wrc[2], r_p_diff...) +push!(wrc[3], c_p_diff...) +push!(wrc[1], w_divu_diff...) +push!(wrc[2], r_divu_diff...) +push!(wrc[3], c_divu_diff...) +A = assemble_matrix(assem, wrc) +res_stab_updivu_diff = compute_quantities(problem,A,b,dΩ) #ok + +# UFULL + DIVUFULL + "MIX" +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_u_full...) +push!(wrc[2], r_u_full...) +push!(wrc[3], c_u_full...) +push!(wrc[1], w_divu_full...) +push!(wrc[2], r_divu_full...) +push!(wrc[3], c_divu_full...) +push!(wrc[1], w_divuq_full...) +push!(wrc[2], r_divuq_full...) +push!(wrc[3], c_divuq_full...) +push!(wrc[1], w_pdivv_full...) +push!(wrc[2], r_pdivv_full...) +push!(wrc[3], c_pdivv_full...) +A = assemble_matrix(assem, wrc) +res_stab_udivumix_full = compute_quantities(problem,A,b,dΩ) + +# UDIFF + DIVUDIFF + "MIX" +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_u_diff...) +push!(wrc[2], r_u_diff...) +push!(wrc[3], c_u_diff...) +push!(wrc[1], w_divu_diff...) +push!(wrc[2], r_divu_diff...) +push!(wrc[3], c_divu_diff...) +A = assemble_matrix(assem, wrc) +res_stab_udivumix_rhs_diff= compute_quantities(problem,A,b_rhsq_diff,dΩ) +res_stab_udivumix_diff = compute_quantities(problem,A,b,dΩ) # this is the same for rhs_func = constant +@assert res_stab_udivumix_rhs_diff == res_stab_udivumix_diff + +# UFULL + DIVUFULL + "MIX" +wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) +push!(wrc[1], w_u_full...) +push!(wrc[2], r_u_full...) +push!(wrc[3], c_u_full...) +push!(wrc[1], w_divu_full...) +push!(wrc[2], r_divu_full...) +push!(wrc[3], c_divu_full...) +A = assemble_matrix(assem, wrc) +res_stab_udivumix_rhs_full= compute_quantities(problem,A,b_rhsq_full,dΩ) +res_stab_udivumix_full = compute_quantities(problem,A,b,dΩ) # this is the same for rhs_func = constant +@assert res_stab_udivumix_rhs_full == res_stab_udivumix_full + +## GENERATE EXCEL SHEET RESULTS`` +print("results") +res_nostab +res_stab_u_full +res_stab_u_diff +res_stab_p_full +res_stab_p_diff +res_stab_divu_full +res_stab_divu_diff +res_stab_pdivv_full # THIS IS OK. +res_stab_divuq_full_rhs # ERRORS SEEM TO BE OF SAME ORDER NOW. + +res_stab_updivu_full +res_stab_updivu_diff +res_stab_udivumix_full +res_stab_udivumix_rhs_diff +res_stab_udivumix_rhs_full \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index dfa65cd1..bb0510fc 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,2 +1,3 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +GridapEmbedded = "8838a6a3-0006-4405-b874-385995508d5d" diff --git a/docs/make.jl b/docs/make.jl index 3bad67a4..fd3e61bc 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,14 +1,25 @@ using Documenter, GridapEmbedded +pages = [ + "Home" => "index.md", + "Constructive Solid Geometry (CSG)" => "CSG.md", + "Embedded Interfaces" => "Interfaces.md", + "Level Set Cutters" => "LevelSetCutters.md", + "Aggregated FEM" => "AggregatedFEM.md", + "Moment-Fitted Quadratures" => "MomentFittedQuadratures.md", + "Geometrical Derivatives" => "GeometricalDerivatives.md", + "Distributed computing" => "Distributed.md", +] + makedocs(; - modules=[GridapEmbedded], - format=Documenter.HTML(), - pages=[ - "Home" => "index.md", - ], - repo="https://github.com/gridap/GridapEmbedded.jl/blob/{commit}{path}#L{line}", - sitename="GridapEmbedded.jl", - authors="Francesc Verdugo , Eric Neiva and Santiago Badia ", + modules = [GridapEmbedded], + format = Documenter.HTML( + size_threshold=nothing + ), + sitename = "GridapEmbedded.jl", + authors = "Francesc Verdugo , Eric Neiva and Santiago Badia ", + pages = pages, + warnonly = false, ) deploydocs(; diff --git a/docs/src/AggregatedFEM.md b/docs/src/AggregatedFEM.md new file mode 100644 index 00000000..a2d7be88 --- /dev/null +++ b/docs/src/AggregatedFEM.md @@ -0,0 +1,16 @@ + +# Aggregated FEM + +```@meta +CurrentModule = GridapEmbedded.AgFEM +``` + +```@autodocs +Modules = [AgFEM,] +Order = [:type, :constant, :macro, :function] +Pages = [ + "/AgFEM.jl", + "/AgFEMSpaces.jl", + "CellAggregation.jl" +] +``` diff --git a/docs/src/CSG.md b/docs/src/CSG.md new file mode 100644 index 00000000..d801c34e --- /dev/null +++ b/docs/src/CSG.md @@ -0,0 +1,15 @@ + +# Constructive Solid Geometry (CSG) + +```@meta +CurrentModule = GridapEmbedded.CSG +``` + +```@autodocs +Modules = [CSG,] +Order = [:type, :constant, :macro, :function] +Pages = [ + "/Nodes.jl", + "/Geometries.jl", +] +``` diff --git a/docs/src/Distributed.md b/docs/src/Distributed.md new file mode 100644 index 00000000..740e228b --- /dev/null +++ b/docs/src/Distributed.md @@ -0,0 +1,23 @@ + +# Distributed computing + +```@meta +CurrentModule = GridapEmbedded.Distributed +``` + +We support distributed computing through [GridapDistributed.jl](https://github.com/gridap/GridapDistributed.jl). As per usual, we design our libraries so that the high-level API is unchanged when using distributed computing. This means that for most users, the changes to your driver will be minimal. + +The following features are currently supported: + +- Level-Set Cutters +- STL Cutters + +The folowing features are not yet supported: + +- Aggregated FEM +- Moment-Fitted Quadratures + +```@autodocs +Modules = [Distributed,] +Order = [:type, :constant, :macro, :function] +``` diff --git a/docs/src/GeometricalDerivatives.md b/docs/src/GeometricalDerivatives.md new file mode 100644 index 00000000..82298b40 --- /dev/null +++ b/docs/src/GeometricalDerivatives.md @@ -0,0 +1,33 @@ +# Geometrical Derivatives + +```@meta +CurrentModule = GridapEmbedded +``` + +The geometrical differentiation capabilities are based on the following work: + +!!! note "Reference" + "Level-set topology optimisation with unfitted finite elements and automatic shape differentiation", + by Z. J. Wegert, J. Manyer, C. Mallon, S. Badia, V. J. Challis (2025) + +To see examples of usage, please refer to the tests in `test/LevelSetCuttersTests/GeometricalDifferentiationTests.jl`. + +## Discretize then differentiate + +```@autodocs +Modules = [GridapEmbedded.Interfaces] +Order = [:type, :constant, :macro, :function] +Pages = [ + "/CutFaceBoundaryTriangulations.jl", +] +``` + +## Autodiff + +```@autodocs +Modules = [GridapEmbedded.LevelSetCutters] +Order = [:type, :constant, :macro, :function] +Pages = [ + "/DifferentiableTriangulations.jl", +] +``` diff --git a/docs/src/Interfaces.md b/docs/src/Interfaces.md new file mode 100644 index 00000000..a41c22cf --- /dev/null +++ b/docs/src/Interfaces.md @@ -0,0 +1,87 @@ + +# Embedded Interfaces + +```@meta +CurrentModule = GridapEmbedded.Interfaces +``` + +## Domain Nomenclature + +Throughout this documentation, many methods accept arguments that select different parts of the cut domain. We split the domain into the following parts: + +**The background mesh entities** (cells, facets, nodes) are classified as `IN`, `OUT` or `CUT`. The `IN` and `OUT` background cells are uncut, i.e completely inside or outside the geometry, respectively. These states are internally defined as constants: + +```julia + const IN = -1 + const OUT = 1 + const CUT = 0 +``` + +**The `CUT` background cells** are cut by the embedded boundary, and split into subcells/subfacets. The subcells/subfacets are classified as `IN` or `OUT` depending on whether they are inside or outside the geometry. `CUT_IN` and `CUT_OUT` subentities can be accessed using the `CutInOrOut` objects: + +```julia + struct CutInOrOut + in_or_out::Int + end + const CUT_IN = CutInOrOut(IN) + const CUT_OUT = CutInOrOut(OUT) +``` + +For FEM, we generally want to get sets of uncut and cut cells together, for a given state `IN/OUT`. These are referred as `PHYSICAL` parts of the domain. Moreover, FE spaces are generally defined over the background mesh and need to span both `IN/OUT` and `CUT` background cells. These are referred as `ACTIVE` parts of the domain. You can extract the `PHYSICAL` and `ACTIVE` parts of the domain using the following constants: + +```julia +const PHYSICAL_IN = (CUT_IN,IN) +const PHYSICAL_OUT = (CUT_OUT,OUT) +const PHYSICAL = PHYSICAL_IN +``` + +```julia +struct ActiveInOrOut + in_or_out::Int +end +const ACTIVE_IN = ActiveInOrOut(IN) +const ACTIVE_OUT = ActiveInOrOut(OUT) +const ACTIVE = ACTIVE_IN +``` + +## Cutters + +Cutters are used to cut the background mesh according to a provided geometry. + +```@autodocs +Modules = [Interfaces,] +Order = [:type, :constant, :macro, :function] +Pages = [ + "/Cutters.jl", +] +``` + +We provide several types of cutters, including: + +- **Level-Set Cutters**: Cutters for Level-Set and function-defined geometries. See [Level-Set Cutters](@ref). +- **STL Cutters**: Cutters for STL based geometries. Provided by [STLCutters.jl](https://github.com/gridap/STLCutters.jl). + +## Embedded Discretizations + +After cutting the background mesh, you will be returned an `EmbeddedDiscretization` object. These contain all the information you need to generate the integration meshes for embedded methods. + +```@docs +AbstractEmbeddedDiscretization +EmbeddedDiscretization +EmbeddedFacetDiscretization +``` + +## Embedded Triangulations + +From `EmbeddedDiscretization` objects, you can extract all the triangulations you need to perform integration for embedded methods. We currently provide the following methods: + +```@docs +Gridap.Geometry.Triangulation(::EmbeddedDiscretization,::Any) +EmbeddedBoundary(::EmbeddedDiscretization) +GhostSkeleton(::EmbeddedDiscretization) +Gridap.Geometry.BoundaryTriangulation(::EmbeddedFacetDiscretization,::Any) +Gridap.Geometry.SkeletonTriangulation(::EmbeddedFacetDiscretization,::Any) +SubCellTriangulation +SubFacetTriangulation +SubFacetBoundaryTriangulation +``` diff --git a/docs/src/LevelSetCutters.md b/docs/src/LevelSetCutters.md new file mode 100644 index 00000000..8980ddc6 --- /dev/null +++ b/docs/src/LevelSetCutters.md @@ -0,0 +1,16 @@ + +# Level-Set Cutters + +```@meta +CurrentModule = GridapEmbedded.LevelSetCutters +``` + +```@autodocs +Modules = [LevelSetCutters,] +Order = [:type, :constant, :macro, :function] +Pages = [ + "/AnalyticalGeometries.jl", + "/DiscreteGeometries.jl", + "LevelSetCutters.jl", +] +``` diff --git a/docs/src/MomentFittedQuadratures.md b/docs/src/MomentFittedQuadratures.md new file mode 100644 index 00000000..5f076ddd --- /dev/null +++ b/docs/src/MomentFittedQuadratures.md @@ -0,0 +1,11 @@ + +# Moment-Fitted Quadratures + +```@meta +CurrentModule = GridapEmbedded.MomentFittedQuadratures +``` + +```@autodocs +Modules = [MomentFittedQuadratures,] +Order = [:type, :constant, :macro, :function] +``` diff --git a/docs/src/index.md b/docs/src/index.md index 84432dfd..7287c762 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,8 +1,19 @@ # GridapEmbedded.jl -```@index -``` +## Introduction + +GridapEmbedded.jl is a package for the simulation of PDEs on embedded domains within the Gridap.jl ecosystem. Please refer to the [Gridap.jl documentation](https://gridap.github.io/Gridap.jl/stable/) for information on the core capabilities of the Gridap.jl library. + +## Manual -```@autodocs -Modules = [GridapEmbedded] +```@contents +Pages = [ + "CSG.md", + "Interfaces.md", + "LevelSetCutters.md", + "AggregatedFEM.md", + "MomentFittedQuadratures.md", + "GeometricalDerivatives.md", + "Distributed.md", +] ``` diff --git a/src/AgFEM/AgFEM.jl b/src/AgFEM/AgFEM.jl index 72fed372..187f1162 100644 --- a/src/AgFEM/AgFEM.jl +++ b/src/AgFEM/AgFEM.jl @@ -1,6 +1,6 @@ module AgFEM -using LightGraphs +using Graphs using LinearAlgebra using Gridap diff --git a/src/BGP/BGP.jl b/src/BGP/BGP.jl new file mode 100644 index 00000000..c1ef960a --- /dev/null +++ b/src/BGP/BGP.jl @@ -0,0 +1,27 @@ +using Gridap +using FillArrays +using LinearAlgebra + +include("aggregates_bounding_boxes_tools.jl") + +export setup_aggregate_to_cells +export setup_aggregates_bounding_box_model +export flatten +export restrict_cells +export restrict_aggregate_to_cells +export setup_cells_to_aggregate +export setup_ref_agg_cell_to_ref_bb_map +export setup_aggregate_to_local_cells +export compute_agg_cells_local_dof_ids +export compute_aggregate_dof_ids + +include("bulk_ghost_penalty_stab_tools.jl") +export setup_L2_proj_in_bb_space +export bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D +export bulk_ghost_penalty_stabilization_collect_cell_vector_on_D + +include("fields_and_blocks_tools.jl") +export _restrict_to_block + +include("BulkGhostPenaltyAssembleMaps.jl") + diff --git a/BulkGhostPenaltyAssembleMaps.jl b/src/BGP/BulkGhostPenaltyAssembleMaps.jl similarity index 69% rename from BulkGhostPenaltyAssembleMaps.jl rename to src/BGP/BulkGhostPenaltyAssembleMaps.jl index 3068b92c..9c85630e 100644 --- a/BulkGhostPenaltyAssembleMaps.jl +++ b/src/BGP/BulkGhostPenaltyAssembleMaps.jl @@ -13,7 +13,7 @@ function Gridap.Fields.return_cache(m::BulkGhostPenaltyAssembleLhsMap,cells) evaluate_result=Gridap.Arrays.CachedArray(eltype(T),_get_rank(T)) cache_unassembled_lhs,evaluate_result end - + function Gridap.Fields.evaluate!(cache,m::BulkGhostPenaltyAssembleLhsMap,cells) cache_unassembled_lhs,result=cache contrib = getindex!(cache_unassembled_lhs,m.agg_cells_lhs_contribs,1) @@ -26,11 +26,10 @@ function Gridap.Fields.evaluate!(cache,m::BulkGhostPenaltyAssembleLhsMap,cells) end result.array end - -struct BulkGhostPenaltyAssembleRhsMap{A,B} <: Gridap.Fields.Map - agg_cells_local_dof_ids::A +struct BulkGhostPenaltyAssembleRhsMap{A,B} <: Gridap.Fields.Map + agg_cells_local_dof_ids::A agg_cells_rhs_contribs::B -end +end function Gridap.Fields.return_cache(m::BulkGhostPenaltyAssembleRhsMap,aggregate_local_cells) cache_agg_cells_local_dof_ids=array_cache(m.agg_cells_local_dof_ids) @@ -52,13 +51,36 @@ function Gridap.Fields.evaluate!(cache,m::BulkGhostPenaltyAssembleRhsMap,aggrega end Gridap.Arrays.setsize!(result,(size(contrib,1),max_local_dof_id)) + result.array .= 0.0 for (i,cell) in enumerate(aggregate_local_cells) current_cell_local_dof_ids = getindex!(cache_agg_cells_local_dof_ids,m.agg_cells_local_dof_ids,cell) contrib = getindex!(cache_unassembled_rhs,m.agg_cells_rhs_contribs,cell) for (j,local_dof) in enumerate(current_cell_local_dof_ids) - result.array[:,local_dof] += contrib[:,j] + result.array[:,local_dof] += contrib[:,j] end end result.array +end + +struct BulkGhostPenaltyAssembleRhsFEFunctionMap{A} <: Gridap.Fields.Map + agg_cells_rhs_contribs::A +end + +function Gridap.Fields.return_cache(m::BulkGhostPenaltyAssembleRhsFEFunctionMap,aggregate_local_cells) + cache_unassembled_rhs=array_cache(m.agg_cells_rhs_contribs) + evaluate_result=Gridap.Arrays.CachedArray(eltype(eltype(m.agg_cells_rhs_contribs)),1) + cache_unassembled_rhs,evaluate_result +end + +function Gridap.Fields.evaluate!(cache,m::BulkGhostPenaltyAssembleRhsFEFunctionMap,aggregate_local_cells) + cache_unassembled_rhs,result=cache + contrib = getindex!(cache_unassembled_rhs,m.agg_cells_rhs_contribs,1) + Gridap.Arrays.setsize!(result,(size(contrib,1),)) + result.array .= 0.0 + for (i,cell) in enumerate(aggregate_local_cells) + contrib = getindex!(cache_unassembled_rhs,m.agg_cells_rhs_contribs,cell) + result.array .+= contrib + end + result.array end \ No newline at end of file diff --git a/src/BGP/aggregates_bounding_boxes_tools.jl b/src/BGP/aggregates_bounding_boxes_tools.jl new file mode 100644 index 00000000..bde8087c --- /dev/null +++ b/src/BGP/aggregates_bounding_boxes_tools.jl @@ -0,0 +1,275 @@ +""" + Creates an array of arrays with as many entries + as aggregates. For each aggregate, the array + contains the global cell IDs of that cells in the background + model that belong to the same aggregate + + TO-DO: with efficiency in mind we may want to store this + array of arrays as a Gridap.Arrays.Table. +""" +function setup_aggregate_to_cells(aggregates) + size_aggregates=Dict{Int,Int}() + for (i,agg) in enumerate(aggregates) + if agg>0 + if !haskey(size_aggregates,agg) + size_aggregates[agg]=1 + else + size_aggregates[agg]+=1 + end + end + end + + touched=Dict{Int,Int}() + aggregate_to_cells=Vector{Vector{Int}}() + current_aggregate=1 + for (i,agg) in enumerate(aggregates) + if agg>0 + if (size_aggregates[agg]>1) + if !haskey(touched,agg) + push!(aggregate_to_cells,[i]) + touched[agg]=current_aggregate + current_aggregate+=1 + else + push!(aggregate_to_cells[touched[agg]],i) + end + end + end + end + aggregate_to_cells +end + +""" + Creates an array containing the cell IDs of a selection of the background cells. The cell type filter IN (-1) selects the interior cells, OUT (1) the exterior cells, and GridapEmbedded.Interfaces.CUT (0) the cut cells. + TO-DO: publish CUT, so that GridapEmbedded.Interfaces.CUT can be shortened to CUT? + TO-DO: be careful with using restrict_cells(cutgeo,GridapEmbedded.Interfaces.CUT) to replace flatten(restrict_aggregate_to_cells(cutgeo,aggregate_to_cells,GridapEmbedded.Interfaces.CUT)) + +""" +function restrict_cells(cutgeo,cell_type_filter) + restricted_cells=Int64[] + cell_to_inoutcut=compute_bgcell_to_inoutcut(cutgeo,cutgeo.geo) + for cell in 1:length(cell_to_inoutcut) + if cell_to_inoutcut[cell] == cell_type_filter + push!(restricted_cells, cell) + end + end + restricted_cells +end + +""" + Creates an array of arrays with as many entries + as aggregates. For each aggregate, the array + contains a selection of the global cell IDs of + the cells in the background model that belong to + the same aggregate. The cell type filter IN (-1) + selects the interior cells, OUT (1) the exterior + cells, and GridapEmbedded.Interfaces.CUT (0) the + cut cells. + TO-DO: publish CUT, so that GridapEmbedded.Interfaces.CUT can be shortened to CUT? +""" +function restrict_aggregate_to_cells(cutgeo,aggregate_to_cells,cell_type_filter) + aggregate_to_restricted_cells=Vector{Int}[] + cell_to_inoutcut=compute_bgcell_to_inoutcut(cutgeo,cutgeo.geo) + for agg in aggregate_to_cells + restricted_cells = Int64[] + for cell in agg + if cell_to_inoutcut[cell] == cell_type_filter + push!(restricted_cells, cell) + end + end + push!(aggregate_to_restricted_cells,restricted_cells) + end + aggregate_to_restricted_cells +end + +function setup_aggregates_bounding_box_model(bgmodel, aggregate_to_cells) + g=get_grid(bgmodel) + cell_coords=get_cell_coordinates(g) + D=num_dims(bgmodel) + xmin=Vector{Float64}(undef,D) + xmax=Vector{Float64}(undef,D) + + # Compute coordinates of the nodes defining the bounding boxes + bounding_box_node_coords= + Vector{Point{D,Float64}}(undef,length(aggregate_to_cells)*2^D) + ptr = [ (((i-1)*2^D)+1) for i in 1:length(aggregate_to_cells)+1 ] + data = collect(1:length(bounding_box_node_coords)) + bounding_box_node_ids = Gridap.Arrays.Table(data,ptr) + for (agg,cells) in enumerate(aggregate_to_cells) + p=first(cell_coords[cells[1]]) + for i in 1:D + xmin[i]=p[i] + xmax[i]=p[i] + end + for cell in cells + for p in cell_coords[cell] + for i in 1:D + xmin[i]=min(xmin[i],p[i]) + xmax[i]=max(xmax[i],p[i]) + end + end + end + bounds = [(xmin[i], xmax[i]) for i in 1:D] + point_iterator = Iterators.product(bounds...) + bounding_box_node_coords[bounding_box_node_ids[agg]] = + reshape([Point(p...) for p in point_iterator],2^D) + end + + # Set up the discrete model of bounding boxes + HEX_AXIS=1 + polytope=Polytope(Fill(HEX_AXIS,D)...) + scalar_reffe=ReferenceFE(polytope,lagrangian,Float64,1) + cell_types=fill(1,length(bounding_box_node_ids)) + cell_reffes=[scalar_reffe] + grid = Gridap.Geometry.UnstructuredGrid(bounding_box_node_coords, + bounding_box_node_ids, + cell_reffes, + cell_types, + Gridap.Geometry.Oriented()) + Gridap.Geometry.UnstructuredDiscreteModel(grid) +end + +""" + Flattens an array of arrays +""" +function flatten(array_of_arrays) + vcat(array_of_arrays...) + end + +""" + Generate an array that given the local ID of an "agg_cell" + returns the ID of the aggregate to which it belongs + (i.e., flattened version of aggregate_to_cells) + + + TO-DO: perhaps merge this function with , + setup_cut_cells_in_agg_cells_to_aggregate + +""" +function setup_cells_to_aggregate(aggregate_to_cells) + cells_to_aggregate=Vector{Int}() + for (i,cells) in enumerate(aggregate_to_cells) + for _ in cells + push!(cells_to_aggregate,i) + end + end + cells_to_aggregate +end + +""" + Renumbers the global cell IDs to local cell IDs in the array of arrays + with as many entries as aggregates. For each aggregate, the array + now contains the local cell IDs of that cells in the background + model that belong to the same aggregate + + TO-DO: with efficiency in mind we may want to store this + array of arrays as a Gridap.Arrays.Table. +""" +function setup_aggregate_to_local_cells(aggregate_to_cells) + aggregate_to_local_cells=deepcopy(aggregate_to_cells) + current_local_cell=1 + for (i,cells) in enumerate(aggregate_to_local_cells) + for j in 1:length(cells) + cells[j]=current_local_cell + current_local_cell+=1 + end + end + aggregate_to_local_cells +end + +""" + Changes the domain of a trial/test basis defined on + the reference space of bounding boxes to the reference + space of the agg cells + + TO-DO: in the future, for system of PDEs (MultiField) we should + also take care of blocks (BlockMap) +""" +function change_domain_bb_to_agg_cells(basis_bb, + ref_agg_cell_to_ref_bb_map, + Ωagg_cells, + agg_cells_to_aggregate) + @assert num_cells(Ωagg_cells)==length(ref_agg_cell_to_ref_bb_map) + @assert Gridap.CellData.DomainStyle(basis_bb)==ReferenceDomain() + bb_basis_style = Gridap.FESpaces.BasisStyle(basis_bb) + bb_basis_array = Gridap.CellData.get_data(basis_bb) + if (bb_basis_style==Gridap.FESpaces.TrialBasis()) + # Remove transpose map; we will add it later + @assert isa(bb_basis_array,Gridap.Arrays.LazyArray) + @assert isa(bb_basis_array.maps,Fill) + @assert isa(bb_basis_array.maps.value,typeof(transpose)) + bb_basis_array=bb_basis_array.args[1] + end + + bb_basis_array_to_Ωagg_cells_array = lazy_map(Reindex(bb_basis_array),agg_cells_to_aggregate) + bb_basis_array_to_Ωagg_cells_array = lazy_map(Broadcasting(∘), + bb_basis_array_to_Ωagg_cells_array, + ref_agg_cell_to_ref_bb_map) + if (bb_basis_style==Gridap.FESpaces.TrialBasis()) + # Add transpose + bb_basis_array_to_Ωagg_cells_array=lazy_map(transpose, bb_basis_array_to_Ωagg_cells_array) + end + + Gridap.CellData.GenericCellField(bb_basis_array_to_Ωagg_cells_array, + Ωagg_cells, + ReferenceDomain()) +end + +""" + Define mapping ref_agg_cell_to_ref_bb_map: K_ref -> K -> bb -> bb_ref +""" +function setup_ref_agg_cell_to_ref_bb_map(aggregates_bounding_box_model,agg_cells_to_aggregate,ref_agg_cell_to_agg_cell_map) + bb_to_ref_bb=lazy_map(Gridap.Fields.inverse_map,get_cell_map(aggregates_bounding_box_model)) + bb_to_ref_bb_agg_cells=lazy_map(Reindex(bb_to_ref_bb),agg_cells_to_aggregate) + ref_agg_cell_to_ref_bb_map= + lazy_map(Broadcasting(∘),bb_to_ref_bb_agg_cells,ref_agg_cell_to_agg_cell_map) +end + +""" + Compute local dof ids of the aggregated cells +""" +function compute_agg_cells_local_dof_ids(agg_cells_dof_ids, aggregate_to_agg_cells) + agg_cells_local_dof_ids=copy(agg_cells_dof_ids) + current_cell=1 + for agg_cells in aggregate_to_agg_cells + g2l=Dict{Int32,Int32}() + current_local_dof=1 + for (i,_) in enumerate(agg_cells) + current_cell_dof_ids=agg_cells_dof_ids[current_cell] + for (j, dof) in enumerate(current_cell_dof_ids) + if !(dof in keys(g2l)) + g2l[dof]=current_local_dof + agg_cells_local_dof_ids[current_cell][j]=current_local_dof + current_local_dof+=1 + else + agg_cells_local_dof_ids[current_cell][j]=g2l[dof] + end + end + current_cell+=1 + end + end + agg_cells_local_dof_ids +end + +""" + Returns the dof ids for the aggregates +""" +function compute_aggregate_dof_ids(agg_cells_dof_ids, aggregate_to_agg_cells) + aggregate_dof_ids=Vector{Vector{Int}}(undef, length(aggregate_to_agg_cells)) + current_aggregate=1 + current_cell=1 + for agg_cells in aggregate_to_agg_cells + current_aggregate_dof_ids=Int[] + for (i,_) in enumerate(agg_cells) + current_cell_dof_ids=agg_cells_dof_ids[current_cell] + for (j, dof) in enumerate(current_cell_dof_ids) + if !(dof in current_aggregate_dof_ids) + push!(current_aggregate_dof_ids, dof) + end + end + current_cell+=1 + end + aggregate_dof_ids[current_aggregate]=current_aggregate_dof_ids + current_aggregate+=1 + end + aggregate_dof_ids +end \ No newline at end of file diff --git a/src/BGP/bulk_ghost_penalty_stab_tools.jl b/src/BGP/bulk_ghost_penalty_stab_tools.jl new file mode 100644 index 00000000..b3186a34 --- /dev/null +++ b/src/BGP/bulk_ghost_penalty_stab_tools.jl @@ -0,0 +1,375 @@ +""" + returns the L2 projection in the bounding box space of the trial `u` and test `v` functions after `operation` (e.g. identity or divergence) has been applied. That is, the L2 projection is defined through + + ∫ (operation(u) - Π_{Zbb}(operation(u)) zbb dΩbg_agg_cells = 0 ∀ zbb ∈ Zbb(T), + + with T ∈ T_agg and `Zbb` the bounding box space. Note that Ωbg_agg_cells ⊆ Ωbg_bb. Additionally, Π_{Zbb}(operation(u)) appears on the lhs as the trial function wbb ∈ Zbb. +""" +function setup_L2_proj_in_bb_space( + dΩbg_agg_cells, # measure of aggregated cells in background domain + ref_agg_cell_to_ref_bb_map, # map + agg_cells_to_aggregate, # + aggregate_to_local_cells, # + u, # Trial basis (to project) + v, # Test basis + wbb, # Trial basis of bounding box space Zbb + zbb, # Test basis of bounding box space Zbb + operation, # operation to be applied to u and v + U_agg_cells_local_dof_ids) # aggregates local dof ids for space U + + # (0) Obtain triangulation of aggregate cell domain from its measure + Ωbg_agg_cells=dΩbg_agg_cells.quad.trian + + # (1) Change domain of zbb (test function of Zbb) from Ωbb to Ωbg_agg_cells + zbb_Ωbg_agg_cells=change_domain_bb_to_agg_cells(zbb, + ref_agg_cell_to_ref_bb_map, + Ωbg_agg_cells, + agg_cells_to_aggregate) + + # (2) Change domain of wbb (trial function of Zbb) from Ωbb to Ωbg_agg_cells + wbb_Ωbg_agg_cells=change_domain_bb_to_agg_cells(wbb, + ref_agg_cell_to_ref_bb_map, + Ωbg_agg_cells, + agg_cells_to_aggregate) + # (3) TODO: Compute & assemble contributions to LHS of L2 projection + agg_cells_to_lhs_contribs=get_array(∫(zbb_Ωbg_agg_cells⋅wbb_Ωbg_agg_cells)dΩbg_agg_cells) + ass_lhs_map=BulkGhostPenaltyAssembleLhsMap(agg_cells_to_lhs_contribs) + lhs = lazy_map(ass_lhs_map,aggregate_to_local_cells) + + # (4) Compute & assemble contributions to RHS of L2 projection + op_u_single_field = operation(_get_single_field_fe_basis(u)) + agg_cells_rhs_contribs=get_array(∫(zbb_Ωbg_agg_cells⋅op_u_single_field)dΩbg_agg_cells) + ass_rhs_map=BulkGhostPenaltyAssembleRhsMap(U_agg_cells_local_dof_ids,agg_cells_rhs_contribs) + rhs=lazy_map(ass_rhs_map,aggregate_to_local_cells) + + # (5) TO-DO: optimize using our own optimized version Gridap.Fields.Map + # of backslash that re-uses storage for lu factors among cells, etc. + op_v_proj_Zbb_dofs=lazy_map(\,lhs,rhs) + + # (6) Generate bb-wise array of fields. For each aggregate's bounding box, + # it provides the l2 projection of operation(u) restricted to the cells + # included in the bounding box of the aggregate. + op_v_proj_Zbb_array=lazy_map(Gridap.Fields.linear_combination, + op_v_proj_Zbb_dofs, + Gridap.CellData.get_data(zbb)) + + # (7) Change domain of proj_op_u and proj_op_v from Ωbb to Ωbg_agg_cells + op_v_proj_Zbb_array_agg_cells=lazy_map(Broadcasting(∘), + lazy_map(Reindex(op_v_proj_Zbb_array),agg_cells_to_aggregate), + ref_agg_cell_to_ref_bb_map) + op_u_proj_Zbb_array_agg_cells=lazy_map(transpose, op_v_proj_Zbb_array_agg_cells) + if (_is_multifield_fe_basis_component(u)) + @assert _is_multifield_fe_basis_component(v) + @assert _nfields(u)==_nfields(v) + nfields=_nfields(u) + fieldid=_fieldid(u) + op_u_proj_Zbb_array_agg_cells=lazy_map( + Gridap.Fields.BlockMap((1,nfields),fieldid), + op_u_proj_Zbb_array_agg_cells) + end + op_u_proj_Zbb_agg_cells = Gridap.CellData.GenericCellField(op_u_proj_Zbb_array_agg_cells,Ωbg_agg_cells,ReferenceDomain()) + + if (_is_multifield_fe_basis_component(v)) + @assert _is_multifield_fe_basis_component(v) + @assert _nfields(u)==_nfields(v) + nfields=_nfields(v) + fieldid=_fieldid(v) + op_v_proj_Zbb_array_agg_cells=lazy_map( + Gridap.Fields.BlockMap(nfields,fieldid), + op_v_proj_Zbb_array_agg_cells) + end + + op_v_proj_Zbb_agg_cells = Gridap.CellData.GenericCellField(op_v_proj_Zbb_array_agg_cells,Ωbg_agg_cells,ReferenceDomain()) + + op_u_proj_Zbb_agg_cells, op_v_proj_Zbb_agg_cells +end + +""" + returns the L2 projection in the bounding box space of a function. That is, the L2 projection is defined through + + ∫ (function - Π_{Zbb}(operation(u)) zbb dΩbg_agg_cells = 0 ∀ zbb ∈ Zbb(T), + + with T ∈ T_agg and `Zbb` the bounding box space. Note that Ωbg_agg_cells ⊆ Ωbg_bb. Additionally, Π_{Zbb}(operation(u)) appears on the lhs as the trial function wbb ∈ Zbb. +""" +function setup_L2_proj_in_bb_space( + dΩbg_agg_cells, # measure of aggregated cells in background domain + ref_agg_cell_to_ref_bb_map, # map + agg_cells_to_aggregate, # + aggregate_to_local_cells, # + func, # Function to be projected + wbb, # Trial basis of bounding box space Zbb + zbb) # Test basis of bounding box space Zbb + + # (0) Obtain triangulation of aggregate cell domain from its measure + Ωbg_agg_cells=dΩbg_agg_cells.quad.trian + + # (1) Change domain of zbb (test function of Zbb) from Ωbb to Ωbg_agg_cells + zbb_Ωbg_agg_cells=change_domain_bb_to_agg_cells(zbb, + ref_agg_cell_to_ref_bb_map, + Ωbg_agg_cells, + agg_cells_to_aggregate) + + # (2) Change domain of wbb (trial function of Zbb) from Ωbb to Ωbg_agg_cells + wbb_Ωbg_agg_cells=change_domain_bb_to_agg_cells(wbb, + ref_agg_cell_to_ref_bb_map, + Ωbg_agg_cells, + agg_cells_to_aggregate) + # (3) Compute & assemble contributions to LHS of L2 projection + agg_cells_to_lhs_contribs=get_array(∫(zbb_Ωbg_agg_cells⋅wbb_Ωbg_agg_cells)dΩbg_agg_cells) + ass_lhs_map=BulkGhostPenaltyAssembleLhsMap(agg_cells_to_lhs_contribs) + lhs = lazy_map(ass_lhs_map,aggregate_to_local_cells) + + # (4) Compute & assemble contributions to RHS of L2 projection + agg_cells_rhs_contribs=get_array(∫(zbb_Ωbg_agg_cells⋅func)dΩbg_agg_cells) + rhs=lazy_map(sum,lazy_map(Broadcasting(Reindex(agg_cells_rhs_contribs)), aggregate_to_local_cells)) + + # (5) TO-DO: optimize using our own optimized version Gridap.Fields.Map + # of backslash that re-uses storage for lu factors among cells, etc. + func_proj_Zbb_dofs=lazy_map(\,lhs,rhs) + + # (6) Generate bb-wise array of fields. For each aggregate's bounding box, + # it provides the l2 projection of func restricted to the cells + # included in the bounding box of the aggregate. + func_proj_Zbb_array=lazy_map(Gridap.Fields.linear_combination, + func_proj_Zbb_dofs, + Gridap.CellData.get_data(zbb)) + + # (7) Change domain of proj_op_u and proj_func from Ωbb to Ωbg_agg_cells + func_proj_Zbb_array_agg_cells=lazy_map(Broadcasting(∘), + lazy_map(Reindex(func_proj_Zbb_array),agg_cells_to_aggregate), + ref_agg_cell_to_ref_bb_map) + func_proj_Zbb_agg_cells = Gridap.CellData.GenericCellField(func_proj_Zbb_array_agg_cells,Ωbg_agg_cells,ReferenceDomain()) + + func_proj_Zbb_agg_cells +end + +""" + Compute and assemble the bulk penalty stabilization term + + γ ∫( (operation_u(u) - proj_op_u)⊙(operation_v(v) - proj_op_v) )*dD + + with `u` and `v` the trial and test basis functions (which may be components of a MultiField) and do not have to belong to the same space. The operations `operation_u` and `operation_v` (e.g. identity or divergence) are applied to u and v, respectively. The L2 projections `proj_op_u` and `proj_op_v` can be computed via `setup_L2_proj_in_bb_space` and are to be passed as arguments. `dD` is the measure of the cut cells or full aggregate, that is `dΩbg_cut_cells` or `dΩbg_agg_cells`. +""" +function bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dD, + Ωbg_agg_cells, + γ, + u, + v, + proj_op_u, + proj_op_v, + dof_ids_u, + dof_ids_v, + dof_ids_proj_u, + dof_ids_proj_v, + operation_u, + operation_v) + + # # BlockMap preparatory steps for the dof ids + # if (_is_multifield_fe_basis_component(u)) + # nfields=_nfields(u) + # fieldid=_fieldid(u) + # dof_ids_u=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_u) + # end + # if (_is_multifield_fe_basis_component(v)) + # nfields=_nfields(v) + # fieldid=_fieldid(v) + # dof_ids_v=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_v) + # end + # if (_is_multifield_fe_basis_component(u)) + # nfields=_nfields(u) + # fieldid=_fieldid(u) + # dof_ids_proj_u=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_proj_u) + # end + # if (_is_multifield_fe_basis_component(v)) + # nfields=_nfields(v) + # fieldid=_fieldid(v) + # dof_ids_proj_v=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_proj_v) + # end + + # Manually set up the arrays that collect_cell_matrix would return automatically + w = [] + r = [] + c = [] + + # (1) op_u⊙op_v term + op_u_op_v_mat_contribs=get_array(∫(γ*operation_u(u)⊙operation_v(v))*dD) + push!(w, op_u_op_v_mat_contribs) + push!(r, dof_ids_v) + push!(c, dof_ids_u) + + # (2) proj_op_u⊙op_v term + + # In the MultiField case, I have had to add this change domain + # call before setting up the term right below. Otherwise, we get an error + # when trying to multiply the fields. Not sure why this is happening + op_v_on_Ωbg_agg_cells = Gridap.CellData.change_domain(operation_v(v),Ωbg_agg_cells,ReferenceDomain()) + + proj_op_u_op_v_mat_contribs=get_array(∫(γ*(-1.0)*(proj_op_u⊙op_v_on_Ωbg_agg_cells))*dD) + push!(w, proj_op_u_op_v_mat_contribs) + push!(r, dof_ids_v) + push!(c, dof_ids_proj_u) + + # (3) proj_op_u⊙proj_op_v + proj_op_u_proj_op_v_mat_contribs=get_array(∫(γ*(proj_op_u⊙proj_op_v))*dD) + push!(w, proj_op_u_proj_op_v_mat_contribs) + push!(r, dof_ids_proj_v) + push!(c, dof_ids_proj_u) + + # (4) op_u⊙proj_op_v + + # In the MultiField case, I have had to add this change domain + # call before setting up the term right below. Otherwise, we get an error + # when trying to multiply the fields. Not sure why this is happening + op_u_on_Ωbg_agg_cells = Gridap.CellData.change_domain(operation_u(u),Ωbg_agg_cells,ReferenceDomain()) + + op_u_proj_op_v_mat_contribs=get_array(∫(-γ*op_u_on_Ωbg_agg_cells⊙proj_op_v)*dD) + push!(w, op_u_proj_op_v_mat_contribs) + push!(r, dof_ids_proj_v) + push!(c, dof_ids_u) + + w, r, c +end + +""" + Compute and assemble the bulk penalty stabilization term + + γ ∫( (operation_u(u) - proj_op_u)⊙(operation_v(v)) )*dD + + with `u` and `v` the trial and test basis functions (which may be components of a MultiField). The operations `operation_u` and `operation_v` (e.g. identity or divergence) are applied to u and v, respectively. The L2 projections `proj_op_u` and `proj_op_v` can be computed via `setup_L2_proj_in_bb_space` and are to be passed as arguments. `dD` is the measure of the cut cells or full aggregate, that is `dΩbg_cut_cells` or `dΩbg_agg_cells`. + +""" +function bulk_ghost_penalty_stabilization_collect_cell_matrix_on_D(dD, + Ωbg_agg_cells, + γ, + u, + v, + proj_op_u, + dof_ids_u, + dof_ids_v, + dof_ids_proj_u, + operation_u, + operation_v) + + # # BlockMap preparatory steps for the dof ids + # if (_is_multifield_fe_basis_component(u)) + # nfields=_nfields(u) + # fieldid=_fieldid(u) + # dof_ids_u=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_u) + # end + # if (_is_multifield_fe_basis_component(v)) + # nfields=_nfields(v) + # fieldid=_fieldid(v) + # dof_ids_v=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_v) + # end + # if (_is_multifield_fe_basis_component(u)) + # nfields=_nfields(u) + # fieldid=_fieldid(u) + # dof_ids_proj_u=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_proj_u) + # end + + # Manually set up the arrays that collect_cell_matrix would return automatically + w = [] + r = [] + c = [] + + # (1) op_u⊙op_v term + op_u_op_v_mat_contribs=get_array(∫(γ*operation_u(u)⊙operation_v(v))*dD) + push!(w, op_u_op_v_mat_contribs) + push!(r, dof_ids_v) + push!(c, dof_ids_u) + + # (2) proj_op_u⊙op_v term + + # In the MultiField case, I have had to add this change domain + # call before setting up the term right below. Otherwise, we get an error + # when trying to multiply the fields. Not sure why this is happening + op_v_on_Ωbg_agg_cells = Gridap.CellData.change_domain(operation_v(v),Ωbg_agg_cells,ReferenceDomain()) + + proj_op_u_op_v_mat_contribs=get_array(∫(γ*(-1.0)*(proj_op_u⊙op_v_on_Ωbg_agg_cells))*dD) + push!(w, proj_op_u_op_v_mat_contribs) + push!(r, dof_ids_v) + push!(c, dof_ids_proj_u) + + w, r, c +end + +""" + Compute and assemble the bulk penalty stabilization term + + γ ∫( (operation_v(v) - proj_op_v)⊙(func - proj_func) )*dD + + with `v` the test basis functions (which may be components of a MultiField). The operation `operation_v` (e.g. identity or divergence) is applied to v. The L2 projections `proj_op_u` and `proj_func` can be computed via `setup_L2_proj_in_bb_space`. `dD` is the measure of the cut cells or full aggregate, that is `dΩbg_cut_cells` or `dΩbg_agg_cells`. The function `func` is the forcing term appearing on the right hand-side of the PDE. + +""" +function bulk_ghost_penalty_stabilization_collect_cell_vector_on_D(dD, + γ, + v, + proj_op_v, + dof_ids_v, + dof_ids_proj_v, + operation_v, + func, + proj_func) + # # BlockMap preparatory steps for the dof ids + # if (_is_multifield_fe_basis_component(v)) + # nfields=_nfields(v) + # fieldid=_fieldid(v) + # dof_ids_v=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_v) + # end + # if (_is_multifield_fe_basis_component(v)) + # nfields=_nfields(v) + # fieldid=_fieldid(v) + # dof_ids_proj_v=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_proj_v) + # end + + # Manually set up the arrays that collect_cell_vector would return automatically + w = [] + r = [] + + # (1) op_v⊙(func-proj_func) term + op_v_diff_func_contribs=get_array(∫(γ*operation_v(v)⊙(func - proj_func))*dD) + push!(w, op_v_diff_func_contribs) + push!(r, dof_ids_v) + + # (2) proj_op_v⊙(func-proj_func) + proj_op_v_diff_func_vec_contribs=get_array(∫(γ*(-1.0)*(proj_op_v⊙(func - proj_func)))*dD) + push!(w, proj_op_v_diff_func_vec_contribs) + push!(r, dof_ids_proj_v) + + w, r +end + +""" + Compute and assemble the bulk penalty stabilization term + + γ ∫( (operation_v(v))⊙(func - proj_func) )*dD + + with `v` the test basis functions (which may be components of a MultiField). The operation `operation_v` (e.g. identity or divergence) is applied to v. The L2 projection of `func`, stored as `proj_func`, can be computed via + `setup_L2_proj_in_bb_space`. `dD` is the measure of the cut cells or full aggregate, that is `dΩbg_cut_cells` or `dΩbg_agg_cells`. The function `func` is the forcing term appearing on the right hand-side of the PDE. + +""" +function bulk_ghost_penalty_stabilization_collect_cell_vector_on_D(dD, + γ, + v, + dof_ids_v, + operation_v, + func, + proj_func) + # # BlockMap preparatory steps for the dof ids + # if (_is_multifield_fe_basis_component(v)) + # nfields=_nfields(v) + # fieldid=_fieldid(v) + # dof_ids_v=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),dof_ids_v) + # end + + # Manually set up the arrays that collect_cell_vector would return automatically + w = [] + r = [] + + # (1) op_v⊙(func-proj_func) + op_v_diff_func_vec_contribs=get_array(∫(γ*operation_v(v)⊙(func - proj_func))*dD) + push!(w, op_v_diff_func_vec_contribs) + push!(r, dof_ids_v) + + w, r +end \ No newline at end of file diff --git a/src/BGP/darcy_preconditioning_tools.jl b/src/BGP/darcy_preconditioning_tools.jl new file mode 100644 index 00000000..2fd4955a --- /dev/null +++ b/src/BGP/darcy_preconditioning_tools.jl @@ -0,0 +1,105 @@ +function assemble_darcy_preconditioner_matrix(cutgeo, degree, X, Y, s, h, β₀, + aggregate_to_local_cells, + agg_cells_to_aggregate, + ref_agg_cell_to_ref_bb_map, + dΩbg_agg_cells, + dΩbg_cut_cells, + # FLUX-RELATED DATA + dv, # Test basis + du, # Trial basis (to project) + vbb, # Bounding box space test basis + u_lhs, + U_Ωbg_cut_cell_dof_ids, + U_agg_cells_local_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + γ_u, + # PRESSURE-RELATED DATA + dq, # Test basis + dp, # Trial basis (to project) + qbb, # Bounding box space test basis + p_lhs, + P_Ωbg_cut_cell_dof_ids, + P_agg_cells_local_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + γ_p, + + γ_div) + + + # Physical domain + Ω = Triangulation(cutgeo,PHYSICAL) + dΩ = Measure(Ω,degree) + + # Bounadary + ΓN = EmbeddedBoundary(cutgeo) + dΓN = Measure(ΓN,degree) + nN = get_normal_vector(ΓN) + β = β₀*h.^(s) + a((u,p), (v,q)) = ∫(v⋅u)dΩ + ∫(p⋅q)dΩ + ∫(β*(u⋅nN)*(v⋅nN))dΓN + + dx = get_trial_fe_basis(X) + dy = get_fe_basis(Y) + + + ## FULL stabilization terms: + wu_full,ru_full,cu_full= + bulk_ghost_penalty_stabilization_collect_cell_matrix_on_cut_cells_full(aggregate_to_local_cells, + agg_cells_to_aggregate, + ref_agg_cell_to_ref_bb_map, + dΩbg_agg_cells, + dv, # Test basis + du, # Trial basis (to project) + vbb, # Bounding box space test basis + u_lhs, + U_Ωbg_cut_cell_dof_ids, + U_agg_cells_local_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + γ_u, + dΩbg_cut_cells) + + wp_full,rp_full,cp_full= + bulk_ghost_penalty_stabilization_collect_cell_matrix_on_cut_cells_full(aggregate_to_local_cells, + agg_cells_to_aggregate, + ref_agg_cell_to_ref_bb_map, + dΩbg_agg_cells, + dq, # Test basis + dp, # Trial basis (to project) + qbb, # Bounding box space test basis + p_lhs, + P_Ωbg_cut_cell_dof_ids, + P_agg_cells_local_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + γ_p, + dΩbg_cut_cells) + + wdiv_full, rdiv_full, cdiv_full = + div_penalty_stabilization_collect_cell_matrix_on_cut_cells_full(aggregate_to_local_cells, + agg_cells_to_aggregate, + ref_agg_cell_to_ref_bb_map, + dΩbg_agg_cells, + dv, # Test basis + du, # Trial basis (to project) + qbb, # Bounding box space test basis + p_lhs, + U_Ωbg_cut_cell_dof_ids, + U_agg_cells_local_dof_ids, + U_cut_cells_to_aggregate_dof_ids, + γ_div, + dΩbg_cut_cells) + + assem=SparseMatrixAssembler(X,Y) + wrc=Gridap.FESpaces.collect_cell_matrix(X,Y,a(dx,dy)) + push!(wrc[1], wu_full...) + push!(wrc[2], ru_full...) + push!(wrc[3], cu_full...) + + push!(wrc[1], wp_full...) + push!(wrc[2], rp_full...) + push!(wrc[3], cp_full...) + + push!(wrc[1], wdiv_full...) + push!(wrc[2], rdiv_full...) + push!(wrc[3], cdiv_full...) + + assemble_matrix(assem, wrc) +end \ No newline at end of file diff --git a/src/BGP/fields_and_blocks_tools.jl b/src/BGP/fields_and_blocks_tools.jl new file mode 100644 index 00000000..ff40c7f6 --- /dev/null +++ b/src/BGP/fields_and_blocks_tools.jl @@ -0,0 +1,26 @@ +function _restrict_to_block(cell_dof_ids::Gridap.Arrays.LazyArray{<:Fill{<:Gridap.Fields.BlockMap}}, blockid) + map=cell_dof_ids.maps.value + @assert length(map.size)==1 + @assert blockid >= 1 + @assert blockid <= map.size[1] + cell_dof_ids.args[blockid] +end + +function _get_single_field_fe_basis(a::Gridap.MultiField.MultiFieldFEBasisComponent) + a.single_field +end +function _get_single_field_fe_basis(a) + a +end +function _is_multifield_fe_basis_component(a::Gridap.MultiField.MultiFieldFEBasisComponent) + true +end +function _is_multifield_fe_basis_component(a) + false +end +function _nfields(a::Gridap.MultiField.MultiFieldFEBasisComponent) + a.nfields +end +function _fieldid(a::Gridap.MultiField.MultiFieldFEBasisComponent) + a.fieldid +end diff --git a/src/CSG/Geometries.jl b/src/CSG/Geometries.jl index 38c39080..f37ee04b 100644 --- a/src/CSG/Geometries.jl +++ b/src/CSG/Geometries.jl @@ -1,14 +1,35 @@ +""" + abstract type Geometry + +Abstract type for the definition of a geometry. + +## Interface + +- `get_tree(geo::Geometry)` +- `similar_geometry(a::Geometry,tree::Node)` +- `compatible_geometries(a::Geometry,b::Geometry)` + +""" abstract type Geometry end +""" + get_tree(geo::Geometry) +""" function get_tree(geo::Geometry) @abstractmethod end +""" + similar_geometry(a::Geometry,tree::Node) +""" function similar_geometry(a::Geometry,tree::Node) @abstractmethod end +""" + compatible_geometries(a::Geometry,b::Geometry) +""" function compatible_geometries(a::Geometry,b::Geometry) @abstractmethod end @@ -51,24 +72,36 @@ function _replace_metadata(tree::Leaf,meta) Leaf(data) end +""" + Base.union(a::Geometry,b::Geometry;name::String="",meta=nothing) +""" function Base.union(a::Geometry,b::Geometry;name::String="",meta=nothing) _a, _b = compatible_geometries(a,b) tree = union(get_tree(_a),get_tree(_b),name,meta) similar_geometry(_a,tree) end +""" + Base.intersect(a::Geometry,b::Geometry;name::String="",meta=nothing) +""" function Base.intersect(a::Geometry,b::Geometry;name::String="",meta=nothing) _a, _b = compatible_geometries(a,b) tree = intersect(get_tree(_a),get_tree(_b),name,meta) similar_geometry(_a,tree) end +""" + Base.setdiff(a::Geometry,b::Geometry;name::String="",meta=nothing) +""" function Base.setdiff(a::Geometry,b::Geometry;name::String="",meta=nothing) _a, _b = compatible_geometries(a,b) tree = setdiff(get_tree(_a),get_tree(_b),name,meta) similar_geometry(_a,tree) end +""" + Base.:!(a::Geometry;name::String="",meta=nothing) +""" function Base.:!(a::Geometry;name::String="",meta=nothing) tree = !(get_tree(a),name,meta) similar_geometry(a,tree) @@ -119,5 +152,3 @@ function get_geometry_node(a::Node,name::String) end @unreachable "There is no entity called $name" end - - diff --git a/src/Distributed/Distributed.jl b/src/Distributed/Distributed.jl index c68af9a9..97d6e7ba 100644 --- a/src/Distributed/Distributed.jl +++ b/src/Distributed/Distributed.jl @@ -10,6 +10,7 @@ using Gridap.CellData using Gridap.Geometry using Gridap.Helpers using Gridap.ReferenceFEs +using Gridap.FESpaces using PartitionedArrays: VectorFromDict @@ -22,21 +23,26 @@ using GridapEmbedded.Interfaces: SubFacetTriangulation using GridapEmbedded.Interfaces: SubCellData using GridapEmbedded.Interfaces: SubFacetData using GridapEmbedded.Interfaces: AbstractEmbeddedDiscretization +using GridapEmbedded.Interfaces: CutFaceBoundaryTriangulation +using GridapEmbedded.Interfaces: CutFaceSkeletonTriangulation using GridapEmbedded.AgFEM: _touch_aggregated_cells! using GridapEmbedded.AgFEM: AggregateCutCellsByThreshold using GridapEmbedded.MomentFittedQuadratures: MomentFitted -using Gridap.Geometry: AppendedTriangulation +using GridapEmbedded.LevelSetCutters: DifferentiableTriangulation +using GridapEmbedded.LevelSetCutters: DifferentiableAppendedTriangulation +using GridapEmbedded.LevelSetCutters: DifferentiableTriangulationView +using GridapEmbedded.LevelSetCutters: update_trian! +using Gridap.Geometry: AppendedTriangulation, TriangulationView using Gridap.Geometry: get_face_to_parent_face +using Gridap.Arrays: find_inverse_index_map, testitem, return_type using Gridap.FESpaces: FESpaceWithLinearConstraints using Gridap.FESpaces: _dof_to_DOF, _DOF_to_dof -using GridapDistributed: DistributedDiscreteModel -using GridapDistributed: DistributedTriangulation -using GridapDistributed: DistributedFESpace -using GridapDistributed: DistributedSingleFieldFESpace -using GridapDistributed: DistributedMeasure -using GridapDistributed: add_ghost_cells -using GridapDistributed: generate_gids -using GridapDistributed: generate_cell_gids + +using GridapDistributed: DistributedDiscreteModel, DistributedTriangulation, DistributedMeasure +using GridapDistributed: DistributedFESpace, DistributedSingleFieldFESpace +using GridapDistributed: DistributedCellField, DistributedMultiFieldCellField +using GridapDistributed: NoGhost, WithGhost, filter_cells_when_needed, add_ghost_cells +using GridapDistributed: generate_gids, generate_cell_gids using GridapDistributed: _find_vector_type import GridapEmbedded.AgFEM: aggregate @@ -46,18 +52,29 @@ import GridapEmbedded.Interfaces: cut_facets import GridapEmbedded.Interfaces: EmbeddedBoundary import GridapEmbedded.Interfaces: compute_bgfacet_to_inoutcut import GridapEmbedded.Interfaces: compute_bgcell_to_inoutcut +import GridapEmbedded.Interfaces: GhostSkeleton +import GridapEmbedded.Interfaces: get_subfacet_normal_vector, get_ghost_normal_vector, get_conormal_vector import GridapEmbedded.CSG: get_geometry +import GridapEmbedded.LevelSetCutters: discretize, DiscreteGeometry import Gridap.Geometry: Triangulation import Gridap.Geometry: SkeletonTriangulation import Gridap.Geometry: BoundaryTriangulation import Gridap.Geometry: get_background_model +import Gridap.Geometry: num_cells +import Gridap.CellData: get_tangent_vector import GridapDistributed: local_views import GridapDistributed: remove_ghost_cells include("DistributedDiscretizations.jl") +include("DistributedDiscreteGeometries.jl") + +include("DistributedSubFacetTriangulations.jl") + include("DistributedAgFEM.jl") include("DistributedQuadratures.jl") +include("GeometricalDerivatives.jl") + end # module diff --git a/src/Distributed/DistributedAgFEM.jl b/src/Distributed/DistributedAgFEM.jl index 4501ba76..cdb65ab1 100644 --- a/src/Distributed/DistributedAgFEM.jl +++ b/src/Distributed/DistributedAgFEM.jl @@ -3,8 +3,8 @@ function AgFEMSpace( bgmodel::DistributedDiscreteModel, f::DistributedFESpace, bgcell_to_bgcellin::AbstractArray{<:AbstractVector}, - g::DistributedFESpace=f) - + g::DistributedFESpace=f +) bgmodel_gids = get_cell_gids(bgmodel) spaces = map( local_views(f), @@ -13,9 +13,7 @@ function AgFEMSpace( local_views(bgmodel_gids)) do f,bgcell_to_bgcellin,g,gids AgFEMSpace(f,bgcell_to_bgcellin,g,local_to_global(gids)) end - trians = map(get_triangulation,local_views(f)) - trian = DistributedTriangulation(trians,bgmodel) - trian = add_ghost_cells(trian) + trian = add_ghost_cells(get_triangulation(f)) trian_gids = generate_cell_gids(trian) cell_to_cellin = _active_aggregates(bgcell_to_bgcellin) cell_to_ldofs = cell_ldof_to_mdof(spaces,cell_to_cellin) @@ -26,7 +24,7 @@ function AgFEMSpace( end function aggregate(strategy,cutgeo::DistributedEmbeddedDiscretization,args...) - aggregates,aggregate_owner = distributed_aggregate(strategy,cutgeo,args...) + aggregates, aggregate_owner = distributed_aggregate(strategy,cutgeo,args...) bgmodel = get_background_model(cutgeo) if has_remote_aggregation(bgmodel,aggregates) bgmodel = add_remote_aggregates(bgmodel,aggregates,aggregate_owner) @@ -40,8 +38,8 @@ end function distributed_aggregate( strategy::AggregateCutCellsByThreshold, cut::DistributedEmbeddedDiscretization, - in_or_out=IN) - + in_or_out=IN +) geo = get_geometry(cut) distributed_aggregate(strategy,cut,geo,in_or_out) end @@ -50,14 +48,13 @@ function distributed_aggregate( strategy::AggregateCutCellsByThreshold, cut::DistributedEmbeddedDiscretization, geo::CSG.Geometry, - in_or_out=IN) - + in_or_out=IN +) bgmodel = get_background_model(cut) facet_to_inoutcut = compute_bgfacet_to_inoutcut(bgmodel,geo) _distributed_aggregate_by_threshold(strategy.threshold,cut,geo,in_or_out,facet_to_inoutcut) end - function _distributed_aggregate_by_threshold(threshold,cutgeo,geo,loc,facet_to_inoutcut) @assert loc in (IN,OUT) @@ -82,15 +79,14 @@ function _distributed_aggregate_by_threshold(threshold,cutgeo,geo,loc,facet_to_i _distributed_aggregate_by_threshold_barrier( threshold,cell_to_unit_cut_meas,facet_to_inoutcut,cell_to_inoutcut, - loc,cell_to_coords,cell_to_faces,face_to_cells,gids) + loc,cell_to_coords,cell_to_faces,face_to_cells,gids + ) end - function _distributed_aggregate_by_threshold_barrier( threshold,cell_to_unit_cut_meas,facet_to_inoutcut,cell_to_inoutcut, - loc,cell_to_coords,cell_to_faces,face_to_cells,gids) - - + loc,cell_to_coords,cell_to_faces,face_to_cells,gids +) ocell_to_touched = map(cell_to_unit_cut_meas) do c_to_m map(≥,c_to_m,Fill(threshold,length(c_to_m))) end @@ -111,7 +107,6 @@ function _distributed_aggregate_by_threshold_barrier( end cell_to_neig = map(n->zeros(Int32,n),n_cells) - cell_to_root_part = map(collect,local_to_owner(gids)) c1 = map(array_cache,cell_to_faces) @@ -129,7 +124,8 @@ function _distributed_aggregate_by_threshold_barrier( cell_to_faces, face_to_cells, facet_to_inoutcut, - loc) + loc + ) PVector(cell_to_touched,partition(gids)) |> consistent! |> wait PVector(cell_to_neig,partition(gids)) |> consistent! |> wait @@ -248,8 +244,8 @@ function _find_best_neighbor_from_centroid_distance( cell_to_touched, cell_to_root_centroid, facet_to_inoutcut, - loc) - + loc +) faces = getindex!(c1,cell_to_faces,cell) dmin = Inf T = eltype(eltype(face_to_cells)) @@ -559,6 +555,8 @@ function _local_aggregates(cell_to_gcellin,gcell_to_cell) end end +# change_bgmodel + function change_bgmodel(cell_to_gcellin,gids::PRange) map(change_bgmodel,cell_to_gcellin,local_to_global(gids)) end @@ -571,3 +569,87 @@ function change_bgmodel(cell_to_gcellin,ncell_to_gcell) end ncell_to_gcellin end + +function change_bgmodel( + cutgeo::DistributedEmbeddedDiscretization, + model::DistributedDiscreteModel +) + cuts = map(change_bgmodel,local_views(cutgeo),local_views(model)) + DistributedEmbeddedDiscretization(cuts,model) +end + +function change_bgmodel( + cutgeo::DistributedEmbeddedDiscretization, + model::DistributedDiscreteModel, + cell_to_new_cell +) + cuts = map(change_bgmodel,local_views(cutgeo),local_views(model),cell_to_new_cell) + DistributedEmbeddedDiscretization(cuts,model) +end + +function change_bgmodel( + cut::EmbeddedDiscretization, + newmodel::DiscreteModel, + cell_to_newcell=1:num_cells(get_background_model(cut)) +) + ls_to_bgc_to_ioc = map(cut.ls_to_bgcell_to_inoutcut) do bgc_to_ioc + new_bgc_to_ioc = Vector{Int8}(undef,num_cells(newmodel)) + new_bgc_to_ioc[cell_to_newcell] = bgc_to_ioc + new_bgc_to_ioc + end + subcells = change_bgmodel(cut.subcells,cell_to_newcell) + subfacets = change_bgmodel(cut.subfacets,cell_to_newcell) + EmbeddedDiscretization( + newmodel, + ls_to_bgc_to_ioc, + subcells, + cut.ls_to_subcell_to_inout, + subfacets, + cut.ls_to_subfacet_to_inout, + cut.oid_to_ls, + cut.geo + ) +end + +function change_bgmodel( + cut::EmbeddedFacetDiscretization, + newmodel::DiscreteModel, + facet_to_newfacet=1:num_facets(get_background_model(cut)) +) + nfacets = num_facets(newmodel) + ls_to_bgf_to_ioc = map(cut.ls_to_facet_to_inoutcut) do bgf_to_ioc + new_bgf_to_ioc = Vector{Int8}(undef,nfacets) + new_bgf_to_ioc[facet_to_newfacet] = bgf_to_ioc + new_bgf_to_ioc + end + subfacets = change_bgmodel(cut.subfacets,facet_to_newfacet) + EmbeddedFacetDiscretization( + newmodel, + ls_to_bgf_to_ioc, + subfacets, + cut.ls_to_subfacet_to_inout, + cut.oid_to_ls, + cut.geo + ) +end + +function change_bgmodel(cells::SubCellData,cell_to_newcell) + cell_to_bgcell = lazy_map(Reindex(cell_to_newcell),cells.cell_to_bgcell) + SubCellData( + cells.cell_to_points, + collect(Int32,cell_to_bgcell), + cells.point_to_coords, + cells.point_to_rcoords + ) +end + +function change_bgmodel(facets::SubFacetData,cell_to_newcell) + facet_to_bgcell = lazy_map(Reindex(cell_to_newcell),facets.facet_to_bgcell) + SubFacetData( + facets.facet_to_points, + facets.facet_to_normal, + collect(Int32,facet_to_bgcell), + facets.point_to_coords, + facets.point_to_rcoords + ) +end diff --git a/src/Distributed/DistributedDiscreteGeometries.jl b/src/Distributed/DistributedDiscreteGeometries.jl new file mode 100644 index 00000000..0f9b55f2 --- /dev/null +++ b/src/Distributed/DistributedDiscreteGeometries.jl @@ -0,0 +1,86 @@ + +struct DistributedDiscreteGeometry{A} <: GridapType + geometries::A +end + +local_views(a::DistributedDiscreteGeometry) = a.geometries + +function DiscreteGeometry(φh::CellField,model::DistributedDiscreteModel;name::String="") + geometries = map(local_views(φh),local_views(model)) do φh, model + DiscreteGeometry(φh,model;name) + end + DistributedDiscreteGeometry(geometries) +end + +function distributed_geometry(a::AbstractArray{<:DiscreteGeometry}) + DistributedDiscreteGeometry(a) +end + +function discretize(a::AnalyticalGeometry,model::DistributedDiscreteModel) + geometries = map(local_views(model)) do model + discretize(a,model) + end + DistributedDiscreteGeometry(geometries) +end + +function cut( + cutter::Cutter,bgmodel::DistributedDiscreteModel,geom::DistributedDiscreteGeometry +) + gids = get_cell_gids(bgmodel) + cuts = map(local_views(bgmodel),local_views(geom)) do bgmodel,geom + cut(cutter,bgmodel,geom) + end + @notimplementedif !isconsistent_bgcell_to_inoutcut(cuts,partition(gids)) + DistributedEmbeddedDiscretization(cuts,bgmodel) +end + +function cut_facets( + cutter::Cutter,bgmodel::DistributedDiscreteModel{Dc},geom::DistributedDiscreteGeometry +) where Dc + gids = get_face_gids(bgmodel,Dc-1) + cuts = map(local_views(bgmodel),local_views(geom)) do bgmodel,geom + cut_facets(cutter,bgmodel,geom) + end + @notimplementedif !isconsistent_bgcell_to_inoutcut(cuts,partition(gids)) + DistributedEmbeddedDiscretization(cuts,bgmodel) +end + +for TT in (:Triangulation,:SkeletonTriangulation,:BoundaryTriangulation,:EmbeddedBoundary,:GhostSkeleton) + @eval begin + function $TT(portion,cutgeo::DistributedEmbeddedDiscretization,cutinorout,geom::DistributedDiscreteGeometry) + model = get_background_model(cutgeo) + gids = get_cell_gids(model) + trians = map(local_views(cutgeo),local_views(geom),partition(gids)) do cutgeo, geom, gids + $TT(portion,gids,cutgeo,cutinorout,geom) + end + DistributedTriangulation(trians,model) + end + end +end + +function distributed_aggregate( + strategy::AggregateCutCellsByThreshold, + cut::DistributedEmbeddedDiscretization, + geo::DistributedDiscreteGeometry, + in_or_out = IN +) + bgmodel = get_background_model(cut) + facet_to_inoutcut = compute_bgfacet_to_inoutcut(bgmodel,geo) + _distributed_aggregate_by_threshold(strategy.threshold,cut,geo,in_or_out,facet_to_inoutcut) +end + +function compute_bgcell_to_inoutcut( + cutgeo::DistributedEmbeddedDiscretization,geo::DistributedDiscreteGeometry +) + map(local_views(cutgeo),local_views(geo)) do cutgeo, geo + compute_bgcell_to_inoutcut(cutgeo,geo) + end +end + +function compute_bgfacet_to_inoutcut( + cutter::Cutter, bgmodel::DistributedDiscreteModel, geo::DistributedDiscreteGeometry +) + map(local_views(bgmodel),local_views(geo)) do model, geo + compute_bgfacet_to_inoutcut(cutter,model,geo) + end +end \ No newline at end of file diff --git a/src/Distributed/DistributedDiscretizations.jl b/src/Distributed/DistributedDiscretizations.jl index 08816f3e..c8939060 100644 --- a/src/Distributed/DistributedDiscretizations.jl +++ b/src/Distributed/DistributedDiscretizations.jl @@ -3,11 +3,12 @@ struct DistributedEmbeddedDiscretization{A,B} <: GridapType discretizations::A model::B function DistributedEmbeddedDiscretization( - d::AbstractArray{<:AbstractEmbeddedDiscretization}, - model::DistributedDiscreteModel) - A = typeof(d) + discretizations::AbstractArray{<:AbstractEmbeddedDiscretization}, + model::DistributedDiscreteModel + ) + A = typeof(discretizations) B = typeof(model) - new{A,B}(d,model) + new{A,B}(discretizations,model) end end @@ -16,22 +17,25 @@ local_views(a::DistributedEmbeddedDiscretization) = a.discretizations get_background_model(a::DistributedEmbeddedDiscretization) = a.model function get_geometry(a::DistributedEmbeddedDiscretization) - cut = local_views(a) |> PartitionedArrays.getany - get_geometry(cut) + geometries = map(get_geometry,local_views(a)) + distributed_geometry(geometries) +end + +# Needed for dispatching between analytical geometries and discrete geometries +function distributed_geometry(geometries::AbstractArray{<:CSG.Geometry}) + PartitionedArrays.getany(geometries) end function cut(bgmodel::DistributedDiscreteModel,args...) cut(LevelSetCutter(),bgmodel,args...) end -function cut(cutter::Cutter,bgmodel::DistributedDiscreteModel,args...) - gids = get_cell_gids(bgmodel) - cuts = map(local_views(bgmodel),local_views(gids)) do bgmodel,gids - ownmodel = remove_ghost_cells(bgmodel,gids) - cutgeo = cut(cutter,ownmodel,args...) - change_bgmodel(cutgeo,bgmodel,own_to_local(gids)) +function cut(cutter::Cutter,bgmodel::DistributedDiscreteModel{Dc},args...) where Dc + gids = get_face_gids(bgmodel,Dc) + cuts = map(local_views(bgmodel)) do bgmodel + cut(cutter,bgmodel,args...) end - consistent_bgcell_to_inoutcut!(cuts,gids) + @notimplementedif !isconsistent_bgcell_to_inoutcut(cuts,partition(gids)) DistributedEmbeddedDiscretization(cuts,bgmodel) end @@ -39,307 +43,162 @@ function cut_facets(bgmodel::DistributedDiscreteModel,args...) cut_facets(LevelSetCutter(),bgmodel,args...) end -function cut_facets(cutter::Cutter,bgmodel::DistributedDiscreteModel,args...) - D = map(num_dims,local_views(bgmodel)) |> PartitionedArrays.getany - cell_gids = get_cell_gids(bgmodel) - facet_gids = get_face_gids(bgmodel,D-1) - cuts = map( - local_views(bgmodel), - local_views(cell_gids), - local_views(facet_gids)) do bgmodel,cell_gids,facet_gids - ownmodel = remove_ghost_cells(bgmodel,cell_gids) - facet_to_pfacet = get_face_to_parent_face(ownmodel,D-1) - cutfacets = cut_facets(cutter,ownmodel,args...) - cutfacets = change_bgmodel(cutfacets,bgmodel,facet_to_pfacet) - remove_ghost_subfacets(cutfacets,facet_gids) +function cut_facets(cutter::Cutter,bgmodel::DistributedDiscreteModel{Dc},args...) where Dc + gids = get_face_gids(bgmodel,Dc-1) + cuts = map(local_views(bgmodel)) do bgmodel + cut_facets(cutter,bgmodel,args...) end - consistent_bgfacet_to_inoutcut!(cuts,facet_gids) + @notimplementedif !isconsistent_bgcell_to_inoutcut(cuts,partition(gids)) DistributedEmbeddedDiscretization(cuts,bgmodel) end -function Triangulation( - cutgeo::DistributedEmbeddedDiscretization, - in_or_out::ActiveInOrOut, - args...) - - distributed_embedded_triangulation(Triangulation,cutgeo,in_or_out,args...) -end - -function Triangulation(cutgeo::DistributedEmbeddedDiscretization,args...) - trian = distributed_embedded_triangulation(Triangulation,cutgeo,args...) - remove_ghost_cells(trian) -end +# Note on distributed triangulations: +# +# - We allow for more one argument, `portion`, which allows the user to filter +# some of the cells/faces. In particular, this is used to remove ghosts from the +# local triangulations. +# - The default for `portion` is `NoGhost()`, wich filters out all ghost cells, except +# when we have the argument `in_or_out`. -function EmbeddedBoundary(cutgeo::DistributedEmbeddedDiscretization,args...) - trian = distributed_embedded_triangulation(EmbeddedBoundary,cutgeo,args...) - remove_ghost_cells(trian) -end - -function SkeletonTriangulation(cutgeo::DistributedEmbeddedDiscretization,args...) - trian = distributed_embedded_triangulation(SkeletonTriangulation,cutgeo,args...) - remove_ghost_cells(trian) -end - -function BoundaryTriangulation(cutgeo::DistributedEmbeddedDiscretization,args...) - trian = distributed_embedded_triangulation(BoundaryTriangulation,cutgeo,args...) - remove_ghost_cells(trian) -end - -function distributed_embedded_triangulation( - T, - cutgeo::DistributedEmbeddedDiscretization, - args...) - - trians = map(local_views(cutgeo)) do lcutgeo - T(lcutgeo,args...) - end - bgmodel = get_background_model(cutgeo) - DistributedTriangulation(trians,bgmodel) -end - -function compute_bgfacet_to_inoutcut( - bgmodel::DistributedDiscreteModel, - bgf_to_ioc::AbstractArray{<:AbstractVector}) - - D = num_dims(eltype(local_views(bgmodel))) - gids = get_cell_gids(bgmodel) - bgf_to_ioc = map( - local_views(bgmodel), - local_views(gids), - bgf_to_ioc) do bgmodel,gids,bgf_to_ioc - - ownmodel = remove_ghost_cells(bgmodel,gids) - f_to_pf = Gridap.Geometry.get_face_to_parent_face(ownmodel,D-1) - _bgf_to_ioc = Vector{eltype(bgf_to_ioc)}(undef,num_faces(bgmodel,D-1)) - _bgf_to_ioc[f_to_pf] .= bgf_to_ioc - _bgf_to_ioc - end - facet_gids = get_face_gids(bgmodel,D-1) - pbgf_to_ioc = PVector(bgf_to_ioc,partition(facet_gids)) - consistent!(pbgf_to_ioc) |> wait - local_values(pbgf_to_ioc) -end - -function compute_bgfacet_to_inoutcut( - cutter::Cutter, - bgmodel::DistributedDiscreteModel, - geo) - - gids = get_cell_gids(bgmodel) - bgf_to_ioc = map(local_views(bgmodel),local_views(gids)) do model,gids - ownmodel = remove_ghost_cells(model,gids) - compute_bgfacet_to_inoutcut(cutter,ownmodel,geo) - end - compute_bgfacet_to_inoutcut(bgmodel,bgf_to_ioc) +function Triangulation( + cutgeo::DistributedEmbeddedDiscretization,in_or_out::ActiveInOrOut,args... +) + Triangulation(WithGhost(),cutgeo,in_or_out,args...) end -function compute_bgfacet_to_inoutcut(bgmodel::DistributedDiscreteModel,args...) - cutter = LevelSetCutter() - compute_bgfacet_to_inoutcut(cutter,bgmodel,args...) -end +for TT in (:Triangulation,:SkeletonTriangulation,:BoundaryTriangulation,:EmbeddedBoundary,:GhostSkeleton) + @eval begin + function $TT(cutgeo::DistributedEmbeddedDiscretization,args...) + $TT(NoGhost(),cutgeo,args...) + end -function compute_bgcell_to_inoutcut(cutgeo::DistributedEmbeddedDiscretization,args...) - map(local_views(cutgeo)) do cutgeo - compute_bgcell_to_inoutcut(cutgeo,args...) - end -end + function $TT(portion,cutgeo::DistributedEmbeddedDiscretization,args...) + model = get_background_model(cutgeo) + gids = get_cell_gids(model) + trians = map(local_views(cutgeo),partition(gids)) do cutgeo, gids + $TT(portion,gids,cutgeo,args...) + end + DistributedTriangulation(trians,model) + end -function compute_bgfacet_to_inoutcut(cutgeo::DistributedEmbeddedDiscretization,args...) - map(local_views(cutgeo)) do cutgeo - compute_bgfacet_to_inoutcut(cutgeo,args...) + function $TT(portion,gids::AbstractLocalIndices,cutgeo::AbstractEmbeddedDiscretization,args...) + trian = $TT(cutgeo,args...) + filter_cells_when_needed(portion,gids,trian) + end end end -function remove_ghost_cells(trian::DistributedTriangulation) - model = get_background_model(trian) - gids = get_cell_gids(model) - trians = map(local_views(trian),local_views(gids)) do trian,gids - remove_ghost_cells(trian,gids) - end - DistributedTriangulation(trians,model) +# TODO: This should go to GridapDistributed +function get_tangent_vector(a::DistributedTriangulation) + fields = map(get_tangent_vector,local_views(a)) + DistributedCellField(fields,a) end +# TODO: This should go to GridapDistributed function remove_ghost_cells(trian::AppendedTriangulation,gids) a = remove_ghost_cells(trian.a,gids) b = remove_ghost_cells(trian.b,gids) - lazy_append(a,b) + iszero(num_cells(a)) && return b + iszero(num_cells(b)) && return a + return lazy_append(a,b) end -function remove_ghost_cells(trian::SubFacetTriangulation,gids) - model = get_background_model(trian) - D = num_cell_dims(model) - glue = get_glue(trian,Val{D}()) +function remove_ghost_cells(trian::SubFacetTriangulation{Df,Dc},gids) where {Df,Dc} + glue = get_glue(trian,Val{Dc}()) remove_ghost_cells(glue,trian,gids) end -function remove_ghost_cells(model::DiscreteModel,gids::AbstractLocalIndices) - DiscreteModelPortion(model,own_to_local(gids)) -end - -function consistent_bgcell_to_inoutcut!( - cuts::AbstractArray{<:AbstractEmbeddedDiscretization}, - gids::PRange) - - ls_to_bgcell_to_inoutcut = map(get_ls_to_bgcell_to_inoutcut,cuts) - _consistent!(ls_to_bgcell_to_inoutcut,gids) -end - -function get_ls_to_bgcell_to_inoutcut(cut::EmbeddedDiscretization) - cut.ls_to_bgcell_to_inoutcut -end - -function consistent_bgfacet_to_inoutcut!( - cuts::AbstractArray{<:AbstractEmbeddedDiscretization}, - gids::PRange) - - ls_to_bgfacet_to_inoutcut = map(get_ls_to_bgfacet_to_inoutcut,cuts) - _consistent!(ls_to_bgfacet_to_inoutcut,gids) -end - -function get_ls_to_bgfacet_to_inoutcut(cut::EmbeddedFacetDiscretization) - cut.ls_to_facet_to_inoutcut -end - -function _consistent!( - p_to_i_to_a::AbstractArray{<:Vector{<:Vector}}, - prange::PRange) - - n = map(length,p_to_i_to_a) |> PartitionedArrays.getany - for i in 1:n - p_to_a = map(i_to_a->i_to_a[i],p_to_i_to_a) - PVector(p_to_a,partition(prange)) |> consistent! |> wait - map(p_to_a,p_to_i_to_a) do p_to_a,p_to_ia - copyto!(p_to_ia[i],p_to_a) +function remove_ghost_subfacets(cut::EmbeddedFacetDiscretization,facet_gids) + bgfacet_mask = map(!iszero,local_to_owner(facet_gids)) + subfacet_mask = map(Reindex(bgfacet_mask),cut.subfacets.cell_to_bgcell) + new_subfacets = findall(subfacet_mask) + subfacets = SubCellData(cut.subfacets,new_subfacets) + ls_to_subfacet_to_inout = map(cut.ls_to_subfacet_to_inout) do sf_to_io + map(Reindex(sf_to_io),new_subfacets) + end + EmbeddedFacetDiscretization( + cut.bgmodel, + cut.ls_to_facet_to_inoutcut, + subfacets, + ls_to_subfacet_to_inout, + cut.oid_to_ls, + cut.geo + ) +end + +# Distributed InOutCut flag methods + +# isconsistent_bgcell_to_inoutcut(cut::DistributedEmbeddedDiscretization) +# isconsistent_bgcell_to_inoutcut(cuts::AbstractArray{<:AbstractEmbeddedDiscretization},indices) +# +# Returns true if the local `ls_to_bgcell_to_inoutcut` arrays are consistent +# accross processors. +function isconsistent_bgcell_to_inoutcut( + cut::DistributedEmbeddedDiscretization{Dc} +) where Dc + model = get_background_model(cut) + gids = get_face_gids(model,Dc) + isconsistent_bgcell_to_inoutcut(local_views(cut),partition(gids)) +end + +function isconsistent_bgcell_to_inoutcut( + cuts::AbstractArray{<:AbstractEmbeddedDiscretization},indices::AbstractArray +) + get_inoutcut(cut::EmbeddedDiscretization) = Tuple(cut.ls_to_bgcell_to_inoutcut) + get_inoutcut(cut::EmbeddedFacetDiscretization) = Tuple(cut.ls_to_facet_to_inoutcut) + ls_to_bgcell_to_inoutcut = tuple_of_arrays(map(get_inoutcut,cuts)) + return isconsistent_bgcell_to_inoutcut(ls_to_bgcell_to_inoutcut,indices) +end + +function isconsistent_bgcell_to_inoutcut( + ls_to_bgcell_to_inoutcut::NTuple{N,<:AbstractArray{<:Vector}},indices::AbstractArray +) where N + for bgcell_to_inoutcut in ls_to_bgcell_to_inoutcut + if !isconsistent_bgcell_to_inoutcut(bgcell_to_inoutcut,indices) + return false end end + return true end -function change_bgmodel( - cutgeo::DistributedEmbeddedDiscretization, - model::DistributedDiscreteModel, - args...) - - cuts = _change_bgmodels(cutgeo,model,args...) - gids = get_cell_gids(model) - ls_to_bgcell_to_inoutcut = map(c->c.ls_to_bgcell_to_inoutcut,cuts) - _consistent!(ls_to_bgcell_to_inoutcut,gids) - DistributedEmbeddedDiscretization(cuts,model) -end - -function change_bgmodel( - cutgeo::DistributedEmbeddedDiscretization{<:AbstractArray{<:EmbeddedFacetDiscretization}}, - model::DistributedDiscreteModel, - args...) - - D = map(num_dims,local_views(model)) |> PartitionedArrays.getany - cuts = _change_bgmodels(cutgeo,model,args...) - gids = get_face_gids(model,D-1) - ls_to_facet_to_inoutcut = map(c->c.ls_to_facet_to_inoutcut,cuts) - _consistent!(ls_to_facet_to_inoutcut,gids) - DistributedEmbeddedDiscretization(cuts,model) -end - - -function _change_bgmodels( - cutgeo::DistributedEmbeddedDiscretization, - model::DistributedDiscreteModel, - cell_to_newcell) - - map(local_views(cutgeo),local_views(model),cell_to_newcell) do c,m,c_to_nc - change_bgmodel(c,m,c_to_nc) +function isconsistent_bgcell_to_inoutcut( + bgcell_to_inoutcut::AbstractArray{<:Vector},indices::AbstractArray +) + # TODO: Some allocations can be avoided by going to the low-level communication API + ref = map(copy,bgcell_to_inoutcut) + wait(consistent!(PVector(ref,indices))) + is_consistent = map(bgcell_to_inoutcut,ref) do bgcell_to_inoutcut,ref + bgcell_to_inoutcut == ref end + return reduce(&,is_consistent,init=true) end -function _change_bgmodels( - cutgeo::DistributedEmbeddedDiscretization, - model::DistributedDiscreteModel) - - map(local_views(cutgeo),local_views(model)) do c,m - change_bgmodel(c,m) - end +# TODO: Should we check for consistency here? +function compute_bgfacet_to_inoutcut(bgmodel::DistributedDiscreteModel,args...) + cutter = LevelSetCutter() + compute_bgfacet_to_inoutcut(cutter,bgmodel,args...) end -function change_bgmodel( - cut::EmbeddedDiscretization, - newmodel::DiscreteModel, - cell_to_newcell=1:num_cells(get_background_model(cut))) - - ls_to_bgc_to_ioc = map(cut.ls_to_bgcell_to_inoutcut) do bgc_to_ioc - new_bgc_to_ioc = Vector{Int8}(undef,num_cells(newmodel)) - new_bgc_to_ioc[cell_to_newcell] = bgc_to_ioc - new_bgc_to_ioc +function compute_bgfacet_to_inoutcut(cutter::Cutter,bgmodel::DistributedDiscreteModel,args...) + map(local_views(bgmodel)) do bgmodel + compute_bgfacet_to_inoutcut(cutter,bgmodel,args...) end - subcells = change_bgmodel(cut.subcells,cell_to_newcell) - subfacets = change_bgmodel(cut.subfacets,cell_to_newcell) - EmbeddedDiscretization( - newmodel, - ls_to_bgc_to_ioc, - subcells, - cut.ls_to_subcell_to_inout, - subfacets, - cut.ls_to_subfacet_to_inout, - cut.oid_to_ls, - cut.geo) end -function change_bgmodel( - cut::EmbeddedFacetDiscretization, - newmodel::DiscreteModel, - facet_to_newfacet=1:num_facets(get_background_model(cut))) - - nfacets = num_facets(newmodel) - - ls_to_bgf_to_ioc = map(cut.ls_to_facet_to_inoutcut) do bgf_to_ioc - new_bgf_to_ioc = Vector{Int8}(undef,nfacets) - new_bgf_to_ioc[facet_to_newfacet] = bgf_to_ioc - new_bgf_to_ioc +function compute_bgcell_to_inoutcut(cutgeo::DistributedEmbeddedDiscretization,args...) + map(local_views(cutgeo)) do cutgeo + compute_bgcell_to_inoutcut(cutgeo,args...) end - subfacets = change_bgmodel(cut.subfacets,facet_to_newfacet) - EmbeddedFacetDiscretization( - newmodel, - ls_to_bgf_to_ioc, - subfacets, - cut.ls_to_subfacet_to_inout, - cut.oid_to_ls, - cut.geo) end -function change_bgmodel(cells::SubCellData,cell_to_newcell) - cell_to_bgcell = lazy_map(Reindex(cell_to_newcell),cells.cell_to_bgcell) - SubCellData( - cells.cell_to_points, - collect(Int32,cell_to_bgcell), - cells.point_to_coords, - cells.point_to_rcoords) -end - -function change_bgmodel(facets::SubFacetData,cell_to_newcell) - facet_to_bgcell = lazy_map(Reindex(cell_to_newcell),facets.facet_to_bgcell) - SubFacetData( - facets.facet_to_points, - facets.facet_to_normal, - collect(Int32,facet_to_bgcell), - facets.point_to_coords, - facets.point_to_rcoords) -end - -function remove_ghost_subfacets(cut::EmbeddedFacetDiscretization,facet_gids) - bgfacet_mask = map(!iszero,local_to_owner(facet_gids)) - subfacet_mask = map(Reindex(bgfacet_mask),cut.subfacets.cell_to_bgcell) - new_subfacets = findall(subfacet_mask) - subfacets = SubCellData(cut.subfacets,new_subfacets) - ls_to_subfacet_to_inout = map(cut.ls_to_subfacet_to_inout) do sf_to_io - map(Reindex(sf_to_io),new_subfacets) +function compute_bgfacet_to_inoutcut(cutgeo::DistributedEmbeddedDiscretization,args...) + map(local_views(cutgeo)) do cutgeo + compute_bgfacet_to_inoutcut(cutgeo,args...) end - EmbeddedFacetDiscretization( - cut.bgmodel, - cut.ls_to_facet_to_inoutcut, - subfacets, - ls_to_subfacet_to_inout, - cut.oid_to_ls, - cut.geo) end +# AMR + function compute_redistribute_wights( cut::DistributedEmbeddedDiscretization, args...) diff --git a/src/Distributed/DistributedQuadratures.jl b/src/Distributed/DistributedQuadratures.jl index 06a40a4c..634b7786 100644 --- a/src/Distributed/DistributedQuadratures.jl +++ b/src/Distributed/DistributedQuadratures.jl @@ -1,18 +1,18 @@ function CellData.Measure( - t::DistributedTriangulation, + trian::DistributedTriangulation, quad::Tuple{MomentFitted,Vararg}; kwargs...) @notimplemented name, _args, _kwargs = quad cut,cutfacets,_args... = _args - t = remove_ghost_cells(t) - measures = map( - local_views(t), - local_views(cut), - local_views(cutfacets)) do trian,cut,cutfacets - quad = name, (cut,cutfacets,_args...), _kwargs - Measure(trian,quad;kwargs...) + + model = get_background_model(trian) + gids = get_cell_gids(model) + trian = remove_ghost_cells(trian,gids) + measures = map(local_views(trian),local_views(cut),local_views(cutfacets)) do trian,cut,cutfacets + quad = name, (cut,cutfacets,_args...), _kwargs + Measure(trian,quad;kwargs...) end DistributedMeasure(measures) end diff --git a/src/Distributed/DistributedSubFacetTriangulations.jl b/src/Distributed/DistributedSubFacetTriangulations.jl new file mode 100644 index 00000000..51c0099a --- /dev/null +++ b/src/Distributed/DistributedSubFacetTriangulations.jl @@ -0,0 +1,94 @@ + +const DistributedSubFacetTriangulation{Df,Dc} = DistributedTriangulation{Df,Dc,<:AbstractArray{<:Union{SubFacetTriangulation{Df,Dc},TriangulationView{Df,Dc,<:SubFacetTriangulation{Df,Dc}}}}} + +# Each cut facet belongs to the background cell containing it. So we can generate +# ownership information for the cut facets from the background cell gids. +function GridapDistributed.generate_cell_gids( + trian::DistributedSubFacetTriangulation{Df,Dc}, +) where {Df,Dc} + model = get_background_model(trian) + cgids = get_cell_gids(model) + + n_lfacets, bgcell_to_lfacets = map(local_views(trian)) do trian + model = get_background_model(trian) + lfacet_to_bgcell = get_glue(trian,Val(Dc)).tface_to_mface + n_lfacets = length(lfacet_to_bgcell) + + ptrs = zeros(Int32,num_cells(model)+1) + for bgcell in lfacet_to_bgcell + ptrs[bgcell+1] += 1 + end + Arrays.length_to_ptrs!(ptrs) + @assert ptrs[end] == n_lfacets+1 + + data = zeros(Int32,n_lfacets) + for (lfacet,bgcell) in enumerate(lfacet_to_bgcell) + data[ptrs[bgcell]] = lfacet + ptrs[bgcell] += 1 + end + Arrays.rewind_ptrs!(ptrs) + + return n_lfacets, Table(data,ptrs) + end |> tuple_of_arrays + + return GridapDistributed.generate_gids( + cgids, bgcell_to_lfacets, n_lfacets + ) +end + +function GridapDistributed.add_ghost_cells( + trian::DistributedSubFacetTriangulation{Df,Dc}, +) where {Df,Dc} + + # In this case, we already have all ghost facets + if eltype(local_views(trian)) <: SubFacetTriangulation + return trian + end + + # First, we create a new Triangulation containing all the cut facets + model = get_background_model(trian) + bgtrians, facet_to_bgfacet = map(local_views(trian)) do trian + @assert isa(trian,TriangulationView) + trian.parent, trian.cell_to_parent_cell + end |> tuple_of_arrays + bgtrian = DistributedTriangulation(bgtrians,model) + fgids = partition(generate_cell_gids(bgtrian)) + + # Exchange info about cut facets + inside_facets = map(fgids,facet_to_bgfacet) do fgids, facet_to_bgfacet + inside_facets = falses(local_length(fgids)) + inside_facets[facet_to_bgfacet] .= true + return inside_facets + end + wait(consistent!(PVector(inside_facets,fgids))) # Exchange information + + # Return ghosted Triangulation + covers_all = reduce(&,map(all,inside_facets),init=true) + if covers_all + ghosted_trian = bgtrian + else + ghosted_trian = DistributedTriangulation( + map(TriangulationView,bgtrians,inside_facets), model + ) + end + return ghosted_trian +end + +function num_cells(trian::DistributedSubFacetTriangulation) + model = get_background_model(trian) + Dc = num_cell_dims(model) + gids = get_face_gids(model,Dc) + n_loc_ocells = map(local_views(trian),partition(gids)) do trian, gids + glue = get_glue(trian,Val(Dc)) + @assert isa(glue,FaceToFaceGlue) + tcell_to_mcell = glue.tface_to_mface + if isa(tcell_to_mcell,IdentityVector) + own_length(gids) + else + mcell_to_owned = local_to_own(gids) + is_owned(mcell) = !iszero(mcell_to_owned[mcell]) + sum(is_owned,tcell_to_mcell;init=0) + end + end + return sum(n_loc_ocells) +end diff --git a/src/Distributed/GeometricalDerivatives.jl b/src/Distributed/GeometricalDerivatives.jl new file mode 100644 index 00000000..d6450be5 --- /dev/null +++ b/src/Distributed/GeometricalDerivatives.jl @@ -0,0 +1,56 @@ + + +function remove_ghost_cells( + trian::Union{<:CutFaceBoundaryTriangulation,<:CutFaceSkeletonTriangulation},gids +) + model = get_background_model(trian) + Dm = num_cell_dims(model) + glue = get_glue(trian,Val(Dm)) + remove_ghost_cells(glue,trian,gids) +end + +for func in (:get_subfacet_normal_vector,:get_ghost_normal_vector,:get_conormal_vector) + @eval begin + function $func(a::DistributedTriangulation) + fields = map($func,local_views(a)) + DistributedCellField(fields,a) + end + end +end + +function LevelSetCutters.DifferentiableTriangulation(trian::DistributedTriangulation,fe_space) + model = get_background_model(trian) + trians = map(DifferentiableTriangulation,local_views(trian),local_views(fe_space)) + return DistributedTriangulation(trians,model) +end + +function FESpaces._change_argument( + op,f, + local_trians::AbstractArray{<:Union{<:DifferentiableTriangulation,<:DifferentiableAppendedTriangulation,<:DifferentiableTriangulationView}}, + uh::GridapDistributed.DistributedADTypes +) + function dist_cf(uh::DistributedCellField,cfs) + DistributedCellField(cfs,get_triangulation(uh)) + end + function dist_cf(uh::DistributedMultiFieldCellField,cfs) + sf_cfs = map(DistributedCellField, + [tuple_of_arrays(map(cf -> Tuple(cf.single_fields),cfs))...], + map(get_triangulation,uh) + ) + DistributedMultiFieldCellField(sf_cfs,cfs) + end + + uhs = local_views(uh) + spaces = map(get_fe_space,uhs) + function g(cell_u) + cfs = map(CellField,spaces,cell_u) + cf = dist_cf(uh,cfs) + map(update_trian!,local_trians,spaces,local_views(cf)) + cg = f(cf) + map(local_trians,spaces) do Ω, V + update_trian!(Ω,V,nothing) + end + map(get_contribution,local_views(cg),local_trians) + end + g +end diff --git a/src/GridapEmbedded.jl b/src/GridapEmbedded.jl index 545ae5fd..458700cf 100644 --- a/src/GridapEmbedded.jl +++ b/src/GridapEmbedded.jl @@ -16,4 +16,6 @@ include("Distributed/Distributed.jl") include("Exports.jl") +include("BGP/BGP.jl") + end # module diff --git a/src/Interfaces/CutFaceBoundaryTriangulations.jl b/src/Interfaces/CutFaceBoundaryTriangulations.jl new file mode 100644 index 00000000..872413a4 --- /dev/null +++ b/src/Interfaces/CutFaceBoundaryTriangulations.jl @@ -0,0 +1,432 @@ + +# Ghost triangulations + +function generate_ghost_trian( + trian::CompositeTriangulation, bgmodel +) + Dc = num_cell_dims(bgmodel) + cell_glue = get_glue(trian,Val(Dc)) + return generate_ghost_trian(trian,bgmodel,cell_glue) +end + +function generate_ghost_trian( + trian::CompositeTriangulation, bgmodel, cell_glue::SkeletonPair{<:FaceToFaceGlue} +) + Dc = num_cell_dims(bgmodel) + topo = get_grid_topology(bgmodel) + face_to_cell = get_faces(topo,Dc-1,Dc) + cell_to_face = get_faces(topo,Dc,Dc-1) + + n_bgfaces = num_faces(bgmodel,Dc-1) + n_faces = num_cells(trian) + ghost_faces = zeros(Int32,n_faces) + p_lcell = ones(Int8,n_bgfaces) + m_lcell = ones(Int8,n_bgfaces) + for (i,(p_cell, m_cell)) in enumerate(zip(cell_glue.plus.tface_to_mface,cell_glue.minus.tface_to_mface)) + inter = intersect(cell_to_face[p_cell],cell_to_face[m_cell]) + @assert length(inter) == 1 + face = first(inter) + ghost_faces[i] = face + + nbors = face_to_cell[ghost_faces[i]] + p_lcell[face] = findfirst(==(p_cell),nbors) + m_lcell[face] = findfirst(==(m_cell),nbors) + end + + plus = BoundaryTriangulation(bgmodel,ghost_faces,p_lcell) + minus = BoundaryTriangulation(bgmodel,ghost_faces,m_lcell) + return SkeletonTriangulation(plus,minus) +end + +function generate_ghost_trian( + trian::CompositeTriangulation, bgmodel, cell_glue::FaceToFaceGlue +) + Dc = num_cell_dims(bgmodel) + topo = get_grid_topology(bgmodel) + face_to_cell = get_faces(topo,Dc-1,Dc) + cell_to_face = get_faces(topo,Dc,Dc-1) + is_boundary(f) = isone(length(view(face_to_cell,f))) + + n_faces = num_cells(trian) + ghost_faces = zeros(Int32,n_faces) + for (i,cell) in enumerate(cell_glue.tface_to_mface) + faces = filter(is_boundary,view(cell_to_face,cell)) + @assert length(faces) == 1 # TODO: This will break if we are in a corner + face = first(faces) + ghost_faces[i] = face + end + + # NOTE: lcell is always 1 for boundary facets + return BoundaryTriangulation(bgmodel,ghost_faces) +end + +""" + get_ghost_mask( + face_trian::SubFacetTriangulation{Df,Dc}, + face_model = get_active_model(face_trian) + ) where {Df,Dc} + +Returns a mask for ghost faces. We define ghost faces as the interfaces between two +different cut facets that are located in different background cells. + +The second condition is important: In 3D, some cuts subcells may not be simplices. +In this case, we simplexify the subcell. This creates extra cut interfaces that are +interior to a background cell. These are not considered ghost faces. + +- In 2D: Dc = 2, Df = 1 -> Ghost faces have dimension 0 (i.e interface points) +- In 3D: Dc = 3, Df = 2 -> Ghost faces have dimension 1 (i.e interface edges) +""" +function get_ghost_mask( + face_trian::SubFacetTriangulation{Df,Dc}, + face_model = get_active_model(face_trian) +) where {Df,Dc} + topo = get_grid_topology(face_model) + face_to_facets = get_faces(topo,Df-1,Df) + + subfacets = face_trian.subfacets + facet_to_bgcell = subfacets.facet_to_bgcell + + n_faces = num_faces(topo,Df-1) + face_is_ghost = zeros(Bool,n_faces) + for face in 1:n_faces + facets = view(face_to_facets,face) + is_boundary = isone(length(facets)) + if !is_boundary + @assert length(facets) == 2 + bgcells = view(facet_to_bgcell,facets) + is_ghost = (bgcells[1] != bgcells[2]) + face_is_ghost[face] = is_ghost + end + end + + return face_is_ghost +end + +""" + struct CutFaceBoundaryTriangulation{Di,Df,Dp} <: Triangulation{Di,Dp} + +Triangulation containing the interfaces between subfacets. We always have dimensions + + - Dc :: Dimension of the background mesh + - Df = Dc-1 :: Dimension of the cut subfacets + - Di = Dc-2 :: Dimension of the subfacet interfaces + +# Properties + +- `face_trian` :: Original SubFacetTriangulation, built on top of the background mesh. +- `face_model` :: Subfacet model. Active model for `face_trian`. +- `face_boundary` :: Triangulation of the interfaces between subfacets. It is glued to the `face_model`. +- `cell_boundary` :: Conceptually the same as `face_boundary`, but it is glued to the + background mesh cells. Created as a CompositeTriangulation between `face_trian` and `face_boundary`. +- `ghost_boundary` :: Triangulation of the background facets that contain each interface. + +The "real" triangulation is `cell_boundary`, but we require the other triangulations to +perform complex changes of domain. Most of the `Triangulation` API is delegated to `cell_boundary`. + +## Constructors + + Boundary(face_trian::SubFacetTriangulation) + Skeleton(face_trian::SubFacetTriangulation) + +""" +struct CutFaceBoundaryTriangulation{Di,Df,Dp} <: Triangulation{Di,Dp} + face_model :: UnstructuredDiscreteModel{Df,Dp} + face_trian :: SubFacetTriangulation{Df,Dp} # Cut Facet -> BG Cell + cell_boundary :: CompositeTriangulation{Di,Dp} # Interface -> BG Cell + face_boundary :: BoundaryTriangulation{Di,Dp} # Interface -> Cut Facet + ghost_boundary :: BoundaryTriangulation{Df,Dp} # Ghost Facet -> BG Cell + interface_sign :: AbstractArray{<:Number} +end + +function BoundaryTriangulation(face_trian::SubFacetTriangulation) + bgmodel = get_background_model(face_trian) + face_model = get_active_model(face_trian) + + face_boundary = BoundaryTriangulation(face_model) + cell_boundary = CompositeTriangulation(face_trian,face_boundary) + ghost_boundary = generate_ghost_trian(cell_boundary,bgmodel) + interface_sign = get_interface_sign(cell_boundary,face_trian,ghost_boundary) + + return CutFaceBoundaryTriangulation( + face_model,face_trian,cell_boundary,face_boundary,ghost_boundary,interface_sign + ) +end + +function get_background_model(t::CutFaceBoundaryTriangulation) + get_background_model(t.cell_boundary) +end + +function get_active_model(t::CutFaceBoundaryTriangulation) + get_active_model(t.cell_boundary) +end + +function get_grid(t::CutFaceBoundaryTriangulation) + get_grid(t.cell_boundary) +end + +# Domain changes + +function get_glue(ttrian::CutFaceBoundaryTriangulation{Di,Df,Dp},::Val{D}) where {D,Di,Df,Dp} + get_glue(ttrian.cell_boundary,Val(D)) +end + +function is_change_possible( + strian::SubFacetTriangulation,ttrian::CutFaceBoundaryTriangulation +) + return strian === ttrian.face_trian +end + +function CellData.change_domain( + a::CellField,ttrian::CutFaceBoundaryTriangulation,tdomain::DomainStyle +) + strian = get_triangulation(a) + if strian === ttrian + # 1) CellField defined on the skeleton + return change_domain(a,DomainStyle(a),tdomain) + end + + if is_change_possible(strian,ttrian.cell_boundary) + # 2) CellField defined on the bgmodel + b = change_domain(a,ttrian.cell_boundary,tdomain) + elseif strian === ttrian.face_trian + # 3) CellField defined on the cut facets + itrian = Triangulation(ttrian.face_model) + _a = CellData.similar_cell_field(a,CellData.get_data(a),itrian,DomainStyle(a)) + b = change_domain(_a,ttrian.face_boundary,tdomain) + else + @notimplemented + end + return CellData.similar_cell_field(b,CellData.get_data(b),ttrian,DomainStyle(b)) +end + +function CellData.change_domain( + f::CellData.OperationCellField,ttrian::CutFaceBoundaryTriangulation,tdomain::DomainStyle +) + args = map(i->change_domain(i,ttrian,tdomain),f.args) + CellData.OperationCellField(f.op,args...) +end + +# Normal vector to the cut facets , n_∂Ω +function get_subfacet_normal_vector(trian::CutFaceBoundaryTriangulation) + n_∂Ω = get_subfacet_facet_normal(trian.cell_boundary,trian.face_trian) + return GenericCellField(n_∂Ω,trian,ReferenceDomain()) +end + +# Normal vector to the ghost facets, n_k +function get_ghost_normal_vector(trian::CutFaceBoundaryTriangulation) + n = get_ghost_facet_normal(trian.cell_boundary,trian.ghost_boundary) + return GenericCellField(n,trian,ReferenceDomain()) +end + +# Orientation of the interface +function get_interface_sign(trian::CutFaceBoundaryTriangulation) + data = lazy_map(constant_field,trian.interface_sign) + return GenericCellField(data,trian,ReferenceDomain()) +end + +# TODO: This is only valid when dealing with linear meshes (where normals are constant over facets). +# If we wanted to use higher-order meshes, we would need to generate the geometric map +# going from the facets to the interfaces. +# However, having a high-order background mesh seems quite silly. +function get_ghost_facet_normal( + itrian::CompositeTriangulation{Di,Dc}, # Interface -> BG Cell + gtrian::BoundaryTriangulation{Df,Dc} # Ghost Facet -> BG Cell +) where {Di,Df,Dc} + n_g = get_facet_normal(gtrian) + n_i = lazy_map(evaluate,n_g,Fill(zero(VectorValue{Df,Float64}),num_cells(itrian))) + return lazy_map(constant_field,n_i) +end + +# This one would be fine for higher-order meshes. +function get_subfacet_facet_normal( + itrian::CompositeTriangulation{Di,Dc}, # Interface -> BG Cell + ftrian::SubFacetTriangulation{Df,Dc}, # Cut Facet -> BG Cell +) where {Di,Df,Dc} + glue = get_glue(itrian.dtrian,Val(Df)) + i_to_f_ids = glue.tface_to_mface + i_to_f_map = glue.tface_to_mface_map + n_f = lazy_map(Reindex(get_facet_normal(ftrian)),i_to_f_ids) + n_i = lazy_map(Broadcasting(∘),n_f,i_to_f_map) + return n_i +end + +# There is still something sweaty about this... +# Why do we apply the sign change but at the same time call `get_edge_tangents` in the +# creation of conormal vectors in 3D? It's like we are cancelling the sign change... +# There is more to think about here. +function get_interface_sign( + itrian::CompositeTriangulation{Di,Dc}, # Interface -> BG Cell + ftrian::SubFacetTriangulation{Df,Dc}, # Cut Facet -> BG Cell + gtrian::BoundaryTriangulation{Df,Dc}, # Ghost Facet -> BG Cell +) where {Di,Df,Dc} + function signdot(a,b) + s = sign(dot(a,b)) + return ifelse(iszero(s),1,s) + end + n_∂Ω = get_subfacet_facet_normal(itrian,ftrian) + n_k = get_ghost_facet_normal(itrian,gtrian) + if Di == 0 + cross2D(n) = VectorValue(-n[2],n[1]) + n_S = lazy_map(Operation(cross2D),n_k) + else + t_S = get_edge_tangents(itrian.dtrian) + n_S = lazy_map(Operation(cross),n_k,t_S) + end + sgn = lazy_map(Operation(signdot),n_∂Ω,n_S) + return collect(lazy_map(evaluate,sgn,Fill(zero(VectorValue{Di,Float64}),num_cells(itrian)))) +end + +function get_edge_tangents(trian::BoundaryTriangulation{1}) + function t(c) + @assert length(c) == 2 + t = c[2] - c[1] + return t/norm(t) + end + return lazy_map(constant_field,lazy_map(t,get_cell_coordinates(trian))) +end + +function get_edge_tangents(trian::CutFaceBoundaryTriangulation{1}) + data = get_edge_tangents(trian.face_boundary) + return GenericCellField(data,trian,ReferenceDomain()) +end + +# Normal vector to the cut interface, n_S +function get_normal_vector(trian::CutFaceBoundaryTriangulation{Di}) where {Di} + n_k = get_ghost_normal_vector(trian) + isign = get_interface_sign(trian) + if Di == 0 # 2D + cross2D(n) = VectorValue(-n[2],n[1]) + n_S = Operation(cross2D)(n_k) # nS = nk x tS and tS = ±e₃ in 2D + elseif Di == 1 # 3D + t_S = get_edge_tangents(trian) + n_S = Operation(cross)(n_k,t_S) # nk = tS x nS -> nS = nk x tS (eq 6.25) + else + @notimplemented + end + return n_S * isign +end + +get_facet_normal(trian::CutFaceBoundaryTriangulation) = get_data(get_normal_vector(trian)) + +# Tangent vector to the cut interface, t_S = n_S x n_k +function get_tangent_vector(trian::CutFaceBoundaryTriangulation{Di}) where {Di} + @notimplementedif Di != 1 + n_S = get_normal_vector(trian) + n_k = get_ghost_normal_vector(trian) + return Operation(cross)(n_S,n_k) +end + +# Conormal vectors, m_k = t_S x n_∂Ω +function get_conormal_vector(trian::CutFaceBoundaryTriangulation{Di}) where {Di} + n_∂Ω = get_subfacet_normal_vector(trian) + isign = get_interface_sign(trian) + if Di == 0 # 2D + cross2D(n) = VectorValue(n[2],-n[1]) + m_k = Operation(cross2D)(n_∂Ω) + elseif Di == 1 # 3D + t_S = get_edge_tangents(trian) + m_k = Operation(cross)(t_S,n_∂Ω) # m_k = t_S x n_∂Ω (eq 6.26) + else + @notimplemented + end + return m_k * isign +end + +# CutFaceSkeletonTriangulation & CutFaceBoundaryTriangulationView +const CutFaceBoundaryTriangulationView{Di,Df,Dp} = TriangulationView{Di,Dp,CutFaceBoundaryTriangulation{Di,Df,Dp}} +const CutFaceSkeletonTriangulation{Di,Df,Dp} = SkeletonTriangulation{Di,Dp,<:Union{ + CutFaceBoundaryTriangulation{Di,Df,Dp}, + CutFaceBoundaryTriangulationView{Di,Df,Dp} + } +} + +function SkeletonTriangulation(face_trian::SubFacetTriangulation) + bgmodel = get_background_model(face_trian) + face_model = get_active_model(face_trian) + + ghost_mask = get_ghost_mask(face_trian,face_model) + face_skeleton = SkeletonTriangulation(face_model,ghost_mask) + cell_skeleton = CompositeTriangulation(face_trian,face_skeleton) + ghost_skeleton = generate_ghost_trian(cell_skeleton,bgmodel) + + ctrian_plus = CompositeTriangulation(face_trian,face_skeleton.plus) + ctrian_minus = CompositeTriangulation(face_trian,face_skeleton.minus) + isign_plus = get_interface_sign(ctrian_plus,face_trian,ghost_skeleton.plus) + isign_minus = get_interface_sign(ctrian_plus,face_trian,ghost_skeleton.minus) + + plus = CutFaceBoundaryTriangulation( + face_model,face_trian,ctrian_plus, + face_skeleton.plus,ghost_skeleton.plus,isign_plus + ) + minus = CutFaceBoundaryTriangulation( + face_model,face_trian,ctrian_minus, + face_skeleton.minus,ghost_skeleton.minus,isign_minus + ) + return SkeletonTriangulation(plus,minus) +end + +for func in (:get_subfacet_normal_vector,:get_ghost_normal_vector,:get_conormal_vector) + @eval begin + function $func(trian::CutFaceSkeletonTriangulation) + plus = GenericCellField(CellData.get_data($func(trian.plus)),trian,ReferenceDomain()) + minus = GenericCellField(CellData.get_data($func(trian.minus)),trian,ReferenceDomain()) + return SkeletonPair(plus,minus) + end + end +end + +for func in (:get_normal_vector,:get_tangent_vector) + @eval begin + function $func(trian::CutFaceSkeletonTriangulation) + return GenericCellField(CellData.get_data($func(trian.plus)),trian,ReferenceDomain()) + end + end +end + +for func in (:get_tangent_vector,:get_subfacet_normal_vector,:get_ghost_normal_vector,:get_conormal_vector) + @eval begin + function $func(trian::CutFaceBoundaryTriangulationView) + data = CellData.get_data($func(trian.parent)) + restricted_data = restrict(data,trian.cell_to_parent_cell) + return GenericCellField(restricted_data,trian,ReferenceDomain()) + end + end +end + +############################################################################################ +# This will go to Gridap +# +# function Arrays.evaluate!(cache,k::Operation,a::SkeletonPair{<:CellField}) +# plus = k(a.plus) +# minus = k(a.minus) +# SkeletonPair(plus,minus) +# end + +# function Arrays.evaluate!(cache,k::Operation,a::SkeletonPair{<:CellField},b::SkeletonPair{<:CellField}) +# plus = k(a.plus,b.plus) +# minus = k(a.minus,b.minus) +# SkeletonPair(plus,minus) +# end + +# import Gridap.TensorValues: inner, outer +# import LinearAlgebra: dot +# import Base: abs, *, +, -, / + +# for op in (:/,) +# @eval begin +# ($op)(a::CellField,b::SkeletonPair{<:CellField}) = Operation($op)(a,b) +# ($op)(a::SkeletonPair{<:CellField},b::CellField) = Operation($op)(a,b) +# end +# end + +# for op in (:outer,:*,:dot,:/) +# @eval begin +# ($op)(a::SkeletonPair{<:CellField},b::SkeletonPair{<:CellField}) = Operation($op)(a,b) +# end +# end + +# function CellData.change_domain(a::SkeletonPair, ::ReferenceDomain, ::PhysicalDomain) +# plus = change_domain(a.plus,ReferenceDomain(),PhysicalDomain()) +# minus = change_domain(a.minus,ReferenceDomain(),PhysicalDomain()) +# return SkeletonPair(plus,minus) +# end diff --git a/src/Interfaces/Cutters.jl b/src/Interfaces/Cutters.jl index 5104430d..2fca363e 100644 --- a/src/Interfaces/Cutters.jl +++ b/src/Interfaces/Cutters.jl @@ -1,17 +1,60 @@ + +""" + abstract type Cutter <: GridapType end + +Abstract type for all mesh cutters. Has to be paired with a [`CSG.Geometry`](@ref) to +cut the background mesh. + +## Methods + +- [`cut(cutter::Cutter,background,geom)`](@ref) +- [`cut_facets(cutter::Cutter,background,geom)`](@ref) +- [`compute_bgcell_to_inoutcut(cutter::Cutter,background,geom)`](@ref) +- [`compute_bgfacet_to_inoutcut(cutter::Cutter,background,geom)`](@ref) + +Generally `cut` and `cut_facets` dispatch based on the geometry provided, so it is +generally more convennient to call the following methods instead: + +- [`cut(background,geom)`](@ref) +- [`cut_facets(background,geom)`](@ref) + +""" abstract type Cutter <: GridapType end +""" + cut(cutter::Cutter,background,geom) + +Cut the background mesh with the provided cutter and geometry, returnning the cut cells. +The cut cells are returned as an [`EmbeddedDiscretization`](@ref) object. +""" function cut(cutter::Cutter,background,geom) @abstractmethod end +""" + compute_bgcell_to_inoutcut(cutter::Cutter,background,geom) + +Returns an array of IN/OUT/CUT states for each cell in the background mesh. +""" function compute_bgcell_to_inoutcut(cutter::Cutter,background,geom) @abstractmethod end +""" + cut_facets(cutter::Cutter,background,geom) + +Cut the background mesh with the provided cutter and geometry, returning the cut facets. +The cut facets are returned as an [`EmbeddedFacetDiscretization`](@ref) object. +""" function cut_facets(cutter::Cutter,background,geom) @abstractmethod end +""" + compute_bgfacet_to_inoutcut(cutter::Cutter,background,geom) + +Returns an array of IN/OUT/CUT states for each facet in the background mesh. +""" function compute_bgfacet_to_inoutcut(cutter::Cutter,background,geom) @abstractmethod end diff --git a/src/Interfaces/EmbeddedDiscretizations.jl b/src/Interfaces/EmbeddedDiscretizations.jl index 9dcf6237..82c9486b 100644 --- a/src/Interfaces/EmbeddedDiscretizations.jl +++ b/src/Interfaces/EmbeddedDiscretizations.jl @@ -1,12 +1,44 @@ - +""" + abstract type EmbeddedDiscretization <: GridapType +""" abstract type AbstractEmbeddedDiscretization <: GridapType end -struct EmbeddedDiscretization{Dp,T} <: AbstractEmbeddedDiscretization +""" + struct EmbeddedDiscretization{Dc,T} <: AbstractEmbeddedDiscretization + +This structure contains all the required information to build integration `Triangulation`s +for a cut model. + +## Constructors + + cut(cutter::Cutter,background,geom) + +## Properties + +- `bgmodel::DiscreteModel`: the background mesh +- `geo::CSG.Geometry`: the geometry used to cut the background mesh +- `subcells::SubCellData`: collection of cut subcells, attached to the background mesh +- `subfacets::SubFacetData`: collection of cut facets, attached to the background mesh +- `ls_to_bgcell_to_inoutcut::Vector{Vector{Int8}}`: list of IN/OUT/CUT states for each cell + in the background mesh, for each node in the geometry tree. +- `ls_to_subcell_to_inoutcut::Vector{Vector{Int8}}`: list of IN/OUT/CUT states for each subcell + in the cut part of the mesh, for each node in the geometry tree. +- `ls_to_subfacet_to_inoutcut::Vector{Vector{Int8}}`: list of IN/OUT/CUT states for each subfacet + in the cut part of the mesh, for each node in the geometry tree. + +## Methods + +- [`Triangulation(cut::EmbeddedDiscretization,in_or_out)`](@ref) +- [`EmbeddedBoundary(cut::EmbeddedDiscretization)`](@ref) +- [`GhostSkeleton(cut::EmbeddedDiscretization)`](@ref) + +""" +struct EmbeddedDiscretization{Dc,T} <: AbstractEmbeddedDiscretization bgmodel::DiscreteModel ls_to_bgcell_to_inoutcut::Vector{Vector{Int8}} - subcells::SubCellData{Dp,Dp,T} + subcells::SubCellData{Dc,Dc,T} ls_to_subcell_to_inout::Vector{Vector{Int8}} - subfacets::SubFacetData{Dp,T} + subfacets::SubFacetData{Dc,T} ls_to_subfacet_to_inout::Vector{Vector{Int8}} oid_to_ls::Dict{UInt,Int} geo::CSG.Geometry @@ -224,6 +256,18 @@ function Triangulation(cut::EmbeddedDiscretization) Triangulation(cut,PHYSICAL_IN,cut.geo) end +""" + Triangulation(cut::EmbeddedDiscretization[,in_or_out=PHYSICAL_IN]) + +Creates a triangulation containing the cell and subcells of the embedded domain selected by +`in_or_out`. + +- If only background cells are selected, the result will be a regular Gridap triangulation. +- If only subcells are selected, the result will be a [`SubCellTriangulation`](@ref). +- If both background cells and subcells are selected, the result will be an `AppendedTriangulation`, + containing a [`SubCellTriangulation`](@ref) and a regular Gridap triangulation. + +""" function Triangulation(cut::EmbeddedDiscretization,in_or_out) Triangulation(cut,in_or_out,cut.geo) end @@ -355,6 +399,12 @@ function _compute_inout_complementary(inout_1) end end +""" + EmbeddedBoundary(cut::EmbeddedDiscretization) + +Creates a triangulation containing the cut facets of the embedded domain boundary. +The result is a [`SubFacetTriangulation`](@ref). +""" function EmbeddedBoundary(cut::EmbeddedDiscretization) EmbeddedBoundary(cut,cut.geo) end @@ -381,7 +431,6 @@ function EmbeddedBoundary(cut::EmbeddedDiscretization,geo::CSG.Geometry) neworientation = orientation[newsubfacets] fst = SubFacetData(cut.subfacets,newsubfacets,neworientation) SubFacetTriangulation(fst,cut.bgmodel) - end function EmbeddedBoundary(cut::EmbeddedDiscretization,name1::String,name2::String) @@ -411,9 +460,16 @@ function EmbeddedBoundary(cut::EmbeddedDiscretization,geo1::CSG.Geometry,geo2::C neworientation = orientation[newsubfacets] fst = SubFacetData(cut.subfacets,newsubfacets,neworientation) SubFacetTriangulation(fst,cut.bgmodel) - end +""" + GhostSkeleton(cut::EmbeddedDiscretization[,in_or_out=ACTIVE_IN]) + +Creates a triangulation containing the ghost facets. Ghosts facets are defined as the facets +of the **background mesh** that are adjacent to at least one `CUT` background cell. + +Mostly used for CUT-FEM stabilisation. +""" function GhostSkeleton(cut::EmbeddedDiscretization) GhostSkeleton(cut,ACTIVE_IN) end diff --git a/src/Interfaces/EmbeddedFacetDiscretizations.jl b/src/Interfaces/EmbeddedFacetDiscretizations.jl index 4086fb69..eb71d9df 100644 --- a/src/Interfaces/EmbeddedFacetDiscretizations.jl +++ b/src/Interfaces/EmbeddedFacetDiscretizations.jl @@ -1,4 +1,30 @@ +""" + struct EmbeddedFacetDiscretization{Dc,Dp,T} <: AbstractEmbeddedDiscretization + +This structure contains all the required information to build integration `Triangulations` +for a cut model boundary. + +## Constructors + + cut_facets(cutter::Cutter,background,geom) + +## Properties + +- `bgmodel::DiscreteModel`: the background mesh +- `geo::CSG.Geometry`: the geometry used to cut the background mesh +- `subfacets::SubFacetData`: collection of cut facets, attached to the background mesh +- `ls_to_facet_to_inoutcut::Vector{Vector{Int8}}`: list of IN/OUT/CUT states for each facet + in the background mesh, for each node in the geometry tree. +- `ls_to_subfacet_to_inoutcut::Vector{Vector{Int8}}`: list of IN/OUT/CUT states for each subfacet + in the cut part of the mesh, for each node in the geometry tree. + +## Methods + +- [`BoundaryTriangulation(cut::EmbeddedFacetDiscretization,in_or_out)`](@ref) +- [`SkeletonTriangulation(cut::EmbeddedFacetDiscretization,in_or_out)`](@ref) + +""" struct EmbeddedFacetDiscretization{Dc,Dp,T} <: AbstractEmbeddedDiscretization bgmodel::DiscreteModel{Dp,Dp} ls_to_facet_to_inoutcut::Vector{Vector{Int8}} @@ -20,6 +46,9 @@ function SkeletonTriangulation(cut::EmbeddedFacetDiscretization) SkeletonTriangulation(cut,PHYSICAL_IN) end +""" + SkeletonTriangulation(cut::EmbeddedFacetDiscretization[, in_or_out=PHYSICAL_IN]) +""" function SkeletonTriangulation( cut::EmbeddedFacetDiscretization, in_or_out) @@ -75,6 +104,9 @@ function BoundaryTriangulation( BoundaryTriangulation(cut,PHYSICAL_IN;tags=tags) end +""" + BoundaryTriangulation(cut::EmbeddedFacetDiscretization[, in_or_out=PHYSICAL_IN; tags=nothing]) +""" function BoundaryTriangulation( cut::EmbeddedFacetDiscretization, in_or_out; @@ -126,10 +158,9 @@ function BoundaryTriangulation( in_or_out::Tuple, geo::CSG.Geometry) - trian1 = BoundaryTriangulation(facets,cut,in_or_out[1],geo) - trian2 = BoundaryTriangulation(facets,cut,in_or_out[2],geo) - num_cells(trian1) == 0 ? trian2 : lazy_append(trian1,trian2) - + a = BoundaryTriangulation(facets,cut,in_or_out[1],geo) + b = BoundaryTriangulation(facets,cut,in_or_out[2],geo) + iszero(num_cells(a)) ? b : lazy_append(a,b) end function BoundaryTriangulation( @@ -139,7 +170,7 @@ function BoundaryTriangulation( geo::CSG.Geometry) bgfacet_to_inoutcut = compute_bgfacet_to_inoutcut(cut,geo) - bgfacet_to_mask = lazy_map( a->a==in_or_out, bgfacet_to_inoutcut) + bgfacet_to_mask = lazy_map(isequal(in_or_out), bgfacet_to_inoutcut) _restrict_boundary_triangulation(cut.bgmodel,facets,bgfacet_to_mask) end @@ -161,7 +192,7 @@ function BoundaryTriangulation( geo::CSG.Geometry) bgfacet_to_inoutcut = compute_bgfacet_to_inoutcut(cut,geo) - bgfacet_to_mask = lazy_map( a->a==CUT, bgfacet_to_inoutcut) + bgfacet_to_mask = lazy_map(isequal(CUT), bgfacet_to_inoutcut) facets = _restrict_boundary_triangulation(cut.bgmodel,_facets,bgfacet_to_mask) facet_to_bgfacet = facets.glue.face_to_bgface @@ -173,7 +204,7 @@ function BoundaryTriangulation( _subfacet_to_facet = lazy_map(Reindex(bgfacet_to_facet),cut.subfacets.cell_to_bgcell) subfacet_to_inout = compute_subfacet_to_inout(cut,geo) - pred(a,b,c) = c != 0 && a==CUT && b==in_or_out.in_or_out + pred(a,b,c) = !iszero(c) && a==CUT && b==in_or_out.in_or_out mask = lazy_map( pred, subfacet_to_inoutcut, subfacet_to_inout, _subfacet_to_facet ) newsubfacets = findall(mask) subfacets = SubCellData(cut.subfacets,newsubfacets) @@ -183,14 +214,16 @@ function BoundaryTriangulation( end function _restrict_boundary_triangulation(model,facets,bgfacet_to_mask) - facet_to_bgfacet = facets.glue.face_to_bgface - facet_to_mask = lazy_map(Reindex(bgfacet_to_mask),facet_to_bgfacet) + n_bgfacets = length(bgfacet_to_mask) - bgfacet_to_mask2 = fill(false,n_bgfacets) - bgfacet_to_mask2[facet_to_bgfacet] .= facet_to_mask + new_bgfacet_to_mask = fill(false,n_bgfacets) + new_bgfacet_to_mask[facet_to_bgfacet] .= view(bgfacet_to_mask,facet_to_bgfacet) - BoundaryTriangulation(model,bgfacet_to_mask2,facets.glue.bgface_to_lcell) + new_bgfacet_to_lcell = fill(Int8(1),n_bgfacets) + new_bgfacet_to_lcell[facet_to_bgfacet] .= facets.glue.face_to_lcell + + BoundaryTriangulation(model,new_bgfacet_to_mask,new_bgfacet_to_lcell) end function compute_bgfacet_to_inoutcut(cut::EmbeddedFacetDiscretization,geo::CSG.Geometry) @@ -225,6 +258,21 @@ function compute_subfacet_to_inout(cut::EmbeddedFacetDiscretization,geo::CSG.Geo compute_inoutcut(newtree) end +""" + struct SubFacetBoundaryTriangulation{Dc,Dp,T} <: Triangulation{Dc,Dp} + +Triangulation of cut facets from the background mesh, i.e each of the facets +in this triangulation is part of a background facet that has been cut by the geometry. + +This differs from the the `SubFacetTriangulation` in that the facets in the `SubFacetTriangulation` +are not cut background facets, but rather subfacets on the interior of a background cell. + +They result from calling `Boundary` or `Skeleton` on an `EmbeddedFacetDiscretization` object. + + BoundaryTriangulation(cut::EmbeddedFacetDiscretization,in_or_out;tags=nothing) + SkeletonTriangulation(cut::EmbeddedFacetDiscretization,in_or_out) + +""" struct SubFacetBoundaryTriangulation{Dc,Dp,T} <: Triangulation{Dc,Dp} facets::BoundaryTriangulation{Dc,Dp} subfacets::SubCellData{Dc,Dp,T} diff --git a/src/Interfaces/Interfaces.jl b/src/Interfaces/Interfaces.jl index 8f351d66..c46961dd 100644 --- a/src/Interfaces/Interfaces.jl +++ b/src/Interfaces/Interfaces.jl @@ -1,5 +1,8 @@ module Interfaces +using FillArrays + +using Gridap using Gridap.Helpers using Gridap.Arrays using Gridap.Fields @@ -21,12 +24,21 @@ import Gridap.Geometry: get_reffes import Gridap.Geometry: get_cell_type import Gridap.Geometry: get_background_model import Gridap.Geometry: get_active_model +import Gridap.Geometry: compute_active_model import Gridap.Geometry: get_glue import Gridap.Geometry: get_grid import Gridap.Geometry: FaceToFaceGlue import Gridap.Geometry: get_facet_normal import Gridap.Geometry: move_contributions +import Gridap.Geometry: is_change_possible using Gridap.Geometry: GenericTriangulation +using Gridap.Geometry: CompositeTriangulation +using Gridap.Geometry: TriangulationView +using Gridap.Geometry: restrict + +import Gridap.CellData: get_normal_vector +import Gridap.CellData: get_tangent_vector +import Gridap.Geometry: get_facet_normal using GridapEmbedded.CSG @@ -84,6 +96,8 @@ include("EmbeddedDiscretizations.jl") include("EmbeddedFacetDiscretizations.jl") +include("CutFaceBoundaryTriangulations.jl") + include("Cutters.jl") function Simplex(p::Polytope) diff --git a/src/Interfaces/SubCellTriangulations.jl b/src/Interfaces/SubCellTriangulations.jl index aa5139ce..dbcf2404 100644 --- a/src/Interfaces/SubCellTriangulations.jl +++ b/src/Interfaces/SubCellTriangulations.jl @@ -18,6 +18,11 @@ end # Implementation of Triangulation interface +""" + struct SubCellTriangulation{Dc,Dp} <: Triangulation{Dc,Dp} + +A triangulation for subcells. +""" struct SubCellTriangulation{Dc,Dp,T,A} <: Triangulation{Dc,Dp} subcells::SubCellData{Dc,Dp,T} bgmodel::A diff --git a/src/Interfaces/SubFacetTriangulations.jl b/src/Interfaces/SubFacetTriangulations.jl index 39ae92e2..3cc1bbb2 100644 --- a/src/Interfaces/SubFacetTriangulations.jl +++ b/src/Interfaces/SubFacetTriangulations.jl @@ -22,6 +22,11 @@ end # Implementation of the Gridap.Triangulation interface +""" + struct SubFacetTriangulation{Dc,Dp,T,A} <: Triangulation{Dc,Dp} + +A triangulation for subfacets. +""" struct SubFacetTriangulation{Dc,Dp,T,A} <: Triangulation{Dc,Dp} subfacets::SubFacetData{Dp,T} bgmodel::A @@ -39,14 +44,6 @@ function get_background_model(a::SubFacetTriangulation) a.bgmodel end -function get_active_model(a::SubFacetTriangulation) - msg = """ - This is not implemented, but also not needed in practice. - Embedded Grids implemented for integration, not interpolation. - """ - @notimplemented msg -end - function get_grid(a::SubFacetTriangulation) a.subgrid end @@ -88,6 +85,49 @@ function move_contributions(scell_to_val::AbstractArray,strian::SubFacetTriangul acell_to_val, Ωa end +# Compute the active model +# To do this, we need to glue together the subfacets, which unfortunately requires comparing +# the point coordinates... +function compute_active_model(trian::SubFacetTriangulation) + subgrid = trian.subgrid + subfacets = trian.subfacets + facet_to_uids, uid_to_point = consistent_facet_to_points( + subfacets.facet_to_points, subfacets.point_to_coords + ) + topo = UnstructuredGridTopology( + subgrid, facet_to_uids, uid_to_point + ) + return UnstructuredDiscreteModel(subgrid,topo,FaceLabeling(topo)) +end + +function consistent_facet_to_points( + facet_to_points::Table, point_to_coords::Vector +) + f(pt::VectorValue) = VectorValue(round.(pt.data;sigdigits=12)) + f(id::Integer) = f(point_to_coords[id]) + + # Create a list of the unique points composing the facets + npts = length(point_to_coords) + nfaces = length(facet_to_points) + touched = zeros(Bool,npts) + for face in 1:nfaces + pts = view(facet_to_points,face) + touched[pts] .= true + end + touched_ids = findall(touched) + unique_ids = unique(f,touched_ids) + + # Create a mapping from the old point ids to the new ones + touched_to_uid = collect(Int32,indexin(f.(touched_ids),f.(unique_ids))) + point_to_uid = extend(touched_to_uid,PosNegPartition(touched_ids,npts)) + + facet_to_uids = Table( + collect(Int32,lazy_map(Reindex(point_to_uid),facet_to_points.data)), + facet_to_points.ptrs + ) + return facet_to_uids, unique_ids +end + # API function UnstructuredGrid(st::SubFacetData{Dp}) where Dp diff --git a/src/LevelSetCutters/AnalyticalGeometries.jl b/src/LevelSetCutters/AnalyticalGeometries.jl index 48485ed5..a34103a3 100644 --- a/src/LevelSetCutters/AnalyticalGeometries.jl +++ b/src/LevelSetCutters/AnalyticalGeometries.jl @@ -1,4 +1,33 @@ +""" + struct AnalyticalGeometry <: CSG.Geometry + tree::Node + end + +A structure to represent analytical geometries, used to cut background meshes. + +## Constructor + + AnalyticalGeometry(f::Function;name=string(nameof(f))) + +where `f: Ω -> R ` is a function that, similiarly to a level set function, is negative inside the +geometry and positive outside. + +## Predefined geometries + + doughnut(R,r;x0=zero(Point{3,typeof(R)}),name="doughnut") + popcorn(r0=0.6, σ=0.2, A=2, x0=zero(Point{3,typeof(r0)}), name="popcorn") + sphere(R;x0=zero(Point{3,eltype(R)}),name="sphere") + disk(R;x0=zero(Point{2,eltype(R)}),name="disk") + cylinder(R;x0=zero(Point{3,eltype(R)}),v=VectorValue(1,0,0),name="cylinder") + plane(x0=Point(0,0,0),v=VectorValue(1,0,0),name="plane") + square(L=1,x0=Point(0,0),name="square",edges=["edge_i" for i in 1:4]) + quadrilateral(x0=Point(0,0),d1=VectorValue(1,0),d2=VectorValue(0,1),name="quadrilateral") + cube(L=1,x0=Point(0,0,0),name="cube") + tube(R,L;x0=zero(Point{3,typeof(R)}),v=VectorValue(1,0,0),name="tube") + olympic_rings(R,r,name="olympic_rings") + +""" struct AnalyticalGeometry <: CSG.Geometry tree::Node end @@ -14,8 +43,8 @@ struct BoundingBox{D,T} pmax::Point{D,T} end -function AnalyticalGeometry(f::Function) - tree = Leaf((f,string(nameof(f)),nothing)) +function AnalyticalGeometry(f::Function;name=string(nameof(f))) + tree = Leaf((f,name,nothing)) AnalyticalGeometry(tree) end diff --git a/src/LevelSetCutters/CutTriangulations.jl b/src/LevelSetCutters/CutTriangulations.jl index f1bd1f69..17068706 100644 --- a/src/LevelSetCutters/CutTriangulations.jl +++ b/src/LevelSetCutters/CutTriangulations.jl @@ -149,10 +149,14 @@ function cut_sub_triangulation(m::CutTriangulation, mpoint_to_value) end function count_sub_triangulation(m,mpoint_to_value) - n_scells = 0 - n_spoints = 0 mcell_to_mpoints = get_cell_to_points(m) table = get_lookup_table(m) + count_sub_triangulation(table,mcell_to_mpoints,mpoint_to_value) +end + +function count_sub_triangulation(table,mcell_to_mpoints,mpoint_to_value) + n_scells = 0 + n_spoints = 0 for mcell in 1:length(mcell_to_mpoints) case = compute_case(mcell_to_mpoints,mpoint_to_value,mcell) n_scells += length(table.case_to_subcell_to_inout[case]) @@ -444,24 +448,18 @@ end function initial_sub_triangulation(grid::Grid,geom::DiscreteGeometry) ugrid = UnstructuredGrid(grid) - tree = get_tree(geom) - ls_to_point_to_value, oid_to_ls = _find_unique_leaves(tree) + ls_to_point_to_value, oid_to_ls = _find_unique_leaves(get_tree(geom)) out = _initial_sub_triangulation(ugrid,ls_to_point_to_value) out[1], out[2], out[3], oid_to_ls end function _initial_sub_triangulation(grid::UnstructuredGrid,ls_to_point_to_value) - cutgrid, ls_to_cutpoint_to_value, ls_to_bgcell_to_inoutcut = _extract_grid_of_cut_cells(grid,ls_to_point_to_value) - subtrian, ls_to_subpoint_to_value = _simplexify_and_isolate_cells_in_cutgrid(cutgrid,ls_to_cutpoint_to_value) - - subtrian, ls_to_subpoint_to_value, ls_to_bgcell_to_inoutcut + return subtrian, ls_to_subpoint_to_value, ls_to_bgcell_to_inoutcut end function _extract_grid_of_cut_cells(grid,ls_to_point_to_value) - - p = _check_and_get_polytope(grid) table = LookupTable(p) cell_to_points = get_cell_node_ids(grid) @@ -682,4 +680,4 @@ function _ensure_positive_jacobians_facets_work!(tcell_to_tpoints,c1,c2,tjac_q, tcell_to_tpoints.data[p+2] = p1 end end -end \ No newline at end of file +end diff --git a/src/LevelSetCutters/DifferentiableTriangulations.jl b/src/LevelSetCutters/DifferentiableTriangulations.jl new file mode 100644 index 00000000..3328f493 --- /dev/null +++ b/src/LevelSetCutters/DifferentiableTriangulations.jl @@ -0,0 +1,516 @@ + + +""" + mutable struct DifferentiableTriangulation{Dc,Dp} <: Triangulation{Dc,Dp} + +A DifferentiableTriangulation is a wrapper around an embedded triangulation +(i.e SubCellTriangulation or SubFacetTriangulation) implementing all the necessary +methods to compute derivatives w.r.t. deformations of the embedded mesh. + +To do so, it propagates dual numbers into the geometric maps mapping cut subcells/subfacets +to the background mesh. + +## Constructor: + + DifferentiableTriangulation(trian::Triangulation,fe_space::FESpace) + +where `trian` must be an embedded triangulation and `fe_space` is the `FESpace` where +the level-set function lives. + +""" +mutable struct DifferentiableTriangulation{Dc,Dp,A,B} <: Triangulation{Dc,Dp} + trian :: A + fe_space :: B + cell_values + caches + function DifferentiableTriangulation( + trian :: Triangulation{Dc,Dp}, + fe_space :: FESpace, + cell_values,caches + ) where {Dc,Dp} + A = typeof(trian) + B = typeof(fe_space) + new{Dc,Dp,A,B}(trian,fe_space,cell_values,caches) + end +end + +# Constructors + +DifferentiableTriangulation(trian::Triangulation,fe_space) = trian + +function DifferentiableTriangulation( + trian::Union{<:SubCellTriangulation,<:SubFacetTriangulation}, + fe_space::FESpace +) + caches = precompute_autodiff_caches(trian) + return DifferentiableTriangulation(trian,fe_space,nothing,caches) +end + +# Update cell values + +(t::DifferentiableTriangulation)(φh) = update_trian!(t,get_fe_space(φh),φh) + +update_trian!(trian::Triangulation,U,φh) = trian + +function update_trian!(trian::DifferentiableTriangulation,space::FESpace,φh) + (trian.fe_space !== space) && return trian + trian.cell_values = extract_dualized_cell_values(trian.trian,φh) + return trian +end + +function update_trian!(trian::DifferentiableTriangulation,::FESpace,::Nothing) + trian.cell_values = nothing + return trian +end + +const MultiFieldSpaceTypes = Union{<:MultiFieldFESpace,<:DistributedMultiFieldFESpace} + +function update_trian!(trian::DifferentiableTriangulation,space::MultiFieldSpaceTypes,φh) + map((Ui,φi)->update_trian!(trian,Ui,φi),space,φh) + return trian +end + +function update_trian!(trian::DifferentiableTriangulation,::MultiFieldSpaceTypes,::Nothing) + trian.cell_values = nothing + return trian +end + +# Autodiff + +function FESpaces._change_argument( + op,f,trian::DifferentiableTriangulation,uh +) + U = get_fe_space(uh) + function g(cell_u) + cf = CellField(U,cell_u) + update_trian!(trian,U,cf) + cell_grad = f(cf) + update_trian!(trian,U,nothing) # TODO: experimental + get_contribution(cell_grad,trian) + end + g +end + +function FESpaces._compute_cell_ids(uh,ttrian::DifferentiableTriangulation) + FESpaces._compute_cell_ids(uh,ttrian.trian) +end + +function Geometry.get_background_model(t::DifferentiableTriangulation) + get_background_model(t.trian) +end + +function Geometry.get_grid(t::DifferentiableTriangulation) + get_grid(t.trian) +end + +function Geometry.get_cell_reffe(t::DifferentiableTriangulation) + get_cell_reffe(t.trian) +end + +# TODO: Do we ever need to dualize the cell points? +# I think its not necessary, since all the dual numbers are propagated through the cellmaps... +# Also: The current version dualizes only the phys points... +# If we want to indeed dualize this, we should probably also dualize the ref points +# in the case where ttrian.trian is a SubCellTriangulation (but not in the case of a SubFacetTriangulation) +# Anyway, I don't think this matters for now... +function CellData.get_cell_points(ttrian::DifferentiableTriangulation) + pts = get_cell_points(ttrian.trian) + cell_ref_point = pts.cell_ref_point + if isnothing(ttrian.cell_values) || isempty(ttrian.cell_values) + cell_phys_point = pts.cell_phys_point + else + c = ttrian.caches + cell_phys_point = lazy_map( + DualizeCoordsMap(),c.face_to_coords,c.face_to_bgcoords, + ttrian.cell_values,c.face_to_edges,c.face_to_edge_lists + ) + end + return CellPoint(cell_ref_point, cell_phys_point, ttrian, DomainStyle(pts)) +end + +function Geometry.get_cell_map(ttrian::DifferentiableTriangulation) + if isnothing(ttrian.cell_values) || isempty(ttrian.cell_values) + return get_cell_map(ttrian.trian) + end + c = ttrian.caches + cell_values = ttrian.cell_values + cell_to_coords = lazy_map( + DualizeCoordsMap(),c.face_to_coords,c.face_to_bgcoords, + cell_values,c.face_to_edges,c.face_to_edge_lists + ) + cell_reffe = get_cell_reffe(ttrian) + cell_map = compute_cell_maps(cell_to_coords,cell_reffe) + return cell_map +end + +function face_normal(face_coords::Vector{<:Point},orientation::Int8) + n = face_normal(face_coords) + return n*orientation +end +function face_normal(face_coords::Vector{<:Point{2}}) + p1, p2 = face_coords[1:2] + LevelSetCutters._normal_vector(p2-p1) +end +function face_normal(face_coords::Vector{<:Point{3}}) + p1, p2, p3 = face_coords[1:3] + LevelSetCutters._normal_vector(p2-p1,p3-p1) +end + +function Geometry.get_facet_normal( + ttrian::DifferentiableTriangulation{Dc,Dp,<:SubFacetTriangulation} +) where {Dc,Dp} + if isnothing(ttrian.cell_values) || isempty(ttrian.cell_values) + return get_facet_normal(ttrian.trian) + end + c = ttrian.caches + cell_values = ttrian.cell_values + cell_to_coords = lazy_map( + DualizeCoordsMap(),c.face_to_coords,c.face_to_bgcoords, + cell_values,c.face_to_edges,c.face_to_edge_lists + ) + facet_normals = lazy_map(face_normal,cell_to_coords,c.orientations) + return lazy_map(constant_field,facet_normals) +end + +function Geometry.get_glue(ttrian::DifferentiableTriangulation,val::Val{D}) where {D} + glue = get_glue(ttrian.trian,val) + if isnothing(glue) || isnothing(ttrian.cell_values) || isempty(ttrian.cell_values) + return glue + end + + # New reference maps + c = ttrian.caches + cell_values = ttrian.cell_values + cell_to_rcoords = lazy_map( + DualizeCoordsMap(),c.face_to_rcoords,c.face_to_bgrcoords, + cell_values,c.face_to_edges,c.face_to_edge_lists + ) + cell_reffe = get_cell_reffe(ttrian) + ref_cell_map = compute_cell_maps(cell_to_rcoords,cell_reffe) + + return FaceToFaceGlue( + glue.tface_to_mface, + ref_cell_map, + glue.mface_to_tface, + ) +end + +function Geometry.is_change_possible( + strian::A,ttrian::DifferentiableTriangulation{Dc,Dp,A} +) where {Dc,Dp,A <: Union{SubCellTriangulation,SubFacetTriangulation}} + return strian === ttrian.trian +end + +function Geometry.best_target( + strian::A,ttrian::DifferentiableTriangulation{Dc,Dp,A} +) where {Dc,Dp,A <: Union{SubCellTriangulation,SubFacetTriangulation}} + return ttrian +end + +for tdomain in (:ReferenceDomain,:PhysicalDomain) + for sdomain in (:ReferenceDomain,:PhysicalDomain) + @eval begin + function CellData.change_domain( + a::CellField,strian::A,::$sdomain,ttrian::DifferentiableTriangulation{Dc,Dp,A},::$tdomain + ) where {Dc,Dp,A <: Union{SubCellTriangulation,SubFacetTriangulation}} + @assert is_change_possible(strian,ttrian) + b = change_domain(a,$(tdomain)()) + return CellData.similar_cell_field(a,CellData.get_data(b),ttrian,$(tdomain)()) + end + end + end +end + +function FESpaces.get_cell_fe_data(fun,f,ttrian::DifferentiableTriangulation) + FESpaces.get_cell_fe_data(fun,f,ttrian.trian) +end + +function compute_cell_maps(cell_coords,cell_reffes) + cell_shapefuns = lazy_map(get_shapefuns,cell_reffes) + default_cell_map = lazy_map(linear_combination,cell_coords,cell_shapefuns) + default_cell_grad = lazy_map(∇,default_cell_map) + cell_poly = lazy_map(get_polytope,cell_reffes) + cell_q0 = lazy_map(p->zero(first(get_vertex_coordinates(p))),cell_poly) + origins = lazy_map(evaluate,default_cell_map,cell_q0) + gradients = lazy_map(evaluate,default_cell_grad,cell_q0) + cell_map = lazy_map(Fields.affine_map,gradients,origins) + return cell_map +end + +# DualizeCoordsMap + +struct DualizeCoordsMap <: Map end + +function Arrays.return_cache( + k::DualizeCoordsMap, + coords::Vector{<:Point{Dp,Tp}}, + bg_coords::Vector{<:Point{Dp,Tp}}, + values::Vector{Tv}, + edges::Vector{Int8}, + edge_list::Vector{Vector{Int8}} +) where {Dp,Tp,Tv} + T = Point{Dp,Tv} + return CachedArray(zeros(T, length(coords))) +end + +function Arrays.evaluate!( + cache, + k::DualizeCoordsMap, + coords::Vector{<:Point{Dp,Tp}}, + bg_coords::Vector{<:Point{Dp,Tp}}, + values::Vector{Tv}, + edges::Vector{Int8}, + edge_list::Vector{Vector{Int8}} +) where {Dp,Tp,Tv} + setsize!(cache,(length(coords),)) + new_coords = cache.array + for (i,e) in enumerate(edges) + if e == -1 + new_coords[i] = coords[i] + else + n1, n2 = edge_list[e] + q1, q2 = bg_coords[n1], bg_coords[n2] + v1, v2 = abs(values[n1]), abs(values[n2]) + λ = v1/(v1+v2) + new_coords[i] = q1 + λ*(q2-q1) + end + end + return new_coords +end + +""" + precompute_cut_edge_ids(rcoords,bg_rcoords,edge_list) + +Given + - `rcoords`: the node ref coordinates of the cut subcell/subfacet, + - `bg_rcoords`: the node ref coordinates of the background cell containing it, + - `edge_list`: the list of nodes defining each edge of the background cell, + +this function returns a vector that for each node of the cut subcell/subfacet contains + - `-1` if the node is also a node of the background cell, + - the id of the edge containing the node otherwise. +""" +function precompute_cut_edge_ids( + rcoords::Vector{<:Point{Dp,Tp}}, + bg_rcoords::Vector{<:Point{Dp,Tp}}, + edge_list::Vector{<:Vector{<:Integer}} +) where {Dp,Tp} + tol = 10*eps(Tp) + edges = Vector{Int8}(undef,length(rcoords)) + for (i,p) in enumerate(rcoords) + if any(q -> norm(q-p) < tol, bg_rcoords) + edges[i] = Int8(-1) + else + e = findfirst(edge -> belongs_to_edge(p,edge,bg_rcoords), edge_list) + edges[i] = Int8(e) + end + end + return edges +end + +function get_edge_list(poly::Polytope) + ltcell_to_lpoints, simplex = simplexify(poly) + simplex_edges = get_faces(simplex,1,0) + ltcell_to_edges = map(pts -> map(e -> pts[e], simplex_edges), ltcell_to_lpoints) + return collect(Vector{Int8},unique(sort,vcat(ltcell_to_edges...))) +end + +function belongs_to_edge( + p::Point{D,T},edge::Vector{<:Integer},bgpts::Vector{Point{D,T}} +) where {D,T} + tol = 10*eps(T) + p1, p2 = bgpts[edge] + return norm(cross(p-p1,p2-p1)) < tol +end + +function precompute_autodiff_caches( + trian::SubCellTriangulation +) + bgmodel = get_background_model(trian) + subcells = trian.subcells + + precompute_autodiff_caches( + bgmodel, + subcells.cell_to_bgcell, + subcells.cell_to_points, + subcells.point_to_rcoords, + subcells.point_to_coords, + ) +end + +function precompute_autodiff_caches( + trian::SubFacetTriangulation +) + bgmodel = get_background_model(trian) + subfacets = trian.subfacets + + caches = precompute_autodiff_caches( + bgmodel, + subfacets.facet_to_bgcell, + subfacets.facet_to_points, + subfacets.point_to_rcoords, + subfacets.point_to_coords, + ) + + # Precompute orientations + orientations = collect(lazy_map(orient,subfacets.facet_to_normal,caches.face_to_coords)) + + cache = (; caches..., orientations) + return cache +end + +orient(n,fcoords) = round(Int8,dot(n,face_normal(fcoords))) +Arrays.return_value(::typeof(orient),n,face_coords) = zero(Int8) + +function precompute_autodiff_caches( + bgmodel, + face_to_bgcell, + face_to_points, + point_to_rcoords, + point_to_coords, +) + bg_ctypes = get_cell_type(bgmodel) + bgcell_to_polys = expand_cell_data(get_polytopes(bgmodel),bg_ctypes) + bgcell_to_coords = get_cell_coordinates(bgmodel) + bgcell_to_rcoords = lazy_map(get_vertex_coordinates,bgcell_to_polys) + + face_to_bgcoords = lazy_map(Reindex(bgcell_to_coords),face_to_bgcell) + face_to_bgrcoords = lazy_map(Reindex(bgcell_to_rcoords),face_to_bgcell) + face_to_rcoords = lazy_map(Broadcasting(Reindex(point_to_rcoords)),face_to_points) + face_to_coords = lazy_map(Broadcasting(Reindex(point_to_coords)),face_to_points) + + bgcell_to_edge_lists = lazy_map(get_edge_list,bgcell_to_polys) + face_to_edge_lists = lazy_map(Reindex(bgcell_to_edge_lists),face_to_bgcell) + face_to_edges = collect(lazy_map(precompute_cut_edge_ids,face_to_rcoords,face_to_bgrcoords,face_to_edge_lists)) + + cache = (; + face_to_rcoords, + face_to_coords, + face_to_bgrcoords, + face_to_bgcoords, + face_to_edges, + face_to_edge_lists + ) + return cache +end + +function extract_dualized_cell_values( + trian::SubCellTriangulation, + φh::CellField, +) + @assert isa(DomainStyle(φh),ReferenceDomain) + bgmodel = get_background_model(trian) + bgcell_to_values = extract_dualized_cell_values(bgmodel,φh) + + subcells = trian.subcells + cell_to_bgcell = subcells.cell_to_bgcell + cell_to_values = lazy_map(Reindex(bgcell_to_values),cell_to_bgcell) + return cell_to_values +end + +function extract_dualized_cell_values( + trian::SubFacetTriangulation, + φh::CellField, +) + @assert isa(DomainStyle(φh),ReferenceDomain) + bgmodel = get_background_model(trian) + bgcell_to_values = extract_dualized_cell_values(bgmodel,φh) + + subfacets = trian.subfacets + facet_to_bgcell = subfacets.facet_to_bgcell + facet_to_values = lazy_map(Reindex(bgcell_to_values),facet_to_bgcell) + return facet_to_values +end + +function extract_dualized_cell_values( + bgmodel::DiscreteModel, + φh::CellField, +) + @assert isa(DomainStyle(φh),ReferenceDomain) + bg_ctypes = get_cell_type(bgmodel) + bgcell_to_polys = expand_cell_data(get_polytopes(bgmodel),bg_ctypes) + bgcell_to_rcoords = lazy_map(get_vertex_coordinates,bgcell_to_polys) + bgcell_to_fields = CellData.get_data(φh) + bgcell_to_values = lazy_map(evaluate,bgcell_to_fields,bgcell_to_rcoords) + return bgcell_to_values +end + +# TriangulationView +# This is mostly used in distributed, where we remove ghost cells by taking a view +# of the local triangulations. + +const DifferentiableTriangulationView{Dc,Dp} = Geometry.TriangulationView{Dc,Dp,<:DifferentiableTriangulation} + +function DifferentiableTriangulation( + trian :: Geometry.TriangulationView, + fe_space :: FESpace +) + parent = DifferentiableTriangulation(trian.parent,fe_space) + return Geometry.TriangulationView(parent,trian.cell_to_parent_cell) +end + +function update_trian!(trian::Geometry.TriangulationView,U,φh) + update_trian!(trian.parent,U,φh) + return trian +end + +function FESpaces._change_argument( + op,f,trian::DifferentiableTriangulationView,uh +) + U = get_fe_space(uh) + function g(cell_u) + cf = CellField(U,cell_u) + update_trian!(trian,U,cf) + cell_grad = f(cf) + update_trian!(trian,U,nothing) + get_contribution(cell_grad,trian) + end + g +end + +# AppendedTriangulation +# +# When cutting an embedded domain, we will usually end up with an AppendedTriangulation +# containing +# a) a regular triangulation with the IN/OUT cells +# b) a SubCell/SubFacetTriangulation with the CUT cells +# We only need to propagate the dual numbers to the CUT cells, which is what the +# following implementation does: + +const DifferentiableAppendedTriangulation{Dc,Dp,A} = + AppendedTriangulation{Dc,Dp,<:Union{<:DifferentiableTriangulation,<:DifferentiableTriangulationView{Dc,Dp}}} + +function DifferentiableTriangulation( + trian::AppendedTriangulation, fe_space::FESpace +) + a = DifferentiableTriangulation(trian.a,fe_space) + b = DifferentiableTriangulation(trian.b,fe_space) + return AppendedTriangulation(a,b) +end + +function update_trian!(trian::DifferentiableAppendedTriangulation,U,φh) + update_trian!(trian.a,U,φh) + update_trian!(trian.b,U,φh) + return trian +end + +function FESpaces._change_argument( + op,f,trian::DifferentiableAppendedTriangulation,uh +) + U = get_fe_space(uh) + function g(cell_u) + cf = CellField(U,cell_u) + update_trian!(trian,U,cf) + cell_grad = f(cf) + update_trian!(trian,U,nothing) + get_contribution(cell_grad,trian) + end + g +end + +# TODO: Move to Gridap +function FESpaces._compute_cell_ids(uh,ttrian::AppendedTriangulation) + ids_a = FESpaces._compute_cell_ids(uh,ttrian.a) + ids_b = FESpaces._compute_cell_ids(uh,ttrian.b) + lazy_append(ids_a,ids_b) +end diff --git a/src/LevelSetCutters/DiscreteGeometries.jl b/src/LevelSetCutters/DiscreteGeometries.jl index 5289299a..8ad1f024 100644 --- a/src/LevelSetCutters/DiscreteGeometries.jl +++ b/src/LevelSetCutters/DiscreteGeometries.jl @@ -1,4 +1,16 @@ +""" + struct DiscreteGeometry{D,T} <: CSG.Geometry + tree::Node + point_to_coords::Vector{Point{D,T}} + end + +## Constructors + + DiscreteGeometry(φh::FEFunction,model::DiscreteModel;name::String="") + DiscreteGeometry(f::Function,model::DiscreteModel;name::String="") + +""" struct DiscreteGeometry{D,T} <: CSG.Geometry tree::Node point_to_coords::Vector{Point{D,T}} @@ -34,7 +46,6 @@ function discretize(a::AnalyticalGeometry,point_to_coords::AbstractArray{<:Point end function discretize(a::AnalyticalGeometry,point_to_coords::Vector{<:Point}) - tree = get_tree(a) j_to_fun, oid_to_j = _find_unique_leaves(tree) j_to_ls = [ fun.(point_to_coords) for fun in j_to_fun ] @@ -48,13 +59,10 @@ function discretize(a::AnalyticalGeometry,point_to_coords::Vector{<:Point}) end newtree = replace_data(identity,conversion,tree) - DiscreteGeometry(newtree,point_to_coords) - end function _find_unique_leaves(tree) - i_to_fun = map(n->first(n.data),collect(Leaves(tree))) i_to_oid = map(objectid,i_to_fun) j_to_oid = unique(i_to_oid) @@ -65,9 +73,26 @@ function _find_unique_leaves(tree) j_to_fun, oid_to_j end +function DiscreteGeometry(f::Function,model::DiscreteModel;name::String="") + geo = AnalyticalGeometry(f;name=name) + discretize(geo,model) +end + function DiscreteGeometry( point_to_value::AbstractVector,point_to_coords::AbstractVector;name::String="") data = (point_to_value,name,nothing) tree = Leaf(data) DiscreteGeometry(tree,point_to_coords) end + +# TODO: This assumes that the level set φh is 1st order, i.e that there is a 1-to-1 correspondence +# between nodes in the mesh and dofs in φh. +# Even if we allowed higher order, the cuts are always linear. Not only it would be a waste +# of time to use higher order, but cuts could actually be wrong. +# This might be developped in the future. +function DiscreteGeometry( + φh::CellField,model::DiscreteModel;name::String="") + point_to_value = get_free_dof_values(φh) + point_to_coords = collect1d(get_node_coordinates(model)) + DiscreteGeometry(point_to_value,point_to_coords;name) +end diff --git a/src/LevelSetCutters/LevelSetCutters.jl b/src/LevelSetCutters/LevelSetCutters.jl index aa5b1472..9c2cda0c 100644 --- a/src/LevelSetCutters/LevelSetCutters.jl +++ b/src/LevelSetCutters/LevelSetCutters.jl @@ -15,6 +15,7 @@ import GridapEmbedded.Interfaces: compute_bgfacet_to_inoutcut using GridapEmbedded.Interfaces: Simplex using GridapEmbedded.Interfaces: merge_sub_face_data using GridapEmbedded.Interfaces: compute_inoutcut +using GridapEmbedded.Interfaces: SubCellTriangulation, SubFacetTriangulation using LinearAlgebra using MiniQhull @@ -22,12 +23,17 @@ using MiniQhull using Gridap.TensorValues using Gridap.ReferenceFEs using Gridap.Arrays +using Gridap.Arrays: testitem, return_type using Gridap.Fields using Gridap.Helpers using Gridap.Geometry using Gridap.CellData using Gridap.Polynomials using Gridap.Visualization +using Gridap.FESpaces +using Gridap.MultiField + +using GridapDistributed: DistributedMultiFieldFESpace export LevelSetCutter export AnalyticalGeometry @@ -53,6 +59,21 @@ include("LookupTables.jl") include("CutTriangulations.jl") +include("DifferentiableTriangulations.jl") + +""" + struct LevelSetCutter <: Cutter end + +Cutter for `DiscreteGeometry` and `AnalyticalGeometry`. + +## Usage + + cut(background::DiscreteModel,geom::AnalyticalGeometry) + cut(background::DiscreteModel,geom::DiscreteGeometry) + cut_facets(background::DiscreteModel,geom::AnalyticalGeometry) + cut_facets(background::DiscreteModel,geom::DiscreteGeometry) + +""" struct LevelSetCutter <: Cutter end function cut(cutter::LevelSetCutter,background::DiscreteModel,geom) diff --git a/test/AgFEMTests/PeriodicAgFEMSpacesTests.jl b/test/AgFEMTests/PeriodicAgFEMSpacesTests.jl new file mode 100644 index 00000000..9627e598 --- /dev/null +++ b/test/AgFEMTests/PeriodicAgFEMSpacesTests.jl @@ -0,0 +1,68 @@ +module PeriodicAgFEMSpacesTests + +using Test +using Gridap +using GridapEmbedded +using Gridap.Geometry: get_active_model + +const R = 0.55 +geom = disk(R,x0=Point(0.5,0.5)) +n = 21 +partition = (n,n) + +domain = (0,1,0,1) +bgmodel = CartesianDiscreteModel(domain,partition;isperiodic=(true,true)) + +cutdisc = cut(bgmodel,geom) + +strategy = AggregateCutCellsByThreshold(1) +aggregates = aggregate(strategy,cutdisc) + +Ω_bg = Triangulation(bgmodel) +Ω_ac = Triangulation(cutdisc,ACTIVE) +Ω = Triangulation(cutdisc,PHYSICAL) +Ω_in = Triangulation(cutdisc,IN) + +dΩ_bg = Measure(Ω_bg,2) +dΩ = Measure(Ω,2) +dΩ_in = Measure(Ω_in,2) + +model = get_active_model(Ω_ac) +order = 1 + +# In the physical domain +cell_fe = FiniteElements(PhysicalDomain(),model,lagrangian,Float64,order) +Vstd = FESpace(Ω_ac,cell_fe) + +Vagg = AgFEMSpace(Vstd,aggregates) +U = TrialFESpace(Vagg) + +v(x) = (x[1]-0.5)^2 + (x[2]-0.5)^2 +vhagg = interpolate(v,Vagg) + +path = mktempdir() +writevtk(Ω_ac,joinpath(path,"test"),cellfields=["v"=>vhagg]) + +tol = 10e-7 +@test sum( ∫(abs2(v-vhagg))dΩ ) < tol +@test sum( ∫(abs2(v-vhagg))dΩ_in ) < tol + +# In the reference space + +reffe = ReferenceFE(lagrangian,Float64,order) +V = FESpace(Ω_ac,reffe) +Vagg = AgFEMSpace(V,aggregates) + +v(x) = (x[1]-0.5)^2 + (x[2]-0.5)^2 +vhagg = interpolate(v,Vagg) + +tol = 10e-7 +@test sum( ∫(abs2(v-vhagg))dΩ ) < tol +@test sum( ∫(abs2(v-vhagg))dΩ_in ) < tol + +#cellfields = ["vh"=>vh,"vhagg"=>vhagg,"e"=>vh-vhagg] +#writevtk(Ω_bg,"trian_bg",nsubcells=10,cellfields=cellfields) +#writevtk(Ω_in,"trian_in",nsubcells=10,cellfields=cellfields) +#writevtk(Ω,"trian_phys",cellfields=cellfields) + +end # module diff --git a/test/AgFEMTests/runtests.jl b/test/AgFEMTests/runtests.jl index 9d039176..66d7f07b 100644 --- a/test/AgFEMTests/runtests.jl +++ b/test/AgFEMTests/runtests.jl @@ -6,4 +6,6 @@ using Test @testset "AgFEMSpaces" begin include("AgFEMSpacesTests.jl") end +@testset "PeriodicAgFEMSpaces" begin include("PeriodicAgFEMSpacesTests.jl") end + end # module diff --git a/test/AlgoimUtilsTests/VisualizationTests.jl b/test/AlgoimUtilsTests/VisualizationTests.jl index 91181789..d0b52930 100644 --- a/test/AlgoimUtilsTests/VisualizationTests.jl +++ b/test/AlgoimUtilsTests/VisualizationTests.jl @@ -37,7 +37,8 @@ module VisualizationTests vquad = Quadrature(algoim,phi,degree,phase=IN) _,dΩ = TriangulationAndMeasure(Ω,vquad) - writevtk(dΓ,"res_sur",cellfields=["f"=>fₕ],qhulltype=convexhull) - writevtk([dΩ,dΓ],"res_vol",cellfields=["f"=>fₕ]) + path = mktempdir() + writevtk(dΓ,joinpath(path,"res_sur"),cellfields=["f"=>fₕ],qhulltype=convexhull) + writevtk([dΩ,dΓ],joinpath("res_vol"),cellfields=["f"=>fₕ]) end # module \ No newline at end of file diff --git a/test/DistributedTests/AggregationTests.jl b/test/DistributedTests/AggregationTests.jl index 0dff75cf..7175232b 100644 --- a/test/DistributedTests/AggregationTests.jl +++ b/test/DistributedTests/AggregationTests.jl @@ -38,8 +38,6 @@ bgf_to_ioc = compute_bgfacet_to_inoutcut(bgmodel,geo) Ω = Triangulation(cutgeo) -writevtk(Ω,"trian") - strategy = AggregateCutCellsByThreshold(1.0) aggregates,aggregate_owner,aggregate_neig = distributed_aggregate( strategy,cutgeo,geo,IN) @@ -74,9 +72,10 @@ end Ωin = Triangulation(cutgeo,IN) Γ = EmbeddedBoundary(cutgeo) -writevtk(Ωin,"trian_in") -writevtk(Γ,"bnd") -writevtk(Ωbg,"bgtrian",celldata= +path = mktempdir() +writevtk(Ωin,joinpath(path,"trian_in")) +writevtk(Γ,joinpath(path,"bnd")) +writevtk(Ωbg,joinpath(path,"bgtrian"),celldata= ["aggregate"=>oaggregates, "aggregate_owner"=>oaggregate_owner]) diff --git a/test/DistributedTests/DistributedDiscreteGeometryPoissonTest.jl b/test/DistributedTests/DistributedDiscreteGeometryPoissonTest.jl new file mode 100644 index 00000000..9388124d --- /dev/null +++ b/test/DistributedTests/DistributedDiscreteGeometryPoissonTest.jl @@ -0,0 +1,144 @@ +module DistributedDiscreteGeometryPoissonTest + +using Gridap +using GridapEmbedded +using GridapDistributed +using PartitionedArrays +using Test + +using GridapEmbedded.CSG +using GridapEmbedded.LevelSetCutters + +function main(distribute,parts; + threshold=1, + n=8, + cells=(n,n), + geometry=:circle) + + ranks = distribute(LinearIndices((prod(parts),))) + + u(x) = x[1] - x[2] + f(x) = -Δ(u)(x) + ud(x) = u(x) + + geometries = Dict( + :circle => circle_geometry, + :remotes => remotes_geometry, + ) + + bgmodel,_geo = geometries[geometry](ranks,parts,cells) + geo = discretize(_geo,bgmodel) + + D = 2 + cell_meas = map(get_cell_measure∘Triangulation,local_views(bgmodel)) + meas = map(first,cell_meas) |> PartitionedArrays.getany + h = meas^(1/D) + + cutgeo = cut(bgmodel,geo) + cutgeo_facets = cut_facets(bgmodel,geo) + + strategy = AggregateCutCellsByThreshold(threshold) + bgmodel,cutgeo,aggregates = aggregate(strategy,cutgeo) + + Ω_bg = Triangulation(bgmodel) + Ω_act = Triangulation(cutgeo,ACTIVE) + Ω = Triangulation(cutgeo,PHYSICAL) + Γ = EmbeddedBoundary(cutgeo) + + n_Γ = get_normal_vector(Γ) + + order = 1 + degree = 2*order + dΩ = Measure(Ω,degree) + dΓ = Measure(Γ,degree) + + reffe = ReferenceFE(lagrangian,Float64,order) + + Vstd = FESpace(Ω_act,reffe) + + V = AgFEMSpace(bgmodel,Vstd,aggregates) + U = TrialFESpace(V) + + + γd = 10.0 + + a(u,v) = + ∫( ∇(v)⋅∇(u) ) * dΩ + + ∫( (γd/h)*v*u - v*(n_Γ⋅∇(u)) - (n_Γ⋅∇(v))*u ) * dΓ + + l(v) = + ∫( v*f ) * dΩ + + ∫( (γd/h)*v*ud - (n_Γ⋅∇(v))*ud ) * dΓ + + op = AffineFEOperator(a,l,U,V) + uh = solve(op) + + e = u - uh + + l2(u) = sqrt(sum( ∫( u*u )*dΩ )) + h1(u) = sqrt(sum( ∫( u*u + ∇(u)⋅∇(u) )*dΩ )) + + el2 = l2(e) + eh1 = h1(e) + ul2 = l2(uh) + uh1 = h1(uh) + + # + colors = map(color_aggregates,aggregates,local_views(bgmodel)) + gids = get_cell_gids(bgmodel) + + + global_aggregates = map(aggregates,local_to_global(gids)) do agg,gid + map(i-> i==0 ? 0 : gid[i],agg) + end + own_aggregates = map(global_aggregates,own_to_local(gids)) do agg,oid + map(Reindex(agg),oid) + end + own_colors = map(colors,own_to_local(gids)) do col,oid + map(Reindex(col),oid) + end + + path = mktempdir() + writevtk(Ω_bg,joinpath(path,"trian"), + celldata=[ + "aggregate"=>own_aggregates, + "color"=>own_colors, + "gid"=>own_to_global(gids)])#, + # cellfields=["uh"=>uh]) + + writevtk(Ω,joinpath(path,"trian_O"),cellfields=["uh"=>uh]) + writevtk(Γ,joinpath(path,"trian_G")) + @test el2/ul2 < 1.e-8 + @test eh1/uh1 < 1.e-7 + +end + +function circle_geometry(ranks,parts,cells) + L = 1 + p0 = Point(0.0,0.0) + pmin = p0-L/2 + pmax = p0+L/2 + R = 0.35 + geo = disk(R,x0=p0) + bgmodel = CartesianDiscreteModel(ranks,parts,pmin,pmax,cells) + bgmodel,geo +end + +function remotes_geometry(ranks,parts,cells) + x0 = Point(0.05,0.05) + d1 = VectorValue(0.9,0.0) + d2 = VectorValue(0.0,0.1) + geo1 = quadrilateral(;x0=x0,d1=d1,d2=d2) + + x0 = Point(0.15,0.1) + d1 = VectorValue(0.25,0.0) + d2 = VectorValue(0.0,0.6) + geo2 = quadrilateral(;x0=x0,d1=d1,d2=d2) + geo = union(geo1,geo2) + + domain = (0, 1, 0, 1) + bgmodel = CartesianDiscreteModel(ranks,parts,domain,cells) + bgmodel,geo +end + +end # module \ No newline at end of file diff --git a/test/DistributedTests/DistributedLSDiscreteGeometryPoissonTest.jl b/test/DistributedTests/DistributedLSDiscreteGeometryPoissonTest.jl new file mode 100644 index 00000000..d27d8f7c --- /dev/null +++ b/test/DistributedTests/DistributedLSDiscreteGeometryPoissonTest.jl @@ -0,0 +1,111 @@ +module DistributedLSDiscreteGeometryPoissonTest + +using Gridap +using GridapEmbedded +using GridapDistributed +using PartitionedArrays +using Test + +using GridapEmbedded.CSG +using GridapEmbedded.LevelSetCutters + +function main(distribute,parts; + threshold=1, + n=21, + cells=(n,n)) + + order = 2 + ranks = distribute(LinearIndices((prod(parts),))) + domain = (0,1,0,1) + bgmodel = CartesianDiscreteModel(ranks,parts,domain,cells) + + u(x) = (x[1]-0.5)^2 + (x[2]-0.5)^2 + f(x) = -Δ(u)(x) + ud(x) = u(x) + + reffe = ReferenceFE(lagrangian,Float64,order) + Ω_bg = Triangulation(bgmodel) + V_bg = FESpace(Ω_bg,reffe) + φh = interpolate(x->sqrt((x[1]-0.5)^2+(x[2]-0.5)^2)-0.35,V_bg) + geo = DiscreteGeometry(φh,bgmodel) + + D = 2 + cell_meas = map(get_cell_measure∘Triangulation,local_views(bgmodel)) + meas = map(first,cell_meas) |> PartitionedArrays.getany + h = meas^(1/D) + + cutgeo = cut(bgmodel,geo) + cutgeo_facets = cut_facets(bgmodel,geo) + + strategy = AggregateCutCellsByThreshold(threshold) + bgmodel,cutgeo,aggregates = aggregate(strategy,cutgeo) + + Ω_act = Triangulation(cutgeo,ACTIVE) + Ω = Triangulation(cutgeo,PHYSICAL) + Γ = EmbeddedBoundary(cutgeo) + + n_Γ = get_normal_vector(Γ) + + order = 2 + degree = 2*order + dΩ = Measure(Ω,degree) + dΓ = Measure(Γ,degree) + + Vstd = FESpace(Ω_act,reffe) + V = AgFEMSpace(bgmodel,Vstd,aggregates) + U = TrialFESpace(V) + + γd = 10.0 + + a(u,v) = + ∫( ∇(v)⋅∇(u) ) * dΩ + + ∫( (γd/h)*v*u - v*(n_Γ⋅∇(u)) - (n_Γ⋅∇(v))*u ) * dΓ + + l(v) = + ∫( v*f ) * dΩ + + ∫( (γd/h)*v*ud - (n_Γ⋅∇(v))*ud ) * dΓ + + op = AffineFEOperator(a,l,U,V) + uh = solve(op) + + e = u - uh + + l2(u) = sqrt(sum( ∫( u*u )*dΩ )) + h1(u) = sqrt(sum( ∫( u*u + ∇(u)⋅∇(u) )*dΩ )) + + el2 = l2(e) + eh1 = h1(e) + ul2 = l2(uh) + uh1 = h1(uh) + + # + colors = map(color_aggregates,aggregates,local_views(bgmodel)) + gids = get_cell_gids(bgmodel) + + + global_aggregates = map(aggregates,local_to_global(gids)) do agg,gid + map(i-> i==0 ? 0 : gid[i],agg) + end + own_aggregates = map(global_aggregates,own_to_local(gids)) do agg,oid + map(Reindex(agg),oid) + end + own_colors = map(colors,own_to_local(gids)) do col,oid + map(Reindex(col),oid) + end + + path = mktempdir() + writevtk(Ω_bg,joinpath(path,"trian"), + celldata=[ + "aggregate"=>own_aggregates, + "color"=>own_colors, + "gid"=>own_to_global(gids)])#, + # cellfields=["uh"=>uh]) + + writevtk(Ω,joinpath(path,"trian_O"),cellfields=["uh"=>uh]) + writevtk(Γ,joinpath(path,"trian_G")) + @test el2/ul2 < 1.e-8 + @test eh1/uh1 < 1.e-7 + +end + +end # module \ No newline at end of file diff --git a/test/DistributedTests/GeometricalDifferentiationTests.jl b/test/DistributedTests/GeometricalDifferentiationTests.jl new file mode 100644 index 00000000..147cc6a1 --- /dev/null +++ b/test/DistributedTests/GeometricalDifferentiationTests.jl @@ -0,0 +1,301 @@ +module DistributedGeometricalDifferentitationTests +############################################################################################ +# These tests are meant to verify the correctness differentiation of functionals w.r.t the +# level set defining the cut domain. +# They are based on the following work: +# "Level-set topology optimisation with unfitted finite elements and automatic shape differentiation" +# by Z. J. Wegert, J. Manyer, C. Mallon, S. Badia, V. J. Challis (2025) +############################################################################################ +using Test + +using Gridap, Gridap.Geometry, Gridap.Adaptivity +using GridapEmbedded, GridapEmbedded.Interfaces, GridapEmbedded.LevelSetCutters + +using GridapDistributed, PartitionedArrays + +using Gridap.Arrays: Operation +using Gridap.CellData: get_tangent_vector, get_normal_vector +using GridapEmbedded.Interfaces: get_conormal_vector, get_subfacet_normal_vector, get_ghost_normal_vector + +using GridapEmbedded.LevelSetCutters: DifferentiableTriangulation +using GridapDistributed: i_am_main + +function generate_model(D,n,ranks,mesh_partition) + domain = (D==2) ? (0,1,0,1) : (0,1,0,1,0,1) + cell_partition = (D==2) ? (n,n) : (n,n,n) + base_model = UnstructuredDiscreteModel(CartesianDiscreteModel(ranks,mesh_partition,domain,cell_partition)) + ref_model = refine(base_model, refinement_method = "barycentric") + model = Adaptivity.get_model(ref_model) + return model +end + +function level_set(shape::Symbol;N=4) + if shape == :square + x -> max(abs(x[1]-0.5),abs(x[2]-0.5))-0.25 # Square + elseif shape == :corner_2d + x -> ((x[1]-0.5)^N+(x[2]-0.5)^N)^(1/N)-0.25 # Curved corner + elseif shape == :diamond + x -> abs(x[1]-0.5)+abs(x[2]-0.5)-0.25-0/n/10 # Diamond + elseif shape == :circle + x -> sqrt((x[1]-0.5)^2+(x[2]-0.5)^2)-0.5223 # Circle + elseif shape == :circle_2 + x -> sqrt((x[1]-0.5)^2+(x[2]-0.5)^2)-0.23 # Circle + elseif shape == :square_prism + x -> max(abs(x[1]-0.5),abs(x[2]-0.5),abs(x[3]-0.5))-0.25 # Square prism + elseif shape == :corner_3d + x -> ((x[1]-0.5)^N+(x[2]-0.5)^N+(x[3]-0.5)^N)^(1/N)-0.25 # Curved corner + elseif shape == :diamond_prism + x -> abs(x[1]-0.5)+abs(x[2]-0.5)+abs(x[3]-0.5)-0.25-0/n/10 # Diamond prism + elseif shape == :sphere + x -> sqrt((x[1]-0.5)^2+(x[2]-0.5)^2+(x[3]-0.5)^2)-0.53 # Sphere + elseif shape == :regular_2d + x -> cos(2π*x[1])*cos(2π*x[2])-0.11 # "Regular" LSF + elseif shape == :regular_3d + x -> cos(2π*x[1])*cos(2π*x[2])*cos(2π*x[3])-0.11 # "Regular" LSF + else + error("Unknown shape") + end +end + +function main_generic( + model,φ::Function,f::Function +) + order = 1 + reffe = ReferenceFE(lagrangian,Float64,order) + V_φ = TestFESpace(model,reffe) + + U = TestFESpace(model,reffe) + + φh = interpolate(φ,V_φ) + fh = interpolate(f,V_φ) + uh = interpolate(x->x[1]+x[2],U) + + map(partition(get_free_dof_values(φh))) do x_φ + idx = findall(isapprox(0.0;atol=10^-10),x_φ) + !isempty(idx) && @info "Correcting level values!" + x_φ[idx] .+= 100*eps(eltype(x_φ)) + end + + geo = DiscreteGeometry(φh,model) + cutgeo = cut(model,geo) + + # A.1) Volume integral + + Ω = Triangulation(cutgeo,PHYSICAL_IN) + Ω_AD = DifferentiableTriangulation(Ω,V_φ) + dΩ = Measure(Ω_AD,2*order) + + Γ = EmbeddedBoundary(cutgeo) + n_Γ = get_normal_vector(Γ) + dΓ = Measure(Γ,2*order) + + J_bulk(φ) = ∫(fh)dΩ + dJ_bulk_AD = gradient(J_bulk,φh) + dJ_bulk_AD_vec = assemble_vector(dJ_bulk_AD,V_φ) + + dJ_bulk_exact(q) = ∫(-fh*q/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJ_bulk_exact_vec = assemble_vector(dJ_bulk_exact,V_φ) + + @test norm(dJ_bulk_AD_vec - dJ_bulk_exact_vec) < 1e-10 + + # A.1.1) Volume integral with another field + + J_bulk_1(u,φ) = ∫(u+fh)dΩ + dJ_bulk_1_AD = gradient(φ->J_bulk_1(uh,φ),φh) + dJ_bulk_1_AD_vec = assemble_vector(dJ_bulk_1_AD,V_φ) + + dJ_bulk_1_exact(q,u) = ∫(-(u+fh)*q/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJ_bulk_1_exact_vec = assemble_vector(q->dJ_bulk_1_exact(q,uh),V_φ) + + @test norm(dJ_bulk_1_AD_vec - dJ_bulk_1_exact_vec) < 1e-10 + + dJ_bulk_1_AD_in_u = gradient(u->J_bulk_1(u,φh),uh) + dJ_bulk_1_AD_in_u_vec = assemble_vector(dJ_bulk_1_AD_in_u,U) + + dJ_bulk_1_exact_in_u(q,u) = ∫(q)dΩ + dJ_bulk_1_exact_in_u_vec = assemble_vector(q->dJ_bulk_1_exact_in_u(q,uh),U) + + @test norm(dJ_bulk_1_AD_in_u_vec - dJ_bulk_1_exact_in_u_vec) < 1e-10 + + # Multifield case + U = TestFESpace(model,reffe) + V_φu = MultiFieldFESpace([V_φ,U]) + φuh = interpolate([φh,x->x[1]],V_φu) + J_bulk_mult((φ,u)) = ∫(fh)dΩ + dJ_bulk_AD = gradient(J_bulk_mult,φuh) + dJ_bulk_AD_vec = assemble_vector(dJ_bulk_AD,V_φu) + + dJ_bulk_exact_mult((dφ,du)) = ∫(-fh*dφ/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJ_bulk_exact_vec = assemble_vector(dJ_bulk_exact_mult,V_φu) + + @test norm(dJ_bulk_AD_vec - dJ_bulk_exact_vec) < 1e-10 + + # A.2) Volume integral + + g(fh) = ∇(fh)⋅∇(fh) + J_bulk2(φ) = ∫(g(fh))dΩ + dJ_bulk_AD2 = gradient(J_bulk2,φh) + dJ_bulk_AD_vec2 = assemble_vector(dJ_bulk_AD2,V_φ) + + dJ_bulk_exact2(q) = ∫(-g(fh)*q/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJ_bulk_exact_vec2 = assemble_vector(dJ_bulk_exact2,V_φ) + + @test norm(dJ_bulk_AD_vec2 - dJ_bulk_exact_vec2) < 1e-10 + + # B.1) Facet integral + + Γ = EmbeddedBoundary(cutgeo) + n_Γ = get_normal_vector(Γ) + Γ_AD = DifferentiableTriangulation(Γ,V_φ) + Λ = Skeleton(Γ) + Σ = Boundary(Γ) + + dΓ = Measure(Γ,2*order) + dΛ = Measure(Λ,2*order) + dΣ = Measure(Σ,2*order) + + n_Γ = get_normal_vector(Γ) + + n_S_Λ = get_normal_vector(Λ) + m_k_Λ = get_conormal_vector(Λ) + ∇ˢφ_Λ = Operation(abs)(n_S_Λ ⋅ ∇(φh).plus) + + n_S_Σ = get_normal_vector(Σ) + m_k_Σ = get_conormal_vector(Σ) + ∇ˢφ_Σ = Operation(abs)(n_S_Σ ⋅ ∇(φh)) + + dΓ_AD = Measure(Γ_AD,2*order) + J_int(φ) = ∫(fh)dΓ_AD + dJ_int_AD = gradient(J_int,φh) + dJ_int_AD_vec = assemble_vector(dJ_int_AD,V_φ) + + dJ_int_exact(w) = ∫((-n_Γ⋅∇(fh))*w/(abs(n_Γ ⋅ ∇(φh))))dΓ + + ∫(-n_S_Λ ⋅ (jump(fh*m_k_Λ) * mean(w) / ∇ˢφ_Λ))dΛ + + ∫(-n_S_Σ ⋅ (fh*m_k_Σ * w / ∇ˢφ_Σ))dΣ + dJ_int_exact_vec = assemble_vector(dJ_int_exact,V_φ) + + @test norm(dJ_int_AD_vec - dJ_int_exact_vec) < 1e-10 + + # B.2) Facet integral + + h(fh) = ∇(fh)⋅∇(fh) + J_int2(φ) = ∫(h(fh))dΓ_AD + dJ_int_AD2 = gradient(J_int2,φh) + dJ_int_AD_vec2 = assemble_vector(dJ_int_AD2,V_φ) + + ∇g(∇∇f,∇f) = ∇∇f⋅∇f + ∇f⋅∇∇f + dJ_int_exact2(w) = ∫((-n_Γ⋅ (∇g ∘ (∇∇(fh),∇(fh))))*w/(abs(n_Γ ⋅ ∇(φh))))dΓ + + ∫(-n_S_Λ ⋅ (jump(h(fh)*m_k_Λ) * mean(w) / ∇ˢφ_Λ))dΛ + + ∫(-n_S_Σ ⋅ (h(fh)*m_k_Σ * w / ∇ˢφ_Σ))dΣ + dJ_int_exact_vec2 = assemble_vector(dJ_int_exact2,V_φ) + + @test norm(dJ_int_AD_vec2 - dJ_int_exact_vec2) < 1e-10 +end + +## Concering integrals of the form `φ->∫(f ⋅ n(φ))dΓ(φ)` +function main_normal( + model,φ::Function,f::Function; + vtk=false, + name="embedded", + run_test=true +) + order = 1 + reffe = ReferenceFE(lagrangian,Float64,order) + V_φ = TestFESpace(model,reffe) + + φh = interpolate(φ,V_φ) + + geo = DiscreteGeometry(φh,model) + cutgeo = cut(model,geo) + + Γ = EmbeddedBoundary(cutgeo) + n_Γ = get_normal_vector(Γ) + Γ_AD = DifferentiableTriangulation(Γ,V_φ) + dΓ_AD = Measure(Γ_AD,2*order) + dΓ = Measure(Γ,2*order) + + fh_Γ = CellField(f,Γ) + fh_Γ_AD = CellField(f,Γ_AD) + + function J_int(φ) + n = get_normal_vector(Γ_AD) + ∫(fh_Γ_AD⋅n)dΓ_AD + end + dJ_int_AD = gradient(J_int,φh) + dJ_int_AD_vec = assemble_vector(dJ_int_AD,V_φ) + + _n(∇φ) = ∇φ/(10^-20+norm(∇φ)) + dJ_int_phi = ∇(φ->∫(fh_Γ_AD ⋅ (_n ∘ (∇(φ))))dΓ_AD,φh) + dJh_int_phi = assemble_vector(dJ_int_phi,V_φ) + + run_test && @test norm(dJ_int_AD_vec - dJh_int_phi) < 1e-10 + + # Analytic + # Note: currently, the analytic result is only valid on closed domains thanks + # to the divergence theorem. I think it would take significant work to compute + # the analytic derivative generally as we can't rely on divergence theorem to + # rewrite it in a convenient way. As a result, we don't have an analytic result + # for general cases such as ∫( f(n(φ)) )dΓ(φ), nor the case when Γ intersects + # ∂D. Thankfully, we have AD instead ;) + # Note 2: For the case that Γ does intersect the surface, the result is correct + # everywhere except on the intersection. + + fh2(x) = VectorValue((1-x[1])^2,(1-x[2])^2) + fh_Γ = CellField(fh2,Γ) + fh_Γ_AD = CellField(fh2,Γ_AD) + + # Note: this comes from rewriting via the divergence theorem: + # ∫(f ⋅ n(φ))dΓ(φ) = ∫(∇⋅f)dΩ(φ) + dJ_int_exact3(w) = ∫(-(∇⋅(fh_Γ))*w/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJh_int_exact3 = assemble_vector(dJ_int_exact3,V_φ) + + run_test && @test norm(dJh_int_exact3 - dJ_int_AD_vec) < 1e-10 + + if vtk + path = "results/$(name)" + Ω_bg = Triangulation(model) + writevtk(Ω_bg,path,cellfields=[ + "dJ_AD"=>FEFunction(V_φ,dJ_int_AD_vec), + "dJ_AD_with_phi"=>FEFunction(V_φ,dJh_int_phi), + "dJ_exact"=>FEFunction(V_φ,dJh_int_exact3) + ]) + end +end + +####################### + +function main(distribute,np) + @assert np == 4 + + # 2D + mesh_partition = (2,2) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + D = 2 + n = 12 + model = generate_model(D,n,ranks,mesh_partition) + + φ0 = level_set(:circle_2) + f0((x,y)) = VectorValue((1-x)^2,(1-y)^2) + main_normal(model,φ0,f0) + + φ1 = level_set(:circle) + f1(x) = 1.0 + main_generic(model,φ1,f1) + + φ2 = level_set(:circle) + f2(x) = x[1] + x[2] + main_generic(model,φ2,f2) + + # 3D + mesh_partition = (2,2,1) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + D = 3 + n = 8 + model = generate_model(D,n,ranks,mesh_partition) + + # φ3 = level_set(:sphere) + # f3(x) = x[1] + x[2] + # main_generic(model,φ3,f3) +end + +end \ No newline at end of file diff --git a/test/DistributedTests/PeriodicDistributedDiscreteGeometryPoissonTest.jl b/test/DistributedTests/PeriodicDistributedDiscreteGeometryPoissonTest.jl new file mode 100644 index 00000000..2c7de035 --- /dev/null +++ b/test/DistributedTests/PeriodicDistributedDiscreteGeometryPoissonTest.jl @@ -0,0 +1,194 @@ +module PeriodicDistributedDiscreteGeometryPoissonTest + +using Gridap +using GridapEmbedded +using GridapDistributed +using PartitionedArrays +using Test + +using GridapDistributed: DistributedDiscreteModel + +using Gridap.Geometry +using Gridap.Geometry: get_vertex_coordinates,get_faces + +using GridapEmbedded.CSG +using GridapEmbedded.LevelSetCutters + +function update_labels!(e::Integer,model::DistributedDiscreteModel,f_Γ::Function,name::String) + mask = mark_nodes(f_Γ,model) + cell_to_entity = map(local_views(model),local_views(mask)) do model,mask + _update_labels_locally!(e,model,mask,name) + end + cell_gids=get_cell_gids(model) + cache=GridapDistributed.fetch_vector_ghost_values_cache(cell_to_entity,partition(cell_gids)) + GridapDistributed.fetch_vector_ghost_values!(cell_to_entity,cache) + nothing +end + +function _update_labels_locally!(e,model::CartesianDiscreteModel{2},mask,name) + topo = get_grid_topology(model) + labels = get_face_labeling(model) + cell_to_entity = labels.d_to_dface_to_entity[end] + entity = maximum(cell_to_entity) + e + # Vertices + vtxs_Γ = findall(mask) + vtx_edge_connectivity = Array(get_faces(topo,0,1)[vtxs_Γ]) + # Edges + edge_entries = [findall(x->any(x .∈ vtx_edge_connectivity[1:end.!=j]), + vtx_edge_connectivity[j]) for j = 1:length(vtx_edge_connectivity)] + edge_Γ = unique(reduce(vcat,getindex.(vtx_edge_connectivity,edge_entries),init=[])) + labels.d_to_dface_to_entity[1][vtxs_Γ] .= entity + labels.d_to_dface_to_entity[2][edge_Γ] .= entity + add_tag!(labels,name,[entity]) + return cell_to_entity +end + +function mark_nodes(f,model::DistributedDiscreteModel) + local_masks = map(local_views(model)) do model + mark_nodes(f,model) + end + gids = get_face_gids(model,0) + mask = PVector(local_masks,partition(gids)) + assemble!(|,mask) |> fetch # Ghosts -> Owned with `or` applied + consistent!(mask) |> fetch # Owned -> Ghost + return mask +end + +function mark_nodes(f,model::DiscreteModel) + topo = get_grid_topology(model) + coords = get_vertex_coordinates(topo) + mask = map(f,coords) + return mask +end + +function main(distribute,parts; + threshold=1, + n=8, + cells=(n,n), + geometry=:circle) + + ranks = distribute(LinearIndices((prod(parts),))) + + u(x) = (x[1]-0.5)^2 + (x[2]-0.5)^2 + f(x) = -Δ(u)(x) + ud(x) = u(x) + + geometries = Dict( + :circle => circle_geometry, + :remotes => remotes_geometry, + ) + + bgmodel,_geo = geometries[geometry](ranks,parts,cells) + update_labels!(1,bgmodel,x->iszero(x[1]) || iszero(x[2]),"outer_boundary") + + geo = discretize(_geo,bgmodel) + + D = 2 + cell_meas = map(get_cell_measure∘Triangulation,local_views(bgmodel)) + meas = map(first,cell_meas) |> PartitionedArrays.getany + h = meas^(1/D) + + cutgeo = cut(bgmodel,geo) + cutgeo_facets = cut_facets(bgmodel,geo) + + strategy = AggregateCutCellsByThreshold(threshold) + bgmodel,cutgeo,aggregates = aggregate(strategy,cutgeo) + + Ω_bg = Triangulation(bgmodel) + Ω_act = Triangulation(cutgeo,ACTIVE) + Ω = Triangulation(cutgeo,PHYSICAL) + Γ = EmbeddedBoundary(cutgeo) + + n_Γ = get_normal_vector(Γ) + + order = 2 + degree = 2*order + dΩ = Measure(Ω,degree) + dΓ = Measure(Γ,degree) + + reffe = ReferenceFE(lagrangian,Float64,order) + + Vstd = FESpace(Ω_act,reffe;dirichlet_tags="outer_boundary") + + V = AgFEMSpace(bgmodel,Vstd,aggregates) + U = TrialFESpace(V,ud) + + γd = 10.0 + + a(u,v) = + ∫( ∇(v)⋅∇(u) ) * dΩ + + ∫( (γd/h)*v*u - v*(n_Γ⋅∇(u)) - (n_Γ⋅∇(v))*u ) * dΓ + + l(v) = + ∫( v*f ) * dΩ + + ∫( (γd/h)*v*ud - (n_Γ⋅∇(v))*ud ) * dΓ + + op = AffineFEOperator(a,l,U,V) + uh = solve(op) + + e = u - uh + + l2(u) = sqrt(sum( ∫( u*u )*dΩ )) + h1(u) = sqrt(sum( ∫( u*u + ∇(u)⋅∇(u) )*dΩ )) + + el2 = l2(e) + eh1 = h1(e) + ul2 = l2(uh) + uh1 = h1(uh) + + # + colors = map(color_aggregates,aggregates,local_views(bgmodel)) + gids = get_cell_gids(bgmodel) + + + global_aggregates = map(aggregates,local_to_global(gids)) do agg,gid + map(i-> i==0 ? 0 : gid[i],agg) + end + own_aggregates = map(global_aggregates,own_to_local(gids)) do agg,oid + map(Reindex(agg),oid) + end + own_colors = map(colors,own_to_local(gids)) do col,oid + map(Reindex(col),oid) + end + + path = mktempdir() + writevtk(Ω_bg,joinpath(path,"trian"), + celldata=[ + "aggregate"=>own_aggregates, + "color"=>own_colors, + "gid"=>own_to_global(gids)])#, + # cellfields=["uh"=>uh]) + + writevtk(Ω,joinpath(path,"trian_O"),cellfields=["uh"=>uh]) + writevtk(Γ,joinpath(path,"trian_G")) + @test el2/ul2 < 1.e-8 + @test eh1/uh1 < 1.e-7 + +end + +function circle_geometry(ranks,parts,cells) + p0 = Point(0.5,0.5) + R = 0.55 + geo = disk(R,x0=p0) + bgmodel = CartesianDiscreteModel(ranks,parts,(0,1,0,1),cells;isperiodic=(true,true)) + bgmodel,geo +end + +function remotes_geometry(ranks,parts,cells) + x0 = Point(0.0,0.4) + d1 = VectorValue(1.0,0.0) + d2 = VectorValue(0.0,0.2) + geo1 = quadrilateral(;x0=x0,d1=d1,d2=d2) + + x0 = Point(0.4,0.0) + d1 = VectorValue(0.2,0.0) + d2 = VectorValue(0.0,1.0) + geo2 = quadrilateral(;x0=x0,d1=d1,d2=d2) + geo = union(geo1,geo2) + + domain = (0, 1, 0, 1) + bgmodel = CartesianDiscreteModel(ranks,parts,domain,cells;isperiodic=(true,true)) + bgmodel,geo +end + +end # module \ No newline at end of file diff --git a/test/DistributedTests/PoissonTests.jl b/test/DistributedTests/PoissonTests.jl index feb6e9b2..d5ece5b9 100644 --- a/test/DistributedTests/PoissonTests.jl +++ b/test/DistributedTests/PoissonTests.jl @@ -96,15 +96,16 @@ function main(distribute,parts; map(Reindex(col),oid) end - writevtk(Ω_bg,"trian", + path = mktempdir() + writevtk(Ω_bg,joinpath(path,"trian"), celldata=[ "aggregate"=>own_aggregates, "color"=>own_colors, "gid"=>own_to_global(gids)])#, # cellfields=["uh"=>uh]) - writevtk(Ω,"trian_O",cellfields=["uh"=>uh,"eh"=>e]) - writevtk(Γ,"trian_G") + writevtk(Ω,joinpath(path,"trian_O"),cellfields=["uh"=>uh,"eh"=>e]) + writevtk(Γ,joinpath(path,"trian_G")) @test el2/ul2 < 1.e-8 @test eh1/uh1 < 1.e-7 diff --git a/test/DistributedTests/mpi/runtests.jl b/test/DistributedTests/mpi/runtests.jl index 4f2c40c9..44a1968b 100644 --- a/test/DistributedTests/mpi/runtests.jl +++ b/test/DistributedTests/mpi/runtests.jl @@ -6,8 +6,8 @@ using MPI #Sysimage sysimage=nothing if length(ARGS)==1 - @assert isfile(ARGS[1]) "$(ARGS[1]) must be a valid Julia sysimage file" - sysimage=ARGS[1] + @assert isfile(ARGS[1]) "$(ARGS[1]) must be a valid Julia sysimage file" + sysimage=ARGS[1] end mpidir = @__DIR__ @@ -17,10 +17,10 @@ repodir = joinpath(testdir,"..","..") function run_driver(procs,file,sysimage) mpiexec() do cmd if sysimage!=nothing - extra_args="-J$(sysimage)" - run(`$cmd -n $procs $(Base.julia_cmd()) $(extra_args) --project=$repodir $(joinpath(mpidir,file))`) + extra_args="-J$(sysimage)" + run(`$cmd -n $procs $(Base.julia_cmd()) $(extra_args) --project=$repodir $(joinpath(mpidir,file))`) else - run(`$cmd -n $procs $(Base.julia_cmd()) --project=$repodir $(joinpath(mpidir,file))`) + run(`$cmd -n $procs $(Base.julia_cmd()) --project=$repodir $(joinpath(mpidir,file))`) end @test true end diff --git a/test/DistributedTests/mpi/runtests_body.jl b/test/DistributedTests/mpi/runtests_body.jl index fe600b7c..ddce7ee4 100644 --- a/test/DistributedTests/mpi/runtests_body.jl +++ b/test/DistributedTests/mpi/runtests_body.jl @@ -6,6 +6,10 @@ using MPI include("../PoissonTests.jl") include("../AggregatesTests.jl") +include("../DistributedDiscreteGeometryPoissonTest.jl") +include("../DistributedLSDiscreteGeometryPoissonTest.jl") +include("../PeriodicDistributedDiscreteGeometryPoissonTest.jl") +include("../GeometricalDifferentiationTests.jl") if ! MPI.Initialized() MPI.Init() @@ -21,8 +25,24 @@ function all_tests(distribute,parts) PoissonTests.main(distribute,(prod(parts),1),cells=(12,12),geometry=:remotes) PArrays.toc!(t,"Poisson") + PArrays.tic!(t) + DistributedDiscreteGeometryPoissonTest.main(distribute,parts) + DistributedDiscreteGeometryPoissonTest.main(distribute,(prod(parts),1),cells=(12,12),geometry=:remotes) + PArrays.toc!(t,"DistributedDiscreteGeometryPoisson") + + PArrays.tic!(t) + DistributedLSDiscreteGeometryPoissonTest.main(distribute,parts,cells=(21,21)) + PArrays.toc!(t,"DistributedLSDiscreteGeometryPoissonTest") + + ## Disabled due to Issue #87 + # PArrays.tic!(t) + # PeriodicDistributedDiscreteGeometryPoissonTest.main(distribute,(4,4),cells=(31,31),geometry=:circle) + # PeriodicDistributedDiscreteGeometryPoissonTest.main(distribute,(4,1),cells=(31,31),geometry=:remotes) + # PArrays.toc!(t,"PeriodicDistributedDiscreteGeometryPoisson") + if prod(parts) == 4 DistributedAggregatesTests.main(distribute,parts) + DistributedGeometricalDifferentitationTests.main(distribute,4) end PArrays.toc!(t,"Aggregates") diff --git a/test/DistributedTests/sequential/DistributedDiscreteGeometryPoissonTest.jl b/test/DistributedTests/sequential/DistributedDiscreteGeometryPoissonTest.jl new file mode 100644 index 00000000..080d4b9f --- /dev/null +++ b/test/DistributedTests/sequential/DistributedDiscreteGeometryPoissonTest.jl @@ -0,0 +1,8 @@ +module DistributedDiscreteGeometryPoissonTestsSeq +using PartitionedArrays +include("../DistributedDiscreteGeometryPoissonTest.jl") +with_debug() do distribute + DistributedDiscreteGeometryPoissonTest.main(distribute,(2,2)) + DistributedDiscreteGeometryPoissonTest.main(distribute,(4,1),cells=(12,12),geometry=:remotes) +end +end diff --git a/test/DistributedTests/sequential/DistributedLSDiscreteGeometryPoissonTest.jl b/test/DistributedTests/sequential/DistributedLSDiscreteGeometryPoissonTest.jl new file mode 100644 index 00000000..afdba273 --- /dev/null +++ b/test/DistributedTests/sequential/DistributedLSDiscreteGeometryPoissonTest.jl @@ -0,0 +1,7 @@ +module DistributedLSDiscreteGeometryPoissonTestsSeq +using PartitionedArrays +include("../DistributedLSDiscreteGeometryPoissonTest.jl") +with_debug() do distribute + DistributedLSDiscreteGeometryPoissonTest.main(distribute,(2,2),cells=(21,21)) +end +end diff --git a/test/DistributedTests/sequential/PeriodicDistributedDiscreteGeometryPoissonTest.jl b/test/DistributedTests/sequential/PeriodicDistributedDiscreteGeometryPoissonTest.jl new file mode 100644 index 00000000..7802ba31 --- /dev/null +++ b/test/DistributedTests/sequential/PeriodicDistributedDiscreteGeometryPoissonTest.jl @@ -0,0 +1,8 @@ +module PeriodicDistributedDiscreteGeometryPoissonTest +using PartitionedArrays +include("../PeriodicDistributedDiscreteGeometryPoissonTest.jl") +with_debug() do distribute + PeriodicDistributedDiscreteGeometryPoissonTest.main(distribute,(4,4),cells=(31,31),geometry=:circle) + PeriodicDistributedDiscreteGeometryPoissonTest.main(distribute,(4,1),cells=(31,31),geometry=:remotes) +end +end diff --git a/test/DistributedTests/sequential/runtests.jl b/test/DistributedTests/sequential/runtests.jl index e68831c3..57910a85 100644 --- a/test/DistributedTests/sequential/runtests.jl +++ b/test/DistributedTests/sequential/runtests.jl @@ -3,5 +3,10 @@ module SequentialTests using Test @time @testset "PoissonSeq" begin include("PoissonTests.jl") end +@time @testset "DiscreteGeoPoissonSeq" begin include("DistributedDiscreteGeometryPoissonTest.jl") end +@time @testset "LSDiscreteGeoPoissonSeq" begin include("DistributedLSDiscreteGeometryPoissonTest.jl") end + +## Disabled due to Issue #87 +# @time @testset "PeriodicDiscreteGeoPoissonSeq" begin include("PeriodicDistributedDiscreteGeometryPoissonTest.jl") end end diff --git a/test/DistributedTests/testing_remote_no_aggs.jl b/test/DistributedTests/testing_remote_no_aggs.jl index 87e5d1aa..0bee27bd 100644 --- a/test/DistributedTests/testing_remote_no_aggs.jl +++ b/test/DistributedTests/testing_remote_no_aggs.jl @@ -133,18 +133,19 @@ uh1 = h1(uh) Γ = EmbeddedBoundary(cutgeo) Ωbg = Triangulation(bgmodel) -writevtk(Ω,"trian"); -writevtk(Γ,"bnd"); -writevtk(Ωbg,"bg_trian"); -writevtk(Ω_act,"act_trian"); +path = mktempdir() +writevtk(Ω,joinpath(path,"trian")); +writevtk(Γ,joinpath(path,"bnd")); +writevtk(Ωbg,joinpath(path,"bg_trian")); +writevtk(Ω_act,joinpath(path,"act_trian")); -writevtk(Ω,"trian", +writevtk(Ω,joinpath(path,"trian"), cellfields=["uh"=>uh,"u"=>u,"e"=>e],); map(local_views(uh),local_views(bgmodel),ranks) do uh,m,p trian = Triangulation(m) - writevtk(trian,"ltrian_$p",cellfields=["uh"=>uh]) + writevtk(trian,joinpath(path,"ltrian_$p"),cellfields=["uh"=>uh]) end end # module diff --git a/test/GridapEmbeddedTests/EmbeddedBimaterialPoissonCutFEMTests.jl b/test/GridapEmbeddedTests/EmbeddedBimaterialPoissonCutFEMTests.jl index 3d3e718d..138857b5 100644 --- a/test/GridapEmbeddedTests/EmbeddedBimaterialPoissonCutFEMTests.jl +++ b/test/GridapEmbeddedTests/EmbeddedBimaterialPoissonCutFEMTests.jl @@ -103,10 +103,11 @@ uh1, uh2 = solve(op) uh = (uh1,uh2) # Postprocess +path = mktempdir() qh1 = α1*∇(uh1) qh2 = α2*∇(uh2) -writevtk(Ω1,"results1",cellfields=["uh"=>uh1,"qh"=>qh1]) -writevtk(Ω2,"results2",cellfields=["uh"=>uh2,"qh"=>qh2]) +writevtk(Ω1,joinpath(path,"results1"),cellfields=["uh"=>uh1,"qh"=>qh1]) +writevtk(Ω2,joinpath(path,"results2"),cellfields=["uh"=>uh2,"qh"=>qh2]) #writevtk(model1,"model1") #writevtk(model2,"model2") diff --git a/test/GridapEmbeddedTests/PeriodicPoissonAgFEMTests.jl b/test/GridapEmbeddedTests/PeriodicPoissonAgFEMTests.jl new file mode 100644 index 00000000..0ce321ef --- /dev/null +++ b/test/GridapEmbeddedTests/PeriodicPoissonAgFEMTests.jl @@ -0,0 +1,111 @@ +module PeriodicPoissonAgFEMTests + +using Gridap +using GridapEmbedded +using Test + +function update_labels!(e::Integer,model::CartesianDiscreteModel,f_Γ::Function,name::String) + mask = mark_nodes(f_Γ,model) + _update_labels_locally!(e,model,mask,name) + nothing +end + +function _update_labels_locally!(e,model::CartesianDiscreteModel{2},mask,name) + topo = Gridap.Geometry.get_grid_topology(model) + labels = Gridap.Geometry.get_face_labeling(model) + cell_to_entity = labels.d_to_dface_to_entity[end] + entity = maximum(cell_to_entity) + e + # Vertices + vtxs_Γ = findall(mask) + vtx_edge_connectivity = Array(Gridap.Geometry.get_faces(topo,0,1)[vtxs_Γ]) + # Edges + edge_entries = [findall(x->any(x .∈ vtx_edge_connectivity[1:end.!=j]), + vtx_edge_connectivity[j]) for j = 1:length(vtx_edge_connectivity)] + edge_Γ = unique(reduce(vcat,getindex.(vtx_edge_connectivity,edge_entries),init=[])) + labels.d_to_dface_to_entity[1][vtxs_Γ] .= entity + labels.d_to_dface_to_entity[2][edge_Γ] .= entity + add_tag!(labels,name,[entity]) + return cell_to_entity +end + +function mark_nodes(f,model::DiscreteModel) + topo = Gridap.Geometry.get_grid_topology(model) + coords = Gridap.Geometry.get_vertex_coordinates(topo) + mask = map(f,coords) + return mask +end + +u(x) = (x[1]-0.5)^2 + 2(x[2]-0.5)^2 +f(x) = -Δ(u)(x) +ud(x) = u(x) + +# R = 0.3 +# geo1 = square(;L=2) +# geo2 = disk(R,x0=Point(0.5,0.5)) + +geom = disk(0.55,x0=Point(0.5,0.5)) +# geom = setdiff(geo1,geo2) + +n = 31 +partition = (n,n) +domain = (0,1,0,1) +bgmodel = CartesianDiscreteModel(domain,partition;isperiodic=(true,true)) +update_labels!(1,bgmodel,x->iszero(x[1]) || iszero(x[2]),"outer_boundary") + +dp = 1 +const h = dp/n + +cutgeo = cut(bgmodel,geom) + +strategy = AggregateAllCutCells() +aggregates = aggregate(strategy,cutgeo) + +Ω_bg = Triangulation(bgmodel) +Ω_act = Triangulation(cutgeo,ACTIVE) +Ω = Triangulation(cutgeo,PHYSICAL) +Γ = EmbeddedBoundary(cutgeo) + +n_Γ = get_normal_vector(Γ) + +order = 2 +degree = 2*order +dΩ = Measure(Ω,degree) +dΓ = Measure(Γ,degree) + +model = get_active_model(Ω_act) +Vstd = FESpace(Ω_act,FiniteElements(PhysicalDomain(),model,lagrangian,Float64,order);dirichlet_tags="outer_boundary") + +V = AgFEMSpace(Vstd,aggregates) +U = TrialFESpace(V,ud) + +const γd = 10.0 + +a(u,v) = + ∫( ∇(v)⋅∇(u) ) * dΩ + + ∫( (γd/h)*v*u - v*(n_Γ⋅∇(u)) - (n_Γ⋅∇(v))*u ) * dΓ + +l(v) = + ∫( v*f ) * dΩ + + ∫( (γd/h)*v*ud - (n_Γ⋅∇(v))*ud ) * dΓ + +op = AffineFEOperator(a,l,U,V) +uh = solve(op) + +e = u - uh + +l2(u) = sqrt(sum( ∫( u*u )*dΩ )) +h1(u) = sqrt(sum( ∫( u*u + ∇(u)⋅∇(u) )*dΩ )) + +el2 = l2(e) +eh1 = h1(e) +ul2 = l2(uh) +uh1 = h1(uh) + +#colors = color_aggregates(aggregates,bgmodel) +#writevtk(Ω_bg,"trian",celldata=["aggregate"=>aggregates,"color"=>colors],cellfields=["uh"=>uh]) +# writevtk(Ω,"trian_O",cellfields=["uh"=>uh,"u"=>u]) +# writevtk(Γ,"trian_G") +@test el2/ul2 < 1.e-8 +@test eh1/uh1 < 1.e-7 + +end # module diff --git a/test/GridapEmbeddedTests/TraceFEMTests.jl b/test/GridapEmbeddedTests/TraceFEMTests.jl index 7953e8f0..0e46c7d2 100644 --- a/test/GridapEmbeddedTests/TraceFEMTests.jl +++ b/test/GridapEmbeddedTests/TraceFEMTests.jl @@ -26,8 +26,6 @@ cutgeom = cut(bgmodel,geom) Γ = EmbeddedBoundary(cutgeom,geom) Γg = GhostSkeleton(cutgeom,CUT,geom) -writevtk(Γg,"Γg") - order=1 V = TestFESpace(Ωc,ReferenceFE(lagrangian,Float64,order),conformity=:H1) U = TrialFESpace(V) diff --git a/test/GridapEmbeddedTests/runtests.jl b/test/GridapEmbeddedTests/runtests.jl index 1576bc42..f6d52df3 100644 --- a/test/GridapEmbeddedTests/runtests.jl +++ b/test/GridapEmbeddedTests/runtests.jl @@ -6,6 +6,8 @@ using Test @time @testset "PoissonAgFEM" begin include("PoissonAgFEMTests.jl") end +@time @testset "PeriodicPoissonAgFEM" begin include("PeriodicPoissonAgFEMTests.jl") end + @time @testset "PoissonModalC0AgFEM" begin include("PoissonModalC0AgFEMTests.jl") end @time @testset "BimaterialPoissonCutFEM" begin include("BimaterialPoissonCutFEMTests.jl") end diff --git a/test/InterfacesTests/EmbeddedFacetDiscretizationsTests.jl b/test/InterfacesTests/EmbeddedFacetDiscretizationsTests.jl index 7e6c9416..aa6bbec0 100644 --- a/test/InterfacesTests/EmbeddedFacetDiscretizationsTests.jl +++ b/test/InterfacesTests/EmbeddedFacetDiscretizationsTests.jl @@ -8,6 +8,9 @@ using Gridap.Geometry using GridapEmbedded.Interfaces using GridapEmbedded.LevelSetCutters +########################################## +# 2D tests + n = 10 partition = (n,n) domain = (0,1,0,1) @@ -37,6 +40,10 @@ u = interpolate(x->x[1]+x[2],V) Γ = lazy_append(Γu,Γf) Λ = SkeletonTriangulation(cutgeo_facets,PHYSICAL) +face_model = get_active_model(Γu) +Σb = BoundaryTriangulation(Γu) +Σi = SkeletonTriangulation(Γu) + test_triangulation(Ω) test_triangulation(Γ) test_triangulation(Λ) @@ -71,14 +78,18 @@ cellfields_Λ = ["normal"=> n_Λ.⁺,"jump_v"=>jump(v),"jump_u"=>jump(u)] d = mktempdir() try - writevtk(Ωbg,joinpath(d,"trian")) - writevtk(Ω,joinpath(d,"trian_O"),celldata=celldata_Ω,cellfields=cellfields_Ω) - writevtk(Γ,joinpath(d,"trian_G"),celldata=celldata_Γ,cellfields=cellfields_Γ) - writevtk(Λ,joinpath(d,"trian_sO"),celldata=celldata_Λ,cellfields=cellfields_Λ) + writevtk(Ωbg,joinpath(d,"trian"),append=false) + writevtk(Ω,joinpath(d,"trian_O"),celldata=celldata_Ω,cellfields=cellfields_Ω,append=false) + writevtk(Γ,joinpath(d,"trian_G"),celldata=celldata_Γ,cellfields=cellfields_Γ,append=false) + writevtk(Λ,joinpath(d,"trian_sO"),celldata=celldata_Λ,cellfields=cellfields_Λ,append=false) + writevtk(Γf,joinpath(d,"trian_Gf"),append=false) finally rm(d,recursive=true) end +########################################## +# 3D tests + n = 10 partition = (n,n,n) domain = (0,1,0,1,0,1) @@ -95,11 +106,16 @@ trian_s = SkeletonTriangulation(bgmodel) trian_sΩ = SkeletonTriangulation(trian_s,cutgeo_facets,PHYSICAL_IN,geo) trian_sΩo = SkeletonTriangulation(trian_s,cutgeo_facets,PHYSICAL_OUT,geo) +Γu = EmbeddedBoundary(cutgeo) +face_model = get_active_model(Γu) +Σb = BoundaryTriangulation(Γu) +Σi = SkeletonTriangulation(Γu) + d = mktempdir() try -writevtk(trian_s,joinpath(d,"trian_s")) -writevtk(trian_sΩ,joinpath(d,"trian_sO")) -writevtk(trian_sΩo,joinpath(d,"trian_sOo")) + writevtk(trian_s,joinpath(d,"trian_s")) + writevtk(trian_sΩ,joinpath(d,"trian_sO")) + writevtk(trian_sΩo,joinpath(d,"trian_sOo")) finally rm(d,recursive=true) end diff --git a/test/LevelSetCuttersTests/DiscreteGeometriesTests.jl b/test/LevelSetCuttersTests/DiscreteGeometriesTests.jl index 5a7479e3..f246c2e0 100644 --- a/test/LevelSetCuttersTests/DiscreteGeometriesTests.jl +++ b/test/LevelSetCuttersTests/DiscreteGeometriesTests.jl @@ -38,4 +38,16 @@ test_geometry(geo4_y) #print_tree(stdout,get_tree(geo4_x)) #print_tree(stdout,replace_data(d->objectid(first(d)),get_tree(geo4_x))) +#using a cellfield +domain = (0,1,0,1) +n = 10 +bgmodel = CartesianDiscreteModel(domain,(n,n)) + +reffe = ReferenceFE(lagrangian,Float64,1) +Ω_bg = Triangulation(bgmodel) +V_bg = FESpace(Ω_bg,reffe) +φh = interpolate(x->sqrt((x[1]-0.5)^2+(x[2]-0.5)^2)-0.55,V_bg) +geo = DiscreteGeometry(φh,bgmodel) +test_geometry(geo) + end # module diff --git a/test/LevelSetCuttersTests/GeometricalDifferentiationTests.jl b/test/LevelSetCuttersTests/GeometricalDifferentiationTests.jl new file mode 100644 index 00000000..d834b0eb --- /dev/null +++ b/test/LevelSetCuttersTests/GeometricalDifferentiationTests.jl @@ -0,0 +1,513 @@ +module GeometricalDifferentiationTests +############################################################################################ +# These tests are meant to verify the correctness differentiation of functionals w.r.t the +# level set defining the cut domain. +# They are based on the following work: +# "Level-set topology optimisation with unfitted finite elements and automatic shape differentiation" +# by Z. J. Wegert, J. Manyer, C. Mallon, S. Badia, V. J. Challis (2025) +############################################################################################ +using Test, FiniteDiff + +using Gridap, Gridap.Geometry, Gridap.Adaptivity, Gridap.Arrays, Gridap.MultiField +using GridapEmbedded, GridapEmbedded.LevelSetCutters, GridapEmbedded.Interfaces + +using GridapEmbedded.Interfaces: get_conormal_vector +using GridapEmbedded.Interfaces: get_subfacet_normal_vector +using GridapEmbedded.Interfaces: get_ghost_normal_vector + +using GridapEmbedded.LevelSetCutters: DifferentiableTriangulation + +# We general a simplicial model where the simplices are created in a symmetric way using +# varycentric refinement of QUADs and HEXs. +function generate_model(D,n) + domain = (D==2) ? (0,1,0,1) : (0,1,0,1,0,1) + cell_partition = (D==2) ? (n,n) : (n,n,n) + base_model = UnstructuredDiscreteModel((CartesianDiscreteModel(domain,cell_partition))) + ref_model = refine(base_model, refinement_method = "barycentric") + model = ref_model.model + return model +end + +function level_set(shape::Symbol;N=4) + if shape == :square + x -> max(abs(x[1]-0.5),abs(x[2]-0.5))-0.25 # Square + elseif shape == :corner_2d + x -> ((x[1]-0.5)^N+(x[2]-0.5)^N)^(1/N)-0.25 # Curved corner + elseif shape == :diamond + x -> abs(x[1]-0.5)+abs(x[2]-0.5)-0.25-0/n/10 # Diamond + elseif shape == :circle + x -> sqrt((x[1]-0.5)^2+(x[2]-0.5)^2)-0.5223 # Circle + elseif shape == :circle_2 + x -> sqrt((x[1]-0.5)^2+(x[2]-0.5)^2)-0.23 # Circle + elseif shape == :square_prism + x -> max(abs(x[1]-0.5),abs(x[2]-0.5),abs(x[3]-0.5))-0.25 # Square prism + elseif shape == :corner_3d + x -> ((x[1]-0.5)^N+(x[2]-0.5)^N+(x[3]-0.5)^N)^(1/N)-0.25 # Curved corner + elseif shape == :diamond_prism + x -> abs(x[1]-0.5)+abs(x[2]-0.5)+abs(x[3]-0.5)-0.25-0/n/10 # Diamond prism + elseif shape == :sphere + x -> sqrt((x[1]-0.5)^2+(x[2]-0.5)^2+(x[3]-0.5)^2)-0.53 # Sphere + elseif shape == :sphere_2 + x -> sqrt((x[1]-0.5)^2+(x[2]-0.5)^2+(x[3]-0.5)^2)-0.23 # Sphere + elseif shape == :regular_2d + x -> cos(2π*x[1])*cos(2π*x[2])-0.11 # "Regular" LSF + elseif shape == :regular_3d + x -> cos(2π*x[1])*cos(2π*x[2])*cos(2π*x[3])-0.11 # "Regular" LSF + else + error("Unknown shape") + end +end + +function main( + model,ls::Function,f::Function; + vtk=false, + name="embedded", + verbose=false, + fdm=false +) + order = 1 + reffe = ReferenceFE(lagrangian,Float64,order) + V_φ = TestFESpace(model,reffe) + + U = TestFESpace(model,reffe) + + φh = interpolate(ls,V_φ) + fh = interpolate(f,V_φ) + uh = interpolate(x->x[1]+x[2],U) + + # Correction if level set is on top of a node + x_φ = get_free_dof_values(φh) + idx = findall(isapprox(0.0;atol=10^-10),x_φ) + !isempty(idx) && @info "Correcting level values!" + x_φ[idx] .+= 100*eps(eltype(x_φ)) + + geo = DiscreteGeometry(φh,model) + cutgeo = cut(model,geo) + + # A.1) Volume integral + + Ω = Triangulation(cutgeo,PHYSICAL_IN) + Ω_AD = DifferentiableTriangulation(Ω,V_φ) + dΩ = Measure(Ω_AD,2*order) + + Γ = EmbeddedBoundary(cutgeo) + n_Γ = get_normal_vector(Γ) + dΓ = Measure(Γ,2*order) + + J_bulk(φ) = ∫(fh)dΩ + dJ_bulk_AD = gradient(J_bulk,φh) + dJ_bulk_AD_vec = assemble_vector(dJ_bulk_AD,V_φ) + + dJ_bulk_exact(q) = ∫(-fh*q/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJ_bulk_exact_vec = assemble_vector(dJ_bulk_exact,V_φ) + + abs_error = norm(dJ_bulk_AD_vec - dJ_bulk_exact_vec,Inf) + + if fdm + function J_fdm_bulk(φ) + φh = FEFunction(V_φ,φ) + cutgeo = cut(model,DiscreteGeometry(φh,model)) + Ω = Triangulation(cutgeo,PHYSICAL_IN) + dΩ = Measure(Ω,2*order) + sum(∫(fh)dΩ) + end + dJ_FD = FiniteDiff.finite_difference_gradient(J_fdm_bulk,get_free_dof_values(φh)) + + abs_error_fdm = norm(dJ_bulk_AD_vec - dJ_FD,Inf) + end + + if verbose + println("A.1) Volume integral:") + println(" - norm(dJ_AD - dJ_exact,Inf) = ",abs_error) + fdm && println(" - norm(dJ_AD - dJ_FDM,Inf) = ",abs_error_fdm) + end + + @test abs_error < 1e-10 + + # Multifield case + U = TestFESpace(model,reffe) + V_φu = MultiFieldFESpace([V_φ,U]) + φuh = interpolate([φh,x->x[1]],V_φu) + J_bulk_mult((φ,u)) = ∫(fh)dΩ + dJ_bulk_AD = gradient(J_bulk_mult,φuh) + dJ_bulk_AD_vec = assemble_vector(dJ_bulk_AD,V_φu) + + dJ_bulk_exact_mult((dφ,du)) = ∫(-fh*dφ/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJ_bulk_exact_vec = assemble_vector(dJ_bulk_exact_mult,V_φu) + + abs_error = norm(dJ_bulk_AD_vec - dJ_bulk_exact_vec,Inf) + if verbose + println(" - norm(dJ_AD - dJ_exact,Inf) = ",abs_error," | Multifield test") + end + @test abs_error < 1e-10 + + # A.1.1) Volume integral with another field + + J_bulk_1(u,φ) = ∫(u+fh)dΩ + dJ_bulk_1_AD = gradient(φ->J_bulk_1(uh,φ),φh) + dJ_bulk_1_AD_vec = assemble_vector(dJ_bulk_1_AD,V_φ) + + dJ_bulk_1_exact(q,u) = ∫(-(u+fh)*q/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJ_bulk_1_exact_vec = assemble_vector(q->dJ_bulk_1_exact(q,uh),V_φ) + @test norm(dJ_bulk_1_AD_vec - dJ_bulk_1_exact_vec) < 1e-10 + + dJ_bulk_1_AD_in_u = gradient(u->J_bulk_1(u,φh),uh) + dJ_bulk_1_AD_in_u_vec = assemble_vector(dJ_bulk_1_AD_in_u,U) + dJ_bulk_1_exact_in_u(q,u) = ∫(q)dΩ + dJ_bulk_1_exact_in_u_vec = assemble_vector(q->dJ_bulk_1_exact_in_u(q,uh),U) + + abs_error = norm(dJ_bulk_1_AD_in_u_vec - dJ_bulk_1_exact_in_u_vec,Inf) + if verbose + println("A.1.1) Volume integral with another field:") + println(" - norm(dJ_AD - dJ_exact,Inf) = ",abs_error) + end + @test abs_error < 1e-10 + + # A.2) Volume integral + + g(fh) = ∇(fh)⋅∇(fh) + J_bulk2(φ) = ∫(g(fh))dΩ + dJ_bulk_AD2 = gradient(J_bulk2,φh) + dJ_bulk_AD_vec2 = assemble_vector(dJ_bulk_AD2,V_φ) + + dJ_bulk_exact2(q) = ∫(-g(fh)*q/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJ_bulk_exact_vec2 = assemble_vector(dJ_bulk_exact2,V_φ) + + abs_error = norm(dJ_bulk_AD_vec2 - dJ_bulk_exact_vec2,Inf) + + if verbose + println("A.2) Volume integral with grad of fields:") + println(" - norm(dJ_AD - dJ_exact,Inf) = ",abs_error) + end + + @test abs_error < 1e-10 + + # B.1) Facet integral + + Γ = EmbeddedBoundary(cutgeo) + Γ_AD = DifferentiableTriangulation(Γ,V_φ) + Λ = Skeleton(Γ) + Σ = Boundary(Γ) + + dΓ = Measure(Γ,2*order) + dΛ = Measure(Λ,2*order) + dΣ = Measure(Σ,2*order) + + n_Γ = get_normal_vector(Γ) + + n_S_Λ = get_normal_vector(Λ) + m_k_Λ = get_conormal_vector(Λ) + ∇ˢφ_Λ = Operation(abs)(n_S_Λ ⋅ ∇(φh).plus) + + n_S_Σ = get_normal_vector(Σ) + m_k_Σ = get_conormal_vector(Σ) + ∇ˢφ_Σ = Operation(abs)(n_S_Σ ⋅ ∇(φh)) + + dΓ_AD = Measure(Γ_AD,2*order) + J_int(φ) = ∫(fh)dΓ_AD + dJ_int_AD = gradient(J_int,φh) + dJ_int_AD_vec = assemble_vector(dJ_int_AD,V_φ) + + dJ_int_exact(w) = ∫((-n_Γ⋅∇(fh))*w/(abs(n_Γ ⋅ ∇(φh))))dΓ + + ∫(-n_S_Λ ⋅ (jump(fh*m_k_Λ) * mean(w) / ∇ˢφ_Λ))dΛ + + ∫(-n_S_Σ ⋅ (fh*m_k_Σ * w / ∇ˢφ_Σ))dΣ + dJ_int_exact_vec = assemble_vector(dJ_int_exact,V_φ) + + abs_error = norm(dJ_int_AD_vec - dJ_int_exact_vec,Inf) + + if fdm + Ω_data = EmbeddedCollection(model,φh) do cutgeo,_,_ + Γ_AD = DifferentiableTriangulation(EmbeddedBoundary(cutgeo),V_φ) + (;:dΓ_AD => Measure(Γ_AD,2*order)) + end + function J_fdm_surf(φ) + sum(∫(fh)Ω_data.dΓ_AD) + end + dJ_surf_FD = FiniteDiff.finite_difference_gradient(J_fdm_surf,get_free_dof_values(φh)) + + abs_error_fdm = norm(dJ_int_AD_vec - dJ_surf_FD,Inf) + end + + if verbose + println("B.1) Surface integral:") + println(" - norm(dJ_AD - dJ_exact,Inf) = ",abs_error) + fdm && println(" - norm(dJ_AD - dJ_FDM,Inf) = ",abs_error_fdm) + end + @test abs_error < 1e-10 + + # B.2) Facet integral + g(fh) = ∇(fh)⋅∇(fh) + ∇g(∇∇f,∇f) = ∇∇f⋅∇f + ∇f⋅∇∇f + + J_int2(φ) = ∫(g(fh))dΓ_AD + dJ_int_AD2 = gradient(J_int2,φh) + dJ_int_AD_vec2 = assemble_vector(dJ_int_AD2,V_φ) + + dJ_int_exact2(w) = ∫((-n_Γ⋅ (∇g ∘ (∇∇(fh),∇(fh))))*w/(abs(n_Γ ⋅ ∇(φh))))dΓ + + ∫(-n_S_Λ ⋅ (jump(g(fh)*m_k_Λ) * mean(w) / ∇ˢφ_Λ))dΛ + + ∫(-n_S_Σ ⋅ (g(fh)*m_k_Σ * w / ∇ˢφ_Σ))dΣ + dJ_int_exact_vec2 = assemble_vector(dJ_int_exact2,V_φ) + + abs_error = norm(dJ_int_AD_vec2 - dJ_int_exact_vec2,Inf) + if verbose + println("B.2) Surface integral with grad of other fields:") + println(" - norm(dJ_AD - dJ_exact,Inf) = ",abs_error) + end + @test abs_error < 1e-10 + + if vtk + path = "results/$(name)/" + mkpath(path) + Ω_bg = Triangulation(model) + writevtk( + Ω_bg,"$(path)results", + cellfields = [ + "φh" => φh,"∇φh" => ∇(φh), + "dJ_bulk_AD" => FEFunction(V_φ,dJ_bulk_AD_vec), + "dJ_bulk_exact" => FEFunction(V_φ,dJ_bulk_exact_vec), + "dJ_int_AD" => FEFunction(V_φ,dJ_int_AD_vec), + "dJ_int_exact" => FEFunction(V_φ,dJ_int_exact_vec) + ], + celldata = [ + "inoutcut" => GridapEmbedded.Interfaces.compute_bgcell_to_inoutcut(model,geo) + ]; + append = false + ) + + writevtk( + Ω, "$(path)omega"; append = false + ) + writevtk( + Γ, "$(path)gamma"; append = false + ) + + n_∂Ω_Λ = get_subfacet_normal_vector(Λ) + n_k_Λ = get_ghost_normal_vector(Λ) + writevtk( + Λ, "$(path)_lambda", + cellfields = [ + "n_∂Ω.plus" => n_∂Ω_Λ.plus,"n_∂Ω.minus" => n_∂Ω_Λ.minus, + "n_k.plus" => n_k_Λ.plus,"n_k.minus" => n_k_Λ.minus, + "n_S" => n_S_Λ, + "m_k.plus" => m_k_Λ.plus,"m_k.minus" => m_k_Λ.minus, + "∇ˢφ" => ∇ˢφ_Λ, + "∇φh_Γs_plus" => ∇(φh).plus,"∇φh_Γs_minus" => ∇(φh).minus, + "jump(fh*m_k)" => jump(fh*m_k_Λ) + ]; + append = false + ) + + if num_cells(Σ) > 0 + n_∂Ω_Σ = get_subfacet_normal_vector(Σ) + n_k_Σ = get_ghost_normal_vector(Σ) + writevtk( + Σ, "$(path)_sigma", + cellfields = [ + "n_∂Ω" => n_∂Ω_Σ, "n_k" => n_k_Σ, + "n_S" => n_S_Σ, "m_k" => m_k_Σ, + "∇ˢφ" => ∇ˢφ_Σ, "∇φh_Γs" => ∇(φh), + ]; + append = false + ) + end + end +end + +## Concering integrals of the form `φ->∫(f ⋅ n(φ))dΓ(φ)` +function main_normal( + model,ls::Function,f_vec::Function; + vtk=false, + name="flux integrals", + run_test=true, + verbose=false, + fdm=false +) + order = 1 + reffe = ReferenceFE(lagrangian,Float64,order) + V_φ = TestFESpace(model,reffe) + + φh = interpolate(ls,V_φ) + + # Correction if level set is on top of a node + x_φ = get_free_dof_values(φh) + idx = findall(isapprox(0.0;atol=10^-10),x_φ) + !isempty(idx) && @info "Correcting level values!" + x_φ[idx] .+= 100*eps(eltype(x_φ)) + + geo = DiscreteGeometry(φh,model) + cutgeo = cut(model,geo) + + Γ = EmbeddedBoundary(cutgeo) + n_Γ = get_normal_vector(Γ) + Γ_AD = DifferentiableTriangulation(Γ,V_φ) + dΓ_AD = Measure(Γ_AD,2*order) + dΓ = Measure(Γ,2*order) + + fh_Γ = CellField(f_vec,Γ) + fh_Γ_AD = CellField(f_vec,Γ_AD) + + function J_int(φ) + n = get_normal_vector(Γ_AD) + ∫(fh_Γ_AD⋅n)dΓ_AD + end + dJ_int_AD = gradient(J_int,φh) + dJ_int_AD_vec = assemble_vector(dJ_int_AD,V_φ) + + _n(∇φ) = ∇φ/(10^-20+norm(∇φ)) + dJ_int_phi = ∇(φ->∫(fh_Γ_AD ⋅ (_n ∘ (∇(φ))))dΓ_AD,φh) + dJh_int_phi = assemble_vector(dJ_int_phi,V_φ) + + run_test && @test norm(dJ_int_AD_vec - dJh_int_phi) < 1e-10 + + # Analytic + # Note: currently, the analytic result is only valid on closed domains thanks + # to the divergence theorem. I think it would take significant work to compute + # the analytic derivative generally as we can't rely on divergence theorem to + # rewrite it in a convenient way. As a result, we don't have an analytic result + # for general cases such as ∫( f(n(φ)) )dΓ(φ), nor the case when Γ intersects + # ∂D. Thankfully, we have AD instead ;) + # Note 2: For the case that Γ does intersect the surface, the result is correct + # everywhere except on the intersection. + + fh_Γ = CellField(f_vec,Γ) + fh_Γ_AD = CellField(f_vec,Γ_AD) + + # Note: this comes from rewriting via the divergence theorem: + # ∫(f ⋅ n(φ))dΓ(φ) = ∫(∇⋅f)dΩ(φ) + dJ_int_exact3(w) = ∫(-(∇⋅(fh_Γ))*w/(abs(n_Γ ⋅ ∇(φh))))dΓ + dJh_int_exact3 = assemble_vector(dJ_int_exact3,V_φ) + + # Finite diff + if fdm + function J_fdm_surf(φ) + φh = FEFunction(V_φ,φ) + cutgeo = cut(model,DiscreteGeometry(φh,model)) + Γ = EmbeddedBoundary(cutgeo) + dΓ = Measure(Γ,2*order) + n = get_normal_vector(Γ) + f = CellField(f_vec,Γ) + n = get_normal_vector(Γ) + sum(∫(f⋅n)dΓ) + end + dJ_FD = FiniteDiff.finite_difference_gradient(J_fdm_surf,get_free_dof_values(φh)) + + abs_error_fdm = norm(dJ_int_AD_vec - dJ_FD,Inf) + end + abs_error = norm(dJh_int_exact3 - dJ_int_AD_vec,Inf) + + if verbose + println("C) Flux integral:") + println(" - norm(dJ_AD - dJ_exact,Inf) = ",abs_error) + fdm && println(" - norm(dJ_AD - dJ_FDM,Inf) = ",abs_error_fdm) + end + + run_test && @test abs_error < 1e-10 + + if vtk + path = "results/$(name)/" + mkpath(path) + Ω_bg = Triangulation(model) + writevtk(Ω_bg,path*"Results",cellfields=[ + "dJ_AD"=>FEFunction(V_φ,dJ_int_AD_vec), + "dJ_AD_with_phi"=>FEFunction(V_φ,dJh_int_phi), + "dJ_exact"=>FEFunction(V_φ,dJh_int_exact3) + ]) + writevtk(Γ,path*"Gamma") + end +end + +# Finite difference verification of gradients of integrals of the form `φ->∫(f(n))dΓ(φ)` and Hessian's. +# Both of these do not currently have rigorous mathematical counterparts so we verify them +# with finite differences. +function main_fdm_only_verif(model,ls::Function; + verbose=false,compute_hess=false,fdm=false +) + order = 1 + reffe = ReferenceFE(lagrangian,Float64,order) + V_φ = TestFESpace(model,reffe) + φh = interpolate(ls,V_φ) + + # Correction if level set is on top of a node + x_φ = get_free_dof_values(φh) + idx = findall(isapprox(0.0;atol=10^-10),x_φ) + !isempty(idx) && @info "Correcting level values!" + x_φ[idx] .+= 100*eps(eltype(x_φ)) + + geo = DiscreteGeometry(φh,model) + cutgeo = cut(model,geo) + + Γ = EmbeddedBoundary(cutgeo) + Γ_AD = DifferentiableTriangulation(Γ,V_φ) + dΓ_AD = Measure(Γ_AD,2*order) + + # Sec 6.2 - 10.1007/s00466-017-1383-6 + g((x,y)) = x - 1/10*sin(2π*y/6) + gh = interpolate(g,V_φ) + _n_g(∇g) = ∇g/(10^-20+norm(∇g)) + n_g = _n_g ∘ ∇(gh) + j(x) = norm(x)^2 + + function J_int(φ) + n = get_normal_vector(Γ_AD) + ∫(j ∘ (n-n_g))dΓ_AD + end + dJ_int_AD = gradient(J_int,φh) + dJ_int_AD_vec = assemble_vector(dJ_int_AD,V_φ) + hess = hessian(J_int,φh) + d²J = assemble_matrix(hess,V_φ,V_φ) + + # Finite diff + if fdm + function J_fdm_surf(φ) + φh = FEFunction(V_φ,φ) + cutgeo = cut(model,DiscreteGeometry(φh,model)) + Γ = EmbeddedBoundary(cutgeo) + dΓ = Measure(Γ,2*order) + n = get_normal_vector(Γ) + sum(∫(j ∘ (n-n_g))dΓ) + end + dJ_FD = FiniteDiff.finite_difference_gradient(J_fdm_surf,get_free_dof_values(φh)) + d²J_FD = compute_hess ? FiniteDiff.finite_difference_hessian(J_fdm_surf,get_free_dof_values(φh)) : nothing + + abs_error_fdm = norm(dJ_int_AD_vec - dJ_FD,Inf) + abs_error_fdm_hess = compute_hess ? norm(d²J - d²J_FD,Inf) : nothing + + if verbose + println("D) g(n) surf integral:") + println(" - norm(dJ_AD - dJ_exact,Inf) = ","N/A") + println(" - norm(dJ_AD - dJ_FDM,Inf) = ",abs_error_fdm) + compute_hess && println(" - norm(d²J_AD - d²J_FDM,Inf) = ",abs_error_fdm_hess) + end + end +end + +####################### + +# FDM is quite expensive, so we only run if required. + +D = 2 +n = 10 +model = generate_model(D,n) +f(x) = x[1]+x[2] +fvec((x,y)) = VectorValue((1-x)^2,(1-y)^2) +main(model,level_set(:circle_2),f;vtk=false,verbose=true,name="2D_circle/")#,fdm=true) +main(model,level_set(:regular_2d),f;vtk=false,verbose=true,name="2D_reg/")#,fdm=true) +main_normal(model,level_set(:circle_2),fvec;vtk=false,verbose=true,name="2D_circle_flux/",run_test=true)#,fdm=true) +main_normal(model,level_set(:regular_2d),fvec;vtk=false,verbose=true,name="2D_reg_flux/",run_test=false)#,fdm=true) # This will fail as expected +main_fdm_only_verif(model,level_set(:circle_2),verbose=true)#,fdm=true) +main_fdm_only_verif(model,level_set(:regular_2d),verbose=true,compute_hess=true)#,fdm=true) + +D = 3 +n = 4 +model = generate_model(D,n) +φ = level_set(:regular_3d) +f(x) = x[1]+x[2] +fvec2((x,y,z)) = VectorValue((1-x)^2,(1-y)^2,0) +main(model,level_set(:sphere_2),f;vtk=false,verbose=true,name="3D_circle/")#,fdm=true) +main(model,level_set(:regular_3d),f;vtk=false,verbose=true,name="3D_reg/")#,fdm=true) +main_normal(model,level_set(:sphere_2),fvec2;vtk=false,verbose=true,name="3D_circle_flux/",run_test=true)#,fdm=true) +main_normal(model,level_set(:regular_3d),fvec2;vtk=false,verbose=true,name="3D_reg_flux/",run_test=false,)#fdm=true) # This will fail as expected +main_fdm_only_verif(model,level_set(:sphere_2),verbose=true)#,fdm=true) +main_fdm_only_verif(model,level_set(:regular_3d),verbose=true)#,fdm=true) + +end \ No newline at end of file diff --git a/test/LevelSetCuttersTests/runtests.jl b/test/LevelSetCuttersTests/runtests.jl index c108a65e..548e1149 100644 --- a/test/LevelSetCuttersTests/runtests.jl +++ b/test/LevelSetCuttersTests/runtests.jl @@ -12,4 +12,6 @@ using Test @testset "LevelSetCutters" begin include("LevelSetCuttersTests.jl") end +@testset "GeometricalDifferentiation" begin include("GeometricalDifferentiationTests.jl") end + end # module diff --git a/test_proj_rhs_func.jl b/test_proj_rhs_func.jl new file mode 100644 index 00000000..98e05f84 --- /dev/null +++ b/test_proj_rhs_func.jl @@ -0,0 +1,395 @@ +using Gridap +using GridapEmbedded + +include("./src/BGP/BGP.jl") + +# Problem selection +problem = 0 # 0 = Manufactured solution (L2-like projection), 1 = Darcy problem + +# Manufactured solution +order = 1 +uex(x) = -VectorValue(2*x[1],2*x[2]) +pex(x) = (x[1]^2 + x[2]^2) +divuex(x) = -4.0 + +# Select geometry +nint = 3 # number of (uncut) interior elements along single direction +# cut length +ε = 0.2/2.0 # not so small cut +# ε = 0.2e-2/2.0 # smaller cut +# ε = 0.2e-6/2.0 # smallest cut +pmin = Point(0.0,0.0) +pmax = Point(1.0,1.0) +function setup_geometry(nint, ε, pmin, pmax) + nbg = nint + 2 + 2 # number of elements in the background mesh (2 from cut, 2 dummy) + dp = pmax - pmin + h = dp[1]/nint + bgpmin = pmin - Point(2*h,2*h) + bgpmax = pmax + Point(2*h,2*h) + bgdp = bgpmax - bgpmin + partition = (nbg,nbg) + bgmodel = CartesianDiscreteModel(bgpmin,bgpmax,partition) + hbg = bgdp[1]/nbg + @assert abs(hbg - h) < 10e-10 + @assert abs(ε/h) < 0.5 + + # The following is based on square (part of GridapEmbedded): + e1 = VectorValue(1,0) + e2 = VectorValue(0,1) + x0 = pmin + Point(0.5*dp[1],0.5*dp[2]) + L1 = dp[1] + 2*ε + L2 = dp[2] + 2*ε + plane1 = plane(x0=x0-0.5*L2*e2,v=-e2,name="bottom") + plane2 = plane(x0=x0+0.5*L1*e1,v= e1,name="right") + plane3 = plane(x0=x0+0.5*L2*e2,v= e2,name="top") + plane4 = plane(x0=x0-0.5*L1*e1,v=-e1,name="left") + + geo12 = intersect(plane1,plane2) + geo34 = intersect(plane3,plane4) + + square = intersect(geo12,geo34) + cutgeo = cut(bgmodel, square) + + bgmodel, cutgeo, h +end +bgmodel, cutgeo, h= setup_geometry(nint, ε, pmin, pmax) + +# Setup aggregates +strategy = AggregateAllCutCells() +aggregates= aggregate(strategy,cutgeo) +aggregate_to_cells=setup_aggregate_to_cells(aggregates) +aggregates_bounding_box_model= + setup_aggregates_bounding_box_model(bgmodel,aggregate_to_cells) + +# Triangulations +Ωbg = Triangulation(bgmodel) +Ωact = Triangulation(cutgeo,ACTIVE) + +# Physical domain +Ω = Triangulation(cutgeo,PHYSICAL) +degree=2*2*(order+1) +dΩ = Measure(Ω,degree) + +# Setup agg cells and triangulation +agg_cells =flatten(aggregate_to_cells) #[CLEAN]: replaced setup_agg_cells +Ωbg_agg_cells=view(Ωbg,agg_cells) + +# Pressure space +if problem==0 + Q = FESpace(Ωact, ReferenceFE(lagrangian,Float64,order), conformity=:L2) +elseif problem==1 + # Set up zero-mean pressure space, with fixed interior dof + Qnzm = FESpace(Ωact, ReferenceFE(lagrangian,Float64,order), conformity=:L2) + int_cells = restrict_cells(cutgeo,IN) # global (background mesh') cell identifiers of interior cells + nonagg_int_cell = int_cells[findfirst(!in(agg_cells),int_cells)] # cell identifier (background mesh) of first interior cell not in aggregate + local_id = findfirst(isequal(nonagg_int_cell),Ωact.tface_to_mface) # local (active mesh') cell id of interior cell not in aggregate + dof_to_fix = get_cell_dof_ids(Qnzm)[local_id][1] + spaceWithConstantFixed = Gridap.FESpaces.FESpaceWithConstantFixed(Qnzm,true,Int64(dof_to_fix)) + Qzm_vol_i = assemble_vector(v->∫(v)*dΩ,Qnzm) + Qzm_vol = sum(Qzm_vol_i) + Q = Gridap.FESpaces.ZeroMeanFESpace(spaceWithConstantFixed,Qzm_vol_i,Qzm_vol) +end + +# Set up global spaces +V = FESpace(Ωact, ReferenceFE(raviart_thomas,Float64,order),conformity=:HDiv) +U = TrialFESpace(V) +P = TrialFESpace(Q) +Y = MultiFieldFESpace([V, Q]) +X = MultiFieldFESpace([U, P]) +dx = get_trial_fe_basis(X) +dy = get_fe_basis(Y) +du,dp = dx +dv,dq = dy + +# ref_agg_cell_to_agg_cell_map: \hat{K} -> K +ref_agg_cell_to_agg_cell_map=get_cell_map(Ωbg_agg_cells) +agg_cells_to_aggregate =setup_cells_to_aggregate(aggregate_to_cells) +ref_agg_cell_to_ref_bb_map =setup_ref_agg_cell_to_ref_bb_map(aggregates_bounding_box_model, + agg_cells_to_aggregate,ref_agg_cell_to_agg_cell_map) + +# Spaces on bounding boxes +reffeₚ_bb =ReferenceFE(lagrangian,Float64,order) + +Qbb=FESpace(aggregates_bounding_box_model,reffeₚ_bb,conformity=:L2) # We need a DG space to represent the L2 projection +Pbb=TrialFESpace(Qbb) +pbb=get_trial_fe_basis(Pbb) +qbb=get_fe_basis(Qbb) +reffeᵤ_bb=ReferenceFE(raviart_thomas,Float64,order) +Vbb=FESpace(aggregates_bounding_box_model,reffeᵤ_bb,conformity=:L2) +Ubb=TrialFESpace(Vbb) +ubb=get_trial_fe_basis(Ubb) +vbb=get_fe_basis(Vbb) + + +# Numerical integration (Measures) +dΩbg_agg_cells = Measure(Ωbg_agg_cells,degree) + +# # LHS of L2 projection on bounding boxes. + +# Selecting relevant global dofs ids of aggregate cells (from background mesh) +Ωbg_agg_cell_dof_ids = get_cell_dof_ids(X,Ωbg_agg_cells) +U_Ωbg_agg_cell_dof_ids = _restrict_to_block(Ωbg_agg_cell_dof_ids, 1) +P_Ωbg_agg_cell_dof_ids = _restrict_to_block(Ωbg_agg_cell_dof_ids, 2) + +# Computing local (per aggregate) dof ids +aggregate_to_local_cells=setup_aggregate_to_local_cells(aggregate_to_cells) +U_agg_cells_local_dof_ids= + compute_agg_cells_local_dof_ids(U_Ωbg_agg_cell_dof_ids, aggregate_to_local_cells) +P_agg_cells_local_dof_ids= + compute_agg_cells_local_dof_ids(P_Ωbg_agg_cell_dof_ids, aggregate_to_local_cells) + +# Compute global dofs ids per aggregate and reindex these +U_aggregate_dof_ids=compute_aggregate_dof_ids(U_Ωbg_agg_cell_dof_ids,aggregate_to_cells) +U_agg_cells_to_aggregate_dof_ids=lazy_map(Reindex(U_aggregate_dof_ids),agg_cells_to_aggregate) +P_aggregate_dof_ids=compute_aggregate_dof_ids(P_Ωbg_agg_cell_dof_ids,aggregate_to_cells) +P_agg_cells_to_aggregate_dof_ids=lazy_map(Reindex(P_aggregate_dof_ids),agg_cells_to_aggregate) + +# parameters +γ = 10.0 # Interior bulk-penalty stabilization parameter + +########################################### +### STABILIZATION ON Ωagg\Troot ### +########################################### + +# Setup cut cells and triangulation +aggregate_to_cut_cells = restrict_aggregate_to_cells(cutgeo,aggregate_to_cells,GridapEmbedded.Interfaces.CUT) +cut_cells = flatten(aggregate_to_cut_cells) +#TO-DO: look into why cut_cells = restrict_cells(cutgeo,GridapEmbedded.Interfaces.CUT) can not be used instead of the above +Ωbg_cut_cells = view(Ωbg,cut_cells) +dΩbg_cut_cells = Measure(Ωbg_cut_cells,degree) + +# Selecting relevant global dofs ids of cut cells (from background mesh) +Ωbg_cut_cell_dof_ids = get_cell_dof_ids(X,Ωbg_cut_cells) +U_Ωbg_cut_cell_dof_ids = _restrict_to_block(Ωbg_cut_cell_dof_ids, 1) +P_Ωbg_cut_cell_dof_ids = _restrict_to_block(Ωbg_cut_cell_dof_ids, 2) + +# Compute global dofs ids per aggregate and reindex these +cut_cells_to_aggregate = setup_cells_to_aggregate(aggregate_to_cut_cells) +U_cut_cells_to_aggregate_dof_ids=lazy_map(Reindex(U_aggregate_dof_ids),cut_cells_to_aggregate) +P_cut_cells_to_aggregate_dof_ids=lazy_map(Reindex(P_aggregate_dof_ids),cut_cells_to_aggregate) + +##==============================================================================## +# OBJECTIVE +##==============================================================================## +#= + Compute and assemble the bulk penalty stabilization term + + γ ∫( (v)⊙(rhs_function - proj_rhs_function) )*dD + + with `v` the test basis functions (which may be components of a MultiField). The L2 projections `proj_rhs_function` can be computed via `setup_L2_proj_in_bb_space`. `dD` is the measure of the cut cells or full aggregate, that is `dΩbg_cut_cells` or `dΩbg_agg_cells`. The function `rhs_function` is the forcing term appearing on the right hand-side of the PDE. + + Here, we are interested in using the pressure basis functions for the test space, thus v = dq. In addition, dD = dΩbg_cut_cells. +=# + +# Manually set up the arrays that collect_cell_vector would return automatically +test_w = [] +test_r = [] + +# Pick a rhs function +# rhs_func(x) = 10.0*x[1]^2 + sin(x[2]) +rhs_func(x) = 10.0*x[1] +# rhs_func(x) = 1000.0 + +##==============================================================================## +# First term: γ ∫( (v)⊙(rhs_function) )*dD +##==============================================================================## +test_rhs_vec_contribs1=get_array(∫(γ*(rhs_func⊙dq))*dΩbg_cut_cells) # 16-element array, one entry per T ∈ Ωbg_cut_cells, with 1-element Vector (for pressure) per cell T. +push!(test_w, test_rhs_vec_contribs1) + +if (_is_multifield_fe_basis_component(dq)) + nfields=_nfields(dq) + fieldid=_fieldid(dq) + testP_Ωbg_cut_cell_dof_ids=lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),P_Ωbg_cut_cell_dof_ids) +end +push!(test_r, testP_Ωbg_cut_cell_dof_ids) + +##==============================================================================## +# Second term: - γ ∫( (v)⊙(proj_rhs_function) )*dD +##==============================================================================## + +## (1) Set-up proj_rhs_function using +#= + +returns the L2 projection in the bounding box space of the rhs function. That is, the L2 projection is defined through + + ∫ (rhs_func - Π_{Zbb}(operation(u)) zbb dΩbg_agg_cells = 0 ∀ zbb ∈ Zbb(T), + +with T ∈ T_agg and `Zbb` the bounding box space. Note that Ωbg_agg_cells ⊆ Ωbg_bb. Additionally, Π_{Zbb}(operation(u)) appears in the lhs as the trial function wbb ∈ Zbb. + +=# +test_wbb = pbb # Bounding box trial space (for pressure) +test_zbb = qbb # Bounding box test space (for pressure) + +test_wbb_Ωbg_agg_cells=change_domain_bb_to_agg_cells(test_wbb, ref_agg_cell_to_ref_bb_map, Ωbg_agg_cells, agg_cells_to_aggregate) # trial +test_zbb_Ωbg_agg_cells=change_domain_bb_to_agg_cells(test_zbb, ref_agg_cell_to_ref_bb_map, Ωbg_agg_cells, agg_cells_to_aggregate) # test +test_agg_cells_to_lhs_contribs=get_array(∫(test_zbb_Ωbg_agg_cells⋅test_wbb_Ωbg_agg_cells)dΩbg_agg_cells) +test_ass_lhs_map= BulkGhostPenaltyAssembleLhsMap(test_agg_cells_to_lhs_contribs) +test_lhs = lazy_map(test_ass_lhs_map,aggregate_to_local_cells) +test_agg_cells_rhs_contribs=get_array(∫(test_zbb_Ωbg_agg_cells⋅rhs_func)dΩbg_agg_cells) +test_rhs = lazy_map(sum, + lazy_map(Broadcasting(Reindex(test_agg_cells_rhs_contribs)), + aggregate_to_local_cells)) +test_f_proj_Zbb_dofs=lazy_map(\,test_lhs,test_rhs) +test_f_proj_Zbb_array=lazy_map(Gridap.Fields.linear_combination, + test_f_proj_Zbb_dofs, + Gridap.CellData.get_data(test_zbb)) + +# Change domain of proj_op_u and proj_op_v from Ωbb to Ωbg_agg_cells +test_f_proj_Zbb_array_agg_cells=lazy_map(Broadcasting(∘), + lazy_map(Reindex(test_f_proj_Zbb_array),agg_cells_to_aggregate), ref_agg_cell_to_ref_bb_map) +test_f_proj_Zbb_agg_cells = + Gridap.CellData.GenericCellField(test_f_proj_Zbb_array_agg_cells,Ωbg_agg_cells,ReferenceDomain()) + +## (2) Define γ ∫( (v)⊙(proj_rhs_function) )*dD +dq_on_Ωbg_agg_cells = Gridap.CellData.change_domain(dq,Ωbg_agg_cells,ReferenceDomain()) +test_rhs_vec_contribs2 = get_array(∫((-1.0)*γ*(test_f_proj_Zbb_agg_cells)⋅dq_on_Ωbg_agg_cells)*dΩbg_cut_cells) +push!(test_w, test_rhs_vec_contribs2) +push!(test_r, testP_Ωbg_cut_cell_dof_ids) # same dofs as as contr1? TOCHECK! + +# ### TEST FOR INDIVIDUAL CONTRIBUTIONS: I expect these to cancel out +# for i= 1:16 +# println("===== i: $i") +# println("contr1: $(test_rhs_vec_contribs1[i][2])") +# println("contr2: $(test_rhs_vec_contribs2[i][2])") +# end + +# ### TEST FOR SUM OF CONTRIBUTIONS: I expect these to cancel out +# ([sum(get_array(∫(test_f_proj_Zbb_agg_cells)dΩbg_cut_cells)[i]) for i=1:16]) +# sum_cut_cells_rhs = sum([sum(get_array(∫(test_f_proj_Zbb_agg_cells)dΩbg_cut_cells)[i]) for i=1:16]) +# sum((get_array(∫(rhs_func)*dΩbg_cut_cells))) +# difference = abs(sum_cut_cells_rhs - sum((get_array(∫(rhs_func)*dΩbg_cut_cells)))) + +#### -- TEST PROBLEM -- #### +test_a((u,p),(v,q))=∫(u⋅v+q*p)dΩ +uex(x) = -VectorValue(2.0,2.0) +test_l((v,q))=∫(uex⋅v+0.0⋅q)dΩ + +test_mat_A=Gridap.FESpaces.collect_cell_matrix(X,Y,test_a(dx,dy)) +test_vec_b=Gridap.FESpaces.collect_cell_vector(Y,test_l(dy)) +push!(test_vec_b[1], test_w...) +push!(test_vec_b[2], test_r...) + +assem=SparseMatrixAssembler(X,Y) + +test_A = assemble_matrix(assem, test_mat_A) +test_b = assemble_vector(assem, test_vec_b) + +test_sol = test_A\test_b + +test_sol_FE = FEFunction(X, test_sol) +test_sol_u, test_sol_p = test_sol_FE + +norm_sol_u = (sum(∫(test_sol_u⋅test_sol_u)*dΩ)) +norm_sol_p = (sum(∫(test_sol_p*test_sol_p)*dΩ)) + +err_sol_u = uex - test_sol_u +err_sol_p = 0.0 - test_sol_p +L2error_sol_u = (sum(∫(err_sol_u⋅err_sol_u)*dΩ)) +L2error_sol_p = (sum(∫(err_sol_p⋅err_sol_p)*dΩ)) + +##################### TRY TO DEFINE FUNCTIONS TO COLLECT VEC_STAB +proj_rhs_func = setup_L2_proj_in_bb_space(dΩbg_agg_cells, + ref_agg_cell_to_ref_bb_map, # map + agg_cells_to_aggregate, # + aggregate_to_local_cells, # + rhs_func, # Function to be projected + pbb, # Trial basis of bounding box space Zbb + qbb) +# #THIS TEST SEEMS TO IMPLY THAT ALL IS FINE. +# for i=1:16 +# println(get_array(∫(proj_rhs_func)dΩbg_cut_cells)[i]) +# println(get_array(∫(proj_rhs_func)dΩbg_cut_cells)[i]-get_array(∫(test_f_proj_Zbb_agg_cells)dΩbg_cut_cells)[i]) +# end + +wvec, rvec = bulk_ghost_penalty_stabilization_collect_cell_vector_on_D(dΩbg_cut_cells, + γ, + dq, + testP_Ωbg_cut_cell_dof_ids, + identity, + rhs_func, + proj_rhs_func) + +#### -- TEST PROBLEM -- #### MET wvec +test_a((u,p),(v,q))=∫(u⋅v+q*p)dΩ +uex(x) = -VectorValue(2.0,2.0) +test_l((v,q))=∫(uex⋅v+0.0⋅q)dΩ + +test_mat_A=Gridap.FESpaces.collect_cell_matrix(X,Y,test_a(dx,dy)) +test_vec_b=Gridap.FESpaces.collect_cell_vector(Y,test_l(dy)) +push!(test_vec_b[1], wvec...) +push!(test_vec_b[2], rvec...) + +assem=SparseMatrixAssembler(X,Y) + +test_A = assemble_matrix(assem, test_mat_A) +test_b = assemble_vector(assem, test_vec_b) + +test_sol = test_A\test_b + +test_sol_FE = FEFunction(X, test_sol) +test_sol_u, test_sol_p = test_sol_FE + +norm_sol_u = (sum(∫(test_sol_u⋅test_sol_u)*dΩ)) +norm_sol_p = (sum(∫(test_sol_p*test_sol_p)*dΩ)) + +err_sol_u = uex - test_sol_u +err_sol_p = 0.0 - test_sol_p +L2error_sol_u = (sum(∫(err_sol_u⋅err_sol_u)*dΩ)) +L2error_sol_p = (sum(∫(err_sol_p⋅err_sol_p)*dΩ)) + +#### +dp_proj_Qbb, dq_proj_Qbb = setup_L2_proj_in_bb_space( + dΩbg_agg_cells, # measure of aggregated cells in background domain + ref_agg_cell_to_ref_bb_map, # map + agg_cells_to_aggregate, # + aggregate_to_local_cells, # + dp, # Trial basis (to project) + dq, # Test basis + pbb, # Trial basis of bounding box space Qbb + qbb, # Test basis of bounding box space Qbb + identity, # operation to be applied to u and v + P_agg_cells_local_dof_ids) # aggregates local dof ids for space P + +if (_is_multifield_fe_basis_component(dp)) + nfields=_nfields(dp) + fieldid=_fieldid(dp) + P_cut_cells_to_aggregate_dof_ids= + lazy_map(Gridap.Fields.BlockMap(nfields,fieldid),P_cut_cells_to_aggregate_dof_ids) +end +wvec2, rvec2 = bulk_ghost_penalty_stabilization_collect_cell_vector_on_D(dΩbg_cut_cells, + γ, + dq, + dq_proj_Qbb, + testP_Ωbg_cut_cell_dof_ids, + P_cut_cells_to_aggregate_dof_ids, + identity, + rhs_func, + proj_rhs_func) + +#### -- TEST PROBLEM -- #### MET wvec2 +test_a((u,p),(v,q))=∫(u⋅v+q*p)dΩ +uex(x) = -VectorValue(2.0,2.0) +test_l((v,q))=∫(uex⋅v+0.0⋅q)dΩ + +test_mat_A=Gridap.FESpaces.collect_cell_matrix(X,Y,test_a(dx,dy)) +test_vec_b=Gridap.FESpaces.collect_cell_vector(Y,test_l(dy)) +push!(test_vec_b[1], wvec2...) +push!(test_vec_b[2], rvec2...) + +assem=SparseMatrixAssembler(X,Y) + +test_A = assemble_matrix(assem, test_mat_A) +test_b = assemble_vector(assem, test_vec_b) + +test_sol = test_A\test_b + +test_sol_FE = FEFunction(X, test_sol) +test_sol_u, test_sol_p = test_sol_FE + +norm_sol_u = (sum(∫(test_sol_u⋅test_sol_u)*dΩ)) +norm_sol_p = (sum(∫(test_sol_p*test_sol_p)*dΩ)) + +err_sol_u = uex - test_sol_u +err_sol_p = 0.0 - test_sol_p +L2error_sol_u = (sum(∫(err_sol_u⋅err_sol_u)*dΩ)) +L2error_sol_p = (sum(∫(err_sol_p⋅err_sol_p)*dΩ)) \ No newline at end of file