PyCBA 0.3__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyCBA
3
- Version: 0.3
3
+ Version: 0.4.1
4
4
  Summary: Python Continuous Beam Analysis
5
5
  Author-email: Colin Caprani <colin.caprani@monash.edu>
6
6
  License: Apache 2.0
@@ -49,3 +49,4 @@ One of the main functions of `PyCBA` is that the basic analysis engine forms the
49
49
 
50
50
  - Influence line generation
51
51
  - Moving load analysis for bridges, targeted at bridge access assessments
52
+ - Load patterning and enveloping
@@ -14,3 +14,4 @@ One of the main functions of `PyCBA` is that the basic analysis engine forms the
14
14
 
15
15
  - Influence line generation
16
16
  - Moving load analysis for bridges, targeted at bridge access assessments
17
+ - Load patterning and enveloping
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyCBA
3
- Version: 0.3
3
+ Version: 0.4.1
4
4
  Summary: Python Continuous Beam Analysis
5
5
  Author-email: Colin Caprani <colin.caprani@monash.edu>
6
6
  License: Apache 2.0
@@ -49,3 +49,4 @@ One of the main functions of `PyCBA` is that the basic analysis engine forms the
49
49
 
50
50
  - Influence line generation
51
51
  - Moving load analysis for bridges, targeted at bridge access assessments
52
+ - Load patterning and enveloping
@@ -13,6 +13,7 @@ src/pycba/beam.py
13
13
  src/pycba/bridge.py
14
14
  src/pycba/inf_lines.py
15
15
  src/pycba/load.py
16
+ src/pycba/pattern.py
16
17
  src/pycba/results.py
17
18
  src/pycba/utils.py
18
19
  src/pycba/vehicle.py
@@ -2,7 +2,7 @@
2
2
  PyCBA - Continuous Beam Analysis in Python
3
3
  """
4
4
 
5
- __version__ = "0.3"
5
+ __version__ = "0.4.1"
6
6
 
7
7
  from .analysis import *
8
8
  from .beam import *
@@ -12,3 +12,4 @@ from .inf_lines import *
12
12
  from .utils import *
13
13
  from .bridge import *
14
14
  from .vehicle import *
15
+ from .pattern import *
@@ -351,7 +351,8 @@ class BeamAnalysis:
351
351
 
352
352
  ax = axs[0]
353
353
  ax.plot([0, L], [0, 0], "k", lw=2)
354
- ax.plot(res.x, -res.M, "r")
354
+ ax.plot(res.x, res.M, "r")
355
+ ax.invert_yaxis()
355
356
  ax.grid()
356
357
  ax.set_ylabel("Bending Moment (kNm)")
357
358
 
@@ -799,7 +799,7 @@ def parse_LM(LM: LoadMatrix) -> List[Load]:
799
799
  return loads
800
800
 
801
801
 
802
- def add_LM(LM1: LoadMatrix, LM2: LoadMatrix):
802
+ def add_LM(LM1: LoadMatrix, LM2: LoadMatrix) -> LoadMatrix:
803
803
  """
804
804
  Adds two load matrices and returns the sum; this enables superposition
805
805
 
@@ -824,3 +824,35 @@ def add_LM(LM1: LoadMatrix, LM2: LoadMatrix):
824
824
  LM.append(load)
825
825
 
826
826
  return LM
