"""This module provides a base class for representing masonry infill
walls within the BNSM layer.
"""
# Imports from installed packages
from abc import ABC
import numpy as np
import openseespy.opensees as ops
from typing import Tuple, List
# Imports from bnsm library
from .node import Node
# Imports from bdim base library
from ...bdim.baselib.infill import InfillBase as InfillDesign
# Imports from utils library
from ....utils.units import mm, MPa
from ....utils.misc import PRECISION, round_list
# Infill property mapper (Parameters from Hak et al. 2012)
HAKETAL2012_INFILLS = {
"Weak": { # Typology: T1
"fwh": 1.18, # Horizontal compressive strength [MPa]
"fwv": 2.02, # Vertical compressive strength [MPa]
"fwu": 0.44, # Sliding shear resistance of mortar joints [MPa]
"fws": 0.55, # Shear resistance under diagonal compression [MPa]
"Ewh": 991.0, # Horizontal secant modulus [MPa]
"Ewv": 1873.0, # Vertical secant modulus [MPa]
"Gw": 1089.0, # Shear modulus [MPa]
"tw": 80.0, # Wall thickness [mm]
"sig_v": 0.0, # Vertical compression due to gravity loading [MPa]
"v": 0.2, # Poisson's ratio [-]
"W": 6.87, # Unit weight [kN/m3]
},
"Medium": { # Typology: T2
"fwh": 1.11, # Horizontal compressive strength [MPa]
"fwv": 1.50, # Vertical compressive strength [MPa]
"fwu": 0.25, # Sliding shear resistance of mortar joints [MPa]
"fws": 0.31, # Shear resistance under diagonal compression [MPa]
"Ewh": 991.0, # Horizontal secant modulus [MPa]
"Ewv": 1873.0, # Vertical secant modulus [MPa]
"Gw": 1089.0, # Shear modulus [MPa]
"tw": 240.0, # Wall thickness [mm]
"sig_v": 0.0, # Vertical compression due to gravity loading [MPa]
"v": 0.2, # Poisson's ratio [-]
"W": 6.87, # Unit weight [kN/m3]
},
"Strong": { # Typology: T3
"fwh": 1.50, # Horizontal compressive strength [MPa]
"fwv": 3.51, # Vertical compressive strength [MPa]
"fwu": 0.30, # Sliding shear resistance of mortar joints [MPa]
"fws": 0.36, # Shear resistance under diagonal compression [MPa]
"Ewh": 1050.0, # Horizontal secant modulus [MPa]
"Ewv": 3240.0, # Vertical secant modulus [MPa]
"Gw": 1296.0, # Shear modulus [MPa]
"tw": 300.0, # Wall thickness [mm]
"sig_v": 0.0, # Vertical compression due to gravity loading [MPa]
"v": 0.2, # Poisson's ratio [-]
"W": 7.36, # Unit weight [kN/m3]
},
}
[docs]
class InfillBase(ABC):
"""Abstract Base Class for masonry infill wall implementations in the BNSM
layer. It provides methods to define an infill in the OpenSees domain and
to export equivalent Python and Tcl commands.
The implementation follows a macro-modeling approach, where infill walls
are represented by two diagonal truss elements with a nonlinear uniaxial
material model. Infill typology and strut modelling strategy are based on
Hak et al. (2012).
Attributes
----------
design : ~simdesign.rcmrf.bdim.baselib.infill.InfillBase
Instance of infill design information model.
strut1_nodes : List[Node]
Element nodes of 1st diagonal strut.
strut2_nodes : List[Node]
Element nodes of 2nd diagonal strut.
References
----------
Hak, S., Morandi, P., Magenes, G., & Sullivan, T. J. (2012). Damage
control for clay masonry infills in the design of RC frame structures.
Journal of Earthquake Engineering, 16(sup1), 1-35.
https://doi.org/10.1080/13632469.2012.670575
"""
design: InfillDesign
strut1_nodes: List[Node]
strut2_nodes: List[Node]
def __init__(self, design: InfillDesign,
strut1_nodes: List[Node],
strut2_nodes: List[Node]) -> None:
"""
Initialize an `Infill` with its design data and diagonal strut nodes.
Parameters
----------
design : ~simdesign.rcmrf.bdim.baselib.infill.InfillBase
Infill design information (geometry, tags, typology).
strut1_nodes : List[Node]
Nodes defining the first diagonal truss element (e.g., [i, j]).
strut2_nodes : List[Node]
Nodes defining the second diagonal truss element (e.g., [i, j]).
"""
self.design = design
self.strut1_nodes = strut1_nodes
self.strut2_nodes = strut2_nodes
[docs]
def add_to_ops(self) -> None:
"""Adds infill components to the OpenSees domain
(i.e, truss elements and uniaxialmaterial).
"""
# Retrieve element and material inputs
strut1_inputs, strut2_inputs, mat_inputs = self._get_strut_inputs()
# Create the material for diagonal struts
ops.uniaxialMaterial(*mat_inputs)
# Create the elements for diagonal struts
ops.element(*strut1_inputs)
ops.element(*strut2_inputs)
[docs]
def to_py(self) -> List[str]:
"""Gets the Python commands to construct infill components in OpenSees
domain (i.e, truss elements and uniaxialmaterial).
Returns
-------
List[str]
List of Python commands for constructing the components of infill
object in OpenSees.
"""
# Retrieve element and material inputs
strut1_inputs, strut2_inputs, mat_inputs = self._get_strut_inputs()
# Create the material for diagonal struts
content = ['# Create the material for diagonal struts']
mat_inputs = ', '.join(
repr(item) if isinstance(item, str) else str(item)
for item in mat_inputs
)
content.append(f"ops.uniaxialMaterial({mat_inputs})")
# Create the elements for diagonal struts
content.append('# Create the elements for diagonal struts')
strut1_inputs = ', '.join(
repr(item) if isinstance(item, str) else str(item)
for item in strut1_inputs
)
strut2_inputs = ', '.join(
repr(item) if isinstance(item, str) else str(item)
for item in strut2_inputs
)
content.append(f"ops.element({strut1_inputs})")
content.append(f"ops.element({strut2_inputs})")
return content
[docs]
def to_tcl(self) -> List[str]:
"""Gets the Tcl commands to construct infill components in OpenSees
domain (i.e, truss elements and uniaxialmaterial).
Returns
-------
List[str]
List of Tcl commands for constructing the components of infill
object in OpenSees.
"""
# Retrieve element and material inputs
strut1_inputs, strut2_inputs, mat_inputs = self._get_strut_inputs()
# Create the material for diagonal struts
content = ['# Create the material for diagonal struts']
mat_inputs = ' '.join([f"{item}" for item in mat_inputs])
content.append(f"uniaxialMaterial {mat_inputs}")
# Create the elements for diagonal struts
content.append('# Create the elements for diagonal struts')
strut1_inputs = ' '.join([f"{item}" for item in strut1_inputs])
strut2_inputs = ' '.join([f"{item}" for item in strut2_inputs])
content.append(f"element {strut1_inputs}")
content.append(f"element {strut2_inputs}")
return content
def _get_strut_inputs(self) -> Tuple[List[str | int | float]]:
"""
Build OpenSees argument lists for the two diagonal strut elements and
the associated uniaxial material.
Returns
-------
strut1_inputs : List[str | int | float]
1st strut element argument list.
strut2_inputs : List[str | int | float]
2nd strut element argument list.
mat_inputs : List[str | int | float]
`uniaxialMaterial` argument list.
Notes
-----
Element tags are derived from the infill `rectangle.tag` and are
intended to be unique within the model. The material tag is set to
`rectangle.tag`.
"""
mat_tag = self.design.rectangle.tag
mat_inputs, area = self._get_mat_inputs()
ele1_tag = int(str(self.design.rectangle.tag) + '001')
ele2_tag = int(str(self.design.rectangle.tag) + '002')
ele1_nodes = [self.strut1_nodes[0].tag, self.strut1_nodes[1].tag]
ele2_nodes = [self.strut2_nodes[0].tag, self.strut2_nodes[1].tag]
strut1_inputs = ['Truss', ele1_tag] + ele1_nodes + [area, mat_tag]
strut2_inputs = ['Truss', ele2_tag] + ele2_nodes + [area, mat_tag]
return strut1_inputs, strut2_inputs, mat_inputs
def _get_mat_inputs(self) -> Tuple[List[str | float | int], float]:
"""
Compute the uniaxial material definition for the equivalent diagonal
strut and the corresponding strut cross-sectional area based on
Hak et al. 2012.
Returns
-------
mat_inputs : List[str | float | int]
OpenSees `uniaxialMaterial` argument list.
Aw : float
Equivalent digaonal strut area (m2).
References
----------
Hak, S., Morandi, P., Magenes, G., Sullivan, T. (2012). Damage control
for clay masonry infills in the design of RC frame structures.
Journal of Earthquake Engineering, 16(sup1), 1-35.
https://doi.org/10.1080/13632469.2012.670575
"""
# DETERMINE EQUIVALENT DIAGONAL STRUT GEOMETRY
# Typology dependent properties
props = HAKETAL2012_INFILLS.get(self.design.typology)
# Terms based on Figure 7, Hak et al. 2012
# Note average values from surrounding members are considered
plane = self.design.plane
if plane == "XZ":
b_attr = "bx"
I_attr = "Iy"
elif plane == "YZ":
b_attr = "by"
I_attr = "Ix"
hbs = []
hcs = []
Ics = []
Ecs = []
for column in self.design.columns: # Loop through columns
if isinstance(column, list):
for column_ in column:
if column_:
hcs.append(getattr(column_, b_attr))
Ics.append(getattr(column_, I_attr))
Ecs.append(column_.concrete.Ecm)
elif column:
hcs.append(getattr(column, b_attr))
Ics.append(getattr(column, I_attr))
Ecs.append(column.concrete.Ecm)
for beam in self.design.beams: # Loop through beams
if beam:
hbs.append(beam.h)
hb = sum(hbs) / len(hbs) # Beam height
hc = sum(hcs) / len(hcs) # Column height
Ic = (sum(Ics) / len(Ics)) / (mm**4) # Column moment of inertia (mm4)
Ec = (sum(Ecs) / len(Ecs)) / MPa # Colum concrete modulus (MPa)
h = self.design.height / mm # infill height, between joints (mm)
L = self.design.length / mm # infill length, between joints (mm)
Lw = h - hc # clear infill length (mm)
hw = L - hb # clear infill height (mm)
theta = np.arctan(hw / Lw) # inclination of the diagonal strut (rad)
dw = (Lw**2 + hw**2) ** 0.5 # diagonal length of the infill panel (mm)
Ewtheta = 1.0 / ( # Equation (3), Hak et al. 2012
(np.cos(theta) ** 4) / props['Ewh']
+ (np.sin(theta) ** 4) / props['Ewv']
+ (np.cos(theta) ** 2) * (np.sin(theta) ** 2)
* (1.0 / props['Gw'] - 2.0 * props['v'] / props['Ewv'])
)
lambda_h = h * ( # Equation (2), Hak et al. 2012
Ewtheta * props['tw'] * np.sin(2.0 * theta) / (4.0 * Ec * Ic * hw)
) ** 0.25
# Table 4, Hak et al. 2012
if lambda_h < 3.14:
K1, K2 = 1.300, -0.178
elif 3.14 < lambda_h < 7.85:
K1, K2 = 0.707, 0.010
else:
K1, K2 = 0.470, 0.040
# Equivalent digaonal strut width (mm)
bw = dw * (K1 / lambda_h + K2) # Equation (1), Hak et al. 2012
# DETERMINE CRITICAL STRESS IN EACH MODE
# Compression in centre, Equation (4)
sigw1 = 1.16 * props['fwv'] * np.tan(theta) / (K1 + K2 * lambda_h)
# Compression at corners, Equation (5)
sigw2 = (
(1.12 * props['fwv'] * np.sin(theta) * np.cos(theta))
/ (K1 * (lambda_h**-0.12) + K2 * (lambda_h**0.88))
)
# Shear sliding, Equation (6)
sigw3 = (
((1.2 * np.sin(theta) + 0.45 * np.cos(theta)) * props['fwu']
+ 0.3 * props['sig_v']) / (bw / dw)
)
# Diagonal tension, Equation (7)
sigw4 = (0.6 * props['fws'] + 0.3 * props['sig_v']) / (bw / dw)
# Governing failure mechanism
sigw = min(sigw1, sigw2, sigw3, sigw4) * MPa # (kPa)
# Equivalent digaonal strut area (m2)
Aw = (bw * props['tw'] * mm**2)
# Uniaxial material Inputs from Hak et al. 2012.
fpc = -sigw # Compressive strength of strut
fpcu = 0.01 * fpc # assumed, %1 to avoid numerical issues
epsc0 = -0.0013 # from Hak et al. 2012, Figure 12b
epscu = -0.0045 # from Hak et al. 2012, Figure 12b
mat_inputs = round_list([fpc, epsc0, fpcu, epscu], PRECISION)
mat_inputs = ['Concrete01', self.design.rectangle.tag] + mat_inputs
return mat_inputs, Aw