Source code for mento.material

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, TYPE_CHECKING, Any
import math
from mento.units import kg, m, MPa, ksi, GPa, psi, Pa, lb, ft, kPa

# Conditional import for type checking only
if TYPE_CHECKING:
    from pint import Quantity


[docs] @dataclass class Material: name: str # Name will be provided by subclasses or instances
[docs] @dataclass class Concrete(Material): f_c: Quantity = field(default=25 * MPa) design_code: str = field(default="ACI 318-19", init=False) unit_system: str = field(init=False) density: Quantity = field(init=False) def __post_init__(self) -> None: # Detect the unit system based on f_c if self.f_c.units == MPa or self.f_c.units == Pa or self.f_c.units == kPa: self.unit_system = "metric" self.density: Quantity = 2500 * kg / m**3 elif self.f_c.units == psi or self.f_c.units == ksi: self.unit_system = "imperial" self.density = 155 * lb / ft**3 else: raise ValueError( f"Unsupported unit system for f_c ({self.f_c.units}). Please use MPa, Pa, kPa, psi, or ksi." )
[docs] def get_properties(self) -> Dict[str, Any]: # Return properties in the appropriate unit system properties = {"f_c": self.f_c, "density": self.density} return properties
[docs] @dataclass class Concrete_ACI_318_19(Concrete): """ Concrete_ACI_318_19 represents a concrete material model based on the ACI 318-19 code provisions. This class extends the base `Concrete` class and implements calculation of key material properties according to the American Concrete Institute (ACI) 318-19 standard, including modulus of elasticity, modulus of rupture, and stress block parameters. It supports both metric and imperial unit systems. Inputs: name: Name of the concrete material. f_c: Characteristic compressive strength of concrete. Methods: get_properties() -> Dict[str, Any]: Returns a dictionary of concrete properties. E_c: Returns modulus of elasticity. f_r (property): Returns modulus of rupture. beta_1 (property): Returns β₁ value. lambda_factor (property): Returns λ factor for lightweight concrete. phi_v (property): Returns shear strength reduction factor. phi_c (property): Returns compression-controlled strength reduction factor. phi_y (property): Returns tension-controlled strength reduction factor. Usage: Instantiate this class to represent a concrete material with properties and code factors compliant with ACI 318-19, suitable for use in structural analysis and design calculations. """ _E_c: Quantity = field(init=False) _f_r: Quantity = field(init=False) _epsilon_c: float = field(default=0.003, init=False) _beta_1: float = field(init=False) _lambda: float = field(init=False) _phi_v: float = field(init=False) _phi_c: float = field(init=False) _phi_t: float = field(init=False) _flexural_min_reduction: bool = field(init=False) def __post_init__(self) -> None: super().__post_init__() # Adjust calculations based on unit system if self.unit_system == "metric": self._E_c = ( ((self.density / (kg / m**3)) ** 1.5) * 0.043 * math.sqrt(self.f_c / MPa) * MPa ) self._f_r = 0.625 * math.sqrt(self.f_c / MPa) * MPa else: # imperial self._E_c = ( ((self.density / (lb / ft**3)) ** 1.5) * 33 * math.sqrt(self.f_c / psi) * psi ) self._f_r = 7.5 * math.sqrt(self.f_c / psi) * psi self._beta_1 = self.__beta_1() self._lambda = 1 # Normalweight concrete self._phi_v = 0.75 # Shear strength reduction factor self._phi_c = 0.65 # Compression controlled strength reduction factor self._phi_t = 0.90 # Tension controlled strength reduction factor self._flexural_min_reduction = ( True # True selects 4/3 of calculated steel if it's less than minimum )
[docs] def get_properties(self) -> Dict[str, Any]: properties = super().get_properties() properties["E_c"] = self._E_c properties["f_r"] = self._f_r properties["beta_1"] = self._beta_1 properties["epsilon_c"] = self._epsilon_c properties["lambda"] = self._lambda properties["phi_v"] = self._phi_v properties["phi_c"] = self._phi_c properties["phi_t"] = self._phi_t return properties
''' def __beta_1(self) -> float: # Table 22.2.2.4.3—Values of β1 for equivalent rectangular concrete stress distribution # Page 399 fc_MPa = self.f_c.to("MPa").magnitude # Ensure comparison in MPa if 17 <= fc_MPa <= 28: return 0.85 elif 28 < fc_MPa <= 55: return 0.85 - 0.05 / 7 * (fc_MPa - 28) elif fc_MPa > 55: return 0.65 else: # Handle case where f_c / MPa < 17 return 0.85 ''' def __beta_1(self) -> float: if self.unit_system == "metric": fc_MPa = self.f_c.to("MPa").magnitude if fc_MPa <= 28: return 0.85 elif fc_MPa <= 55: return 0.85 - 0.05 / 7 * (fc_MPa - 28) else: return 0.65 else: fc_psi = self.f_c.to("psi").magnitude if fc_psi <= 4000: return 0.85 elif fc_psi <= 8000: return 0.85 - 0.05 / 1000 * (fc_psi - 4000) else: return 0.65 @property def E_c(self) -> Quantity: return self._E_c @property def f_r(self) -> Quantity: return self._f_r @property def beta_1(self) -> float: return self._beta_1 @property def lambda_factor(self) -> float: return self._lambda @property def phi_v(self) -> float: return self._phi_v @property def phi_c(self) -> float: return self._phi_c @property def phi_y(self) -> float: return self._phi_t def __str__(self) -> str: properties = self.get_properties() return ( f"Concrete Properties ({self.name}):\n" f" f_c: {properties['f_c']}\n" f" Density: {properties['density']}\n" f" E_c: {properties['E_c']}\n" f" f_r: {properties['f_r']}\n" f" beta_1: {properties['beta_1']:.3f}\n" # Format as float f" epsilon_c: {properties['epsilon_c']:.4f}\n" # Format as float f" λ: {properties['lambda']:.2f}\n" f" phi_v: {properties['phi_v']:.2f}\n" f" phi_c: {properties['phi_c']:.2f}\n" f" phi_t: {properties['phi_t']:.2f}\n" )
[docs] @dataclass class Concrete_CIRSOC_201_25(Concrete_ACI_318_19): """Concrete class for a CIRSOC 201-25 design code with metric units.""" def __post_init__(self) -> None: # Call the parent class's __post_init__ to inherit initializations super().__post_init__() # Override the design code for this specific class self.design_code = "CIRSOC 201-25"
[docs] @dataclass class Concrete_EN_1992_2004(Concrete): """ Concrete_EN_1992_2004 represents concrete material properties and design parameters according to Eurocode EN 1992-1-1:2004. This class extends the base `Concrete` class, providing Eurocode-specific calculations for characteristic and mean strengths, modulus of elasticity, and other design factors. It encapsulates the following key methods: Inputs: name: Name of the concrete material. f_c: Characteristic compressive strength of concrete (f_ck for Eurocode). Methods: get_properties() -> Dict[str, Any]: Returns a dictionary of all relevant material properties. E_cm (property): Returns the secant modulus of elasticity. f_ck (property): Returns the characteristic compressive strength. f_cm (property): Returns the mean compressive strength. f_ctm (property): Returns the mean tensile strength. epsilon_cu3 (property): Returns the ultimate strain in concrete. gamma_c (property): Returns the partial safety factor for concrete. alpha_cc (property): Returns the α_cc coefficient. Lambda_factor (property): Returns the λ factor. Eta_factor (property): Returns the η factor. Usage: This class is intended for use in structural engineering applications where concrete properties must comply with EN 1992-1-1:2004. It provides all necessary parameters for design and verification according to the code. """ def __post_init__(self) -> None: # Crucial: Call parent's __post_init__ first to set unit_system and density super().__post_init__() self.design_code = "EN 1992-2004" # The f_c passed to Concrete is the f_ck for Eurocode self._delta = 0.85 self._f_ck = self.f_c self._f_cm = self._f_ck + 8 * MPa self._E_cm = 22000 * (self._f_cm.to("MPa").magnitude / 10) ** 0.3 * MPa self._f_ctm = 0.3 * (self._f_ck.to("MPa").magnitude) ** (2 / 3) * MPa self._epsilon_cu1 = ( 2.8 + 27 * ((98 - self._f_cm.to("MPa").magnitude) / 100) ** 4 if self._f_ck >= 50 * MPa else 3.5 ) * 1e-3 self._epsilon_c2 = ( 2.0 + 0.085 * ((self._f_ck.to("MPa").magnitude - 50)) ** 0.53 if self._f_ck >= 50 * MPa else 2.0 ) * 1e-3 self._epsilon_cu2 = ( 2.6 + 35 * ((90 - self._f_ck.to("MPa").magnitude) / 100) ** 4 if self._f_ck >= 50 * MPa else 3.5 ) * 1e-3 self._epsilon_c3 = ( 1.75 + 0.55 * ((self._f_ck.to("MPa").magnitude - 50) / 40) if self._f_ck >= 50 * MPa else 1.75 ) * 1e-3 self._epsilon_cu3 = ( 2.6 + 35 * ((90 - self._f_ck.to("MPa").magnitude) / 100) ** 4 if self._f_ck >= 50 * MPa else 3.5 ) * 1e-3 self._gamma_c = 1.5 self._gamma_s = 1.15 self._alpha_cc = self._alpha_cc_calc() # Default values for k values EN_1992-1-1 - ART 5.5: self._k_1 = 0.44 self._k_2 = 1.25 * (0.6 + 0.0014 / self._epsilon_cu2) self._k_3 = 0.54 self._k_4 = 1.25 * (0.6 + 0.0014 / self._epsilon_cu2) self._k_5 = 0.7 self._k_6 = 0.8 * self._epsilon_cu2 def _alpha_cc_calc(self) -> float: # Implementation for alpha_cc, as per Eurocode EN 1992-1-1 # Designers Guide to EN 1992-1-1, Page 62 # Study of the data available on the behaviour of compression zones at failure suggests that the # use of 1.0 is unconservative. For this reason, the UK National Annex recommends a value for # αcc of 0.85, as is proposed in the CEB Model Codes. return 0.85 def _lambda_factor(self) -> float: """ Calculate the effective compression zone depth factor (λ) as per EN 1992-1-1. """ f_ck_mpa = self._f_ck.to("MPa").magnitude # Ensure comparison in MPa if f_ck_mpa <= 50: return 0.8 else: return 0.8 - (f_ck_mpa - 50) / 400 def _eta_factor(self) -> float: """ Calculate the effective compressive strength factor (η) as per EN 1992-1-1. """ f_ck_mpa = self._f_ck.to("MPa").magnitude # Ensure comparison in MPa if f_ck_mpa <= 50: return 1.0 else: return 1.0 - (f_ck_mpa - 50) / 200
[docs] def get_properties(self) -> Dict[str, Any]: properties = super().get_properties() properties.update( { "E_cm": self._E_cm, "f_ck": self._f_ck, "f_cm": self._f_cm, "f_ctm": self._f_ctm, "epsilon_cu3": self._epsilon_cu3, "gamma_c": self._gamma_c, "gamma_s": self._gamma_s, "alpha_cc": self._alpha_cc, "lambda_factor": self._lambda_factor(), "eta_factor": self._eta_factor(), } ) return properties
@property def E_cm(self) -> Quantity: return self._E_cm @property def f_ck(self) -> Quantity: return self._f_ck @property def f_cm(self) -> Quantity: return self._f_cm @property def f_ctm(self) -> Quantity: return self._f_ctm @property def epsilon_cu3(self) -> float: return self._epsilon_cu3 @property def gamma_c(self) -> float: return self._gamma_c @property def gamma_s(self) -> float: return self._gamma_s @property def alpha_cc(self) -> float: # This property returns the calculated alpha_cc return self._alpha_cc @property def Lambda_factor(self) -> float: # Property for lambda_factor return self._lambda_factor() @property def Eta_factor(self) -> float: # Property for eta_factor return self._eta_factor() def __str__(self) -> str: """Customize the string representation for user-friendly display.""" properties = self.get_properties() # Access magnitude for dimensionless quantities return ( f"Concrete Properties ({self.name}):\n" f" Design Code: {self.design_code}\n" f" f_c (Characteristic): {properties['f_ck']}\n" f" f_cm (Mean Compressive): {properties['f_cm']}\n" f" f_ctm (Mean Tensile): {properties['f_ctm']}\n" f" E_cm (Secant Modulus): {properties['E_cm']}\n" f" Density: {properties['density']}\n" f" ε_cu3: {properties['epsilon_cu3']:.4f}\n" f" γ_c: {properties['gamma_c']:.2f}\n" f" γ_s: {properties['gamma_s']:.2f}\n" f" α_cc: {properties['alpha_cc']:.2f}\n" f" λ Factor: {properties['lambda_factor']:.2f}\n" f" Eta Factor: {properties['eta_factor']:.2f}" )
[docs] @dataclass class Steel(Material): _f_y: Quantity = field(init=False) _density: Quantity = field(default=7850 * kg / m**3) def __init__( self, name: str, f_y: Quantity, density: Quantity = 7850 * kg / m**3, gamma_s: float = 1.15, ): super().__init__(name) self._f_y = f_y self._density = density @property def f_y(self) -> Quantity: return self._f_y @property def density(self) -> Quantity: return self._density
[docs] @dataclass class SteelBar(Steel): _E_s: Quantity = field(default=200 * GPa) _epsilon_y: Quantity = field(init=False) def __init__( self, name: str, f_y: Quantity, density: Quantity = 7850 * kg / m**3 ): super().__init__(name, f_y, density) self._epsilon_y = f_y.to("MPa") / (self._E_s.to("MPa")) # 21.2.2.1 - Page 392 @property def E_s(self) -> Quantity: return self._E_s @property def epsilon_y(self) -> Quantity: return self._epsilon_y
[docs] def get_properties(self) -> Dict[str, Any]: properties = { "E_s": self._E_s.to("GPa"), "f_y": self._f_y.to("MPa"), "epsilon_y": self._epsilon_y, } return properties
def __str__(self) -> str: """Customize the string representation for user-friendly display.""" properties = self.get_properties() return ( f"SteelBar Properties ({self.name}):\n" f" f_y: {properties['f_y']}\n" f" E_s: {properties['E_s']}\n" f" epsilon_y: {properties['epsilon_y'].magnitude:.4f}\n" f" Density: {self.density}" )
[docs] @dataclass class SteelStrand(Steel): _f_u: Quantity = field(default=1860 * MPa) _E_s: Quantity = field(default=190000 * MPa) prestress_stress: Quantity = field(default=0 * MPa) _epsilon_y: Quantity = field(init=False) def __init__( self, name: str, f_y: Quantity, density: Quantity = 7850 * kg / m**3 ): super().__init__(name, f_y, density) self._epsilon_y = self._f_y / self._E_s
[docs] def get_properties(self) -> Dict[str, Any]: properties = { "E_s": self._E_s.to("MPa"), "f_y": self._f_y.to("MPa"), "f_u": self._f_u.to("MPa"), } return properties
@property def f_u(self) -> Quantity: return self._f_u @property def E_s(self) -> Quantity: return self._E_s @property def epsilon_y(self) -> Quantity: return self._epsilon_y def __str__(self) -> str: """Customize the string representation for user-friendly display.""" properties = self.get_properties() return ( f"SteelStrand Properties ({self.name}):\n" f" f_y: {properties['f_y']}\n" f" f_u: {properties['f_u']}\n" f" E_s: {properties['E_s']}\n" f" epsilon_y: {self.epsilon_y.magnitude:.4f}\n" f" Prestress Stress: {self.prestress_stress}\n" f" Density: {self.density}" )