1use nalgebra::DMatrix;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct BarElement1D {
9 pub e: f64,
11 pub a: f64,
13 pub length: f64,
15 pub nodes: [usize; 2],
17}
18
19impl BarElement1D {
20 pub fn new(e: f64, a: f64, length: f64, node_i: usize, node_j: usize) -> Self {
22 Self { e, a, length, nodes: [node_i, node_j] }
23 }
24
25 pub fn stiffness(&self) -> f64 {
27 self.e * self.a / self.length
28 }
29
30 pub fn local_stiffness_matrix(&self) -> [[f64; 2]; 2] {
32 let k = self.stiffness();
33 [[k, -k], [-k, k]]
34 }
35
36 pub fn axial_stress(&self, u_i: f64, u_j: f64) -> f64 {
38 self.e * (u_j - u_i) / self.length
39 }
40
41 pub fn axial_force(&self, u_i: f64, u_j: f64) -> f64 {
43 self.stiffness() * (u_j - u_i)
44 }
45
46 pub fn strain_energy(&self, u_i: f64, u_j: f64) -> f64 {
48 let du = u_j - u_i;
49 0.5 * self.stiffness() * du * du
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct FemAssembler1D {
56 pub num_nodes: usize,
58 pub elements: Vec<BarElement1D>,
60}
61
62impl FemAssembler1D {
63 pub fn new(num_nodes: usize) -> Self {
65 Self { num_nodes, elements: Vec::new() }
66 }
67
68 pub fn add_element(&mut self, element: BarElement1D) {
70 self.elements.push(element);
71 }
72
73 pub fn uniform_bar(e: f64, a: f64, total_length: f64, n_elements: usize) -> Self {
75 let n_nodes = n_elements + 1;
76 let le = total_length / n_elements as f64;
77 let mut assembler = Self::new(n_nodes);
78 for i in 0..n_elements {
79 assembler.add_element(BarElement1D::new(e, a, le, i, i + 1));
80 }
81 assembler
82 }
83
84 pub fn assemble_global_stiffness(&self) -> DMatrix<f64> {
86 let n = self.num_nodes;
87 let mut k_global = DMatrix::zeros(n, n);
88
89 for elem in &self.elements {
90 let ke = elem.local_stiffness_matrix();
91 let ni = elem.nodes[0];
92 let nj = elem.nodes[1];
93
94 k_global[(ni, ni)] += ke[0][0];
95 k_global[(ni, nj)] += ke[0][1];
96 k_global[(nj, ni)] += ke[1][0];
97 k_global[(nj, nj)] += ke[1][1];
98 }
99
100 k_global
101 }
102
103 pub fn apply_boundary_conditions(
108 &self,
109 k_global: &DMatrix<f64>,
110 forces: &[f64],
111 fixed_nodes: &[usize],
112 ) -> (DMatrix<f64>, Vec<f64>, Vec<usize>) {
113 let fixed_set: std::collections::HashSet<usize> = fixed_nodes.iter().cloned().collect();
114 let free_dofs: Vec<usize> = (0..self.num_nodes)
115 .filter(|i| !fixed_set.contains(i))
116 .collect();
117
118 let nf = free_dofs.len();
119 let mut k_reduced = DMatrix::zeros(nf, nf);
120 let mut f_reduced = vec![0.0; nf];
121
122 for (ir, &i) in free_dofs.iter().enumerate() {
123 f_reduced[ir] = forces[i];
124 for (jc, &j) in free_dofs.iter().enumerate() {
125 k_reduced[(ir, jc)] = k_global[(i, j)];
126 }
127 }
128
129 (k_reduced, f_reduced, free_dofs)
130 }
131
132 pub fn solve(
135 &self,
136 forces: &[f64],
137 fixed_nodes: &[usize],
138 ) -> Option<Vec<f64>> {
139 let k_global = self.assemble_global_stiffness();
140 let (k_red, f_red, free_dofs) = self.apply_boundary_conditions(&k_global, forces, fixed_nodes);
141
142 let u_red = k_red.lu().solve(&nalgebra::DVector::from_vec(f_red))?;
144
145 let mut u_full = vec![0.0; self.num_nodes];
147 for (i, &dof) in free_dofs.iter().enumerate() {
148 u_full[dof] = u_red[i];
149 }
150
151 Some(u_full)
152 }
153
154 pub fn element_stresses(&self, displacements: &[f64]) -> Vec<f64> {
156 self.elements.iter().map(|elem| {
157 let ui = displacements[elem.nodes[0]];
158 let uj = displacements[elem.nodes[1]];
159 elem.axial_stress(ui, uj)
160 }).collect()
161 }
162
163 pub fn total_strain_energy(&self, displacements: &[f64]) -> f64 {
165 self.elements.iter().map(|elem| {
166 let ui = displacements[elem.nodes[0]];
167 let uj = displacements[elem.nodes[1]];
168 elem.strain_energy(ui, uj)
169 }).sum()
170 }
171
172 pub fn check_equilibrium(
174 &self,
175 displacements: &[f64],
176 forces: &[f64],
177 ) -> f64 {
178 let k = self.assemble_global_stiffness();
179 let u = nalgebra::DVector::from_vec(displacements.to_vec());
180 let f = nalgebra::DVector::from_vec(forces.to_vec());
181 (k * u - f).norm()
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use approx::assert_relative_eq;
189
190 #[test]
191 fn test_bar_element_stiffness() {
192 let bar = BarElement1D::new(200e9, 1e-4, 1.0, 0, 1);
193 assert_relative_eq!(bar.stiffness(), 2e7);
194 }
195
196 #[test]
197 fn test_local_stiffness_symmetry() {
198 let bar = BarElement1D::new(200e9, 1e-4, 1.0, 0, 1);
199 let k = bar.local_stiffness_matrix();
200 assert_relative_eq!(k[0][1], -k[0][0]);
201 assert_relative_eq!(k[1][0], -k[1][1]);
202 }
203
204 #[test]
205 fn test_single_bar_fixed_free() {
206 let bar = BarElement1D::new(200e9, 1e-4, 1.0, 0, 1);
207 let mut fem = FemAssembler1D::new(2);
208 fem.add_element(bar);
209 let forces = vec![0.0, 10000.0];
210 let fixed = vec![0];
211 let u = fem.solve(&forces, &fixed).unwrap();
212 assert_relative_eq!(u[1], 5e-4, epsilon = 1e-8);
214 }
215
216 #[test]
217 fn test_uniform_bar_convergence() {
218 let e = 200e9;
220 let a = 1e-4;
221 let l = 5.0;
222 let f = 50000.0;
223 let analytical = f * l / (e * a);
224
225 for n in [1, 5, 10, 50] {
227 let fem = FemAssembler1D::uniform_bar(e, a, l, n);
228 let mut forces = vec![0.0; n + 1];
229 forces[n] = f;
230 let u = fem.solve(&forces, &[0]).unwrap();
231 assert_relative_eq!(u[n], analytical, epsilon = 1e-6);
232 }
233 }
234
235 #[test]
236 fn test_stress_uniform_in_uniform_bar() {
237 let e = 200e9;
238 let a = 1e-4;
239 let l = 5.0;
240 let f = 50000.0;
241 let fem = FemAssembler1D::uniform_bar(e, a, l, 5);
242 let mut forces = vec![0.0; 6];
243 forces[5] = f;
244 let u = fem.solve(&forces, &[0]).unwrap();
245 let stresses = fem.element_stresses(&u);
246 let expected_stress = f / a;
248 for &s in &stresses {
249 assert_relative_eq!(s, expected_stress, epsilon = 1e-3);
250 }
251 }
252
253 #[test]
254 fn test_strain_energy_conservation() {
255 let e = 200e9;
256 let a = 1e-4;
257 let l = 3.0;
258 let f = 30000.0;
259 let fem = FemAssembler1D::uniform_bar(e, a, l, 10);
260 let mut forces = vec![0.0; 11];
261 forces[10] = f;
262 let u = fem.solve(&forces, &[0]).unwrap();
263 let se = fem.total_strain_energy(&u);
264 let expected = f * f * l / (2.0 * e * a);
266 assert_relative_eq!(se, expected, epsilon = 1e-2);
267 }
268
269 #[test]
270 fn test_equilibrium_check() {
271 let e = 200e9;
272 let a = 1e-4;
273 let l = 2.0;
274 let fem = FemAssembler1D::uniform_bar(e, a, l, 4);
275 let f_end = 10000.0;
276 let mut forces = vec![0.0; 5];
277 forces[4] = f_end;
278 let u = fem.solve(&forces, &[0]).unwrap();
279
280 let k = fem.assemble_global_stiffness();
282 for i in 1..5 {
283 let mut ku_i = 0.0_f64;
284 for j in 0..5 {
285 ku_i += k[(i, j)] * u[j];
286 }
287 assert_relative_eq!(ku_i, forces[i], epsilon = 0.1);
288 }
289 }
290}