Skip to content

Commit a538e8d

Browse files
Add biaxial bending diagram
1 parent 3e0e204 commit a538e8d

File tree

5 files changed

+169
-27
lines changed

5 files changed

+169
-27
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,8 @@ documentation can found at [https://robbievanleeuwen.github.io/concrete-properti
6161
- [x] Squash load
6262
- [x] Tensile load
6363
- [x] Moment interaction diagrams
64-
- [x] Basic M-N curve
65-
- [ ] Combined M-N curve (sagging & hogging)
66-
- [ ] Biaxial bending curve
64+
- [x] M-N curves
65+
- [x] Biaxial bending curve
6766

6867
### Stress Analysis
6968
- [ ] Uncracked stresses

concreteproperties/concrete_section.py

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,9 @@ def calculate_service_section_actions(
632632
self,
633633
d_n: float,
634634
kappa: float,
635-
moment_curvature: res.MomentCurvatureResults = res.MomentCurvatureResults(),
635+
moment_curvature: res.MomentCurvatureResults = res.MomentCurvatureResults(
636+
theta=0
637+
),
636638
) -> res.MomentCurvatureResults:
637639
"""Given a neutral axis depth `d_n` and curvature `kappa`, calculates the
638640
resultant axial force and bending moment.
@@ -782,7 +784,7 @@ def ultimate_bending_capacity(
782784
b = d_t # neutral axis at extreme tensile fibre
783785

784786
# initialise ultimate bending results
785-
ultimate_results = res.UltimateBendingResults()
787+
ultimate_results = res.UltimateBendingResults(theta=theta)
786788

787789
# find neutral axis that gives convergence of the axial force
788790
(d_n, r) = brentq(
@@ -805,8 +807,7 @@ def ultimate_normal_force_convergence(
805807
ultimate_results: results.UltimateBendingResults,
806808
) -> float:
807809
"""Given a neutral axis depth `d_n` and neutral axis angle `theta`, calculates
808-
the difference between the target net axial force `n` and the axial force
809-
given `d_n` & `theta`.
810+
the difference between the target net axial force `n` and the axial force.
810811
811812
:param float d_n: Depth of the neutral axis from the extreme compression fibre
812813
:param float n: Net axial force
@@ -829,7 +830,9 @@ def ultimate_normal_force_convergence(
829830
def calculate_ultimate_section_actions(
830831
self,
831832
d_n: float,
832-
ultimate_results: results.UltimateBendingResults,
833+
ultimate_results: res.UltimateBendingResults = res.UltimateBendingResults(
834+
theta=0
835+
),
833836
) -> results.UltimateBendingResults:
834837
"""Given a neutral axis depth `d_n` and neutral axis angle `theta`, calculates
835838
the resultant bending moments `mx`, `my`, `mv` and the net axial force `n`.
@@ -962,13 +965,16 @@ def calculate_ultimate_section_actions(
962965
def moment_interaction_diagram(
963966
self,
964967
theta: float = 0,
968+
m_neg: bool = False,
965969
n_points: int = 24,
966970
) -> res.MomentInteractionResults:
967971
"""Generates a moment interaction diagram given a neutral axis angle `theta`
968972
and `n_points` calculation points between the decompression case and the pure
969973
bending case.
970974
971975
:param float theta: Angle the neutral axis makes with the horizontal axis
976+
:param bool m_neg: If set to true, also calculates the moment interaction for
977+
theta = theta + pi, i.e. positive and negative bending
972978
:param int n_points: Number of calculation points between the decompression
973979
case and the pure bending case.
974980
@@ -994,9 +1000,14 @@ def moment_interaction_diagram(
9941000

9951001
# create progress bar
9961002
with utils.create_known_progress() as progress:
1003+
progress_length = n_points
1004+
1005+
if m_neg:
1006+
progress_length *= 2
1007+
9971008
task = progress.add_task(
9981009
description="[red]Generating M-N diagram",
999-
total=n_points,
1010+
total=progress_length,
10001011
)
10011012

10021013
for d_n in d_n_list:
@@ -1007,16 +1018,100 @@ def moment_interaction_diagram(
10071018
mi_results.m.append(ultimate_results.mv)
10081019
progress.update(task, advance=1)
10091020

1021+
if not m_neg:
1022+
progress.update(
1023+
task,
1024+
description="[bold green]:white_check_mark: M-N diagram generated",
1025+
)
1026+
1027+
# add tensile load
1028+
mi_results.n.append(self.gross_properties.tensile_load)
1029+
mi_results.m.append(0)
1030+
1031+
# if calculating negative bending
1032+
if m_neg:
1033+
theta += np.pi
1034+
# compute extreme tensile fibre
1035+
_, d_t = utils.calculate_extreme_fibre(
1036+
points=self.geometry.points, theta=theta
1037+
)
1038+
1039+
# compute neutral axis depth for pure bending case
1040+
ultimate_results = self.ultimate_bending_capacity(theta=theta, n=0)
1041+
1042+
# generate list of neutral axes
1043+
d_n_list = np.linspace(
1044+
start=d_t, stop=ultimate_results.d_n, num=n_points
1045+
)
1046+
1047+
for d_n in reversed(d_n_list):
1048+
ultimate_results = self.calculate_ultimate_section_actions(
1049+
d_n=d_n, ultimate_results=ultimate_results
1050+
)
1051+
mi_results.n.append(ultimate_results.n)
1052+
mi_results.m.append(-ultimate_results.mv)
1053+
progress.update(task, advance=1)
1054+
1055+
progress.update(
1056+
task,
1057+
description="[bold green]:white_check_mark: M-N diagram generated",
1058+
)
1059+
1060+
# add squash load
1061+
mi_results.n.append(self.gross_properties.squash_load)
1062+
mi_results.m.append(0)
1063+
1064+
return mi_results
1065+
1066+
def biaxial_bending_diagram(
1067+
self,
1068+
n: float = 0,
1069+
n_points: int = 48,
1070+
) -> res.BiaxialBendingResults:
1071+
"""Generates a biaxial bending diagram given a net axial force `n` and
1072+
`n_points` calculation points.
1073+
1074+
:param float n: Net axial force
1075+
:param int n_points: Number of calculation points between the decompression
1076+
case and the pure bending case.
1077+
1078+
:return: Biaxial bending results
1079+
:rtype: :class:`~concreteproperties.results.BiaxialBendingResults`
1080+
"""
1081+
1082+
# initialise results
1083+
bb_results = res.BiaxialBendingResults(n=n)
1084+
1085+
# calculate d_theta
1086+
d_theta = 2*np.pi / n_points
1087+
1088+
# generate list of thetas
1089+
theta_list = np.linspace(start=0, stop=2*np.pi-d_theta, num=n_points)
1090+
1091+
# create progress bar
1092+
with utils.create_known_progress() as progress:
1093+
task = progress.add_task(
1094+
description="[red]Generating biaxial bending diagram",
1095+
total=n_points,
1096+
)
1097+
1098+
# loop through thetas
1099+
for theta in theta_list:
1100+
ultimate_results = self.ultimate_bending_capacity(theta=theta, n=n)
1101+
bb_results.mx.append(ultimate_results.mx)
1102+
bb_results.my.append(ultimate_results.my)
1103+
progress.update(task, advance=1)
1104+
1105+
# add first result to end of list top
1106+
bb_results.mx.append(bb_results.mx[0])
1107+
bb_results.my.append(bb_results.my[0])
1108+
10101109
progress.update(
10111110
task,
1012-
description="[bold green]:white_check_mark: M-N diagram generated",
1111+
description="[bold green]:white_check_mark: Biaxial bending diagram generated",
10131112
)
10141113

1015-
# add tensile load
1016-
mi_results.n.append(self.gross_properties.tensile_load)
1017-
mi_results.m.append(0)
1018-
1019-
return mi_results
1114+
return bb_results
10201115

10211116
def get_c_local(
10221117
self,

concreteproperties/results.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,13 @@ def calculate_transformed_properties(
184184

185185
@dataclass
186186
class MomentCurvatureResults:
187-
"""Class for storing moment curvature results."""
187+
"""Class for storing moment curvature results.
188+
189+
:param float theta: Angle the neutral axis makes with the horizontal axis
190+
"""
188191

189192
# results
190-
theta: float = 0
193+
theta: float
191194
kappa: List[float] = field(default_factory=list)
192195
moment: List[float] = field(default_factory=list)
193196

@@ -234,9 +237,12 @@ def plot_results(
234237

235238
@dataclass
236239
class UltimateBendingResults:
237-
"""Class for storing ultimate bending results."""
240+
"""Class for storing ultimate bending results.
241+
242+
:param float theta: Angle the neutral axis makes with the horizontal axis
243+
"""
238244

239-
theta: float = 0
245+
theta: float
240246
d_n: float = 0
241247
n: float = 0
242248
mx: float = 0
@@ -329,3 +335,45 @@ def plot_multiple_diagrams(
329335
ax.legend(loc="center left", bbox_to_anchor=(1, 0.5))
330336

331337
return ax
338+
339+
@dataclass
340+
class BiaxialBendingResults:
341+
"""Class for storing biaxial bending results.
342+
343+
:param float n: Net axial force
344+
"""
345+
346+
n: float
347+
mx: List[float] = field(default_factory=list)
348+
my: List[float] = field(default_factory=list)
349+
350+
def plot_diagram(
351+
self,
352+
m_scale: float = 1e-6,
353+
**kwargs,
354+
) -> matplotlib.axes._subplots.AxesSubplot:
355+
"""Plots a biaxial bending diagram.
356+
357+
:param float m_scale: Scaling factor to apply to bending moment
358+
:param kwargs: Passed to :func:`~concreteproperties.post.plotting_context`
359+
360+
:return: Matplotlib axes object
361+
:rtype: :class:`matplotlib.axes._subplots.AxesSubplot`
362+
"""
363+
364+
# create plot and setup the plot
365+
with plotting_context(title=f"Biaxial Bending Diagram, $N = {self.n:.3e}$", **kwargs) as (
366+
fig,
367+
ax,
368+
):
369+
# scale results
370+
mx = np.array(self.mx) * m_scale
371+
my = np.array(self.my) * m_scale
372+
373+
ax.plot(mx, my, "o-", markersize=3)
374+
375+
plt.xlabel("Bending Moment $M_x$")
376+
plt.ylabel("Bending Moment $M_y$")
377+
plt.grid(True)
378+
379+
return ax

examples/ex1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
d=600,
4242
n=32,
4343
dia=20,
44-
n_bar=6,
44+
n_bar=7,
4545
n_circle=4,
4646
area_conc=np.pi * 600 * 600 / 4,
4747
area_bar=310,
@@ -51,6 +51,6 @@
5151
)
5252

5353
conc_sec = ConcreteSection(geometry)
54-
5554
print(conc_sec.ultimate_bending_capacity())
5655
conc_sec.moment_interaction_diagram().plot_diagram()
56+
conc_sec.biaxial_bending_diagram(n=4000e3).plot_diagram()

examples/ex2.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@
4343
b=400,
4444
d=600,
4545
dia_top=16,
46-
n_top=6,
47-
dia_bot=16,
48-
n_bot=6,
46+
n_top=3,
47+
dia_bot=24,
48+
n_bot=3,
4949
n_circle=4,
50-
cover=66,
50+
cover=30,
5151
area_top=200,
52-
area_bot=200,
52+
area_bot=450,
5353
conc_mat=concrete,
5454
steel_mat=steel,
5555
)
@@ -58,7 +58,7 @@
5858

5959
pprint(conc_sec.gross_properties)
6060
pprint(conc_sec.get_transformed_gross_properties(elastic_modulus=32.8e3))
61-
mi_res = conc_sec.moment_interaction_diagram()
61+
mi_res = conc_sec.moment_interaction_diagram(m_neg=True)
6262
pprint(mi_res)
6363
mi_res.plot_diagram()
6464
cracked_res = conc_sec.calculate_cracked_properties()

0 commit comments

Comments
 (0)