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

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

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

# Imports from utils library
from ....utils import rcsection as rc
from ....utils.misc import round_list, PRECISION
from ....utils.tags import sub_tag

STIFF_INTERIOR_FACTOR = 1e3
"""Factor on the moments of inertia of the elastic interior sections of the
``CP01`` and ``CP02`` members. The springs then carry the whole flexural
flexibility of the member, as calibrated."""


[docs] class Column(ColumnBase): """Column implementation for the ``CP01`` model. The column is modeled using a concentrated plasticity approach. Plastic rotations are concentrated at the element ends via ``beamIntegration('ConcentratedPlasticity', ...)`` (end hinge integration points with an elastic interior region), while the element formulation is provided by ``ColumnBase``. End-hinge flexural behaviour about the local z-axis (Mz) is defined using a uniaxial ``Hysteretic`` material, expressed in terms of bending moment vs. chord rotation of the whole member (the yield rotation includes the elastic deformations). The elastic interior is therefore nearly rigid in flexure (``STIFF_INTERIOR_FACTOR``), and the springs carry the whole flexural flexibility. If there is no capacity design, degrading shear springs are defined using the uniaxial ``LimitState`` material coupled with a ``ThreePoint`` ``limitCurve`` (drift-based), following the strength and post-peak degradation formulations adopted from Elwood & Moehle (2003) and Sezen & Moehle (2004). See Also -------- :class:`~simdesign.rcmrf.bnsm.baselib.column.ColumnBase` Base column definition extended by this class. """ @property def p_mat_tag(self) -> int: """Elastic material tag for the axial response (P) of the hinges.""" return sub_tag(self.design.line.tag, 98) @property def t_mat_tag(self) -> int: """Elastic material tag for the torsional response (T) of the hinges.""" return sub_tag(self.design.line.tag, 99)
[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_rot_hinge_mat_inputs('x')) ops.uniaxialMaterial(*self._get_rot_hinge_mat_inputs('y')) for mat_inputs in self._get_hinge_elastic_mat_inputs(): ops.uniaxialMaterial(*mat_inputs) if not self.capacity_design: vy_mat, vy_curve = self._get_shear_hinge_mat_inputs('y') vz_mat, vz_curve = self._get_shear_hinge_mat_inputs('x') ops.limitCurve(*vy_curve) ops.uniaxialMaterial(*vy_mat) ops.limitCurve(*vz_curve) ops.uniaxialMaterial(*vz_mat) # Create element sections ops.section(*self._get_elastic_sec_inputs()) ops.section(*self._get_hinge_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') mz_mat_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_rot_hinge_mat_inputs('x') ) my_mat_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_rot_hinge_mat_inputs('y') ) content.append(f"ops.uniaxialMaterial({mz_mat_inputs})") content.append(f"ops.uniaxialMaterial({my_mat_inputs})") for mat_inputs in self._get_hinge_elastic_mat_inputs(): mat_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in mat_inputs ) content.append(f"ops.uniaxialMaterial({mat_inputs})") if not self.capacity_design: vy_mat, vy_curve = self._get_shear_hinge_mat_inputs('y') vz_mat, vz_curve = 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 ) vy_curve_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in vy_curve ) vz_curve_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in vz_curve ) content.append(f"ops.limitCurve({vy_curve_inputs})") content.append(f"ops.uniaxialMaterial({vy_mat_inputs})") content.append(f"ops.limitCurve({vz_curve_inputs})") content.append(f"ops.uniaxialMaterial({vz_mat_inputs})") # Create sections content.append('# Create element sections') elastic_sec_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_elastic_sec_inputs() ) inelastic_sec_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_hinge_sec_inputs() ) content.append(f"ops.section({elastic_sec_inputs})") content.append(f"ops.section({inelastic_sec_inputs})") # 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') mz_mat_inputs = ' '.join( f"{item}" for item in self._get_rot_hinge_mat_inputs('x')) my_mat_inputs = ' '.join( f"{item}" for item in self._get_rot_hinge_mat_inputs('y')) content.append(f"uniaxialMaterial {mz_mat_inputs}") content.append(f"uniaxialMaterial {my_mat_inputs}") for mat_inputs in self._get_hinge_elastic_mat_inputs(): mat_inputs = ' '.join(f"{item}" for item in mat_inputs) content.append(f"uniaxialMaterial {mat_inputs}") if not self.capacity_design: vy_mat, vy_curve = self._get_shear_hinge_mat_inputs('y') vz_mat, vz_curve = 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) vy_curve_inputs = ' '.join(f"{item}" for item in vy_curve) vz_curve_inputs = ' '.join(f"{item}" for item in vz_curve) content.append(f"limitCurve {vy_curve_inputs}") content.append(f"uniaxialMaterial {vy_mat_inputs}") content.append(f"limitCurve {vz_curve_inputs}") content.append(f"uniaxialMaterial {vz_mat_inputs}") # Create sections content.append('# Create element sections') elastic_sec_inputs = ' '.join( f"{item}" for item in self._get_elastic_sec_inputs()) inelastic_sec_inputs = ' '.join( f"{item}" for item in self._get_hinge_sec_inputs()) content.append(f"section {elastic_sec_inputs}") content.append(f"section {inelastic_sec_inputs}") # 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_hinge_elastic_mat_inputs( self ) -> List[List[str | float | int]]: """Retrieves the elastic material inputs of the hinge sections. Returns ------- List[List[str | float | int]] Inputs of the elastic axial (EA) and torsional (GJ) materials. Notes ----- ``ConcentratedPlasticity`` gives each hinge section a weight of one length unit and removes the same length from the elastic interior. The hinge sections therefore carry the elastic axial and torsional flexibility of that length. With rigid materials, the axial stiffness becomes EA/(L-2), which is negative for clear lengths below 2 m. """ return [ round_list(['Elastic', self.p_mat_tag, self.Ecm_q * self.design.Ag]), round_list(['Elastic', self.t_mat_tag, self.Gcm_q * self.design.J]) ] def _get_hinge_sec_inputs(self) -> List[str | int]: """Retrieves inputs for the aggregated hinge section. Returns ------- List[str | int] List of inputs for the aggregated hinge section, with elastic axial (P) and torsional (T) materials. """ sec_inputs = self._get_inelastic_sec_inputs() sec_inputs[2], sec_inputs[12] = self.p_mat_tag, self.t_mat_tag return sec_inputs def _get_elastic_sec_inputs(self) -> List[str | float | int]: """Retrieves elastic column section inputs. Returns ------- List[str | float | int] List of elastic column section inputs. Notes ----- The moments of inertia are multiplied by ``STIFF_INTERIOR_FACTOR``. The backbone rotations are chord rotations of the whole member, so the springs carry the whole flexural flexibility and the interior is nearly rigid in flexure. ``ConcentratedPlasticity`` removes one length unit of the interior flexibility at each end, which then is negligible. """ sec_inputs = super()._get_elastic_sec_inputs() sec_inputs[4] = round(sec_inputs[4] * STIFF_INTERIOR_FACTOR, PRECISION) sec_inputs[5] = round(sec_inputs[5] * STIFF_INTERIOR_FACTOR, PRECISION) return sec_inputs def _get_int_inputs(self) -> List[str | float]: """Retrieves column integration inputs. Returns ------- List[float] List of column integration inputs. """ int_inputs = [ 'ConcentratedPlasticity', self.int_tag, self.inelastic_sec_tag, self.inelastic_sec_tag, self.elastic_sec_tag ] return int_inputs def _get_rot_hinge_mat_inputs(self, axis: Literal['x', 'y'] ) -> List[int | float | str]: """Gets the plastic hinge material inputs for given axis. Parameters ---------- axis : Literal['x', 'y'] The local axis considered for the calculations. Returns ------- rot_mat_inputs : List[int | float | str] Hysteretic material model inputs for the plastic hinge describing behaviour in flexure around `axis`. """ if axis == 'x': # The integer tag for the material describing flexure behaviour # around local -x (corresponds to z in ops) flex_mat_tag = self.mz_mat_tag elif axis == 'y': # The integer tag for the material describing flexure behaviour # around local -y axis (corresponds to y in ops) flex_mat_tag = self.my_mat_tag # Plastic hinge properties ( _, My, Mc, Mr, theta_y, theta_cap_pl, theta_pc ) = self._get_rot_hinge_props(axis) # The shear distortion term is excluded from the yield rotation when # the shear deformations are modelled explicitly by the shear springs # (see _get_rot_hinge_props) # Rotation values for monotonic loading theta_1 = theta_y theta_2 = theta_1 + theta_cap_pl theta_3 = theta_2 + theta_pc # Material inputs rot_mat_inputs = [ 'Hysteretic', flex_mat_tag, My, theta_1, Mc, theta_2, Mr, theta_3, -My, -theta_1, -Mc, -theta_2, -Mr, -theta_3, *HYSTERETIC_PARAMETERS ] # Rounding to precision rot_mat_inputs = round_list(rot_mat_inputs) return rot_mat_inputs def _get_yield_rotation(self, axis: Literal['x', 'y']) -> float: """Gets the yield rotation of the flexural plastic hinge. Parameters ---------- axis : Literal['x', 'y'] The local axis considered for the calculations. Returns ------- float Yield rotation of the flexural hinge around `axis`, used also to define the drift limits of the shear failure curve. """ return self._get_rot_hinge_props(axis)[4] def _get_shear_hinge_mat_inputs( self, axis: Literal['x', 'y'], ) -> Tuple[List[int | float | str], List[int | float | str]]: """Gets inputs the shear plastic hinge materials for given axis. Parameters ---------- axis : Literal['x', 'y'] The local axis considered for the calculations. Returns ------- mat_inputs : List[int | float | str] Inputs for the limit state material for describing the shear behaviour in `axis`. limit_curve_inputs : List[int | float | str] Inputs for limit curve used by the limit state material describing the shear behaviour in `axis`. References ---------- CEN (2005) Eurocode 8: Design of structures for earthquake resistance - Part 3: Assessment and retrofitting of existing buildings. Brussels, Belgium ASCE/SEI 41-17. (2017). Seismic rehabilitation of existing buildings. American Society of Civil Engineers. LeBorgne, M. R., & Ghannoum, W. M. (2014). Calibrated analytical element for lateral-strength degradation of reinforced concrete columns. Engineering Structures, 81, 35-48. Sezen, H. and Moehle, J.P. (2004). Shear Strength Model for Lightly Reinforced Concrete Columns. J Struct Eng 130:1692-1703. https://doi.org/10.1061/(asce)0733-9445(2004)130:11(1692) Elwood K. J., & Moehle J. P. (2003). Shake table tests and analytical studies on the gravity load collapse of reinforced concrete frames. PEER report 2003/01. Pacific Earthquake Engineering Research Center, College of Engineering, University of California, Berkeley. """ if axis == 'y': # theta_y is about local-x (-z in ops) theta_y = self._get_yield_rotation('x') # Section height h = self.design.by # along y # Number of horizontal bars (stirrup legs) nbh = self.design.nbh_y_q # The integer tag for the Limit Curve defining the limit surface limit_curve_tag = self.vy_mat_tag # The integer tag for the material describing shear behaviour in # local -y (corresponds to y in ops) shear_mat_tag = self.vy_mat_tag elif axis == 'x': # theta_y is about local-y theta_y = self._get_yield_rotation('y') # Section height h = self.design.bx # along x # Number of horizontal bars (stirrup legs) nbh = self.design.nbh_x_q # The integer tag for the Limit Curve defining the limit surface limit_curve_tag = self.vz_mat_tag # The integer tag for the material describing shear behaviour in # local -x (corresponds to z in ops) shear_mat_tag = self.vz_mat_tag # Horizontal (stirrup) reinforcement diameter dbh = self.design.dbh_q # Clear length of column, without the rigid joint offsets Ln = self.clear_length # Stirrup spacing sbh = self.design.sbh_q # Shear span, assuming equal to 50% of the free length of the element Ls = Ln / 2 # NOTE: Could be varied with intensity of loading, but ok. # Transverse reinforcement yield strength (quality adjusted) fsyh = self.design.fsyh_q # in base units # Gross corss-section area Ag = self.design.Ag # Compressive axial load (+) - Positive in compression Nu = max(-self.axial_force, 0) # Transverse reinforcement area Av = nbh * np.pi * (dbh**2) / 4 # Initial shear strength - ASCE/SEI 41-17 - Equation 10-3, with the # effective depth permitted by ASCE (0.8 h) Vn = rc.get_shear_strength_asce41_17( self.design.fc_q, Ag, 0.8 * h, Av, fsyh, sbh, Ls, Nu) # Number of shear springs in series along the member; the same hinge # section (with the shear springs) is used at both ends n_springs = 2 # Shear-spring elastic slope - LeBorgne and Ghannoum (2014) - Eqn. 1 # NOTE: The member stiffness is multiplied by the number of springs # so that springs in series give the member shear stiffness. k_el = n_springs * rc.get_shear_stiffness_leborgne_ghannoum2014( self.Gcm_q, Ag, Ln) # Depth of the column core, measured to the centerline of the # transverse reinforcement (Elwood and Moehle, 2005) dc = h - 2 * (self.design.cover_q + dbh / 2) # Axial load in the degrading slope, clipped to the range of axial # load ratios (0.15 to 0.61) for which the expression was verified P_lims = [0.15 * Ag * self.design.fc_q, 0.61 * Ag * self.design.fc_q] P_deg = float(np.clip(Nu, *P_lims)) # Degrading slope of the shear-drift spring backbone - Shoraka and # Elwood (2013) - Eqn. 20 k_deg = rc.get_shear_degrading_slope_shoraka_elwood2013( P_deg, Av, fsyh, dc, sbh, Ln) # Inputs for Three-Point Limit Curve - Elwood and Moehle 2003 # integer element tag for the associated beam-column element eleTag = self.design.line.tag # Three-Point strength degradation model by Sezen and Moehle 2004 k0, k1, k2 = 1.0, 1.0, 0.7 # Strength degradation factors mu_y0, mu_y1, mu_y2 = 0.0, 2.0, 6.0 # Displacement ductilities # The coordinates of points on the limit curve # x1, y1 = -10, Vn # the first point x1, y1 = mu_y0 * theta_y, k0 * Vn # the first point TODO: verify # x2, y2 = 0, Vn # the second point x2, y2 = mu_y1 * theta_y, k1 * Vn # the second point TODO: verify # x3, y3 = 10, Vn # the third point x3, y3 = mu_y2 * theta_y, k2 * Vn # the third point TODO: verify # Floating point value for the slope of the third branch in the # post-failure backbone, assumed to be negative # NOTE: Each spring takes the member slope (not multiplied by the # number of springs). If both springs soften, the member degrades at # half the slope of Eq. 20, which helps convergence; if one spring # softens alone (localization), at the slope of Eq. 20. Kdeg = -k_deg # Floating point value for the residual force capacity of the # post-failure backbone Fres = 0.1 * Vn # should be a small value - shear failure # Integer flag for type of deformation defining the abscissa # of the limit curve: # 1 = maximum beam-column chord rotations # 2 = drift based on displacement of nodes ndI and ndJ defType = 2 # Integer flag for type of force defining the ordinate of the # limit curve: # 0 = force in associated limit state material # 1 = shear in beam-column element # 2 = axial load in beam-column element # Option 1 assumes no member loads forType = 0 # Integer node tag for the first associated node # (normally node I of $eleTag beam-column element) ndl = self.ele_node_i.tag # Integer node tag for the second associated node # (normally node J of $eleTag beam-column element) ndJ = self.ele_node_j.tag # Nodal degree of freedom to monitor for drift if axis == 'x': dof = 1 elif axis == 'y': dof = 2 # Perpendicular global direction from which length is # determined to compute drift: 1 = X, 2 = Y, 3 = Z perpDirn = 3 # Variable containing limit curve inputs limit_curve_inputs = [ 'ThreePoint', limit_curve_tag, eleTag, x1, y1, x2, y2, x3, y3, Kdeg, Fres, defType, forType, ndl, ndJ, dof, perpDirn ] # Inputs for LimitState material model - Elwood and Moehle 2003 # Stress and strain (or force & deformation) at the three points of # the envelope in the positive direction s1p, e1p = 0.25 * Vn, 0.25 * Vn / k_el # 1st s2p, e2p = 0.75 * Vn, 0.75 * Vn / k_el # 2nd s3p, e3p = 2.5 * Vn, 2.5 * Vn / k_el # 3rd # Stress and strain (or force & deformation) at the three points the # envelope in the negative direction (all are negative values) s1n, e1n = -0.25 * Vn, -0.25 * Vn / k_el # 1st s2n, e2n = -0.75 * Vn, -0.75 * Vn / k_el # 2nd s3n, e3n = -2.5 * Vn, -2.5 * Vn / k_el # 3rd # Pinching factor for strain (or deformation) during reloading pinchX = 0.4 # TODO: Reference # Pinching factor for stress (or force) during reloading pinchY = 0.3 # TODO: Reference # Damage due to ductility: D1(m-1) damage1 = 0.003 # TODO: Reference # Damage due to energy: D2(Ei/Eult) damage2 = 0.0 # TODO: Reference # Power used to determine the degraded unloading stiffness based on # ductility, mu-beta (optional, default=0.0) beta = 0.0 # TODO: Reference # The integer defining the type of LimitCurve (0 = no curve, # 1 = axial curve, all other curves can be any other integer) curveType = 2 # Variable containing limit state material inputs mat_inputs = [ 'LimitState', shear_mat_tag, s1p, e1p, s2p, e2p, s3p, e3p, s1n, e1n, s2n, e2n, s3n, e3n, pinchX, pinchY, damage1, damage2, beta, limit_curve_tag, curveType ] # Rounding to precision limit_curve_inputs = round_list(limit_curve_inputs) mat_inputs = round_list(mat_inputs) return mat_inputs, limit_curve_inputs