"""This module provides the quality class implementation representing the
quality-based modifications for the ``tr_7599`` design class in the BDIM layer.
"""
# Imports from installed packages
from pathlib import Path
from typing import List, Literal
import numpy as np
from scipy.stats import lognorm, uniform, truncnorm
import json
# Imports from tr_7599
from .column import Column
from .beam import Beam
# Imports from bdim base library
from ..baselib.quality import QualityBase
from ..baselib.quality import QualityModelData as QualityModelDataBase
from ..baselib.quality import QualityData as QualityDataBase
POSSIBLE_FI = np.array([12, 16, 20, 24]) / 1000
"""Array of allowed reinforcement bar diameters (in meters) commonly used in
design."""
ALPHA_LOW = 0
"""Lower bound for scaling factor used in grid search for reinforcement
adjustment."""
ALPHA_HIGH = 1.05
"""Upper bound for scaling factor used in grid search for reinforcement
adjustment."""
STEP = 0.05
"""Step size for generating alpha scaling factors between ALPHA_LOW
and ALPHA_HIGH."""
# Generate scaling factor grid for modifying reinforcement configurations
alphas = np.arange(ALPHA_LOW, ALPHA_HIGH, STEP)
# Generate 2D meshgrid of alphas for longitudinal bar diameters and bar counts
alpha_dbl, alpha_nbl = np.meshgrid(alphas, alphas)
# Flatten the scaling factors to vectors
alpha_dbl_flat = alpha_dbl.ravel()
alpha_nbl_flat = alpha_nbl.ravel()
[docs]
class QualityModelData(QualityModelDataBase):
"""Construction quality model data for the ``tr_7599`` design class.
This class extends the ``QualityModelData`` class in baselib by introducing
new attributes related to the adjusted longitudinal reinforcement ratio.
Attributes
----------
uniform_low_sbh_column : float
Lower boundary of uniform stirrup spacing ratio distribution in
columns.
uniform_up_sbh_column : float
Upper boundary of uniform stirrup spacing ratio distribution in
columns.
mean_col_rhol : float
Mean of in-situ to design rhol ratio distribution.
std_col_rhol : float
Standard deviation of in-situ to design rhol ratio distribution.
lower_col_rhol : float
Lower bound of in-situ to design rhol ratio distribution.
upper_col_rhol : float
Upper bound of in-situ to design rhol ratio distribution.
mean_beam_rhol_top : float
Mean of in-situ to design rhol ratio distribution.
std_beam_rhol_top : float
Standard deviation of in-situ to design rhol ratio distribution.
lower_beam_rhol_top : float
Lower bound of in-situ to design rhol ratio distribution.
upper_beam_rhol_top : float
Upper bound of in-situ to design rhol ratio distribution.
mean_beam_rhol_bot : float
Mean of in-situ to design rhol ratio distribution.
std_beam_rhol_bot : float
Standard deviation of in-situ to design rhol ratio distribution.
lower_beam_rhol_bot : float
Lower bound of in-situ to design rhol ratio distribution.
upper_beam_rhol_bot : float
Upper bound of in-situ to design rhol ratio distribution.
See Also
--------
:class:`~QualityModelDataBase`
Base class defining the core behaviour and configuration.
"""
uniform_low_sbh_column: float
uniform_up_sbh_column: float
mean_col_rhol: float
std_col_rhol: float
lower_col_rhol: float
upper_col_rhol: float
mean_beam_rhol_top: float
std_beam_rhol_top: float
lower_beam_rhol_top: float
upper_beam_rhol_top: float
mean_beam_rhol_bot: float
std_beam_rhol_bot: float
lower_beam_rhol_bot: float
upper_beam_rhol_bot: float
[docs]
class QualityData(QualityDataBase):
"""Construction quality models for the ``tr_7599`` design class.
This class extends ``QualityDataBase`` class in baselib by narrowing
the attribute types.
Attributes
----------
high : QualityModelData
High construction quality model.
moderate : QualityModelData
Moderate construction quality model.
low : QualityModelData
Low construction quality model.
See Also
--------
:class:`~QualityDataBase`
Base class defining the core behaviour and configuration.
"""
high: QualityModelData
moderate: QualityModelData
low: QualityModelData
[docs]
class Quality(QualityBase):
"""Quality implementation for the ``tr_7599`` design class.
This class extends ``QualityBase`` by narrowing the attribute types
and overriding :meth:`set_adjusted_properties` method.
Attributes
----------
data_path : Path
Path to the JSON file containing construction quality data.
data : QualityData
All the construction quality data considered for the design class.
_model : QualityModelData
Internal attribute for selected construction quality model.
See Also
--------
:class:`~QualityBase`
Base class defining the core behaviour and configuration.
"""
data_path: Path | str = Path(__file__).parent / 'data' / 'quality.json'
data: QualityData
_model: QualityModelData
def __init__(self) -> None:
"""Initialize a new instance of Quality class.
"""
# Load quality model data
with open(self.data_path, 'r') as json_file:
# Load the JSON data into a Python dictionary
data = json.load(json_file)
# Store the quality model
self.data = QualityData(**data)
# Set the seed to use
if self.seed:
np.random.seed(self.seed)
@property
def model(self) -> QualityModelData:
"""Get the selected construction quality model.
Returns
-------
QualityModelData
Construction quality model.
"""
return self._model
@model.setter
def model(self, identifier: Literal[0, 1, 2, 3]) -> None:
"""Select construction quality model based on the quality identifier.
Parameters
----------
identifier : Literal[0, 1, 2, 3]
Construction quality identifier
0. Excellent construction quality.
1. High construction quality.
2. Moderate construction quality.
3. Low construction quality.
Notes
-----
Excellent construction quality (0) corresponds to the expected values.
In other words, quality based modification is not performed to adjust
the expected parameters through sampling (deterministic mode). Fixed
parameters such as bondslip_factor correspond to that of high
construction quality.
"""
mapper = {
0: self.data.high,
1: self.data.high,
2: self.data.moderate,
3: self.data.low
}
self._model = mapper.get(identifier)
if identifier == 0:
self._model.rand = False
[docs]
def set_adjusted_properties(self, beams: List[Beam],
columns: List[Column]) -> None:
"""Set the quality adjusted properties of beams and columns:
Notes
-----
In particular, the following properties are modified:
- Concrete compressive strength
- Longitudinal reinforcement yield strength
- Transverse reinforcement yield strength
- Concrete cover
- Stirrup spacing
- Number of longitudinal bars
- Diameter of longitudinal bars
Parameters
----------
beams : List[~simdesign.rcmrf.bnsm.baselib.beam.BeamBase]
List of beams whose properties will be adjusted.
columns : List[~simdesign.rcmrf.bnsm.baselib.column.ColumnBase]
List of columns whose properties will be adjusted.
"""
# Initialize some parameters
theta_fck = self.model.theta_fck
sigma_fck = self.model.sigma_fck
theta_fsyk = self.model.theta_fsyk
sigma_fsyk = self.model.sigma_fsyk
theta_cover = self.model.theta_cover
sigma_cover = self.model.sigma_cover
uniform_low_sbh = self.model.uniform_low_sbh
uniform_up_sbh = self.model.uniform_up_sbh
uniform_low_sbh_column = self.model.uniform_low_sbh_column
uniform_up_sbh_column = self.model.uniform_up_sbh_column
num_columns = len(columns)
num_beams = len(beams)
# NOTE: Mean strength ratio is also randomised*
theta_fck = lognorm.ppf(np.random.rand(), s=sigma_fck, scale=theta_fck)
if self._model.rand:
# Concrete compressive strength
col_fc_ratio = lognorm.ppf(
np.random.rand(num_columns), s=sigma_fck, scale=theta_fck
)
beam_fc_ratio = lognorm.ppf(
np.random.rand(num_beams), s=sigma_fck, scale=theta_fck
)
# Longitudinal steel yield strength
col_fsyl_ratio = lognorm.ppf(
np.random.rand(num_columns), s=sigma_fsyk, scale=theta_fsyk
)
beam_fsyl_ratio = lognorm.ppf(
np.random.rand(num_beams), s=sigma_fsyk, scale=theta_fsyk
)
# Transverse steel yield strength
col_fsyh_ratio = lognorm.ppf(
np.random.rand(num_columns), s=sigma_fsyk, scale=theta_fsyk
)
beam_fsyh_ratio = lognorm.ppf(
np.random.rand(num_beams), s=sigma_fsyk, scale=theta_fsyk
)
# Concrete cover
col_cover_ratio = lognorm.ppf(
np.random.rand(num_columns), s=sigma_cover, scale=theta_cover
)
beam_cover_ratio = lognorm.ppf(
np.random.rand(num_beams), s=sigma_cover, scale=theta_cover
)
# Stirrup spacing
col_sbh_ratio = uniform.ppf(
np.random.rand(num_columns),
loc=uniform_low_sbh_column,
scale=uniform_up_sbh_column - uniform_low_sbh_column,
)
beam_sbh_start_ratio = uniform.ppf(
np.random.rand(num_beams),
loc=uniform_low_sbh,
scale=uniform_up_sbh - uniform_low_sbh,
)
beam_sbh_end_ratio = uniform.ppf(
np.random.rand(num_beams),
loc=uniform_low_sbh,
scale=uniform_up_sbh - uniform_low_sbh,
)
beam_sbh_mid_ratio = uniform.ppf(
np.random.rand(num_beams),
loc=uniform_low_sbh,
scale=uniform_up_sbh - uniform_low_sbh,
)
else:
# Concrete compressive strength
col_fc_ratio = np.ones(num_columns)
beam_fc_ratio = np.ones(num_beams)
# Longitudinal steel yield strength
col_fsyl_ratio = np.ones(num_columns)
beam_fsyl_ratio = np.ones(num_beams)
# Transverse steel yield strength
col_fsyh_ratio = np.ones(num_columns)
beam_fsyh_ratio = np.ones(num_beams)
# Concrete cover
col_cover_ratio = np.ones(num_columns)
beam_cover_ratio = np.ones(num_beams)
# Stirrup spacing
col_sbh_ratio = np.ones(num_columns)
beam_sbh_start_ratio = np.ones(num_beams)
beam_sbh_end_ratio = np.ones(num_beams)
beam_sbh_mid_ratio = np.ones(num_beams)
# Store adjusted properties of columns
for i, col in enumerate(columns):
col.fc_q = col.fcm * col_fc_ratio[i]
col.fsyl_q = col.fsym * col_fsyl_ratio[i]
col.fsyh_q = col.fsym * col_fsyh_ratio[i]
col.cover_q = col.cover * col_cover_ratio[i]
col.sbh_q = col.sbh * col_sbh_ratio[i]
# Extra design parameters which can be quality adjusted
col.dbh_q = col.dbh
col.nbh_x_q = col.nbh_x
col.nbh_y_q = col.nbh_x
col.dbl_cor_q = col.dbl_cor
col.dbl_int_q = col.dbl_int
col.nblx_int_q = col.nblx_int
col.nbly_int_q = col.nbly_int
# Store adjusted properties of beams
for i, beam in enumerate(beams):
beam.fc_q = beam.fcm * beam_fc_ratio[i]
beam.fsyl_q = beam.fsym * beam_fsyl_ratio[i]
beam.fsyh_q = beam.fsym * beam_fsyh_ratio[i]
beam.cover_q = beam.cover * beam_cover_ratio[i]
sbh_start = beam.sbh[0] * beam_sbh_start_ratio[i]
sbh_mid = beam.sbh[1] * beam_sbh_mid_ratio[i]
sbh_end = beam.sbh[2] * beam_sbh_end_ratio[i]
beam.sbh_q = np.array([sbh_start, sbh_mid, sbh_end])
# Extra design parameters which can be quality adjusted
beam.nbh_b_q = beam.nbh_b
beam.nbh_h_q = beam.nbh_h
beam.dbh_q = beam.dbh
beam.dbl_t1_q = beam.dbl_t1
beam.nbl_t1_q = beam.nbl_t1
beam.dbl_t2_q = beam.dbl_t2
beam.nbl_t2_q = beam.nbl_t2
beam.dbl_b1_q = beam.dbl_b1
beam.nbl_b1_q = beam.nbl_b1
beam.dbl_b2_q = beam.dbl_b2
beam.nbl_b2_q = beam.nbl_b2
if not self._model.rand:
return
# Start adjusting column longitudinal reinforcement ratio
# Compute ratio of in-situ vs. design rhol
if self._model.std_col_rhol == 0.0:
ratios = self._model.mean_col_rhol * np.ones(len(columns))
else:
mean = self._model.mean_col_rhol
std = self._model.std_col_rhol
lower = self._model.lower_col_rhol
upper = self._model.upper_col_rhol
# Convert to standard normal bounds
a, b = (lower - mean) / std, (upper - mean) / std
# Create and sample
trunc_normal = truncnorm(a, b, loc=mean, scale=std)
ratios = trunc_normal.rvs(size=len(columns))
# Start changing column
for i, col in enumerate(columns):
self._grid_search_reduce_col_rhol_q(col, ratios[i])
# Start adjusting beam top longitudinal reinforcement ratio
# Compute ratio of in-situ vs. design rhol
if self._model.std_beam_rhol_top == 0.0:
ratios = self._model.mean_beam_rhol_top * np.ones(len(beams))
else:
mean = self._model.mean_beam_rhol_top
std = self._model.std_beam_rhol_top
lower = self._model.lower_beam_rhol_top
upper = self._model.upper_beam_rhol_top
# Convert to standard normal bounds
a, b = (lower - mean) / std, (upper - mean) / std
# Create and sample
trunc_normal = truncnorm(a, b, loc=mean, scale=std)
ratios = trunc_normal.rvs(size=len(beams))
# Start changing beam
for i, beam in enumerate(beams):
self._grid_search_reduce_beam_rhol_top_q(beam, ratios[i])
# Start adjusting beam bottom longitudinal reinforcement ratio
# Compute ratio of in-situ vs. design rhol
if self._model.std_beam_rhol_bot == 0.0:
ratios = self._model.mean_beam_rhol_bot * np.ones(len(beams))
else:
mean = self._model.mean_beam_rhol_bot
std = self._model.std_beam_rhol_bot
lower = self._model.lower_beam_rhol_bot
upper = self._model.upper_beam_rhol_bot
# Convert to standard normal bounds
a, b = (lower - mean) / std, (upper - mean) / std
# Create and sample
trunc_normal = truncnorm(a, b, loc=mean, scale=std)
ratios = trunc_normal.rvs(size=len(beams))
# Start changing beam
for i, beam in enumerate(beams):
self._grid_search_reduce_beam_rhol_bot_q(beam, ratios[i])
def _grid_search_reduce_col_rhol_q(
self, col: Column, target_ratio: float
) -> None:
"""Reduces the longitudinal reinforcement ratio (rhol) of a column
by selecting the closest feasible bar configuration.
This is done via a brute-force grid search over a precomputed meshgrid
of scaling factors applied to:
- Corner and internal longitudinal bar diameters.
- Internal bar counts in x and y directions.
The best combination is selected to minimize the error from
the target in-situ/design rhol ratio.
Parameters
----------
col : Column
Column object whose reinforcement configuration is to be modified.
target_ratio : float
Target ratio of in-situ to design longitudinal reinforcement.
"""
if target_ratio == 1.0:
return
orig_dbl_cor = col.dbl_cor
orig_dbl_int = col.dbl_int
orig_nbly = col.nbly_int
orig_nblx = col.nblx_int
Ag = col.Ag
original_rhol = col.rhol
target_rhol = target_ratio * original_rhol
# Compute scaled diameters and bar counts
dbl_cor_scaled = self.__closest_diameter(orig_dbl_cor * alpha_dbl_flat)
dbl_int_scaled = self.__closest_diameter(orig_dbl_int * alpha_dbl_flat)
nbly_scaled = np.maximum(0, (orig_nbly * alpha_nbl_flat).astype(int))
nblx_scaled = np.maximum(0, (orig_nblx * alpha_nbl_flat).astype(int))
# Compute areas
Abl_cor = (np.pi * dbl_cor_scaled**2) / 4
Abl_int = (np.pi * dbl_int_scaled**2) / 4
nbl_cor = 4
nbl_int = 2 * (nbly_scaled + nblx_scaled)
# Compute rhol_q for all combinations
rhol_q = (nbl_cor * Abl_cor + nbl_int * Abl_int) / Ag
error = np.abs(rhol_q - target_rhol)
# Find best configuration
best_idx = np.argmin(error)
col.dbl_cor_q = dbl_cor_scaled[best_idx]
col.dbl_int_q = dbl_int_scaled[best_idx]
col.nbly_int_q = nbly_scaled[best_idx]
col.nblx_int_q = nblx_scaled[best_idx]
def _grid_search_reduce_beam_rhol_top_q(
self, beam: Beam, target_ratio: float
) -> None:
"""Reduces the top longitudinal reinforcement ratio
(rhol) of a beam by selecting the closest feasible bar
configuration.
This is done via a brute-force grid search over a precomputed meshgrid
of scaling factors applied to:
- Top layer bar diameters (dbl_t1, dbl_t2).
- Secondary top bar count (nbl_t2).
The best combination is selected to minimize the error from
the target in-situ/design rhol ratio.
Parameters
----------
beam : Beam
Beam object whose reinforcement configuration is to be modified.
target_ratio : float
Target ratio of in-situ to design longitudinal reinforcement.
"""
if target_ratio == 1.0:
return
orig_dbl_t1 = beam.dbl_t1[0]
orig_dbl_t2 = beam.dbl_t2[0]
orig_nbl_t2 = beam.nbl_t2[0]
Ag = beam.Ag
original_rhol_top = beam.rhol_top[0]
target_rhol_top = target_ratio * original_rhol_top
# Compute scaled diameters and bar counts
dbl_t1_scaled = self.__closest_diameter(orig_dbl_t1 * alpha_dbl_flat)
dbl_t2_scaled = self.__closest_diameter(orig_dbl_t2 * alpha_dbl_flat)
nbl_t2_scaled = np.maximum(
0, (orig_nbl_t2 * alpha_nbl_flat).astype(int)
)
# Compute areas
Abl_t1 = (np.pi * dbl_t1_scaled**2) / 4
Abl_t2 = (np.pi * dbl_t2_scaled**2) / 4
# Compute rhol_q for all combinations
rhol_top_q = (2 * Abl_t1 + nbl_t2_scaled * Abl_t2) / Ag
error = np.abs(rhol_top_q - target_rhol_top)
# Find best configuration
best_idx = np.argmin(error)
beam.dbl_t1_q[0] = dbl_t1_scaled[best_idx]
beam.dbl_t2_q[0] = dbl_t2_scaled[best_idx]
beam.nbl_t2_q[0] = nbl_t2_scaled[best_idx]
beam.dbl_t1_q[2] = dbl_t1_scaled[best_idx]
beam.dbl_t2_q[2] = dbl_t2_scaled[best_idx]
beam.nbl_t2_q[2] = nbl_t2_scaled[best_idx]
def _grid_search_reduce_beam_rhol_bot_q(
self, beam: Beam, target_ratio: float
) -> None:
"""Reduces the bottom longitudinal reinforcement ratio
(rhol) of a beam by selecting the closest feasible bar
configuration.
This is done via a brute-force grid search over a precomputed meshgrid
of scaling factors applied to:
- Bottom layer bar diameters (dbl_b1, dbl_b2).
- Secondary bottom bar count (nbl_b2).
The best combination is selected to minimize the error from
the target in-situ/design rhol ratio.
Parameters
----------
beam : Beam
Beam object whose reinforcement configuration is to be modified.
target_ratio : float
Target ratio of in-situ to design longitudinal reinforcement.
"""
if target_ratio == 1.0:
return
orig_dbl_b1 = beam.dbl_b1[0]
orig_dbl_b2 = beam.dbl_b2[0]
orig_nbl_b2 = beam.nbl_b2[0]
Ag = beam.Ag
original_rhol_bot = beam.rhol_bot[0]
target_rhol_bot = target_ratio * original_rhol_bot
# Compute scaled diameters and bar counts
dbl_b1_scaled = self.__closest_diameter(orig_dbl_b1 * alpha_dbl_flat)
dbl_b2_scaled = self.__closest_diameter(orig_dbl_b2 * alpha_dbl_flat)
nbl_b2_scaled = np.maximum(
0, (orig_nbl_b2 * alpha_nbl_flat).astype(int)
)
# Compute areas
Abl_b1 = (np.pi * dbl_b1_scaled**2) / 4
Abl_b2 = (np.pi * dbl_b2_scaled**2) / 4
# Compute rhol_q for all combinations
rhol_bot_q = (2 * Abl_b1 + nbl_b2_scaled * Abl_b2) / Ag
error = np.abs(rhol_bot_q - target_rhol_bot)
# Find best configuration
best_idx = np.argmin(error)
beam.dbl_b1_q[0] = dbl_b1_scaled[best_idx]
beam.dbl_b2_q[0] = dbl_b2_scaled[best_idx]
beam.nbl_b2_q[0] = nbl_b2_scaled[best_idx]
beam.dbl_b1_q[2] = dbl_b1_scaled[best_idx]
beam.dbl_b2_q[2] = dbl_b2_scaled[best_idx]
beam.nbl_b2_q[2] = nbl_b2_scaled[best_idx]
@staticmethod
def __closest_diameter(values):
"""Returns the largest possible diameter that is less than or equal
to each value in the input array.
Parameters
----------
values : np.ndarray
Input array of diameter values.
Returns
-------
np.ndarray
Array of closest possible diameters.
"""
indices = np.searchsorted(POSSIBLE_FI, values, side='right') - 1
indices = np.clip(indices, 0, len(POSSIBLE_FI) - 1)
return POSSIBLE_FI[indices]