diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e69ae1ab..dfb5e09e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,23 @@ name: CI + on: - - push - - pull_request + push: + branches: [master] + paths-ignore: + - 'CONTRIBUTING.md' + - 'CODE_OF_CONDUCT.md' + - 'LICENSE' + - 'README.md' + - 'NEWS.md' + pull_request: + paths-ignore: + - 'CONTRIBUTING.md' + - 'CODE_OF_CONDUCT.md' + - 'LICENSE' + - 'README.md' + - 'NEWS.md' + workflow_dispatch: # Allow manual triggering of the workflow from the Actions tab + jobs: SequentialTests: name: Serial Tests - Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} diff --git a/NEWS.md b/NEWS.md index c1b4cb4f..d791a38a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,10 +5,13 @@ 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.4.11] - 2026-02-20 ### Added +- Added further support for polytopal methods and meshes. Since PR[#192](https://github.com/gridap/GridapDistributed.jl/pull/192). +- Added new methods to ensure consistent orientation of faces across processors. Since PR[#192](https://github.com/gridap/GridapDistributed.jl/pull/192). +- Added a new function `restrict_gids` to create a subsets of gids. Since PR[#192](https://github.com/gridap/GridapDistributed.jl/pull/192). - New overloads for the `TrialFESpace` constructor where the data to be imposed is passed as a `DistributedCellField`. Since PR[#185](https://github.com/gridap/GridapDistributed.jl/pull/185). - Added missing `FESpace` constructors for distributed triangulations specialized for the RT FEs case. Since PR[#188](https://github.com/gridap/GridapDistributed.jl/pull/188) - Added optional argument `isconsistent` to interpolate functions. Since PR[#190](https://github.com/gridap/GridapDistributed.jl/pull/190). diff --git a/Project.toml b/Project.toml index a9af61c4..2f7129a5 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "GridapDistributed" uuid = "f9701e48-63b3-45aa-9a63-9bc6c271f355" authors = ["S. Badia ", "A. F. Martin ", "F. Verdugo "] -version = "0.4.10" +version = "0.4.11" [deps] BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" @@ -21,7 +21,7 @@ BlockArrays = "1" CircularArrays = "1.4.0" FillArrays = "1" ForwardDiff = "0.10, 1" -Gridap = "0.19.6" +Gridap = "0.19.8" LinearAlgebra = "1" MPI = "0.16, 0.17, 0.18, 0.19, 0.20" PartitionedArrays = "0.3.3" diff --git a/src/Adaptivity.jl b/src/Adaptivity.jl index 0161ccd0..a9e798c4 100644 --- a/src/Adaptivity.jl +++ b/src/Adaptivity.jl @@ -733,3 +733,201 @@ function refine_cell_gids( end return refine_cell_gids(cmodel,fmodels,f_own_to_local) end + +# Coarsening for polytopal meshes +# +# We assume some properties of the coarsening: +# - The patch topology must the a partition of the owned fine cells, i.e +# + All patches are disjoint +# + All owned fine cells are part of a patch +# - All patches are fully owned by a single processor. I.e we do NOT allow +# patches that are split between multiple processors. +# The second property is slightly restrictive, but I think it is very reasonable... allowing +# for split patches would add an insane amount of complexity to the code. Basically, +# this property means that we can always coarsen the owned fine cells locally, then +# communicate to find out ghosts. +# If we ever need to have split patches, one should instead re-distribute the model accordingly +# and then coarsen. +# +# The main idea of the algorithm is as follows: +# 1. First, we coarsen the owned part of the models +# 2. We then create a global numering of the coarse cells +# 3. We can use the above to build the cell-to-node connectivity +# 4. We also need a global numbering of the coarse nodes, which we need to +# communicate the model coordinates +# 5. We can then create our coarse model +function Adaptivity.coarsen(fmodel::DistributedDiscreteModel, ptopo::DistributedPatchTopology; return_glue=false) + + # First, some preliminary checks + fgids = partition(get_cell_gids(fmodel)) + map(local_views(ptopo), fgids) do ptopo, fids + patch_cells = Geometry.get_patch_cells(ptopo) + lcell_to_ocell = local_to_own(fids) + ocell_to_lcell = own_to_local(fids) + @check allunique(patch_cells.data) "Patches must not overlap" + @check all(c -> !iszero(lcell_to_ocell[c]), patch_cells.data) "All local patches must be fully owned" + @check issubset(ocell_to_lcell, patch_cells.data) "All owned cells must be part of a patch" + end + + # 1. Local coarsening on the owned part of the model + own_polys, own_connectivity = map(local_views(fmodel), local_views(ptopo)) do fmodel, ptopo + Adaptivity.generate_patch_polytopes(fmodel,ptopo) + end |> tuple_of_arrays + + # 2. Coarse cell gids + # - Each processor can easily create the global numbering of their owned coarse cells. + # - Through the fine cell ghosts, we have to communicate the ghost coarse cell gids, and + # from that get the local-to-global mapping. + Dc = num_cell_dims(fmodel) + fcell_gids = partition(get_face_gids(fmodel, Dc)) + n_own_ccells = map(Geometry.num_patches, local_views(ptopo)) + n_cgids = sum(n_own_ccells) + first_cgid = scan(+,n_own_ccells,type=:exclusive,init=1) + fcell_to_cgid = map(local_views(ptopo), fcell_gids, first_cgid) do ptopo, fcell_gids, first_cgid + patch_cells = Geometry.get_patch_cells(ptopo) + fcell_to_cgid = zeros(Int, local_length(fcell_gids)) + Arrays.flatten_partition!(fcell_to_cgid,patch_cells) + fcell_to_cgid .+= first_cgid - 1 + return fcell_to_cgid + end + consistent!(PVector(fcell_to_cgid, fcell_gids)) |> wait + + ccell_gids, fcell_to_ccell = map(fcell_gids, fcell_to_cgid, first_cgid, n_own_ccells) do fids, fcell_to_cgid, first_cgid, n_own_ccells + owner = part_id(fids) + own_range = first_cgid:(first_cgid+n_own_ccells-1) + c_own_to_global = collect(Int, own_range) + c_own_to_flid = collect(Int32, indexin(c_own_to_global, fcell_to_cgid)) + + is_ghost(gid) = !iszero(gid) && (gid ∉ own_range) + c_ghost_to_global = filter!(is_ghost, unique(fcell_to_cgid)) + c_ghost_to_flid = collect(Int32, indexin(c_ghost_to_global, fcell_to_cgid)) + c_ghost_to_owner = local_to_owner(fids)[c_ghost_to_flid] + + own_cgids = OwnIndices(n_cgids, owner, c_own_to_global) + ghost_cgids = GhostIndices(n_cgids, c_ghost_to_global, c_ghost_to_owner) + cgids = OwnAndGhostIndices(own_cgids, ghost_cgids) + + cgid_to_clid = global_to_local(cgids) + fcell_to_ccell = zeros(Int32, local_length(fids)) + for (flid, cgid) in enumerate(fcell_to_cgid) + fcell_to_ccell[flid] = cgid_to_clid[cgid] + end + return cgids, fcell_to_ccell + end |> tuple_of_arrays + + # 3 & 4. Coarse node gids: + # - Each coarse node corresponds to a fine node. However: not all local coarse nodes + # are also local fine nodes. Therefore we will have to work with the + # global fine node ids to have a unique numbering. + # - We will communicate the coarse-cell-to-fine-node-gid connectivity, then + # build a coarse node numbering from that. + + # First: We communicate the number of nodes per coarse cell + ccell_to_nnodes = map(ccell_gids, own_connectivity) do ccids, own_connectivity + nnodes = zeros(Int32, local_length(ccids)) + nnodes[own_to_local(ccids)] .= map(length, own_connectivity) + return nnodes + end + consistent!(PVector(ccell_to_nnodes, ccell_gids)) |> wait + + # We communicate the connectivity: + # For each coarse cell, the fine node gids in that cell + fnode_gids = partition(get_face_gids(fmodel, 0)) + connectivity = map( + own_connectivity, ccell_to_nnodes, ccell_gids, fnode_gids + ) do own_connectivity, ccell_to_nnodes, ccids, fnids + fnode_local_to_global = local_to_global(fnids) + ptrs = pushfirst!(ccell_to_nnodes, 0) + Arrays.length_to_ptrs!(ptrs) + data = zeros(Int32, ptrs[end]-1) + for (oid, lid) in enumerate(own_to_local(ccids)) + node_lids = view(own_connectivity, oid) + node_gids = view(fnode_local_to_global,node_lids) + data[ptrs[lid]:(ptrs[lid+1]-1)] .= node_gids + end + return JaggedArray(data, ptrs) + end + consistent!(PVector(connectivity, ccell_gids)) |> wait + connectivity = map(c -> Table(c.data, c.ptrs), connectivity) + + # We create a local numbering of the coarse nodes and renumber the connectivity. + # For later, we also return the local-to-local mapping of the fine nodes to coarse nodes. + n_cnodes, fnode_to_cnode = map(fnode_gids, connectivity) do fnids, conn + n_lid = 0 + fgid_to_clid = Dict{Int,Int32}() + for k in eachindex(conn.data) + fgid = conn.data[k] + clid = get!(fgid_to_clid, fgid, n_lid + 1) + n_lid += (clid == n_lid + 1) # Increment if it's new + conn.data[k] = clid # Renumber the connectivity + end + fnode_to_cnode = zeros(Int32, local_length(fnids)) + for (flid, fgid) in enumerate(local_to_global(fnids)) + fnode_to_cnode[flid] = get!(fgid_to_clid,fgid,0) + end + return n_lid, fnode_to_cnode + end |> tuple_of_arrays + + # Finally, we use pre-existing routines to generate the coarse node gids + cnode_gids = generate_gids(PRange(ccell_gids), connectivity, n_cnodes) |> partition + + # Coarse node coordinates: + cnode_coords = map(local_views(fmodel), cnode_gids, fnode_to_cnode) do fmodel, cngids, fnode_to_cnode + @check issubset(own_to_local(cngids), fnode_to_cnode) + fnode_coords = Geometry.get_vertex_coordinates(get_grid_topology(fmodel)) + cnode_coords = Vector{eltype(fnode_coords)}(undef, local_length(cngids)) + for (flid, clid) in enumerate(fnode_to_cnode) + if clid > 0 + cnode_coords[clid] = fnode_coords[flid] # Fill owned info + end + end + return cnode_coords + end + consistent!(PVector(cnode_coords, cnode_gids)) |> wait # Communicate coarse coords + + # Coarse polytopes: + # - We already have the owned polytopes from the local coarsening + # - We create the ghost polytopes from the connectivity and the coarse node coordinates + # TODO: The creation of 3D polyhedra requires a bit more information! This is the only thing missing for 3D. + @notimplementedif Dc != 2 "Coarsening is only implemented for 2D polytopal meshes" + polys = map(ccell_gids, connectivity, own_polys, cnode_coords) do ccids, conn, own_polys, cnode_coords + polys = Vector{eltype(own_polys)}(undef, local_length(ccids)) + for (lid, oid) in enumerate(local_to_own(ccids)) + if oid > 0 + polys[lid] = own_polys[oid] # Owned polytope + else + polys[lid] = Polygon(cnode_coords[view(conn, lid)]) # Ghost polytope + end + end + return polys + end + + # 5. We can finally create our coarse model + face_gids = Vector{PRange}(undef, Dc+1) + face_gids[end] = PRange(ccell_gids) + face_gids[1] = PRange(cnode_gids) + ctopo = GridapDistributed.DistributedGridTopology( + map(Geometry.PolytopalGridTopology, cnode_coords, connectivity, polys), face_gids + ) + _setup_consistent_faces!(ctopo) + labels = Geometry.FaceLabeling(ctopo) + cmodels = map(local_views(ctopo), local_views(labels)) do ctopo, labels + cgrid = Geometry.PolytopalGrid(ctopo) + Geometry.PolytopalDiscreteModel(cgrid, ctopo, labels) + end + cmodel = GridapDistributed.DistributedDiscreteModel(cmodels, face_gids) + (!return_glue) && (return cmodel) + + # If required, create the adaptivity glues + ftopo = get_grid_topology(fmodel) + glues = map( + ccell_gids, cnode_gids, fcell_to_ccell, fnode_to_cnode, local_views(ftopo), local_views(ctopo) + ) do ccids, cnids, fcell_to_ccell, fnode_to_cnode, ftopo, ctopo + ccell_to_fcell = Arrays.inverse_table(fcell_to_ccell, local_length(ccids)) + cnode_to_fnode = find_inverse_index_map(fnode_to_cnode, local_length(cnids)) + Adaptivity.generate_patch_adaptivity_glue( + ftopo, ctopo, fcell_to_ccell, ccell_to_fcell, fnode_to_cnode, cnode_to_fnode, + ) + end + return cmodel, glues +end diff --git a/src/Algebra.jl b/src/Algebra.jl index b1074be1..772cea1a 100644 --- a/src/Algebra.jl +++ b/src/Algebra.jl @@ -198,6 +198,9 @@ function change_parts(::Union{Nothing,MPIVoidVector}, new_parts, defaults) return defaults end +change_parts(f::Function, x, args...; kwargs...) = change_parts(f(x), args...; kwargs...) +change_parts(f::Function, x::Nothing, args...; kwargs...) = change_parts(nothing, args...; kwargs...) + function generate_subparts(parts::MPIArray,new_comm_size) root_comm = parts.comm root_size = MPI.Comm_size(root_comm) diff --git a/src/FESpaces.jl b/src/FESpaces.jl index 2b4cb313..b3354472 100644 --- a/src/FESpaces.jl +++ b/src/FESpaces.jl @@ -913,6 +913,8 @@ function _compute_new_distributed_fixedval( return c end +# Constant FESpace + """ ConstantFESpace( model::DistributedDiscreteModel; @@ -960,3 +962,24 @@ function FESpaces.ConstantFESpace( vector_type = _find_vector_type(spaces,gids) return DistributedSingleFieldFESpace(spaces,gids,trian,vector_type) end + +# Polytopal FESpaces + +function FESpaces.PolytopalFESpace( + _trian::DistributedTriangulation,args...;kwargs... +) + trian = add_ghost_cells(_trian) + spaces = map(local_views(trian)) do t + FESpaces.PolytopalFESpace(t,args...;kwargs...) + end + gids = generate_gids(trian,spaces) + vector_type = _find_vector_type(spaces,gids) + return DistributedSingleFieldFESpace(spaces,gids,trian,vector_type) +end + +function FESpaces.PolytopalFESpace( + model::DistributedDiscreteModel,args...;kwargs... +) + trian = Triangulation(with_ghost,model) + FESpaces.PolytopalFESpace(trian,args...;kwargs...) +end diff --git a/src/Geometry.jl b/src/Geometry.jl index 64398c9f..5ef8d06f 100644 --- a/src/Geometry.jl +++ b/src/Geometry.jl @@ -99,6 +99,65 @@ function _setup_face_gids!(topo::DistributedGridTopology{Dc},dim) where {Dc} end end +# In some cases, the orientation of locally computed faces is NOT consistent. +# The following functions can be used to check for consistent orientation and fix it. +function _setup_consistent_faces!(topo::DistributedGridTopology) + # Setting up consistent face-to-vertex maps should be enough + # to guarantee consistent face orientation if it is done before + # any other face-to-face map is setup. So we should call this function + # just after creating the new models. + D = num_cell_dims(topo) + for dimfrom in 1:D-1 + _setup_consistent_faces!(topo, dimfrom, 0) + end +end + +function _setup_consistent_faces!(topo::DistributedGridTopology, dimfrom::Integer, dimto::Integer) + @check 0 <= dimto <= dimfrom <= num_cell_dims(topo) + gids_from = partition(get_face_gids(topo, dimfrom)) + gids_to = partition(get_face_gids(topo, dimto)) + lfrom_to_gto = map(local_views(topo), gids_to) do topo, gids_to + lfrom_to_lto = get_faces(topo, dimfrom, dimto) + to_global!(lfrom_to_lto.data, gids_to) + JaggedArray(lfrom_to_lto.data, lfrom_to_lto.ptrs) + end + wait(consistent!(PVector(lfrom_to_gto, gids_from))) + map(lfrom_to_gto, gids_to) do lfrom_to_gto, gids_to + to_local!(lfrom_to_gto.data, gids_to) + end + return nothing +end + +function isconsistent_faces(topo::DistributedGridTopology) + D = num_cell_dims(topo) + for dimfrom in 1:D-1 + for dimto in 0:dimfrom-1 + !isconsistent_faces(topo, dimfrom, dimto) && return false + end + end + return true +end + +function isconsistent_faces(topo::DistributedGridTopology, dimfrom::Integer, dimto::Integer) + @check 0 <= dimto <= dimfrom <= num_cell_dims(topo) + gids_from = partition(get_face_gids(topo, dimfrom)) + gids_to = partition(get_face_gids(topo, dimto)) + + lfrom_to_lto = map(local_views(topo)) do topo + get_faces(topo, dimfrom, dimto) + end + lfrom_to_gto = map(lfrom_to_lto, gids_to) do lfrom_to_lto, gids_to + lto_gto = local_to_global(gids_to) + JaggedArray(lto_gto[lfrom_to_lto.data],lfrom_to_lto.ptrs) + end + wait(consistent!(PVector(lfrom_to_gto, gids_from))) + isconsistent = map(lfrom_to_lto, lfrom_to_gto, gids_to) do lfrom_to_lto, lfrom_to_gto, gids_to + gto_to_lto = global_to_local(gids_to) + lfrom_to_lto.data == gto_to_lto[lfrom_to_gto.data] + end + return reduce(&, isconsistent) +end + function Geometry.get_isboundary_face(topo::DistributedGridTopology, d::Integer) face_gids = get_face_gids(topo, d) is_local_boundary = map(local_views(topo)) do topo @@ -159,10 +218,6 @@ end """ abstract type DistributedDiscreteModel{Dc,Dp} <: GridapType end -function generate_gids(::DistributedDiscreteModel) - @abstractmethod -end - function get_cell_gids(model::DistributedDiscreteModel{Dc}) where Dc @abstractmethod end @@ -220,11 +275,9 @@ struct GenericDistributedDiscreteModel{Dc,Dp,A,B,C} <: DistributedDiscreteModel{ metadata::C function GenericDistributedDiscreteModel( models::AbstractArray{<:DiscreteModel{Dc,Dp}}, - gids::PRange; + face_gids::AbstractArray{<:PRange}; metadata = nothing ) where {Dc,Dp} - face_gids=Vector{PRange}(undef,Dc+1) - face_gids[Dc+1] = gids A = typeof(models) B = typeof(face_gids) C = typeof(metadata) @@ -232,6 +285,14 @@ struct GenericDistributedDiscreteModel{Dc,Dp,A,B,C} <: DistributedDiscreteModel{ end end +function GenericDistributedDiscreteModel( + models::AbstractArray{<:DiscreteModel{Dc,Dp}}, gids::PRange; metadata = nothing +) where {Dc,Dp} + face_gids = Vector{PRange}(undef,Dc+1) + face_gids[Dc+1] = gids + GenericDistributedDiscreteModel(models,face_gids;metadata) +end + # This is to support old API function DistributedDiscreteModel(args...;kwargs...) GenericDistributedDiscreteModel(args...;kwargs...) @@ -251,17 +312,16 @@ end function _setup_face_gids!(dmodel::GenericDistributedDiscreteModel{Dc},dim) where {Dc} Gridap.Helpers.@check 0 <= dim <= Dc if !isassigned(dmodel.face_gids,dim+1) - mgids = dmodel.face_gids[Dc+1] + cell_gids = dmodel.face_gids[Dc+1] nlfaces = map(local_views(dmodel)) do model num_faces(model,dim) end cell_lfaces = map(local_views(dmodel)) do model - topo = get_grid_topology(model) - faces = get_faces(topo, Dc, dim) + topo = get_grid_topology(model) + get_faces(topo, Dc, dim) end - dmodel.face_gids[dim+1] = generate_gids(mgids,cell_lfaces,nlfaces) + dmodel.face_gids[dim+1] = generate_gids(cell_gids,cell_lfaces,nlfaces) end - return end # CartesianDiscreteModel @@ -466,7 +526,7 @@ function Geometry.DiscreteModel( @assert size(cell_graph,1) == ncells @assert size(cell_graph,2) == ncells - lcell_to_cell, lcell_to_part, gid_to_part = map(parts) do part + lcell_to_cell, lcell_to_part = map(parts) do part cell_to_mask = fill(false,ncells) icell_to_jcells_ptrs = cell_graph.colptr icell_to_jcells_data = cell_graph.rowval @@ -482,9 +542,8 @@ function Geometry.DiscreteModel( end end lcell_to_cell = findall(cell_to_mask) - lcell_to_part = zeros(Int32,length(lcell_to_cell)) - lcell_to_part .= cell_to_part[lcell_to_cell] - lcell_to_cell, lcell_to_part, cell_to_part + lcell_to_part = collect(Int32,view(cell_to_part,lcell_to_cell)) + lcell_to_cell, lcell_to_part end |> tuple_of_arrays partition = map(parts,lcell_to_cell,lcell_to_part) do part, lcell_to_cell, lcell_to_part @@ -518,6 +577,17 @@ function Geometry.UnstructuredDiscreteModel(model::GenericDistributedDiscreteMod ) end +# PolytopalDiscreteModel + +function Geometry.PolytopalDiscreteModel(model::GenericDistributedDiscreteModel) + pmodel = GenericDistributedDiscreteModel( + map(Geometry.PolytopalDiscreteModel,local_views(model)), + get_cell_gids(model) + ) + _setup_consistent_faces!(get_grid_topology(pmodel)) + return pmodel +end + # Simplexify function Geometry.simplexify(model::DistributedDiscreteModel;kwargs...) @@ -526,6 +596,14 @@ function Geometry.simplexify(model::DistributedDiscreteModel;kwargs...) return UnstructuredDiscreteModel(Adaptivity.get_model(ref_model)) end +# Restrict + +function Geometry.restrict(model::DistributedDiscreteModel, cell_to_parent_cell::AbstractArray) + models = map(Geometry.restrict, local_views(model), cell_to_parent_cell) + gids = restrict_gids(get_cell_gids(model), cell_to_parent_cell) + return GenericDistributedDiscreteModel(models, gids) +end + # Triangulation # We do not inherit from Triangulation on purpose. @@ -887,54 +965,53 @@ function generate_cell_gids(dmodel::DistributedDiscreteModel{Dm}, if (covers_all_faces) tgids = mgids else - # count number owned cells - notcells, tcell_to_mcell = map( - local_views(dmodel),local_views(dtrian),PArrays.partition(mgids)) do model,trian,partition - lid_to_owner = local_to_owner(partition) - part = part_id(partition) + tcell_to_mcell = map(local_views(dtrian)) do trian glue = get_glue(trian,Val(Dt)) @assert isa(glue,FaceToFaceGlue) - tcell_to_mcell = glue.tface_to_mface - notcells = count(tcell_to_mcell) do mcell - lid_to_owner[mcell] == part - end - notcells, tcell_to_mcell - end |> tuple_of_arrays - - # Find the global range of owned dofs - first_gtcell = scan(+,notcells,type=:exclusive,init=one(eltype(notcells))) - - # Assign global cell ids to owned cells - mcell_to_gtcell = map( - first_gtcell,tcell_to_mcell,partition(mgids)) do first_gtcell,tcell_to_mcell,partition - mcell_to_gtcell = zeros(Int,local_length(partition)) - loc_to_owner = local_to_owner(partition) - part = part_id(partition) - gtcell = first_gtcell - for mcell in tcell_to_mcell - if loc_to_owner[mcell] == part - mcell_to_gtcell[mcell] = gtcell - gtcell += 1 - end - end - mcell_to_gtcell + return glue.tface_to_mface end + tgids = restrict_gids(mgids,tcell_to_mcell) + end + return tgids +end + +function restrict_gids(gids::PRange, new_to_old_lid::AbstractArray) + + n_own = map(partition(gids), new_to_old_lid) do ids, n2o_lid + rank = part_id(ids) + return count(isequal(rank), view(local_to_owner(ids), n2o_lid)) + end - cache = fetch_vector_ghost_values_cache(mcell_to_gtcell,partition(mgids)) - fetch_vector_ghost_values!(mcell_to_gtcell,cache) |> wait - - # Prepare new partition - ngtcells = reduction(+,notcells,destination=:all,init=zero(eltype(notcells))) - indices = map( - ngtcells,mcell_to_gtcell,tcell_to_mcell,partition(mgids) - ) do ngtcells,mcell_to_gtcell,tcell_to_mcell,partition - tcell_to_gtcell = mcell_to_gtcell[tcell_to_mcell] - lid_to_owner = local_to_owner(partition) - tcell_to_part = lid_to_owner[tcell_to_mcell] - LocalIndices(ngtcells,part_id(partition),tcell_to_gtcell,tcell_to_part) + # Assign global ids to owned lids + first_gid = scan(+,n_own,type=:exclusive,init=one(eltype(n_own))) + + old_lid_to_new_gid = map(first_gid,new_to_old_lid,partition(gids)) do first_gid, n2o_lid, ids + old_lid_to_new_gid = zeros(Int,local_length(ids)) + old_lid_to_owner = local_to_owner(ids) + rank = part_id(ids) + gid = first_gid + for old in n2o_lid + if old_lid_to_owner[old] == rank + old_lid_to_new_gid[old] = gid + gid += 1 + end end - _find_neighbours!(indices, partition(mgids)) - tgids = PRange(indices) + return old_lid_to_new_gid end - return tgids -end \ No newline at end of file + + consistent!(PVector(old_lid_to_new_gid,partition(gids))) |> wait + + # Prepare new partition + n_gids = reduction(+,n_own,destination=:all,init=zero(eltype(n_own))) + + new_indices = map( + n_gids, old_lid_to_new_gid, new_to_old_lid, partition(gids) + ) do n_gids, old_lid_to_new_gid, new_to_old_lid, ids + lid_to_gid = old_lid_to_new_gid[new_to_old_lid] + lid_to_owner = local_to_owner(ids)[new_to_old_lid] + return LocalIndices(n_gids,part_id(ids),lid_to_gid,lid_to_owner) + end + _find_neighbours!(new_indices, partition(gids)) + + return PRange(new_indices) +end diff --git a/src/GridapDistributed.jl b/src/GridapDistributed.jl index c48d9f16..edfce595 100644 --- a/src/GridapDistributed.jl +++ b/src/GridapDistributed.jl @@ -64,14 +64,14 @@ include("MultiField.jl") include("ODEs.jl") -include("Redistribution.jl") - -include("Adaptivity.jl") - include("Autodiff.jl") include("MacroDiscreteModels.jl") include("PatchAssemblers.jl") +include("Redistribution.jl") + +include("Adaptivity.jl") + end # module diff --git a/src/MultiField.jl b/src/MultiField.jl index 510b6675..4b2b1626 100644 --- a/src/MultiField.jl +++ b/src/MultiField.jl @@ -92,6 +92,7 @@ MultiField.MultiFieldStyle(a::DistributedMultiFieldFESpace) = MultiField.MultiFi local_views(a::DistributedMultiFieldFESpace) = a.part_fe_space MultiField.num_fields(m::DistributedMultiFieldFESpace) = length(m.field_fe_space) +MultiField.num_fields(m::DistributedFESpace) = 1 # Default for single-field Base.iterate(m::DistributedMultiFieldFESpace) = iterate(m.field_fe_space) Base.iterate(m::DistributedMultiFieldFESpace,state) = iterate(m.field_fe_space,state) Base.getindex(m::DistributedMultiFieldFESpace,field_id::Integer) = m.field_fe_space[field_id] @@ -105,6 +106,24 @@ function FESpaces.get_free_dof_ids(fs::DistributedMultiFieldFESpace) fs.gids end +function BlockArrays.blocks(f::DistributedMultiFieldFESpace{<:BlockMultiFieldStyle}) + block_gids = blocks(get_free_dof_ids(f)) + block_ranges = MultiField.get_block_ranges(MultiField.get_block_parameters(MultiFieldStyle(f))...) + block_spaces = map(block_ranges,block_gids) do range, gids + if (length(range) == 1) + space = f[range[1]] + else + global_sf_spaces = f.field_fe_space[range] + local_sf_spaces = to_parray_of_arrays(map(local_views,global_sf_spaces)) + local_mf_spaces = map(MultiFieldFESpace,local_sf_spaces) + vector_type = _find_vector_type(local_mf_spaces,gids) + space = DistributedMultiFieldFESpace(global_sf_spaces,local_mf_spaces,gids,vector_type) + end + space + end + return block_spaces +end + function MultiField.restrict_to_field( f::DistributedMultiFieldFESpace,free_values::AbstractVector,field::Integer ) diff --git a/src/PatchAssemblers.jl b/src/PatchAssemblers.jl index c9f015e1..29207683 100644 --- a/src/PatchAssemblers.jl +++ b/src/PatchAssemblers.jl @@ -40,6 +40,15 @@ function Geometry.PatchTopology( return Geometry.PatchTopology(topo,patch_cells,metadata) end +function Geometry.PatchTopology(model::DistributedDiscreteModel;kwargs...) + D = num_cell_dims(model) + Geometry.PatchTopology(ReferenceFE{D},model;kwargs...) +end + +function Geometry.get_patch_cells(ptopo::DistributedPatchTopology) + map(Geometry.get_patch_cells,local_views(ptopo)) +end + # PatchTriangulation function Geometry.PatchTriangulation(model::DistributedDiscreteModel,ptopo::DistributedPatchTopology;kwargs...) @@ -99,8 +108,8 @@ end # LocalOperators -struct DistributedLocalOperator - ops :: AbstractArray{<:LocalOperator} +struct DistributedLocalOperator{A} + ops :: A model :: DistributedDiscreteModel end @@ -122,20 +131,26 @@ function Arrays.evaluate!( ) n_fields = num_fields(v) mf_fields = map(evaluate,local_views(k),local_views(v)) - sf_fields = map(1:n_fields) do field - sf_fields = map(f -> f[field], mf_fields) - trians = map(get_triangulation,sf_fields) + if eltype(mf_fields) <: MultiField.MultiFieldCellField + sf_fields = map(1:n_fields) do field + sf_fields = map(f -> f[field], mf_fields) + trians = map(get_triangulation,sf_fields) + trian = DistributedTriangulation(trians,k.model) + DistributedCellField(sf_fields,trian) + end + return DistributedMultiFieldCellField(sf_fields, mf_fields) + else + trians = map(get_triangulation,mf_fields) trian = DistributedTriangulation(trians,k.model) - DistributedCellField(sf_fields,trian) + return DistributedCellField(mf_fields,trian) end - return DistributedMultiFieldCellField(sf_fields, mf_fields) end # Patch assembly struct DistributedPatchAssembler{A,B} <: Assembler assems :: A - axes :: NTuple{2,PRange{B}} + axes :: B end local_views(assem::DistributedPatchAssembler) = assem.assems @@ -226,6 +241,35 @@ end # merge_assembly_data +function merge_assembly_data(data::AbstractArray...) + map(FESpaces.merge_assembly_data,data...) +end + function FESpaces.merge_assembly_matvec_data(data::AbstractArray...) map(FESpaces.merge_assembly_matvec_data,data...) end + + +function FESpaces.collect_and_merge_cell_matrix(assem::DistributedPatchAssembler,contributions...) + data = () + for c in contributions + data = (data..., FESpaces.collect_patch_cell_matrix(assem,c...)) + end + FESpaces.merge_assembly_data(data...) +end + +function FESpaces.collect_and_merge_cell_vector(assem::DistributedPatchAssembler,contributions...) + data = () + for c in contributions + data = (data..., FESpaces.collect_patch_cell_vector(assem,c...)) + end + FESpaces.merge_assembly_data(data...) +end + +function FESpaces.collect_and_merge_cell_matrix_and_vector(assem::DistributedPatchAssembler,contributions...) + data = () + for c in contributions + data = (data..., FESpaces.collect_patch_cell_matrix_and_vector(assem,c...)) + end + FESpaces.merge_assembly_matvec_data(data...) +end diff --git a/src/Redistribution.jl b/src/Redistribution.jl index 601de3b4..f3d8a8f2 100644 --- a/src/Redistribution.jl +++ b/src/Redistribution.jl @@ -458,7 +458,13 @@ function redistribute_array_by_cells( ) if !isnothing(old_cell_to_old_lid) && !isnothing(old_lid_to_data) old_cell_to_old_data = map(old_cell_to_old_lid, old_lid_to_data) do old_cell_to_old_lid, old_lid_to_data - jagged_array(view(old_lid_to_data,old_cell_to_old_lid.data), old_cell_to_old_lid.ptrs) + data = zeros(eltype(old_lid_to_data),length(old_cell_to_old_lid.data)) + for (k,lid) in enumerate(old_cell_to_old_lid.data) + if lid > 0 # Avoid Dirichlet dofs + data[k] = old_lid_to_data[lid] + end + end + jagged_array(data, old_cell_to_old_lid.ptrs) end else old_cell_to_old_data = nothing @@ -469,8 +475,10 @@ function redistribute_array_by_cells( if !isnothing(new_cell_to_new_lid) && !isnothing(new_cell_to_old_data) new_lid_to_old_data = map(new_cell_to_new_lid, new_cell_to_old_data) do new_cell_to_new_lid, new_cell_to_old_data new_lid_to_old_data = zeros(T, maximum(new_cell_to_new_lid.data;init=0)) - for (new_lid, old_data) in zip(new_cell_to_new_lid, new_cell_to_old_data) - new_lid_to_old_data[new_lid] .= old_data + for (new_lid, old_data) in zip(new_cell_to_new_lid.data, new_cell_to_old_data.data) + if new_lid > 0 # Avoid Dirichlet dofs + new_lid_to_old_data[new_lid] = old_data + end end return new_lid_to_old_data end @@ -539,6 +547,34 @@ function redistribute_indices( return old_ids_bis, red_old_ids end +# Purpose of the following function: +# If you have +# - two partitions with the same local layout, but different global IDs (in this case indices and reindexed_indices), +# - and two partitions with the same global IDs, but different local layouts (in this case indices and new_indices), +# then this function returns the new partition with the same local layout as `new_indices` +# and the same global IDs as `reindexed_indices`. +# When is this useful? If we do not have a way of redistributing some partition through the mesh cells, +# for instance the matrix rows/columns, we can redistribute the dof ids and then reindex the partition +# to have the matrix row/column layout. +# This allows us to redistribute algebraic arrays without the need to copy them in FE layout first. +function reindex_partition(new_indices, indices, reindexed_indices) + l2g = PVector(map(collect∘local_to_global,reindexed_indices),indices) + new_l2g = pzeros(Int, new_indices) + t1 = consistent!(copy!(new_l2g, l2g)) + + l2o = PVector(map(collect∘local_to_owner,reindexed_indices),indices) + new_l2o = pzeros(Int32, new_indices) + t2 = consistent!(copy!(new_l2o, l2o)) + + wait(t1) + wait(t2) + + reindexed_new_indices = map(reindexed_indices, partition(new_l2g), partition(new_l2o)) do reindexed_indices, new_l2g, new_l2o + LocalIndices(global_length(reindexed_indices), part_id(reindexed_indices), new_l2g, new_l2o) + end + return reindexed_new_indices +end + function redistribution_neighbors(indices, indices_red) nbors_rcv, nbors_snd = assembly_neighbors(indices_red) return nbors_snd, nbors_rcv @@ -631,6 +667,16 @@ function redistribute(v::PVector,new_indices) end function redistribute!(w::PVector,v::PVector,cache) + values, indices = partition(v), partition(axes(v,1)) + values_red, indices_red = partition(w), partition(axes(w,1)) + t = redistribute!(values_red, values, indices_red, indices, cache) + @async begin + wait(t) + return w + end +end + +function redistribute!(values_red, values, indices_red, indices, cache) function setup_snd(values, cache) cache.buffer_snd.data .= view(values,cache.local_indices_snd.data) return cache.buffer_rcv, cache.buffer_snd, cache.neighbors_rcv, cache.neighbors_snd @@ -651,8 +697,6 @@ function redistribute!(w::PVector,v::PVector,cache) view(values_red, cache.local_indices_rcv.data) .= cache.buffer_rcv.data end - values, indices = partition(v), partition(axes(v,1)) - values_red, indices_red = partition(w), partition(axes(w,1)) buffer_rcv, buffer_snd, nbors_rcv, nbors_snd = map(setup_snd, values, cache) |> tuple_of_arrays graph = ExchangeGraph(nbors_snd, nbors_rcv) t = PartitionedArrays.exchange!(buffer_rcv, buffer_snd, graph) @@ -660,6 +704,6 @@ function redistribute!(w::PVector,v::PVector,cache) map(copy_owned, values_red, values, indices_red, indices) wait(t) map(copy_rcv, values_red, cache) - return w + return values_red end end diff --git a/test/AdaptivityCartesianTests.jl b/test/AdaptivityCartesianTests.jl index 7b7b789c..b35ae70d 100644 --- a/test/AdaptivityCartesianTests.jl +++ b/test/AdaptivityCartesianTests.jl @@ -44,7 +44,7 @@ function test_redistribution_new(redist_model, model, redist_space, space, glue) old_data_ids = partition(get_free_dof_ids(space)) old_cell_to_old_lid = map(get_cell_dof_ids,local_views(space)) else - old_data_ids, old_cell_to_old_lid = nothing, nothing, nothing + old_data_ids, old_cell_to_old_lid = nothing, nothing end old_ids, red_old_ids = GridapDistributed.redistribute_indices( diff --git a/test/GeometryTests.jl b/test/GeometryTests.jl index 2d3b2893..6ddfa226 100644 --- a/test/GeometryTests.jl +++ b/test/GeometryTests.jl @@ -6,8 +6,75 @@ using PartitionedArrays using LinearAlgebra using Test -function main(distribute,parts) +using Gridap.Geometry + +function test_local_part_face_labelings_consistency(lmodel::CartesianDiscreteModel{D},gids,gmodel) where {D} + local_topology = lmodel.grid_topology + global_topology = gmodel.grid_topology + local_labelings = lmodel.face_labeling + global_labelings = gmodel.face_labeling + l_d_to_dface_to_entity = local_labelings.d_to_dface_to_entity + g_d_to_dface_to_entity = global_labelings.d_to_dface_to_entity + loc_to_glo = local_to_global(gids) + #traverse local cells + for cell_lid=1:num_cells(lmodel) + cell_gid=loc_to_glo[cell_lid] + for d = 0:D-1 + local_cell_to_faces = local_topology.n_m_to_nface_to_mfaces[D+1,d+1] + global_cell_to_faces = global_topology.n_m_to_nface_to_mfaces[D+1,d+1] + la = local_cell_to_faces.ptrs[cell_lid] + lb = local_cell_to_faces.ptrs[cell_lid+1] + ga = global_cell_to_faces.ptrs[cell_gid] + gb = global_cell_to_faces.ptrs[cell_gid+1] + @assert (lb-la)==(gb-ga) + for i = 0:lb-la-1 + face_lid = local_cell_to_faces.data[la+i] + face_gid = global_cell_to_faces.data[ga+i] + local_entity = l_d_to_dface_to_entity[d+1][face_lid] + global_entity = g_d_to_dface_to_entity[d+1][face_gid] + if (local_entity != global_entity) + return false + end + end + end + end + return true +end + +function test_model(model) + D = num_cell_dims(model) + + grid = get_grid(model) + labels = get_grid(model) + + topo = get_grid_topology(model) + GridapDistributed.isconsistent_faces(topo) + + for d in 0:D + fgids = get_face_gids(model,d) + trian = Triangulation(no_ghost,ReferenceFE{d},model) + @test num_cells(trian) == length(fgids) + trian = Triangulation(with_ghost,ReferenceFE{d},model) + @test num_cells(trian) == length(fgids) + end + + Ω = Triangulation(with_ghost,model) + @test num_cells(Ω) == num_cells(model) + Ω = Triangulation(no_ghost,model) + @test num_cells(Ω) == num_cells(model) + + Γ = Boundary(with_ghost,model,tags="boundary") + nbfacets = num_cells(Γ) + + Γ = Boundary(no_ghost,model,tags="boundary") + @test num_cells(Γ) == nbfacets + + return true +end + +function main_cartesian(distribute,parts) + ranks = distribute(LinearIndices((prod(parts),))) output = mkpath(joinpath(@__DIR__,"output")) if length(parts) == 2 @@ -18,14 +85,12 @@ function main(distribute,parts) cells = (4,4,4) end - ranks = distribute(LinearIndices((prod(parts),))) - - model = CartesianDiscreteModel(ranks,parts,domain,cells) writevtk(model,joinpath(output,"model")) - @test num_cells(model)==prod(cells) - @test num_vertices(model)==prod(cells .+ 1) + @test test_model(model) + @test num_cells(model) == prod(cells) + @test num_vertices(model) == prod(cells .+ 1) @test num_cell_dims(model) == length(cells) @test num_point_dims(model) == length(cells) @@ -54,25 +119,6 @@ function main(distribute,parts) @test test_local_part_face_labelings_consistency(lmodel,gids,gmodel) end - grid = get_grid(model) - labels = get_grid(model) - - Ω = Triangulation(with_ghost,model) - writevtk(Ω,joinpath(output,"Ω")) - @test num_cells(Ω) == num_cells(model) - - Ω = Triangulation(no_ghost,model) - writevtk(Ω,joinpath(output,"Ω")) - @test num_cells(Ω) == num_cells(model) - - Γ = Boundary(with_ghost,model,tags="boundary") - writevtk(Γ,joinpath(output,"Γ")) - nbfacets = num_cells(Γ) - - Γ = Boundary(no_ghost,model,tags="boundary") - writevtk(Γ,joinpath(output,"Γ")) - @test num_cells(Γ) == nbfacets - function is_in(coords) R = 1.6 n = length(coords) @@ -111,40 +157,48 @@ function main(distribute,parts) # Multiple ghost layers model = CartesianDiscreteModel(ranks,parts,domain,cells;ghost=map(i->2,parts)) + @test test_model(model) + + # Unstructured conversion + model = Geometry.UnstructuredDiscreteModel( + CartesianDiscreteModel(ranks,parts,domain,cells) + ) + @test test_model(model) + + # Simplexify + model = simplexify( + CartesianDiscreteModel(ranks,parts,domain,cells) + ) + @test test_model(model) end -function test_local_part_face_labelings_consistency(lmodel::CartesianDiscreteModel{D},gids,gmodel) where {D} - local_topology = lmodel.grid_topology - global_topology = gmodel.grid_topology - local_labelings = lmodel.face_labeling - global_labelings = gmodel.face_labeling - l_d_to_dface_to_entity = local_labelings.d_to_dface_to_entity - g_d_to_dface_to_entity = global_labelings.d_to_dface_to_entity - loc_to_glo = local_to_global(gids) - #traverse local cells - for cell_lid=1:num_cells(lmodel) - cell_gid=loc_to_glo[cell_lid] - for d=0:D-1 - local_cell_to_faces = local_topology.n_m_to_nface_to_mfaces[D+1,d+1] - global_cell_to_faces = global_topology.n_m_to_nface_to_mfaces[D+1,d+1] - la = local_cell_to_faces.ptrs[cell_lid] - lb = local_cell_to_faces.ptrs[cell_lid+1] - ga = global_cell_to_faces.ptrs[cell_gid] - gb = global_cell_to_faces.ptrs[cell_gid+1] - @assert (lb-la)==(gb-ga) - for i=0:lb-la-1 - face_lid = local_cell_to_faces.data[la+i] - face_gid = global_cell_to_faces.data[ga+i] - local_entity = l_d_to_dface_to_entity[d+1][face_lid] - global_entity = g_d_to_dface_to_entity[d+1][face_gid] - if (local_entity != global_entity) - return false - end - end - end - end - return true +function main_polytopal(distribute,parts) + ranks = distribute(LinearIndices((prod(parts),))) + + if length(parts) == 2 + domain = (0,4,0,4) + cells = (4,4) + elseif length(parts) == 3 + domain = (0,4,0,4,0,4) + cells = (4,4,4) + end + + model = Geometry.PolytopalDiscreteModel( + CartesianDiscreteModel(ranks,parts,domain,cells) + ) + @test test_model(model) + + model = Geometry.PolytopalDiscreteModel( + simplexify(CartesianDiscreteModel(ranks,parts,domain,cells)) + ) + @test test_model(model) + +end + +function main(distribute,parts) + main_cartesian(distribute,parts) + main_polytopal(distribute,parts) end end # module diff --git a/test/PolytopalCoarseningTests.jl b/test/PolytopalCoarseningTests.jl new file mode 100644 index 00000000..c1abdc3e --- /dev/null +++ b/test/PolytopalCoarseningTests.jl @@ -0,0 +1,58 @@ +module PolytopalCoarseningTests + +using Test +using Gridap +using GridapDistributed, PartitionedArrays + +using Gridap.Adaptivity, Gridap.Geometry, Gridap.Arrays +using Gridap.ReferenceFEs + +function distributed_voronoi(ranks,np,nc,domain) + serial_model = Geometry.voronoi(simplexify(CartesianDiscreteModel(domain,nc))) + cell_to_rank = zeros(Int,num_cells(serial_model)) + for (r,cells) in enumerate(uniform_partition(1:prod(np),np,nc .+ 1)) + cell_to_rank[cells] .= r + end + return DiscreteModel(ranks,serial_model,cell_to_rank) +end + +function main(distribute, np) + ranks = distribute(LinearIndices((prod(np),))) + + fmodel = distributed_voronoi(ranks,np,(8,8),(0,1,0,1)) + #writevtk(fmodel,"tmp/fmodel") + + fgids = partition(get_cell_gids(fmodel)) + patch_cells = map(fgids) do fids + Table([collect(own_to_local(fids))]) + end + ptopo = Geometry.PatchTopology(get_grid_topology(fmodel), patch_cells) + + cmodel, glues = Adaptivity.coarsen(fmodel,ptopo; return_glue=true) + @test GridapDistributed.isconsistent_faces(get_grid_topology(cmodel)) + #writevtk(cmodel, "tmp/cmodel") +end + +# ranks = collect(1:2) +# +# fmodel_serial = Geometry.PolytopalDiscreteModel(CartesianDiscreteModel((0,1,0,1),(4,4))) +# cmodel_serial = Adaptivity.coarsen( +# fmodel_serial, Geometry.PatchTopology( +# get_grid_topology(fmodel_serial), Table([[1,2,5,6],[3,4,7,8],[9,10,13,14],[11,12,15,16]]) +# ) +# ) +# fmodel_good = DiscreteModel(ranks,fmodel_serial,[1,1,2,2,1,1,2,2,1,1,2,2,1,1,2,2]) +# cmodel_good = DiscreteModel(ranks,cmodel_serial,[1,2,1,2]) +# +# fmodel = Geometry.PolytopalDiscreteModel(CartesianDiscreteModel(ranks,(2,1),(0,1,0,1),(4,4))) +# cmodel = Adaptivity.coarsen( +# fmodel, Geometry.PatchTopology( +# get_grid_topology(fmodel),[Table([[1,2,4,5],[7,8,10,11]]),Table([[2,3,5,6],[8,9,11,12]])] +# ) +# ) +# +# GridapDistributed.isconsistent_faces(get_grid_topology(fmodel)) +# GridapDistributed.isconsistent_faces(get_grid_topology(cmodel)) + + +end # module \ No newline at end of file diff --git a/test/TestApp/src/TestApp.jl b/test/TestApp/src/TestApp.jl index 95739c9c..56a30701 100644 --- a/test/TestApp/src/TestApp.jl +++ b/test/TestApp/src/TestApp.jl @@ -16,6 +16,7 @@ module TestApp include("../../AdaptivityCartesianTests.jl") include("../../AdaptivityUnstructuredTests.jl") include("../../AdaptivityMultiFieldTests.jl") + include("../../PolytopalCoarseningTests.jl") include("../../BlockSparseMatrixAssemblersTests.jl") include("../../VisualizationTests.jl") include("../../AutodiffTests.jl") diff --git a/test/mpi/runtests_np4_body.jl b/test/mpi/runtests_np4_body.jl index 6acb9616..eca6f4c9 100644 --- a/test/mpi/runtests_np4_body.jl +++ b/test/mpi/runtests_np4_body.jl @@ -48,6 +48,7 @@ function all_tests(distribute,parts) TestApp.AdaptivityCartesianTests.main(distribute) TestApp.AdaptivityMultiFieldTests.main(distribute) TestApp.AdaptivityUnstructuredTests.main(distribute) + TestApp.PolytopalCoarseningTests.main(distribute,parts) PArrays.toc!(t,"Adaptivity") end diff --git a/test/sequential/AdaptivityTests.jl b/test/sequential/AdaptivityTests.jl index 0d51b107..dadf53cd 100644 --- a/test/sequential/AdaptivityTests.jl +++ b/test/sequential/AdaptivityTests.jl @@ -4,9 +4,11 @@ using PartitionedArrays include("../AdaptivityCartesianTests.jl") include("../AdaptivityUnstructuredTests.jl") +include("../PolytopalCoarseningTests.jl") with_debug() do distribute AdaptivityCartesianTests.main(distribute) AdaptivityUnstructuredTests.main(distribute) + PolytopalCoarseningTests.main(distribute,(2,2)) end end # module \ No newline at end of file