forked from 1b-agent/fieldcraft-upstream
mesh: renumber nodes and elements in the model, every reference following (#234)
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
b5e54cb802
commit
1a91bb324d
2 files changed
+131
No files matched your search
@@ -266,6 +266,13 @@ impl Classification {
|
||||
pub(crate) fn forget_node(&mut self, label: u32) {
|
||||
self.nodes.remove(&label);
|
||||
}
|
||||
|
||||
/// Give tagged nodes and elements their new labels (labels `node` or `element` leaves alone
|
||||
/// keep theirs; the caller checked the new labels are distinct).
|
||||
pub(crate) fn relabel(&mut self, node: &dyn Fn(u32) -> u32, element: &dyn Fn(u32) -> u32) {
|
||||
self.nodes = std::mem::take(&mut self.nodes).into_iter().map(|(l, t)| (node(l), t)).collect();
|
||||
self.elements = std::mem::take(&mut self.elements).into_iter().map(|(l, t)| (element(l), t)).collect();
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
|
||||
@@ -384,6 +384,7 @@ impl Model {
|
||||
set.nodes = nodes;
|
||||
}
|
||||
}
|
||||
self.relabel_step_nodes(&target);
|
||||
for (&from, &to) in map {
|
||||
if let Some(tag) = self.classification.nodes().get(&from).copied()
|
||||
&& !self.classification.nodes().contains_key(&to)
|
||||
@@ -396,6 +397,83 @@ impl Model {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Give nodes and elements new labels: `nodes[old]` and `elements[old]` (labels not listed
|
||||
/// keep theirs). Everything that names them follows: element connectivity, set members,
|
||||
/// geometry tags and an arc-length step's displacement limit. The new labels must be valid,
|
||||
/// distinct, and no node may take a reference point's label (decks write reference points as
|
||||
/// nodes). Refused, with nothing changed, otherwise.
|
||||
pub fn renumber(&mut self, nodes: &BTreeMap<u32, u32>, elements: &BTreeMap<u32, u32>) -> Result<()> {
|
||||
fn check(what: &str, map: &BTreeMap<u32, u32>, labels: &mut dyn Iterator<Item = u32>, exists: &dyn Fn(u32) -> bool) -> Result<()> {
|
||||
for (&from, &to) in map {
|
||||
if !exists(from) {
|
||||
return Err(Error::NotFound(format!("{what} {from}")));
|
||||
}
|
||||
check_label(what, to)?;
|
||||
}
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
for l in labels {
|
||||
if !seen.insert(l) {
|
||||
return Err(Error::Invalid(format!("two {what}s would both be {what} {l}")));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
let node = |l: u32| nodes.get(&l).copied().unwrap_or(l);
|
||||
let element = |l: u32| elements.get(&l).copied().unwrap_or(l);
|
||||
check("node", nodes, &mut self.nodes.keys().map(|&l| node(l)), &|l| self.nodes.contains_key(&l))?;
|
||||
check("element", elements, &mut self.elements.keys().map(|&l| element(l)), &|l| self.elements.contains_key(&l))?;
|
||||
for (name, p) in &self.reference_points {
|
||||
if let Some((&from, _)) = nodes.iter().find(|(_, to)| **to == p.label) {
|
||||
return Err(Error::Invalid(format!("node {from} would take label {}, reference point `{name}`'s", p.label)));
|
||||
}
|
||||
}
|
||||
if nodes.is_empty() && elements.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.nodes = std::mem::take(&mut self.nodes).into_iter().map(|(l, n)| (node(l), n)).collect();
|
||||
self.elements = std::mem::take(&mut self.elements)
|
||||
.into_iter()
|
||||
.map(|(l, mut e)| {
|
||||
for n in &mut e.nodes {
|
||||
*n = node(*n);
|
||||
}
|
||||
(element(l), e)
|
||||
})
|
||||
.collect();
|
||||
self.classification.relabel(&node, &element);
|
||||
for set in self.sets.values_mut() {
|
||||
set.nodes = set.nodes.iter().map(|&n| node(n)).collect();
|
||||
set.nodes.sort_unstable();
|
||||
set.nodes.dedup();
|
||||
set.elements = set.elements.iter().map(|&e| element(e)).collect();
|
||||
set.elements.sort_unstable();
|
||||
set.elements.dedup();
|
||||
for f in &mut set.faces {
|
||||
f.element = element(f.element);
|
||||
}
|
||||
set.faces.sort_unstable();
|
||||
set.faces.dedup();
|
||||
for e in &mut set.edges {
|
||||
e.element = element(e.element);
|
||||
}
|
||||
set.edges.sort_unstable();
|
||||
set.edges.dedup();
|
||||
}
|
||||
self.relabel_step_nodes(&node);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The nodes steps name by label (an arc-length step's displacement limit), relabelled.
|
||||
fn relabel_step_nodes(&mut self, node: &dyn Fn(u32) -> u32) {
|
||||
for step in &mut self.steps {
|
||||
if let crate::steps::Procedure::NonlinearStatic(ns) = &mut step.procedure
|
||||
&& let Some(limit) = ns.arc_length.as_mut().and_then(|a| a.displacement_limit.as_mut())
|
||||
{
|
||||
limit.node = node(limit.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Element counts by kind, in [`ElementKind::ALL`] order, kinds with none left out.
|
||||
pub fn element_counts(&self) -> Vec<(ElementKind, usize)> {
|
||||
ElementKind::ALL.into_iter().map(|k| (k, self.elements.values().filter(|e| e.kind == k).count())).filter(|(_, n)| *n > 0).collect()
|
||||
@@ -566,4 +644,50 @@ mod tests {
|
||||
assert_eq!(m.classification().nodes().get(&1), Some(&vertex));
|
||||
assert_eq!(m.classification().elements().get(&2).and_then(|t| t.faces.get(&1)), Some(&face));
|
||||
}
|
||||
|
||||
/// Renumbering moves every reference to a label with it (#234): connectivity, set members,
|
||||
/// tags and a step's displacement limit; colliding labels and a reference point's label are
|
||||
/// refused, changing nothing.
|
||||
#[test]
|
||||
fn renumbering_keeps_every_reference() {
|
||||
use crate::steps::{ArcLength, DisplacementLimit, NonlinearStatic, Procedure, Step};
|
||||
use crate::{GeometryRef, Set};
|
||||
let mut m = cube();
|
||||
m.set_element(2, ElementKind::Tet4, vec![1, 2, 4, 5]).unwrap();
|
||||
let face = m.geometry_tag(&GeometryRef::face("B", "bottom").unwrap()).unwrap();
|
||||
m.tag_element_face(1, 1, face).unwrap();
|
||||
let vertex = m.geometry_tag(&GeometryRef::new("B", crate::geometry::Entity::Vertex("v".into())).unwrap()).unwrap();
|
||||
m.tag_node(8, vertex).unwrap();
|
||||
m.add_set("s", Set { nodes: vec![1, 8], elements: vec![2], faces: vec![ElementFace { element: 1, face: 2 }], ..Set::default() }).unwrap();
|
||||
let limit = DisplacementLimit { node: 8, direction: 3, displacement: 1.0 };
|
||||
let arc = ArcLength { displacement_limit: Some(limit), ..ArcLength::default() };
|
||||
m.add_step(Step::new("Riks", Procedure::NonlinearStatic(NonlinearStatic { arc_length: Some(arc), ..NonlinearStatic::default() })), None)
|
||||
.unwrap();
|
||||
m.add_reference_point("RP", crate::ReferencePoint { xyz: [0.0; 3], label: 50 }).unwrap();
|
||||
|
||||
let before = m.clone();
|
||||
assert!(m.renumber(&[(1, 2)].into(), &BTreeMap::new()).is_err(), "node 2 is taken");
|
||||
assert!(m.renumber(&[(1, 50)].into(), &BTreeMap::new()).is_err(), "the reference point's label");
|
||||
assert!(m.renumber(&[(9, 10)].into(), &BTreeMap::new()).is_err());
|
||||
assert!(m.renumber(&[(1, 0)].into(), &BTreeMap::new()).is_err());
|
||||
assert!(m.renumber(&BTreeMap::new(), &[(1, 2)].into()).is_err());
|
||||
assert_eq!(m, before, "a refused renumbering changes nothing");
|
||||
|
||||
// Swap nodes 1 and 8, move elements to 11 and 12.
|
||||
m.renumber(&[(1, 8), (8, 1)].into(), &[(1, 11), (2, 12)].into()).unwrap();
|
||||
assert_eq!(m.node(8).unwrap().xyz, before.node(1).unwrap().xyz);
|
||||
assert_eq!(m.element(11).unwrap().nodes, vec![8, 2, 3, 4, 5, 6, 7, 1]);
|
||||
assert_eq!(m.element(12).unwrap().nodes, vec![8, 2, 4, 5]);
|
||||
let set = m.set("s").unwrap();
|
||||
assert_eq!((set.nodes.clone(), set.elements.clone(), set.faces.clone()), (vec![1, 8], vec![12], vec![ElementFace { element: 11, face: 2 }]));
|
||||
assert_eq!(m.classification().nodes().get(&1), Some(&vertex));
|
||||
assert_eq!(m.classification().elements().get(&11).and_then(|t| t.faces.get(&1)), Some(&face));
|
||||
let Procedure::NonlinearStatic(ns) = &m.steps()[0].procedure else { panic!() };
|
||||
assert_eq!(ns.arc_length.as_ref().and_then(|a| a.displacement_limit.as_ref()).map(|l| l.node), Some(1));
|
||||
// Merging moves the limit to the survivor.
|
||||
m.set_node(21, [0.0, 0.0, 1.0]).unwrap();
|
||||
m.merge_nodes(&[(1, 21)].into()).unwrap();
|
||||
let Procedure::NonlinearStatic(ns) = &m.steps()[0].procedure else { panic!() };
|
||||
assert_eq!(ns.arc_length.as_ref().and_then(|a| a.displacement_limit.as_ref()).map(|l| l.node), Some(21));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user