Skip to main content

runmat_analysis_fea/assembly/
dofs.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
4#[serde(rename_all = "snake_case")]
5pub enum StructuralDofKind {
6    Ux,
7    Uy,
8    Uz,
9    Rx,
10    Ry,
11    Rz,
12}
13
14impl StructuralDofKind {
15    pub const ORDER: [StructuralDofKind; 6] = [
16        StructuralDofKind::Ux,
17        StructuralDofKind::Uy,
18        StructuralDofKind::Uz,
19        StructuralDofKind::Rx,
20        StructuralDofKind::Ry,
21        StructuralDofKind::Rz,
22    ];
23
24    pub fn is_translational(self) -> bool {
25        matches!(
26            self,
27            StructuralDofKind::Ux | StructuralDofKind::Uy | StructuralDofKind::Uz
28        )
29    }
30
31    pub fn is_rotational(self) -> bool {
32        matches!(
33            self,
34            StructuralDofKind::Rx | StructuralDofKind::Ry | StructuralDofKind::Rz
35        )
36    }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct StructuralNodeDofSet {
41    active: Vec<StructuralDofKind>,
42}
43
44impl StructuralNodeDofSet {
45    pub fn translational() -> Self {
46        Self::from_kinds([
47            StructuralDofKind::Ux,
48            StructuralDofKind::Uy,
49            StructuralDofKind::Uz,
50        ])
51    }
52
53    pub fn translational_rotational() -> Self {
54        Self::from_kinds(StructuralDofKind::ORDER)
55    }
56
57    pub fn from_kinds<const N: usize>(kinds: [StructuralDofKind; N]) -> Self {
58        let mut active = Vec::with_capacity(N);
59        for kind in StructuralDofKind::ORDER {
60            if kinds.contains(&kind) {
61                active.push(kind);
62            }
63        }
64        Self { active }
65    }
66
67    pub fn contains(&self, kind: StructuralDofKind) -> bool {
68        self.active.contains(&kind)
69    }
70
71    pub fn active(&self) -> &[StructuralDofKind] {
72        &self.active
73    }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77pub struct StructuralDofAddress {
78    pub node_index: usize,
79    pub kind: StructuralDofKind,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct StructuralDofLayout {
84    node_sets: Vec<StructuralNodeDofSet>,
85    rows: Vec<StructuralDofAddress>,
86}
87
88impl Default for StructuralDofLayout {
89    fn default() -> Self {
90        Self::from_node_sets(Vec::new())
91    }
92}
93
94impl StructuralDofLayout {
95    pub fn from_node_sets(node_sets: Vec<StructuralNodeDofSet>) -> Self {
96        let rows = node_sets
97            .iter()
98            .enumerate()
99            .flat_map(|(node_index, set)| {
100                set.active()
101                    .iter()
102                    .copied()
103                    .map(move |kind| StructuralDofAddress { node_index, kind })
104            })
105            .collect();
106        Self { node_sets, rows }
107    }
108
109    pub fn legacy_translational_rows(dof_count: usize) -> Self {
110        if dof_count == 0 {
111            return Self::from_node_sets(Vec::new());
112        }
113        let node_count = dof_count.div_ceil(3);
114        let mut kinds_by_node = vec![Vec::new(); node_count];
115        for row in 0..dof_count {
116            let kind = match row % 3 {
117                0 => StructuralDofKind::Ux,
118                1 => StructuralDofKind::Uy,
119                _ => StructuralDofKind::Uz,
120            };
121            kinds_by_node[row / 3].push(kind);
122        }
123        let node_sets = kinds_by_node
124            .into_iter()
125            .map(|kinds| {
126                let mut active = Vec::new();
127                for kind in StructuralDofKind::ORDER {
128                    if kinds.contains(&kind) {
129                        active.push(kind);
130                    }
131                }
132                StructuralNodeDofSet { active }
133            })
134            .collect();
135        Self::from_node_sets(node_sets)
136    }
137
138    pub fn total_dof_count(&self) -> usize {
139        self.rows.len()
140    }
141
142    pub fn node_count(&self) -> usize {
143        self.node_sets.len()
144    }
145
146    pub fn rotation_node_count(&self) -> usize {
147        self.node_sets
148            .iter()
149            .filter(|set| set.active().iter().any(|kind| kind.is_rotational()))
150            .count()
151    }
152
153    pub fn translational_dof_count(&self) -> usize {
154        self.rows
155            .iter()
156            .filter(|address| address.kind.is_translational())
157            .count()
158    }
159
160    pub fn rotational_dof_count(&self) -> usize {
161        self.rows
162            .iter()
163            .filter(|address| address.kind.is_rotational())
164            .count()
165    }
166
167    pub fn index(&self, node_index: usize, kind: StructuralDofKind) -> Option<usize> {
168        self.rows
169            .iter()
170            .position(|address| address.node_index == node_index && address.kind == kind)
171    }
172
173    pub fn address(&self, row: usize) -> Option<StructuralDofAddress> {
174        self.rows.get(row).copied()
175    }
176
177    pub fn node_set(&self, node_index: usize) -> Option<&StructuralNodeDofSet> {
178        self.node_sets.get(node_index)
179    }
180
181    pub fn has_rotational_dofs(&self) -> bool {
182        self.rotational_dof_count() > 0
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn structural_dof_layout_indexes_full_rotational_nodes_deterministically() {
192        let layout = StructuralDofLayout::from_node_sets(vec![
193            StructuralNodeDofSet::translational_rotational(),
194            StructuralNodeDofSet::translational(),
195        ]);
196
197        assert_eq!(layout.total_dof_count(), 9);
198        assert_eq!(layout.node_count(), 2);
199        assert_eq!(layout.translational_dof_count(), 6);
200        assert_eq!(layout.rotational_dof_count(), 3);
201        assert_eq!(layout.rotation_node_count(), 1);
202        assert_eq!(layout.index(0, StructuralDofKind::Ux), Some(0));
203        assert_eq!(layout.index(0, StructuralDofKind::Rz), Some(5));
204        assert_eq!(layout.index(1, StructuralDofKind::Ux), Some(6));
205        assert_eq!(layout.index(1, StructuralDofKind::Rz), None);
206        assert_eq!(
207            layout.address(4),
208            Some(StructuralDofAddress {
209                node_index: 0,
210                kind: StructuralDofKind::Ry,
211            })
212        );
213    }
214
215    #[test]
216    fn legacy_translational_layout_preserves_existing_row_count() {
217        let layout = StructuralDofLayout::legacy_translational_rows(5);
218
219        assert_eq!(layout.total_dof_count(), 5);
220        assert_eq!(layout.node_count(), 2);
221        assert_eq!(layout.translational_dof_count(), 5);
222        assert_eq!(layout.rotational_dof_count(), 0);
223        assert_eq!(layout.index(1, StructuralDofKind::Ux), Some(3));
224        assert_eq!(layout.index(1, StructuralDofKind::Uz), None);
225    }
226}