Skip to main content

solid_mechanics/
beam.rs

1//! Euler-Bernoulli beam mechanics — deflection, bending moments, shear forces.
2
3use serde::{Deserialize, Serialize};
4
5/// Euler-Bernoulli beam model for a prismatic beam.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct EulerBernoulliBeam {
8    /// Young's modulus (Pa)
9    pub e: f64,
10    /// Second moment of area (m⁴)
11    pub i: f64,
12    /// Beam length (m)
13    pub length: f64,
14    /// Applied loads
15    pub loads: Vec<BeamLoad>,
16}
17
18/// A load applied to the beam.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub enum BeamLoad {
21    /// Point load: (magnitude in N, position along beam in m)
22    Point { p: f64, a: f64 },
23    /// Uniformly distributed load: (magnitude in N/m, starts at, ends at)
24    Distributed { w: f64, a_start: f64, a_end: f64 },
25    /// Point moment: (magnitude in N·m, position along beam in m)
26    Moment { m: f64, a: f64 },
27}
28
29impl EulerBernoulliBeam {
30    /// Create a new simply-supported beam.
31    pub fn new(e: f64, i: f64, length: f64) -> Self {
32        Self { e, i, length, loads: Vec::new() }
33    }
34
35    /// Flexural rigidity EI.
36    pub fn flexural_rigidity(&self) -> f64 {
37        self.e * self.i
38    }
39
40    /// Add a point load.
41    pub fn with_point_load(mut self, p: f64, a: f64) -> Self {
42        self.loads.push(BeamLoad::Point { p, a });
43        self
44    }
45
46    /// Add a uniformly distributed load.
47    pub fn with_distributed_load(mut self, w: f64, a_start: f64, a_end: f64) -> Self {
48        self.loads.push(BeamLoad::Distributed { w, a_start, a_end });
49        self
50    }
51
52    /// Add a point moment.
53    pub fn with_moment(mut self, m: f64, a: f64) -> Self {
54        self.loads.push(BeamLoad::Moment { m, a });
55        self
56    }
57
58    /// Deflection at position x for a simply-supported beam with a single centered point load P.
59    /// v(x) = Px(L² - 4a²)/(48EI) for a = L/2
60    /// General: v(x) = Pb(L² - b²)^(1/2) x / (9√3 EI L) for max deflection
61    ///
62    /// For a simply supported beam with point load P at distance a from left support:
63    /// v(x) = Pb/(6LEI) * [L²/b - b²)x - x³/L] for x ≤ a  (simplified version)
64    pub fn deflection_simply_supported_point(&self, x: f64, p: f64, a: f64) -> f64 {
65        let l = self.length;
66        let ei = self.flexural_rigidity();
67        let b = l - a;
68        if x <= a {
69            p * b / (6.0 * l * ei) * ((l * l - b * b) * x - x.powi(3))
70        } else {
71            let x2 = l - x; // mirror
72            p * a / (6.0 * l * ei) * ((l * l - a * a) * x2 - x2.powi(3))
73        }
74    }
75
76    /// Maximum deflection for a simply-supported beam with centered point load P.
77    /// δ_max = PL³ / (48EI)
78    pub fn max_deflection_centered_point(&self, p: f64) -> f64 {
79        let l = self.length;
80        let ei = self.flexural_rigidity();
81        p * l.powi(3) / (48.0 * ei)
82    }
83
84    /// Bending moment at position x for a simply-supported beam with point load P at a.
85    /// M(x) = Pb·x/L for x ≤ a, M(x) = Pa(L-x)/L for x > a
86    pub fn bending_moment_simply_supported_point(&self, x: f64, p: f64, a: f64) -> f64 {
87        let l = self.length;
88        let b = l - a;
89        if x <= a {
90            p * b * x / l
91        } else {
92            p * a * (l - x) / l
93        }
94    }
95
96    /// Shear force at position x for a simply-supported beam with point load P at a.
97    pub fn shear_force_simply_supported_point(&self, x: f64, p: f64, a: f64) -> f64 {
98        let l = self.length;
99        let b = l - a;
100        if x < a {
101            p * b / l
102        } else {
103            -p * a / l
104        }
105    }
106
107    /// Maximum bending moment (at load point) for simply-supported beam with point load at a.
108    pub fn max_bending_moment_point(&self, p: f64, a: f64) -> f64 {
109        let l = self.length;
110        let b = l - a;
111        p * a * b / l
112    }
113
114    /// Deflection at position x for a cantilever beam with point load P at the free end.
115    /// v(x) = P/(6EI) * (3Lx² - x³)
116    pub fn deflection_cantilever_point_end(&self, x: f64, p: f64) -> f64 {
117        let l = self.length;
118        let ei = self.flexural_rigidity();
119        p / (6.0 * ei) * (3.0 * l * x * x - x.powi(3))
120    }
121
122    /// Maximum deflection of cantilever with end point load: δ = PL³ / (3EI)
123    pub fn max_deflection_cantilever_point_end(&self, p: f64) -> f64 {
124        let l = self.length;
125        let ei = self.flexural_rigidity();
126        p * l.powi(3) / (3.0 * ei)
127    }
128
129    /// Bending moment at position x for cantilever with end point load.
130    /// M(x) = P(L - x)
131    pub fn bending_moment_cantilever_point_end(&self, x: f64, p: f64) -> f64 {
132        let l = self.length;
133        p * (l - x)
134    }
135
136    /// Deflection at position x for a cantilever with uniformly distributed load w (N/m).
137    /// v(x) = w/(24EI) * (x⁴ - 4Lx³ + 6L²x²)
138    pub fn deflection_cantilever_udl(&self, x: f64, w: f64) -> f64 {
139        let l = self.length;
140        let ei = self.flexural_rigidity();
141        w / (24.0 * ei) * (x.powi(4) - 4.0 * l * x.powi(3) + 6.0 * l * l * x * x)
142    }
143
144    /// Max deflection of cantilever with UDL: δ = wL⁴/(8EI)
145    pub fn max_deflection_cantilever_udl(&self, w: f64) -> f64 {
146        let l = self.length;
147        let ei = self.flexural_rigidity();
148        w * l.powi(4) / (8.0 * ei)
149    }
150
151    /// Bending stress at position (x, y): σ = My/I
152    pub fn bending_stress(&self, moment: f64, y: f64) -> f64 {
153        moment * y / self.i
154    }
155
156    /// Maximum bending stress for given moment and section height h: σ = M(h/2)/I
157    pub fn max_bending_stress(&self, moment: f64, section_height: f64) -> f64 {
158        self.bending_stress(moment, section_height / 2.0)
159    }
160
161    /// Reaction forces for simply-supported beam with point load P at a.
162    /// Returns (R_left, R_right).
163    pub fn reactions_simply_supported_point(&self, p: f64, a: f64) -> (f64, f64) {
164        let l = self.length;
165        let b = l - a;
166        (p * b / l, p * a / l)
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use approx::assert_relative_eq;
174
175    #[test]
176    fn test_simply_supported_centered_deflection() {
177        let beam = EulerBernoulliBeam::new(200e9, 1e-4, 5.0);
178        // P at center: δ = PL³/(48EI)
179        let p = 10000.0;
180        let expected = p * 125.0 / (48.0 * 200e9 * 1e-4);
181        let actual = beam.max_deflection_centered_point(p);
182        assert_relative_eq!(actual, expected, epsilon = 1e-10);
183    }
184
185    #[test]
186    fn test_cantilever_end_deflection() {
187        let beam = EulerBernoulliBeam::new(200e9, 1e-4, 3.0);
188        let p = 5000.0;
189        let expected = p * 27.0 / (3.0 * 200e9 * 1e-4);
190        let actual = beam.max_deflection_cantilever_point_end(p);
191        assert_relative_eq!(actual, expected, epsilon = 1e-10);
192    }
193
194    #[test]
195    fn test_bending_moment_simply_supported() {
196        let beam = EulerBernoulliBeam::new(200e9, 1e-4, 4.0);
197        let p = 10000.0;
198        let a = 2.0;
199        let m = beam.max_bending_moment_point(p, a);
200        assert_relative_eq!(m, 10000.0, epsilon = 1e-6);
201    }
202
203    #[test]
204    fn test_reactions_sum_to_load() {
205        let beam = EulerBernoulliBeam::new(200e9, 1e-4, 4.0);
206        let p = 10000.0;
207        let (rl, rr) = beam.reactions_simply_supported_point(p, 1.0);
208        assert_relative_eq!(rl + rr, p);
209        assert_relative_eq!(rl, 7500.0);
210        assert_relative_eq!(rr, 2500.0);
211    }
212
213    #[test]
214    fn test_bending_stress() {
215        let beam = EulerBernoulliBeam::new(200e9, 1e-4, 4.0);
216        // σ = My/I = 5000 * 0.05 / 1e-4 = 2_500_000
217        let stress = beam.bending_stress(5000.0, 0.05);
218        assert_relative_eq!(stress, 2_500_000.0);
219    }
220
221    #[test]
222    fn test_cantilever_udl_deflection() {
223        let beam = EulerBernoulliBeam::new(200e9, 1e-4, 2.0);
224        let w = 5000.0;
225        let expected = w * 16.0 / (8.0 * 200e9 * 1e-4);
226        let actual = beam.max_deflection_cantilever_udl(w);
227        assert_relative_eq!(actual, expected, epsilon = 1e-10);
228    }
229
230    #[test]
231    fn test_deflection_zero_at_supports() {
232        let beam = EulerBernoulliBeam::new(200e9, 1e-4, 5.0);
233        let p = 10000.0;
234        let a = 2.5;
235        let d0 = beam.deflection_simply_supported_point(0.0, p, a);
236        let d_l = beam.deflection_simply_supported_point(5.0, p, a);
237        assert_relative_eq!(d0, 0.0, epsilon = 1e-10);
238        assert_relative_eq!(d_l, 0.0, epsilon = 1e-10);
239    }
240}