1use crate::model::Model;
2
3#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
4pub enum Dof {
5 Ux = 0,
6 Uy = 1,
7 Uz = 2,
8 Rx = 3,
9 Ry = 4,
10 Rz = 5,
11}
12
13pub const DOF_PER_NODE: usize = 6;
14
15pub const PANEL_DOF_PER_NODE: usize = 2;
25
26#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
27pub struct Dof6Mask(pub u8);
28
29impl Dof6Mask {
30 pub const FREE: Self = Dof6Mask(0b000000);
31 pub const FIXED: Self = Dof6Mask(0b111111);
32 pub const PINNED: Self = Dof6Mask(0b000111);
33 pub fn is_fixed(self, d: Dof) -> bool {
34 self.0 & (1 << d as u8) != 0
35 }
36 pub fn set_fixed(&mut self, d: Dof) {
37 self.0 |= 1 << d as u8;
38 }
39 pub fn set_free(&mut self, d: Dof) {
41 self.0 &= !(1 << d as u8);
42 }
43 pub fn set(&mut self, d: Dof, fixed: bool) {
45 if fixed {
46 self.set_fixed(d);
47 } else {
48 self.set_free(d);
49 }
50 }
51}
52
53pub type GlobalDof = usize;
54
55pub fn structural_nodes(model: &Model) -> Vec<bool> {
65 let mut structural = vec![false; model.nodes.len()];
66 for e in &model.elements {
67 for n in &e.nodes {
68 if let Some(slot) = structural.get_mut(n.index()) {
69 *slot = true;
70 }
71 }
72 }
73 for c in &model.constraints {
74 use crate::model::Constraint;
75 match c {
76 Constraint::RigidDiaphragm { master, .. } | Constraint::RigidLink { master, .. } => {
77 if let Some(slot) = structural.get_mut(master.index()) {
78 *slot = true;
79 }
80 }
81 Constraint::Mpc { terms, .. } => {
83 for (n, _, _) in terms {
84 if let Some(slot) = structural.get_mut(n.index()) {
85 *slot = true;
86 }
87 }
88 }
89 }
90 }
91 structural
92}
93
94pub fn panel_zone_nodes(model: &Model) -> Vec<bool> {
99 let mut is_panel = vec![false; model.nodes.len()];
100 for e in &model.elements {
101 if !matches!(e.kind, crate::model::ElementKind::PanelZone) {
102 continue;
103 }
104 if let Some(n) = e.nodes.first() {
105 if let Some(slot) = is_panel.get_mut(n.index()) {
106 *slot = true;
107 }
108 }
109 }
110 is_panel
111}
112
113#[derive(Clone, Debug, Default)]
114pub struct DofMap {
115 active_of: Vec<Option<u32>>,
116 global_of: Vec<GlobalDof>,
117 n_active: usize,
118 panel_slot_of: Vec<Option<u32>>,
122 panel_node_of: Vec<u32>,
124 n_node_global: usize,
126}
127
128impl DofMap {
129 pub fn build(model: &Model) -> Self {
130 let structural = structural_nodes(model);
135 let is_panel = panel_zone_nodes(model);
136
137 let n_node_global = model.nodes.len() * DOF_PER_NODE;
140 let mut panel_slot_of = vec![None; model.nodes.len()];
141 let mut panel_node_of = Vec::new();
142 for (ni, &p) in is_panel.iter().enumerate() {
143 if p && structural[ni] {
146 panel_slot_of[ni] = Some(panel_node_of.len() as u32);
147 panel_node_of.push(ni as u32);
148 }
149 }
150
151 let n_global = n_node_global + panel_node_of.len() * PANEL_DOF_PER_NODE;
152 let mut active_of = vec![None; n_global];
153 let mut global_of = Vec::new();
154 let mut counter = 0u32;
155 for (ni, node) in model.nodes.iter().enumerate() {
156 if !structural[ni] {
157 continue;
158 }
159 for d in 0..DOF_PER_NODE {
160 let g = ni * DOF_PER_NODE + d;
161 let dof = match d {
162 0 => Dof::Ux,
163 1 => Dof::Uy,
164 2 => Dof::Uz,
165 3 => Dof::Rx,
166 4 => Dof::Ry,
167 _ => Dof::Rz,
168 };
169 if !node.restraint.is_fixed(dof) {
170 active_of[g] = Some(counter);
171 global_of.push(g);
172 counter += 1;
173 }
174 }
175 }
176 for (ni, slot) in panel_slot_of.iter().enumerate() {
180 let Some(s) = slot else { continue };
181 let _ = ni;
182 for d in 0..PANEL_DOF_PER_NODE {
183 let g = n_node_global + *s as usize * PANEL_DOF_PER_NODE + d;
184 active_of[g] = Some(counter);
185 global_of.push(g);
186 counter += 1;
187 }
188 }
189 DofMap {
190 active_of,
191 global_of,
192 n_active: counter as usize,
193 panel_slot_of,
194 panel_node_of,
195 n_node_global,
196 }
197 }
198
199 pub fn n_active(&self) -> usize {
200 self.n_active
201 }
202 pub fn active(&self, g: GlobalDof) -> Option<u32> {
203 self.active_of.get(g).copied().flatten()
204 }
205
206 pub fn expand_to_nodes(&self, u_free: &[f64], n_nodes: usize) -> Vec<[f64; 6]> {
213 let mut out = vec![[0.0f64; 6]; n_nodes];
214 for (ni, d6) in out.iter_mut().enumerate() {
215 for (d, slot) in d6.iter_mut().enumerate() {
216 if let Some(a) = self.active(ni * DOF_PER_NODE + d) {
217 *slot = u_free[a as usize];
218 }
219 }
220 }
221 out
222 }
223 pub fn global(&self, a: u32) -> GlobalDof {
224 self.global_of[a as usize]
225 }
226
227 pub fn panel_dof(&self, node_idx: usize, d: usize) -> Option<u32> {
230 let slot = (*self.panel_slot_of.get(node_idx)?)? as usize;
231 if d >= PANEL_DOF_PER_NODE {
232 return None;
233 }
234 self.active(self.n_node_global + slot * PANEL_DOF_PER_NODE + d)
235 }
236
237 pub fn has_panel_dof(&self, node_idx: usize) -> bool {
239 self.panel_slot_of
240 .get(node_idx)
241 .is_some_and(|s| s.is_some())
242 }
243
244 pub fn is_node_dof(&self, g: GlobalDof) -> bool {
248 g < self.n_node_global
249 }
250
251 pub fn panel_dof_ref(&self, g: GlobalDof) -> Option<(usize, usize)> {
254 let off = g.checked_sub(self.n_node_global)?;
255 let slot = off / PANEL_DOF_PER_NODE;
256 let d = off % PANEL_DOF_PER_NODE;
257 Some((*self.panel_node_of.get(slot)? as usize, d))
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::dof::Dof6Mask;
265 use crate::ids::*;
266 use crate::model::*;
267
268 fn make_model_with_restraints(restraints: &[Dof6Mask]) -> Model {
269 let nodes: Vec<Node> = restraints
270 .iter()
271 .enumerate()
272 .map(|(i, &r)| Node {
273 id: NodeId(i as u32),
274 coord: [i as f64 * 1000.0, 0.0, 0.0],
275 restraint: r,
276 mass: None,
277 story: None,
278 support_spring: None,
279 })
280 .collect();
281 let elements: Vec<ElementData> = (0..restraints.len().max(2) - 1)
284 .map(|i| ElementData {
285 id: ElemId(i as u32),
286 kind: ElementKind::Beam,
287 nodes: [
288 NodeId(i as u32),
289 NodeId(((i + 1) % restraints.len()) as u32),
290 ]
291 .into_iter()
292 .collect(),
293 section: None,
294 local_axis: LocalAxis {
295 ref_vector: [0.0, 0.0, 1.0],
296 },
297 end_cond: [EndCondition::Fixed, EndCondition::Fixed],
298 force_regime: ForceRegime::Auto,
299 rigid_zone: Default::default(),
300 plastic_zone: None,
301 spring: None,
302 })
303 .collect();
304 Model {
305 nodes,
306 elements,
307 ..Default::default()
308 }
309 }
310
311 #[test]
312 fn test_set_free_and_set_toggle() {
313 let mut m = Dof6Mask::FIXED;
314 m.set_free(Dof::Ux);
315 assert!(!m.is_fixed(Dof::Ux));
316 assert!(m.is_fixed(Dof::Uy));
317 m.set(Dof::Uy, false);
319 assert!(!m.is_fixed(Dof::Uy));
320 m.set(Dof::Ux, true);
321 assert!(m.is_fixed(Dof::Ux));
322 let mut p = Dof6Mask::PINNED;
324 p.set(Dof::Rz, true);
325 assert!(p.is_fixed(Dof::Ux) && p.is_fixed(Dof::Uy) && p.is_fixed(Dof::Uz));
326 assert!(p.is_fixed(Dof::Rz));
327 assert!(!p.is_fixed(Dof::Rx) && !p.is_fixed(Dof::Ry));
328 }
329
330 #[test]
331 fn test_all_free() {
332 let model = make_model_with_restraints(&[Dof6Mask::FREE; 3]);
333 let map = DofMap::build(&model);
334 assert_eq!(map.n_active(), 18);
335 }
336
337 #[test]
340 fn test_no_panel_keeps_dof_map_identical() {
341 let model = make_model_with_restraints(&[Dof6Mask::FREE; 3]);
342 let map = DofMap::build(&model);
343 assert_eq!(map.n_active(), 3 * DOF_PER_NODE);
344 for ni in 0..3 {
345 assert!(!map.has_panel_dof(ni));
346 assert!(map.panel_dof(ni, 0).is_none());
347 }
348 for a in 0..map.n_active() {
350 assert!(map.is_node_dof(map.global(a as u32)));
351 }
352 }
353
354 #[test]
357 fn test_panel_node_gets_two_extra_dofs() {
358 let mut model = make_model_with_restraints(&[Dof6Mask::FREE; 3]);
359 let base = DofMap::build(&model).n_active();
360
361 model.elements.push(ElementData {
363 id: ElemId(100),
364 kind: ElementKind::PanelZone,
365 nodes: [NodeId(1), NodeId(0), NodeId(2)].into_iter().collect(),
366 section: None,
367 local_axis: LocalAxis {
368 ref_vector: [0.0, 0.0, 1.0],
369 },
370 end_cond: [EndCondition::Fixed, EndCondition::Fixed],
371 force_regime: ForceRegime::Auto,
372 rigid_zone: Default::default(),
373 plastic_zone: None,
374 spring: None,
375 });
376
377 let map = DofMap::build(&model);
378 assert_eq!(map.n_active(), base + PANEL_DOF_PER_NODE);
379 assert!(map.has_panel_dof(1));
380 assert!(!map.has_panel_dof(0) && !map.has_panel_dof(2));
381
382 let gx = map.panel_dof(1, 0).expect("γX");
384 let gy = map.panel_dof(1, 1).expect("γY");
385 assert_eq!(gy, gx + 1);
386 assert!(gx as usize >= base);
387 assert!(map.panel_dof(1, 2).is_none(), "成分は 2 個まで");
388
389 assert!(!map.is_node_dof(map.global(gx)));
391 assert_eq!(map.panel_dof_ref(map.global(gx)), Some((1, 0)));
392 assert_eq!(map.panel_dof_ref(map.global(gy)), Some((1, 1)));
393 }
394
395 #[test]
396 fn test_one_fixed() {
397 let model = make_model_with_restraints(&[Dof6Mask::FREE, Dof6Mask::FIXED, Dof6Mask::FREE]);
398 let map = DofMap::build(&model);
399 assert_eq!(map.n_active(), 12);
400 }
401
402 #[test]
403 fn test_all_fixed() {
404 let model = make_model_with_restraints(&[Dof6Mask::FIXED]);
405 let map = DofMap::build(&model);
406 assert_eq!(map.n_active(), 0);
407 }
408
409 #[test]
410 fn test_pinned() {
411 let model = make_model_with_restraints(&[Dof6Mask::PINNED]);
412 let map = DofMap::build(&model);
413 assert_eq!(map.n_active(), 3);
414 }
415
416 #[test]
417 fn test_mixed() {
418 let model = make_model_with_restraints(&[Dof6Mask::FREE, Dof6Mask::PINNED]);
419 let map = DofMap::build(&model);
420 assert_eq!(map.n_active(), 6 + 3);
421 }
422
423 #[test]
426 fn test_unreferenced_node_is_inactive() {
427 let mut model = make_model_with_restraints(&[Dof6Mask::FREE, Dof6Mask::FREE]);
428 model.nodes.push(Node {
430 id: NodeId(2),
431 coord: [500.0, 0.0, 0.0],
432 restraint: Dof6Mask::FREE,
433 mass: None,
434 story: None,
435 support_spring: None,
436 });
437 let map = DofMap::build(&model);
438 assert_eq!(map.n_active(), 12, "孤立自由節点は自由度を持たない");
439 assert!(map.active(2 * DOF_PER_NODE).is_none());
440
441 model.constraints.push(Constraint::rigid_diaphragm(
443 StoryId(0),
444 NodeId(2),
445 vec![NodeId(0), NodeId(1)],
446 ));
447 let map = DofMap::build(&model);
448 assert_eq!(map.n_active(), 18, "拘束マスターは自由度を持つ");
449 }
450}