827
+
828
+
829
+ def factor_LM(LM: LoadMatrix, gamma: float) -> LoadMatrix:
830
+ """
831
+ Applies a factor to the loads in a `LoadMatrix` object
832
+
833
+ Parameters
834
+ ----------
835
+ LM : LoadMatrix
836
+ The `LoadMatrix` object
837
+
838
+ gamma : float
839
+ A factor to apply to the load magnitudes
840
+
841
+ Returns
842
+ -------
843
+ LM : LoadMatrix
844
+ The factored `LoadMatrix` object
845
+ """
846
+ LMnew = []
847
+ for load in LM:
848
+ i_span = load[0]
849
+ l_type = load[1]
850
+ mag = gamma * load[2]
851
+ if l_type == 1: # UDL
852
+ LMnew.append([i_span, l_type, mag])
853
+ elif l_type == 2 or l_type == 4: # PL or ML
854
+ LMnew.append([i_span, l_type, mag, load[3]])
855
+ else: # PUDL
856
+ LMnew.append([i_span, l_type, mag, load[3], load[4]])
857
+
858
+ return LMnew
@@ -0,0 +1,164 @@
1
+ """
2
+ PyCBA - Continuous Beam Analysis - Load Patterning Module
3
+ """
4
+
5
+ from __future__ import annotations # https://bit.ly/3KYiL2o
6
+ from typing import Optional, Union, Dict, List
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ from .analysis import BeamAnalysis
10
+ from .results import Envelopes, BeamResults
11
+ from .load import LoadMatrix
12
+
13
+
14
+ class LoadPattern:
15
+ """
16
+ Automatically patterns dead and live loads to achieve critical load effects.
17
+ """
18
+
19
+ def __init__(self, ba: BeamAnalysis):
20
+ """
21
+ Initialize the LoadPattern class with the beam.
22
+
23
+ Parameters
24
+ ----------
25
+ ba : BeamAnalysis, optional
26
+ A :class:`pycba.analysis.BeamAnalysis` object. The default is None.
27
+
28
+ Returns
29
+ -------
30
+ None.
31
+ """
32
+
33
+ self.ba = ba
34
+ self.LMg = None
35
+ self.LMq = None
36
+ self.gamma_g_min = 0
37
+ self.gamma_g_max = 0
38
+ self.gamma_q_min = 0
39
+ self.gamma_q_max = 0
40
+
41
+ def set_dead_loads(self, LM: LoadMatrix, gamma_max: float, gamma_min: float):
42
+ """
43
+ Set the nominal dead loads acting on the beam, and the maximum and
44
+ minimum load factor.
45
+
46
+ Parameters
47
+ ----------
48
+ LM : List[List[Union[int, float]]]
49
+ The load matrix for the beam, for this loadcase.
50
+ gamma_max : float
51
+ The maximum load factor.
52
+ gamma_min : float
53
+ The minimum load factor.
54
+
55
+ Returns
56
+ -------
57
+ None.
58
+ """
59
+
60
+ self.LMg = LM
61
+ self.gamma_g_max = gamma_max
62
+ self.gamma_g_min = gamma_min
63
+
64
+ def set_live_loads(self, LM: LoadMatrix, gamma_max: float, gamma_min: float):
65
+ """
66
+ Set the nominal live loads acting on the beam, and the maximum and
67
+ minimum load factor.
68
+
69
+ Parameters
70
+ ----------
71
+ LM : List[List[Union[int, float]]]
72
+ The load matrix for the beam, for this loadcase.
73
+ gamma_max : float
74
+ The maximum load factor.
75
+ gamma_min : float
76
+ The minimum load factor.
77
+
78
+ Returns
79
+ -------
80
+ None.
81
+ """
82
+
83
+ self.LMq = LM
84
+ self.gamma_q_max = gamma_max
85
+ self.gamma_q_min = gamma_min
86
+
87
+ def analyze(self, npts: Optional[int] = None) -> Envelopes:
88
+ """
89
+ Conduct the load patterning analysis.
90
+
91
+ Parameters
92
+ ----------
93
+ npts : Optional[int]
94
+ The number of evaluation points along a member for load effects.
95
+
96
+ Returns
97
+ -------
98
+ Envelopes : `pycba.Envelopes`
99
+ The load effect envelopes from the patterning.
100
+ """
101
+
102
+ # Helper function to get the BeamResults object easily
103
+ def analyze_loadcase(w):
104
+ self.ba.set_loads(w.tolist())
105
+ self.ba.analyze(npts)
106
+ return self.ba.beam_results
107
+
108
+ # Basic copies of the load matrices
109
+ wg = np.array(self.LMg)
110
+ wq = np.array(self.LMq)
111
+ gr, gc = wg.shape
112
+ qr, qc = wq.shape
113
+ w = np.zeros((gr + qr, max(gc, qc)))
114
+ wmax = w.copy()
115
+ wmin = w.copy()
116
+
117
+ # Maximum load matrix
118
+ wmax[:gr, :] = wg
119
+ wmax[:gr, 2] = self.gamma_g_max * wmax[:gr, 2]
120
+ wmax[gr : gr + qr, :] = wq
121
+ wmax[gr : gr + qr, 2] = self.gamma_q_max * wmax[gr : gr + qr, 2]
122
+
123
+ # Minimum load matrix
124
+ wmin[:gr, :] = wg
125
+ wmin[:gr, 2] = self.gamma_g_min * wmin[:gr, 2]
126
+ wmin[gr : gr + qr, :] = wq
127
+ wmin[gr : gr + qr, 2] = self.gamma_q_min * wmin[gr : gr + qr, 2]
128
+
129
+ # Parameters for looping over loadcases
130
+ N = self.ba.beam.no_spans
131
+ vResults = []
132
+
133
+ # Maximum support hogging and reaction -
134
+ # adjacent spans fully loaded with MAX, other spans loaded with MIN
135
+ n_max_hog = max(N - 1, 0)
136
+ for i in range(1, n_max_hog + 1):
137
+ w = wmin.copy()
138
+ adjacent_spans = np.array([i, i + 1])
139
+ mask = np.isin(wmax[:, 0], adjacent_spans)
140
+ w[mask] = wmax[mask]
141
+ res = analyze_loadcase(w)
142
+ vResults.append(res)
143
+
144
+ # Odd numbered spans loaded with MAX for maximum sagging moments
145
+ w = wmax.copy() # set all loading to maximum
146
+ odd_spans = np.array([i for i in range(2, N + 1, 2)])
147
+ mask = np.isin(wmax[:, 0], odd_spans)
148
+ w[mask] = wmin[mask]
149
+ res = analyze_loadcase(w)
150
+ vResults.append(res)
151
+
152
+ # Even numbered spans loaded with MAX for maximum sagging moments
153
+ w = wmin.copy() # set all loading to minimum
154
+ even_spans = np.array([i for i in range(2, N + 1, 2)])
155
+ mask = np.isin(wmin[:, 0], even_spans)
156
+ w[mask] = wmax[mask] # make the load a max
157
+ res = analyze_loadcase(w)
158
+ vResults.append(res)
159
+
160
+ # All spans loaded with MAX
161
+ res = analyze_loadcase(wmax)
162
+ vResults.append(res)
163
+
164
+ return Envelopes(vResults)
@@ -5,6 +5,7 @@ PyCBA - Beam Results module
5
5
  from __future__ import annotations # https://bit.ly/3KYiL2o
