Source code for simdesign.rcmrf.bnsm.dp03.column

"""This module provides the column class implementation for the ``DP03`` model
in the BNSM layer.
"""
# Imports from installed packages
import numpy as np
import openseespy.opensees as ops
from typing import Literal, List

# Imports from bnsm base library
from ..baselib.column import ColumnBase

# Imports from utils library
from ....utils.units import MPa, mm
from ....utils.misc import round_list
from ....utils.rcsection import (
    get_mander_confinement, build_column_rebar_layout
)


[docs] class Column(ColumnBase): """Column implementation for the ``DP03`` model. This class extends ``ColumnBase`` by defining fiber sections at the plastic hinge regions. The column is modeled using a force-based beam-column element with a plastic hinge integration scheme. Nonlinear behavior is distributed over specified hinge lengths at the member ends, while the interior region remains elastic. The hinge sections are discretized into concrete and steel fibers, where each fiber is associated with a uniaxial material model. The section response is obtained by integrating stresses over the fibers, enabling axial force-bending moment interaction to be captured. The interior region is modeled as elastic using cracked section properties. The shear response is represented as in the base class. Attributes ---------- concrete_material : Literal['Concrete01', 'Concrete04'], optional Uniaxial material type for concrete, by default 'Concrete04'. interior_section : Literal['Elastic', 'Inelastic'], optional Interior section used in the plastic hinge integration formulation, by default 'Inelastic'. See Also -------- :class:`~ColumnBase` Base column definition extended by this class. """ concrete_material: Literal['Concrete01', 'Concrete04'] = 'Concrete04' interior_section: Literal['Elastic', 'Inelastic'] = 'Inelastic' @property def Ecm_q(self) -> float: """ Returns ------- float Elastic young's modulus of concrete (in base units). References ---------- Priestley, M. J. N., Calvi, G. M., & Kowalsky, M. J. (2007). *Displacement-based seismic design of structures*. IUSS Press. """ # Use quality adjusted elastic youngs modulus fc_mpa = self.design.fc_q / MPa # convert to MPa return (4700 * (fc_mpa**0.5)) * MPa @property def steel_mat_tag_(self) -> int: """Steel material tag.""" return int(str(self.design.line.tag) + '990') @property def steel_mat_tag(self) -> int: """MinMax material tag for the steel fibers. Used to simulate bar buckling or rupture.""" return int(str(self.design.line.tag) + '991') @property def uconf_conc_mat_tag(self) -> int: """Material tag for the unconfined concrete fibers.""" return int(str(self.design.line.tag) + '992') @property def conf_conc_mat_tag(self) -> int: """Material tag for the confined concrete fibers.""" return int(str(self.design.line.tag) + '993') @property def inelastic_fiber_sec_tag(self) -> int: """Section tag for the inelastic fiber section.""" return int(str(self.design.line.tag) + '991')
[docs] def add_to_ops(self) -> None: """Adds column components to the OpenSees domain (i.e, column element and nodes). Notes ----- Same hinge materials are used at both ends. """ # Define geometric transformation ops.geomTransf(*self._get_geo_transf_inputs()) # Create the section materials ops.uniaxialMaterial(*self._get_steel_mat_inputs()) ops.uniaxialMaterial(*self._get_steel_minmax_mat_inputs()) ops.uniaxialMaterial(*self._get_unconfined_concrete_mat_inputs()) ops.uniaxialMaterial(*self._get_confined_concrete_mat_inputs()) if not self.capacity_design: vy_mat, minmax_vy = self._get_shear_hinge_mat_inputs('y') vz_mat, minmax_vz = self._get_shear_hinge_mat_inputs('x') ops.uniaxialMaterial(*vy_mat) ops.uniaxialMaterial(*vz_mat) ops.uniaxialMaterial(*minmax_vy) ops.uniaxialMaterial(*minmax_vz) # Create element sections section, fibers, patches = self._get_inelastic_fiber_sec_inputs() if self.interior_section == 'Elastic': # use elastic interior ops.section(*self._get_elastic_sec_inputs()) # Fiber section ops.section(*section) for fiber in fibers: ops.fiber(*fiber) for patch in patches: ops.patch(*patch) if not self.capacity_design: # Aggregated section ops.section(*self._get_inelastic_aggregated_sec_inputs()) # Create beam integration ops.beamIntegration(*self._get_int_inputs()) # Create the element ops.element(*self._get_ele_inputs())
[docs] def to_py(self) -> List[str]: """Gets the Python commands to construct column components in OpenSees domain (i.e, column element and nodes). Returns ------- List[str] List of Python commands for constructing the components of column object in OpenSees. Notes ----- Same hinge materials are used at both ends. """ # Define geometric transformation content = ['# Create geometric transformation'] transf_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_geo_transf_inputs() ) content.append(f"ops.geomTransf({transf_inputs})") # Create the section materials content.append('# Create uniaxial materials') # Fiber section materials steel = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_steel_mat_inputs() ) steel_minmax = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_steel_minmax_mat_inputs() ) unconc = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_unconfined_concrete_mat_inputs() ) conc = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_confined_concrete_mat_inputs() ) content.append(f"ops.uniaxialMaterial({steel})") content.append(f"ops.uniaxialMaterial({steel_minmax})") content.append(f"ops.uniaxialMaterial({unconc})") content.append(f"ops.uniaxialMaterial({conc})") # Uniaxial shear materials if not self.capacity_design: vy_mat, minmax_vy = self._get_shear_hinge_mat_inputs('y') vz_mat, minmax_vz = self._get_shear_hinge_mat_inputs('x') vy_mat_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in vy_mat ) vz_mat_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in vz_mat ) minmax_vy_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in minmax_vy ) minmax_vz_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in minmax_vz ) content.append(f"ops.uniaxialMaterial({vy_mat_inputs})") content.append(f"ops.uniaxialMaterial({vz_mat_inputs})") content.append(f"ops.uniaxialMaterial({minmax_vy_inputs})") content.append(f"ops.uniaxialMaterial({minmax_vz_inputs})") # Create sections content.append('# Create element sections') # Interior Section - Elastic if self.interior_section == 'Elastic': # use elastic interior elastic_sec_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_elastic_sec_inputs() ) content.append(f"ops.section({elastic_sec_inputs})") # End Sections - Fiber fb_section, fibers, patches = self._get_inelastic_fiber_sec_inputs() fb_section = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in fb_section ) content.append(f"ops.section({fb_section})") for fiber in fibers: fiber = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in fiber ) content.append(f"ops.fiber({fiber})") for patch in patches: patch = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in patch ) content.append(f"ops.patch({patch})") # End Sections - Aggregate(Fiber + Uniaxial shear) if not self.capacity_design: agg_section = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_inelastic_aggregated_sec_inputs() ) content.append(f"ops.section({agg_section})") # Create beam integration content.append('# Create integration scheme') int_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_int_inputs() ) content.append(f"ops.beamIntegration({int_inputs})") # Create column element content.append('# Create element') ele_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_ele_inputs() ) content.append(f"ops.element({ele_inputs})") return content
[docs] def to_tcl(self) -> List[str]: """Gets the Tcl commands to construct column components in OpenSees domain (i.e, column element and nodes). Returns ------- List[str] List of Tcl commands for constructing the components of column object in OpenSees. Notes ----- Same hinge materials are used at both ends. """ # Define geometric transformation content = ['# Create geometric transformation'] transf_inputs = ' '.join(f"{item}" for item in self._get_geo_transf_inputs()) content.append(f"geomTransf {transf_inputs}") # Create the section materials content.append('# Create uniaxial materials') # Fiber section materials steel = ' '.join( f"{item}" for item in self._get_steel_mat_inputs() ) steel_minmax = ' '.join( f"{item}" for item in self._get_steel_minmax_mat_inputs() ) unconc = ' '.join( f"{item}" for item in self._get_unconfined_concrete_mat_inputs() ) conc = ' '.join( f"{item}" for item in self._get_confined_concrete_mat_inputs() ) content.append(f"uniaxialMaterial {steel}") content.append(f"uniaxialMaterial {steel_minmax}") content.append(f"uniaxialMaterial {unconc}") content.append(f"uniaxialMaterial {conc}") # Uniaxial shear materials if not self.capacity_design: vy_mat, minmax_vy = self._get_shear_hinge_mat_inputs('y') vz_mat, minmax_vz = self._get_shear_hinge_mat_inputs('x') vy_mat_inputs = ' '.join(f"{item}" for item in vy_mat) vz_mat_inputs = ' '.join(f"{item}" for item in vz_mat) minmax_vy_inputs = ' '.join(f"{item}" for item in minmax_vy) minmax_vz_inputs = ' '.join(f"{item}" for item in minmax_vz) content.append(f"uniaxialMaterial {vy_mat_inputs}") content.append(f"uniaxialMaterial {vz_mat_inputs}") content.append(f"uniaxialMaterial {minmax_vy_inputs}") content.append(f"uniaxialMaterial {minmax_vz_inputs}") # Create sections content.append('# Create element sections') # Interior Section - Elastic if self.interior_section == 'Elastic': # use elastic interior elastic_sec_inputs = ' '.join( f"{item}" for item in self._get_elastic_sec_inputs()) content.append(f"section {elastic_sec_inputs}") # End Sections - Fiber fb_section, fibers, patches = self._get_inelastic_fiber_sec_inputs() fb_section = ' '.join(f"{item}" for item in fb_section) content.append(f"section {fb_section} " + "{") for fiber in fibers: fiber = ' '.join(f"{item}" for item in fiber) content.append(f" fiber {fiber}") for patch in patches: patch = ' '.join(f"{item}" for item in patch) content.append(f" patch {patch}") content.append("}") # End Sections - Aggregate(Fiber + Uniaxial shear) if not self.capacity_design: agg_section = ' '.join( f"{item}" for item in self._get_inelastic_aggregated_sec_inputs() ) content.append(f"section {agg_section}") # Create beam integration content.append('# Create integration scheme') int_inputs = ' '.join( f"{item}" for item in self._get_int_inputs()) content.append(f"beamIntegration {int_inputs}") # Create column element content.append('# Create element') ele_inputs = ' '.join( f"{item}" for item in self._get_ele_inputs()) content.append(f"element {ele_inputs}") return content
def _get_inelastic_fiber_sec_inputs(self) -> List[str | float | int]: """Retrieves inputs for inelastic fiber column section. Returns ------- List[str | float | int] List of inputs for inelastic fiber column section. """ # Section and material tags sec_tag = self.inelastic_fiber_sec_tag conf_con = self.conf_conc_mat_tag uconf_con = self.uconf_conc_mat_tag steel = self.steel_mat_tag # Section geometry lx = self.design.bx ly = self.design.by cv = self.design.cover_q # Shear reinforcement dbh = self.design.dbh_q # Longitudinal reinforcement nbl_int_x = int(self.design.nblx_int_q) nbl_int_y = int(self.design.nbly_int_q) dbl_cor = self.design.dbl_cor_q dbl_int = self.design.dbl_int_q # Get reinforcement layout layout = build_column_rebar_layout( dbl_cor, dbl_int, nbl_int_x, nbl_int_y, dbh, lx, ly, cv ) # Steel fibers steel_fibers = [] for x, y, d in zip(layout['rein_x'], layout['rein_y'], layout['rein_d']): area = 0.25 * np.pi * d**2 yLoc = y - ly/2 # Origin is the section center zLoc = x - lx/2 # Origin is the section center inputs = [float(yLoc), float(zLoc), float(area), steel] steel_fibers.append(inputs) # Concrete patches cover_y = float(ly / 2) # The distance from y-axis to the cover edge cover_z = float(lx / 2) # The distance from z-axis to the cover edge core_y = float(ly / 2 - cv) # The dist. from y-axis to the core edge core_z = float(lx / 2 - cv) # The dist. from z-axis to the core edge nf_core_y = 8 # num. of fibers for concrete in y-direction - core nf_core_z = 8 # num. of fibers for concrete in z-direction - core nf_cover_y = 10 # num.r of fibers for concrete in y-direction - cover nf_cover_z = 10 # num. of fibers for concrete in z-direction - cover # Uncofined concrete patch_bottom = ['rect', uconf_con, 1, nf_cover_z, -cover_y, -cover_z, -core_y, cover_z] patch_right = ['rect', uconf_con, nf_cover_y, 1, -core_y, core_z, core_y, cover_z] patch_top = ['rect', uconf_con, 1, nf_cover_z, core_y, -cover_z, cover_y, cover_z] patch_left = ['rect', uconf_con, nf_cover_y, 1, -core_y, -cover_z, core_y, -core_z] # Confined concrete core_patch = ['rect', conf_con, nf_core_y, nf_core_z, -core_y, -core_z, core_y, core_z] concrete_patches = [ core_patch, patch_bottom, patch_right, patch_top, patch_left ] # Fiber section GJ = self.Gcm_q * self.design.J section = ['Fiber', sec_tag, '-GJ', GJ] # Rounding section = round_list(section) concrete_patches = round_list(concrete_patches) steel_fibers = round_list(steel_fibers) return section, steel_fibers, concrete_patches def _get_inelastic_aggregated_sec_inputs(self) -> List[str | float | int]: """Retrieves inputs for inelastic aggregated column section. Returns ------- List[str | float | int] List of inputs for inelastic aggregated column section. """ sec_inputs = [ 'Aggregator', self.inelastic_sec_tag, self.vy_mat_tag, 'Vy', self.vz_mat_tag, 'Vz', '-section', self.inelastic_fiber_sec_tag ] return sec_inputs def _get_int_inputs(self) -> List[str | float | int]: """Retrieves column integration inputs. Returns ------- List[str | float | int] List of column integration inputs. """ # Inputs for aggregated section with elastic interior int_inputs = super()._get_int_inputs() if self.capacity_design: # Use fiber section int_inputs[2], int_inputs[4] = (self.inelastic_fiber_sec_tag,) * 2 if self.interior_section == 'Inelastic': # use inelastic interior int_inputs[-1] = self.inelastic_fiber_sec_tag elif self.interior_section == 'Inelastic': # use inelastic interior int_inputs[-1] = self.inelastic_sec_tag return int_inputs def _get_confined_concrete_mat_inputs(self) -> List[str | float | int]: """Builds OpenSees confined concrete material inputs for the end sections. Returns ------- List[str | float | int] Arguments for ``ops.uniaxialMaterial`` defining a confined concrete material ('Concrete01' or 'Concrete04') in OpenSees. See Also -------- ``get_mander_confinement``: Computes confined peak stress and strains using the Mander model. ``build_column_rebar_layout``: Sets rebar coordinates and diameters for the column section. Notes ----- - Concrete confinement parameters are computed using the Mander model. - For Concrete01, residual (crushing) stress is taken as ``fpcu = 0.2 * fpc``. """ # Materials [MPa] fc = self.design.fc_q / MPa fsyh = self.design.fsyh_q / MPa # Section geometry [mm] lx = self.design.bx / mm ly = self.design.by / mm cv = self.design.cover_q / mm # Shear reinforcement [mm] dbh = self.design.dbh_q / mm sbh = self.design.sbh_q / mm legs_x = int(self.design.nbh_x_q) legs_y = int(self.design.nbh_y_q) # Longitudinal reinforcement [mm] nbl_int_x = int(self.design.nblx_int_q) nbl_int_y = int(self.design.nbly_int_q) dbl_cor = self.design.dbl_cor_q / mm dbl_int = self.design.dbl_int_q / mm # Get reinforcement layout layout = build_column_rebar_layout( dbl_cor, dbl_int, nbl_int_x, nbl_int_y, dbh, lx, ly, cv ) # Determine confinement based on Mander model params = get_mander_confinement( fc=fc, Lx=lx, Ly=ly, cover=cv, db_v=dbh, s=sbh, legs_x=legs_x, legs_y=legs_y, fy_v=fsyh, rein_x=layout['rein_x'], rein_y=layout['rein_y'], rein_d=layout['rein_d'] ) # note that strength parameters are in MPa # Set concrete01 material parameters if self.concrete_material == 'Concrete01': fpc = -params['fcc'] * MPa # concrete compressive strength epsc0 = -params['eps_cc'] # concrete strain at comp. strength fpcu = 0.2*fpc # concrete crushing strength epsU = -params['eps_cu'] # concrete strain at crushing strength mat_inputs = [fpc, epsc0, fpcu, epsU] elif self.concrete_material == 'Concrete04': fpc = -params['fcc'] * MPa # concrete compressive strength epsc = -params['eps_cc'] # concrete strain at compressive strength epscu = -params['eps_cu'] # concrete strain at crushing strength Ec = self.Ecm_q # elastic modulus mat_inputs = [fpc, epsc, epscu, Ec] # Rounding mat_tags = [self.concrete_material, self.conf_conc_mat_tag] mat_inputs = round_list(mat_tags + mat_inputs) return mat_inputs def _get_unconfined_concrete_mat_inputs(self) -> List[str | float | int]: """Builds cover (unconfined) concrete material inputs for OpenSees. Returns ------- List[str | float | int] Arguments for ``ops.uniaxialMaterial`` defining an unconfined concrete material ('Concrete01' or 'Concrete04') in OpenSees. Notes ----- - Peak strain is taken as ``epsc0 = -0.002``. - Crushing strain is taken as ``epsU = -0.006``. - For Concrete01, residual (crushing) stress is taken as ``fpcu = 0.2 * fpc``. """ # Set concrete01 material parameters if self.concrete_material == 'Concrete01': fpc = -self.design.fc_q # concrete compressive strength epsc0 = -0.002 # concrete strain at compressive strength fpcu = 0.2*fpc # concrete crushing strength epsU = -0.006 # concrete strain at crushing strength mat_inputs = [fpc, epsc0, fpcu, epsU] elif self.concrete_material == 'Concrete04': fpc = -self.design.fc_q # concrete compressive strength epsc = -0.002 # concrete strain at compressive strength epscu = -0.006 # concrete strain at crushing strength Ec = self.Ecm_q # elastic modulus mat_inputs = [fpc, epsc, epscu, Ec] # Rounding mat_tags = [self.concrete_material, self.uconf_conc_mat_tag] mat_inputs = round_list(mat_tags + mat_inputs) return mat_inputs def _get_steel_mat_inputs(self) -> List[str | float]: """Builds OpenSees reinforcing steel material inputs. Returns ------- List[str | float | int] Arguments for ``ops.uniaxialMaterial`` defining a ``Steel01`` material in OpenSees Notes ----- - The strain-hardening ratio is assumed to be 0.005. """ b = 0.005 # strain-hardening ratio - assumed Fy = self.design.fsyl_q # yield strength E0 = self.design.Es # initial elastic tangent mat_inputs = ['Steel01', self.steel_mat_tag_, Fy, E0, b] mat_inputs = round_list(mat_inputs) return mat_inputs def _get_steel_minmax_mat_inputs(self) -> List[str | float | int]: """Builds OpenSees MinMax wrapper material inputs for reinforcing steel. Wraps the underlying steel material to cap strains in tension/compression, enabling a simple representation of bar rupture or buckling limits. Returns ------- List[str | float | int] Arguments for ``ops.uniaxialMaterial`` defining a ``MinMax`` material in OpenSees. Notes ----- - The same absolute strain limit is used in tension and compression. - Here ``epsU = 0.08`` is used per Priestley et al. 2007. """ epsU = 0.08 # bar buckling or bar rupture mat_inputs = ['MinMax', self.steel_mat_tag, self.steel_mat_tag_, '-min', -epsU, '-max', epsU] mat_inputs = round_list(mat_inputs) return mat_inputs