"""This module provides the base class for representing buildings
within the BDIM layer.
"""
# Imports from installed packages
from abc import ABC
from dataclasses import dataclass
from typing import List, Type, Literal, Dict, Tuple, Optional, Union
import numpy as np
from math import ceil
from pathlib import Path
import pandas as pd
# Imports from bdim base library
from .analysis import ElasticModelBase
from .beam import BeamBase
from .column import ColumnBase
from .joint import JointBase
from .loads import LoadsBase
from .materials import MaterialsBase, ConcreteBase, SteelBase
from .quality import QualityBase
from .rebars import RebarsBase
from .slab import SlabBase
from .stairs import StairsBase
from .infill import InfillBase
# Imports from geometry library
from ...geometry.base import GeometryBase, Point, Line, Rectangle
# Imports from utils library
from ....utils.misc import make_dir
from ....utils.units import MPa, mm
[docs]
@dataclass
class TaxonomyData:
"""Taxonomy data required for performing simulated-designs.
Attributes
----------
slab_type : Literal[1, 2, 3]
Slab typology:
1: Solid two-way cast-in-situ slabs (SS2).
2: Solid one-way cast-in-situ slabs (SS1).
3: One-way composite slab with ceramic blocks and RC joists or
pre-stressed beams (HS).
beam_type : Literal[1, 2]
Beam typology:
1: Wide beams.
2: Emergent beams.
column_section : Literal[1, 2]
Column cross-section type:
1: Square solid section.
2: Rectangular solid section.
steel_grade : str
Steel material class ID, e.g., 'S400'.
concrete_grade : str
Concrete material class ID, e.g., 'C20/25'.
quality : Literal[0, 1, 2, 3]
Construction quality level:
0: Excellent quality.
1: High quality.
2: Moderate quality.
3: Low quality.
geometry : GeometryBase
Building geometry instance.
beta : float
Design lateral load factor.
design_class : str
Building design class selected.
beta_v : float, optional
Vertical load factor. By default None.
staircase_slab_depth : float, optional
Depth of the staircase slabs. By default None.
slab_thickness : float, optional
Slab thickness (depth). By default None.
slab_orientation : Literal[1, 2, 3], optional
Slab unloading orientation. By default None.
1: Unloading in beams along X direction.
2: Unloading in beams along Y direction.
3: Unloading in beams along both directions.
"""
slab_type: Literal[1, 2, 3]
beam_type: Literal[1, 2]
column_section: Literal[1, 2]
steel_grade: str
concrete_grade: str
quality: Literal[0, 1, 2, 3]
geometry: GeometryBase
beta: float
design_class: str
beta_v: Optional[float] = None
staircase_slab_depth: Optional[float] = None
slab_thickness: Optional[float] = None
slab_orientation: Optional[Literal[1, 2, 3]] = None
[docs]
class BuildingBase(ABC):
"""Abstract base class for buildings.
Must be inherited by design-class-specific buildings.
Attributes
----------
taxonomy : TaxonomyData
Building taxonomy data.
beams : List[~simdesign.rcmrf.bdim.baselib.beam.BeamBase]
List of beam instances.
columns : List[~simdesign.rcmrf.bdim.baselib.column.ColumnBase]
List of column instances.
joints : List[~simdesign.rcmrf.bdim.baselib.joint.JointBase]
List of joint instances.
slabs : List[SlabBase]
List of slab instances.
stairs : List[StairsBase]
List of stairs instances.
infills : List[~simdesign.rcmrf.bdim.baselib.infill.InfillBase]
List of infill wall instances.
steel : SteelBase
Steel material instance considered in design of beams and
columns.
concrete : ConcreteBase
Concrete material instance considered in design of beams and
columns.
loads : LoadsBase
Loads instance used to apply building loads.
materials : MaterialsBase
Materials instance used to set building materials.
rebars : RebarsBase
Rebars instance used to determine reinforcement arrangement.
quality : QualityBase
Quality instance used to adjust properties of structural
elements.
ColumnClass : Type[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase]
Column class used to instantiate column objects.
BeamClass : Type[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase]
Beam class used to instantiate beam objects.
JointClass : Type[~simdesign.rcmrf.bdim.baselib.joint.JointBase]
Joint class used to instantiate joint objects.
SlabClass : Type[SlabBase]
Slab class used to instantiate slab objects.
StairsClass : Type[StairsBase]
Stairs class used to instantiate stairs objects.
InfillClass : Type[~simdesign.rcmrf.bnsm.baselib.infill.InfillBase]
Infill class used to instantiate infill objects.
ElasticModelClass : Type[ElasticModelBase]
Elastic model class used to run structural analyses.
ok : bool
Decision flag for determining whether design is ok or not.
ITER_MAX : int
Maximum number of iterations in the iterative design routine.
By default 200.
COLUMN_UNIFORMIZATION_STEP : int, optional
Step size for column section uniformization. For example, if
equals to 2, the same column section might be varied at every
two storeys from bottom to top. If None, a constant section is
used at all storeys. By default None.
OVERSTRENGTH_FACTOR_COLUMN_MOMENT : float, optional
Overstrength factor for capacity design moments for columns
(strong-column weak-beam principle). If None, column capacity
design moments are not considered. By default None.
OVERSTRENGTH_FACTOR_BEAM_SHEAR : float, optional
Overstrength factor for capacity design shear forces for beams.
If None, beam capacity design shear forces are not considered.
By default None.
OVERSTRENGTH_FACTOR_COLUMN_SHEAR : float, optional
Overstrength factor for capacity design shear forces for
columns. If None, column capacity design shear forces are not
considered. By default None.
COLUMN_POSITION_FACTORS : Dict[...]
Position factors to account for column axial force increase due
to seismic loading. Used to compute preliminary axial forces on
columns.
"""
taxonomy: TaxonomyData
beams: List[BeamBase]
columns: List[ColumnBase]
joints: List[JointBase]
slabs: List[SlabBase]
stairs: List[StairsBase]
infills: List[InfillBase]
steel: SteelBase
concrete: ConcreteBase
loads: LoadsBase
materials: MaterialsBase
rebars: RebarsBase
quality: QualityBase
ColumnClass: Type[ColumnBase]
BeamClass: Type[BeamBase]
JointClass: Type[JointBase]
SlabClass: Type[SlabBase]
StairsClass: Type[StairsBase]
InfillClass: Type[InfillBase]
ElasticModelClass: Type[ElasticModelBase]
ok: bool
ITER_MAX: int = 200
COLUMN_UNIFORMIZATION_STEP: Optional[int] = None
OVERSTRENGTH_FACTOR_COLUMN_MOMENT: Optional[float] = None
OVERSTRENGTH_FACTOR_BEAM_SHEAR: Optional[float] = None
OVERSTRENGTH_FACTOR_COLUMN_SHEAR: Optional[float] = None
COLUMN_POSITION_FACTORS: Dict[
Literal["bot", "top"], Dict[Literal["central", "exterior"], float]
] = {
"bot": {"central": 1.1, "exterior": 1.3},
"top": {"central": 1.3, "exterior": 1.5},
}
@property
def stairs_midstorey_beams(self) -> List[BeamBase]:
"""List of stairs midstorey beams.
Returns
-------
List[~simdesign.rcmrf.bdim.baselib.beam.BeamBase]
List of stairs midstorey beams.
"""
return self._get_stairs_midstorey_beams()
@property
def next_steel(self) -> SteelBase:
"""Next steel material.
Returns
-------
SteelBase
Next steel material.
"""
return self.materials.get_next_steel(self.steel)
@property
def next_concrete(self) -> ConcreteBase:
"""Next concrete material.
Returns
-------
ConcreteBase
Next concrete material.
"""
return self.materials.get_next_concrete(self.concrete)
@property
def geometry(self) -> GeometryBase:
"""Frame building geometry instance.
Returns
-------
GeometryBase
Frame building geometry instance.
"""
return self.taxonomy.geometry
@property
def beta(self) -> float:
"""Design lateral load factor (in g).
Returns
-------
float
Design lateral load factor (in g).
"""
return self.taxonomy.beta
@property
def beam_type(self) -> Literal[1, 2]:
"""Typology of beams.
Returns
-------
Literal[1, 2]
Typology of beams.
1: Wide beams are allowed.
2: Emergent beams are used only.
"""
return self.taxonomy.beam_type
@beam_type.setter
def beam_type(self, new_type: Literal[1, 2]) -> None:
"""Setter for `beam_type`."""
for beam in self.beams:
beam.typology = new_type
self.taxonomy.beam_type = new_type
@property
def column_section(self) -> Literal[1, 2]:
"""Cross-section of columns.
Returns
-------
Literal[1, 2]
Cross-section of columns.
1: Square solid section.
2: Rectangular solid section.
"""
return self.taxonomy.column_section
@column_section.setter
def column_section(self, new_section: Literal[1, 2]) -> None:
"""Setter for `column_section`."""
for column in self.columns:
column.section = new_section
self.taxonomy.column_section = new_section
@property
def design_class(self) -> str:
"""Building design class, e.g., 'eu_cdh'.
Returns
-------
str
Building design class, e.g., 'eu_cdh'.
"""
return self.taxonomy.design_class
@property
def slab_thickness(self) -> float | None:
"""Slab thickness (depth) in the building.
Returns
-------
float | None
Slab thickness (depth) considered in the building,
if already set in the taxonomy, otherwise, None.
"""
if hasattr(self.taxonomy, 'slab_thickness'):
return self.taxonomy.slab_thickness
else:
return None
@slab_thickness.setter
def slab_thickness(self, new_thickness: str) -> None:
"""Setter for `slab_thickness`."""
self.taxonomy.slab_thickness = new_thickness
@property
def slab_type(self) -> Literal[1, 2, 3]:
"""Slab typology considered in the building.
Returns
-------
Literal[1, 2, 3]
Slab typology considered in the building.
1: Solid two-way cast-in-situ slabs (SS2).
2: Solid one-way cast-in-situ slabs (SS1).
3: One-way composite slab with ceramic blocks and RC joists or
pre-stressed beams (HS).
"""
return self.taxonomy.slab_type
@slab_type.setter
def slab_type(self, new_type: Literal[1, 2, 3]) -> None:
"""Setter for `slab_type`."""
self.taxonomy.slab_type = new_type
@property
def staircase_slab_depth(self) -> float:
"""Building staircase slab thickness (depth).
Returns
-------
float
Building staircase slab thickness (depth).
"""
if hasattr(self.taxonomy, 'staircase_slab_depth'):
return self.taxonomy.staircase_slab_depth
else:
return None
@staircase_slab_depth.setter
def staircase_slab_depth(self, new_thickness: str) -> None:
"""Setter for `staircase_slab_depth`."""
self.taxonomy.staircase_slab_depth = new_thickness
@property
def steel_grade(self) -> str:
"""Reinforcing steel material grade, e.g., 'S400'.
Returns
-------
str
Reinforcing steel material grade, e.g., 'S400'.
"""
return self.taxonomy.steel_grade
@steel_grade.setter
def steel_grade(self, new_grade: str) -> None:
"""Setter for `steel_grade`."""
self.taxonomy.steel_grade = new_grade
@property
def concrete_grade(self) -> str:
"""Concrete material grade, e.g., 'C20/25'.
Returns
-------
str
Concrete material grade, e.g., 'C20/25'.
"""
return self.taxonomy.concrete_grade
@concrete_grade.setter
def concrete_grade(self, new_grade: str) -> None:
"""Setter for `concrete_grade`."""
self.taxonomy.concrete_grade = new_grade
@property
def gamma_rc(self) -> float:
"""Reinforced concrete unit weight in building.
Returns
-------
float
Reinforced concrete unit weight in building.
"""
return self.loads.permanent.gamma_rc
@property
def continuous_columns(
self
) -> Dict[Tuple[Union[float, int]], List[List[ColumnBase]]]:
"""Continuous columns grouped by grid position on XY-plane.
Returns
-------
Dict[Tuple[Union[float, int]], \
List[List[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase]]]
Lists of continuous columns at each grid on XY-plane.
Notes
-----
Key of the returned dictionary corresponds to grid ids (x, y).
Items contain nested list of continuous columns at corresponding grid.
If there are discontinuous columns, the nested list will contain more
than one list of continuous columns.
"""
lines_dict = self.geometry.continuous_lines_along_z
col_dict: Dict[Tuple[Union[float, int]], List[List[ColumnBase]]] = {}
for grid_ids, lines in lines_dict.items():
col_dict[grid_ids] = []
columns = []
for line in lines:
if line is None:
if any(columns):
col_dict[grid_ids].append(columns.copy())
columns = []
else:
column = self._find_column_by_line(line)
columns.append(column)
col_dict[grid_ids].append(columns.copy())
return col_dict
@property
def _continuous_columns(self) -> List[List[ColumnBase]]:
"""All continuous columns as a single flat list.
Returns
-------
List[List[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase]]
All continuous columns as a single flat list.
"""
return [columns
for _, columns_list in self.continuous_columns.items()
for columns in columns_list]
@property
def continuous_beams_x(
self
) -> Dict[Tuple[Union[float, int]], List[List[BeamBase]]]:
"""Continuous beams along -X grouped by grid on YZ-plane.
Returns
-------
Dict[Tuple[Union[float, int]], \
List[List[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase]]]
Lists of continuous beams at each grid on YZ-plane.
Notes
-----
Key of the returned dictionary corresponds to grid ids (y, z).
Items contain nested list of continuous beams at corresponding grid.
If there are discontinuous beams, the nested list will contain more
than one list of continuous beams.
"""
lines_dict = self.geometry.continuous_lines_along_x
beam_dict: Dict[Tuple[Union[float, int]], List[List[BeamBase]]] = {}
for grid_ids, lines in lines_dict.items():
beam_dict[grid_ids] = []
beams = []
for line in lines:
if line is None:
if any(beams):
beam_dict[grid_ids].append(beams.copy())
beams = []
else:
beam = self._find_beam_by_line(line)
beams.append(beam)
beam_dict[grid_ids].append(beams.copy())
return beam_dict
@property
def continuous_beams_y(
self
) -> Dict[Tuple[Union[float, int]], List[List[BeamBase]]]:
"""Continuous beams along -Y grouped by grid on XZ-plane.
Returns
-------
Dict[Tuple[Union[float, int]], \
List[List[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase]]]
Lists of continuous beams at each grid on XZ-plane.
Notes
-----
Key of the returned dictionary corresponds to grid ids (x, z).
Items contain nested list of continuous beams at corresponding grid.
If there are discontinuous beams, the nested list will contain more
than one list of continuous beams.
"""
lines_dict = self.geometry.continuous_lines_along_y
beam_dict: Dict[Tuple[Union[float, int]], List[List[BeamBase]]] = {}
for grid_ids, lines in lines_dict.items():
beam_dict[grid_ids] = []
beams = []
for line in lines:
if line is None:
if any(beams):
beam_dict[grid_ids].append(beams.copy())
beams = []
else:
beam = self._find_beam_by_line(line)
beams.append(beam)
beam_dict[grid_ids].append(beams.copy())
return beam_dict
@property
def _continuous_beams(self) -> List[List[BeamBase]]:
"""All continuous beams as a single flat list.
Returns
-------
List[List[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase]]
All continuous beams as a single flat list.
"""
# Get continuous beams as single list
cont_beams_all: List[List[BeamBase]] = []
for _, beams_list in self.continuous_beams_x.items():
for beams in beams_list:
cont_beams_all.append(beams)
for _, beams_list in self.continuous_beams_y.items():
for beams in beams_list:
cont_beams_all.append(beams)
return cont_beams_all
@property
def num_storeys(self) -> int:
"""Number of storeys in the building.
Returns
-------
int
Number of storeys in the building.
Notes
-----
The results is correct if at least one column is fully
continuous along the building.
"""
max_level = 0 # This corresponds to storey
all_stairs_columns = self._get_stairs_columns_agg()
for _, column_lists in self.continuous_columns.items():
tmp = 0
for columns in column_lists:
# Find the columns with equal sections
steps = [0.5 if column in all_stairs_columns else 1.0
for column in columns]
levels = np.cumsum(steps)
tmp += max(levels)
max_level = int(max(max_level, tmp))
if max_level > 9:
raise NotImplementedError('Frame buildings are not allowed to '
'have more than 9 stories.')
else:
return max_level
@property
def dim_change(self) -> bool:
"""Flag indicating whether section dimensions can be increased.
Returns
-------
bool
Flag indicating whether it is possible to increase section
dimensions or not.
"""
return all((self.dim_change_column, self.dim_change_beam))
@property
def dim_change_column(self) -> bool:
"""Flag indicating whether column dimensions can be increased.
Returns
-------
bool
Flag indicating whether it is possible to increase column section
dimensions or not.
"""
return all(max(col.bx, col.by) <= col.max_b for col in self.columns)
@property
def dim_change_beam(self) -> bool:
"""Flag indicating whether beam dimensions can be increased.
Returns
-------
bool
Flag indicating whether it is possible to increase beam section
dimensions or not.
"""
# Beam breadth
bool1 = all(beam.b <= beam.max_b for beam in self.beams)
# Beam height
bool2 = all(beam.h <= beam.max_h for beam in self.beams)
return all((bool1, bool2))
@property
def mat_change(self) -> bool:
"""Flag for determining if materials can be changed.
Returns
-------
bool
Flag for determining if it is possible change materials or not.
"""
if self.next_concrete or self.next_steel:
return True
else:
return False
@property
def beam_change(self) -> bool:
"""Flag for determining if beam type can be changed.
Returns
-------
bool
Flag for determining if it is possible change ``beam_type``
or not.
Notes
-----
Can be overwritten.
"""
if self.beam_type == 1:
return True
else:
return False
@property
def column_change(self) -> bool:
"""Flag for determining if column section can be changed.
Returns
-------
bool
Flag for determining if it is possible change
``column_section`` or not.
Notes
-----
Can be overwritten.
"""
if self.column_section == 2:
return True
else:
return False
@property
def beams_fail(self) -> bool:
"""Boolean indicating if any beams failed the design check.
Returns
-------
bool
Boolean indicating if beams failed to pass the design check or not.
"""
return not all(beam.ok for beam in self.beams)
@property
def columns_fail(self) -> bool:
"""Boolean indicating if any columns failed the design check.
Returns
-------
bool
Boolean indicating if columns failed to pass the design check or
not.
"""
return not all(col.ok_x and col.ok_y for col in self.columns)
@property
def elastic_nodes(self) -> List[Point]:
"""Nodes (points) in the linear elastic numerical model.
Returns
-------
List[Point]
Nodes (points) in the linear elastic numerical model.
"""
return self.geometry.points
@property
def elastic_nodes_ground(self) -> List[Point]:
"""Ground-level nodes in the linear elastic numerical model.
Returns
-------
List[Point]
Ground-level nodes in the linear elastic numerical model.
"""
return [cols[0][0].line.points[0]
for _, cols in self.continuous_columns.items()]
def __init__(self, taxonomy: TaxonomyData) -> None:
"""Initialize a new instance of BuildingBase.
Parameters
----------
taxonomy : TaxonomyData
Taxonomy data required for performing simulated-designs.
"""
self.taxonomy = taxonomy
self.beams = []
self.columns = []
self.joints = []
self.slabs = []
self.infills = []
self.stairs = []
self.ok = False
self._initialize_columns()
self._initialize_beams()
self._initialize_joints()
self._initialize_slabs()
self._initialize_stairs()
self._initialize_materials()
self._initialize_infills()
self._set_beam_gravity_loads()
self._set_restore_point()
self._set_quality_model()
def _initialize_beams(self) -> None:
"""Initialize building beams.
"""
# Lines of exterior beams
exterior_beam_lines = self.geometry.exterior_horizontal_lines
# Beams along global x-axis
for line in self.geometry.lines_x:
beam = self.BeamClass(line=line, typology=self.beam_type,
gamma_rc=self.gamma_rc)
# Check if it is an exterior beam
if line in exterior_beam_lines:
beam.exterior = True
# Set the beam cover
beam.cover = self.rebars.beam_cover
# Find end columns
beam.columns = self._find_columns_by_beam(beam)
# Initialize superimposed gravity loads from the infills
beam.infill_wg = 0.0
# Append to beams list
self.beams.append(beam)
# Beams along global y-axis
for line in self.geometry.lines_y:
beam = self.BeamClass(line=line, typology=self.beam_type,
gamma_rc=self.gamma_rc)
# Check if it is an exterior beam
if line in exterior_beam_lines:
beam.exterior = True
# Set the beam cover
beam.cover = self.rebars.beam_cover
# Find end columns
beam.columns = self._find_columns_by_beam(beam)
# Initialize superimposed gravity loads from the infills
beam.infill_wg = 0.0
# Append to beams list
self.beams.append(beam)
def _initialize_columns(self) -> None:
"""Initialize building columns.
"""
# Start loop through each vertical line
for line in self.geometry.column_lines:
column = self.ColumnClass(line=line,
section=self.column_section,
gamma_rc=self.gamma_rc)
# Set the longer dimension direction in global axis for
# rectangular column.
if line.rot_angle == 90.0:
column.orient = 'y'
elif line.rot_angle == 0.0:
column.orient = 'x'
# Set the column cover
column.cover = self.rebars.col_cover
# Append to the column list
self.columns.append(column)
def _initialize_slabs(self) -> None:
"""Initialize building slabs.
"""
for rectangle in self.geometry.slab_rectangles:
slab = self.SlabClass(rectangle=rectangle,
typology=self.slab_type,
thickness=self.slab_thickness)
slab.roof = rectangle in self.geometry.roof_rectangles
slab.set_loads(self.loads.permanent, self.loads.variable)
self.slabs.append(slab)
# If slab thickness is not provided, set a global thickness value
if self.slab_thickness is None:
self.slab_thickness = max(slab.t for slab in self.slabs)
for slab in self.slabs:
slab.t = self.slab_thickness
def _initialize_stairs(self) -> None:
"""Initialize building stairs.
"""
for rectangle in self.geometry.stairs_rectangles:
slab = self.StairsClass(
rectangle=rectangle,
thickness=self.staircase_slab_depth)
slab.roof = rectangle in self.geometry.roof_rectangles
slab.set_loads(self.loads.permanent, self.loads.variable)
self.stairs.append(slab)
# If staircase slab depth is not provided, set a global depth value
if self.staircase_slab_depth is None and any(self.stairs):
self.staircase_slab_depth = max(slab.t for slab in self.stairs)
for slab in self.stairs:
slab.t = self.staircase_slab_depth
def _initialize_materials(self) -> None:
"""Initialize building materials.
"""
# Set the current materials
self.steel = self.materials.get_steel(self.steel_grade)
self.concrete = self.materials.get_concrete(self.concrete_grade)
# Update element materials
self._update_element_materials()
def _initialize_joints(self) -> None:
"""Initialize building joints.
"""
for node in self.elastic_nodes:
# Get elements connected to the node
(
left_beam, right_beam,
front_beam, rear_beam,
top_column, bottom_column
) = self._find_elements_by_point(node)
# Create joint
joint = self.JointClass(
elastic_node=node,
left_beam=left_beam, right_beam=right_beam,
front_beam=front_beam, rear_beam=rear_beam,
top_column=top_column, bottom_column=bottom_column
)
self.joints.append(joint)
def _initialize_infills(self) -> None:
"""Initialize building infill walls.
"""
exterior_beam_lines = self.geometry.exterior_horizontal_lines
for rectangle in self.geometry.infill_rectangles:
beams = []
cols = []
loc = 'interior'
for i, line in enumerate(rectangle.lines):
if i in [1, 3]: # beams
if line:
beams.append(self._find_beam_by_line(line))
if beams[-1] in exterior_beam_lines:
loc = 'exterior'
else:
beams.append(None) # Could be None at base level
elif i in [0, 2]: # columns
if isinstance(line, list):
# Handle the case with multiple columns
tmp = [self._find_column_by_line(li) for li in line]
cols.append(tmp)
else:
cols.append(self._find_column_by_line(line))
infill = self.InfillClass(rectangle, beams, cols, loc)
self.infills.append(infill)
def _set_quality_model(self) -> None:
"""Set construction quality model.
"""
self.quality.model = self.taxonomy.quality
def _update_element_materials(self) -> None:
"""Update beam-column elements' materials."""
# Update the material grades
self.concrete_grade = self.concrete.grade
self.steel_grade = self.steel.grade
# Update the materials in rebars object
self.rebars.concrete = self.concrete
self.rebars.steel = self.steel
# Update the materials in beam objects
for beam in self.beams:
beam.steel = self.steel
beam.concrete = self.concrete
# Update the materials in column objects
for column in self.columns:
column.steel = self.steel
column.concrete = self.concrete
# Update the materials in joint objects
for joint in self.joints:
joint.steel = self.steel
joint.concrete = self.concrete
def _get_unique_seism_combo_grav_factors(
self
) -> List[Dict[Literal['G', 'Q'], float]]:
"""Retrieve the list containing gravity load factors from seismic
load combos.
These factors can be used to compute the mean gravity forces obtained
from seismic load combinations.
Returns
-------
List[Dict[Literal['G', 'Q'], float]]
A list containing gravity load factors from seismic load combos.
"""
factors = []
unique_pairs = set()
seismic_strs = ["E+X", "E-X", "E+Y", "E-Y"]
for combo in self.loads.combinations:
if any(item in combo.loads for item in seismic_strs):
if 'G' in combo.loads:
g_fact = combo.loads['G']
else:
g_fact = 0.0
if 'Q' in combo.loads:
q_fact = combo.loads['Q']
else:
q_fact = 0.0
pair = (g_fact, q_fact)
if pair not in unique_pairs:
unique_pairs.add(pair)
factors.append({"G": g_fact, "Q": q_fact})
return factors
def _get_stairs_midstorey_beams(self) -> List[BeamBase]:
"""Return midstorey stairs beams along x-direction.
Returns
-------
List[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase]
List of mid-storey stairs beams along x-direction.
"""
return [self._find_beam_by_line(line)
for line in self.geometry.stairs_lines_x1]
def _get_stairs_columns_disagg(self) -> List[List[List[ColumnBase]]]:
"""Return disaggregated stairs columns.
The first level of items corresponds to each stairs columns (has
length of num stairs).
There are two second level of items corresponding to the list of z1
and z2 columns.
There are two third level of items:
for List[i][0] these are z11 and z12 columns
for List[i][1] these are z21 and z22 columns.
Returns
-------
List[List[List[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase]]]
List of disaggregated stairs columns lists.
"""
columns_list_per_stairs = []
z11_lines = self.geometry.stairs_lines_z11
z12_lines = self.geometry.stairs_lines_z12
z21_lines = self.geometry.stairs_lines_z21
z22_lines = self.geometry.stairs_lines_z22
for i in range(len(self.geometry.stairs_rectangles)):
columns = []
columns_z1 = []
columns_z2 = []
columns_z1.append(self._find_column_by_line(z11_lines[i]))
columns_z1.append(self._find_column_by_line(z12_lines[i]))
columns_z2.append(self._find_column_by_line(z21_lines[i]))
columns_z2.append(self._find_column_by_line(z22_lines[i]))
columns.append(columns_z1.copy())
columns.append(columns_z2.copy())
columns_list_per_stairs.append(columns.copy())
return columns_list_per_stairs
def _get_stairs_columns_agg(self) -> List[ColumnBase]:
"""Return aggregated list of stairs columns.
Returns
-------
List[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase]
List of all stairs columns.
"""
columns = []
z11_lines = self.geometry.stairs_lines_z11
z12_lines = self.geometry.stairs_lines_z12
z21_lines = self.geometry.stairs_lines_z21
z22_lines = self.geometry.stairs_lines_z22
for i in range(len(self.geometry.stairs_rectangles)):
columns.append(self._find_column_by_line(z11_lines[i]))
columns.append(self._find_column_by_line(z12_lines[i]))
columns.append(self._find_column_by_line(z21_lines[i]))
columns.append(self._find_column_by_line(z22_lines[i]))
return columns
def _find_beam_by_line(self, line: Line) -> Optional[BeamBase]:
"""Find the beam object with given line attribute.
Parameters
----------
line : Line
Geometric object (line) representing beam.
Returns
-------
~simdesign.rcmrf.bnsm.baselib.beam.BeamBase | None
Returns Beam object if line attribute matches with given,
otherwise, returns None.
"""
filtered_beams = filter(lambda beam: beam.line == line, self.beams)
# Retrieve the first beam matching the condition
matching_beam = next(filtered_beams, None)
return matching_beam
def _find_column_by_line(self, line: Line) -> Optional[ColumnBase]:
"""Find the column object with given line attribute.
Parameters
----------
line : Line
Geometric object (line) representing column.
Returns
-------
~simdesign.rcmrf.bnsm.baselib.column.ColumnBase | None
Returns Column object if line attribute matches with given,
otherwise, returns None.
"""
filtered_columns = filter(lambda column: column.line == line,
self.columns)
# Retrieve the first column matching the condition
matching_column = next(filtered_columns, None)
return matching_column
def _find_columns_by_beam(self, beam: BeamBase
) -> List[Optional[ColumnBase]]:
"""Find the columns at both ends of the given beam.
Parameters
----------
beam : ~simdesign.rcmrf.bdim.baselib.beam.BeamBase
Beam object for which columns are going to be found.
Returns
-------
List[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase | None]
List of found Column objects [Ci_top, Ci_bot, Cj_top, Cj_end],
Columns which are not found are equal to None.
"""
columns = []
for node in beam.elastic_nodes:
filtered_columns_top = filter(
lambda column: column.elastic_nodes[0] == node, self.columns)
filtered_columns_bot = filter(
lambda column: column.elastic_nodes[1] == node, self.columns)
# Retrieve the first columns matching the condition
top_column = next(filtered_columns_top, None)
bot_column = next(filtered_columns_bot, None)
columns.extend([top_column, bot_column])
return columns
def _find_slab_by_rectangle(self, rectangle: Rectangle
) -> Optional[SlabBase]:
"""Find slab with given rectangle attribute.
Parameters
----------
rectangle : Rectangle
Geometric object (rectangle) representing slab.
Returns
-------
SlabBase | None
Returns Slab object if rectangle attribute matches with
given, otherwise, returns None.
"""
filtered_slabs = filter(
lambda slab: slab.rectangle == rectangle, self.slabs)
# Retrieve the first slab matching the condition
matching_slab = next(filtered_slabs, None)
return matching_slab
def _find_stairs_by_rectangle(self, rectangle: Rectangle
) -> Optional[StairsBase]:
"""Find stairs with given rectangle attribute.
Parameters
----------
rectangle : Rectangle
Geometric object (rectangle) representing stairs.
Returns
-------
StairsBase | None
Returns Stairs object if rectangle attribute matches with
given, otherwise, returns None.
"""
filtered_stairs = filter(
lambda slab: slab.rectangle == rectangle, self.stairs)
# Retrieve the first slab matching the condition
matching_stairs = next(filtered_stairs, None)
return matching_stairs
def _find_elements_by_point(self, node: Point) -> Tuple[
Optional[BeamBase],
Optional[BeamBase],
Optional[BeamBase],
Optional[BeamBase],
Optional[ColumnBase],
Optional[ColumnBase],
]:
"""Find the elements connected to the given node/point.
Parameters
----------
node : Point
Point instance used for search.
Returns
-------
Tuple[ Optional[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase], \
Optional[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase],
Optional[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase], \
Optional[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase],
Optional[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase], \
Optional[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase] ]
0. If exists left beam along global x-axis, otherwise None,
1. If exists right beam along global x-axis, otherwise None,
2. If exists front beam along global y-axis, otherwise None,
3. If exists rear beam along global y-axis, otherwise None,
4. If exists top column along global z-axis, otherwise None,
5. If exists bottom column along global z-axis, otherwise None
"""
left_beamx = None
right_beamx = None
front_beamy = None
rear_beamy = None
top_column = None
bottom_column = None
lines = self.geometry.find_lines_by_point(node)
for line in lines:
beam = self._find_beam_by_line(line)
if beam:
if np.all(line.unit_vector == np.array([1.0, 0.0, 0.0])):
if node == beam.elastic_nodes[0]:
right_beamx = beam
elif node == beam.elastic_nodes[1]:
left_beamx = beam
elif np.all(line.unit_vector == np.array([0.0, 1.0, 0.0])):
if node == beam.elastic_nodes[0]:
front_beamy = beam
elif node == beam.elastic_nodes[1]:
rear_beamy = beam
else:
column = self._find_column_by_line(line)
if column:
if node == column.elastic_nodes[0]:
top_column = column
elif node == column.elastic_nodes[1]:
bottom_column = column
return (
left_beamx, right_beamx,
front_beamy, rear_beamy,
top_column, bottom_column
)
def _find_joint_by_point(self, node: Point) -> Optional[JointBase]:
"""Find the joints by the given node/point.
Parameters
----------
node : Point
Point instance used for search.
Returns
-------
~simdesign.rcmrf.bdim.baselib.joint.JointBase | None
Returns Joint object if elastic_node attribute matches with
given node, otherwise, returns None.
"""
filtered_joints = filter(
lambda joint: joint.elastic_node == node, self.joints)
# Retrieve the first joint matching the condition
matching_joint = next(filtered_joints, None)
return matching_joint
def _set_beam_gravity_loads(self) -> None:
"""Define the uniformly distributed gravity loads for beams.
The loads transferred from slabs are not factored by alpha.
Hence, alpha factors corresponding to loads transferred from
each slab are also set.
TODO
----
I think we can still assume partitions exist. Even if
they do not have influence on the lateral capacity (e.g., drywalls)
and we can add include their weights as default on the beams.
"""
# Add loads to floor beams
for slab in self.slabs:
beams = [self._find_beam_by_line(line)
for line in slab.rectangle.lines]
# Loop through each slab beam
for i, beam in enumerate(beams):
tributary_area = slab.beam_tributary_areas[i]
alpha = slab.beam_alpha_coeffs[i]
# Add loads from slabs
beam.slab_alpha.append(alpha)
beam.slab_wg.append(tributary_area * slab.pg / beam.L)
beam.slab_wq.append(tributary_area * slab.pq / beam.L)
# Add loads to the beams supporting staircase
mid_beams = self.stairs_midstorey_beams
for i, slab in enumerate(self.stairs):
# Beams with staircase loads
mid_beam = mid_beams[i]
rect_beams = [self._find_beam_by_line(line)
for line in slab.rectangle.lines]
# Set the loads for mid-level beam
mid_beam.stairs_wg = slab.beam_influence_areas * \
slab.pg / mid_beam.L
mid_beam.stairs_wq = slab.beam_influence_areas * \
slab.pq / mid_beam.L
for j, beam in enumerate(rect_beams):
if j == 1: # The beam which supports staircase
beam.stairs_wg = slab.beam_influence_areas * \
slab.pg / beam.L
beam.stairs_wq = slab.beam_influence_areas * \
slab.pq / beam.L
# Add superimposed gravity loads from the infills to the beams
for infill in self.infills:
infill.set_beam_infill_load()
def _set_beam_predesign_forces(self) -> None:
"""Define the beam predesign forces (mid-span moments and shears).
The expected predesign forces are computed based on the gravity loads.
The forces computed herein should be used only for the preliminary
design of the beams.
"""
# Get the gravity load combinations
combos = self.loads.get_gravity_load_combos()
for beam in self.beams:
Md = 0.0
Vd = 0.0
for combo in combos:
g_fact = combo.loads['G']
q_fact = combo.loads['Q']
# Moment at mid-span (alpha factored load)
Md_tmp = g_fact * beam.simple_Mg + q_fact * beam.simple_Mq
# Compute the shear at the support
Vd_tmp = g_fact * beam.simple_Vg + q_fact * beam.simple_Vq
# Update maximums
Md = max(Md, Md_tmp)
Vd = max(Vd, Vd_tmp)
# Save the pre-design loads
beam.pre_Md = Md
beam.pre_Vd = Vd
def _set_column_predesign_forces(self) -> None:
"""Define the predesign forces (axial forces) for columns.
The expected axial forces are first computed due to the gravity loads.
These are then factored to account for increase in column axial forces
due to the lateral loads in frame. The forces computed herein should be
used only for the preliminary design of columns.
"""
# Get the gravity load combinations
grav_combos = self.loads.get_gravity_load_combos()
# Get the gravity load combinations
seism_combos = self.loads.get_seismic_load_combos()
# Get properties of all vertical lines in the geometry
vertical_lines = self.geometry.lines_z
facade_ids = self.geometry.lines_z_facades
max_floor = max(self.geometry.system_grid_data.z.ids)
# Loop through each column and compute the axial forces due to
# gravity loads of floor beams and column self weights.
for column in self.columns:
# Get position factor
_, p2 = column.elastic_nodes
if p2.grid_ids[2] > max_floor / 2:
lvl = "top"
else:
lvl = "bot"
line_idx = vertical_lines.index(column.line)
facade_id = facade_ids[line_idx]
if facade_id == 0:
loc = 'central'
else:
loc = 'exterior'
factor = self.COLUMN_POSITION_FACTORS[lvl][loc]
column.position_factor = self.COLUMN_POSITION_FACTORS[lvl][loc]
# Find the lines of beams transfering gravity loads
lines = self.geometry.find_lines_by_point(p2)
# Compute the gravity loads from beams
Ng_factored = 0.0
Nq_factored = 0.0
Ng = 0.0
Nq = 0.0
for line in lines:
# Skip the columns
if line not in vertical_lines:
beam = self._find_beam_by_line(line)
Ng += (beam.wg_total * beam.L) / 2
Nq += (beam.wq_total * beam.L) / 2
Ng_factored += factor * (beam.wg_total * beam.L) / 2
Nq_factored += factor * (beam.wq_total * beam.L) / 2
# Add the self weight of column
Ng_factored += column.self_wg * column.H
Ng += column.self_wg * column.H
column.pre_Nq_pos = Nq_factored
column.pre_Ng_pos = Ng_factored
column.pre_Nq = Nq
column.pre_Ng = Ng
# If the the column is continuous, add upper storey forces
for _, column_lists in self.continuous_columns.items():
for columns in column_lists:
columns.reverse()
for i, column in enumerate(columns):
if i != 0:
column_i_1 = columns[i - 1]
column.pre_Nq_pos += column_i_1.pre_Nq_pos
column.pre_Ng_pos += column_i_1.pre_Ng_pos
column.pre_Nq += column_i_1.pre_Nq
column.pre_Ng += column_i_1.pre_Ng
# Compute maximum resulting axial forces
for column in self.columns:
Nd = 0.0
# From gravity load combinations
for combo in grav_combos:
g_fact = combo.loads["G"]
q_fact = combo.loads["Q"]
tmp = (g_fact * column.pre_Ng
+ q_fact * column.pre_Nq)
Nd = max(tmp, Nd)
# From seismic load combinations
# NOTE: This could be also linked to the beta coefficient
if self.beta > 0.01:
for combo in seism_combos:
g_fact = combo.masses["G"]
q_fact = combo.masses["Q"]
tmp = (g_fact * column.pre_Ng_pos
+ q_fact * column.pre_Nq_pos)
Nd = max(tmp, Nd)
column.pre_Nd = Nd
def _uniformize_stairs_columns(self) -> None:
"""Uniformize the continuous columns of the same stairs.
"""
stairs_columns_disagg = self._get_stairs_columns_disagg()
for columns_list in stairs_columns_disagg:
for columns in columns_list:
bot_col, top_col = columns
bx_max = max(bot_col.bx, top_col.bx)
by_max = max(bot_col.by, top_col.by)
bot_col.bx = bx_max
bot_col.by = by_max
top_col.bx = bx_max
top_col.by = by_max
def _ensure_column_dim_consistency(self) -> None:
"""Ensure lower column dimensions are always greater or equal.
"""
for _, column_lists in self.continuous_columns.items():
for columns in column_lists:
columns.reverse()
for i, col in enumerate(columns):
if i != 0: # Skip upper column
top_col = columns[i - 1]
bx_max = max([col.bx, top_col.bx])
by_max = max([col.by, top_col.by])
if col.bx <= bx_max:
col.bx = bx_max
col.by = by_max
def _uniformize_columns_geometry(self) -> None:
"""Uniformize column section dimensions on the same continuous lines.
Notes
-----
If step size is only 1 storey lower than total number of stories in the
building a constant section is used for column along the building.
"""
# Uniformize the continuous columns of same stairs
# NOTE: This seems reduntant for now, following is enough.
self._uniformize_stairs_columns()
# Ensuring bottom column dimensions are always greater or equal
self._ensure_column_dim_consistency()
# Start section uniformisation
step = self.COLUMN_UNIFORMIZATION_STEP # step
if step is None or (step >= self.num_storeys - 1): # Constant section
# If step is None or
# If the difference between step and number of storeys is 1
for _, column_lists in self.continuous_columns.items():
for columns in column_lists:
# Get the maximums
bx = max(column.bx for column in columns)
by = max(column.by for column in columns)
# Assign maximum width to each column
for column in columns:
column.bx = bx
column.by = by
else:
all_stairs_columns = self._get_stairs_columns_agg()
for _, column_lists in self.continuous_columns.items():
for columns in column_lists:
# Find the columns with equal sections
steps = [0.5 if column in all_stairs_columns else 1.0
for column in columns]
levels = np.cumsum(steps)
remainders = levels % step
slices: List[List[ColumnBase]] = []
start = 0
for i, remainder in enumerate(remainders):
if remainder == 0:
slices.append(columns[start:i + 1])
start = i + 1
# Append the last slice if necessary
if remainders[-1] != 0:
slices.append(columns[start:])
# Now that we identified the columns with equal sections
bx = 0
by = 0
for cols in slices:
# Maximum dimensions per "step" columns
bx_tmp = max(column.bx for column in cols)
by_tmp = max(column.by for column in cols)
# To ensure that max difference is 0.10 m between
# consecutive varying sections
bx = ceil(20 * max(bx_tmp, bx - 0.10)) / 20
by = ceil(20 * max(by_tmp, by - 0.10)) / 20
for column in cols:
column.bx = bx
column.by = by
def _uniformize_beam_geometry(self) -> None:
"""Uniformize the beam section dimensions on the same continuous lines.
"""
# Beams along x direction
for _, beam_lists in self.continuous_beams_x.items():
for beams in beam_lists:
b_max = ceil(20 * max(beam.b for beam in beams)) / 20
h_max = ceil(20 * max(beam.h for beam in beams)) / 20
for beam in beams:
beam.b = b_max
beam.h = h_max
# Beams along y direction
for _, beam_lists in self.continuous_beams_y.items():
for beams in beam_lists:
b_max = ceil(20 * max(beam.b for beam in beams)) / 20
h_max = ceil(20 * max(beam.h for beam in beams)) / 20
for beam in beams:
beam.b = b_max
beam.h = h_max
def _predesign(self) -> None:
"""Make preliminary design of the building beams and columns.
1) Makes initial guess for section dimensions.
2) Uniformizes the column sections along the continuous lines.
3) Uniformizes the beam sections along the continuous lines.
4) Store preliminary design dimensions.
"""
# Set design forces for columns and beams
self._set_column_predesign_forces()
self._set_beam_predesign_forces()
# Predesign columns, guess the initial dimensions
for column in self.columns:
column.predesign_section_dimensions()
# Uniformize column sections
self._uniformize_columns_geometry()
# Set preliminary design dimensions
for column in self.columns:
column.pre_bx = column.bx
column.pre_by = column.by
# Predesign beams, guess the initial dimensions
for beam in self.beams:
beam.predesign_section_dimensions(self.slab_thickness)
# Uniformize beam sections
self._uniformize_beam_geometry()
# Set preliminary design dimensions
for beam in self.beams:
beam.pre_b = beam.b
beam.pre_h = beam.h
def _change_materials(self) -> None:
"""The method used for changing materials in iterative design
algorithm.
Can be overwritten for each design class.
"""
# Update the concrete material if it is not the last one
if self.next_concrete:
self.concrete = self.next_concrete
# Update the steel material if it is not the last one
if self.next_steel:
self.steel = self.next_steel
def _change_beam_type(self) -> None:
"""The method used for changing beam types.
Can be overwritten for each design class.
"""
self.beam_type = 2
def _change_slab_type(self) -> None:
"""The method used for changing slab types.
Can be overwritten for each design class.
"""
aspect_ratios = [max(slab.lx / slab.ly, slab.ly / slab.lx)
for slab in self.slabs]
if self.slab_type == 3 and max(aspect_ratios) < 2:
self.slab_type = 1
elif self.slab_type == 3:
self.slab_type = 2
def _change_column_section(self) -> None:
"""The method used for changing column sections.
Can be overwritten for each design class.
"""
self.column_section = 1
def _increase_beam_dimensions(self) -> None:
"""Used to increase beam dimensions in iterative design algorithm.
"""
for beam in self.beams:
beam.increase_dimensions()
def _increase_column_dimensions(self) -> None:
"""Used to increase column dimensions in iterative design algorithm.
"""
for column in self.columns:
column.increase_dimensions()
def _restore_dimensions(self) -> None:
"""Restore beam and column dimensions from preliminary design."""
for column in self.columns:
column.restore_dimensions()
for beam in self.beams:
beam.restore_dimensions()
def _restore_materials(self) -> None:
"""Restore beam and column materials to initial."""
self._initialize_materials()
def _set_restore_point(self) -> None:
"""Sets the restore point for beams and columns."""
for column in self.columns:
column.set_restore_point()
for beam in self.beams:
beam.set_restore_point()
def _perform_structural_analyses(self) -> None:
"""Analyze the building with all the load cases and combinations.
"""
# Create the elastic model with current beam and column dimensions
elastic_model = self.ElasticModelClass(
self.beams, self.columns, self.loads, self.geometry, self.beta)
# Determine element forces for each loading combination
elastic_model.analyze_for_all()
def _make_trial_structural_design(self) -> None:
"""Make a trial structural design and checks if it is ok or not.
"""
self._design_beams_for_flexure()
self._design_columns_for_flexure()
self._design_beams_for_shear()
self._design_columns_for_shear()
self._validate_structural_design()
def _validate_structural_design(self) -> None:
"""Determine whether the current design is satisfactory or not.
All structural element should meet the design criteria.
"""
if self.beams_fail or self.columns_fail:
self.ok = False
else:
self.ok = True
def __print_beam_failure(self, text: str) -> None:
"""Print the number of failed beams and their dimensions.
"""
idxs = [i for i, beam in enumerate(self.beams) if not beam.ok]
print(
len(idxs), f"beams failed to pass {text} design verification."
)
# for idx in idxs:
# print(f"beam:{idx}, h={self.beams[idx].h},"
# f"b={self.beams[idx].b}")
def __print_column_failure(self, text: str) -> None:
"""Print the number of failed columns and their dimensions.
"""
idxs = [i for i, col in enumerate(self.columns)
if not (col.ok_x and col.ok_y)]
print(
f"{len(idxs)} columns failed to pass {text} design verification."
)
# for idx in idxs:
# print(f"Column:{idx}, bx={self.columns[idx].bx},"
# f"by={self.columns[idx].by}")
def _validate_beam_section_dimensions(self) -> None:
"""Check if section dimensions of beams are adequate and
validate against maximum dimensions."""
for beam in self.beams:
beam.verify_section_adequacy()
beam.validate_section_dimensions()
def _validate_column_section_dimensions(self) -> None:
"""Check if section dimensions of columns are adequate and
validate against maximum dimensions."""
for column in self.columns:
column.verify_section_adequacy()
column.validate_section_dimensions()
def _design_beams_for_flexure(self) -> None:
"""Perform flexure design of the beams.
1. Perform section adequacy checks (e.g., max. normalized moment).
2. If the section is adequate for given design forces,
compute required longitudinal reinforcement area. Then,
find the rebar solution to meet detailing requirements and validate.
"""
# STAGE 1: Section adequacy check for design forces.
self._validate_beam_section_dimensions()
if self.beams_fail:
self.__print_beam_failure('stage 1 flexure')
return # Beams are not ok, exit and try again with new section
# STAGE 2: Longitudinal reinforcement design.
for beam in self.beams:
beam.compute_required_longitudinal_reinforcement()
for beams in self._continuous_beams:
# Append required information of continuous beams
Asl_top = np.array([])
Asl_bot = np.array([])
b = np.array([])
for beam in beams:
Asl_top = np.append(Asl_top, beam.Asl_top_req)
Asl_bot = np.append(Asl_bot, beam.Asl_bot_req)
b = np.append(b, np.array([beam.b] * len(beam.Asl_top_req)))
# Determine rebar arrangement
(
dbl_t1, nbl_t1, dbl_t2, nbl_t2,
dbl_b1, nbl_b1, dbl_b2, nbl_b2
) = self.rebars.get_beam_long_rebars(Asl_top, Asl_bot, b)
# NOTE: Use the same reinforcement at two adjacent beam ends
# Required for lap splices at joints (cont. reinf.)
# NOTE: This could be unrealistic for adjacent spans with
# very different lengths
for kk in range(2, len(nbl_b2) - 3, 3):
nbl_t1[kk] = max(nbl_t1[kk], nbl_t1[kk + 1])
nbl_t1[kk + 1] = max(nbl_t1[kk], nbl_t1[kk + 1])
nbl_t2[kk] = max(nbl_t2[kk], nbl_t2[kk + 1])
nbl_t2[kk + 1] = max(nbl_t2[kk], nbl_t2[kk + 1])
nbl_b1[kk] = max(nbl_b1[kk], nbl_b1[kk + 1])
nbl_b1[kk + 1] = max(nbl_b1[kk], nbl_b1[kk + 1])
nbl_b2[kk] = max(nbl_b2[kk], nbl_b2[kk + 1])
nbl_b2[kk + 1] = max(nbl_b2[kk], nbl_b2[kk + 1])
# Store rebar solutions and validate
for i, beam in enumerate(beams):
beam.dbl_t1 = dbl_t1[3 * i:3 * (i + 1)]
beam.nbl_t1 = nbl_t1[3 * i:3 * (i + 1)]
beam.dbl_t2 = dbl_t2[3 * i:3 * (i + 1)]
beam.nbl_t2 = nbl_t2[3 * i:3 * (i + 1)]
beam.dbl_b1 = dbl_b1[3 * i:3 * (i + 1)]
beam.nbl_b1 = nbl_b1[3 * i:3 * (i + 1)]
beam.dbl_b2 = dbl_b2[3 * i:3 * (i + 1)]
beam.nbl_b2 = nbl_b2[3 * i:3 * (i + 1)]
beam.validate_longitudinal_reinforcement()
# Check if beams fail stage 2 flexure design verification
if self.beams_fail:
self.__print_beam_failure('stage 2 flexure')
def _set_column_capacity_design_moments(self) -> None:
"""Append the capacity design moments to the list of design forces
for columns.
Notes
-----
EN 1998-1:2004, Clause 4.4.2.3(4).
References
----------
Comité Européen de Normalisation, CEN (2004). Eurocode 8: Design of
Structures for Earthquake Resistance — Part 1: General Rules,
Seismic Actions and Rules for Buildings.
European Committee for Standardization, Brussels, Belgium.
"""
if self.OVERSTRENGTH_FACTOR_COLUMN_MOMENT is None: # Do not add
return
else:
gamma_rd = self.OVERSTRENGTH_FACTOR_COLUMN_MOMENT
# Unique gravity load factors from seismic load combinations
combo_grav_factors = self._get_unique_seism_combo_grav_factors()
# Loop through each beam-column joint
for joint in self.joints:
# Sum of the design values of moment of resistances for beams
sum_mrdb_x_pos = 0.0
sum_mrdb_x_neg = 0.0
sum_mrdb_y_pos = 0.0
sum_mrdb_y_neg = 0.0
if joint.left_beam:
sum_mrdb_x_pos += joint.left_beam.mrd_pos[-1]
sum_mrdb_x_neg += joint.left_beam.mrd_neg[-1]
if joint.right_beam:
sum_mrdb_x_pos += joint.right_beam.mrd_neg[0]
sum_mrdb_x_neg += joint.right_beam.mrd_pos[0]
if joint.rear_beam:
sum_mrdb_y_pos += joint.rear_beam.mrd_pos[-1]
sum_mrdb_y_neg += joint.rear_beam.mrd_neg[-1]
if joint.front_beam:
sum_mrdb_y_pos += joint.front_beam.mrd_neg[0]
sum_mrdb_y_neg += joint.front_beam.mrd_pos[0]
sum_mrdb_y = max(sum_mrdb_y_pos, sum_mrdb_y_neg)
sum_mrdb_x = max(sum_mrdb_x_pos, sum_mrdb_x_neg)
# Capacity design forces for columns
for factors in combo_grav_factors:
# Both top and bottom columns exist
if joint.top_column and joint.bottom_column:
forces_top = (
factors['G'] * joint.top_column.forces['G/seismic']
+ factors['Q'] * joint.top_column.forces['Q/seismic']
)
forces_bottom = (
factors['G'] * joint.bottom_column.forces['G/seismic']
+ factors['Q']
* joint.bottom_column.forces['Q/seismic']
)
forces_top.Mx1 = 0.5 * gamma_rd * sum_mrdb_y
forces_top.My1 = 0.5 * gamma_rd * sum_mrdb_x
joint.top_column.design_forces.append(forces_top)
forces_bottom.Mx9 = 0.5 * gamma_rd * sum_mrdb_y
forces_bottom.My9 = 0.5 * gamma_rd * sum_mrdb_x
joint.bottom_column.design_forces.append(forces_bottom)
# Only top column exists
elif joint.top_column:
forces_top = (
factors['G'] * joint.top_column.forces['G/seismic']
+ factors['Q'] * joint.top_column.forces['Q/seismic']
)
forces_top.Mx1 = gamma_rd * sum_mrdb_y
forces_top.My1 = gamma_rd * sum_mrdb_x
joint.top_column.design_forces.append(forces_top)
# Only bottom column exists
elif joint.bottom_column:
forces_bottom = (
factors['G'] * joint.bottom_column.forces['G/seismic']
+ factors['Q']
* joint.bottom_column.forces['Q/seismic']
)
forces_bottom.Mx9 = gamma_rd * sum_mrdb_y
forces_bottom.My9 = gamma_rd * sum_mrdb_x
joint.bottom_column.design_forces.append(forces_bottom)
def _set_beam_capacity_design_shear_forces(self) -> None:
"""Append the beam capacity design shear forces to the list of
beam design forces.
Notes
-----
EN 1998-1:2004, Clause 5.4.2.2.
References
----------
Comité Européen de Normalisation, CEN (2004). Eurocode 8: Design of
Structures for Earthquake Resistance — Part 1: General Rules,
Seismic Actions and Rules for Buildings.
European Committee for Standardization, Brussels, Belgium.
"""
def get_sum_mrdb_at_joint(joint: JointBase) -> Tuple[float, float]:
"""Get the summation of moment of resistances of joint beams.
Parameters
----------
joint : ~simdesign.rcmrf.bdim.baselib.joint.JointBase
Instance of the Joint object.
Returns
-------
Tuple[float, float]
Summation of moment of resistances of joint beams for
positive and negative directions.
"""
# Sum of the design values of moment of resistances for beams
sum_mrd_pos = 0.0 # In positive direction
sum_mrd_neg = 0.0 # In negative direction
if beam.direction == 'x':
# Beam is along x-axis
if joint.left_beam:
sum_mrd_pos += joint.left_beam.mrd_pos[-1]
sum_mrd_neg += joint.left_beam.mrd_neg[-1]
if joint.right_beam:
sum_mrd_pos += joint.right_beam.mrd_neg[0]
sum_mrd_neg += joint.right_beam.mrd_pos[0]
elif beam.direction == 'y':
# Beam is along y-axis
if joint.rear_beam:
sum_mrd_pos += joint.rear_beam.mrd_pos[-1]
sum_mrd_neg += joint.rear_beam.mrd_neg[-1]
if joint.front_beam:
sum_mrd_pos += joint.front_beam.mrd_neg[0]
sum_mrd_neg += joint.front_beam.mrd_pos[0]
return sum_mrd_pos, sum_mrd_neg
def get_sum_mrdc_at_joint(joint: JointBase) -> float:
"""Get the summation of moment of resistances of joint columns.
Parameters
----------
joint : ~simdesign.rcmrf.bdim.baselib.joint.JointBase
Instance of the Joint object.
Returns
-------
float
Summation of moment of resistances of joint columns.
"""
# Sum of the design values of moment of resistances for columns
sum_mrd = 0.0 # Same for positive and negative directions
# Top column exists
if joint.top_column:
forces_top = (
factors['G'] * joint.top_column.forces['G/seismic']
+ factors['Q'] * joint.top_column.forces['Q/seismic']
)
if beam.direction == 'x': # use column mrd_y
sum_mrd += joint.top_column.get_mrdy(Ned=forces_top.N1)
elif beam.direction == 'y': # use column mrd_x
sum_mrd += joint.top_column.get_mrdx(Ned=forces_top.N1)
# Bottom column exists
if joint.bottom_column:
forces_bottom = (
factors['G'] * joint.bottom_column.forces['G/seismic']
+ factors['Q'] * joint.bottom_column.forces['Q/seismic']
)
if beam.direction == 'x': # Use column mrd_y
sum_mrd += joint.bottom_column.get_mrdy(
Ned=forces_bottom.N9)
elif beam.direction == 'y': # Use column mrd_x
sum_mrd += joint.bottom_column.get_mrdx(
Ned=forces_bottom.N9)
return sum_mrd
def get_beam_clear_length() -> float:
"""Get beam clear length (distance between column faces).
Returns
-------
float
Beam clear length.
"""
# i'th joint width
if joint_i.top_column and joint_i.bottom_column:
if beam.direction == 'x':
bc_i = min(joint_i.top_column.bx, joint_i.bottom_column.bx)
elif beam.direction == 'y':
bc_i = min(joint_i.top_column.by, joint_i.bottom_column.by)
elif joint_i.top_column:
if beam.direction == 'x':
bc_i = joint_i.top_column.bx
if beam.direction == 'y':
bc_i = joint_i.top_column.by
elif joint_i.bottom_column:
if beam.direction == 'x':
bc_i = joint_i.bottom_column.bx
if beam.direction == 'y':
bc_i = joint_i.bottom_column.by
# j'th joint width
if joint_j.top_column and joint_j.bottom_column:
if beam.direction == 'x':
bc_j = min(joint_j.top_column.bx, joint_j.bottom_column.bx)
elif beam.direction == 'y':
bc_j = min(joint_j.top_column.by, joint_j.bottom_column.by)
elif joint_j.top_column:
if beam.direction == 'x':
bc_j = joint_j.top_column.bx
if beam.direction == 'y':
bc_j = joint_j.top_column.by
elif joint_j.bottom_column:
if beam.direction == 'x':
bc_j = joint_j.bottom_column.bx
if beam.direction == 'y':
bc_j = joint_j.bottom_column.by
# Return beam clear length
return beam.L - (bc_i + bc_j) / 2
# Check if the capacity design forces will be added
if self.OVERSTRENGTH_FACTOR_BEAM_SHEAR is None: # Do not add
return
else: # Add
gamma_rd = self.OVERSTRENGTH_FACTOR_BEAM_SHEAR
# Unique gravity load factors from seismic load combinations
combo_grav_factors = self._get_unique_seism_combo_grav_factors()
# Loop through each beam
for beam in self.beams:
# Find joints at both ends
joint_i = self._find_joint_by_point(beam.elastic_nodes[0])
joint_j = self._find_joint_by_point(beam.elastic_nodes[1])
# Sum of the moment of resistances of the joint beams
sum_mrdb_i_pos, sum_mrdb_i_neg = get_sum_mrdb_at_joint(joint_i)
sum_mrdb_j_pos, sum_mrdb_j_neg = get_sum_mrdb_at_joint(joint_j)
# Clear length of beam
beam_lc = get_beam_clear_length()
# Capacity design forces for columns
for factors in combo_grav_factors:
# Shear force due to gravity loads (g+nq)
Vd = (factors['G'] * beam.wg_total
+ factors['Q'] * beam.wq_total) * (beam_lc / 2)
# Sum of the moment of resistances of the joint columns
sum_mrdc_i = get_sum_mrdc_at_joint(joint_i)
sum_mrdc_j = get_sum_mrdc_at_joint(joint_j)
# The beam moment of resistances at both ends
Mpi_pos = gamma_rd * beam.mrd_pos[0] * min(
1.0, sum_mrdc_i / sum_mrdb_i_pos)
Mpi_neg = gamma_rd * beam.mrd_neg[0] * min(
1.0, sum_mrdc_i / sum_mrdb_i_neg)
Mpj_pos = gamma_rd * beam.mrd_pos[-1] * min(
1.0, sum_mrdc_j / sum_mrdb_j_pos)
Mpj_neg = gamma_rd * beam.mrd_neg[-1] * min(
1.0, sum_mrdc_j / sum_mrdb_j_neg)
# Capacity design shear forces at both ends
Ved_i_pos = Vd + (Mpi_pos + Mpj_neg) / beam_lc
Ved_i_neg = Vd - (Mpi_neg + Mpj_pos) / beam_lc
Ved_j_pos = Vd + (Mpj_pos + Mpi_neg) / beam_lc
Ved_j_neg = Vd - (Mpj_neg + Mpi_pos) / beam_lc
# Set the forces and append
forces = beam.design_forces[-1] - beam.design_forces[-1]
forces.case = 'seismic'
forces.V1 = max(Ved_i_pos, abs(Ved_i_neg))
forces.V9 = max(Ved_j_pos, abs(Ved_j_neg))
"""TODO: Set also design moments as Mp values?
It does not makes sense to make another check for design
moment though. Hence, we might need to separate section
adequacy verification for shear and flexure forces.
"""
beam.design_forces.append(forces)
def _design_beams_for_shear(self) -> None:
"""Make shear design of the beams.
1. Check if beam and column flexure designs are ok, if ok continue.
2. Add capacity design shear forces to the design forces list. Then,
perform section adequacy checks (e.g., checks shear stress).
If beam sections are ok, continue.
3. Compute required shear reinforcement (area / spacing). Then,
find the rebar solution to meet detailing requirements and validate.
"""
# STAGE 1: Check beam and column flexure designs.
if self.beams_fail or self.columns_fail:
return # Beams or columns are not ok, exit and try again
# STAGE 2: Section adequacy check for capacity design forces
self._set_beam_capacity_design_shear_forces()
self._validate_beam_section_dimensions()
if self.beams_fail:
self.__print_beam_failure('stage 1 shear')
return # Beams are not ok, exit and try again with new section
# STAGE 3: Transverse reinforcement design
for beam in self.beams:
# Compute required transverse reinforcement
beam.compute_required_transverse_reinforcement()
# Determine transverse reinforcement (horizontal rebar) solution
for beams in self._continuous_beams:
# Append required information of continuous beams
Ash_sbh = np.array([])
nbl_t1 = np.array([])
nbl_t2 = np.array([])
nbl_b1 = np.array([])
nbl_b2 = np.array([])
dbl_t1 = np.array([])
dbl_b1 = np.array([])
b = np.array([])
h = np.array([])
for beam in beams:
Ash_sbh = np.append(Ash_sbh, beam.Ash_sbh_req)
nbl_t1 = np.append(nbl_t1, beam.nbl_t1)
nbl_t2 = np.append(nbl_t2, beam.nbl_t2)
nbl_b1 = np.append(nbl_b1, beam.nbl_b1)
nbl_b2 = np.append(nbl_b2, beam.nbl_b2)
dbl_t1 = np.append(dbl_t1, beam.dbl_t1)
dbl_b1 = np.append(dbl_b1, beam.dbl_b1)
b = np.append(b, np.array([beam.b] * len(beam.Asl_top_req)))
h = np.append(h, np.array([beam.h] * len(beam.Asl_top_req)))
# Determine transverse reinforcement solution
dbh, sbh, nbh_x, nbh_y = self.rebars.get_beam_transv_rebars(
Ash_sbh, nbl_t1, nbl_t2, nbl_b1,
nbl_b2, dbl_t1, dbl_b1, b, h
)
# Store rebar solutions and validate
for i, beam in enumerate(beams):
beam.dbh = dbh[3 * i:3 * (i + 1)]
beam.sbh = sbh[3 * i:3 * (i + 1)]
beam.nbh_b = nbh_x[3 * i:3 * (i + 1)]
beam.nbh_h = nbh_y[3 * i:3 * (i + 1)]
beam.validate_transverse_reinforcement()
if self.beams_fail:
self.__print_beam_failure('stage 2 shear')
def _design_columns_for_flexure(self) -> None:
"""Make flexure design of the columns.
1. Check if beam flexure designs are ok. If beams are ok, continue.
2. Add capacity design moments to the design forces list. Then,
perform section adequacy checks (e.g., checks shear stress).
If column sections are ok, continue.
3. Compute required longitudinal reinforcement area. Then,
find the rebar solution to meet detailing requirements and validate.
"""
# STAGE 1: Check beam flexure designs
if self.beams_fail: # Beam sections are not verified.
# Do section adequacy checks regardless (for faster convergence)
self._validate_column_section_dimensions()
return # Beams are not ok, exit and try again.
# STAGE 2: Section adequacy checks with capacity design moments.
self._set_column_capacity_design_moments()
self._validate_column_section_dimensions()
if self.columns_fail:
self.__print_column_failure('stage 1 flexure')
return # Columns are not ok, exit and try again
# STAGE 3: Longitudinal reinforcement design
for col in self.columns:
col.compute_required_longitudinal_reinforcement()
for columns in self._continuous_columns:
# Append required information of continuous columns
Aslx_req = np.array([])
Asly_req = np.array([])
rhol_min = np.array([])
bx = np.array([])
by = np.array([])
for col in columns:
Aslx_req = np.append(Aslx_req, col.Aslx_req)
Asly_req = np.append(Asly_req, col.Asly_req)
rhol_min = np.append(rhol_min, col.rhol_min)
bx = np.append(bx, col.bx)
by = np.append(by, col.by)
# Determine rebar arrangement
(
dbl_cor, dbl_int, nblx_int, nbly_int
) = self.rebars.get_column_long_rebars(
Aslx_req, Asly_req, rhol_min, bx, by
)
# Store rebar solutions and validate
for i, col in enumerate(columns):
col.dbl_cor = dbl_cor[i]
col.dbl_int = dbl_int[i]
col.nblx_int = nblx_int[i]
col.nbly_int = nbly_int[i]
col.validate_longitudinal_reinforcement()
# Check if columns fail stage 2 flexure design verification
if self.columns_fail:
self.__print_column_failure('stage 2 flexure')
def _set_column_capacity_design_shear_forces(self) -> None:
"""Append the column capacity design shear forces to the list of
column design forces.
Notes
-----
EN 1998-1:2004, Clause 5.4.2.3.
References
----------
Comité Européen de Normalisation, CEN (2004). Eurocode 8: Design of
Structures for Earthquake Resistance — Part 1: General Rules,
Seismic Actions and Rules for Buildings.
European Committee for Standardization, Brussels, Belgium.
"""
def get_sum_mrdb_at_joint(joint: JointBase) -> Tuple[float, float]:
"""Get the summation of moment of resistances of joint beams.
Parameters
----------
joint : ~simdesign.rcmrf.bdim.baselib.joint.JointBase
Instance of the Joint object.
Returns
-------
Tuple[float, float]
Summation of moment of resistances of joint beams for
positive and negative directions.
"""
# Sum of the design values of moment of resistances for beams
sum_mrdb_x_pos = 0.0
sum_mrdb_x_neg = 0.0
sum_mrdb_y_pos = 0.0
sum_mrdb_y_neg = 0.0
if joint.left_beam:
sum_mrdb_x_pos += joint.left_beam.mrd_pos[-1]
sum_mrdb_x_neg += joint.left_beam.mrd_neg[-1]
if joint.right_beam:
sum_mrdb_x_pos += joint.right_beam.mrd_neg[0]
sum_mrdb_x_neg += joint.right_beam.mrd_pos[0]
if joint.rear_beam:
sum_mrdb_y_pos += joint.rear_beam.mrd_pos[-1]
sum_mrdb_y_neg += joint.rear_beam.mrd_neg[-1]
if joint.front_beam:
sum_mrdb_y_pos += joint.front_beam.mrd_neg[0]
sum_mrdb_y_neg += joint.front_beam.mrd_pos[0]
return (sum_mrdb_x_pos, sum_mrdb_x_neg,
sum_mrdb_y_pos, sum_mrdb_y_neg)
def get_sum_mrdc_at_joint(joint: JointBase) -> Tuple[float, float]:
"""Get the summation of moment of resistances of joint columns.
Parameters
----------
joint : ~simdesign.rcmrf.bdim.baselib.joint.JointBase
Instance of the Joint object.
Returns
-------
Tuple[float, float]
Summation of moment of resistances of joint columns around
local x and y axes of columns.
"""
# Sum of the design values of moment of resistances for columns
sum_mrd_x = 0.0
sum_mrd_y = 0.0
# Top column exists
if joint.top_column:
forces_top = (
factors['G'] * joint.top_column.forces['G/seismic']
+ factors['Q'] * joint.top_column.forces['Q/seismic']
)
sum_mrd_x += joint.top_column.get_mrdx(Ned=forces_top.N1)
sum_mrd_y += joint.top_column.get_mrdy(Ned=forces_top.N1)
# Bottom column exists
if joint.bottom_column:
forces_bottom = (
factors['G'] * joint.bottom_column.forces['G/seismic']
+ factors['Q'] * joint.bottom_column.forces['Q/seismic']
)
sum_mrd_x += joint.bottom_column.get_mrdx(Ned=forces_bottom.N9)
sum_mrd_y += joint.bottom_column.get_mrdy(Ned=forces_bottom.N9)
return sum_mrd_x, sum_mrd_y
def get_column_clear_length() -> Tuple[float, float]:
"""Get column clear length (distance between column faces).
Returns
-------
Tuple[float, float]
Column clear lengths for shear forces in local x and y axes.
"""
hx_i = 1000
hx_j = 1000
hy_i = 1000
hy_j = 1000
# i'th joint height
if joint_i.left_beam:
hx_i = min(hx_i, joint_i.left_beam.h)
if joint_i.right_beam:
hx_i = min(hx_i, joint_i.right_beam.h)
if joint_i.rear_beam:
hy_i = min(hy_i, joint_i.rear_beam.h)
if joint_i.front_beam:
hy_i = min(hy_i, joint_i.front_beam.h)
# j'th joint height
if joint_j.left_beam:
hx_j = min(hx_j, joint_j.left_beam.h)
if joint_j.right_beam:
hx_j = min(hx_j, joint_j.right_beam.h)
if joint_j.rear_beam:
hy_j = min(hy_j, joint_j.rear_beam.h)
if joint_j.front_beam:
hy_j = min(hy_j, joint_j.front_beam.h)
if hx_i == 1000:
hx_i = 0.0
if hx_j == 1000:
hx_j = 0.0
if hy_i == 1000:
hy_i = 0.0
if hy_j == 1000:
hy_j = 0.0
# Return beam clear length
lclx = column.H - (hx_i + hx_j) / 2
lcly = column.H - (hy_i + hy_j) / 2
return lclx, lcly
# Check if the capacity design forces will be added
if self.OVERSTRENGTH_FACTOR_COLUMN_SHEAR is None: # Do not add
return
else: # Add
gamma_rd = self.OVERSTRENGTH_FACTOR_COLUMN_SHEAR
# Unique gravity load factors from seismic load combinations
combo_grav_factors = self._get_unique_seism_combo_grav_factors()
# Loop through each beam
for column in self.columns:
# Find joints at both ends
joint_i = self._find_joint_by_point(column.elastic_nodes[0])
joint_j = self._find_joint_by_point(column.elastic_nodes[1])
# Sum of the moment of resistances of the joint beams
(sum_mrdb_x_pos_i, sum_mrdb_x_neg_i, sum_mrdb_y_pos_i,
sum_mrdb_y_neg_i) = get_sum_mrdb_at_joint(joint_i)
(sum_mrdb_x_pos_j, sum_mrdb_x_neg_j, sum_mrdb_y_pos_j,
sum_mrdb_y_neg_j) = get_sum_mrdb_at_joint(joint_j)
# Clear lengths of column in x and y
col_lcx, col_lcy = get_column_clear_length()
# Capacity design forces for columns
for factors in combo_grav_factors:
# Initialize column forces
forces = (
factors['G'] * column.forces['G/seismic']
+ factors['Q'] * column.forces['Q/seismic']
)
forces.case = 'seismic'
# The beam moment of resistances at both ends
mrdx_i = column.get_mrdx(Ned=forces.N1)
mrdy_i = column.get_mrdy(Ned=forces.N1)
mrdx_j = column.get_mrdx(Ned=forces.N9)
mrdy_j = column.get_mrdy(Ned=forces.N9)
# Sum of the moment of resistances of the joint columns
sum_mrdc_x_i, sum_mrdc_y_i = get_sum_mrdc_at_joint(joint_i)
sum_mrdc_x_j, sum_mrdc_y_j = get_sum_mrdc_at_joint(joint_j)
# Plastic moments around column local x and y
if column.elastic_nodes[0] in self.elastic_nodes_ground:
"""TODO: Special case is the ground floor --> Discuss
Even though not specified in the building codes (i.e, EC8),
max moment is the column capacity at the support node."""
Mpi_x_pos = gamma_rd * mrdx_i
Mpi_x_neg = gamma_rd * mrdx_i
Mpi_y_pos = gamma_rd * mrdy_i
Mpi_y_neg = gamma_rd * mrdy_i
else:
Mpi_x_pos = gamma_rd * mrdx_i * min(
1.0, sum_mrdb_y_pos_i / sum_mrdc_x_i)
Mpi_x_neg = gamma_rd * mrdx_i * min(
1.0, sum_mrdb_y_neg_i / sum_mrdc_x_i)
Mpi_y_pos = gamma_rd * mrdy_i * min(
1.0, sum_mrdb_x_pos_i / sum_mrdc_y_i)
Mpi_y_neg = gamma_rd * mrdy_i * min(
1.0, sum_mrdb_x_neg_i / sum_mrdc_y_i)
Mpj_x_pos = gamma_rd * mrdx_j * min(
1.0, sum_mrdb_y_pos_j / sum_mrdc_x_j)
Mpj_x_neg = gamma_rd * mrdx_j * min(
1.0, sum_mrdb_y_neg_j / sum_mrdc_x_j)
Mpj_y_pos = gamma_rd * mrdy_j * min(
1.0, sum_mrdb_x_pos_j / sum_mrdc_y_j)
Mpj_y_neg = gamma_rd * mrdy_j * min(
1.0, sum_mrdb_x_neg_j / sum_mrdc_y_j)
# Capacity design shear forces at both ends
Vdx_pos = (Mpi_y_pos + Mpj_y_neg) / col_lcx
Vdx_neg = (Mpi_y_neg + Mpj_y_pos) / col_lcx
Vdy_pos = (Mpi_x_pos + Mpj_x_neg) / col_lcy
Vdy_neg = (Mpi_x_neg + Mpj_x_pos) / col_lcy
# Set the forces and append
forces.Vx1 = max(Vdx_pos, Vdx_neg)
forces.Vx9 = max(Vdx_pos, Vdx_neg)
forces.Vy1 = max(Vdy_pos, Vdy_neg)
forces.Vy9 = max(Vdy_pos, Vdy_neg)
"""TODO: Set also design moments as Mp values?
It does not makes sense to make another check for design
moment though. Hence, we might need to separate section
adequacy verification for shear and flexure forces.
"""
column.design_forces.append(forces)
def _design_columns_for_shear(self) -> None:
"""Make shear design of the columns.
1. Check if beam and column flexure designs are ok, if ok continue.
2. Add capacity design shear forces to the design forces list. Then,
perform section adequacy checks (e.g., checks shear stress).
If column sections are ok, continue.
3. Compute required shear reinforcement (area / spacing). Then,
find the rebar solution to meet detailing requirements and validate.
"""
# STAGE 1: Check beam designs and column flexure designs
if self.beams_fail or self.columns_fail: # They are not ok
return # Exit and try again.
# STAGE 2: Section adequacy checks with capacity design shear forces.
self._set_column_capacity_design_shear_forces()
self._validate_column_section_dimensions()
if self.columns_fail:
self.__print_column_failure('stage 1 shear')
return # Columns are not ok, exit and try again with new sections
# STAGE 3: Transverse reinforcement design
for col in self.columns:
col.compute_required_transverse_reinforcement()
# Stage 3: Determine rebar distribution
for columns in self._continuous_columns:
# Organize
dbl_c = np.array([])
nbl_ix = np.array([])
nbl_iy = np.array([])
Ashx_sbh_req = np.array([])
Ashy_sbh_req = np.array([])
bx = np.array([])
by = np.array([])
for col in columns:
dbl_c = np.append(dbl_c, col.dbl_cor)
nbl_ix = np.append(nbl_ix, col.nblx_int)
nbl_iy = np.append(nbl_iy, col.nbly_int)
Ashx_sbh_req = np.append(Ashx_sbh_req,
col.Ashx_sbh_req)
Ashy_sbh_req = np.append(Ashy_sbh_req,
col.Ashy_sbh_req)
bx = np.append(bx, col.bx)
by = np.append(by, col.by)
# Determine rebar arrangement
(
dbh, sbh, nbh_x, nbh_y
) = self.rebars.get_column_transv_rebars(
bx, by, Ashx_sbh_req, Ashy_sbh_req, dbl_c, nbl_ix, nbl_iy
)
for i, col in enumerate(columns):
col.dbh = dbh[i]
col.sbh = sbh[i]
col.nbh_x = nbh_x[i]
col.nbh_y = nbh_y[i]
col.validate_transverse_reinforcement()
# Check if columns fail stage 2 shear design verification
if self.columns_fail:
self.__print_column_failure('stage 2 shear')
def _set_expected_axial_forces_on_column_hinges(self) -> None:
"""Set the expected axial forces on column hinges.
These axial forces are used for calculating plastic hinge
properties. By default, they are assumed to be equal to the
factored axial forces computed during preliminary design phase.
"""
# Set the predesign forces based on final configurations
self._set_column_predesign_forces()
# Use the position factored expected column axial forces
for column in self.columns:
column.hinge_Ng = column.pre_Ng_pos
column.hinge_Nq = column.pre_Nq_pos
[docs]
def run_iterative_design_algorithm(self) -> None:
"""Execute the iterative design algorithm.
Notes
-----
The algorithm is slightly different than the original algorithm.
"""
counter = 0 # Counter for design iterations
while not self.ok and counter <= self.ITER_MAX: # Iteration loop
if counter == 0: # Preliminary design stage
self._predesign() # Make a preliminary design
elif self.dim_change: # Try increasing beam and column dimensions
self._increase_beam_dimensions() # Increase beams
self._uniformize_beam_geometry() # Uniformize beams
self._increase_column_dimensions() # Increase columns
self._uniformize_columns_geometry() # Uniformize columns
elif self.beams_fail: # If beams fail to pass design checks
if self.dim_change_beam: # Try increasing beam dimensions
self._increase_beam_dimensions() # Increase beams
self._uniformize_beam_geometry() # Uniformize beams
elif self.beam_change: # Try changing beam type
self._change_beam_type() # Can be overwritten
self._restore_materials() # Restore initial dimensions
self._restore_dimensions() # Restore initial dimensions
self._predesign() # Preliminary design with new settings
elif self.mat_change: # Try changing materials
self._change_materials() # Can be overwritten
self._update_element_materials() # Update materials
self._restore_dimensions() # Restore initial dimensions
self._predesign() # Preliminary design with new settings
else: # Tried everything
break # No design solution is found, it is time to stop
elif self.columns_fail: # If columns fail to pass design checks
if self.dim_change_column: # Try increasing column dimensions
self._increase_column_dimensions() # Increase columns
self._uniformize_columns_geometry() # Uniformize columns
elif self.mat_change: # Try changing materials
self._change_materials() # Can be overwritten
self._update_element_materials() # Update materials
self._restore_dimensions() # Restore initial dimensions
self._predesign() # Preliminary design with new settings
elif self.column_change: # Try changing column section type
self._change_column_section() # Can be overwritten
self._restore_materials() # Restore initial dimensions
self._restore_dimensions() # Restore initial dimensions
self._predesign() # Preliminary design with new settings
else: # Tried everything
break # No design solution is found, it is time to stop
# Perform all necessary structural analysis
self._perform_structural_analyses()
# Make trial structural design
self._make_trial_structural_design()
# Increase the counter
counter += 1
# Adjust member design attributes for given quality
if self.ok:
self.quality.set_adjusted_properties(self.beams, self.columns)
# Set average column axial forces to be used in hinge calculations.
self._set_expected_axial_forces_on_column_hinges()
else:
Warning('No design solution is found.')
[docs]
def to_csv(self, directory: Union[str, Path]) -> None:
"""Save the generated BDIM data into the specified directory.
The files will be saved into the directory upon re-creating it.
Parameters
----------
directory : str | Path
Output directory where the bdim outputs (.csv files) will be saved.
e.g., My/Directory/Path
"""
if not self.ok:
return
# Create the output directory if needed
directory = Path(directory)
if not Path.exists(directory):
make_dir(directory)
# Set the paths for .csv files
infills_path = directory / 'infills.csv'
joints_path = directory / 'joints.csv'
columns_path = directory / 'columns.csv'
beams_path = directory / 'beams.csv'
slabs_path = directory / 'slabs.csv'
stairs_path = directory / 'stairs.csv'
# Retrive the bondslip factor and joint model type
bondslip_factor = self.quality.model.bondslip_factor
joint_model = self.quality.model.joint
# Keys to save for infills
i_keys = (
'id', 'node_1', 'node_2', 'node_3', 'node_4',
'typology', 'tw [mm]',
)
# Start infills dictionary
i_dict = {key: [] for key in i_keys}
for i in self.infills:
i_dict['id'].append(i.rectangle.tag)
i_dict['node_1'].append(i.rectangle.points[0].tag)
i_dict['node_2'].append(i.rectangle.points[1].tag)
i_dict['node_3'].append(i.rectangle.points[2].tag)
i_dict['node_4'].append(i.rectangle.points[3].tag)
i_dict['typology'].append(i.typology)
i_dict['tw [mm]'].append(i.thickness / mm)
# Convert infills dictionary to Pandas DataFrame
infills = pd.DataFrame(i_dict)
# Export infills DataFrame as .csv file
infills.to_csv(infills_path, index=False, float_format='%g')
# Keys to save for joints
j_keys = (
'id', 'x-coord [m]', 'y-coord [m]', 'z-coord [m]',
'bottom_column', 'top_column',
'left_beam_x', 'right_beam_x', 'left_beam_y', 'right_beam_y',
'fcm [MPa]', 'model_type_q'
)
# Start joints dictionary
j_dict = {key: [] for key in j_keys}
# Loop through each joint and append
for jnt in self.joints:
j_dict['id'].append(jnt.elastic_node.tag)
j_dict['x-coord [m]'].append(jnt.elastic_node.coordinates[0])
j_dict['y-coord [m]'].append(jnt.elastic_node.coordinates[1])
j_dict['z-coord [m]'].append(jnt.elastic_node.coordinates[2])
if jnt.bottom_column is not None:
j_dict['bottom_column'].append(jnt.bottom_column.line.tag)
else:
j_dict['bottom_column'].append(None)
if jnt.top_column is not None:
j_dict['top_column'].append(jnt.top_column.line.tag)
else:
j_dict['top_column'].append(None)
if jnt.left_beam is not None:
j_dict['left_beam_x'].append(jnt.left_beam.line.tag)
else:
j_dict['left_beam_x'].append(None)
if jnt.right_beam is not None:
j_dict['right_beam_x'].append(jnt.right_beam.line.tag)
else:
j_dict['right_beam_x'].append(None)
if jnt.rear_beam is not None:
j_dict['left_beam_y'].append(jnt.rear_beam.line.tag)
else:
j_dict['left_beam_y'].append(None)
if jnt.front_beam is not None:
j_dict['right_beam_y'].append(jnt.front_beam.line.tag)
else:
j_dict['right_beam_y'].append(None)
j_dict['fcm [MPa]'].append(joint_model)
j_dict['model_type_q'].append(joint_model)
# Convert joints dictionary to Pandas DataFrame
joints = pd.DataFrame(j_dict)
# Export joints DataFrame as .csv file
joints.to_csv(joints_path, index=False, float_format='%g')
# Keys to save for columns
c_keys = (
# Element connectivity keys
'id', 'node_i', 'node_j',
# Design keys
'bx [mm]', 'by [mm]', 'length [mm]',
'wg [kN/m]', 'wq [kN/m]', 'fcd [MPa]', 'fsyd [MPa]', 'cover [mm]',
'dbl_cor [mm]', 'dbl_int [mm]', 'nblx_int', 'nbly_int',
'dbh [mm]', 'sbh [mm]', 'nbh_x', 'nbh_y',
# Quality-adjusted reinforcement keys
'dbl_cor_q [mm]', 'dbl_int_q [mm]', 'nblx_int_q', 'nbly_int_q',
'dbh_q [mm]', 'sbh_q [mm]', 'nbh_x_q', 'nbh_y_q',
# Other quality-related fields
'fcm_q [MPa]', 'fsyml_q [MPa]', 'fsymh_q [MPa]', 'cover_q [mm]',
'bondslip_factor_q'
)
# Start columns dictionary
c_dict = {key: [] for key in c_keys}
# Loop through each column and append
for col in self.columns:
# Element connectivity keys
c_dict['id'].append(col.line.tag)
c_dict['node_i'].append(col.elastic_nodes[0].tag)
c_dict['node_j'].append(col.elastic_nodes[1].tag)
# Design keys
c_dict['bx [mm]'].append(col.bx / mm)
c_dict['by [mm]'].append(col.by / mm)
c_dict['length [mm]'].append(col.H / mm)
c_dict['wg [kN/m]'].append(col.self_wg)
c_dict['wq [kN/m]'].append(0.0)
c_dict['fcd [MPa]'].append(col.fcd / MPa)
c_dict['fsyd [MPa]'].append(col.fsyd / MPa)
c_dict['cover [mm]'].append(col.cover / mm)
c_dict['dbl_cor [mm]'].append(col.dbl_cor / mm)
c_dict['dbl_int [mm]'].append(col.dbl_int / mm)
c_dict['nblx_int'].append(col.nblx_int)
c_dict['nbly_int'].append(col.nbly_int)
c_dict['dbh [mm]'].append(col.dbh / mm)
c_dict['sbh [mm]'].append(col.sbh / mm)
c_dict['nbh_x'].append(col.nbh_x)
c_dict['nbh_y'].append(col.nbh_y)
# Quality-adjusted reinforcement keys
c_dict['dbl_cor_q [mm]'].append(col.dbl_cor_q / mm)
c_dict['dbl_int_q [mm]'].append(col.dbl_int_q / mm)
c_dict['nblx_int_q'].append(col.nblx_int_q)
c_dict['nbly_int_q'].append(col.nbly_int_q)
c_dict['dbh_q [mm]'].append(col.dbh_q / mm)
c_dict['sbh_q [mm]'].append(col.sbh_q / mm)
c_dict['nbh_x_q'].append(col.nbh_x_q)
c_dict['nbh_y_q'].append(col.nbh_y_q)
# Other quality-related fields
c_dict['fcm_q [MPa]'].append(col.fc_q / MPa)
c_dict['fsyml_q [MPa]'].append(col.fsyl_q / MPa)
c_dict['fsymh_q [MPa]'].append(col.fsyh_q / MPa)
c_dict['cover_q [mm]'].append(col.cover_q / mm)
c_dict['bondslip_factor_q'].append(bondslip_factor)
# Convert columns dictionary to Pandas DataFrame
columns = pd.DataFrame(c_dict)
# Export columns DataFrame as .csv file
columns.to_csv(columns_path, index=False, float_format='%g')
# Keys to save for beams
b_keys = (
# Element connectivity keys
'id', 'node_i', 'node_j',
# Design keys
'b [mm]', 'h [mm]', 'length [mm]',
'wg [kN/m]', 'wq [kN/m]', 'wg_alpha [kN/m]', 'wq_alpha [kN/m]',
'fcd [MPa]', 'fsyd [MPa]', 'cover [mm]',
'dbl_t1_i [mm]', 'nbl_t1_i', 'dbl_t2_i [mm]', 'nbl_t2_i',
'dbl_b1_i [mm]', 'nbl_b1_i', 'dbl_b2_i [mm]', 'nbl_b2_i',
'dbh_i [mm]', 'sbh_i [mm]', 'nbh_b_i', 'nbh_h_i',
'dbl_t1_mid [mm]', 'nbl_t1_mid', 'dbl_t2_mid [mm]', 'nbl_t2_mid',
'dbl_b1_mid [mm]', 'nbl_b1_mid', 'dbl_b2_mid [mm]', 'nbl_b2_mid',
'dbh_mid [mm]', 'sbh_mid [mm]', 'nbh_b_mid', 'nbh_h_mid',
'dbl_t1_j [mm]', 'nbl_t1_j', 'dbl_t2_j [mm]', 'nbl_t2_j',
'dbl_b1_j [mm]', 'nbl_b1_j', 'dbl_b2_j [mm]', 'nbl_b2_j',
'dbh_j [mm]', 'sbh_j [mm]', 'dbh_mid [mm]', 'sbh_mid [mm]',
'nbh_b_j', 'nbh_h_j',
# Quality-adjusted reinforcement keys
'dbl_t1_i_q [mm]', 'nbl_t1_i_q', 'dbl_t2_i_q [mm]', 'nbl_t2_i_q',
'dbl_b1_i_q [mm]', 'nbl_b1_i_q', 'dbl_b2_i_q [mm]', 'nbl_b2_i_q',
'dbh_i_q [mm]', 'sbh_i_q [mm]', 'nbh_b_i_q', 'nbh_h_i_q',
'dbl_t1_mid_q [mm]', 'nbl_t1_mid_q', 'dbl_t2_mid_q [mm]',
'nbl_t2_mid_q', 'dbl_b1_mid_q [mm]', 'nbl_b1_mid_q',
'dbl_b2_mid_q [mm]', 'nbl_b2_mid_q', 'dbh_mid_q [mm]',
'sbh_mid_q [mm]', 'nbh_b_mid_q', 'nbh_h_mid_q', 'dbl_t1_j_q [mm]',
'nbl_t1_j_q', 'dbl_t2_j_q [mm]', 'nbl_t2_j_q', 'dbl_b1_j_q [mm]',
'nbl_b1_j_q', 'dbl_b2_j_q [mm]', 'nbl_b2_j_q', 'dbh_j_q [mm]',
'sbh_j_q [mm]', 'nbh_b_j_q', 'nbh_h_j_q',
# Other quality-related fields
'fcm_q [MPa]', 'fsyml_q [MPa]', 'fsymh_q [MPa]', 'cover_q [mm]',
'bondslip_factor_q'
)
# Start beams dictionary
b_dict = {key: [] for key in b_keys}
# Loop through each beam and append
for beam in self.beams:
# Element connectivity keys
b_dict['id'].append(beam.line.tag)
b_dict['node_i'].append(beam.elastic_nodes[0].tag)
b_dict['node_j'].append(beam.elastic_nodes[1].tag)
# Design keys
b_dict['b [mm]'].append(beam.b / mm)
b_dict['h [mm]'].append(beam.h / mm)
b_dict['length [mm]'].append(beam.L / mm)
b_dict['wg [kN/m]'].append(beam.wg_total)
b_dict['wq [kN/m]'].append(beam.wq_total)
b_dict['wg_alpha [kN/m]'].append(beam.wg_total_alpha)
b_dict['wq_alpha [kN/m]'].append(beam.wq_total_alpha)
b_dict['fcd [MPa]'].append(beam.fcd / MPa)
b_dict['fsyd [MPa]'].append(beam.fsyd / MPa)
b_dict['cover [mm]'].append(beam.cover / mm)
b_dict['dbl_t1_i [mm]'].append(beam.dbl_t1[0] / mm)
b_dict['dbl_t1_mid [mm]'].append(beam.dbl_t1[1] / mm)
b_dict['dbl_t1_j [mm]'].append(beam.dbl_t1[-1] / mm)
b_dict['nbl_t1_i'].append(beam.nbl_t1[0])
b_dict['nbl_t1_mid'].append(beam.nbl_t1[1])
b_dict['nbl_t1_j'].append(beam.nbl_t1[-1])
b_dict['dbl_t2_i [mm]'].append(beam.dbl_t2[0] / mm)
b_dict['dbl_t2_mid [mm]'].append(beam.dbl_t2[1] / mm)
b_dict['dbl_t2_j [mm]'].append(beam.dbl_t2[-1] / mm)
b_dict['nbl_t2_i'].append(beam.nbl_t2[0])
b_dict['nbl_t2_mid'].append(beam.nbl_t2[1])
b_dict['nbl_t2_j'].append(beam.nbl_t2[-1])
b_dict['dbl_b1_i [mm]'].append(beam.dbl_b1[0] / mm)
b_dict['dbl_b1_mid [mm]'].append(beam.dbl_b1[1] / mm)
b_dict['dbl_b1_j [mm]'].append(beam.dbl_b1[-1] / mm)
b_dict['nbl_b1_i'].append(beam.nbl_b1[0])
b_dict['nbl_b1_mid'].append(beam.nbl_b1[1])
b_dict['nbl_b1_j'].append(beam.nbl_b1[-1])
b_dict['dbl_b2_i [mm]'].append(beam.dbl_b2[0] / mm)
b_dict['dbl_b2_mid [mm]'].append(beam.dbl_b2[1] / mm)
b_dict['dbl_b2_j [mm]'].append(beam.dbl_b2[-1] / mm)
b_dict['nbl_b2_i'].append(beam.nbl_b2[0])
b_dict['nbl_b2_mid'].append(beam.nbl_b2[1])
b_dict['nbl_b2_j'].append(beam.nbl_b2[-1])
b_dict['dbh_i [mm]'].append(beam.dbh[0] / mm)
b_dict['sbh_i [mm]'].append(beam.sbh[0] / mm)
b_dict['nbh_b_i'].append(beam.nbh_b[0])
b_dict['nbh_h_i'].append(beam.nbh_h[0])
b_dict['dbh_mid [mm]'].append(beam.dbh[1] / mm)
b_dict['sbh_mid [mm]'].append(beam.sbh[1] / mm)
b_dict['nbh_b_mid'].append(beam.nbh_b[1])
b_dict['nbh_h_mid'].append(beam.nbh_h[1])
b_dict['dbh_j [mm]'].append(beam.dbh[-1] / mm)
b_dict['sbh_j [mm]'].append(beam.sbh[-1] / mm)
b_dict['nbh_b_j'].append(beam.nbh_b[-1])
b_dict['nbh_h_j'].append(beam.nbh_h[-1])
# Quality-adjusted reinforcement keys
b_dict['dbl_t1_i_q [mm]'].append(beam.dbl_t1_q[0] / mm)
b_dict['dbl_t1_mid_q [mm]'].append(beam.dbl_t1_q[1] / mm)
b_dict['dbl_t1_j_q [mm]'].append(beam.dbl_t1_q[-1] / mm)
b_dict['nbl_t1_i_q'].append(beam.nbl_t1_q[0])
b_dict['nbl_t1_mid_q'].append(beam.nbl_t1_q[1])
b_dict['nbl_t1_j_q'].append(beam.nbl_t1_q[-1])
b_dict['dbl_t2_i_q [mm]'].append(beam.dbl_t2_q[0] / mm)
b_dict['dbl_t2_mid_q [mm]'].append(beam.dbl_t2_q[1] / mm)
b_dict['dbl_t2_j_q [mm]'].append(beam.dbl_t2_q[-1] / mm)
b_dict['nbl_t2_i_q'].append(beam.nbl_t2_q[0])
b_dict['nbl_t2_mid_q'].append(beam.nbl_t2_q[1])
b_dict['nbl_t2_j_q'].append(beam.nbl_t2_q[-1])
b_dict['dbl_b1_i_q [mm]'].append(beam.dbl_b1_q[0] / mm)
b_dict['dbl_b1_mid_q [mm]'].append(beam.dbl_b1_q[1] / mm)
b_dict['dbl_b1_j_q [mm]'].append(beam.dbl_b1_q[-1] / mm)
b_dict['nbl_b1_i_q'].append(beam.nbl_b1_q[0])
b_dict['nbl_b1_mid_q'].append(beam.nbl_b1_q[1])
b_dict['nbl_b1_j_q'].append(beam.nbl_b1_q[-1])
b_dict['dbl_b2_i_q [mm]'].append(beam.dbl_b2_q[0] / mm)
b_dict['dbl_b2_mid_q [mm]'].append(beam.dbl_b2_q[1] / mm)
b_dict['dbl_b2_j_q [mm]'].append(beam.dbl_b2_q[-1] / mm)
b_dict['nbl_b2_i_q'].append(beam.nbl_b2_q[0])
b_dict['nbl_b2_mid_q'].append(beam.nbl_b2_q[1])
b_dict['nbl_b2_j_q'].append(beam.nbl_b2_q[-1])
b_dict['dbh_i_q [mm]'].append(beam.dbh_q[0] / mm)
b_dict['sbh_i_q [mm]'].append(beam.sbh_q[0] / mm)
b_dict['nbh_b_i_q'].append(beam.nbh_b_q[0])
b_dict['nbh_h_i_q'].append(beam.nbh_h_q[0])
b_dict['dbh_mid_q [mm]'].append(beam.dbh_q[1] / mm)
b_dict['sbh_mid_q [mm]'].append(beam.sbh_q[1] / mm)
b_dict['nbh_b_mid_q'].append(beam.nbh_b_q[1])
b_dict['nbh_h_mid_q'].append(beam.nbh_h_q[1])
b_dict['dbh_j_q [mm]'].append(beam.dbh_q[-1] / mm)
b_dict['sbh_j_q [mm]'].append(beam.sbh_q[-1] / mm)
b_dict['nbh_b_j_q'].append(beam.nbh_b_q[-1])
b_dict['nbh_h_j_q'].append(beam.nbh_h_q[-1])
# Other quality-related field
b_dict['fcm_q [MPa]'].append(beam.fc_q / MPa)
b_dict['fsyml_q [MPa]'].append(beam.fsyl_q / MPa)
b_dict['fsymh_q [MPa]'].append(beam.fsyh_q / MPa)
b_dict['cover_q [mm]'].append(beam.cover_q / mm)
b_dict['bondslip_factor_q'].append(bondslip_factor)
# Convert beams dictionary to Pandas DataFrame
beams = pd.DataFrame(b_dict)
# Export beams DataFrame as .csv file
beams.to_csv(beams_path, index=False, float_format='%g')
# Keys to save for slabs
slab_keys = (
# Element connectivity keys
'id', 'node_1', 'node_2', 'node_3', 'node_4',
# Design keys
'lx [mm]', 'ly [mm]', 't [mm]'
)
# Start slabs dictionary
slab_dict = {key: [] for key in slab_keys}
# Loop through each slab and append
for slab in self.slabs:
# Element connectivity keys
slab_dict['id'].append(slab.rectangle.tag)
slab_dict['node_1'].append(slab.rectangle.points[0].tag)
slab_dict['node_2'].append(slab.rectangle.points[1].tag)
slab_dict['node_3'].append(slab.rectangle.points[2].tag)
slab_dict['node_4'].append(slab.rectangle.points[3].tag)
# Design keys
slab_dict['lx [mm]'].append(slab.lx / mm)
slab_dict['ly [mm]'].append(slab.ly / mm)
slab_dict['t [mm]'].append(slab.t / mm)
# Convert slabs dictionary to Pandas DataFrame
slabs = pd.DataFrame(slab_dict)
# Export slabs DataFrame as .csv file
slabs.to_csv(slabs_path, index=False, float_format='%g')
# Keys to save for stairs
stairs_keys = (
# Element connectivity keys
'id', 'node_1', 'node_2', 'node_3', 'node_4',
# Design keys
'lx [mm]', 'ly [mm]', 't [mm]'
)
# Start stairs dictionary
stairs_dict = {key: [] for key in stairs_keys}
# Loop through each stairs and append
for stairs in self.stairs:
# Element connectivity keys
stairs_dict['id'].append(stairs.rectangle.tag)
stairs_dict['node_1'].append(stairs.rectangle.points[0].tag)
stairs_dict['node_2'].append(stairs.rectangle.points[1].tag)
stairs_dict['node_3'].append(stairs.rectangle.points[2].tag)
stairs_dict['node_4'].append(stairs.rectangle.points[3].tag)
# Design keys
stairs_dict['lx [mm]'].append(stairs.lx / mm)
stairs_dict['ly [mm]'].append(stairs.ly / mm)
stairs_dict['t [mm]'].append(stairs.t / mm)
# Convert stairs dictionary to Pandas DataFrame
stairs = pd.DataFrame(stairs_dict)
# Export stairs DataFrame as .csv file
stairs.to_csv(stairs_path, index=False, float_format='%g')
[docs]
def set_seed_for_quality_adjustments(self, seed: int) -> None:
"""
Set a new random seed for reproducibility.
This method updates the quality object's seed attribute
and sets the global NumPy random seed to ensure deterministic
behavior in random operations.
Parameters
----------
seed : int
The new seed value to set.
"""
self.quality.set_new_seed(seed)