1use std::collections::{BTreeSet, HashMap, HashSet};
9
10use crate::algs::communicator::{CommTag, Communicator, SectionCommTags, Wait};
11use crate::algs::wire::{WireCount, cast_slice, cast_slice_mut};
12use crate::data::constrained_section::ConstraintSet;
13use crate::data::multi_section::MultiSection;
14use crate::data::section::Section;
15use crate::data::section_layout::{constrained_dof_len, multi_section_dof_len_with_constraints};
16use crate::data::storage::Storage;
17use crate::mesh_error::MeshSieveError;
18use crate::overlap::overlap::Overlap;
19use crate::overlap::overlap::local;
20use crate::topology::ownership::PointOwnership;
21use crate::topology::point::PointId;
22use crate::topology::sieve::sieve_trait::Sieve;
23
24#[derive(Clone, Debug, Default)]
26pub struct LocalToGlobalMap {
27 offsets: Vec<Option<u64>>,
28 dof_lengths: Vec<Option<usize>>,
29 total_dofs: u64,
30}
31
32impl LocalToGlobalMap {
33 pub fn from_section_with_tags_and_ownership<V, S, C>(
35 section: &Section<V, S>,
36 overlap: &Overlap,
37 ownership: &PointOwnership,
38 comm: &C,
39 my_rank: usize,
40 tags: SectionCommTags,
41 ) -> Result<Self, MeshSieveError>
42 where
43 S: Storage<V>,
44 C: Communicator + Sync,
45 {
46 #[cfg(any(
47 debug_assertions,
48 feature = "strict-invariants",
49 feature = "check-invariants"
50 ))]
51 overlap.validate_invariants()?;
52
53 let max_id = section.atlas().points().map(|p| p.get()).max().unwrap_or(0) as usize;
54 let mut map = LocalToGlobalMap {
55 offsets: vec![None; max_id],
56 dof_lengths: vec![None; max_id],
57 total_dofs: 0,
58 };
59 map.populate_dof_lengths_with(section.atlas().points(), |point| {
60 let (_, len) = section
61 .atlas()
62 .get(point)
63 .ok_or(MeshSieveError::PointNotInAtlas(point))?;
64 Ok(len)
65 })?;
66 map.assign_owned_offsets(section.atlas().points(), ownership, comm, my_rank)?;
67
68 let mut has_ghosts = false;
69 for p in section.atlas().points() {
70 let owner = ownership.owner_or_err(p)?;
71 if owner != my_rank {
72 has_ghosts = true;
73 }
74 }
75
76 let mut nb: BTreeSet<usize> = overlap.neighbor_ranks().collect();
77 nb.remove(&my_rank);
78 if nb.is_empty() {
79 if has_ghosts {
80 return Err(MeshSieveError::MissingOverlap {
81 source: format!("rank {my_rank} has ghost points but no overlap").into(),
82 });
83 }
84 return Ok(map);
85 }
86
87 let mut links =
88 neighbour_links_with_ownership_for_atlas(section.atlas(), overlap, ownership, my_rank)?;
89 for link_vec in links.values_mut() {
90 link_vec.sort_unstable_by_key(|(send_loc, _)| send_loc.get());
91 }
92
93 let mut all_neighbors: HashSet<usize> = overlap.neighbor_ranks().collect();
94 all_neighbors.extend(links.keys().copied());
95 all_neighbors.remove(&my_rank);
96
97 let send_counts = build_send_counts(&links, section.atlas(), ownership, my_rank)?;
98 let recv_counts =
99 exchange_sizes_with_counts(&send_counts, comm, tags.sizes, &all_neighbors)?;
100 exchange_offsets(
101 &links,
102 &recv_counts,
103 comm,
104 tags.data,
105 section.atlas(),
106 ownership,
107 &mut map,
108 &all_neighbors,
109 )?;
110
111 map.ensure_complete(section.atlas().points())?;
112 Ok(map)
113 }
114
115 pub fn from_section_with_ownership<V, S, C>(
117 section: &Section<V, S>,
118 overlap: &Overlap,
119 ownership: &PointOwnership,
120 comm: &C,
121 my_rank: usize,
122 ) -> Result<Self, MeshSieveError>
123 where
124 S: Storage<V>,
125 C: Communicator + Sync,
126 {
127 let tags = SectionCommTags::from_base(CommTag::new(0xBEEF));
128 Self::from_section_with_tags_and_ownership(section, overlap, ownership, comm, my_rank, tags)
129 }
130
131 pub fn from_section_with_constraints_and_ownership<V, S, C, CS>(
133 section: &Section<V, S>,
134 constraints: &CS,
135 overlap: &Overlap,
136 ownership: &PointOwnership,
137 comm: &C,
138 my_rank: usize,
139 tags: SectionCommTags,
140 ) -> Result<Self, MeshSieveError>
141 where
142 S: Storage<V>,
143 C: Communicator + Sync,
144 CS: ConstraintSet<V>,
145 {
146 let mut map = LocalToGlobalMap::default();
147 map.populate_dof_lengths_with(section.atlas().points(), |point| {
148 let (_, len) = section
149 .atlas()
150 .get(point)
151 .ok_or(MeshSieveError::PointNotInAtlas(point))?;
152 constrained_dof_len(point, len, constraints.constraints_for(point))
153 })?;
154 map.assign_owned_offsets(section.atlas().points(), ownership, comm, my_rank)?;
155
156 let mut has_ghosts = false;
157 for p in section.atlas().points() {
158 let owner = ownership.owner_or_err(p)?;
159 if owner != my_rank {
160 has_ghosts = true;
161 }
162 }
163
164 let mut nb: BTreeSet<usize> = overlap.neighbor_ranks().collect();
165 nb.remove(&my_rank);
166 if nb.is_empty() {
167 if has_ghosts {
168 return Err(MeshSieveError::MissingOverlap {
169 source: format!("rank {my_rank} has ghost points but no overlap").into(),
170 });
171 }
172 return Ok(map);
173 }
174
175 let mut links =
176 neighbour_links_with_ownership_for_atlas(section.atlas(), overlap, ownership, my_rank)?;
177 for link_vec in links.values_mut() {
178 link_vec.sort_unstable_by_key(|(send_loc, _)| send_loc.get());
179 }
180
181 let mut all_neighbors: HashSet<usize> = overlap.neighbor_ranks().collect();
182 all_neighbors.extend(links.keys().copied());
183 all_neighbors.remove(&my_rank);
184
185 let send_counts = build_send_counts(&links, section.atlas(), ownership, my_rank)?;
186 let recv_counts =
187 exchange_sizes_with_counts(&send_counts, comm, tags.sizes, &all_neighbors)?;
188 exchange_offsets(
189 &links,
190 &recv_counts,
191 comm,
192 tags.data,
193 section.atlas(),
194 ownership,
195 &mut map,
196 &all_neighbors,
197 )?;
198
199 map.ensure_complete(section.atlas().points())?;
200 Ok(map)
201 }
202
203 pub fn from_multi_section_with_tags_and_ownership<V, S, C>(
205 section: &MultiSection<V, S>,
206 overlap: &Overlap,
207 ownership: &PointOwnership,
208 comm: &C,
209 my_rank: usize,
210 tags: SectionCommTags,
211 ) -> Result<Self, MeshSieveError>
212 where
213 S: Storage<V>,
214 C: Communicator + Sync,
215 {
216 let mut map = LocalToGlobalMap::default();
217 map.populate_dof_lengths_with(section.atlas().points(), |point| {
218 multi_section_dof_len_with_constraints(section, point)
219 })?;
220 map.assign_owned_offsets(section.atlas().points(), ownership, comm, my_rank)?;
221
222 let mut has_ghosts = false;
223 for p in section.atlas().points() {
224 let owner = ownership.owner_or_err(p)?;
225 if owner != my_rank {
226 has_ghosts = true;
227 }
228 }
229
230 let mut nb: BTreeSet<usize> = overlap.neighbor_ranks().collect();
231 nb.remove(&my_rank);
232 if nb.is_empty() {
233 if has_ghosts {
234 return Err(MeshSieveError::MissingOverlap {
235 source: format!("rank {my_rank} has ghost points but no overlap").into(),
236 });
237 }
238 return Ok(map);
239 }
240
241 let mut links =
242 neighbour_links_with_ownership_for_atlas(section.atlas(), overlap, ownership, my_rank)?;
243 for link_vec in links.values_mut() {
244 link_vec.sort_unstable_by_key(|(send_loc, _)| send_loc.get());
245 }
246
247 let mut all_neighbors: HashSet<usize> = overlap.neighbor_ranks().collect();
248 all_neighbors.extend(links.keys().copied());
249 all_neighbors.remove(&my_rank);
250
251 let send_counts = build_send_counts(&links, section.atlas(), ownership, my_rank)?;
252 let recv_counts =
253 exchange_sizes_with_counts(&send_counts, comm, tags.sizes, &all_neighbors)?;
254 exchange_offsets(
255 &links,
256 &recv_counts,
257 comm,
258 tags.data,
259 section.atlas(),
260 ownership,
261 &mut map,
262 &all_neighbors,
263 )?;
264
265 map.ensure_complete(section.atlas().points())?;
266 Ok(map)
267 }
268
269 pub fn from_multi_section_with_ownership<V, S, C>(
271 section: &MultiSection<V, S>,
272 overlap: &Overlap,
273 ownership: &PointOwnership,
274 comm: &C,
275 my_rank: usize,
276 ) -> Result<Self, MeshSieveError>
277 where
278 S: Storage<V>,
279 C: Communicator + Sync,
280 {
281 let tags = SectionCommTags::from_base(CommTag::new(0xBEEF));
282 Self::from_multi_section_with_tags_and_ownership(
283 section, overlap, ownership, comm, my_rank, tags,
284 )
285 }
286
287 pub fn global_offset(&self, point: PointId) -> Result<u64, MeshSieveError> {
289 let idx = point_index(point)?;
290 self.offsets
291 .get(idx)
292 .and_then(|val| *val)
293 .ok_or(MeshSieveError::PointNotInAtlas(point))
294 }
295
296 pub fn global_index(&self, point: PointId, dof: usize) -> Result<u64, MeshSieveError> {
298 let idx = point_index(point)?;
299 let len = self
300 .dof_lengths
301 .get(idx)
302 .and_then(|val| *val)
303 .ok_or(MeshSieveError::PointNotInAtlas(point))?;
304 if dof >= len {
305 return Err(MeshSieveError::ConstraintIndexOutOfBounds {
306 point,
307 index: dof,
308 len,
309 });
310 }
311 Ok(self.global_offset(point)? + dof as u64)
312 }
313
314 pub fn global_range(&self, point: PointId) -> Result<std::ops::Range<u64>, MeshSieveError> {
316 let idx = point_index(point)?;
317 let len = self
318 .dof_lengths
319 .get(idx)
320 .and_then(|val| *val)
321 .ok_or(MeshSieveError::PointNotInAtlas(point))? as u64;
322 let start = self.global_offset(point)?;
323 Ok(start..start + len)
324 }
325
326 pub fn remap_points<I>(&self, new_to_old: I) -> Result<Self, MeshSieveError>
332 where
333 I: IntoIterator<Item = (PointId, PointId)>,
334 {
335 let mut out = LocalToGlobalMap {
336 offsets: Vec::new(),
337 dof_lengths: Vec::new(),
338 total_dofs: self.total_dofs,
339 };
340 for (new_point, old_point) in new_to_old {
341 let old_idx = point_index(old_point)?;
342 let new_idx = point_index(new_point)?;
343 let offset = self
344 .offsets
345 .get(old_idx)
346 .and_then(|val| *val)
347 .ok_or(MeshSieveError::PointNotInAtlas(old_point))?;
348 let len = self
349 .dof_lengths
350 .get(old_idx)
351 .and_then(|val| *val)
352 .ok_or(MeshSieveError::PointNotInAtlas(old_point))?;
353 if new_idx >= out.offsets.len() {
354 out.offsets.resize(new_idx + 1, None);
355 out.dof_lengths.resize(new_idx + 1, None);
356 }
357 out.offsets[new_idx] = Some(offset);
358 out.dof_lengths[new_idx] = Some(len);
359 }
360 Ok(out)
361 }
362
363 pub fn total_dofs(&self) -> u64 {
365 self.total_dofs
366 }
367
368 fn populate_dof_lengths_with<I, F>(
369 &mut self,
370 points: I,
371 mut dof_len: F,
372 ) -> Result<(), MeshSieveError>
373 where
374 I: IntoIterator<Item = PointId>,
375 F: FnMut(PointId) -> Result<usize, MeshSieveError>,
376 {
377 for p in points {
378 let len = dof_len(p)?;
379 let idx = point_index(p)?;
380 if idx >= self.dof_lengths.len() {
381 self.dof_lengths.resize(idx + 1, None);
382 self.offsets.resize(idx + 1, None);
383 }
384 self.dof_lengths[idx] = Some(len);
385 }
386 Ok(())
387 }
388
389 fn assign_owned_offsets<I, C>(
390 &mut self,
391 points: I,
392 ownership: &PointOwnership,
393 comm: &C,
394 my_rank: usize,
395 ) -> Result<(), MeshSieveError>
396 where
397 I: IntoIterator<Item = PointId>,
398 C: Communicator + Sync,
399 {
400 let mut owned_points: Vec<PointId> = points
401 .into_iter()
402 .filter(|&p| ownership.is_owned_by(p, my_rank))
403 .collect();
404 owned_points.sort_unstable();
405
406 let mut local_total = 0u64;
407 for p in &owned_points {
408 let idx = point_index(*p)?;
409 let len = self
410 .dof_lengths
411 .get(idx)
412 .and_then(|val| *val)
413 .ok_or(MeshSieveError::PointNotInAtlas(*p))? as u64;
414 self.offsets[idx] = Some(local_total);
415 local_total = local_total.saturating_add(len);
416 }
417
418 let n_ranks = comm.size().max(1);
419 let mut recvbuf = vec![0u8; n_ranks * std::mem::size_of::<u64>()];
420 comm.allgather(&local_total.to_le_bytes(), &mut recvbuf);
421
422 let mut totals = vec![0u64; n_ranks];
423 for (idx, chunk) in recvbuf.chunks_exact(8).enumerate() {
424 let mut raw = [0u8; 8];
425 raw.copy_from_slice(chunk);
426 totals[idx] = u64::from_le_bytes(raw);
427 }
428 let base: u64 = totals.iter().take(my_rank).copied().sum();
429 self.total_dofs = totals.iter().copied().sum();
430
431 for p in &owned_points {
432 let idx = point_index(*p)?;
433 if let Some(offset) = self.offsets.get_mut(idx).and_then(|val| val.as_mut()) {
434 *offset = offset.saturating_add(base);
435 }
436 }
437
438 Ok(())
439 }
440
441 fn ensure_complete<I>(&self, points: I) -> Result<(), MeshSieveError>
442 where
443 I: IntoIterator<Item = PointId>,
444 {
445 for p in points {
446 let idx = point_index(p)?;
447 if self.offsets.get(idx).and_then(|val| *val).is_none() {
448 return Err(MeshSieveError::MissingOverlap {
449 source: format!("missing global offset for point {p:?}").into(),
450 });
451 }
452 }
453 Ok(())
454 }
455}
456
457pub fn global_vector_for_map<V>(map: &LocalToGlobalMap) -> Vec<V>
459where
460 V: Clone + Default,
461{
462 vec![V::default(); map.total_dofs as usize]
463}
464
465fn point_index(point: PointId) -> Result<usize, MeshSieveError> {
466 point
467 .get()
468 .checked_sub(1)
469 .ok_or(MeshSieveError::InvalidPointId)
470 .map(|idx| idx as usize)
471}
472
473fn build_send_counts(
474 links: &HashMap<usize, Vec<(PointId, PointId)>>,
475 atlas: &crate::data::atlas::Atlas,
476 ownership: &PointOwnership,
477 my_rank: usize,
478) -> Result<HashMap<usize, u32>, MeshSieveError> {
479 let mut counts = HashMap::with_capacity(links.len());
480 for (nbr, link_vec) in links {
481 let mut count = 0usize;
482 for &(send_loc, _) in link_vec {
483 if atlas.contains(send_loc) && ownership.owner_or_err(send_loc)? == my_rank {
484 count += 1;
485 }
486 }
487 counts.insert(*nbr, u32::try_from(count).unwrap_or(u32::MAX));
488 }
489 Ok(counts)
490}
491
492fn exchange_sizes_with_counts<C>(
493 send_counts: &HashMap<usize, u32>,
494 comm: &C,
495 tag: CommTag,
496 all_neighbors: &HashSet<usize>,
497) -> Result<HashMap<usize, u32>, MeshSieveError>
498where
499 C: Communicator + Sync,
500{
501 let mut recv_size: HashMap<usize, (C::RecvHandle, WireCount)> = HashMap::new();
502 for &nbr in all_neighbors {
503 let mut cnt = WireCount::new(0);
504 let h = comm.irecv_result(
505 nbr,
506 tag.as_u16(),
507 cast_slice_mut(std::slice::from_mut(&mut cnt)),
508 )?;
509 recv_size.insert(nbr, (h, cnt));
510 }
511
512 let mut pending_sends = Vec::with_capacity(all_neighbors.len());
513 let mut send_bufs = Vec::with_capacity(all_neighbors.len());
514 for &nbr in all_neighbors {
515 let count = send_counts.get(&nbr).copied().unwrap_or(0);
516 let wire = WireCount::new(count as usize);
517 pending_sends.push(comm.isend_result(
518 nbr,
519 tag.as_u16(),
520 cast_slice(std::slice::from_ref(&wire)),
521 )?);
522 send_bufs.push(wire);
523 }
524
525 let mut sizes_in = HashMap::new();
526 let mut maybe_err = None;
527 for (nbr, (h, mut cnt)) in recv_size {
528 match h.wait() {
529 Some(data) if data.len() == std::mem::size_of::<WireCount>() => {
530 if maybe_err.is_none() {
531 let bytes = cast_slice_mut(std::slice::from_mut(&mut cnt));
532 bytes.copy_from_slice(&data);
533 sizes_in.insert(nbr, cnt.get() as u32);
534 }
535 }
536 Some(data) if maybe_err.is_none() => {
537 maybe_err = Some(MeshSieveError::CommError {
538 neighbor: nbr,
539 source: format!(
540 "expected {} bytes for size header, got {}",
541 std::mem::size_of::<WireCount>(),
542 data.len()
543 )
544 .into(),
545 });
546 }
547 None if maybe_err.is_none() => {
548 maybe_err = Some(MeshSieveError::CommError {
549 neighbor: nbr,
550 source: format!("failed to receive size from rank {nbr}").into(),
551 });
552 }
553 _ => {}
554 }
555 }
556
557 for send in pending_sends {
558 let _ = send.wait();
559 }
560 drop(send_bufs);
561
562 if let Some(err) = maybe_err {
563 Err(err)
564 } else {
565 Ok(sizes_in)
566 }
567}
568
569fn exchange_offsets<C>(
570 links: &HashMap<usize, Vec<(PointId, PointId)>>,
571 recv_counts: &HashMap<usize, u32>,
572 comm: &C,
573 tag: CommTag,
574 atlas: &crate::data::atlas::Atlas,
575 ownership: &PointOwnership,
576 map: &mut LocalToGlobalMap,
577 all_neighbors: &HashSet<usize>,
578) -> Result<(), MeshSieveError>
579where
580 C: Communicator + Sync,
581{
582 let mut recv_data: HashMap<usize, (C::RecvHandle, Vec<u64>)> = HashMap::new();
583 for &nbr in all_neighbors {
584 let n_items = recv_counts.get(&nbr).copied().unwrap_or(0) as usize;
585 let mut buffer = vec![0u64; n_items];
586 let h = comm.irecv_result(nbr, tag.as_u16(), cast_slice_mut(&mut buffer))?;
587 recv_data.insert(nbr, (h, buffer));
588 }
589
590 let mut pending_sends = Vec::with_capacity(all_neighbors.len());
591 let mut send_bufs = Vec::with_capacity(all_neighbors.len());
592 for &nbr in all_neighbors {
593 let link_vec = links.get(&nbr).map_or(&[][..], |v| &v[..]);
594 let mut scratch = Vec::new();
595 for &(send_loc, _) in link_vec {
596 if atlas.contains(send_loc) && ownership.owner_or_err(send_loc)? == comm.rank() {
597 let idx = point_index(send_loc)?;
598 if let Some(offset) = map.offsets.get(idx).and_then(|val| *val) {
599 scratch.push(offset);
600 }
601 }
602 }
603 let bytes = cast_slice(&scratch);
604 pending_sends.push(comm.isend_result(nbr, tag.as_u16(), bytes)?);
605 send_bufs.push(scratch);
606 }
607
608 for (nbr, (h, mut buffer)) in recv_data {
609 let raw = h.wait().ok_or_else(|| MeshSieveError::CommError {
610 neighbor: nbr,
611 source: "No data received (wait returned None)".into(),
612 })?;
613 if raw.len() != buffer.len() * std::mem::size_of::<u64>() {
614 return Err(MeshSieveError::BufferSizeMismatch {
615 neighbor: nbr,
616 expected: buffer.len() * std::mem::size_of::<u64>(),
617 got: raw.len(),
618 });
619 }
620 cast_slice_mut(&mut buffer).copy_from_slice(&raw);
621 let parts: &[u64] = &buffer;
622 let link_vec = links.get(&nbr).map_or(&[][..], |v| &v[..]);
623 let mut recv_pairs = Vec::new();
624 for &(send_loc, recv_loc) in link_vec {
625 if !atlas.contains(recv_loc) {
626 continue;
627 }
628 if ownership.owner_or_err(recv_loc)? == nbr {
629 recv_pairs.push((send_loc, recv_loc));
630 }
631 }
632 recv_pairs.sort_unstable_by_key(|(send_loc, _)| send_loc.get());
633 if parts.len() != recv_pairs.len() {
634 return Err(MeshSieveError::PartCountMismatch {
635 neighbor: nbr,
636 expected: recv_pairs.len(),
637 got: parts.len(),
638 });
639 }
640 for ((_, recv_loc), offset) in recv_pairs.iter().zip(parts) {
641 let idx = point_index(*recv_loc)?;
642 if idx >= map.offsets.len() {
643 map.offsets.resize(idx + 1, None);
644 }
645 map.offsets[idx] = Some(*offset);
646 }
647 }
648
649 for send in pending_sends {
650 let _ = send.wait();
651 }
652 drop(send_bufs);
653
654 Ok(())
655}
656
657fn neighbour_links_with_ownership_for_atlas(
658 atlas: &crate::data::atlas::Atlas,
659 ovlp: &Overlap,
660 ownership: &PointOwnership,
661 my_rank: usize,
662) -> Result<HashMap<usize, Vec<(PointId, PointId)>>, MeshSieveError> {
663 let mut out: HashMap<usize, Vec<(PointId, PointId)>> = HashMap::new();
664
665 for p in atlas.points() {
666 let owner = ownership.owner_or_err(p)?;
667 if owner == my_rank {
668 for (_dst, rem) in ovlp.cone(local(p)) {
669 if rem.rank != my_rank {
670 let remote_pt = rem
671 .remote_point
672 .ok_or(MeshSieveError::OverlapLinkMissing(p, rem.rank))?;
673 out.entry(rem.rank).or_default().push((p, remote_pt));
674 }
675 }
676 } else {
677 let mut remote_point = None;
678 for (_dst, rem) in ovlp.cone(local(p)) {
679 if rem.rank == owner {
680 remote_point = rem.remote_point;
681 break;
682 }
683 }
684 let remote_pt = remote_point.ok_or(MeshSieveError::OverlapLinkMissing(p, owner))?;
685 out.entry(owner).or_default().push((remote_pt, p));
686 }
687 }
688
689 if out.is_empty() {
690 return Err(MeshSieveError::MissingOverlap {
691 source: format!("rank {my_rank} has no neighbour links").into(),
692 });
693 }
694
695 Ok(out)
696}