from __future__ import annotations
from math import isinf
from typing import List, Optional, Tuple, Union
import numpy as np
import sectionproperties.pre.geometry as sp_geom
from scipy.optimize import brentq, root_scalar
import concreteproperties.results as res
import concreteproperties.utils as utils
from concreteproperties.analysis_section import AnalysisSection
from concreteproperties.concrete_section import ConcreteSection
from concreteproperties.material import SteelStrand
from concreteproperties.pre import CPGeom, CPGeomConcrete
[docs]class PrestressedSection(ConcreteSection):
"""Class for a prestressed concrete section.
.. note::
Prestressed concrete sections analysed in ``concreteproperties`` must be
symmetric about their vertical (``y``) axis, with all flexure assumed to be
about the ``x`` axis.
.. warning::
The only meshed geometries that are permitted are concrete geometries.
"""
def __init__(
self,
geometry: sp_geom.CompoundGeometry,
moment_centroid: Optional[Tuple[float, float]] = None,
geometric_centroid_override: bool = True,
) -> None:
"""Inits the ConcreteSection class.
:param geometry: *sectionproperties* CompoundGeometry object describing the
prestressed concrete section
:param moment_centroid: If specified, all moments for service and ultimate
analyses are calculated about this point. If not specified, all moments are
calculated about the gross cross-section centroid, i.e. no material
properties applied.
:param geometric_centroid_override: If set to True, sets ``moment_centroid`` to
the geometric centroid i.e. material properties applied
"""
super().__init__(
geometry=geometry,
moment_centroid=moment_centroid,
geometric_centroid_override=geometric_centroid_override,
)
# check symmetry about y-axis
if not np.isclose(
self.gross_properties.e_zyy_minus, self.gross_properties.e_zyy_plus
):
raise ValueError("PrestressedSection must be symmetric about y-axis.")
# check for any meshed geometries
if self.reinf_geometries_meshed:
msg = "Meshed reinforcement geometries are not permitted in "
msg += "PrestressedSection."
raise ValueError(msg)
# sum strand areas
for strand_geom in self.strand_geometries:
self.gross_properties.strand_area += strand_geom.calculate_area()
# calculate prestressed actions
n_prestress = 0
m_prestress = 0
for strand in self.strand_geometries:
if isinstance(strand.material, SteelStrand):
# add axial force
n_strand = (
strand.material.get_prestress_stress() * strand.calculate_area()
)
n_prestress += n_strand
# add moment
centroid = strand.calculate_centroid()
m_prestress += n_strand * (centroid[1] - self.moment_centroid[1])
self.gross_properties.n_prestress = n_prestress
self.gross_properties.m_prestress = m_prestress
[docs] def calculate_cracked_properties(
self,
m_ext: float,
n_ext: float = 0,
) -> res.CrackedResults:
"""Calculates cracked section properties given an axial loading and bending
moment.
:param m_ext: External bending moment
:param n_ext: External axial force
:raises ValueError: If the provided loads do not result in tension within the
concrete
:return: Cracked results object
"""
# check there is tension in the section
uncr_stress = self.calculate_uncracked_stress(n=n_ext, m=m_ext)
if uncr_stress.get_concrete_stress_limits()[0] > 0:
msg = "Analysis failed, section contains no tension. Please provide a "
msg += "combination of m_ext and n_ext that results in a tensile stress "
msg += "within the section when combined with the prestressing actions."
raise utils.AnalysisError(msg)
# initialise cracked results object
cracked_results = res.CrackedResults(
theta=0,
n=self.gross_properties.n_prestress + n_ext,
m=m_ext,
)
# calculate cracking moment
m_cr_pos = self.calculate_cracking_moment(
n=self.gross_properties.n_prestress + n_ext,
m_int=self.gross_properties.m_prestress,
positive=True,
)
m_cr_neg = self.calculate_cracking_moment(
n=self.gross_properties.n_prestress + n_ext,
m_int=self.gross_properties.m_prestress,
positive=False,
)
cracked_results.m_cr = (m_cr_pos, -m_cr_neg)
# set neutral axis depth limits
# depth of neutral axis at extreme tensile fibre
_, d_t = utils.calculate_extreme_fibre(
points=self.compound_geometry.points, theta=0
)
a = 1e-6 * d_t # sufficiently small depth of compressive zone
b = d_t # neutral axis at extreme tensile fibre
# find neutral axis that gives convergence of the the cracked neutral axis
try:
cracked_results.d_nc, r = brentq(
f=self.cracked_neutral_axis_convergence,
a=a,
b=b,
args=(cracked_results),
xtol=1e-3,
rtol=1e-6, # type: ignore
full_output=True,
disp=False,
)
except ValueError:
msg = "Analysis failed. Please raise an issue at "
msg += "https://github.com/robbievanleeuwen/concrete-properties/issues"
raise utils.AnalysisError(msg)
return cracked_results
[docs] def calculate_cracking_moment(
self,
n: float,
m_int: float,
positive: bool,
) -> float:
"""Calculates the cracking moment given an axial load ``n`` and internal bending
moment ``m_int``.
:param n: Axial load
:param m_int: Internal bending moment
:param positive: If set to True, determines the cracking moment for positive
bending, otherwise determines the cracking moment for negative bending
:return: Cracking moment
"""
# determine theta
theta = 0 if positive else np.pi
# get centroidal second moments of area
e_ixx = self.gross_properties.e_ixx_c
# loop through all concrete geometries to find lowest cracking moment
m_c = 0
valid_geom_count = 0
for conc_geom in self.concrete_geometries:
# get distance from centroid to extreme tensile fibre
d = utils.calculate_max_bending_depth(
points=conc_geom.points,
c_local_v=utils.global_to_local(
theta=theta, x=self.gross_properties.cx, y=self.gross_properties.cy
)[1],
theta=theta,
)
# if no part of the section is in tension, go to next geometry
if d == 0:
continue
# calculate stress required for cracking
f_t = conc_geom.material.flexural_tensile_strength
f_n = n * conc_geom.material.elastic_modulus / self.gross_properties.e_a
f_r = f_t + f_n
# cracking moment for this geometry
m_int_sign = -1 if positive else 1
m_c_geom = (f_r / conc_geom.material.elastic_modulus) * (
e_ixx / d
) + m_int_sign * m_int
# if we are the first geometry, initialise cracking moment
if valid_geom_count == 0:
m_c = m_c_geom
# otherwise take smaller cracking moment
else:
m_c = min(m_c, m_c_geom)
valid_geom_count += 1
return m_c
[docs] def cracked_neutral_axis_convergence(
self,
d_nc: float,
cracked_results: res.CrackedResults,
) -> float:
"""Given a trial cracked neutral axis depth ``d_nc``, determines the minimum
concrete stress. For a cracked elastic analysis this should be zero (no tension
allowed).
:param d_nc: Trial cracked neutral axis
:param cracked_results: Cracked results object
:return: Cracked neutral axis convergence
"""
# guess hogging or sagging
m_net_guess = cracked_results.m + self.gross_properties.m_prestress
if m_net_guess > 0:
theta = 0
else:
theta = np.pi
def calc_min_stress():
# calculate extreme fibre in global coordinates
extreme_fibre, d_t = utils.calculate_extreme_fibre(
points=self.compound_geometry.points, theta=theta
)
# find point on neutral axis by shifting by d_nc
point_na = utils.point_on_neutral_axis(
extreme_fibre=extreme_fibre, d_n=d_nc, theta=theta
)
# split concrete geometries above and below d_nc, discard below
cracked_geoms: List[Union[CPGeomConcrete, CPGeom]] = []
for conc_geom in self.concrete_geometries:
top_geoms, _ = conc_geom.split_section(point=point_na, theta=theta)
# save compression geometries
cracked_geoms.extend(top_geoms)
# add reinforcement geometries to list
cracked_geoms.extend(self.reinf_geometries_lumped)
cracked_geoms.extend(self.strand_geometries)
# save cracked geometries and calculate properties
cracked_results.cracked_geometries = cracked_geoms
self.cracked_section_properties(cracked_results=cracked_results)
# conduct cracked stress analysis
return self.calculate_cracked_stress(cracked_results=cracked_results)
cr_stress_res = calc_min_stress()
# check _m_net exists
if cr_stress_res._m_net:
m_net = cr_stress_res._m_net
# if we guess the bending direction wrong
if m_net > 0 and m_net_guess < 0 or m_net < 0 and m_net_guess > 0:
# change bending direction
theta -= np.pi
cr_stress_res = calc_min_stress()
# get minimum concrete stress is zero
min_stress, _ = cr_stress_res.get_concrete_stress_limits()
return min_stress
[docs] def moment_curvature_analysis(
self,
positive: bool = True,
n: float = 0,
kappa_inc: float = 1e-7,
kappa_mult: float = 2,
kappa_inc_max: float = 5e-6,
delta_m_min: float = 0.15,
delta_m_max: float = 0.3,
progress_bar: bool = True,
) -> res.MomentCurvatureResults:
"""Performs a moment curvature analysis given an applied axial force ``n``.
Analysis continues until a material reaches its ultimate strain.
:param positive: If set to True, performs the moment curvature analysis for
positive bending, otherwise performs the moment curvature analysis for
negative bending
:param n: Axial force
:param kappa_inc: Initial curvature increment
:param kappa_mult: Multiplier to apply to the curvature increment ``kappa_inc``
when ``delta_m_max`` is satisfied. When ``delta_m_min`` is satisfied, the
inverse of this multipler is applied to ``kappa_inc``.
:param kappa_inc_max: Maximum curvature increment
:param delta_m_min: Relative change in moment at which to reduce the curvature
increment
:param delta_m_max: Relative change in moment at which to increase the curvature
increment
:param progress_bar: If set to True, displays the progress bar
:return: Moment curvature results object
"""
# determine theta
theta = 0 if positive else np.pi
# determine initial curvature that gives zero moment
def find_intial_curvature(kappa0):
# initialise moment curvature result
mk_res = res.MomentCurvatureResults(theta=theta, n_target=n)
# find neutral axis that gives convergence of axial force
brentq(
f=self.service_normal_force_convergence,
a=-0.1,
b=0.1,
args=(kappa0, mk_res),
)
# calculate moment convergence
return mk_res._m_x_i
# find initial curvature
kappa0 = root_scalar(f=find_intial_curvature, x0=0, x1=-1e-6)
return super().moment_curvature_analysis(
theta=theta,
n=n,
kappa0=kappa0.root,
kappa_inc=kappa_inc,
kappa_mult=kappa_mult,
kappa_inc_max=kappa_inc_max,
delta_m_min=delta_m_min,
delta_m_max=delta_m_max,
progress_bar=progress_bar,
)
[docs] def ultimate_bending_capacity(
self,
positive: bool = True,
n: float = 0,
) -> res.UltimateBendingResults:
"""Given axial force ``n``, calculates the ultimate bending capacity.
Note that ``k_u`` is calculated only for lumped (non-meshed) geometries.
:param positive: If set to True, calculates the positive bending capacity,
otherwise calculates the negative bending capacity.
:param n: Net axial force
:return: Ultimate bending results object
"""
# determine theta
theta = 0 if positive else np.pi
return super().ultimate_bending_capacity(theta=theta, n=n)
[docs] def moment_interaction_diagram(self):
raise NotImplementedError
[docs] def biaxial_bending_diagram(self):
raise NotImplementedError
[docs] def calculate_uncracked_stress(
self,
n: float = 0,
m: float = 0,
) -> res.StressResult:
"""Calculates stresses within the prestressed concrete section assuming an
uncracked section.
Uses gross area section properties to determine concrete, reinforcement and
strand stresses given an axial force ``n`` and bending moment ``m``.
:param n: Axial force
:param m: Bending moment
:return: Stress results object
"""
# initialise stress results
conc_sections = []
conc_sigs = []
conc_forces = []
lumped_reinf_geoms = []
lumped_reinf_sigs = []
lumped_reinf_strains = []
lumped_reinf_forces = []
strand_geoms = []
strand_sigs = []
strand_strains = []
strand_forces = []
# get uncracked section properties
e_a = self.gross_properties.e_a
cx = self.gross_properties.cx
cy = self.gross_properties.cy
e_ixx = self.gross_properties.e_ixx_c
e_iyy = self.gross_properties.e_iyy_c
e_ixy = self.gross_properties.e_ixy_c
# add prestressed actions
n += self.gross_properties.n_prestress
m += self.gross_properties.m_prestress
# calculate neutral axis rotation
theta = 0
# point on neutral axis is centroid
point_na = (cx, cy)
# split meshed geometries above and below neutral axis
split_meshed_geoms = []
for meshed_geom in self.meshed_geometries:
top_geoms, bot_geoms = meshed_geom.split_section(
point=point_na,
theta=theta,
)
split_meshed_geoms.extend(top_geoms)
split_meshed_geoms.extend(bot_geoms)
# loop through all concrete geometries and calculate stress
# for conc_geom in self.concrete_geometries:
for conc_geom in split_meshed_geoms:
analysis_section = AnalysisSection(geometry=conc_geom)
# calculate stress, force and point of action
sig, n_sec, d_x, d_y = analysis_section.get_elastic_stress(
n=n,
m_x=m,
m_y=0,
e_a=e_a,
cx=cx,
cy=cy,
e_ixx=e_ixx,
e_iyy=e_iyy,
e_ixy=e_ixy,
)
# save results
conc_sigs.append(sig)
conc_forces.append((n_sec, d_x, d_y))
conc_sections.append(analysis_section)
# loop through all lumped and strand geometries and calculate stress
for lumped_geom in self.reinf_geometries_lumped + self.strand_geometries:
# initialise stress and position
sig = 0
centroid = lumped_geom.calculate_centroid()
x = centroid[0] - cx
y = centroid[1] - cy
# axial stress
sig += n * lumped_geom.material.elastic_modulus / e_a
# bending moment stress
sig += lumped_geom.material.elastic_modulus * (
-(e_ixy * m) / (e_ixx * e_iyy - e_ixy**2) * x
+ (e_iyy * m) / (e_ixx * e_iyy - e_ixy**2) * y
)
# add initial prestress
if isinstance(lumped_geom.material, SteelStrand):
sig += -lumped_geom.material.get_prestress_stress()
strain = sig / lumped_geom.material.elastic_modulus
# net force and point of action
n_lumped = sig * lumped_geom.calculate_area()
if isinstance(lumped_geom.material, SteelStrand):
strand_sigs.append(sig)
strand_strains.append(strain)
strand_forces.append((n_lumped, x, y))
strand_geoms.append(lumped_geom)
else:
lumped_reinf_sigs.append(sig)
lumped_reinf_strains.append(strain)
lumped_reinf_forces.append((n_lumped, x, y))
lumped_reinf_geoms.append(lumped_geom)
return res.StressResult(
concrete_section=self,
concrete_analysis_sections=conc_sections,
concrete_stresses=conc_sigs,
concrete_forces=conc_forces,
meshed_reinforcement_sections=[],
meshed_reinforcement_stresses=[],
meshed_reinforcement_forces=[],
lumped_reinforcement_geometries=lumped_reinf_geoms,
lumped_reinforcement_stresses=lumped_reinf_sigs,
lumped_reinforcement_strains=lumped_reinf_strains,
lumped_reinforcement_forces=lumped_reinf_forces,
strand_geometries=strand_geoms,
strand_stresses=strand_sigs,
strand_strains=strand_strains,
strand_forces=strand_forces,
)
[docs] def calculate_cracked_stress(
self,
cracked_results: res.CrackedResults,
) -> res.StressResult:
"""Calculates stresses within the prestressed concrete section assuming a
cracked section.
Uses cracked area section properties to determine concrete, reinforcement and
strand stresses given the actions provided during the cracked analysis.
:param cracked_results: Cracked results objects
:return: Stress results object
"""
# initialise stress results
conc_sections = []
conc_sigs = []
conc_forces = []
lumped_reinf_geoms = []
lumped_reinf_sigs = []
lumped_reinf_strains = []
lumped_reinf_forces = []
strand_geoms = []
strand_sigs = []
strand_strains = []
strand_forces = []
# get cracked section properties
e_a = cracked_results.e_a_cr
cx = cracked_results.cx
cy = cracked_results.cy
e_ixx = cracked_results.e_ixx_c_cr
e_iyy = cracked_results.e_iyy_c_cr
e_ixy = cracked_results.e_ixy_c_cr
# determine net moment
# (recalculate moment due to prestressing force about cracked centroid)
m_net = 0
for strand in self.strand_geometries:
if isinstance(strand.material, SteelStrand):
n_strand = (
strand.material.get_prestress_stress() * strand.calculate_area()
)
centroid = strand.calculate_centroid()
m_net += n_strand * (centroid[1] - cy)
m_net += cracked_results.m
# loop through all meshed geometries and calculate stress
for geom in cracked_results.cracked_geometries:
if geom.material.meshed:
analysis_section = AnalysisSection(geometry=geom)
# calculate stress, force and point of action
sig, n_sec, d_x, d_y = analysis_section.get_elastic_stress(
n=cracked_results.n,
m_x=m_net,
m_y=0,
e_a=e_a,
cx=cx,
cy=cy,
e_ixx=e_ixx,
e_iyy=e_iyy,
e_ixy=e_ixy,
)
# save results
conc_sigs.append(sig)
conc_forces.append((n_sec, d_x, d_y))
conc_sections.append(analysis_section)
# loop through all lumped and strand geometries and calculate stress
for lumped_geom in self.reinf_geometries_lumped + self.strand_geometries:
# initialise stress and position of bar
sig = 0
centroid = lumped_geom.calculate_centroid()
x = centroid[0] - cx
y = centroid[1] - cy
# axial stress
sig += cracked_results.n * lumped_geom.material.elastic_modulus / e_a
# bending moment stress
sig += lumped_geom.material.elastic_modulus * (
-(e_ixy * m_net) / (e_ixx * e_iyy - e_ixy**2) * x
+ (e_iyy * m_net) / (e_ixx * e_iyy - e_ixy**2) * y
)
# add initial prestress
if isinstance(lumped_geom.material, SteelStrand):
sig += -lumped_geom.material.get_prestress_stress()
strain = sig / lumped_geom.material.elastic_modulus
# net force and point of action
n_lumped = sig * lumped_geom.calculate_area()
if isinstance(lumped_geom.material, SteelStrand):
strand_sigs.append(sig)
strand_strains.append(strain)
strand_forces.append((n_lumped, x, y))
strand_geoms.append(lumped_geom)
else:
lumped_reinf_sigs.append(sig)
lumped_reinf_strains.append(strain)
lumped_reinf_forces.append((n_lumped, x, y))
lumped_reinf_geoms.append(lumped_geom)
return res.StressResult(
concrete_section=self,
concrete_analysis_sections=conc_sections,
concrete_stresses=conc_sigs,
concrete_forces=conc_forces,
meshed_reinforcement_sections=[],
meshed_reinforcement_stresses=[],
meshed_reinforcement_forces=[],
lumped_reinforcement_geometries=lumped_reinf_geoms,
lumped_reinforcement_stresses=lumped_reinf_sigs,
lumped_reinforcement_strains=lumped_reinf_strains,
lumped_reinforcement_forces=lumped_reinf_forces,
strand_geometries=strand_geoms,
strand_stresses=strand_sigs,
strand_strains=strand_strains,
strand_forces=strand_forces,
_m_net=m_net,
)
[docs] def calculate_service_stress(
self,
moment_curvature_results: res.MomentCurvatureResults,
m: float,
kappa: Optional[float] = None,
) -> res.StressResult:
"""Calculates service stresses within the prestressed concrete section.
Uses linear interpolation of the moment-curvature results to determine the
curvature of the section given the user supplied moment, and thus the stresses
within the section. Otherwise, a curvature can be provided which overrides the
supplied moment.
:param moment_curvature_results: Moment-curvature results objects
:param m: Bending moment
:param kappa: Curvature, if provided overrides the supplied bending moment and
calculates the stress at the given curvature
:return: Stress results object
"""
if kappa is None:
# get curvature
kappa = moment_curvature_results.get_curvature(moment=m)
# initialise variables
mk = res.MomentCurvatureResults(
theta=0, n_target=moment_curvature_results.n_target
)
# find neutral axis that gives convergence of the axial force
try:
eps0, r = brentq(
f=self.service_normal_force_convergence,
a=-0.1,
b=0.1,
args=(kappa, mk),
full_output=True,
disp=False,
)
except ValueError:
msg = "Analysis failed. Confirm that the supplied moment/curvature is "
msg += "within the range of the moment-curvature analysis."
raise utils.AnalysisError(msg)
# initialise stress results
conc_sections = []
conc_sigs = []
conc_forces = []
lumped_reinf_geoms = []
lumped_reinf_sigs = []
lumped_reinf_strains = []
lumped_reinf_forces = []
strand_geoms = []
strand_sigs = []
strand_strains = []
strand_forces = []
# get global coordinates of extreme compressive fibre
ecf, _ = utils.calculate_extreme_fibre(
points=self.compound_geometry.points, theta=0
)
# create splits in meshed geometries at points in stress-strain profiles
meshed_split_geoms: List[Union[CPGeom, CPGeomConcrete]] = []
for meshed_geom in self.meshed_geometries:
split_geoms = utils.split_geom_at_strains_service(
geom=meshed_geom,
theta=0,
ecf=ecf,
eps0=eps0,
kappa=kappa,
)
meshed_split_geoms.extend(split_geoms)
# loop through all meshed geometries and calculate stress
for meshed_geom in meshed_split_geoms:
analysis_section = AnalysisSection(geometry=meshed_geom)
# calculate stress, force and point of action
sig, n_sec, d_x, d_y = analysis_section.get_service_stress(
kappa=kappa,
ecf=ecf,
eps0=eps0,
theta=0,
centroid=self.moment_centroid,
)
# save results
conc_sigs.append(sig)
conc_forces.append((n_sec, d_x, d_y))
conc_sections.append(analysis_section)
# loop through all lumped and strand geometries and calculate stress
for lumped_geom in self.reinf_geometries_lumped + self.strand_geometries:
# calculate area and centroid
area = lumped_geom.calculate_area()
centroid = lumped_geom.calculate_centroid()
# get strain at centroid of lump
strain = utils.get_service_strain(
point=(centroid[0], centroid[1]),
ecf=ecf,
eps0=eps0,
theta=0,
kappa=kappa,
)
# add initial prestress strain
if isinstance(lumped_geom.material, SteelStrand):
eps_pe = -lumped_geom.material.get_prestress_strain(area=area)
strain += eps_pe
# calculate stress, force and point of action
sig = lumped_geom.material.stress_strain_profile.get_stress(strain=strain)
n_lumped = sig * area
if isinstance(lumped_geom.material, SteelStrand):
strand_sigs.append(sig)
strand_strains.append(strain)
strand_forces.append(
(
n_lumped,
centroid[0] - self.moment_centroid[0],
centroid[1] - self.moment_centroid[1],
)
)
strand_geoms.append(lumped_geom)
else:
lumped_reinf_sigs.append(sig)
lumped_reinf_strains.append(strain)
lumped_reinf_forces.append(
(
n_lumped,
centroid[0] - self.moment_centroid[0],
centroid[1] - self.moment_centroid[1],
)
)
lumped_reinf_geoms.append(lumped_geom)
return res.StressResult(
concrete_section=self,
concrete_analysis_sections=conc_sections,
concrete_stresses=conc_sigs,
concrete_forces=conc_forces,
meshed_reinforcement_sections=[],
meshed_reinforcement_stresses=[],
meshed_reinforcement_forces=[],
lumped_reinforcement_geometries=lumped_reinf_geoms,
lumped_reinforcement_stresses=lumped_reinf_sigs,
lumped_reinforcement_strains=lumped_reinf_strains,
lumped_reinforcement_forces=lumped_reinf_forces,
strand_geometries=strand_geoms,
strand_stresses=strand_sigs,
strand_strains=strand_strains,
strand_forces=strand_forces,
)
[docs] def calculate_ultimate_stress(
self,
ultimate_results: res.UltimateBendingResults,
) -> res.StressResult:
"""Calculates ultimate stresses within the prestressed concrete section.
:param ultimate_results: Ultimate bending results objects
:return: Stress results object
"""
# depth of neutral axis at extreme tensile fibre
extreme_fibre, _ = utils.calculate_extreme_fibre(
points=self.compound_geometry.points, theta=ultimate_results.theta
)
# find point on neutral axis by shifting by d_n
if isinf(ultimate_results.d_n):
point_na = (0, 0)
else:
point_na = utils.point_on_neutral_axis(
extreme_fibre=extreme_fibre,
d_n=ultimate_results.d_n,
theta=ultimate_results.theta,
)
# initialise stress results
conc_sections = []
conc_sigs = []
conc_forces = []
lumped_reinf_geoms = []
lumped_reinf_sigs = []
lumped_reinf_strains = []
lumped_reinf_forces = []
strand_geoms = []
strand_sigs = []
strand_strains = []
strand_forces = []
# create splits in meshed geometries at points in stress-strain profiles
meshed_split_geoms: List[Union[CPGeom, CPGeomConcrete]] = []
if isinf(ultimate_results.d_n):
meshed_split_geoms = self.meshed_geometries
else:
for meshed_geom in self.meshed_geometries:
split_geoms = utils.split_geom_at_strains_ultimate(
geom=meshed_geom,
theta=ultimate_results.theta,
point_na=point_na,
ultimate_strain=self.gross_properties.conc_ultimate_strain,
d_n=ultimate_results.d_n,
)
meshed_split_geoms.extend(split_geoms)
# loop through all concrete geometries and calculate stress
for meshed_geom in meshed_split_geoms:
analysis_section = AnalysisSection(geometry=meshed_geom)
# calculate stress, force and point of action
sig, n_sec, d_x, d_y = analysis_section.get_ultimate_stress(
d_n=ultimate_results.d_n,
point_na=point_na,
theta=ultimate_results.theta,
ultimate_strain=self.gross_properties.conc_ultimate_strain,
centroid=self.moment_centroid,
)
# save results
if isinstance(meshed_geom, CPGeomConcrete):
conc_sigs.append(sig)
conc_forces.append((n_sec, d_x, d_y))
conc_sections.append(analysis_section)
# loop through all lumped and strand geometries and calculate stress
for lumped_geom in self.reinf_geometries_lumped + self.strand_geometries:
# calculate area and centroid
area = lumped_geom.calculate_area()
centroid = lumped_geom.calculate_centroid()
# get strain at centroid of lump
if isinf(ultimate_results.d_n):
strain = self.gross_properties.conc_ultimate_strain
else:
strain = utils.get_ultimate_strain(
point=(centroid[0], centroid[1]),
point_na=point_na,
d_n=ultimate_results.d_n,
theta=ultimate_results.theta,
ultimate_strain=self.gross_properties.conc_ultimate_strain,
)
# add initial prestress strain
if isinstance(lumped_geom.material, SteelStrand):
eps_pe = -lumped_geom.material.get_prestress_strain(area=area)
strain += eps_pe
# calculate stress, force and point of action
sig = lumped_geom.material.stress_strain_profile.get_stress(strain=strain)
n_lumped = sig * lumped_geom.calculate_area()
if isinstance(lumped_geom.material, SteelStrand):
strand_sigs.append(sig)
strand_strains.append(strain)
strand_forces.append(
(
n_lumped,
centroid[0] - self.moment_centroid[0],
centroid[1] - self.moment_centroid[1],
)
)
strand_geoms.append(lumped_geom)
else:
lumped_reinf_sigs.append(sig)
lumped_reinf_strains.append(strain)
lumped_reinf_forces.append(
(
n_lumped,
centroid[0] - self.moment_centroid[0],
centroid[1] - self.moment_centroid[1],
)
)
lumped_reinf_geoms.append(lumped_geom)
return res.StressResult(
concrete_section=self,
concrete_analysis_sections=conc_sections,
concrete_stresses=conc_sigs,
concrete_forces=conc_forces,
meshed_reinforcement_sections=[],
meshed_reinforcement_stresses=[],
meshed_reinforcement_forces=[],
lumped_reinforcement_geometries=lumped_reinf_geoms,
lumped_reinforcement_stresses=lumped_reinf_sigs,
lumped_reinforcement_strains=lumped_reinf_strains,
lumped_reinforcement_forces=lumped_reinf_forces,
strand_geometries=strand_geoms,
strand_stresses=strand_sigs,
strand_strains=strand_strains,
strand_forces=strand_forces,
)