"""This module provides the base class for representing
the detailing of structural members in the BDIM layer.
"""
# Imports from installed packages
from abc import ABC
import json
import numpy as np
from pathlib import Path
from pydantic import BaseModel
from typing import List, Literal
# Imports from bdim base library
from .materials import ConcreteBase, SteelBase
# Imports from utils library
from ....utils.units import mm, inch
from ....utils.misc import PRECISION
[docs]
class RebarData(BaseModel):
"""Pydantic model for available rebar sizes and spacings.
Attributes
----------
beam_longitudinal_bar_diameters : List[float]
Possible beams longitudinal bar diameters (nominal).
beam_transverse_bar_diameters : List[float]
Possible beams transverse bar diameters (nominal).
beam_transverse_bar_spacings : List[float]
Possible beams transverse bar spacings.
column_longitudinal_bar_diameters : List[float]
Possible columns longitudinal bar diameters (nominal).
column_transverse_bar_diameters : List[float]
Possible columns transverse bar diameters (nominal).
column_transverse_bar_spacings : List[float]
Possible columns transverse bar spacings.
units : Literal['mm', 'in']
Units of bar diameters and spacings.
"""
beam_longitudinal_bar_diameters: List[float]
beam_transverse_bar_diameters: List[float]
beam_transverse_bar_spacings: List[float]
column_longitudinal_bar_diameters: List[float]
column_transverse_bar_diameters: List[float]
column_transverse_bar_spacings: List[float]
units: Literal['mm', 'in']
[docs]
class RebarsBase(ABC):
"""Abstract base class for rebar detailing of structural members.
Determines rebar configurations for beams and columns. The assumptions
made in these processes can have a significant impact on the final
reinforcement solutions.
Attributes
----------
_data_path : Path | str
Path to the JSON file containing rebar data.
beam_long_bar_diams : np.ndarray
Possible beam longitudinal bar diameters.
beam_trans_bar_diams : np.ndarray
Possible beam transverse bar diameters.
beam_trans_bar_spacings : np.ndarray
Possible beam transverse bar spacings.
col_long_bar_diams : np.ndarray
Possible column longitudinal bar diameters.
col_trans_bar_diams : np.ndarray
Possible column transverse bar diameters.
col_trans_bar_spacings : np.ndarray
Possible column transverse bar spacings.
concrete : ConcreteBase
Concrete material instance considered in design of beams and columns.
steel : SteelBase
Steel material instance considered in design of beams and columns.
beam_max_sbl : float
Maximum spacing between longitudinal bars (reinforcement) for beams.
beam_min_sbl : float
Minimum spacing between longitudinal bars (reinforcement) for beams.
beam_max_leg_dist : float
For beams, maximum distance between longitudinal bars within a beam
section that can be considered to be confined without the need to have
an extra stirrup leg around them.
beam_cover : float
Concrete cover for beams.
col_max_sbl : float
Maximum spacing between longitudinal bars (reinforcement) for columns.
col_min_sbl : float
Minimum spacing between longitudinal bars (reinforcement) for columns.
col_max_leg_dist : float
Maximum distance between longitudinal bars within a column
section that can be considered to be confined without the need to have
an extra stirrup leg around them.
col_cover : float
Concrete cover for columns.
"""
_data_path: Path | str
beam_long_bar_diams: np.ndarray
beam_trans_bar_diams: np.ndarray
beam_trans_bar_spacings: np.ndarray
col_long_bar_diams: np.ndarray
col_trans_bar_diams: np.ndarray
col_trans_bar_spacings: np.ndarray
concrete: ConcreteBase
steel: SteelBase
beam_max_sbl: float = 2000 * mm
beam_min_sbl: float = 40 * mm
beam_max_leg_dist: float = 2000 * mm
beam_cover: float = 30 * mm
col_max_sbl: float = 2000 * mm
col_min_sbl: float = 35 * mm
col_max_leg_dist: float = 2000 * mm
col_cover: float = 30 * mm
def __init__(self) -> None:
"""Initialize available bar diameters and transverse bar spacings from
JSON.
"""
with open(self._data_path, 'r') as json_file:
# Load the JSON data into a Python dictionary
data = json.load(json_file)
# Check data with pydantic model
model = RebarData(**data)
if model.units == 'mm':
self.beam_long_bar_diams = mm * np.array(
model.beam_longitudinal_bar_diameters)
self.beam_trans_bar_diams = mm * np.array(
model.beam_transverse_bar_diameters)
self.beam_trans_bar_spacings = mm * np.array(
model.beam_transverse_bar_spacings)
self.col_long_bar_diams = mm * np.array(
model.column_longitudinal_bar_diameters)
self.col_trans_bar_diams = mm * np.array(
model.column_transverse_bar_diameters)
self.col_trans_bar_spacings = mm * np.array(
model.column_transverse_bar_spacings)
elif model.units == 'in':
self.beam_long_bar_diams = inch * np.array(
model.beam_longitudinal_bar_diameters)
self.beam_trans_bar_diams = inch * np.array(
model.beam_transverse_bar_diameters)
self.beam_trans_bar_spacings = inch * np.array(
model.beam_transverse_bar_spacings)
self.col_long_bar_diams = inch * np.array(
model.column_longitudinal_bar_diameters)
self.col_trans_bar_diams = inch * np.array(
model.column_transverse_bar_diameters)
self.col_trans_bar_spacings = inch * np.array(
model.column_transverse_bar_spacings)
# Sort data
self.beam_long_bar_diams.sort()
self.beam_trans_bar_diams.sort()
self.beam_trans_bar_spacings[::-1].sort() # descending order
self.col_long_bar_diams.sort()
self.col_trans_bar_diams.sort()
self.col_trans_bar_spacings[::-1].sort() # descending order
def _get_min_beam_dbh(self, **kwargs) -> float | np.ndarray:
"""Get the minimum transverse reinforcement diameter in beams.
Parameters
----------
**kwargs
dbl : float or np.ndarray
Longitudinal reinforcement diameter.
Returns
-------
float | np.ndarray
Minimum transverse reinforcement diameter.
"""
dbl = kwargs['dbl']
return np.maximum(dbl / 4, 6 * mm)
def _get_min_col_dbh(self, **kwargs) -> float | np.ndarray:
"""Get the minimum transverse reinforcement diameter in columns.
Parameters
----------
**kwargs
dbl : float or np.ndarray
Longitudinal reinforcement diameter.
Returns
-------
float | np.ndarray
Minimum transverse reinforcement diameter.
"""
dbl = kwargs['dbl']
return np.maximum(dbl / 4, 6 * mm)
def _get_col_max_sbh(self, **kwargs) -> float | np.ndarray:
"""Get maximum spacing between horizontal bars
(transverse reinforcement) for columns.
Parameters
----------
**kwargs
by : float or np.ndarray
Column width along the Y-axis.
bx : float or np.ndarray
Column width along the X-axis.
dbl : float or np.ndarray
Longitudinal reinforcement diameter.
Returns
-------
float | np.ndarray
Maximum spacing between transverse reinforcement.
"""
# maximum allowed spacing in current iteration
by = kwargs['by'] # column width along y
bx = kwargs['bx'] # column width along x
dbl = kwargs['dbl'] # long. reinf. diameter
max_sbh = np.minimum(np.minimum(by, bx), 12 * dbl)
return max_sbh
def _get_beam_max_sbh(self, **kwargs) -> float | np.ndarray:
"""Get maximum spacing between horizontal bars
(transverse reinforcement) for beams.
Parameters
----------
**kwargs
h : float or np.ndarray
Beam depth.
dbh : float or np.ndarray
Transverse reinforcement diameter.
dbl : float or np.ndarray
Longitudinal reinforcement diameter.
Returns
-------
float | np.ndarray
Maximum spacing between transverse reinforcement.
TODO
----
Add another function for max_sbh at midspans.
"""
# maximum allowed spacing in current iteration
h = kwargs['h'] # beam depth
dbh = kwargs['dbh'] # transverse reinf. diameter
dbl = kwargs['dbl'] # long. reinf. diameter
max_sbh = np.minimum(
np.minimum(250 * mm, h), np.minimum(24 * dbh, 12 * dbl)
)
return max_sbh
[docs]
def get_beam_long_rebars(
self, Asl_top: np.ndarray, Asl_bot: np.ndarray, b: np.ndarray
) -> tuple[np.ndarray]:
"""Select the longitudinal reinforcement solution for a generic
beam with dimension `b` over an alignment with N sections.
Parameters
----------
Asl_top : np.ndarray
Required longitudinal reinforcement area at top.
Asl_bot : np.ndarray
Required longitudinal reinforcement area at bottom.
b : np.ndarray
Beam breadth (width).
Returns
-------
dbl_t1 : np.ndarray
Diameter of 1st type of longitudinal bars at top.
nbl_t1 : np.ndarray
Number of 1st type of longitudinal bars at top.
dbl_t2 : np.ndarray
Diameter of 2nd type of longitudinal bars at top.
nbl_t2 : np.ndarray
Number of 2nd type of longitudinal bars at top.
dbl_b1 : np.ndarray
Diameter of 1st type of longitudinal bars at bottom.
nbl_b1 : np.ndarray
Number of 1st type of longitudinal bars at bottom.
dbl_b2 : np.ndarray
Diameter of 2nd type of longitudinal bars at bottom.
nbl_b2 : np.ndarray
Number of 2nd type of longitudinal bars at bottom.
Abbreviations for rebars
------------------------
- b: rebar, bar, reinforcement
- l: longitudinal
- h: horizontal (transverse)
- n: number of
- d: diameter
Assumptions
-----------
- Always starts with top reinforcement
- At top or bottom of the beam sections at most two type of bars can
be used (e.g., dbl_t1, dbl_t2, dbl_b1, dbl_b2).
- The diameter of 1st type long. bar is always greater than or equal to
the diameter of 2nd type long. bar (dbl1 >= dbl2). Even if 2nd diam
is smaller, choose the closest one to dbl1 from the available bars.
- dbl1's and dbl2's at bottom and top parts of the sections do not
necessarily have to be the same. (e.g., dbl_t1=0.020, dbl_b1=0.025)
- Along the beam which is continuous over the multiple spans maximum of
two rebar diameter is allowed.
- At the two ends of beam sections, the provided reinforcement could be
different.
- Number of 1st type longitudinal bars is equal for all the sections.
Number of 1st type longitudinal bars at bottom and also at top
can be minimum of two. Hence, at least four corner bars of 1st type
are provided.
Example Section
---------------
::
Y
^
|
----------------------- -------------
| @t1 @t2 @t2 @t1 | | -- cover
| | |
| | |
| + | h
| | |
| | |
| @b1 @b2 @b1 @b2 @b1 | | -- cover
----------------------- ------------- ----> Z
|--------- b ---------|
Asl_top = 2.Ab_@t1 + 2.Ab_@t2
Asl_bot = 3.Ab_@b1 + 2.Ab_@b2
Number of unique diameter values at a section can be one or two
unique(db_@t1), db_@t2), db_@b1), db_@b2)) = (dbl_1, dbl_2)
OR
unique(db_@t1), db_@t2), db_@b1), db_@b2)) = dbl
TODO
----
1. Allow use of 3rd diameter for longitudinal reinforcement
2. Possibly limit the number of @t1's and @b1's to 2 (corner bars only)
3. Enforce continuous @t1 and @b1 (corner bars) in all beam sections.
"""
def get_two_type_long_bar_solution(
Asl_req: np.ndarray, nbl_1: int, dbl_1: float, dbl_2: float,
min_nbl_2: int | None = None
) -> tuple[np.ndarray]:
"""Determine the long. reinf. solution with two type of bars.
Parameters
----------
Asl_req : np.ndarray
Required longitudinal reinforcement area.
nbl_1 : int
Number of 1st type longitudinal bars.
dbl_1 : float
Diameter of 1st type longitudinal bars.
dbl_2 : float
Diameter of 2nd type longitudinal bars.
min_nbl_2 : int | None, optional
Minimum number of 2nd type longitudinal bars, by default None.
Returns
-------
nbl_2 : np.ndarray
Number of 2nd type longitudinal bars per section.
Asl_rat : np.ndarray
Ratio of long. reinf. area to required area per section.
"""
# Minimum transverse reinforcement diameter
dbh_min = self._get_min_beam_dbh(dbl=max(dbl_1, dbl_2))
# Assumed transverse reinf. diam. for long. bar arrangement
diff = self.beam_trans_bar_diams - dbh_min
dbh = self.beam_trans_bar_diams[np.where(diff >= 0)[0][0]]
if min_nbl_2 is None:
min_nbl_2 = nbl_1 - 1
nbl_2 = np.maximum(
np.ceil((Asl_req - nbl_1 * Ab_dict[dbl_1]) / Ab_dict[dbl_2]),
min_nbl_2)
distx = np.round((b - (nbl_1 * dbl_1 + nbl_2 * dbl_2
+ 2 * self.beam_cover + 2 * dbh)
) / (nbl_1 + nbl_2 - 1), PRECISION)
# Increase num rebars to not exceed max spacing limit
for i in np.where(distx > self.beam_max_sbl)[0].tolist():
while distx[i] > self.beam_max_sbl:
nbl_2[i] += 1
distx[i] = (b[i] - (nbl_1 * dbl_1 + nbl_2[i] * dbl_2
+ 2 * self.beam_cover + 2 * dbh)
) / (nbl_1 + nbl_2[i] - 1)
distx = np.round(distx, PRECISION)
Asl = nbl_1 * Ab_dict[dbl_1] + nbl_2 * Ab_dict[dbl_2]
Asl_rat = np.divide(Asl, Asl_req,
out=np.full_like(Asl, 1e2),
where=Asl_req != 0)
Asl_rat[distx < self.beam_min_sbl] = 1000
return nbl_2, Asl_rat
"""
INITIALIZATION
"""
# Number of beam sections
num_sec = len(Asl_top)
# Maximum number of #1 bars
max_nbl_1 = 4
# Rebar area for given longitudinal bar diameters
Ab_dict = {db: np.pi * (db**2) * 0.25
for db in self.beam_long_bar_diams}
"""
1) TOP LONGITUDINAL REBARS (TOP FLEXURAL REINFORCEMENT)
"""
top_Asl_rat_dict = {} # Ratio of top long. reinf. to required area
nbl_t2_dict = {} # Number of @t2 long. reinf. bars per solution
# ...........................................................................
# 1.A) Solutions with only one diameter (#1t)
# ...........................................................................
m = 2
n_min = 0
for dbl in self.beam_long_bar_diams:
key = f"{m}dbl1{dbl}_dbl2{dbl}"
n, Asl_rat = get_two_type_long_bar_solution(
Asl_req=Asl_top, nbl_1=m, dbl_1=dbl, dbl_2=dbl,
min_nbl_2=n_min)
top_Asl_rat_dict[key] = Asl_rat
nbl_t2_dict[key] = n
# ...........................................................................
# 1.B) Solutions with m #1t and n #2t bars with lower diameter
# ...........................................................................
for i, dbl_1 in enumerate(self.beam_long_bar_diams[1:], 1):
# Using the previous one as lower bar diameter
dbl_2 = self.beam_long_bar_diams[i - 1]
# NOTE: Alternatively iterate for all lower rebars
# for db2 in self.long_bar_diams[:i]:
for m in range(2, max_nbl_1 + 1, 1):
n, Asl_rat = get_two_type_long_bar_solution(
Asl_req=Asl_top, nbl_1=m, dbl_1=dbl_1, dbl_2=dbl_2)
key = f"{m}dbl1{dbl_1}_dbl2{dbl_2}"
nbl_t2_dict[key] = n
top_Asl_rat_dict[key] = Asl_rat
# ...........................................................................
# 1.C) Find solutions based on ratio of available and required areas
# ...........................................................................
err_top = {key: np.sum(rat) for key, rat in top_Asl_rat_dict.items()}
best_top: str = min(err_top, key=err_top.get)
dbl_top1 = float(best_top.split('_')[0].split('dbl1')[1])
dbl_top2 = float(best_top.split('_')[1].split('dbl2')[1])
nbl_top1 = int(best_top.split('_')[0].split('dbl1')[0])
dbl_t1 = dbl_top1 * np.ones(num_sec)
nbl_t1 = np.full(num_sec, nbl_top1)
dbl_t2 = dbl_top2 * np.ones(num_sec)
nbl_t2 = nbl_t2_dict[best_top]
"""
2) BOTTOM LONGITUDINAL REBARS (BOTTOM FLEXURAL REINFORCEMENT)
"""
bot_Asl_rat_dict = {} # Ratio of bot long. reinf. to required area
nbl_b2_dict = {} # Number of @b2 long. reinf. bars per solution
# Add solutions with only one diameter
m = 2
n_min = 0
for dbl in self.beam_long_bar_diams:
# Must have maximum of two different rebar diameters
bool1 = dbl_top1 == dbl_top2
bool2 = dbl_top1 == dbl
bool3 = dbl_top2 == dbl
if bool1 or bool2 or bool3:
key = f"{m}dbl1{dbl}_dbl2{dbl}"
n, Asl_rat = get_two_type_long_bar_solution(
Asl_req=Asl_bot, nbl_1=m, dbl_1=dbl, dbl_2=dbl,
min_nbl_2=n_min)
bot_Asl_rat_dict[key] = Asl_rat
nbl_b2_dict[key] = n
# Add solutions with two diameters
dbl_1 = dbl_top1 # Use the same big bar (#1)
if dbl_top1 != dbl_top2: # no other rebar option
dbl_2 = dbl_top2
for m in range(2, max_nbl_1 + 1, 1):
n, Asl_rat = get_two_type_long_bar_solution(
Asl_req=Asl_bot, nbl_1=m,
dbl_1=dbl_1, dbl_2=dbl_2)
key = f"{m}dbl1{dbl_1}_dbl2{dbl_2}"
nbl_b2_dict[key] = n
bot_Asl_rat_dict[key] = Asl_rat
else: # no other rebar options are possible
# Using the previous one as lower bar diameter
i = np.where(self.beam_long_bar_diams == dbl_1)[0][0]
dbl_2 = self.beam_long_bar_diams[i - 1]
# NOTE: Alternatively iterate for all lower rebars
# for db2 in self.long_bar_diams[:i]:
for m in range(2, max_nbl_1 + 1, 1):
n, Asl_rat = get_two_type_long_bar_solution(
Asl_req=Asl_bot, nbl_1=m,
dbl_1=dbl_1, dbl_2=dbl_2)
key = f"{m}dbl1{dbl_1}_dbl2{dbl_2}"
nbl_b2_dict[key] = n
bot_Asl_rat_dict[key] = Asl_rat
err_bot = {key: np.sum(rat)
for key, rat in bot_Asl_rat_dict.items()}
best_bot: str = min(err_bot, key=err_bot.get)
dbl_bot1 = float(best_bot.split('_')[0].split('dbl1')[1])
dbl_bot2 = float(best_bot.split('_')[1].split('dbl2')[1])
nbl_bot1 = int(best_bot.split('_')[0].split('dbl1')[0])
dbl_b1 = dbl_bot1 * np.ones(num_sec)
nbl_b1 = np.full(num_sec, nbl_bot1)
dbl_b2 = dbl_bot2 * np.ones(num_sec)
nbl_b2 = nbl_b2_dict[best_bot]
return (dbl_t1, nbl_t1, dbl_t2, nbl_t2,
dbl_b1, nbl_b1, dbl_b2, nbl_b2)
[docs]
def get_beam_transv_rebars(
self, Ash_sbh: np.ndarray, nbl_t1: np.ndarray, nbl_t2: np.ndarray,
nbl_b1: np.ndarray, nbl_b2: np.ndarray, dbl_t1: np.ndarray,
dbl_b1: np.ndarray, b: np.ndarray, h: np.ndarray
) -> tuple[np.ndarray]:
"""
Select transverse reinforcement solution for a generic beam with
dimension `b` and `h` over an alignment with N sections.
Parameters
----------
Ash_sbh : np.ndarray
Required transverse reinforcement area to spacing ratio.
nbl_t1 : np.ndarray
Number of 1st type of longitudinal bars at top.
nbl_t2 : np.ndarray
Number of 2nd type of longitudinal bars at top.
nbl_b1 : np.ndarray
Number of 1st type of longitudinal bars at bottom.
nbl_b2 : np.ndarray
Number of 2nd type of longitudinal bars at bottom.
dbl_t1 : np.ndarray
Diameter of 1st type of longitudinal bars at top.
dbl_b1 : np.ndarray
Diameter of 1st type of longitudinal bars at bottom.
b : np.ndarray
Beam breadth (width).
h : np.ndarray
Beam height (depth).
Returns
-------
dbh : np.ndarray
Diameter of horizontal bars (transverse reinforcement).
sbh : np.ndarray
Spacing of horizontal bars (transverse reinforcement).
nbh_parallel_b : np.ndarray
Number of horizontal bars (stirrup legs) parallel to width.
nbh_parallel_h : np.ndarray
Number of horizontal bars (stirrup legs) parallel to height.
Abbreviations for rebars
------------------------
- Same as `get_beam_long_rebars`.
Assumptions
-----------
- Same as `get_beam_long_rebars`.
"""
# Initialization
num_sec = len(Ash_sbh) # Number of beam sections
nbh_x = 2 * np.ones(num_sec) # no. of legs parallel to width, always 2
nbh_y = np.zeros(num_sec) # no. of legs parallel to height
sbh = np.zeros(num_sec) # transverse reinforcement (bar) spacing
dbh = np.zeros(num_sec) # transverse reinforcement (bar) diameter
# Maximum possible number of legs (bars) parallel to height
max_nbh_y = np.minimum(nbl_t1 + nbl_t2, nbl_b1 + nbl_b2)
# Minimum transverse reinforcement diameter
dbh_min = self._get_min_beam_dbh(dbl=np.maximum(dbl_t1, dbl_b1))
for j in range(num_sec):
# Initial assumptions for number of legs and diameters
nbh = 2 # start with closed stirrup
leg_conf_dist = 0.9 * b[j] / (nbh - 1)
while leg_conf_dist > self.beam_max_leg_dist:
nbh += 1 # add stirrup
leg_conf_dist = 0.9 * b[j] / (nbh - 1)
# No. of stirrup legs parallel to height
nbh_y[j] = nbh
# Current transverse reinforcement diameter
diff = self.beam_trans_bar_diams - dbh_min[j]
dbh[j] = self.beam_trans_bar_diams[np.where(diff >= 0)[0][0]]
# maximum allowed spacing in current iteration
max_sbh = self._get_beam_max_sbh(
b=b[j], h=h[j], dbh=dbh[j],
dbl=dbl_t1[j], cover=self.beam_cover)
max_sbh = np.round(max_sbh, PRECISION)
# Iterate for a solution
iterate = True
while iterate:
# total transverse reinforcement area along y
Ashy = 0.25 * np.pi * nbh_y[j] * dbh[j]**2
if Ash_sbh[j] == 0.0:
sbhy = self.beam_trans_bar_spacings[0]
else:
sbhy = Ashy / Ash_sbh[j]
sbh[j] = min(sbhy, max_sbh) # current spacing
for spacing in self.beam_trans_bar_spacings:
if sbh[j] >= spacing:
sbh[j] = spacing # This spacing works for this section
iterate = False # Stop iterating for this section
break # break the for loop
if iterate:
if sbh[j] == sbhy and np.all(nbh_y[j] <= max_nbh_y[j]):
# add a stirrup leg along y
nbh_y[j] += 1
elif iterate and dbh[j] < max(self.beam_trans_bar_diams):
# increase transverse reinforcement diameter
idx = np.where(
self.beam_trans_bar_diams == dbh[j])[0][0]
dbh[j] = self.beam_trans_bar_diams[idx + 1]
elif iterate:
# Accept the underdesign, no solution is found.
nbh_y[j] = max_nbh_y[j]
sbh[j] = min(self.beam_trans_bar_spacings)
dbh[j] = max(self.beam_trans_bar_diams)
iterate = False
return dbh, sbh, nbh_x, nbh_y
[docs]
def get_column_long_rebars(
self, Aslx_req: np.ndarray, Asly_req: np.ndarray,
rhol_min: np.ndarray, bx: np.ndarray, by: np.ndarray
) -> tuple[np.ndarray]:
"""Select the longitudinal reinforcement solution for a generic
column with dimension `bx` and `by` over an alignment with N sections.
Parameters
----------
Aslx_req : np.ndarray
Required longitudinal reinforcement area at
bottom or top side of the section, In other words,
required area of bars distributed along -x on one side.
Asly_req : np.ndarray
Required longitudinal reinforcement area at
left or right side of the section. In other words,
required area of bars distributed along -y on one side.
rhol_min : np.ndarray
Minimum longitudinal reinforcement ratio.
bx : np.ndarray
Breadth (width) along global -x axis.
by : np.ndarray
Breadth (width) along global -y axis.
Returns
-------
dbl_c : np.ndarray
Diameter of corner longitudinal bars.
dbl_i : np.ndarray
Diameter of internal longitudinal bars.
nbl_ix : np.ndarray
Number of internal longitudinal bars (reinforcement) at
bottom or top side of the section. In other words,
half of the total number of internal longitudinal bars
distributed along X (on one side of the section).
nbl_iy : np.ndarray
Number of internal longitudinal bars (reinforcement) at
left or right side of the section. In other words,
half of the total number of internal longitudinal bars
distributed along Y (on one side of the section).
Abbreviations for rebars
------------------------
b: rebar, bar, reinforcement
l: longitudinal
h: horizontal (transverse)
n: number of
d: diameter
i: internal
c: corner
Assumptions
-----------
- All corner bars have the same diameter.
- Corner bars are continuous over the sections.
- All internal bars have the same diameter.
- Internal bars may or may not be continuous.
- Internal bars can have either the same diameters as corner bars or
can have a smaller diameter which is still closest to the corner one.
Example Section
---------------
::
Y
^
|
----------------------- -------------
| @c @i @i @c | | ---- cover
| | |
| @i @i | |
| | |
| @i + @i | by
| | |
| @i @i | |
| | |
| @c @i @i @c | | ---- cover
----------------------- ------------- ----> X
|-------- bx ---------|
Aslx_req = 2.Ab_@c + 2.Ab_@i
Asly_req = 2.Ab_@c + 3.Ab_@i
Number of unique diameter values at a section can be one or two
unique(db_@c, db_@i) = (dbl_c, dbl_i)
OR
unique(db_@c, db_@i) = dbl_c
"""
def get_two_type_long_bar_solution(
Aslx_req: np.ndarray, Asly_req: np.ndarray,
nbl_1x: int, nbl_1y: int, dbl_1: float, dbl_2: float,
min_nbl_2: int
) -> tuple[np.ndarray]:
"""Determine the long. reinf. solution with two type of bars.
Parameters
----------
Aslx_req : np.ndarray
Required longitudinal reinforcement area at
bottom or top side of the section, In other words,
required area of bars distributed along -x on one side.
Asly_req : np.ndarray
Required longitudinal reinforcement area at
left or right side of the section. In other words,
required area of bars distributed along -y on one side.
nbl_1x : int
Number of 1st type longitudinal bars at bottom or top side of
the section.
nbl_1y : int
Number of 1st type longitudinal bars at left or right side of
the section.
dbl_1 : float
Diameter of 1st type longitudinal bars.
dbl_2 : float
Diameter of 2nd type longitudinal bars.
min_nbl_2 : int
Minimum number of 2nd type longitudinal bars.
Returns
-------
nbl_2x : int
Number of 2nd type longitudinal bars at bottom or top side
of the section.
nbl_2y : int
Number of 2nd type longitudinal bars at left or right side
of the section.
Asl : np.ndarray
Total longitudinal reinforcement area per section.
"""
# Minimum transverse reinforcement diameter
dbh_min = self._get_min_col_dbh(dbl=dbl_c)
# Assumed transverse reinf. diam. for long. bar arrangement
diff = self.col_trans_bar_diams - dbh_min
dbh = self.col_trans_bar_diams[np.where(diff >= 0)[0][0]]
# Number of bars with 2nd diameter
nbl_2x = np.maximum(
np.ceil((Aslx_req - nbl_1x * Ab_dict[dbl_1]) / Ab_dict[dbl_2]),
min_nbl_2)
nbl_2y = np.maximum(
np.ceil((Asly_req - nbl_1y * Ab_dict[dbl_1]) / Ab_dict[dbl_2]),
min_nbl_2)
# Bar spacing
dist_x = np.round((bx - (nbl_1x * dbl_1 + nbl_2x * dbl_2
+ 2 * self.col_cover + 2 * dbh)
) / (nbl_1x + nbl_2x - 1), PRECISION)
dist_y = np.round((by - (nbl_1y * dbl_1 + nbl_2y * dbl_2
+ 2 * self.col_cover + 2 * dbh)
) / (nbl_1y + nbl_2y - 1), PRECISION)
# Increase num rebars along X to not exceed max spacing limit
for i in np.where(dist_x > self.col_max_sbl)[0].tolist():
while dist_x[i] > self.col_max_sbl[i]:
nbl_2x[i] += 1
dist_x[i] = (bx[i] - (nbl_1x * dbl_1 + nbl_2x[i] * dbl_2
+ 2 * self.col_cover + 2 * dbh)
) / (nbl_1x + nbl_2x[i] - 1)
dist_x = np.round(dist_x, PRECISION)
# Increase num rebars along Y to not exceed max spacing limit
for i in np.where(dist_y > self.col_max_sbl)[0].tolist():
while dist_y[i] > self.col_max_sbl[i]:
nbl_2y[i] += 1
dist_y[i] = (by[i] - (nbl_1y * dbl_1 + nbl_2y[i] * dbl_2
+ 2 * self.col_cover + 2 * dbh)
) / (nbl_1y + nbl_2y[i] - 1)
dist_y = np.round(dist_y, PRECISION)
# Compute total bar area
Aslx = nbl_1x * Ab_dict[dbl_1] + nbl_2x * Ab_dict[dbl_2]
Asly = nbl_1y * Ab_dict[dbl_1] + nbl_2y * Ab_dict[dbl_2]
Asl = 2 * (Aslx + Asly) - 4 * Ab_dict[dbl_1]
Asl_min = rhol_min * bx * by
# Check for min. long. reinf. and increase reinf.
for i in np.where(Asl_min > Asl)[0].tolist():
while Asl_min[i] > Asl[i]:
if Aslx[i] / Asly[i] < bx[i] / by[i]:
nbl_2x[i] += 1
dist_x[i] = (bx[i] - (
nbl_1x * dbl_1 + nbl_2x[i] * dbl_2
+ 2 * self.col_cover + 2 * dbh)
) / (nbl_1x + nbl_2x[i] - 1)
dist_x = np.round(dist_x, PRECISION)
else:
nbl_2y[i] += 1
dist_y[i] = (by[i] - (
nbl_1y * dbl_1 + nbl_2y[i] * dbl_2
+ 2 * self.col_cover + 2 * dbh)
) / (nbl_1y + nbl_2y[i] - 1)
dist_y = np.round(dist_y, PRECISION)
Aslx[i] = (
nbl_1x * Ab_dict[dbl_1] + nbl_2x[i] * Ab_dict[dbl_2]
)
Asly[i] = (
nbl_1y * Ab_dict[dbl_1] + nbl_2y[i] * Ab_dict[dbl_2]
)
Asl[i] = 2 * (Aslx[i] + Asly[i]) - 4 * Ab_dict[dbl_1]
# Penalize the solution if it does not respect the spacing limit
Asl[dist_x < self.col_min_sbl] = 1e4
Asl[dist_y < self.col_min_sbl] = 1e4
# Return solutions
return nbl_2x, nbl_2y, Asl
"""
INITIALIZATION
"""
# Number of column sections
num_sec = len(Aslx_req)
# Rebar area for given longitudinal bar diameters
Ab_dict = {db: np.pi * (db**2) * 0.25
for db in self.col_long_bar_diams}
Asl_dict = {} # Longitudinal reinforcement area for solutions
# Internal long. reinf. bars distributed along -x per solution
nbl_ix_dict = {}
# Internal long. reinf. bars distributed along -y per solution
nbl_iy_dict = {}
# Get rebar solutions
# NOTE: Assume internal bars will have same diameter,
# and only corner bars could be different (and bigger)
nbl_cx = 2
nbl_cy = 2
min_nbl_i = 0
for i, dbl_c in enumerate(self.col_long_bar_diams):
for dbl_i in reversed(
self.col_long_bar_diams[max(i - 1, 0): i + 1]
):
key = f"dblc{dbl_c}_dbli{dbl_i}"
nbl_ix, nbl_iy, Asl = get_two_type_long_bar_solution(
Aslx_req, Asly_req, nbl_cx, nbl_cy, dbl_c, dbl_i,
min_nbl_i)
Asl_dict[key] = Asl
nbl_ix_dict[key] = nbl_ix
nbl_iy_dict[key] = nbl_iy
# Get the final solution based on the lowest total bar area
# NOTE: The issue is that we do not really check the compatibility of
# columns e.g., for lap-splices
area = {key: np.sum(Asl) for key, Asl in Asl_dict.items()}
best: str = min(area, key=area.get)
dbl_c = float(best.split('_')[0].split('dblc')[1])
dbl_i = float(best.split('_')[1].split('dbli')[1])
dbl_c = dbl_c * np.ones(num_sec)
dbl_i = dbl_i * np.ones(num_sec)
nbl_ix = nbl_ix_dict[best]
nbl_iy = nbl_iy_dict[best]
return dbl_c, dbl_i, nbl_ix, nbl_iy
[docs]
def get_column_transv_rebars(
self, bx: np.ndarray, by: np.ndarray,
Ashx_sbh_req: np.ndarray, Ashy_sbh_req: np.ndarray,
dbl_c: np.ndarray, nbl_ix: np.ndarray, nbl_iy: np.ndarray
) -> tuple[np.ndarray]:
"""Select transverse (horizontal/shear) reinforcement solution
for a generic column with dimension `bx` and `by` over an alignment
with N sections.
Parameters
----------
bx : np.ndarray
Breadth (width) along global -x axis.
by : np.ndarray
Breadth (width) along global -y axis.
Ashx_sbh_req : np.ndarray
Required ratio of transverse reinforcement area along -x
axis (i.e., parallel to -x axis) to the bar spacing.
Ashy_sbh_req : np.ndarray
Required ratio of transverse reinforcement area along -y
axis (i.e., parallel to -y axis) to the bar spacing.
dbl_c : np.ndarray
Diameter of corner longitudinal bars.
nbl_ix : np.ndarray
Number of internal longitudinal bars (reinforcement) at
bottom or top side of the section. In other words,
half of the total number of internal longitudinal bars
distributed along X (on one side of the section).
nbl_iy : np.ndarray
Number of internal longitudinal bars (reinforcement) at
left or right side of the section. In other words,
half of the total number of internal longitudinal bars
distributed along Y (on one side of the section).
Returns
-------
dbh : np.ndarray
Diameter of horizontal bars (transverse reinforcement).
sbh : np.ndarray
Spacing of horizontal bars (transverse reinforcement).
nbh_x : np.ndarray
Number of horizontal bars (stirrup legs) along -x axis.
nbh_y : np.ndarray
Number of horizontal bars (stirrup legs) along -y axis.
Abbreviations for rebars
------------------------
- Same as `get_column_long_rebars`.
Assumptions
-----------
- Same as `get_column_long_rebars`.
"""
# Initialization
nbl_cy = 2 # Number of corner bars parallel to y-axis on one side
nbl_cx = 2 # Number of corner bars parallel to x-axis on one side
num_sec = len(Ashx_sbh_req) # Number of column sections
nbh_x = 2 * np.ones(num_sec) # no. of legs parallel to x-axis
nbh_y = 2 * np.ones(num_sec) # no. of legs parallel to y-axis
sbh = np.zeros(num_sec) # transverse reinforcement (bar) spacing
dbh = np.zeros(num_sec) # transverse reinforcement (bar) diameter
# Maximum possible number of legs (bars)
max_nbh_x = nbl_cy + nbl_iy
max_nbh_y = nbl_cx + nbl_ix
# Minimum transverse reinforcement diameter
dbh_min = self._get_min_col_dbh(dbl=dbl_c)
for j in range(num_sec):
# Initial no. of stirrup legs parallel to x-axis
nbh_x[j] = 2 # start with closed stirrup
leg_conf_dist_x = 0.9 * by[j] / (nbh_x[j] - 1)
while leg_conf_dist_x > self.col_max_leg_dist:
nbh_x[j] += 1 # add stirrup leg
leg_conf_dist_x = 0.9 * by[j] / (nbh_x[j] - 1)
# Initial no. of stirrup legs parallel to y-axis
nbh_y[j] = 2 # start with closed stirrup
leg_conf_dist_y = 0.9 * bx[j] / (nbh_y[j] - 1)
while leg_conf_dist_y > self.col_max_leg_dist:
nbh_y[j] += 1 # add stirrup leg
leg_conf_dist_y = 0.9 * bx[j] / (nbh_y[j] - 1)
# Initial transverse reinforcement diameter
diff = self.col_trans_bar_diams - dbh_min[j]
dbh[j] = self.col_trans_bar_diams[np.where(diff >= 0)[0][0]]
# maximum allowed spacing in current iteration
max_sbh = self._get_col_max_sbh(
bx=bx[j], by=by[j], dbh=dbh[j], dbl=dbl_c[j],
cover=self.col_cover)
max_sbh = np.round(max_sbh, PRECISION)
# Iterate for a solution
iterate = True
while iterate:
# total transverse reinforcement area
Ashx = 0.25 * np.pi * nbh_x[j] * dbh[j]**2 # along x
Ashy = 0.25 * np.pi * nbh_y[j] * dbh[j]**2 # along y
# initial spacing of stirrups along the member
if Ashx_sbh_req[j] == 0.0:
sbhx = self.col_trans_bar_spacings[0]
else:
sbhx = Ashx / Ashx_sbh_req[j]
if Ashy_sbh_req[j] == 0.0:
sbhy = self.col_trans_bar_spacings[0]
else:
sbhy = Ashy / Ashy_sbh_req[j]
sbh[j] = min(sbhx, sbhy, max_sbh)
for spacing in self.col_trans_bar_spacings:
if sbh[j] >= spacing:
sbh[j] = spacing # This spacing works for this section
iterate = False # Stop iterating for this section
break # break the for loop
if iterate:
if sbh[j] == sbhy and np.all(nbh_y[j] <= max_nbh_y[j]):
# add a stirrup leg along y
nbh_y[j] += 1
elif sbh[j] == sbhx and np.all(nbh_x[j] <= max_nbh_x[j]):
# add a stirrup leg along x
nbh_x[j] += 1
elif dbh[j] < max(self.col_trans_bar_diams):
# increase transverse reinforcement diameter
idx = np.where(
self.col_trans_bar_diams == dbh[j])[0][0]
dbh[j] = self.col_trans_bar_diams[idx + 1]
else:
# Accept the underdesign, no solution is found.
nbh_y[j] = max_nbh_y[j]
nbh_x[j] = max_nbh_x[j]
sbh[j] = min(self.col_trans_bar_spacings)
dbh[j] = max(self.col_trans_bar_diams)
iterate = False
return dbh, sbh, nbh_x, nbh_y