Source code for simdesign.rcmrf.bcim.randomization

"""This module provides samplers for randomization in the building
portfolio generation process.
"""
# Imports from installed packages
import numpy as np
from typing import List, Tuple, Literal

# Imports from utils library
from ...utils.stats import (
    random_truncated_lognormal,
    random_multivariate_truncated_lognormal,
)
from ...utils.misc import PRECISION


[docs] class Sampler: """Sampler for regular moment resisting frame structures. Attributes ---------- sample_size : int Number of samples to be generated. staircase_span_length_x : np.ndarray Staircase span length in X direction. typical_span_length_x : np.ndarray Typical span length in X direction. typical_span_length_y : np.ndarray Typical span length in Y direction. layout : np.ndarray Building floor layouts. typical_storey_height : np.ndarray Typical storey heights. ground_storey_height : np.ndarray Ground storey heights. slab_type : np.ndarray Slab typology. 1: Two-way solid slab. 2: One-way solid slab. 3: One-way composite slab with ceramic blocks and RC joists or pre-stressed beams. beam_type : np.ndarray Beam typology. 1: Wide beams. 2: Emergent beams. column_section : np.ndarray Column cross-section. 1: Square solid section. 2: Rectangular solid section. steel_grade : np.ndarray Steel material class ID, e.g., 'S400'. concrete_grade : np.ndarray Concrete material class ID, e.g., 'C20/25'. quality : np.ndarray Construction quality. 1: High quality. 2: Moderate quality. 3: Low quality. ext_infill_type : np.ndarray Exterior masonry infill typology in terms of strength. 1: Weak. 2: Medium. 3: Strong. int_infill_type : np.ndarray Interior masonry infill typology in terms of strength. 1: Weak. 2: Medium. 3: Strong. infill_conf : np.ndarray Masonry infill wall configuration IDs. 1: Exterior only, Regular over the height, XX + YY. 2: Exterior only, Pilotis, XX + YY. 3: Exterior only, Pilotis, XX. 4: Exterior only, Pilotis, YY. 5: Exterior + Interior, Regular over the height, XX + YY. 6: Exterior + Interior, Pilotis, XX + YY. 7: Exterior + Interior, Pilotis, XX. 8: Exterior + Interior, Pilotis, YY. 9: Interior only, Regular over the height, XX + YY. 10: Interior only, Pilotis, XX + YY. 11: Interior only, Pilotis, XX. 12: Interior only, Pilotis, YY. """ sample_size: int staircase_span_length_x: np.ndarray typical_span_length_x: np.ndarray typical_span_length_y: np.ndarray layout: np.ndarray typical_storey_height: np.ndarray ground_storey_height: np.ndarray slab_type: np.ndarray beam_type: np.ndarray column_section: np.ndarray steel_grade: np.ndarray concrete_grade: np.ndarray quality: np.ndarray ext_infill_type: np.ndarray int_infill_type: np.ndarray infill_conf: np.ndarray def __init__(self, num_sample: int, seed: int | None = None) -> None: """Initialize the sampler for regular moment resisting frame structures. Parameters ---------- num_sample : int Number of samples to be generated. seed : int | None Seed number considered in random number generators, by default None. Examples -------- >>> num_sample = 150 >>> seed = 1993 """ # Set the sample size self.sample_size = num_sample # Set the seed for random number generator if seed: np.random.seed(seed)
[docs] def set_stair_span_length_x(self, lower_bound: float, upper_bound: float ) -> List[float]: """Set staircase span lengths. Staircase span lengths are represented by uniform distribution. Parameters ---------- lower_bound : float Lower boundary of the output interval. All values generated will be greater than or equal to lower bound. upper_bound : float Upper boundary of the output interval. All values generated will be less than or equal to upper bound. Returns ------- List[float] Staircase span lengths. Examples -------- >>> lower_bound = 2.8 >>> upper_bound = 3.2 """ sample = np.random.uniform(lower_bound, upper_bound, self.sample_size) staircase_span_length_x = 0.05 * np.round(20 * sample) # Set precision staircase_span_length_x = np.round(staircase_span_length_x, PRECISION) self.staircase_span_length_x = staircase_span_length_x # Return return staircase_span_length_x.tolist()
[docs] def set_typical_span_length( self, corr_coeff: float, lower_bound: Tuple[float], upper_bound: Tuple[float], theta: Tuple[float], std_ln: Tuple[float], ) -> Tuple[List[float]]: """Set typical span lengths in x and Y directions. Typical span lengths are represented with truncated log-normal distribution. It is assumed that span lengths are correlated. The samples are generated from multivariate truncated lognormal distribution. Parameters ---------- corr_coeff : float Correlation coefficient (between span lengths x and y). lower_bound : Tuple[float] Lower bound for truncated log-normal distribution (x, y). upper_bound : Tuple[float] Upper bound for truncated log-normal distribution (x, y). theta : Tuple[float] Median of log-normal distribution. std_ln : Tuple[float] Logarithmic standard deviation of the log-normal distribution (x, y). Notes ----- ~LN(ln(theta), sigma) Returns ------- Tuple[List[float]] Span lengths (x, y). Examples -------- >>> corr_coeff = -0.92 >>> lower_bound = (3.5, 3.5) >>> upper_bound = (7.5, 6.0) >>> theta = (4.5, 4.5) >>> std_ln = (0.35, 0.35) """ # Correlation Matrix rho = np.array([ [1.0, corr_coeff], [corr_coeff, 1.0] ]) # Generate samples from multivarite truncated lognormal distribution samples = random_multivariate_truncated_lognormal( self.sample_size, rho, np.array(lower_bound), np.array(upper_bound), np.array(theta), np.array(std_ln) ) # Rounding typical_span_length_x = 0.05 * np.round(20 * samples[:, 0]) typical_span_length_y = 0.05 * np.round(20 * samples[:, 1]) # Set precision typical_span_length_x = np.round(typical_span_length_x, PRECISION) typical_span_length_y = np.round(typical_span_length_y, PRECISION) # Store self.typical_span_length_x = typical_span_length_x self.typical_span_length_y = typical_span_length_y return typical_span_length_x.tolist(), typical_span_length_y.tolist()
[docs] def set_layout(self, layouts: List[str]) -> List[str]: """Set plan layout IDs for buildings. Parameters ---------- layouts : List[str] Building layouts. Returns ------- List[str] Random building layout assignments. Examples -------- >>> layouts = ["B01", "B02", "B03", "B04", "B04b", ... "B05", "B06", "B07", "B08", "B09", "B10"] """ sample = np.random.choice(layouts, size=self.sample_size) # Store self.layout = sample # Return return sample.tolist()
[docs] def set_typical_storey_height( self, cv: float, mu: float, lower_bound: float, upper_bound: float ) -> List[float]: """Set typical storey heights. Typical storey heights are represented with truncated log-normal distribution. Parameters ---------- cv : float Coefficient of variation, s/mu. mu : float Mean of normal distribution. lower_bound : float Lower bound of truncated log-normal distribution. upper_bound : float Upper bound of truncated log-normal distribution. Returns ------- List[float] Generated typical storey heights. Examples -------- >>> cv = 0.07 >>> mu = 2.90 >>> lower_bound = 2.3 >>> upper_bound = 3.8 """ std_ln = (np.log(1 + cv ** 2)) ** 0.5 mu_ln = np.log(mu) - 0.5 * std_ln ** 2 # Realisations typical_storey_heights = random_truncated_lognormal( self.sample_size, mu_ln, std_ln, lower_bound, upper_bound) # Rounding typical_storey_height = (.05 * np.round(20 * typical_storey_heights)) # Set precision typical_storey_height = np.round(typical_storey_height, PRECISION) # Store self.typical_storey_height = typical_storey_height return typical_storey_height.tolist()
[docs] def set_ground_storey_height( self, factors: List[float], probabilities: List[float], max_height: float ) -> List[float]: """Set ground storey heights. Parameters ---------- factors : List[float] Factors applied on typical storey heights to obtain ground storey heights. probabilities : List[float] Corresponding probabilities of factors. max_height : float Maximum considered ground storey height. Returns ------- List[float] Randomised ground storey heights. Notes ----- 1. `ground_storey_height = min(factor * typical_storey_heights, max_height)` 2. Factors have different probabilities. Examples -------- >>> factors = [1.0, 1.1, 1.2, 1.3, 1.4] >>> probability = [0.55, 0.10, 0.20, 0.10, 0.05] >>> max_height = 4.20 """ # Generate sample for factors factors = np.random.choice( factors, size=self.sample_size, p=probabilities) # Set ground storey heights as factored storey heights ground_storey_height = 0.05 * np.round( 20 * self.typical_storey_height * factors) # Check against the maximum ground_storey_height = np.minimum(ground_storey_height, max_height) # Set precision ground_storey_height = np.round(ground_storey_height, PRECISION) # Store self.ground_storey_height = ground_storey_height # Return return ground_storey_height.tolist()
[docs] def set_slab_type( self, ss1_prob_given_ss1_or_hs: float, ss2_prob_given_ss2_or_hs: float, max_ss_short_span: float, max_ss2_aspect_ratio: float ) -> List[Literal[1, 2, 3]]: """Set slab typology following a span length based decision tree. Parameters ---------- ss1_prob_given_ss1_or_hs : float Probability of having SS1 type slab given that the slab type is either SS1 or HS. ss2_prob_given_ss2_or_hs : float Probability of having SS2 type slab given that the slab type is either SS2 or HS. max_ss_short_span : float Upper limit for the shorter span length in solid slabs (SS1, SS2). max_ss2_aspect_ratio : float Upper limit for the ratio of maximum to minimum span lengths (aspect ratio) in SS2 slabs. Returns ------- List[Literal[1, 2, 3]] Slab typologies. Notes ----- 1. Slab typologies: Solid two-way cast-in-situ slabs (1: SS2), Solid one-way cast-in-situ slabs (2: SS1), Composite slabs with pre-fabricated joists and ceramic blocks (3: HS). 2. Slab type is assigned as HS, if minimum span length is greater than the upper limit for minimum solid slab span length (minimum span length <= `max_ss_short_span`). 3. Otherwise, if the ratio of maximum to minimum span lengths is greater or equal to `max_ss2_aspect_ratio`, slab type is randomly as SS1 or HS based on `ss1_prob_given_ss1_or_hs`. 4. Otherwise, slab type is determined randomly as SS2 or HS based on `ss2_prob_given_ss2_or_hs`. Examples -------- >>> ss1_prob_given_ss1_or_hs = 0.50 >>> ss2_prob_given_ss2_or_hs = 0.65 >>> max_ss_short_span = 6.0 # in meters >>> max_ss2_aspect_ratio = 2.0 """ # Set the storage array slab_type = np.zeros(self.sample_size, dtype='int') # Parameters for decision tree max_span_length = np.maximum(self.typical_span_length_x, self.typical_span_length_y) min_span_length = np.minimum(self.typical_span_length_x, self.typical_span_length_y) max_span_ratio = max_span_length / min_span_length # Parameters for random sampling from discrete probability distribution ids_ss1_hs = [2, 3] probs_ss1_hs = [ss1_prob_given_ss1_or_hs, 1 - ss1_prob_given_ss1_or_hs] ids_ss2_hs = [1, 3] probs_ss2_hs = [ss2_prob_given_ss2_or_hs, 1 - ss2_prob_given_ss2_or_hs] # Decision Tree for i in range(self.sample_size): if min_span_length[i] > max_ss_short_span: slab_type[i] = 3 else: if max_span_ratio[i] >= max_ss2_aspect_ratio: slab_type[i] = np.random.choice(ids_ss1_hs, p=probs_ss1_hs) else: slab_type[i] = np.random.choice(ids_ss2_hs, p=probs_ss2_hs) # Store the typologies self.slab_type = slab_type # Return return slab_type.tolist()
[docs] def set_beam_type(self, wb_prob_given_hs: float) -> List[Literal[1, 2]]: """Set beam types. Parameters ---------- wb_prob_given_hs : float Probability of having wide beams (WB) given slab type is HS. Returns ------- List[Literal[1, 2]] Beam typologies. Notes ----- 1. If slabs are solid slabs, beams are set as emergent beams (2). 2. Otherwise, randomly assign wide (1) or emergent beam (2) based on the user-defined `wb_prob_given_hs` parameter. Examples -------- >>> wb_prob_given_hs = 0.50 """ # Set the storage array beam_type = np.zeros(self.sample_size, dtype='int') # Solid slab cases (can only be emergent) mask = self.slab_type != 3 # Composite slab cases (can be either wide or emergent) beam_types = [1, 2] probs = [wb_prob_given_hs, 1.0 - wb_prob_given_hs] beam_type = np.random.choice( beam_types, size=self.sample_size, p=probs) beam_type[mask] = 2 # Store self.beam_type = beam_type # Return return beam_type.tolist()
[docs] def set_column_type(self, square_prob: float) -> List[Literal[1, 2]]: """Set the type of column or cross-section geometry. Parameters ---------- square_prob : float Probability of having square columns. Returns ------- List[Literal[1, 2]] Randomly assigned column types for each building, square (1) or rectangular (2). Examples -------- >>> square_prob = 0.50 """ probability = [square_prob, 1.0 - square_prob] sections = [1, 2] column_section = np.random.choice(sections, size=self.sample_size, p=probability) # Store self.column_section = column_section # Return return column_section.tolist()
[docs] def set_material_class( self, material_class: List[str], probability: List[float], material: Literal["steel", "concrete"] ) -> List[str]: """Randomly assign material classes to buildings given their occurrence probabilities. Parameters ---------- material_class : List[str] List of defined material classes. probability : List[float] Probabilities of having different material classes. Sum should be equal to 1.0. material : Literal["steel", "concrete"] Type of material, which can be either "steel" or "concrete". Returns ------- List[str] Randomly assigned material classes for building. Examples -------- >>> material_class = ["S240", "S400"] >>> probabilities = [0.60, 0.40] """ sample = np.random.choice( material_class, size=self.sample_size, p=probability) if material == "steel": self.steel_grade = sample elif material == "concrete": self.concrete_grade = sample # Return return sample.tolist()
[docs] def set_construction_quality(self, quality_ids: List[int], probabilities: List[float]) -> List[int]: """Set construction quality in a randomised fashion. Parameters ---------- quality_ids : List[int] Quality identifiers, IDs. probabilities : List[float] Corresponding probabilities for construction qualities. Sum should be equal to 1.0. Returns ------- List[int] Construction quality sample. Examples -------- >>> quality_ids = [1, 2, 3] >>> probabilities = [0.6, 0.3, 0.1] """ quality = np.random.choice( quality_ids, size=self.sample_size, p=probabilities) # Store self.quality = quality # Return return quality.tolist()
[docs] def set_ext_infill_type(self, infill_types: List[int], probabilities: List[float]) -> List[int]: """Set masonry exterior infill wall typologies in a randomised fashion. Parameters ---------- infill_types : List[int] Infill typology IDs. probabilities : List[float] Corresponding probabilities for infill typologies. Sum should be equal to 1.0. Returns ------- List[int] Exterior masonry infill typology sample. Examples -------- >>> infill_types = [1, 2, 3] >>> probabilities = [0.6, 0.3, 0.1] """ infill = np.random.choice( infill_types, size=self.sample_size, p=probabilities) # Store self.ext_infill_type = infill # Return return infill.tolist()
[docs] def set_int_infill_type(self, infill_types: List[int], probabilities: List[float]) -> List[int]: """Set masonry interior infill wall typologies in a randomised fashion. Parameters ---------- infill_types : List[int] Infill typology IDs. probabilities : List[float] Corresponding probabilities for infill typologies. Sum should be equal to 1.0. Returns ------- List[int] Interior masonry infill typology sample. Notes ----- It is assumed that interior infills should be weaker than exterior ones, if they co-exist. Examples -------- >>> infill_types = [1, 2, 3] >>> probabilities = [0.33, 0.34, 0.33] """ # Location settings with both exterior and interior infills MAPPER = [5, 6, 7, 8] # exterior + interior idx = np.where(np.isin(self.infill_conf, MAPPER))[0] # Sample interior infill types infill = np.random.choice( infill_types, size=self.sample_size, p=probabilities) # Ensure that interior infills are weaker when both exist infill[idx] = np.minimum(infill[idx], self.ext_infill_type[idx] - 1) infill[idx] = np.maximum(infill[idx], 1) # Store self.int_infill_type = infill # Return return infill.tolist()
[docs] def set_infill_conf(self, infill_confs: List[int], probabilities: List[float]) -> List[int]: """Set masonry infill wall configurations in a randomised fashion. Parameters ---------- infill_confs : List[int] Infill configuration IDs. probabilities : List[float] Corresponding probabilities for infill configuration IDs. Sum should be equal to 1.0. Returns ------- List[int] Masonry infill configuration sample. Examples -------- >>> infill_confs = [1, 2, 5, 6] >>> probabilities = [0.25, 0.25, 0.25, 0.25] """ infill = np.random.choice( infill_confs, size=self.sample_size, p=probabilities) # Store self.infill_conf = infill # Return return infill.tolist()
[docs] def reset(self) -> None: """Reset all the attributes of the Sampler class instance. """ # Reset attrs = list(self.__dict__.keys()) for attr in attrs: delattr(self, attr)