"""This module provides the base class for representing the beams within the
BDIM layer.
"""
# Imports from installed packages
from abc import ABC, abstractmethod
from dataclasses import dataclass
from math import ceil
import numpy as np
import numpy.typing as npt
from typing import Annotated, Literal, List, Optional, Dict, TypeVar
# Imports from bdim base library
from .materials import SteelBase, ConcreteBase
from .column import ColumnBase
# Imports from geometry library
from ...geometry.base import Line, Point
# Imports from utils library
from ....utils.misc import dot, PRECISION
from ....utils.units import kN, MPa, m, cm
# Numpy Typing with specific shape and datatype
DType = TypeVar("DType", bound=np.generic)
Array3 = Annotated[npt.NDArray[DType], Literal[3]]
[docs]
@dataclass
class BeamForces:
"""Data class for storing element forces.
Attributes
----------
M1 : float
Moment around local-z at 1st gauss point (start-section).
M5 : float
Moment around local-z at 5th gauss point (mid-section).
M9 : float
Moment around local-z at 9th gauss point (end-section).
V1 : float
Shear in local-y at 1st gauss point (start-section).
V5 : float
Shear in local-y at 5th gauss point (mid-section).
V9 : float
Shear in local-y at 9th gauss point (end-section).
case : Literal['gravity', 'seismic', None], optional
Type of load combination if forces are computed for a combo,
otherwise None. By default None.
"""
M1: float
M5: float
M9: float
V1: float
V5: float
V9: float
case: Literal['gravity', 'seismic', None] = None
def __add__(self, other: 'BeamForces') -> 'BeamForces':
"""The addition operator “+” (object is left operand).
Parameters
----------
other : BeamForces
Other BeamForces object.
Returns
-------
BeamForces
Addition of the other two `BeamForces` objects.
Raises
------
TypeError
Unsupported operand type.
"""
if isinstance(other, BeamForces):
return BeamForces(
self.M1 + other.M1,
self.M5 + other.M5,
self.M9 + other.M9,
self.V1 + other.V1,
self.V5 + other.V5,
self.V9 + other.V9
)
else:
raise TypeError("Unsupported operand type(s) for +: ",
"'{}' and '{}'".format(type(self), type(other)))
def __sub__(self, other: 'BeamForces') -> 'BeamForces':
"""The subtraction operator “-” (object is left operand).
Parameters
----------
other : BeamForces
Other BeamForces object.
Returns
-------
BeamForces
Subtraction of the other two `BeamForces` objects.
Raises
------
TypeError
Unsupported operand type.
"""
if isinstance(other, BeamForces):
return BeamForces(
self.M1 - other.M1,
self.M5 - other.M5,
self.M9 - other.M9,
self.V1 - other.V1,
self.V5 - other.V5,
self.V9 - other.V9
)
else:
raise TypeError("Unsupported operand type(s) for +: ",
"'{}' and '{}'".format(type(self), type(other)))
def __mul__(self, factor: float | int) -> 'BeamForces':
"""The multiplication operator “*” (object is left operand).
Parameters
----------
factor : float | int
Factor used for multiplying the force quantities.
Returns
-------
BeamForces
Product of factor and BeamForces object.
"""
return BeamForces(
self.M1 * factor,
self.M5 * factor,
self.M9 * factor,
self.V1 * factor,
self.V5 * factor,
self.V9 * factor
)
def __rmul__(self, factor: float | int) -> 'BeamForces':
"""The multiplication operator “*” (object is right operand).
Parameters
----------
factor : float | int
Factor used for multiplying the force quantities.
Returns
-------
BeamForces
Product of factor and BeamForces object.
"""
return self.__mul__(factor)
def __truediv__(self, factor: float | int) -> 'BeamForces':
"""The division operator “/” (object is left operand).
Parameters
----------
factor : float | int
Factor used for dividing the force quantities.
Returns
-------
BeamForces
Quotient of division operation: BeamForces/factor.
"""
return BeamForces(
self.M1 / factor,
self.M5 / factor,
self.M9 / factor,
self.V1 / factor,
self.V5 / factor,
self.V9 / factor
)
[docs]
@dataclass
class BeamEnvelopeForces:
"""Data class for storing element envelope forces.
Attributes
----------
M1_neg : float
Negative moment envelope around local-z at 1st gauss point
(start-section).
M5_neg : float
Negative moment envelope around local-z at 5th gauss point
(mid-section).
M9_neg : float
Negative moment envelope around local-z at 9th gauss point
(end-section).
M1_pos : float
Positive moment envelope around local-z at 1st gauss point
(start-section).
M5_pos : float
Positive moment envelope around local-z at 5th gauss point
(mid-section).
M9_pos : float
Positive moment envelope around local-z at 9th gauss point
(end-section).
V1 : float
Shear envelope in local-y at 1st gauss point (start-section).
V5 : float
Shear envelope in local-y at 5th gauss point (mid-section).
V9 : float
Shear envelope in local-y at 9th gauss point (end-section).
"""
M1_neg: float
M5_neg: float
M9_neg: float
M1_pos: float
M5_pos: float
M9_pos: float
V1: float
V5: float
V9: float
[docs]
class BeamBase(ABC):
"""Abstract base class for beams.
Must be inherited by design-class-specific beams.
Attributes
----------
ok : bool
Flag to check section adequacy.
line : ~simdesign.rcmrf.geometry.base.Line
Line representation of beam (tag and points).
h : float
Beam height (depth).
b : float
Beam breadth (width).
steel : SteelBase
Steel material.
concrete : ConcreteBase
Concrete material.
typology : Literal[1, 2]
Beam typology: 1 for wide beams, 2 for emergent beams.
exterior : bool
True if the beam is exterior.
slab_wg : List[float]
Uniformly distributed permanent loads transferred from slabs.
slab_wq : List[float]
Uniformly distributed variable loads transferred from slabs.
slab_alpha : List[float]
Beam load distribution coefficients, alpha.
stairs_wg : float
Uniformly distributed permanent loads transferred from stairs.
stairs_wq : float
Uniformly distributed variable loads transferred from stairs.
infill_wg : float
Infill loading on beams.
gamma_rc : float
Reinforced concrete unit weight.
pre_Md : float
Bending moment for preliminary design.
pre_Vd : float
Shear force for preliminary design.
forces : Dict[str, BeamForces]
Dictionary containing forces obtained from unique load cases
(in load combos), e.g., 'G', 'Q', 'E+X', 'E-X', 'E+Y', 'E-Y'.
design_forces : List[BeamForces]
List of forces obtained from each load combination (design forces).
pre_h : float
Preliminary design beam height.
pre_b : float
Preliminary design beam breadth (width).
cover : float
Concrete cover.
columns : List[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase | None]
Columns connected to beam end nodes i and j:
[Ci_top, Ci_bot, Cj_top, Cj_bot].
Columns which are not found are equal to None.
Asl_top_req : Array3[np.float64]
Required longitudinal reinforcement area at top.
Asl_bot_req : Array3[np.float64]
Required longitudinal reinforcement area at bottom.
Ash_sbh_req : Array3[np.float64]
Required transverse reinforcement area to spacing ratio.
dbl_t1 : Array3[np.float64]
Diameter of top corner longitudinal bars.
nbl_t1 : Array3[np.int64]
Number of top corner longitudinal bars.
dbl_t2 : Array3[np.float64]
Diameter of top internal longitudinal bars.
nbl_t2 : Array3[np.int64]
Number of top internal longitudinal bars.
dbl_b1 : Array3[np.float64]
Diameter of bottom corner longitudinal bars.
nbl_b1 : Array3[np.int64]
Number of bottom corner longitudinal bars.
dbl_b2 : Array3[np.float64]
Diameter of bottom internal longitudinal bars.
nbl_b2 : Array3[np.int64]
Number of bottom internal longitudinal bars.
dbh : Array3[np.float64]
Diameter of horizontal bars (transverse reinforcement).
sbh : Array3[np.float64]
Spacing of horizontal bars (transverse reinforcement).
nbh_b : Array3[np.int64]
Number of horizontal bars (stirrup legs) parallel to section width.
nbh_h : Array3[np.int64]
Number of horizontal bars (stirrup legs) parallel to section height.
MIN_H_EB : float
Default minimum height (depth) of emergent beams.
MIN_B_EB : float
Default minimum breadth (width) of emergent beams.
MAX_B_EB : float
Default maximum breadth (width) of emergent beams.
MAX_H_EB : float
Default maximum height (depth) of emergent beams.
MIN_H_WB : float
Default minimum height (depth) of wide beams.
MIN_B_WB : float
Default minimum breadth (width) of wide beams.
MAX_B_WB : float
Default maximum breadth (width) of wide beams.
MAX_H_WB : float
Default maximum height (depth) of wide beams.
B_INCR_EB : float
Amount of breadth increase per design iteration for emergent beams.
H_INCR_EB : float
Amount of height increase per design iteration for emergent beams.
B_INCR_WB : float
Amount of breadth increase per design iteration for wide beams.
H_INCR_WB : float
Amount of height increase per design iteration for wide beams.
MAX_ASPECT_RATIO_EB : float
Maximum aspect ratio (height to breadth) for emergent beams.
MAX_ASPECT_RATIO_WB : float
Maximum aspect ratio (breadth to height) for wide beams.
fc_q : float
In-situ (quality adjusted) concrete compressive strength.
fsyl_q : float
In-situ (quality adjusted) longitudinal reinforcement yield strength.
fsyh_q : float
In-situ (quality adjusted) transverse reinforcement yield strength.
nbh_b_q : Array3[np.int64]
In-situ number of horizontal bars (stirrup legs) parallel to section
width.
nbh_h_q : Array3[np.int64]
In-situ (quality adjusted) number of horizontal bars (stirrup legs)
parallel to section height.
dbh_q : Array3[np.float64]
In-situ (quality adjusted) diameter of transverse bars.
sbh_q : Array3[np.float64]
In-situ (quality adjusted) spacing of transverse bars.
cover_q : float
In-situ (quality adjusted) concrete cover.
dbl_t1_q : Array3[np.float64]
In-situ (quality adjusted) diameter of top corner longitudinal bars.
nbl_t1_q : Array3[np.int64]
In-situ (quality adjusted) number of top corner longitudinal bars.
dbl_t2_q : Array3[np.float64]
In-situ (quality adjusted) diameter of top internal longitudinal bars.
nbl_t2_q : Array3[np.int64]
In-situ (quality adjusted) number of top internal longitudinal bars.
dbl_b1_q : Array3[np.float64]
In-situ (quality adjusted) diameter of bottom corner longitudinal bars.
nbl_b1_q : Array3[np.int64]
In-situ (quality adjusted) number of bottom corner longitudinal bars.
dbl_b2_q : Array3[np.float64]
In-situ (quality adjusted) diameter of bottom internal longitudinal
bars.
nbl_b2_q : Array3[np.int64]
In-situ (quality adjusted) number of bottom internal longitudinal
bars.
Notes
-----
Section view of beams along X direction:
.. code-block:: text
Z (3)
|__Y (2)
-------------- ----
| y | |
| | | |
| z--+ | h
| | |
| | |
-------------- ----
|---- b -----|
Section view of beams along Y direction:
.. code-block:: text
Z (3)
|__X (1)
-------------- ----
| y | |
| | | |
| +--z | h
| | |
| | |
-------------- ----
|---- b -----|
"""
ok: bool
line: Line
h: float
b: float
__h: float
__b: float
steel: SteelBase
concrete: ConcreteBase
typology: Literal[1, 2]
exterior: bool
slab_wg: List[float]
slab_wq: List[float]
slab_alpha: List[float]
stairs_wg: float
stairs_wq: float
infill_wg: float
gamma_rc: float
pre_Md: float
pre_Vd: float
forces: Dict[str, BeamForces]
design_forces: List[BeamForces]
pre_h: float
pre_b: float
cover: float
columns: List[ColumnBase | None]
Asl_top_req: Array3[np.float64]
Asl_bot_req: Array3[np.float64]
Ash_sbh_req: Array3[np.float64]
dbl_t1: Array3[np.float64]
nbl_t1: Array3[np.int64]
dbl_t2: Array3[np.float64]
nbl_t2: Array3[np.int64]
dbl_b1: Array3[np.float64]
nbl_b1: Array3[np.int64]
dbl_b2: Array3[np.float64]
nbl_b2: Array3[np.int64]
dbh: Array3[np.float64]
sbh: Array3[np.float64]
nbh_b: Array3[np.int64]
nbh_h: Array3[np.int64]
MIN_H_EB: float = 30 * cm
MIN_B_EB: float = 20 * cm
MAX_B_EB: float = 1.0 * m
MAX_H_EB: float = 1.0 * m
MIN_H_WB: float = 15 * cm
MIN_B_WB: float = 30 * cm
MAX_B_WB: float = 80 * cm
MAX_H_WB: float = 1.0 * m
B_INCR_EB: float = 5 * cm
H_INCR_EB: float = 5 * cm
B_INCR_WB: float = 5 * cm
H_INCR_WB: float = 5 * cm
MAX_ASPECT_RATIO_EB: float = 2.0
MAX_ASPECT_RATIO_WB: float = 3.0
fc_q: float
fsyl_q: float
fsyh_q: float
nbh_b_q: Array3[np.int64]
nbh_h_q: Array3[np.int64]
dbh_q: Array3[np.float64]
sbh_q: Array3[np.float64]
cover_q: float
dbl_t1_q: Array3[np.float64]
nbl_t1_q: Array3[np.int64]
dbl_t2_q: Array3[np.float64]
nbl_t2_q: Array3[np.int64]
dbl_b1_q: Array3[np.float64]
nbl_b1_q: Array3[np.int64]
dbl_b2_q: Array3[np.float64]
nbl_b2_q: Array3[np.int64]
def __init__(
self, line: Line, typology: Literal[1, 2], gamma_rc: float
) -> None:
"""Initialize a new instance of BeamBase.
Parameters
----------
line : ~simdesign.rcmrf.geometry.base.Line
Geometric mesh representation of beam.
typology : Literal[1, 2]
Beam typology
1: Wide beams.
2: Emergent beams.
gamma_rc : float
Reinforced concrete unit weight.
"""
# Save inputs
self.line = line
self.gamma_rc = gamma_rc
self.typology = typology
# Initialize some parameters
if self.typology == 1:
self.b = self.MIN_B_WB
self.h = self.MIN_H_WB
elif self.typology == 2:
self.b = self.MIN_B_EB
self.h = self.MIN_H_EB
self.exterior = False
self.ok = True
self.slab_wg = []
self.slab_wq = []
self.slab_alpha = []
self.forces = {}
self.stairs_wg = 0.0
self.stairs_wq = 0.0
self.infill_wg = 0.0
def __str__(self) -> str:
"""Return string representation of the beam object.
Returns
-------
str
String representation of the beam object.
"""
beam_rep = self.line.__str__()
beam_rep = beam_rep.replace('Line', 'Beam')
beam_rep = beam_rep.replace('Point', 'Node-')
return beam_rep
@property
def fck(self) -> float:
"""Characteristic concrete compressive strength.
Returns
-------
float
Characteristic concrete compressive strength (in base units).
"""
return self.concrete.fck * MPa
@property
def fsyk(self) -> float:
"""Characteristic steel yield strength.
Returns
-------
float
Characteristic steel yield strength (in base units).
"""
return self.steel.fsyk * MPa
@property
def fsym(self) -> float:
"""Mean steel yield strength.
Returns
-------
float
Mean steel yield strength (in base units).
"""
return self.steel.fsym * MPa
@property
def fcd(self) -> float:
"""Design concrete compressive strength.
Returns
-------
float
Design value of concrete compressive strength (in base units).
"""
return self.concrete.fcd * MPa
@property
def fcm(self) -> float:
"""Mean concrete compressive strength.
Returns
-------
float
Mean value of concrete compressive strength (in base units).
"""
return self.concrete.fcm * MPa
@property
def fsyd(self) -> float:
"""Design steel yield strength.
Returns
-------
float
Design value of steel yield strength (in base units).
"""
return self.steel.fsyd * MPa
@property
def Ecm(self) -> float:
"""Mean elastic Young's modulus of concrete.
Returns
-------
float
Mean value of elastic Young's modulus of concrete (in base units).
"""
return self.concrete.Ecm
@property
def Ecd(self) -> float:
"""Design elastic Young's modulus of concrete.
Returns
-------
float
Design value of elastic Young's modulus of concrete
(in base units).
"""
return self.concrete.Ecd
@property
def Gcm(self) -> float:
"""Mean elastic shear modulus of concrete.
Returns
-------
float
Mean value of elastic shear modulus of concrete (in base units).
"""
return self.concrete.Gcm
@property
def Gcd(self) -> float:
"""Design elastic shear modulus of concrete.
Returns
-------
float
Design value of elastic shear modulus of concrete (in base units).
"""
return self.concrete.Gcd
@property
def Es(self) -> float:
"""Elastic Young's modulus of steel.
Returns
-------
float
Elastic Young's modulus of steel (in base units).
"""
return self.steel.Es
@property
def Ag(self) -> float:
"""Gross cross-sectional area of the beam.
Returns
-------
float
Gross cross-sectional area of the beam.
"""
return self.b * self.h
@property
def Iy(self) -> float:
"""Second moment of area about the y-axis.
Returns
-------
float
Moment of inertia around y-axis of the beam.
"""
return (self.h * self.b**3) / 12
@property
def Iz(self) -> float:
"""Second moment of area about the z-axis.
Returns
-------
float
Moment of inertia around z-axis of the beam.
"""
return (self.b * self.h**3) / 12
@property
def Iy_eff(self) -> float:
"""Effective (cracked) second moment of area about the y-axis.
Returns
-------
float
Moment of inertia around y-axis of the beam.
"""
return self.Iy
@property
def Iz_eff(self) -> float:
"""Effective (cracked) second moment of area about the z-axis.
Returns
-------
float
Moment of inertia around z-axis of the beam.
"""
return self.Iz
@property
def J(self) -> float:
"""Second polar moment of area of the beam.
Returns
-------
float
Second polar moment of area of the beam.
"""
hbmin = np.minimum(self.b, self.h)
hbmax = np.maximum(self.b, self.h)
return (hbmax * hbmin**3) * (
1 / 3 - 0.21 * (hbmin / hbmax) * (1 - (hbmin**4 / (12 * hbmax**4)))
)
@property
def L(self) -> float:
"""Beam length.
Returns
-------
float
Beam length.
"""
return round(self.line.length, PRECISION)
@property
def elastic_nodes(self) -> List[Point]:
"""Beam nodes (points) in the elastic model.
Returns
-------
List[Point]
Beam nodes (points) in elastic model.
"""
return self.line.points
@property
def self_wg(self) -> float:
"""Self-weight per unit length.
Returns
-------
float
Beam unit weight per length.
"""
return self.gamma_rc * self.Ag
@property
def wg_total(self) -> float:
"""Total uniformly distributed permanent load (G).
Returns
-------
float
Summation of uniformly distributed permanent loads (G).
"""
return sum(self.slab_wg) + self.stairs_wg + \
self.infill_wg + self.self_wg
@property
def wg_total_alpha(self) -> float:
"""Total permanent load (G) with alpha-factored slab contributions.
Returns
-------
float
Summation of equivalent uniformly distributed permanent loads (G).
Notes
-----
Differently from `wg_total`, in this case,
the slab loads are factored by alpha coefficient.
"""
return dot(self.slab_wg, self.slab_alpha) + \
self.stairs_wg + self.infill_wg + self.self_wg
@property
def wq_total(self) -> float:
"""Total uniformly distributed variable load (Q).
Returns
-------
float
Summation of uniformly distributed variable loads (Q).
"""
return sum(self.slab_wq) + self.stairs_wq
@property
def wq_total_alpha(self) -> float:
"""Total variable load (Q) with alpha-factored slab contributions.
Returns
-------
float
Summation of equivalent uniformly distributed variable loads (Q).
Notes
-----
Differently from `wq_total`, in this case,
the slab loads are factored by alpha coefficient.
"""
return dot(self.slab_wq, self.slab_alpha) + self.stairs_wq
@property
def simple_Mg(self) -> float:
"""Mid-span bending moment of a simply-supported beam under permanent
loads.
Returns
-------
float
Expected bending moment at mid-span of simply-supported beam due to
permanent loads.
"""
# return self.wg_total_alpha * (self.L**2) / 8
return self.wg_total_alpha * (self.L**2) / 16 # Interior span
@property
def simple_Mq(self) -> float:
"""Mid-span bending moment of a simply-supported beam under variable
loads.
Returns
-------
float
Expected bending moment at mid-span of simply-supported beam due to
variable loads.
"""
# return self.wq_total_alpha * (self.L**2) / 8
return self.wq_total_alpha * (self.L**2) / 16 # Interior span
@property
def simple_Vg(self) -> float:
"""Support shear of a simply-supported beam under permanent loads.
Returns
-------
float
Expected shear force at support of a simply-supported beam due to
permanent loads.
"""
return self.wg_total * self.L / 2
@property
def simple_Vq(self) -> float:
"""Support shear of a simply-supported beam under variable loads.
Returns
-------
float
Expected shear force at support of a simply-supported beam due to
variable loads.
"""
return self.wq_total * self.L / 2
@property
def direction(self) -> Optional[Literal['x', 'y']]:
"""Global axis parallel to the beam's longitudinal axis.
Returns
-------
Literal['x', 'y'] | None
Global axis which is parallel to the beam's line (its direction).
"""
if all(self.line.unit_vector == np.array([1.0, 0.0, 0.0])):
return 'x'
elif all(self.line.unit_vector == np.array([0.0, 1.0, 0.0])):
return 'y'
@property
def envelope_forces(self) -> BeamEnvelopeForces:
"""Envelope forces computed from all design load combinations.
Returns
-------
BeamEnvelopeForces
Envelope forces computed from `design_forces`.
"""
# Get a list of all attributes
attributes = ['M1', 'M5', 'M9', 'V1', 'V5', 'V9']
# Find minimum and maximum for each attribute
min_values = [min(getattr(force, attr) for force in self.design_forces)
for attr in attributes]
max_values = [max(getattr(force, attr) for force in self.design_forces)
for attr in attributes]
return BeamEnvelopeForces(
M1_neg=min(min_values[0], 0.0),
M5_neg=min(min_values[1], 0.0),
M9_neg=min(min_values[2], 0.0),
M1_pos=max(max_values[0], 0.0),
M5_pos=max(max_values[1], 0.0),
M9_pos=max(max_values[2], 0.0),
V1=max(max_values[3], abs(min_values[3])),
V5=max(max_values[4], abs(min_values[4])),
V9=max(max_values[5], abs(min_values[5])),
)
@property
def rhol_top(self) -> Array3[np.float64]:
"""Top longitudinal reinforcement ratio.
Returns
-------
Array3[np.float64]
Top longitudinal reinforcement ratio.
"""
Acorner = self.nbl_t1 * (np.pi * self.dbl_t1**2 / 4)
Ainterior = self.nbl_t2 * (np.pi * self.dbl_t2**2 / 4)
# return (Acorner + Ainterior) / ((self.h-self.cover) * self.b)
return (Acorner + Ainterior) / self.Ag
@property
def rhol_bot(self) -> Array3[np.float64]:
"""Bottom longitudinal reinforcement ratio.
Returns
-------
Array3[np.float64]
Bottom longitudinal reinforcement ratio.
"""
Acorner = self.nbl_b1 * (np.pi * self.dbl_b1**2 / 4)
Ainterior = self.nbl_b2 * (np.pi * self.dbl_b2**2 / 4)
# return (Acorner + Ainterior) / ((self.h-self.cover) * self.b)
return (Acorner + Ainterior) / self.Ag
@property
def rhol(self) -> Array3[np.float64]:
"""Total longitudinal reinforcement ratio.
Returns
-------
Array3[np.float64]
Total longitudinal reinforcement ratio.
"""
return self.rhol_top + self.rhol_bot
@property
def rhol_max_tens(self) -> float:
"""Maximum allowable longitudinal reinforcement ratio.
Returns
-------
float
Maximum longitudinal reinforcement ratio in tens. and comp. zones
"""
return 1.0
@property
def rhoh_z(self) -> Array3[np.float64]:
"""Transverse reinforcement ratio in local-z.
Returns
-------
Array3[np.float64]
Horizontal (transverse) reinforcement ratio in local-z.
"""
Ash = self.nbh_b * np.pi * self.dbh**2 / 4
return Ash / (self.h * self.sbh)
@property
def rhoh_y(self) -> Array3[np.float64]:
"""Transverse reinforcement ratio in local-y.
Returns
-------
Array3[np.float64]
Horizontal (transverse) reinforcement ratio in local-y.
"""
Ash = self.nbh_h * np.pi * self.dbh**2 / 4
return Ash / (self.b * self.sbh)
@property
def max_b(self) -> float:
"""Maximum allowed section breadth (width).
Returns
-------
float
Computed maximum allowed section breadth (width).
"""
if self.direction == 'x': # Beam is along x
bc = max(col.by for col in self.columns if col)
elif self.direction == 'y': # Beam is along y
bc = max(col.bx for col in self.columns if col)
# EC8 5.4.1.2.1 (3) width limit
b_max_code = min(bc + self.h, 2 * bc)
# Masks for finding emergent beams
bool1 = self.typology == 2
bool2 = self.exterior
bool3 = self.stairs_wg != 0.0
if bool1 or bool2 or bool3:
return min(b_max_code, self.MAX_B_EB)
else:
return min(b_max_code, self.MAX_B_WB)
@property
def max_h(self) -> float:
"""Maximum allowed section height (depth).
Returns
-------
float
Computed maximum allowed section height (depth).
"""
# Masks for finding emergent beams
bool1 = self.typology == 2 # Emergent by default
bool2 = self.exterior # Forces exterior beams to emergent
bool3 = self.stairs_wg != 0.0 # Forces stairs beams to be emergent
if bool1 or bool2 or bool3:
return self.MAX_H_EB
else:
return self.MAX_H_WB
@property
def min_b(self) -> float:
"""Minimum allowed section breadth (width).
Returns
-------
float
Computed minimum allowed section breadth (width).
"""
# Masks for finding emergent beams
bool1 = self.typology == 2 # Emergent by default
bool2 = self.exterior # Forces exterior beams to emergent
bool3 = self.stairs_wg != 0.0 # Forces stairs beams to be emergent
if bool1 or bool2 or bool3:
return self.MIN_B_EB
else:
return self.MIN_B_WB
@property
def min_h(self) -> float:
"""Minimum allowed section height (depth).
Returns
-------
float
Computed minimum allowed section height (depth).
"""
# Masks for finding emergent beams
bool1 = self.typology == 2 # Emergent by default
bool2 = self.exterior # Forces exterior beams to emergent
bool3 = self.stairs_wg != 0.0 # Forces stairs beams to be emergent
if bool1 or bool2 or bool3:
return self.MIN_H_EB
else:
return self.MIN_H_WB
@property
def mrd_pos(self) -> Array3[np.float64]:
"""Design resistance moment in the positive (sagging) direction.
Returns
-------
Array3[np.float64]
Design resistance moment of beam in positive direction.
Computed for start, mid, end beam sections.
Notes
-----
Required for capacity design of columns.
"""
return self._get_mrd('pos')
@property
def mrd_neg(self) -> Array3[np.float64]:
"""Design resistance moment in the negative (hogging) direction.
Returns
-------
Array3[np.float64]
Design resistance moment of beam in negative direction.
Computed for start, mid, end beam sections.
Notes
-----
Required for capacity design of columns.
"""
return self._get_mrd('neg')
def _get_mrd_fardis(self, direction: Literal['neg', 'pos']
) -> Array3[np.float64]:
"""
Computes yield moment of the section in the given direction.
Parameters
----------
direction : Literal['neg', 'pos']
Moment capacity direction (based on the sign convention).
Returns
-------
My : np.ndarray
Yield moment of beam in the specified `direction`.
Computed for start, mid, end beam sections.
References
----------
Panagiotakos, T. B., & Fardis, M. N. (2001).
Deformations of reinforced concrete members at yielding and ultimate.
Structural Journal, 98(2), 135-148.
ACI 318-25. (2025). Building Code for Structural Concrete-Code
Requirements and Commentary. American Concrete Institute.
Notes
-----
Positive direction indicates that bottom section is tension zone and
top section is compression zone. Negative direction indicates that top
section is tension zone and bottom section is compression zone.
"""
# Set direction dependent parameters
if direction == 'pos': # positive direction case
# Longitudinal reinforcement area under tension
As_tens = (self.nbl_b1 * ((0.25 * np.pi) * self.dbl_b1**2)
+ self.nbl_b2 * ((0.25 * np.pi) * self.dbl_b2**2))
# Longitudinal reinforcement area under compression
As_comp = (self.nbl_t1 * ((0.25 * np.pi) * self.dbl_t1**2)
+ self.nbl_t2 * ((0.25 * np.pi) * self.dbl_t2**2))
# Dist. from concrete fiber in compression to the rebars in tension
if hasattr(self, 'dbh'):
dd = self.h - self.cover - self.dbh - 0.5 * self.dbl_b1
else:
dd = 0.9 * self.h # Assume
elif direction == 'neg': # negative direction case
# Longitudinal reinforcement area under tension
As_tens = (self.nbl_t1 * ((0.25 * np.pi) * self.dbl_t1**2)
+ self.nbl_t2 * ((0.25 * np.pi) * self.dbl_t2**2))
# Longitudinal reinforcement area under compression
As_comp = (self.nbl_b1 * ((0.25 * np.pi) * self.dbl_b1**2)
+ self.nbl_b2 * ((0.25 * np.pi) * self.dbl_b2**2))
# Dist. from concrete fiber in compression to the rebars in tension
if hasattr(self, 'dbh'):
dd = self.h - self.cover - self.dbh - 0.5 * self.dbl_t1
else:
dd = 0.9 * self.h # Assume
# Concrete crushing strain used for computing section capacity
EPS_CU = 0.0035
# Stress-block coefficient used to compute section capacity
# Table 22.2.2.4.3 of ACI 318-25
if self.fcd < 27.6 * MPa:
betac = 0.85
elif self.fcd > 55.17 * MPa:
betac = 0.65
else:
betac = 1.05 - 0.05 * self.fcd / (6.9 * MPa)
# Yield strain of steel bars
esy = self.fsyk / self.Es
# Concrete modulus of elasticity
Ec = self.Ecd
# Steel modulus of elasticity
Es = self.Es
# Modular ratio
nyoung = Es / Ec
# Distance from ext. concrete fiber (comp.) to the rebars (comp.)
dd_prime = self.h - dd
# Balanced c value: dist. to neutral axis from ext. conc. fiber (comp.)
cb = (EPS_CU * dd) / (EPS_CU + esy)
# Tension and compression longitudinal reinforcement ratio values
rhol_tens = As_tens / (self.b * dd)
rhol_comp = As_comp / (self.b * dd)
# Compute distance to neutral axis with outer faces (simplification)
c = (As_tens * self.fsyd - As_comp * self.fsyd) \
/ (0.85 * self.fcd * self.b * betac)
# Decide whether yielding is controlled by tension or compression zone
# Panagiotakos and Fardis 2001 - Equation 4 & 5
Acomp_cntrl = rhol_tens + rhol_comp
Atens_cntrl = rhol_tens + rhol_comp
Bcomp_cntrl = rhol_tens + rhol_comp * (dd_prime / dd)
Btens_cntrl = rhol_tens + rhol_comp * (dd_prime / dd)
# Yielding is controlled by the tension steel
control = np.ones_like(self.dbl_t1)
A_to_use = Atens_cntrl
B_to_use = Btens_cntrl
# Yielding is controlled by the compression zone
control[c >= cb] = 0
A_to_use[c >= cb] = Acomp_cntrl[c >= cb]
B_to_use[c >= cb] = Bcomp_cntrl[c >= cb]
# The compression zone depth: Panagiotakos and Fardis 2001 - Equation 3
ky = (
(nyoung**2) * (A_to_use**2) + (2 * nyoung * B_to_use)
) ** 0.5 - nyoung * A_to_use
# Panagiotakos and Fardis 2001 - Equation 1
fiy1 = self.fsyd / (Es * (1 - ky) * dd)
# Panagiotakos and Fardis 2001 - Equation 2
fiy2 = (1.8 * (self.fcd) / (Ec * ky * dd))
# Yield curvature
fiy = fiy1
fiy[control == 0] = fiy2[control == 0]
# Yield Moment: Panagiotakos and Fardis 2001 - Equation 6
rhol_int = 0.0 # Beams do not have web-reinforcement
term1 = (Ec * (ky**2) / 2) * (0.5 * (1 + (dd_prime / dd)) - (ky / 3))
term2 = (
(Es / 2)
* (
(1 - ky) * rhol_tens
+ (ky - (dd_prime / dd)) * rhol_comp
+ (rhol_int / 6) * (1 - (dd_prime / dd))
)
* (1 - (dd_prime / dd))
)
My = (self.b * (dd**3)) * fiy * (term1 + term2)
return My
def _get_mrd(self, dir: Literal['neg', 'pos']) -> Array3[np.float64]:
"""
Computes yield moment of the section in the given direction.
Parameters
----------
dir : Literal['neg', 'pos']
Moment capacity direction (based on the sign convention).
Returns
-------
Mrd : np.ndarray
Yield moment of beam in the specified `direction`.
Computed for start, mid, end beam sections.
References
----------
Fardis, M., Carvalho, E., Fajfar, P., & Pecker, A. (2015). Seismic
design of concrete buildings to Eurocode 8. Crc Press.
Notes
-----
Positive direction (sagging) indicates that bottom section is tension
zone and top section is compression zone. Negative direction (hogging)
indicates that top section is tension zone and bottom section is
compression zone.
"""
# Section properties
fyd = self.fsyd
fcd = self.fcd
h = self.h
# Top longitudinal reinforcement area (under compression)
As1 = (self.nbl_t1 * ((0.25 * np.pi) * self.dbl_t1**2)
+ self.nbl_t2 * ((0.25 * np.pi) * self.dbl_t2**2))
# Centroidal distance of As1, from the top of the beam section
d1 = self.cover + 0.5 * self.dbl_t1
# Bottom longitudinal reinforcement area (under tension)
As2 = (self.nbl_b1 * ((0.25 * np.pi) * self.dbl_b1**2)
+ self.nbl_b2 * ((0.25 * np.pi) * self.dbl_b2**2))
# Centroidal distance of As2, from the bottom of the beam section
d2 = self.cover + 0.5 * self.dbl_b1
# If dbh is known
if hasattr(self, 'dbh'):
d1 += self.dbh
d2 += self.dbh
# Equation 5.30a
if dir == 'neg':
bw = self.b
Mrd = np.minimum(As1, As2) * fyd * (h - d1 - d2) + (
np.maximum(0, As1 - As2) * fyd * (
h - d1 - 0.5 * (As1 - As2) * fyd / (bw * fcd)
)
)
# Equation 5.30b
elif dir == 'pos':
# Assume effective width (beff) in compression of the top flange is
# equal to the that of the web (beff = bw)
beff = self.b
Mrd = As2 * fyd * np.maximum(
(h - d2 - 0.5 * As2 * fyd / (beff * fcd)),
(h - d1 - d2)
)
return Mrd
[docs]
def restore_dimensions(self) -> None:
"""Restore beam dimension attributes.
`b, h`
"""
self.b = self.__b
self.h = self.__h
[docs]
def set_restore_point(self):
"""Set the restore point for specific beam attributes.
`b, h`
"""
self.__b = self.b
self.__h = self.h
[docs]
def increase_dimensions(self) -> None:
"""Method used for increasing dimensions of inadequate sections.
Used in design iterations.
Can be overwritten for each design class.
Notes
-----
Usually, the beam aspect ratio is between 1.5-2.0.
Hence, added aspect ratio limit for emergent beams.
"""
if not self.ok:
aspect_ratio = self.h / self.b
# Masks for finding emergent beams
bool1 = self.typology == 2 # Emergent by default
bool2 = self.exterior # Forces exterior beams to emergent
bool3 = self.stairs_wg != 0.0 # Forces stairs beams to be emergent
# Emergent beam cases
if bool1 or bool2 or bool3:
if aspect_ratio < self.MAX_ASPECT_RATIO_EB:
self.h += self.H_INCR_EB
else:
self.b += self.B_INCR_EB
# Wide beam cases
else:
if self.b / self.h <= self.MAX_ASPECT_RATIO_WB:
self.b += self.B_INCR_WB
if self.b > self.max_b:
self.b = self.pre_b
self.h += self.H_INCR_WB
else:
self.h += self.H_INCR_WB
[docs]
def predesign_section_dimensions(self, slab_h: float) -> None:
"""Perform preliminary design of beam.
This method makes initial guess for section dimensions.
Parameters
----------
slab_h : float
Slab thickness.
Notes
-----
It can be overwritten for specific design classes.
"""
# Maximum mu value considered for the economic emergent beam design
ECONOMIC_MU_EB = 0.25
# Maximum mu value considered for the economic wide beam design
ECONOMIC_MU_WB = 0.25
# Unit conversions
Md = self.pre_Md * kN * m
# Emergent beam cases
bool1 = self.typology == 2 # Emergent by default
bool2 = self.exterior # Forces exterior beams to emergent
bool3 = self.stairs_wg != 0.0 # Forces stairs beams to be emergent
if bool1 or bool2 or bool3:
# Set section breadth to minimum
self.b = self.min_b
# Compute height for economic section, assuming d = 0.1h
mu_h = ((Md / (ECONOMIC_MU_EB * self.fcd * self.b))**0.5) / 0.9
# Compute height to control deformations
if self.stairs_wg != 0.0 or sum(self.slab_wg) != 0.0:
# The beam carries a slab (stairs or floor slab)
def_h = self.L / 12
else: # The beam is secondary gravity beam
def_h = self.L / (0.9 * 18)
# Get the maximum slab computed from all
self.h = max(self.min_h, slab_h, mu_h, def_h)
# Iterate for aspect ratio consideration
while self.h / self.b > self.MAX_ASPECT_RATIO_EB:
# Increase breadth
self.b += self.B_INCR_EB
# Compute height for economic section, assuming d = 0.1h
mu_h = ((Md / (ECONOMIC_MU_EB * self.fcd * self.b))**0.5) / 0.9
# Compute height to control deformations
if self.stairs_wg != 0.0 or sum(self.slab_wg) != 0.0:
# The beam carries a slab (stairs or floor slab)
def_h = self.L / 12
else: # The beam is secondary gravity beam
def_h = self.L / (0.9 * 18)
# Get the maximum slab computed from all
self.h = max(self.min_h, slab_h, mu_h, def_h)
# Wide beam cases
else:
# Set section height (slab thickness or minimum)
self.h = max(slab_h, self.min_h)
# Section widths
if sum(self.slab_wg) == 0.0: # Secondary gravity beams
self.b = self.min_b # Use minimum dimension
else: # Primary gravity beams
# Set width based on economic mu value and minimum allowed
self.b = max(
self.min_b,
(Md / (ECONOMIC_MU_WB * self.fcd * (0.9 * self.h) ** 2)),
)
while (
self.b > self.max_b
or self.b / self.h > self.MAX_ASPECT_RATIO_WB
):
self.h += self.H_INCR_WB
self.b = Md / (
ECONOMIC_MU_WB * self.fcd * (0.9 * self.h) ** 2
)
# Round
self.h = ceil(20 * self.h) / 20
self.b = ceil(20 * self.b) / 20
[docs]
def validate_section_dimensions(self) -> None:
"""Method for validating section dimensions against maximum.
"""
if self.b > self.max_b or self.h > self.max_h:
self.ok = False
[docs]
def validate_longitudinal_reinforcement(self) -> None:
"""Method for validating longitudinal reinforcement against maximum.
This method is intended to run after determining beam rebars.
"""
# Adequacy check for maximum longitudinal reinforcement in tension
rhol_tens = max(self.rhol_top.max(), self.rhol_bot.max())
if rhol_tens > self.rhol_max_tens:
self.ok = False
[docs]
def validate_transverse_reinforcement(self) -> None:
"""Method for validating transverse reinforcement.
This method is intended to run after determining beam rebars.
"""
[docs]
@abstractmethod
def verify_section_adequacy(self) -> None:
"""Abstract method for verifying adequacy of section dimensions.
"""
[docs]
@abstractmethod
def compute_required_longitudinal_reinforcement(self) -> None:
"""Abstract method for computing required longitudinal reinforcement.
Final solution is determined after finding rebar solution to meet
the detailing requirements.
"""
[docs]
@abstractmethod
def compute_required_transverse_reinforcement(self) -> None:
"""Abstract method for computing required transverse reinforcement.
Final solution is determined after finding rebar solution to meet
the detailing requirements.
"""