Skip to main content

squid_n_core/
material_grade.rs

1//! 材料グレード名(`SS400`・`SD345`・`Fc21` 等)から規格値を解決する対応表。
2//!
3//! 鋼材の基準強度 F・鉄筋の基準強度・コンクリート設計基準強度 Fc の
4//! 「名称 → 数値」対応は本モジュールに一本化する(H12 建告第2464号、
5//! JIS 規格、大臣認定品の基準強度)。設計計算(squid-n-design-jp)・
6//! ST-Bridge 取込(squid-n-io)・UI プリセット(squid-n-app)は
7//! いずれも本対応表を参照し、独立の表を持たない。
8//!
9//! - [`steel_f_value`] — 鋼材の基準強度 F(完全一致、板厚区分対応)
10//! - [`steel_f_value_prefix`] — 同(前方一致。`SN490B` → `SN490` 等)
11//! - [`rebar_f_value`] — 鉄筋の基準強度(`SD345` → 345 等)
12//! - [`rebar_grade_f_value`] — 同(高強度鉄筋の製品名を含む。`KH785` → 785 等)
13//! - [`rebar_yield_strength`] / [`shear_rebar_yield_strength`] — 配筋の材質からの σy・σwy 解決
14//! - [`is_high_strength_shear_grade`] — 高強度せん断補強筋かどうかの判定
15//! - [`parse_concrete_fc`] — コンクリート `FcXX` 名称の解釈
16//! - [`material_presets`] — UI に提示する標準材料プリセット一覧
17
18use crate::model::MaterialCategory;
19use crate::section_shape::{concrete_young_modulus_gamma, E_STEEL};
20use crate::units::{
21    concrete_unit_weight_kn_m3, to_internal::mass_density_from_unit_weight_kn_m3, ConcreteClass,
22    ConcreteComposition, STEEL_UNIT_WEIGHT_KN_M3,
23};
24
25/// 板厚 2 区分(`t<=40` / `40<t<=100`)の F 値を返す。
26/// 100mm 超は規定がないため最終区分値をそのまま用いる(非保守的になり得る)。
27fn bucket2(t: f64, le40: f64, gt40: f64) -> f64 {
28    if t <= 40.0 {
29        le40
30    } else {
31        gt40
32    }
33}
34
35/// 板厚 3 区分(`t<=40` / `40<t<=75` / `75<t<=100`)の F 値を返す(SM520 用)。
36fn bucket3(t: f64, le40: f64, le75: f64, gt75: f64) -> f64 {
37    if t <= 40.0 {
38        le40
39    } else if t <= 75.0 {
40        le75
41    } else {
42        gt75
43    }
44}
45
46/// 鋼材グレード一覧(前方一致の探索対象。`SN490B` のような接尾辞付き名称を
47/// 解決するため、[`steel_f_value_prefix`] は最長一致のグレードを選ぶ)。
48pub const STEEL_GRADES: &[&str] = &[
49    // JIS 規格品
50    "SS400", "SS490", "SM400", "SM490", "SM520", "SN400", "SN490", "STK400", "STK490", "STKN400",
51    "STKN490", "STKR400", "STKR490", "SNR400", "SNR490", "SSC400", "SWH400",
52    // 冷間成形角形鋼管(大臣認定品。BCR235 は旧グレードの互換)
53    "BCR235", "BCR295", "BCP235", "BCP325",
54    // 建築構造用 TMCP 鋼材(HBL 等の大臣認定品の一般名。板厚 40mm 超でも F 低減なし)
55    "TMCP325", "TMCP355", "TMCP385", "TMCP440",
56    // 建築構造用高性能 590N/mm² 鋼材・建築構造用低降伏点鋼材
57    "SA440", "LY100", "LY225",
58];
59
60/// 鋼材の基準強度 F [N/mm²](完全一致、板厚 [mm] 区分対応。H12 建告第2464号ほか)。
61///
62/// JIS 規格品は厚さ 40mm 以下 / 40mm 超 100mm 以下の 2 区分
63/// (SM520 のみ 40/75/100mm の 3 区分)。大臣認定品(BCR/BCP・TMCP・SA440・LY)は
64/// 板厚区分を持たない。100mm を超える板厚は規定がないため最終区分値を
65/// そのまま用いる(非保守的になり得るため実運用では要確認)。
66///
67/// 戻り値は F 値。長期許容引張・圧縮・曲げ `ft = F/1.5`、
68/// 長期許容せん断 `fs = F/(1.5·√3)`。短期は長期の 1.5 倍(=F, F/√3)。
69pub fn steel_f_value(grade: &str, thickness: f64) -> Option<f64> {
70    match grade {
71        // 400 N/mm² 級(F=235/215)
72        "SS400" | "SM400" | "SN400" | "STK400" | "STKN400" | "STKR400" | "SNR400" | "SSC400"
73        | "SWH400" => Some(bucket2(thickness, 235.0, 215.0)),
74        // SS490(F=275/255)
75        "SS490" => Some(bucket2(thickness, 275.0, 255.0)),
76        // 490 N/mm² 級(F=325/295)
77        "SM490" | "SN490" | "STK490" | "STKN490" | "STKR490" | "SNR490" => {
78            Some(bucket2(thickness, 325.0, 295.0))
79        }
80        // 520 N/mm² 級(F=355/335/325)
81        "SM520" => Some(bucket3(thickness, 355.0, 335.0, 325.0)),
82        // 冷間成形角形鋼管(大臣認定品。板厚区分なし)
83        "BCR295" => Some(295.0),
84        "BCR235" => Some(235.0),
85        "BCP235" => Some(235.0),
86        "BCP325" => Some(325.0),
87        // 建築構造用 TMCP 鋼材(板厚 40mm 超 100mm 以下でも F 低減なし)
88        "TMCP325" => Some(325.0),
89        "TMCP355" => Some(355.0),
90        "TMCP385" => Some(385.0),
91        "TMCP440" => Some(440.0),
92        // 建築構造用高性能 590N/mm² 鋼材
93        "SA440" => Some(440.0),
94        // 建築構造用低降伏点鋼材(基準強度 F: 100N 級=80、225N 級=205)
95        "LY100" => Some(80.0),
96        "LY225" => Some(205.0),
97        _ => None,
98    }
99}
100
101/// 鋼材の基準強度 F [N/mm²](前方一致、板厚 [mm] 区分対応)。
102///
103/// `SN490B`・`SM490YA` のような JIS 種別記号付きの名称を、
104/// [`STEEL_GRADES`] の最長一致で解決する(`SN490B` は `SN400` ではなく
105/// `SN490` に一致)。未知の名称は `None`。
106pub fn steel_f_value_prefix(name: &str, thickness: f64) -> Option<f64> {
107    STEEL_GRADES
108        .iter()
109        .filter(|g| name.starts_with(*g))
110        .max_by_key(|g| g.len())
111        .and_then(|g| steel_f_value(g, thickness))
112}
113
114/// 保有水平耐力計算(プッシュオーバー)用の材料強度割増係数。
115///
116/// 材料強度の基準強度は表の数値の 1.1 倍以下(JIS 規格品・大臣認定品)、
117/// ただし 590N 級(SA440・TMCP440。HBL®440/G440 等の認定条件)は 1.05 倍以下と
118/// できる規定(H12 建告第2464号の運用・各認定条件)に基づく。
119/// 名称から鋼材グレードを解決できない材料(直接入力材料)は割増しない(1.0)。
120///
121/// 本係数は保有水平耐力計算(プッシュオーバー)の部材耐力算定にのみ用い、
122/// 許容応力度計算(一次設計)には適用しない。
123pub fn steel_material_strength_factor(name: &str) -> f64 {
124    let Some(grade) = STEEL_GRADES
125        .iter()
126        .filter(|g| name.starts_with(*g))
127        .max_by_key(|g| g.len())
128    else {
129        return 1.0;
130    };
131    match *grade {
132        // 590N 級(建築構造用高性能 590N/mm² 鋼材・TMCP 590N 級)は 1.05 倍
133        "SA440" | "TMCP440" => 1.05,
134        _ => 1.1,
135    }
136}
137
138/// 保有水平耐力計算(プッシュオーバー)で、材料の降伏強度 fy を
139/// **鋼材**として用いる文脈(鋼材断面の集中ばね・純鋼材ファイバー・
140/// 曲げヒンジ・せん断降伏閾値)の材料強度割増係数。
141///
142/// 直接入力の割増係数([`crate::model::Material::strength_factor`])があれば
143/// それを優先し、なければ材料名から自動判定する
144/// ([`steel_material_strength_factor`]: 鋼材グレード=1.1、590N 級=1.05、
145/// 名称から解決できない材料=1.0)。
146pub fn material_strength_factor_steel(mat: &crate::model::Material) -> f64 {
147    mat.strength_factor
148        .unwrap_or_else(|| steel_material_strength_factor(&mat.name))
149}
150
151/// 保有水平耐力計算(プッシュオーバー)で、材料の降伏強度 fy を
152/// **RC 主筋**として用いる文脈(RC 断面の集中ばね・主筋ファイバー・
153/// 曲げヒンジ・せん断降伏の主筋 σy)の材料強度割増係数。
154///
155/// 直接入力の割増係数([`crate::model::Material::strength_factor`])があれば
156/// それを優先し、なければ 1.1(鉄筋の材料強度は基準強度の 1.1 倍以下と
157/// できる規定)。fy 未設定で既定値(SD345 相当の 345)を用いる場合にも
158/// 同係数を乗じる。**せん断補強筋は割増対象外**(本係数を用いないこと)。
159pub fn material_strength_factor_rebar(mat: &crate::model::Material) -> f64 {
160    mat.strength_factor.unwrap_or(1.1)
161}
162
163/// 鉄筋の基準強度 [N/mm²](H12 建告第2464号)。
164///
165/// 異形鉄筋 `SD` ・丸鋼 `SR` は続く数値が基準強度を表す
166/// (`SD295A`・`SD345`・`SR235` 等)。未知の名称は `None`。
167pub fn rebar_f_value(name: &str) -> Option<f64> {
168    let rest = name
169        .strip_prefix("SD")
170        .or_else(|| name.strip_prefix("SR"))?;
171    let digits: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect();
172    digits.parse::<f64>().ok().filter(|v| *v > 0.0)
173}
174
175/// 鉄筋のグレード名から降伏点(基準強度)[N/mm²] を解決する。
176///
177/// **取り込みの境界専用**。ST-Bridge の `strength_main` などグレード名しか持たない
178/// 入力から材料を起こすときに使う。名称末尾の数値を強度とみなす推定を含むため、
179/// 内部の強度解決には用いない([`rebar_yield_strength`])。
180///
181/// [`rebar_f_value`](異形鉄筋 `SD`・丸鋼 `SR` の接頭辞規則)で解けない名称は、
182/// 大臣認定品の高強度鉄筋として**名称末尾の数値**を強度とみなす
183/// (`USD685` → 685、`KH785`(スーパーフープ)→ 785、`SBPD1275` → 1275 等。
184/// いずれも製品名の数値が降伏点 [N/mm²] を表す命名規則)。
185/// 数値を含まない名称は `None`。
186pub fn rebar_grade_f_value(name: &str) -> Option<f64> {
187    let name = name.trim();
188    if let Some(v) = rebar_f_value(name) {
189        return Some(v);
190    }
191    let digits: String = name
192        .chars()
193        .rev()
194        .take_while(|c| c.is_ascii_digit())
195        .collect::<Vec<_>>()
196        .into_iter()
197        .rev()
198        .collect();
199    digits.parse::<f64>().ok().filter(|v| *v > 0.0)
200}
201
202/// RC 主筋の降伏点 σy [N/mm²] を解決する。
203///
204/// 断面の主筋材料([`crate::model::Section::rebar_material`])の `fy` を用いる。
205/// 未割当・`fy` 未設定は `None` を返し、**既定値で埋めない**(未入力のまま既定
206/// 345 N/mm² を用いると、SD295 の部材で耐力を過大評価する=危険側になるため。
207/// 非線形解析は `None` を入力不備として停止する)。
208///
209/// グレード名から強度を推定するのは**取り込みの境界だけ**の役目とし
210/// ([`rebar_grade_f_value`])、内部では材料テーブルの値だけを見る。名称の
211/// 打ち間違いがそのまま強度として通ることを構造的に防ぐためである。
212pub fn rebar_yield_strength(rebar_mat: Option<&crate::model::Material>) -> Option<f64> {
213    rebar_mat.and_then(|m| m.fy).filter(|v| *v > 0.0)
214}
215
216/// せん断補強筋の降伏点 σwy [N/mm²] を解決する。
217///
218/// 断面のせん断補強筋材料([`crate::model::Section::shear_rebar_material`])の
219/// `fy` を用いる。未設定は `None` を返し、呼び出し側は普通強度せん断補強筋の
220/// SD295 相当(295)を既定とする(規格上の最小グレードであり、実際がより高強度でも
221/// 耐力を過小評価する側=安全側に外れる)。
222pub fn shear_rebar_yield_strength(mat: Option<&crate::model::Material>) -> Option<f64> {
223    mat.and_then(|m| m.fy).filter(|v| *v > 0.0)
224}
225
226/// せん断補強筋の材質が未設定の場合に用いる降伏点 σwy [N/mm²](SD295 相当)。
227pub const SHEAR_REBAR_DEFAULT_FY: f64 = 295.0;
228
229/// 材料の名称が**高強度**せん断補強筋か(普通強度の異形鉄筋・丸鋼は false)。
230///
231/// せん断補強筋の材料欄には普通強度(`SD295A`・`SD345` 等)も割り当てられるため、
232/// 「材料が割り当てられている=高強度品」とみなしてはならない(普通強度を高強度品の
233/// 表で評価すると w_ft を 590 N/mm² と大幅に過大評価し**危険側**になる)。
234/// JIS の異形棒鋼 `SD*`・丸鋼 `SR*` は普通強度として false を返し、それ以外の
235/// 製品名(`UB785`・`KH785`・`SBPD1275`・`USD685` 等)を高強度として扱う。
236///
237/// 許容応力度計算(`squid-n-design-jp` の w_ft・pw 上限)と、耐震壁の終局せん断強度
238/// (`squid-n-element` の Qu 係数 0.053 / 0.068)の両方が本判定を用いる。
239pub fn is_high_strength_shear_grade(name: &str) -> bool {
240    let g = name.trim().to_uppercase();
241    if g.is_empty() {
242        return false;
243    }
244    !(g.starts_with("SD") || g.starts_with("SR"))
245}
246
247/// 断面のせん断補強筋材料が高強度品かどうかを判定する。
248/// 材料が未割当のときは普通強度(false)として扱う。
249pub fn is_high_strength_shear_material(mat: Option<&crate::model::Material>) -> bool {
250    mat.is_some_and(|m| is_high_strength_shear_grade(&m.name))
251}
252
253/// UI で選択できる主筋のグレード(表示順。JIS G 3112 の異形棒鋼と高強度異形棒鋼)。
254pub const MAIN_REBAR_GRADES: &[&str] = &["SD295A", "SD295B", "SD345", "SD390", "SD490", "USD685"];
255
256/// UI で選択できる鉄筋の呼び名サイズ(`D10`〜`D41`。表示順)。
257///
258/// 数値は**呼び名の数値**であり、`BarSet::dia` / `ShearBar::dia` はこの値で保持する
259/// (許容応力度の径依存判定(`dia >= 29.0` = D29 以上)と単位を揃えるため。
260/// 公称直径 [mm] ではない)。
261pub const REBAR_NOMINAL_SIZES: &[f64] = &[
262    10.0, 13.0, 16.0, 19.0, 22.0, 25.0, 29.0, 32.0, 35.0, 38.0, 41.0,
263];
264
265/// UI で選択できるせん断補強筋のグレード(表示順)。
266/// 普通強度(`SD*`)に加え、大臣認定の高強度せん断補強筋を含む。
267pub const SHEAR_REBAR_GRADES: &[&str] = &[
268    "SD295A", "SD295B", "SD345", "SD390", "KH785", "UB785", "KSS785", "SHD685", "SPR785", "MK785",
269    "SBPD1275",
270];
271
272/// コンクリートのグレード名 `FcXX` から設計基準強度 Fc [N/mm²] を取り出す。
273/// 大文字小文字を問わず `Fc` で始まり、続く数値を Fc とする(`Fc21`→21)。
274pub fn parse_concrete_fc(name: &str) -> Option<f64> {
275    let rest = name
276        .strip_prefix("Fc")
277        .or_else(|| name.strip_prefix("FC"))
278        .or_else(|| name.strip_prefix("fc"))?;
279    let digits: String = rest
280        .trim()
281        .chars()
282        .take_while(|c| c.is_ascii_digit() || *c == '.')
283        .collect();
284    digits.parse::<f64>().ok().filter(|v| *v > 0.0)
285}
286
287/// グレード名から材料の区分を推定する。
288///
289/// **ST-Bridge の取込専用**である。ST-Bridge は材料を「グレード名」で表し
290/// (`Fc21`・`SN400B`・`SD345`)、名称が物性を一意に定める規格化された体系なので、
291/// 名前からの推定が成り立つ。
292///
293/// モデル内部では区分を [`crate::model::Material::category`] として明示的に持ち、
294/// 構造種別の判定に名前は用いない(`crate::structure_kind`)。利用者が任意の名前を
295/// 付けた材料でも正しく扱うためである。
296///
297/// 判定できない名称は `None` を返し、呼び出し側が既定を決める。
298pub fn category_of_grade(name: &str) -> Option<MaterialCategory> {
299    let upper = name.trim().to_uppercase();
300    if upper.starts_with("FC") {
301        return Some(MaterialCategory::Concrete);
302    }
303    if upper.starts_with("SD") || upper.starts_with("SR") || upper.starts_with("KH") {
304        return Some(MaterialCategory::Rebar);
305    }
306    const STEEL_PREFIX: &[&str] = &["SS", "SN", "SM", "STK", "ST", "SA", "BC", "TMCP", "LY"];
307    if STEEL_PREFIX.iter().any(|p| upper.starts_with(p)) {
308        return Some(MaterialCategory::Steel);
309    }
310    None
311}
312
313/// UI に提示する標準材料プリセット(内部単位系 N-mm-s。密度は ton/mm³)。
314#[derive(Clone, Debug, PartialEq)]
315pub struct MaterialPreset {
316    pub name: &'static str,
317    pub category: MaterialCategory,
318    /// ヤング係数 E [N/mm²]
319    pub young: f64,
320    /// ポアソン比 ν
321    pub poisson: f64,
322    /// 質量密度 [ton/mm³]
323    pub density: f64,
324    /// コンクリート設計基準強度 Fc [N/mm²](鋼材・鉄筋は None)
325    pub fc: Option<f64>,
326    /// 基準強度 F(板厚 40mm 以下)/鉄筋降伏点 [N/mm²](コンクリートは None)
327    pub fy: Option<f64>,
328}
329
330/// UI プリセットとして提示する鋼材グレード(表示順)。
331const PRESET_STEEL: &[&str] = &[
332    "SS400", "SN400", "SM400", "SM490", "SN490", "BCR295", "BCP235", "TMCP325", "TMCP355",
333    "TMCP385", "TMCP440", "SA440", "LY100", "LY225",
334];
335
336/// UI プリセットとして提示する鉄筋グレード(表示順)。
337const PRESET_REBAR: &[&str] = &["SD295", "SD345", "SD390"];
338
339/// UI プリセットとして提示するコンクリート強度(表示順)。
340const PRESET_CONCRETE_FC: &[f64] = &[
341    18.0, 21.0, 24.0, 27.0, 30.0, 33.0, 36.0, 40.0, 42.0, 45.0, 50.0, 55.0, 60.0,
342];
343
344/// プリセットのコンクリート名(`Fc18`〜`Fc60`)。`PRESET_CONCRETE_FC` と同順。
345const PRESET_CONCRETE_NAMES: &[&str] = &[
346    "Fc18", "Fc21", "Fc24", "Fc27", "Fc30", "Fc33", "Fc36", "Fc40", "Fc42", "Fc45", "Fc50", "Fc55",
347    "Fc60",
348];
349
350/// 標準材料プリセット一覧を生成する。
351///
352/// - 鋼材・鉄筋: E=205000、ν=0.3、γs=77 kN/m³(≒7.85 t/m³)。
353///   `fy` は基準強度 F(板厚 40mm 以下)。設計計算では名称から
354///   [`steel_f_value_prefix`] で板厚区分込みの F を再解決する。
355/// - コンクリート: ν=0.2。E は Ec=3.35·10⁴·(γ/24)²·(Fc/60)^(1/3)
356///   (γ は Fc 帯に応じた普通コンクリートの気乾単位体積重量)。
357///   密度は単位体積重量表の γRC(鉄筋込み)から導出する。
358pub fn material_presets() -> Vec<MaterialPreset> {
359    let steel_density = mass_density_from_unit_weight_kn_m3(STEEL_UNIT_WEIGHT_KN_M3);
360    let mut out = Vec::new();
361    for &name in PRESET_STEEL {
362        out.push(MaterialPreset {
363            name,
364            category: MaterialCategory::Steel,
365            young: E_STEEL,
366            poisson: 0.3,
367            density: steel_density,
368            fc: None,
369            fy: steel_f_value(name, 0.0),
370        });
371    }
372    for &name in PRESET_REBAR {
373        out.push(MaterialPreset {
374            name,
375            category: MaterialCategory::Rebar,
376            young: E_STEEL,
377            poisson: 0.3,
378            density: steel_density,
379            fc: None,
380            fy: rebar_f_value(name),
381        });
382    }
383    for (&fc, &name) in PRESET_CONCRETE_FC.iter().zip(PRESET_CONCRETE_NAMES) {
384        let gamma_c =
385            concrete_unit_weight_kn_m3(fc, ConcreteClass::Normal, ConcreteComposition::Plain);
386        let gamma_rc =
387            concrete_unit_weight_kn_m3(fc, ConcreteClass::Normal, ConcreteComposition::Rc);
388        out.push(MaterialPreset {
389            name,
390            category: MaterialCategory::Concrete,
391            young: concrete_young_modulus_gamma(fc, gamma_c),
392            poisson: 0.2,
393            density: mass_density_from_unit_weight_kn_m3(gamma_rc),
394            fc: Some(fc),
395            fy: None,
396        });
397    }
398    out
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404
405    /// ST-Bridge の代表的なグレード名から区分を判定できることを確認する。
406    #[test]
407    fn test_category_of_grade() {
408        for (name, expected) in [
409            ("Fc21", MaterialCategory::Concrete),
410            ("FC60", MaterialCategory::Concrete),
411            ("SN400B", MaterialCategory::Steel),
412            ("SS400", MaterialCategory::Steel),
413            ("SM490A", MaterialCategory::Steel),
414            ("STKR400", MaterialCategory::Steel),
415            ("BCR295", MaterialCategory::Steel),
416            ("TMCP325", MaterialCategory::Steel),
417            ("LY225", MaterialCategory::Steel),
418            ("SD295A", MaterialCategory::Rebar),
419            ("SD345", MaterialCategory::Rebar),
420            ("SR235", MaterialCategory::Rebar),
421            ("KH785", MaterialCategory::Rebar),
422        ] {
423            assert_eq!(
424                category_of_grade(name),
425                Some(expected),
426                "グレード名 {name} の区分"
427            );
428        }
429    }
430
431    /// 判定できない名称は `None` を返し、取込側が既定を決める。
432    #[test]
433    fn test_category_of_grade_unknown() {
434        for name in ["コンクリート", "普通強度", "", "   ", "X999"] {
435            assert_eq!(category_of_grade(name), None, "未知の名称 {name:?}");
436        }
437    }
438
439    /// 判定の順序(コンクリート → 鉄筋 → 鋼材)を確認する。
440    /// `SD`/`SR` は鋼材の前方一致(`S…`)より先に判定する必要がある。
441    #[test]
442    fn test_category_of_grade_order() {
443        // 鉄筋は鋼材より先に判定する。
444        assert_eq!(category_of_grade("SD390"), Some(MaterialCategory::Rebar));
445        // 大文字小文字は区別しない。
446        assert_eq!(category_of_grade("fc24"), Some(MaterialCategory::Concrete));
447        assert_eq!(category_of_grade("sn400b"), Some(MaterialCategory::Steel));
448        // 前後の空白は無視する。
449        assert_eq!(
450            category_of_grade("  SD345  "),
451            Some(MaterialCategory::Rebar)
452        );
453    }
454
455    /// プリセット表の区分は、名前から推定した区分と一致する。
456    /// 一致しない項目があるとプリセットで追加した材料と ST-Bridge から
457    /// 取り込んだ同名の材料で構造種別が食い違う。
458    #[test]
459    fn test_presets_agree_with_category_of_grade() {
460        for p in material_presets() {
461            if let Some(guessed) = category_of_grade(p.name) {
462                assert_eq!(guessed, p.category, "プリセット {} の区分", p.name);
463            }
464        }
465    }
466
467    /// H12 建告第2464号の基準強度表と一致することを確認する(板厚 40mm 以下)。
468    #[test]
469    fn test_steel_f_value_le40() {
470        for (g, f) in [
471            ("SS400", 235.0),
472            ("SN400", 235.0),
473            ("SM400", 235.0),
474            ("SM490", 325.0),
475            ("SN490", 325.0),
476            ("SM520", 355.0),
477            ("SS490", 275.0),
478            ("BCR295", 295.0),
479            ("BCP235", 235.0),
480            ("BCP325", 325.0),
481            ("TMCP325", 325.0),
482            ("TMCP355", 355.0),
483            ("TMCP385", 385.0),
484            ("TMCP440", 440.0),
485            ("SA440", 440.0),
486            ("LY100", 80.0),
487            ("LY225", 205.0),
488        ] {
489            assert_eq!(steel_f_value(g, 40.0), Some(f), "{g}");
490        }
491    }
492
493    /// 板厚 40mm 超の低減(JIS 規格品)と、TMCP・SA440・LY・BCR/BCP が
494    /// 板厚によらず一定であることを確認する。
495    #[test]
496    fn test_steel_f_value_gt40() {
497        assert_eq!(steel_f_value("SS400", 41.0), Some(215.0));
498        assert_eq!(steel_f_value("SM490", 41.0), Some(295.0));
499        assert_eq!(steel_f_value("SN490", 41.0), Some(295.0));
500        assert_eq!(steel_f_value("SM520", 41.0), Some(335.0));
501        assert_eq!(steel_f_value("SM520", 76.0), Some(325.0));
502        for g in [
503            "TMCP325", "TMCP355", "TMCP385", "TMCP440", "SA440", "BCR295", "LY225",
504        ] {
505            assert_eq!(steel_f_value(g, 41.0), steel_f_value(g, 40.0), "{g}");
506        }
507    }
508
509    /// 前方一致解決: JIS 種別記号付き名称・最長一致を確認する。
510    #[test]
511    fn test_steel_f_value_prefix() {
512        assert_eq!(steel_f_value_prefix("SN490B", 40.0), Some(325.0));
513        assert_eq!(steel_f_value_prefix("SN400C", 40.0), Some(235.0));
514        assert_eq!(steel_f_value_prefix("SM490YA", 40.0), Some(325.0));
515        assert_eq!(steel_f_value_prefix("STKN400W", 40.0), Some(235.0));
516        assert_eq!(steel_f_value_prefix("STKN490B", 40.0), Some(325.0));
517        assert_eq!(steel_f_value_prefix("SNR400A", 40.0), Some(235.0));
518        assert_eq!(steel_f_value_prefix("SNR490B", 40.0), Some(325.0));
519        assert_eq!(steel_f_value_prefix("未知", 40.0), None);
520    }
521
522    /// 材料強度割増係数: 既知鋼材=1.1、590N 級(SA440/TMCP440)=1.05、未知=1.0。
523    #[test]
524    fn test_steel_material_strength_factor() {
525        assert_eq!(steel_material_strength_factor("SS400"), 1.1);
526        assert_eq!(steel_material_strength_factor("SN490B"), 1.1);
527        assert_eq!(steel_material_strength_factor("BCR295"), 1.1);
528        assert_eq!(steel_material_strength_factor("LY225"), 1.1);
529        assert_eq!(steel_material_strength_factor("TMCP385"), 1.1);
530        assert_eq!(steel_material_strength_factor("SA440"), 1.05);
531        assert_eq!(steel_material_strength_factor("TMCP440"), 1.05);
532        assert_eq!(steel_material_strength_factor("未知の材料"), 1.0);
533        assert_eq!(steel_material_strength_factor("SD345"), 1.0);
534    }
535
536    /// 文脈別係数: 直接入力の割増係数が最優先、なければ鋼材=名称判定・主筋=1.1。
537    #[test]
538    fn test_material_strength_factor_by_context() {
539        let mk = |name: &str, factor: Option<f64>| crate::model::Material {
540            id: crate::ids::MaterialId(0),
541            name: name.to_string(),
542            category: MaterialCategory::Steel,
543            young: 205000.0,
544            poisson: 0.3,
545            density: 7.85e-9,
546            shear: None,
547            fc: None,
548            fy: None,
549            concrete_class: Default::default(),
550            strength_factor: factor,
551        };
552        // 鋼材文脈: 名称から自動判定。
553        assert_eq!(material_strength_factor_steel(&mk("SS400", None)), 1.1);
554        assert_eq!(material_strength_factor_steel(&mk("SA440", None)), 1.05);
555        assert_eq!(material_strength_factor_steel(&mk("カスタム", None)), 1.0);
556        // 主筋文脈: 名称によらず 1.1。
557        assert_eq!(material_strength_factor_rebar(&mk("Fc24", None)), 1.1);
558        assert_eq!(material_strength_factor_rebar(&mk("SD345", None)), 1.1);
559        // 直接入力の割増係数は両文脈で最優先。
560        assert_eq!(
561            material_strength_factor_steel(&mk("カスタム", Some(1.2))),
562            1.2
563        );
564        assert_eq!(material_strength_factor_rebar(&mk("Fc24", Some(1.0))), 1.0);
565    }
566
567    #[test]
568    fn test_rebar_f_value() {
569        assert_eq!(rebar_f_value("SD295A"), Some(295.0));
570        assert_eq!(rebar_f_value("SD345"), Some(345.0));
571        assert_eq!(rebar_f_value("SD390"), Some(390.0));
572        assert_eq!(rebar_f_value("SR235"), Some(235.0));
573        assert_eq!(rebar_f_value("SS400"), None);
574    }
575
576    /// 高強度鉄筋の製品名は末尾の数値を降伏点とみなす。
577    #[test]
578    fn test_rebar_grade_f_value_high_strength() {
579        assert_eq!(rebar_grade_f_value("SD345"), Some(345.0));
580        assert_eq!(rebar_grade_f_value("USD685"), Some(685.0));
581        assert_eq!(rebar_grade_f_value("KH785"), Some(785.0));
582        assert_eq!(rebar_grade_f_value("UB785"), Some(785.0));
583        assert_eq!(rebar_grade_f_value("SBPD1275"), Some(1275.0));
584        assert_eq!(rebar_grade_f_value("不明"), None);
585    }
586
587    /// 鉄筋材料の見本(`fy` を指定して作る)。
588    fn rebar_mat(name: &str, fy: Option<f64>) -> crate::model::Material {
589        crate::model::Material {
590            strength_factor: None,
591            concrete_class: Default::default(),
592            id: crate::ids::MaterialId(0),
593            name: name.into(),
594            category: MaterialCategory::Rebar,
595            young: 205000.0,
596            poisson: 0.3,
597            density: 0.0,
598            shear: None,
599            fc: None,
600            fy,
601        }
602    }
603
604    /// 主筋 σy は断面の主筋材料の `fy` だけから決まる。材料が未割当のとき、
605    /// および `fy` が未設定・非正値のときは None(既定値で埋めない)。
606    #[test]
607    fn test_rebar_yield_strength_resolution_order() {
608        let mut mat = rebar_mat("SD295A", Some(295.0));
609        assert_eq!(rebar_yield_strength(Some(&mat)), Some(295.0));
610        // fy 未設定は None(材料名 "SD295A" からは推定しない)。
611        mat.fy = None;
612        assert_eq!(rebar_yield_strength(Some(&mat)), None);
613        // 非正値も未設定と同じ扱いにする。
614        mat.fy = Some(0.0);
615        assert_eq!(rebar_yield_strength(Some(&mat)), None);
616        // 主筋の材料が未割当のときも None(呼び出し側が入力不備として扱う)。
617        assert_eq!(rebar_yield_strength(None), None);
618    }
619
620    /// せん断補強筋 σwy も材料の `fy` から解決し、未設定は None(既定 295 は呼び出し側)。
621    #[test]
622    fn test_shear_rebar_yield_strength() {
623        let mat = rebar_mat("SD295A", Some(295.0));
624        assert_eq!(shear_rebar_yield_strength(Some(&mat)), Some(295.0));
625        let high = rebar_mat("KH785", Some(785.0));
626        assert_eq!(shear_rebar_yield_strength(Some(&high)), Some(785.0));
627        assert_eq!(shear_rebar_yield_strength(None), None);
628        assert_eq!(SHEAR_REBAR_DEFAULT_FY, 295.0);
629    }
630
631    #[test]
632    fn test_parse_concrete_fc() {
633        assert_eq!(parse_concrete_fc("Fc21"), Some(21.0));
634        assert_eq!(parse_concrete_fc("FC36"), Some(36.0));
635        assert_eq!(parse_concrete_fc("fc60"), Some(60.0));
636        assert_eq!(parse_concrete_fc("SD345"), None);
637    }
638
639    /// プリセット一覧: 件数・代表値・コンクリートの γ 帯依存を確認する。
640    #[test]
641    fn test_material_presets() {
642        let presets = material_presets();
643        assert_eq!(presets.len(), 14 + 3 + 13);
644        let find = |name: &str| {
645            presets
646                .iter()
647                .find(|p| p.name == name)
648                .unwrap_or_else(|| panic!("preset {name} not found"))
649        };
650
651        let ss400 = find("SS400");
652        assert_eq!(ss400.category, MaterialCategory::Steel);
653        assert_eq!(ss400.young, 205000.0);
654        assert_eq!(ss400.fy, Some(235.0));
655
656        let ly100 = find("LY100");
657        assert_eq!(ly100.fy, Some(80.0));
658
659        let sd345 = find("SD345");
660        assert_eq!(sd345.category, MaterialCategory::Rebar);
661        assert_eq!(sd345.fy, Some(345.0));
662
663        // コンクリート: Fc≤36 は γ=23、36<Fc≤48 は γ=23.5、48<Fc は γ=24 で Ec を算定。
664        let fc24 = find("Fc24");
665        assert_eq!(fc24.category, MaterialCategory::Concrete);
666        assert_eq!(fc24.fc, Some(24.0));
667        let ec24 = 3.35e4 * (23.0f64 / 24.0).powi(2) * (24.0f64 / 60.0).powf(1.0 / 3.0);
668        assert!((fc24.young - ec24).abs() < 1e-9);
669        let fc42 = find("Fc42");
670        let ec42 = 3.35e4 * (23.5f64 / 24.0).powi(2) * (42.0f64 / 60.0).powf(1.0 / 3.0);
671        assert!((fc42.young - ec42).abs() < 1e-9);
672        let fc60 = find("Fc60");
673        let ec60 = 3.35e4 * (60.0f64 / 60.0).powf(1.0 / 3.0);
674        assert!((fc60.young - ec60).abs() < 1e-9);
675
676        // 密度: 鋼は γs=77 kN/m³、コンクリートは γRC(Fc≤36 で 24.0 kN/m³)。
677        let rho_steel = mass_density_from_unit_weight_kn_m3(77.0);
678        assert!((ss400.density - rho_steel).abs() < 1e-18);
679        let rho_rc = mass_density_from_unit_weight_kn_m3(24.0);
680        assert!((fc24.density - rho_rc).abs() < 1e-18);
681    }
682}