1use nalgebra::{Matrix3, SMatrix, SVector, Vector3};
2
3use super::{Node, Node3, SpatialElementMatrix};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum GeomTransf {
16 Linear,
20 PDelta,
24 Corotational,
28}
29
30#[derive(Debug, Clone, Copy)]
34pub(crate) struct Corotational2d {
35 initial_length: f64,
36 deformed_length: f64,
37 basic: SVector<f64, 3>,
38 jacobian: SMatrix<f64, 3, 6>,
41 global_to_local: SMatrix<f64, 6, 6>,
43 direction: SVector<f64, 2>,
45}
46
47impl Corotational2d {
48 pub(crate) fn new(node_i: &Node, node_j: &Node) -> Self {
49 let reference_dx = node_j.coords[0] - node_i.coords[0];
50 let reference_dy = node_j.coords[1] - node_i.coords[1];
51 let initial_length = reference_dx.hypot(reference_dy);
52 assert!(
53 initial_length > 0.0,
54 "Corotational2d: element endpoints must not coincide"
55 );
56 let reference_angle = reference_dy.atan2(reference_dx);
57
58 let dx = reference_dx + node_j.displacement[0] - node_i.displacement[0];
59 let dy = reference_dy + node_j.displacement[1] - node_i.displacement[1];
60 let deformed_length = dx.hypot(dy);
61 assert!(
62 deformed_length > 1e-12,
63 "Corotational2d: deformed element length must be nonzero"
64 );
65
66 let direction = SVector::<f64, 2>::new(dx / deformed_length, dy / deformed_length);
67 let relative_angle = dy.atan2(dx) - reference_angle;
68 let chord_rotation = relative_angle.sin().atan2(relative_angle.cos());
69 let basic = SVector::<f64, 3>::new(
70 deformed_length - initial_length,
71 node_i.displacement[2] - chord_rotation,
72 node_j.displacement[2] - chord_rotation,
73 );
74
75 let inv_l2 = 1.0 / (deformed_length * deformed_length);
77 let alpha_dx = -dy * inv_l2;
78 let alpha_dy = dx * inv_l2;
79 let mut jacobian = SMatrix::<f64, 3, 6>::zeros();
80 jacobian[(0, 0)] = -direction[0];
81 jacobian[(0, 1)] = -direction[1];
82 jacobian[(0, 3)] = direction[0];
83 jacobian[(0, 4)] = direction[1];
84 for row in [1, 2] {
85 jacobian[(row, 0)] = alpha_dx;
86 jacobian[(row, 1)] = alpha_dy;
87 jacobian[(row, 3)] = -alpha_dx;
88 jacobian[(row, 4)] = -alpha_dy;
89 }
90 jacobian[(1, 2)] = 1.0;
91 jacobian[(2, 5)] = 1.0;
92
93 let cx = direction[0];
94 let cy = direction[1];
95 #[rustfmt::skip]
96 let block = SMatrix::<f64, 3, 3>::new(
97 cx, cy, 0.0,
98 -cy, cx, 0.0,
99 0.0, 0.0, 1.0,
100 );
101 let mut global_to_local = SMatrix::<f64, 6, 6>::zeros();
102 global_to_local
103 .fixed_view_mut::<3, 3>(0, 0)
104 .copy_from(&block);
105 global_to_local
106 .fixed_view_mut::<3, 3>(3, 3)
107 .copy_from(&block);
108
109 Self {
110 initial_length,
111 deformed_length,
112 basic,
113 jacobian,
114 global_to_local,
115 direction,
116 }
117 }
118
119 pub(crate) fn initial_length(&self) -> f64 {
120 self.initial_length
121 }
122
123 pub(crate) fn basic_deformation(&self) -> SVector<f64, 3> {
124 self.basic
125 }
126
127 pub(crate) fn global_resistance(&self, basic_force: &SVector<f64, 3>) -> SVector<f64, 6> {
129 self.jacobian.transpose() * basic_force
130 }
131
132 pub(crate) fn local_resistance(&self, basic_force: &SVector<f64, 3>) -> SVector<f64, 6> {
139 self.global_to_local * self.global_resistance(basic_force)
140 }
141
142 pub(crate) fn global_tangent(
145 &self,
146 basic_tangent: &SMatrix<f64, 3, 3>,
147 basic_force: &SVector<f64, 3>,
148 ) -> SMatrix<f64, 6, 6> {
149 let mut tangent = self.jacobian.transpose() * basic_tangent * self.jacobian;
150 let dx = self.direction[0] * self.deformed_length;
151 let dy = self.direction[1] * self.deformed_length;
152 let l = self.deformed_length;
153 let inv_l2 = 1.0 / (l * l);
154 let inv_l4 = inv_l2 * inv_l2;
155
156 let h_length =
157 (SMatrix::<f64, 2, 2>::identity() - self.direction * self.direction.transpose()) / l;
158 let h_angle = SMatrix::<f64, 2, 2>::new(
159 2.0 * dx * dy * inv_l4,
160 (dy * dy - dx * dx) * inv_l4,
161 (dy * dy - dx * dx) * inv_l4,
162 -2.0 * dx * dy * inv_l4,
163 );
164 let h_relative = basic_force[0] * h_length - (basic_force[1] + basic_force[2]) * h_angle;
165 for i in 0..2 {
166 for j in 0..2 {
167 tangent[(i, j)] += h_relative[(i, j)];
168 tangent[(i, j + 3)] -= h_relative[(i, j)];
169 tangent[(i + 3, j)] -= h_relative[(i, j)];
170 tangent[(i + 3, j + 3)] += h_relative[(i, j)];
171 }
172 }
173 tangent
174 }
175
176 pub(crate) fn global_to_local(&self) -> &SMatrix<f64, 6, 6> {
181 &self.global_to_local
182 }
183
184 pub(crate) fn local_basic_modes() -> SMatrix<f64, 6, 3> {
187 #[rustfmt::skip]
188 let modes = SMatrix::<f64, 6, 3>::from_row_slice(&[
189 0.0, 0.0, 0.0,
190 0.0, 0.0, 0.0,
191 0.0, 1.0, 0.0,
192 1.0, 0.0, 0.0,
193 0.0, 0.0, 0.0,
194 0.0, 0.0, 1.0,
195 ]);
196 modes
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq)]
214pub enum GeomTransf3 {
215 Linear3 {
219 vec_xz: [f64; 3],
224 },
225 PDelta3 { vec_xz: [f64; 3] },
231}
232
233impl GeomTransf3 {
234 pub fn linear(vec_xz: [f64; 3]) -> Self {
235 GeomTransf3::Linear3 { vec_xz }
236 }
237
238 pub fn p_delta(vec_xz: [f64; 3]) -> Self {
239 GeomTransf3::PDelta3 { vec_xz }
240 }
241
242 pub(crate) fn local_axes(&self, node_i: &Node3, node_j: &Node3) -> (f64, Matrix3<f64>) {
246 let (GeomTransf3::Linear3 { vec_xz } | GeomTransf3::PDelta3 { vec_xz }) = self;
247
248 let p_i = Vector3::from(node_i.coords);
249 let p_j = Vector3::from(node_j.coords);
250 let d = p_j - p_i;
251 let length = d.norm();
252 assert!(
253 length > 0.0,
254 "GeomTransf3: element endpoints must not coincide"
255 );
256 let x_axis = d / length;
257
258 let v = Vector3::from(*vec_xz);
259 let y_unnormalized = v.cross(&x_axis);
260 let y_norm = y_unnormalized.norm();
261 assert!(
262 y_norm > 1e-12,
263 "GeomTransf3: vec_xz must not be parallel to the member axis"
264 );
265 let y_axis = y_unnormalized / y_norm;
266 let z_axis = x_axis.cross(&y_axis);
267
268 #[rustfmt::skip]
269 let r = Matrix3::new(
270 x_axis[0], x_axis[1], x_axis[2],
271 y_axis[0], y_axis[1], y_axis[2],
272 z_axis[0], z_axis[1], z_axis[2],
273 );
274 (length, r)
275 }
276
277 pub(crate) fn rotation_matrix(r: &Matrix3<f64>) -> SpatialElementMatrix {
280 let mut t = SpatialElementMatrix::zeros();
281 for offset in [0, 3, 6, 9] {
282 t.fixed_view_mut::<3, 3>(offset, offset).copy_from(r);
283 }
284 t
285 }
286}
287
288#[cfg(test)]
289mod geom_transf3_tests {
290 use super::*;
291
292 #[test]
293 fn axis_aligned_member_with_z_up_gives_identity_rotation() {
294 let node_i = Node3::new([0.0, 0.0, 0.0]);
295 let node_j = Node3::new([5.0, 0.0, 0.0]);
296 let transform = GeomTransf3::linear([0.0, 0.0, 1.0]);
297 let (length, r) = transform.local_axes(&node_i, &node_j);
298 assert_eq!(length, 5.0);
299 assert!((r - Matrix3::identity()).norm() < 1e-12);
300 }
301
302 #[test]
303 #[should_panic(expected = "must not coincide")]
304 fn coincident_endpoints_panics() {
305 let node = Node3::new([1.0, 2.0, 3.0]);
306 GeomTransf3::linear([0.0, 0.0, 1.0]).local_axes(&node, &node);
307 }
308
309 #[test]
310 #[should_panic(expected = "must not be parallel")]
311 fn vec_xz_parallel_to_axis_panics() {
312 let node_i = Node3::new([0.0, 0.0, 0.0]);
313 let node_j = Node3::new([5.0, 0.0, 0.0]);
314 GeomTransf3::linear([1.0, 0.0, 0.0]).local_axes(&node_i, &node_j);
315 }
316}