1use crate::material_grade::rebar_yield_strength;
8use crate::model::{ElementData, Material, Model, Section};
9use crate::rc_capacity::{rc_mu_simple, RcCapacityInput};
10use crate::rc_rebar_geom::rebar_effective_depth;
11use crate::section_shape::{bar_set_area, SectionShape};
12
13#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct FlexuralStrengthFactors {
19 pub steel: f64,
21 pub rebar: f64,
23}
24
25impl FlexuralStrengthFactors {
26 pub const NOMINAL: Self = Self {
28 steel: 1.0,
29 rebar: 1.0,
30 };
31}
32
33pub fn section_elastic_modulus(sec: &Section) -> f64 {
35 let depth = sec.depth.max(sec.width);
36 let i_gross = sec.iz.max(sec.iy);
37 if depth > 0.0 {
38 i_gross / (depth / 2.0)
39 } else {
40 0.0
41 }
42}
43
44pub fn member_flexural_yield_moment(
53 elem: &ElementData,
54 model: &Model,
55 factors: FlexuralStrengthFactors,
56) -> f64 {
57 let sec = elem.section.and_then(|sid| model.sections.get(sid.index()));
58 let mat = model.element_material(elem);
59 let ze = sec.map(section_elastic_modulus).unwrap_or(0.0);
60 let fy = mat.and_then(|m| m.fy);
61 match sec.and_then(|s| s.shape.as_ref()) {
62 Some(SectionShape::RcRect { rebar, d, .. }) | Some(SectionShape::RcCircle { rebar, d }) => {
63 rc_flexural_yield_moment(elem, model, mat, rebar, *d, ze, factors.rebar)
64 }
65 Some(shape) => {
66 let sy = fy.unwrap_or(235.0) * factors.steel;
67 match shape.plastic_modulus_strong() {
68 Some(zp) => sy * zp,
69 None => sy * ze,
70 }
71 }
72 None => fy.unwrap_or(235.0) * factors.steel * ze,
73 }
74}
75
76fn rc_flexural_yield_moment(
77 elem: &ElementData,
78 model: &Model,
79 mat: Option<&Material>,
80 rebar: &crate::section_shape::RcRebar,
81 d: f64,
82 ze: f64,
83 rebar_factor: f64,
84) -> f64 {
85 let rebar_mat = model.element_rebar_material(elem);
86 let sy = rebar_yield_strength(rebar_mat)
87 .or_else(|| mat.and_then(|m| m.fy))
88 .unwrap_or(345.0)
89 * rebar_factor;
90 let fc = mat.and_then(|m| m.fc).unwrap_or(0.0);
91 let at = bar_set_area(&rebar.main_x) / 2.0;
92 let d_eff = rebar_effective_depth(d, rebar);
93 let my = rc_mu_simple(&RcCapacityInput {
94 b: 1.0,
95 d,
96 at,
97 d_eff,
98 sigma_y: sy,
99 fc: fc.max(1e-9),
100 pw: 0.0,
101 sigma_wy: 0.0,
102 clear_span: 1.0,
103 sigma_0: 0.0,
104 });
105 if my > 0.0 {
106 my
107 } else {
108 sy * ze
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::member_flexural_yield_moment;
115 use super::FlexuralStrengthFactors;
116 use crate::ids::{ElemId, MaterialId, SectionId};
117 use crate::model::{
118 ElementData, ElementKind, EndCondition, ForceRegime, LocalAxis, Material, MaterialCategory,
119 Model, Node, RigidZone,
120 };
121 use crate::section_shape::SectionShape;
122
123 #[test]
124 fn steel_yield_uses_plastic_modulus_and_strength_factor() {
125 let mut model = Model::default();
126 model.materials.push(Material {
127 id: MaterialId(0),
128 name: "SN400".into(),
129 category: MaterialCategory::Steel,
130 young: 205_000.0,
131 poisson: 0.3,
132 density: 7.85e-9,
133 shear: None,
134 fc: None,
135 fy: Some(235.0),
136 concrete_class: Default::default(),
137 strength_factor: Some(1.1),
138 });
139 let mut sec = SectionShape::SteelH {
140 height: 400.0,
141 width: 200.0,
142 web_thick: 9.0,
143 flange_thick: 16.0,
144 }
145 .to_section(SectionId(0), "H-400".into());
146 sec.material = Some(MaterialId(0));
147 model.sections.push(sec);
148 model.nodes.extend([
149 Node {
150 id: crate::ids::NodeId(0),
151 coord: [0.0, 0.0, 0.0],
152 restraint: Default::default(),
153 mass: None,
154 story: None,
155 support_spring: None,
156 },
157 Node {
158 id: crate::ids::NodeId(1),
159 coord: [3000.0, 0.0, 0.0],
160 restraint: Default::default(),
161 mass: None,
162 story: None,
163 support_spring: None,
164 },
165 ]);
166 let elem = ElementData {
167 id: ElemId(0),
168 kind: ElementKind::Beam,
169 nodes: smallvec::smallvec![crate::ids::NodeId(0), crate::ids::NodeId(1)],
170 section: Some(SectionId(0)),
171 local_axis: LocalAxis {
172 ref_vector: [0.0, 0.0, 1.0],
173 },
174 end_cond: [EndCondition::Fixed, EndCondition::Fixed],
175 force_regime: ForceRegime::Auto,
176 rigid_zone: RigidZone::default(),
177 plastic_zone: None,
178 spring: None,
179 };
180 let my = member_flexural_yield_moment(
181 &elem,
182 &model,
183 FlexuralStrengthFactors {
184 steel: 1.1,
185 rebar: 1.0,
186 },
187 );
188 let sec = &model.sections[0];
189 let zp = sec
190 .shape
191 .as_ref()
192 .unwrap()
193 .plastic_modulus_strong()
194 .unwrap();
195 assert!((my - 235.0 * 1.1 * zp).abs() < 1e-3 * my.max(1.0));
196 }
197}