Source code for simdesign.rcmrf.bnsm.cp02.building

"""This module provides the Building Nonlinear Structural Model (BNSM)
implementation for the ``CP02`` modelling configuration.
"""
# Imports from installed packages
from typing import List, Type, Optional
from pathlib import Path
import json

# Imports from bnsm ibrary
from .foundation import Foundation
from .joint import StairsJoint, FloorJoint
from .floor import FloorDiaphragm
from .beam import Beam
from .column import Column
from .infill import Infill

# Imports from bnsm base library
from ..baselib.building import BuildingBase
from ..baselib.beam import BeamDesign
from ..baselib.column import ColumnDesign

# Imports from utils library
from ....utils.units import MPa


[docs] class Building(BuildingBase): """BNSM implementation for the ``CP02`` model. This class aggregates CP02-specific structural components (e.g. beams, columns, joints, infills) and relies on the behaviour defined in ``BuildingBase`` without modification. Attributes ---------- foundations : List[~simdesign.rcmrf.bnsm.cp02.foundation.Foundation] List of foundation instances. floors : List[~simdesign.rcmrf.bnsm.cp02.floor.FloorDiaphragm] List of floor instances. floor_joints : List[~simdesign.rcmrf.bnsm.cp02.joint.FloorJoint] List of floor joints instances. stairs_joints : List[~simdesign.rcmrf.bnsm.cp02.joint.StairsJoint] List of stairs joints instances. beams : List[~simdesign.rcmrf.bnsm.cp02.beam.Beam] List of beam instances. columns : List[~simdesign.rcmrf.bnsm.cp02.column.Column] List of column instances. infills : List[~simdesign.rcmrf.bnsm.cp02.infill.Infill] List of infill instances. See Also -------- :class:`~BuildingBase` Base class defining the core behaviour and configuration. """ foundations: List[Foundation] floors: List[FloorDiaphragm] floor_joints: List[FloorJoint] stairs_joints: List[StairsJoint] beams: List[Beam] columns: List[Column] infills: List[Infill] FoundationClass: Type[Foundation] = Foundation FloorDiaphragmClass: Type[FloorDiaphragm] = FloorDiaphragm FloorJointClass: Type[FloorJoint] = FloorJoint StairsJointClass: Type[StairsJoint] = StairsJoint BeamClass: Type[Beam] = Beam ColumnClass: Type[Column] = Column InfillClass: Type[Infill] = Infill def _find_beam_by_design(self, design: BeamDesign) -> Optional[Beam]: """Finds the beam model by the given design. Parameters ---------- design : ~simdesign.rcmrf.bnsm.cp02.beam.BeamDesign Beam design instance used for search. Returns ------- Beam | None Returns Beam object if design attribute matches with given design, otherwise, returns None. """ return super()._find_beam_by_design(design) def _find_column_by_design(self, design: ColumnDesign) -> Optional[Column]: """Finds the column model by the given design. Parameters ---------- design : ~simdesign.rcmrf.bnsm.cp02.column.ColumnDesign Column design instance used for search. Returns ------- Column | None Returns Column object if design attribute matches with given design, otherwise, returns None. """ return super()._find_column_by_design(design)
[docs] def export_hinges(self, directory: Optional[str | Path] = None) -> None: """Export plastic-hinge capacity data for beams and columns to JSON. For every floor joint and foundation joint in the model, this method computes the hysteretic energy capacity (Eh_cap) and ultimate chord rotation (theta_ult) of the plastic hinges framing into that joint, then writes the collected data to "info_hinges.json" in the target directory. The hysteretic energy capacity is computed as:: Eh_cap = My * (theta_ult - theta_y) * lambda where "lambda" is an empirical definition that depends on concrete strength (fc), longitudinal-steel yield strength (fsyl), normalised axial load (niu), and the transverse-reinforcement spacing ratio (s_h = s / h). lambda = 52 * 0.41^(0.01*(fc + 0.1*fsyl)) * 0.34^niu * 0.21^(s/h) Notes ----- - Beam ends are tagged "I" for the start end (right/front beam) and "J" for the end (left/rear beam), following the convention in "BEAM_END_MAP". - Column ends are tagged "I" for the top column and "J" for the bottom column. - For beams, "niu" is assumed to be 0 (axial load neglected). """ # ------------------------------------------------------------------ # Output directory setup # ------------------------------------------------------------------ directory = Path(directory) if directory is not None else Path.cwd() directory.mkdir(parents=True, exist_ok=True) # Maps the joint's beam attribute name to the corresponding hinge # end label. "I" = start end, "J" = end end. right/front beams # frame into the joint at their I-end; left/rear beams at their # J-end. BEAM_END_MAP = {"right_beam": "I", "left_beam": "J", "front_beam": "I", "rear_beam": "J"} # Top-level container: # {storey: {joint_id: {"beams": ..., "columns": ...}}} info_hinges = {} # Iterate over every joint where hinges may form: floor joints # above ground level and foundation joints at the base. for joint in self.floor_joints + self.foundations: # Storey index is the third grid coordinate of the elastic node storey = int(joint.design.elastic_node.grid_ids[2]) # Ensure the storey bucket exists before we write into it info_hinges.setdefault(storey, {}) # Foundation joints carry a "foundation_node", floor joints a # "floor_node". Use whichever is present as the joint identifier. joint_id = (joint.foundation_node.tag if hasattr( joint, "foundation_node") else joint.floor_node.tag) # Initialise the dictionary for the joint info_hinges[storey][joint_id] = {"beams": {}, "columns": {}} # -------------------------------------------------------------- # Beams framing into this joint # -------------------------------------------------------------- for beam_attr, beam_end in BEAM_END_MAP.items(): # Each joint may have up to four incident beams (one per # cardinal direction). beam_design = getattr(joint.design, beam_attr) if beam_design: beam = self._find_beam_by_design(beam_design) else: continue # Pull the backbone moment-rotation inputs for the relevant # end of the beam, plus the normalised stirrup spacing s/h # at that same end. # _get_mz_mat_inputs() -> (inputs_at_I, inputs_at_J) # sbh_q[0] -> stirrup spacing at I-end # sbh_q[-1] -> stirrup spacing at J-end if beam_end == "I": mat_inputs, _ = beam._get_mz_mat_inputs() s_h = beam.design.sbh_q[0] / beam.design.h elif beam_end == "J": _, mat_inputs = beam._get_mz_mat_inputs() s_h = beam.design.sbh_q[-1] / beam.design.h # Backbone parameters: yield moment, yield rotation, # ultimate rotation, and derived plastic rotation. My = mat_inputs[2] theta_y = mat_inputs[3] theta_ult = mat_inputs[7] theta_pl_total = theta_ult - theta_y # Convert material strengths from model units (Pa) to MPa # for use in the empirical lambda formula. fc_mpa = beam.design.fc_q / MPa fsyl_mpa = beam.design.fsyl_q / MPa # Axial load on beams is neglected: niu = 0. niu = 0.0 # Empirical hysteretic energy capacity factor. lamda = (52 * (0.41 ** (0.01 * (fc_mpa + 0.1 * fsyl_mpa))) * (0.34 ** niu) * (0.21 ** s_h)) # Hysteretic energy capacity of the beam hinge Eh_cap = round(My * theta_pl_total * lamda, 1) # Record this beam hinge under its element tag info_hinges[storey][joint_id]["beams"][beam.ele_tag] = { "beam_end": beam_end, "Eh_cap": Eh_cap, "theta_ult": round(theta_ult, 4)} # -------------------------------------------------------------- # Columns framing into this joint # -------------------------------------------------------------- for col_attr in ["top_column", "bottom_column"]: # A joint may have a column above it, below it, or both. col_design = getattr(joint.design, col_attr) if col_design: column = self._find_column_by_design(col_design) else: continue # Material strengths in MPa for the lambda formula fc_mpa = column.design.fc_q / MPa fsyl_mpa = column.design.fsyl_q / MPa # Normalised axial load ratio N = max(-column.axial_force, 0) niu = N / (column.design.Ag * column.design.fc_q) # Stirrup spacing ratio depends on bending axis: s_h_x = column.design.sbh_q / column.design.by s_h_y = column.design.sbh_q / column.design.bx # Calculate hysteretic energy capacity of section # around x (Eh_cap_x) mat_inputs_x = column._get_rot_hinge_mat_inputs("x") My_x = mat_inputs_x[2] theta_y_x = mat_inputs_x[3] theta_ult_x = mat_inputs_x[7] theta_pl_total_x = theta_ult_x - theta_y_x lamda_x = (52 * (0.41 ** (0.01 * (fc_mpa + 0.1 * fsyl_mpa))) * (0.34 ** niu) * (0.21 ** s_h_x)) Eh_cap_x = round(My_x * theta_pl_total_x * lamda_x, 1) # Calculate hysteretic energy capacity of section # around y (Eh_cap_y) mat_inputs_y = column._get_rot_hinge_mat_inputs("y") My_y = mat_inputs_y[2] theta_y_y = mat_inputs_y[3] theta_ult_y = mat_inputs_y[7] theta_pl_total_y = theta_ult_y - theta_y_y lamda_y = (52 * (0.41 ** (0.01 * (fc_mpa + 0.1 * fsyl_mpa))) * (0.34 ** niu) * (0.21 ** s_h_y)) Eh_cap_y = round(My_y * theta_pl_total_y * lamda_y, 1) # Column end label: top column's hinge is the "I" end, # bottom column's hinge is the "J" end. if col_attr == "top_column": column_end = "I" elif col_attr == "bottom_column": column_end = "J" # Record this column hinge under its element tag info_hinges[storey][joint_id]["columns"][column.ele_tag] = { "column_end": column_end, "Eh_cap_x": Eh_cap_x, "Eh_cap_y": Eh_cap_y, "theta_ult_x": round(theta_ult_x, 4), "theta_ult_y": round(theta_ult_y, 4)} # ------------------------------------------------------------------ # Write the json file # ------------------------------------------------------------------ with open(directory / "info_hinges.json", "w") as f: json.dump(info_hinges, f, indent=2)