Skip to main content

ndfunctionspace/function_space/
parallel.rs

1//! Parallel function space
2use crate::{
3    function_space::FunctionSpaceImpl,
4    traits::{FunctionSpace, ParallelFunctionSpace},
5};
6use mpi::{
7    collective::SystemOperation,
8    traits::{Communicator, CommunicatorCollectives, Equivalence},
9};
10use ndelement::{
11    traits::{ElementFamily, MappedFiniteElement},
12    types::ReferenceCellType,
13};
14use ndmesh::{
15    traits::{Entity, Mesh, ParallelMesh},
16    types::Ownership,
17};
18use rlst::{RlstScalar, distributed_tools::array_tools::all_to_allv};
19use std::fmt::Debug;
20use std::hash::Hash;
21
22/// Function space on a process
23pub struct ProcessFunctionSpace<S: FunctionSpace> {
24    space: S,
25    global_size: usize,
26    global_dof_indices: Vec<usize>,
27    ownership: Vec<Ownership>,
28}
29
30impl<S: FunctionSpace> ProcessFunctionSpace<S> {
31    fn new(
32        space: S,
33        global_size: usize,
34        global_dof_indices: Vec<usize>,
35        ownership: Vec<Ownership>,
36    ) -> Self {
37        Self {
38            space,
39            global_size,
40            global_dof_indices,
41            ownership,
42        }
43    }
44}
45
46impl<S: FunctionSpace> FunctionSpace for ProcessFunctionSpace<S> {
47    type T = S::T;
48    type TMesh = S::TMesh;
49    type EntityDescriptor = S::EntityDescriptor;
50    type Mesh = S::Mesh;
51    type FiniteElement = S::FiniteElement;
52
53    fn mesh(&self) -> &S::Mesh {
54        self.space.mesh()
55    }
56
57    fn elements(&self) -> &[S::FiniteElement] {
58        self.space.elements()
59    }
60
61    fn entities_by_element(&self, element_index: usize) -> Option<&[usize]> {
62        self.space.entities_by_element(element_index)
63    }
64
65    fn entity_dofs(
66        &self,
67        entity_type: S::EntityDescriptor,
68        entity_number: usize,
69    ) -> Option<&[usize]> {
70        self.space.entity_dofs(entity_type, entity_number)
71    }
72
73    fn entity_closure_dofs(
74        &self,
75        entity_type: S::EntityDescriptor,
76        entity_number: usize,
77    ) -> Option<&[usize]> {
78        self.space.entity_closure_dofs(entity_type, entity_number)
79    }
80
81    fn process_size(&self) -> usize {
82        self.space.process_size()
83    }
84
85    fn process_owned_size(&self) -> usize {
86        self.ownership
87            .iter()
88            .filter(|x| matches!(x, Ownership::Owned))
89            .count()
90    }
91
92    fn global_size(&self) -> usize {
93        self.global_size
94    }
95
96    fn global_dof_index(&self, process_dof_index: usize) -> usize {
97        self.global_dof_indices[process_dof_index]
98    }
99
100    fn ownership(&self, process_dof_index: usize) -> Ownership {
101        self.ownership[process_dof_index]
102    }
103}
104
105/// Parallel function space.
106pub struct ParallelFunctionSpaceImpl<
107    'a,
108    T: RlstScalar,
109    TMesh: RlstScalar,
110    E: Debug + PartialEq + Eq + Clone + Copy + Hash,
111    G: Mesh<EntityDescriptor = E, T = TMesh>,
112    PG: ParallelMesh<LocalMesh = G>,
113    F: MappedFiniteElement<CellType = E, T = T>,
114    S: FunctionSpace<Mesh = G, EntityDescriptor = E, FiniteElement = F>,
115> {
116    mesh: &'a PG,
117    process_space: ProcessFunctionSpace<S>,
118    global_size: usize,
119}
120
121/// A temporary DOF index value used when constructing a parallel DOF map
122#[derive(Debug, Clone, PartialEq)]
123enum DofIndex {
124    /// No value. This should only be used as a placeholder value
125    None,
126    /// An owned DOF
127    Owned,
128    /// A DOF owned by another process, storing the process id, index of ghost in the list of ghosts, and DOF index on that entity
129    Ghost(usize, usize, usize),
130}
131
132#[derive(Equivalence, Debug, Clone, PartialEq)]
133struct GhostEntity {
134    entity_type: ReferenceCellType,
135    entity_index: usize,
136}
137
138impl GhostEntity {
139    fn new(entity_type: ReferenceCellType, entity_index: usize) -> Self {
140        Self {
141            entity_type,
142            entity_index,
143        }
144    }
145}
146
147impl<
148    'a,
149    T: RlstScalar,
150    TMesh: RlstScalar,
151    G: Mesh<EntityDescriptor = ReferenceCellType, T = TMesh>,
152    PG: ParallelMesh<LocalMesh = G>,
153    F: MappedFiniteElement<CellType = ReferenceCellType, T = T>,
154>
155    ParallelFunctionSpaceImpl<
156        'a,
157        T,
158        TMesh,
159        ReferenceCellType,
160        G,
161        PG,
162        F,
163        FunctionSpaceImpl<'a, T, TMesh, ReferenceCellType, G, F>,
164    >
165{
166    /// Create a new parallel function space
167    pub fn new<EF: ElementFamily<FiniteElement = F, CellType = ReferenceCellType>>(
168        mesh: &'a PG,
169        family: &EF,
170    ) -> Self {
171        let comm = mesh.comm();
172        let local_mesh = mesh.local_mesh();
173        let serial_space = FunctionSpaceImpl::new(local_mesh, family);
174
175        let mut dof_indices = vec![DofIndex::None; serial_space.process_size()];
176
177        // Entities to ask other processes about
178        let mut ask_for = vec![vec![]; comm.size() as usize];
179
180        // Identify which DOFs are associated with owned entities and which are associated with ghosts
181        for dim in 0..=local_mesh.topology_dim() {
182            for entity_type in local_mesh.entity_types(dim) {
183                for entity in local_mesh.entity_iter(*entity_type) {
184                    let dofs = serial_space
185                        .entity_dofs(*entity_type, entity.local_index())
186                        .unwrap();
187                    if !dofs.is_empty() {
188                        match entity.ownership() {
189                            Ownership::Owned => {
190                                for j in dofs {
191                                    dof_indices[*j] = DofIndex::Owned
192                                }
193                            }
194                            Ownership::Ghost(process, entity_index) => {
195                                for (i, j) in dofs.iter().enumerate() {
196                                    dof_indices[*j] =
197                                        DofIndex::Ghost(process, ask_for[process].len(), i);
198                                }
199                                ask_for[process].push((*entity_type, entity_index));
200                            }
201                            _ => {
202                                panic!("Invalid ownership");
203                            }
204                        }
205                    }
206                }
207            }
208        }
209
210        // Number the DOFs owned by this process, starting at 0
211        let mut ndofs = 0;
212        let mut process_dof_indices = vec![None; serial_space.process_size()];
213        for (i, j) in dof_indices.iter().enumerate() {
214            if let DofIndex::Owned = j {
215                process_dof_indices[i] = Some(ndofs);
216                ndofs += 1;
217            }
218        }
219
220        // Communicate number of DOFs to all processes
221        let mut global_size = 0;
222        let mut offset = 0;
223        comm.exclusive_scan_into(&ndofs, &mut offset, SystemOperation::sum());
224        comm.all_reduce_into(&ndofs, &mut global_size, SystemOperation::sum());
225
226        // Ask other processes about their ghosts
227        let counts = ask_for.iter().map(|i| i.len()).collect::<Vec<_>>();
228        let ask_for_flat = ask_for
229            .iter()
230            .flatten()
231            .map(|(t, i)| GhostEntity::new(*t, *i))
232            .collect::<Vec<_>>();
233
234        let (recv_counts, recv_data) = all_to_allv(comm, &counts, &ask_for_flat);
235
236        // Get indices to send to other processes
237        let asked_for_local = recv_data
238            .iter()
239            .map(|entity| {
240                serial_space
241                    .entity_dofs(entity.entity_type, entity.entity_index)
242                    .unwrap()[0]
243            })
244            .collect::<Vec<_>>();
245        let asked_for_global = asked_for_local
246            .iter()
247            .map(|i| {
248                let dof = process_dof_indices[*i].unwrap();
249                dof + offset
250            })
251            .collect::<Vec<_>>();
252
253        // Get indices of ghosts from other processes
254        let (_, process_ghost_data) = all_to_allv(comm, &recv_counts, &asked_for_local);
255        let (_, global_ghost_data) = all_to_allv(comm, &recv_counts, &asked_for_global);
256
257        let mut start = 0;
258        let process_ghost_indices = counts
259            .iter()
260            .map(|c| {
261                let data = &process_ghost_data[start..start + c];
262                start += c;
263                data.to_vec()
264            })
265            .collect::<Vec<_>>();
266        let mut start = 0;
267        let global_ghost_indices = counts
268            .iter()
269            .map(|c| {
270                let data = &global_ghost_data[start..start + c];
271                start += c;
272                data.to_vec()
273            })
274            .collect::<Vec<_>>();
275
276        // Assign global numbers to DOFs on this process
277        // Here it is assumed that the DOFs associated with each entity are contiguously numbered
278        let offset = 1;
279        let global_dof_indices = dof_indices
280            .iter()
281            .enumerate()
282            .map(|(i, index)| match index {
283                DofIndex::Owned => offset + process_dof_indices[i].unwrap(),
284                DofIndex::Ghost(process, index, number) => {
285                    global_ghost_indices[*process][*index] + *number
286                }
287                DofIndex::None => {
288                    panic!("Unset DOF index");
289                }
290            })
291            .collect::<Vec<_>>();
292        let ownership = dof_indices
293            .iter()
294            .map(|i| match i {
295                DofIndex::Owned => Ownership::Owned,
296                DofIndex::Ghost(process, index, number) => {
297                    Ownership::Ghost(*process, process_ghost_indices[*process][*index] + *number)
298                }
299                DofIndex::None => {
300                    panic!("Unset DOF index");
301                }
302            })
303            .collect::<Vec<_>>();
304
305        let process_space =
306            ProcessFunctionSpace::new(serial_space, global_size, global_dof_indices, ownership);
307        Self {
308            mesh,
309            process_space,
310            global_size,
311        }
312    }
313}
314
315impl<
316    'a,
317    T: RlstScalar,
318    TMesh: RlstScalar,
319    E: Debug + PartialEq + Eq + Clone + Copy + Hash,
320    G: Mesh<EntityDescriptor = E, T = TMesh>,
321    PG: ParallelMesh<LocalMesh = G>,
322    F: MappedFiniteElement<CellType = E, T = T>,
323    S: FunctionSpace<Mesh = G, EntityDescriptor = E, FiniteElement = F>,
324> ParallelFunctionSpace for ParallelFunctionSpaceImpl<'a, T, TMesh, E, G, PG, F, S>
325{
326    type ProcessSpace = ProcessFunctionSpace<S>;
327    type C = PG::C;
328
329    fn comm(&self) -> &PG::C {
330        self.mesh.comm()
331    }
332
333    fn process_space(&self) -> &ProcessFunctionSpace<S> {
334        &self.process_space
335    }
336
337    fn global_size(&self) -> usize {
338        self.global_size
339    }
340}