squid_n_core/
adjacency.rs1use crate::ids::NodeId;
25use crate::model::{ElementData, ElementKind, Model};
26use std::collections::HashMap;
27
28#[derive(Clone, Debug, Default)]
30pub struct NodeAdjacency {
31 by_node: HashMap<usize, Vec<usize>>,
32}
33
34impl NodeAdjacency {
35 pub fn build(model: &Model) -> Self {
37 let mut by_node: HashMap<usize, Vec<usize>> = HashMap::new();
38 for (ei, e) in model.elements.iter().enumerate() {
39 if !matches!(e.kind, ElementKind::Beam) || e.nodes.len() < 2 {
40 continue;
41 }
42 for n in e.nodes.iter().take(2) {
44 let list = by_node.entry(n.index()).or_default();
45 if !list.contains(&ei) {
46 list.push(ei);
47 }
48 }
49 }
50 Self { by_node }
51 }
52
53 pub fn indices_at(&self, node: NodeId) -> &[usize] {
55 self.by_node
56 .get(&node.index())
57 .map(|v| v.as_slice())
58 .unwrap_or(&[])
59 }
60
61 pub fn elements_at<'a>(
63 &'a self,
64 model: &'a Model,
65 node: NodeId,
66 ) -> impl Iterator<Item = &'a ElementData> + 'a {
67 self.indices_at(node)
68 .iter()
69 .filter_map(move |&ei| model.elements.get(ei))
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::dof::Dof6Mask;
77 use crate::ids::{ElemId, SectionId};
78 use crate::model::{EndCondition, ForceRegime, LocalAxis, Node};
79
80 fn node(id: u32) -> Node {
81 Node {
82 id: NodeId(id),
83 coord: [id as f64 * 1000.0, 0.0, 0.0],
84 restraint: Dof6Mask::FREE,
85 mass: None,
86 story: None,
87 support_spring: None,
88 }
89 }
90
91 fn elem(id: u32, kind: ElementKind, nodes: &[u32]) -> ElementData {
92 ElementData {
93 id: ElemId(id),
94 kind,
95 nodes: nodes.iter().map(|&n| NodeId(n)).collect(),
96 section: Some(SectionId(0)),
97 local_axis: LocalAxis {
98 ref_vector: [0.0, 1.0, 0.0],
99 },
100 end_cond: [EndCondition::Fixed, EndCondition::Fixed],
101 force_regime: ForceRegime::Auto,
102 rigid_zone: Default::default(),
103 plastic_zone: None,
104 spring: None,
105 }
106 }
107
108 fn model() -> Model {
109 Model {
110 nodes: (0..4).map(node).collect(),
111 elements: vec![
112 elem(0, ElementKind::Beam, &[0, 1]),
113 elem(1, ElementKind::Beam, &[1, 2]),
114 elem(2, ElementKind::Wall, &[0, 1, 2, 3]),
116 elem(3, ElementKind::Beam, &[3]),
118 ],
119 ..Default::default()
120 }
121 }
122
123 #[test]
125 fn test_collects_line_members_only() {
126 let m = model();
127 let adj = NodeAdjacency::build(&m);
128 assert_eq!(adj.indices_at(NodeId(0)), &[0]);
129 assert_eq!(adj.indices_at(NodeId(1)), &[0, 1], "共有節点は 2 本");
130 assert_eq!(adj.indices_at(NodeId(2)), &[1]);
131 assert!(
132 adj.indices_at(NodeId(3)).is_empty(),
133 "壁・単節点要素は入らない"
134 );
135 }
136
137 #[test]
139 fn test_unknown_node_is_empty() {
140 let m = model();
141 let adj = NodeAdjacency::build(&m);
142 assert!(adj.indices_at(NodeId(99)).is_empty());
143 assert_eq!(adj.elements_at(&m, NodeId(99)).count(), 0);
144 }
145
146 #[test]
148 fn test_elements_at_resolves_references() {
149 let m = model();
150 let adj = NodeAdjacency::build(&m);
151 let ids: Vec<_> = adj.elements_at(&m, NodeId(1)).map(|e| e.id).collect();
152 assert_eq!(ids, vec![ElemId(0), ElemId(1)]);
153 }
154}