runmat_analysis_fea/assembly/
dofs.rs1use 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}