"""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
# Imports from utils library
from ....utils.units import MPa, MN
from ....utils.misc import round_list
[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.
plastic rotation.
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:`~ColumnBase`
Base column definition extended by this class.
"""
[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'))
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_inelastic_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})")
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_inelastic_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}")
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_inelastic_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_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,
pinchx, pinchy, damage1, damage2, beta
) = self._get_rot_hinge_props(axis)
# Rotation values for monotonic loading
theta_1 = theta_y
theta_2 = theta_y + theta_cap_pl
theta_3 = theta_y + theta_cap_pl + theta_pc
# Pinching factor for strain (or deformation) during reloading
pinchx = 1.0 # no pinching (default)
# Pinching factor for stress (or force) during reloading
pinchy = 1.0 # no pinching (default)
# Damage due to ductility: D1(mu-1)
damage1 = 0.0 # no degradation (default)
# Damage due to energy: D2(Eii/Eult)
damage2 = 0.0 # no degradation (default)
# Power used to determine the degraded unloading stiffness based on
# ductility, mu-beta (optional, default=0.0)
beta = 0.0 # elastic unloading (default)
# Material inputs other than tag and type
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,
pinchx, pinchy, damage1, damage2, beta
]
# Rounding to precision
rot_mat_inputs = round_list(rot_mat_inputs)
return rot_mat_inputs
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_rot_hinge_props('x')[3]
# 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_rot_hinge_props('y')[3]
# 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
# Concrete compressive strength in MPa
fc_mpa = self.design.fc_q / MPa # convert to MPa
# Nominal length of column
Ln = self.design.H
# 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.
# Effective depth: dist. between outer comp. fiber and tens. steel
d = 0.9 * h
# Transverse reinforcement yield strength (quality adjusted)
fsyh_mpa = self.design.fsyh_q / MPa # in MPa
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)
# Axial load in Mega Newton (mN) - Positive in compression
Nu_MN = Nu / MN # convert to mN
# Transverse reinforcement area
Av = nbh * np.pi * (dbh**2) / 4
# Initial shear strength, see ASCE/SEI 41-17 - Equation 10-3si
k = 1.0 # Degradation factor (1.0 for initial strength)
lambda_ = 1.0 # Assuming normal weight concrete aggregate
d = 0.8 * h # Effective depth, permitted to assume by ASCE
alpha = np.interp(sbh / d, [0.75, 1.0], [1.0, 0.0])
M_V_rat = Ls # Largest ratio of moment to shear, assumed
M_Vd_rat = min(max(M_V_rat / d, 2), 4) # Should satify: 2 <= M/Vd <= 4
Vn = k * (
alpha * (Av * fsyh_mpa * d / sbh)
+ lambda_
* (
(fc_mpa**0.5)
/ (2 * M_Vd_rat)
* (1 + (2 * Nu_MN) / ((fc_mpa**0.5) * Ag)) ** 0.5
)
* (0.8 * Ag)
)
# Convert from MPa units to based units
Vn *= MN
# Shear-spring elastic slope - LeBorgne and Ghannoum (2014) - Eqn. 1
k_el = (5 / 6) * (self.Gcm_q * Ag / Ln)
# Degrading slope of the shear-drift spring backbone
# Shoraka and Elwood (2013) - Eqn. 20
k_deg = (
4.5 * Nu * (((Av * fsyh * 0.9 * h) / (Nu * sbh)) * 4.6 + 1) ** 2
) / 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
Kdeg = -k_deg
# Floating point value for the residual force capacity of the
# post-failure backbone
Fres = 0.05 # 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