6
6
  from typing import List, Tuple
7
7
  import numpy as np
8
+ import matplotlib.pyplot as plt
8
9
  from scipy import integrate
9
10
  from .beam import Beam
10
11
  from .load import MemberResults, LoadMaMb
@@ -348,3 +349,50 @@ class Envelopes:
348
349
  # Ensure no misleading results returned
349
350
  self.Rmax = np.zeros((self.nsup, self.nres))
350
351
  self.Rmin = np.zeros((self.nsup, self.nres))
352
+
353
+ def plot(self, each=False, **kwargs):
354
+ """
355
+ Plots the envelopes of bending and shear.
356
+
357
+ Parameters
358
+ ----------
359
+ each : Boolean
360
+ Wether or not to show each BMD and SFD in the enveloping. The default is False
361
+ **kwargs : Dict
362
+ Matplotlib keyword arguments for plotting.
363
+
364
+ Returns
365
+ -------
366
+ None.
367
+
368
+ """
369
+
370
+ if self.nres < 1:
371
+ raise ValueError("No results to display")
372
+
373
+ L = self.x[-1]
374
+
375
+ fig, axs = plt.subplots(2, 1, sharex=True, **kwargs)
376
+
377
+ ax = axs[0]
378
+ ax.plot([0, L], [0, 0], "k", lw=2)
379
+ ax.plot(self.x, self.Mmax, "r")
380
+ ax.plot(self.x, self.Mmin, "b")
381
+ ax.grid()
382
+ ax.invert_yaxis()
383
+ ax.set_ylabel("Bending Moment (kNm)")
384
+
385
+ ax = axs[1]
386
+ ax.plot([0, L], [0, 0], "k", lw=2)
387
+ ax.plot(self.x, self.Vmax, "r")
388
+ ax.plot(self.x, self.Vmin, "b")
389
+ ax.grid()
390
+ ax.set_ylabel("Shear Force (kN)")
391
+ ax.set_xlabel("Distance along beam (m)")
392
+
393
+ if each:
394
+ for res in self.vResults:
395
+ axs[0].plot(self.x, res.results.M, "r", lw=0.5)
396
+ axs[1].plot(self.x, res.results.V, "b", lw=0.5)
397
+
398
+ return fig, ax
@@ -226,3 +226,42 @@ def test_moment_load():
226
226
  # Check deflection closes
