Source code for simdesign.rcmrf.bdim.baselib.slab

"""This module provides the base class for representing slabs in the BDIM
layer.
"""
# Imports from installed packages
import numpy as np
from abc import ABC
from scipy.interpolate import interp1d
from typing import Literal, List, Optional

# Imports from bdim base library
from .loads import PermanentBase, VariableBase

# Imports from geometry library
from ...geometry.base import Rectangle

# Imports from utils library
from ....utils.misc import PRECISION


[docs] class SlabBase(ABC): """ Abstract base class for floor slab elements. It provides geometry definition, thickness estimation, load transfer, and load assignment behaviour. It must be inherited by design-class- specific slab implementations. Attributes ---------- rectangle : ~simdesign.rcmrf.geometry.base.Rectangle Geometric mesh representation of the slab (tag, points, lines). typology : Literal[1, 2, 3] Slab typology. 1: Solid two-way cast-in-situ slabs (SS2). 2: Solid one-way cast-in-situ slabs (SS1). 3: One-way composite slab with ceramic blocks and RC joists or pre-stressed beams (HS). gamma_rc : float Unit weight of reinforced concrete [kN/m^3]. pg : float Total permanent load per unit area [kN/m^2]. Set via ``set_loads``. pq : float Variable load per unit area [kN/m^2]. Set via ``set_loads``. roof : bool Whether the slab is located at roof level. MAX_THICKNESS : float Maximum allowable slab thickness, by default 0.85 m. _thickness : float | None Default floor slab thickness. If None, thickness is estimated from span length when accessed via ``t``. _orientation : Literal[1, 2, 3] | None Default floor slab load-transfer orientation. If None, orientation is inferred from typology and span ratio when accessed via ``orientation``. Notes ----- Plan view of mesh objects (rectangle) representing slabs:: y |__x l2(p2,p3) p2(x2,y2,z2) o------>o p3(x3,y3,z3) ^ ^ l1(p1,p2) | | l3(p4,p3) | | p1(x1,y1,z1) o------>o p4(x4,y4,z4) l4(p1,p4) pi is the i-th ``Point`` representing a corner point. li is the i-th surrounding ``Line``. xi, yi, and zi are the coordinates of the i-th ``Point``. """ rectangle: Rectangle typology: Literal[1, 2, 3] gamma_rc: float pg: float pq: float roof: bool MAX_THICKNESS: float = 0.85 _orientation: Optional[Literal[1, 2, 3]] _thickness: Optional[float] def __init__( self, rectangle: Rectangle, typology: Literal[1, 2, 3], thickness: Optional[float] = None, orientation: Optional[Literal[1, 2, 3]] = None ) -> None: """Initialize the Slab object. Parameters ---------- rectangle : ~simdesign.rcmrf.geometry.base.Rectangle Geometric mesh representation of the slab (tag, points, lines). typology : Literal[1, 2, 3] Slab typology. 1: Solid two-way cast-in-situ slabs (SS2). 2: Solid one-way cast-in-situ slabs (SS1). 3: One-way composite slab with ceramic blocks and RC joists or pre-stressed beams (HS). thickness : float, optional Slab thickness (depth), by default None. If None, thickness is estimated from span length; see :attr:`t`. orientation : Literal[1, 2, 3], optional Orientation of slab load transfer to beams, by default None. 1: Load is transferred to beams along the X direction. 2: Load is transferred to beams along the Y direction. 3: Load is transferred to beams along both directions. If None, orientation is inferred from typology and span ratio; see :attr:`orientation`. """ self.rectangle = rectangle self.typology = typology self._thickness = thickness self._orientation = orientation @property def lx(self) -> float: """Slab length along the global X-axis. This corresponds to the length of ``rectangle.lines[1]``. Returns ------- float Slab length along the global X-axis [m]. """ return self.rectangle.lines[1].length @property def ly(self) -> float: """Slab length along the global Y-axis. This corresponds to the length of ``rectangle.lines[0]``. Returns ------- float Slab length along the global Y-axis [m]. """ return self.rectangle.lines[0].length @property def area(self) -> float: """Slab plan area. This corresponds to ``rectangle.area``. Returns ------- float Slab plan area [m^2]. """ return self.rectangle.area @property def pself(self) -> float: """ Slab self-weight per unit area. Returns ------- float Slab self-weight per unit area [kN/m^2]. Notes ----- For solid cast-in-situ slabs (typology 1 or 2), self-weight is computed as: pself = gamma_rc * t For composite slabs with pre-fabricated joists and ceramic blocks (typology 3), a logarithmic regression fit to manufacturer data is used: pself = 2.20 * ln(t) + 6.50 where t is the slab thickness in metres. References ---------- https://presdouro.pt/wp-content/themes/presdouro/images/DT_PD2016_VALIDADO.pdf https://lajes.pavimir.pt/pdfs/DA%2060%20-%20Pavimentos%20Aligeirados.pdf """ # Solid cast-in-situ slab if self.typology == 1 or self.typology == 2: return self.gamma_rc * self.t # Composite slab with pre-fabricated joists and ceramic blocks elif self.typology == 3: return 2.20 * np.log(self.t) + 6.50 @property def beam_alpha_coeffs(self) -> List[float]: """ Alpha coefficients for the four beams surrounding the slab. Alpha coefficients convert triangular or trapezoidal slab loads into equivalent uniformly distributed loads on each beam; see :meth:`_get_alpha`. For one-way orientations, all coefficients are 1.0. Returns ------- list[float] Alpha coefficients of four beams surrounding the slab, ordered [l1, l2, l3, l4] following the plan-view convention. """ if self.orientation == 3: long = max(self.lx, self.ly) short = min(self.lx, self.ly) ratio = float(short / long) alpha_l = self._get_alpha(ratio) alpha_s = self._get_alpha(1.0) if self.lx > self.ly: # Long span is along x return [alpha_s, alpha_l, alpha_s, alpha_l] else: # Long span is along y return [alpha_l, alpha_s, alpha_l, alpha_s] else: return [1.0, 1.0, 1.0, 1.0] @property def orientation(self) -> Literal[1, 2, 3]: """ Slab load-transfer orientation. Returns ------- Literal[1, 2, 3] Slab load-transfer orientation. 1: Load is transferred to beams along the X direction. 2: Load is transferred to beams along the Y direction. 3: Load is transferred to beams along both directions. Notes ----- When orientation is not explicitly set, it is inferred as follows: For two-way slabs (typology 1) ``orientation = 3``. For one-way slabs, load transfer is oriented along the longer span axis: ``orientation = 1`` if lx > ly, ``orientation = 2`` otherwise. """ if self._orientation is None: if self.typology == 1: return 3 elif self.lx / self.ly > 1.0: return 1 else: return 2 return self._orientation @orientation.setter def orientation(self, value: Optional[Literal[1, 2, 3]] = None) -> None: """Setter for orientation. If ``value`` is None, the orientation will be inferred from typology and span ratio when next accessed; see :attr:`orientation`. """ self._orientation = value @property def t(self) -> float: """ Slab thickness (depth). Returns ------- float Slab thickness (depth) [m]. Notes ----- When thickness is not explicitly set, it is estimated from the shorter span length ``min(lx, ly)``. For solid cast-in-situ slabs (typology 1 or 2), a span-to-depth ratio of 30 is assumed: t = round(min_span / 30, 2) For composite slabs with pre-fabricated joists and ceramic blocks (typology 3), a linear regression fit to manufacturer data is used: t = round(0.032 * min_span + 0.065, 2) In both cases the result is capped at :attr:`MAX_THICKNESS`. References ---------- https://presdouro.pt/wp-content/themes/presdouro/images/DT_PD2016_VALIDADO.pdf https://lajes.pavimir.pt/pdfs/DA%2060%20-%20Pavimentos%20Aligeirados.pdf """ if self._thickness is None: min_span_length = min(self.lx, self.ly) if self.typology == 1 or self.typology == 2: return min( round(100 * min_span_length / 30) / 100, self.MAX_THICKNESS) elif self.typology == 3: return min( round(100 * (0.032 * min_span_length + 0.065)) / 100, self.MAX_THICKNESS) return self._thickness @t.setter def t(self, value: Optional[float] = None) -> None: """Setter for slab thickness (depth). If ``value`` is None, thickness will be estimated from span length when next accessed; see :attr:`t`. """ self._thickness = value @property def beam_tributary_areas(self) -> List[float]: """ Tributary areas assigned to the surrounding beams. Returns ------- list[float] Tributary areas [m^2] for the four beams surrounding the slab, ordered [l1, l2, l3, l4] following the plan-view convention. Notes ----- For one-way orientations (1 or 2), one pair of parallel beams carries the full half-area each and the perpendicular pair carries nothing. For two-way orientation (3), loads are distributed as triangular areas to beams along the shorter span and trapezoidal areas to beams along the longer span. """ # Loading along beam in x if self.orientation == 1: ax = self.lx * self.ly / 2 ay = 0 # Loading along beam in y elif self.orientation == 2: ax = 0 ay = self.lx * self.ly / 2 # Loading along beams on both sides elif self.orientation == 3: if self.lx > self.ly: # Trapezoid area ax = (((self.lx - self.ly) + self.lx) * self.ly) / 2 # Triangle area ay = ((self.ly / 2) * self.ly) / 2 else: # Triangle area ax = ((self.lx / 2) * self.lx) / 2 # Trapezoid area ay = (((self.ly - self.lx) + self.ly) * self.lx) / 2 ax = round(ax, PRECISION) ay = round(ay, PRECISION) return [ay, ax, ay, ax]
[docs] def set_loads( self, permanent_loads: PermanentBase, variable_loads: VariableBase ) -> None: """Assign permanent and variable loads to the slab. Sets :attr:`gamma_rc`, :attr:`pg` (self-weight + superimposed dead load), and :attr:`pq` from the provided load objects. Load values depend on whether the slab is at roof or floor level (:attr:`roof`). Parameters ---------- permanent_loads : PermanentBase Permanent loads. variable_loads : VariableBase Variable loads. """ self.gamma_rc = permanent_loads.gamma_rc if self.roof: self.pg = self.pself + permanent_loads.roof self.pq = variable_loads.roof else: self.pg = self.pself + permanent_loads.floor self.pq = variable_loads.floor
def _get_alpha(self, ratio: float) -> float: """ Compute correction factor for equivalent uniformly distributed load. The load transferred from the slab to a beam may be triangular or trapezoidal. The correction factor ``alpha`` converts this load to an equivalent uniformly distributed load that produces the same maximum bending moment. Parameters ---------- ratio : float Ratio of shorter to longer slab dimension, in the range (0, 1]. Returns ------- float Correction factor for equivalent uniformly distributed load, in the range [2/3, 1.0]. Notes ----- The input ratio is halved internally before interpolation to align with the reference table, which is defined over half-span ratios. In the mid-span of a beam with length L, a triangular load with peak value wtr results in a maximum moment of: Mmax = (L**2 * wtr) / 12 A uniform load we over the same beam produces: Mmax = (L**2 * we) / 8 Equating both expressions yields alpha = 2/3 for triangular loading. For trapezoidal loads, alpha varies between 2/3 and 1.0 depending on the aspect ratio. """ # Reference table for alpha values ratio_ref = np.arange(0, 0.55, 0.05) alpha_ref = np.array([1.000, 0.9967, 0.9867, 0.97, 0.9467, 0.9167, 0.88, 0.8367, 0.7867, 0.73, 0.6667]) # Ratio is halved and clipped for compatibility with the table. ratio = np.clip(ratio / 2, 0.0, 0.5) alpha = float(interp1d(ratio_ref, alpha_ref)(ratio)) return alpha