Skip to main content

squid_n_core/
face_distance.rs

1//! 柱フェース距離(節点から部材フェースまでの距離)の算定。
2//!
3//! フェース距離は「その端で直交する部材の最大せいの半分」で、接合関係と断面せい
4//! だけから一意に決まる**幾何量**である。剛域長のようなモデル化の設定には
5//! 左右されない。危険断面位置・RC/SRC 梁の自重の内法長・数量積算の鉄筋長さなど、
6//! 剛域とは無関係な用途がこの値を読む。
7//!
8//! # なぜ core にあるか
9//!
10//! 以前はこの算定が `squid_n_element` の剛域算定(`apply_auto_rigid_zones`)の
11//! 中だけにあり、結果を `RigidZone::face_i/face_j` へキャッシュしていた。
12//! そのため「剛域を算定する前に読むと 0 になる」という順序依存があり、
13//! 実際に固定荷重が 9.6% 過大になる不具合を生んだ(`dev_docs/handoff/`
14//! 「実モデル統合テスト」4.1 節)。
15//!
16//! 幾何量は幾何から求めれば順序に依存しない。そこで算定を core へ置き、
17//! 上位クレート(`squid_n_load` の自重算定など)がキャッシュを当てにせず
18//! [`face_distances`] で直接求められるようにしている。
19
20use crate::adjacency::NodeAdjacency;
21use crate::geom::{element_axis as elem_axis, vec3, ORTHOGONAL_DOT_MAX};
22use crate::model::{ElementKind, Model};
23
24/// 節点 `node` で対象部材と概ね直交する Beam 要素の最大せいの半分 [mm]。
25/// 直交材がない端は 0.0。構造種別は問わない(幾何量のため)。
26fn face_at(
27    model: &Model,
28    node: crate::ids::NodeId,
29    target_axis: [f64; 3],
30    target_elem_idx: usize,
31    adjacency: &NodeAdjacency,
32) -> f64 {
33    let mut d_max = 0.0_f64;
34    for &ei in adjacency.indices_at(node) {
35        if ei == target_elem_idx {
36            continue;
37        }
38        let e = &model.elements[ei];
39        if e.kind != ElementKind::Beam {
40            continue;
41        }
42        let axis = elem_axis(model, e);
43        if vec3::dot(axis, target_axis).abs() >= ORTHOGONAL_DOT_MAX {
44            continue;
45        }
46        if let Some(sec) = e.section.and_then(|sid| model.sections.get(sid.index())) {
47            d_max = d_max.max(sec.depth);
48        }
49    }
50    d_max / 2.0
51}
52
53/// モデルの全要素について、両端の柱フェース距離 `[i 端, j 端]` [mm] を求める。
54///
55/// 添字は `model.elements` の並びと一致する。Beam 以外の要素と、節点が 2 つ
56/// 未満の要素は `[0.0, 0.0]`。計算量は O(要素数)。
57///
58/// キャッシュ(`RigidZone::face_i/face_j`)を当てにできない場所から使う。
59pub fn face_distances(model: &Model) -> Vec<[f64; 2]> {
60    let adjacency = NodeAdjacency::build(model);
61    model
62        .elements
63        .iter()
64        .enumerate()
65        .map(|(i, e)| {
66            if e.kind != ElementKind::Beam || e.nodes.len() < 2 {
67                return [0.0, 0.0];
68            }
69            let axis = elem_axis(model, e);
70            let ni = e.nodes[0];
71            let nj = e.nodes[e.nodes.len() - 1];
72            [
73                face_at(model, ni, axis, i, &adjacency),
74                face_at(model, nj, axis, i, &adjacency),
75            ]
76        })
77        .collect()
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::ids::{ElemId, MaterialId, NodeId, SectionId};
84    use crate::model::{
85        ElementData, EndCondition, ForceRegime, LocalAxis, Node, RigidZone, Section,
86    };
87
88    fn node(id: u32, c: [f64; 3]) -> Node {
89        Node {
90            id: NodeId(id),
91            coord: c,
92            restraint: Default::default(),
93            mass: None,
94            story: None,
95            support_spring: None,
96        }
97    }
98
99    fn section(id: u32, depth: f64) -> Section {
100        Section {
101            id: SectionId(id),
102            name: String::new(),
103            area: 0.0,
104            iy: 0.0,
105            iz: 0.0,
106            j: 0.0,
107            depth,
108            width: 0.0,
109            as_y: 0.0,
110            as_z: 0.0,
111            floor: None,
112            panel_thickness: None,
113            thickness: None,
114            shape: None,
115            material: Some(MaterialId(0)),
116            rebar_material: None,
117            shear_rebar_material: None,
118            steel_material: None,
119        }
120    }
121
122    fn elem(id: u32, kind: ElementKind, a: u32, b: u32, sec: u32) -> ElementData {
123        ElementData {
124            id: ElemId(id),
125            kind,
126            nodes: [NodeId(a), NodeId(b)].into_iter().collect(),
127            section: Some(SectionId(sec)),
128            local_axis: LocalAxis {
129                ref_vector: [0.0, 0.0, 1.0],
130            },
131            end_cond: [EndCondition::Fixed, EndCondition::Fixed],
132            force_regime: ForceRegime::Auto,
133            rigid_zone: RigidZone::default(),
134            plastic_zone: None,
135            spring: None,
136        }
137    }
138
139    /// 柱(せい 600)が取り付く端のフェース距離は柱せいの半分、直交材がない端は 0。
140    #[test]
141    fn 直交材のせいの半分をフェース距離とする() {
142        let model = Model {
143            nodes: vec![
144                node(0, [0.0, 0.0, 0.0]),
145                node(1, [0.0, 0.0, 3000.0]),
146                node(2, [4000.0, 0.0, 3000.0]),
147            ],
148            elements: vec![
149                elem(0, ElementKind::Beam, 0, 1, 0),
150                elem(1, ElementKind::Beam, 1, 2, 1),
151            ],
152            sections: vec![section(0, 600.0), section(1, 700.0)],
153            ..Default::default()
154        };
155        let f = face_distances(&model);
156        // 梁(要素 1): i 端に柱が取り付くので 600/2、j 端は直交材なしで 0。
157        assert_eq!(f[1], [300.0, 0.0]);
158        // 柱(要素 0): 上端に梁が取り付くので 700/2、下端は直交材なしで 0。
159        assert_eq!(f[0], [0.0, 350.0]);
160    }
161
162    /// フェース距離を決めるのは柱・大梁だけで、壁は数えない。
163    ///
164    /// 壁を数えると、剛域長を求めるときの「部材フェース」と食い違う。
165    #[test]
166    fn 壁はフェース距離に数えない() {
167        let mut model = Model {
168            nodes: vec![
169                node(0, [0.0, 0.0, 0.0]),
170                node(1, [0.0, 0.0, 3000.0]),
171                node(2, [4000.0, 0.0, 3000.0]),
172            ],
173            elements: vec![elem(0, ElementKind::Beam, 1, 2, 0)],
174            sections: vec![section(0, 700.0), section(1, 9999.0)],
175            ..Default::default()
176        };
177        assert_eq!(face_distances(&model)[0], [0.0, 0.0]);
178
179        // 梁の i 端に直交する壁を足しても変わらない。
180        model.elements.push(elem(1, ElementKind::Wall, 0, 1, 1));
181        assert_eq!(face_distances(&model)[0], [0.0, 0.0]);
182
183        // 同じ位置に柱(Beam)を足すと、そのせいの半分が効く。
184        model.elements.push(elem(2, ElementKind::Beam, 0, 1, 1));
185        assert_eq!(face_distances(&model)[0], [4999.5, 0.0]);
186    }
187}