227
227
  d = beam_analysis.beam_results.D[[0, 2]]
228
228
  assert d == pytest.approx([0.0, 0.0])
229
+
230
+
231
+ def test_envelopes():
232
+ L = [6, 4, 6]
233
+ EI = 30 * 10e9 * 1e-6
234
+ R = [-1, 0, -1, 0, -1, 0, -1, 0]
235
+ beam_analysis = cba.BeamAnalysis(L, EI, R)
236
+
237
+ LMg = [[1, 1, 25, 0, 0], [2, 1, 25, 0, 0], [3, 1, 25, 0, 0]]
238
+ γg_max = 1.4
239
+ γg_min = 1.0
240
+ LMq = [[1, 1, 10, 0, 0], [2, 1, 10, 0, 0], [3, 1, 10, 0, 0]]
241
+ γq_max = 1.6
242
+ γq_min = 0
243
+
244
+ lp = cba.LoadPattern(beam_analysis)
245
+ lp.set_dead_loads(LMg, γg_max, γg_min)
246
+ lp.set_live_loads(LMq, γq_max, γq_min)
247
+ env = lp.analyze()
248
+
249
+ m_locs = np.array([3, 6, 8, 10, 13])
250
+ idx = [(np.abs(env.x - x)).argmin() for x in m_locs]
251
+ assert np.allclose(
252
+ env.Mmax[idx], np.array([163.79, 0, 11.75, 0, 163.79]), atol=1e-2
253
+ )
254
+ assert np.allclose(
255
+ env.Mmin[idx], np.array([0, -163.38, -81.42, -163.38, 0]), atol=1e-2
256
+ )
257
+
258
+ n = beam_analysis.beam_results.npts
259
+ nspans = beam_analysis.beam.no_spans
260
+ Vmax = np.array(
261
+ [np.max(env.Vmax[i * (n + 3) : (i + 1) * (n + 3)]) for i in range(nspans)]
262
+ )
263
+ assert np.allclose(Vmax, np.array([131.1, 123.94, 180.23]), atol=1e-2)
264
+ Vmin = np.array(
265
+ [np.min(env.Vmin[i * (n + 3) : (i + 1) * (n + 3)]) for i in range(nspans)]
266
+ )
267
+ assert np.allclose(Vmin, np.array([-180.23, -123.94, -131.10]), atol=1e-2)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes