PyCBA 0.6.0__tar.gz → 0.7.0__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.
- {pycba-0.6.0/src/PyCBA.egg-info → pycba-0.7.0}/PKG-INFO +1 -1
- {pycba-0.6.0 → pycba-0.7.0/src/PyCBA.egg-info}/PKG-INFO +1 -1
- {pycba-0.6.0 → pycba-0.7.0}/src/PyCBA.egg-info/SOURCES.txt +2 -1
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/__init__.py +1 -1
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/analysis.py +46 -6
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/bridge.py +31 -5
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/inf_lines.py +11 -7
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/load.py +281 -34
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/results.py +153 -39
- {pycba-0.6.0 → pycba-0.7.0}/tests/test_basic.py +305 -6
- pycba-0.7.0/tests/test_bridge.py +274 -0
- {pycba-0.6.0 → pycba-0.7.0}/tests/test_inf_lines.py +27 -0
- pycba-0.7.0/tests/test_results.py +83 -0
- pycba-0.6.0/tests/test_bridge.py +0 -102
- {pycba-0.6.0 → pycba-0.7.0}/LICENSE +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/README.md +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/pyproject.toml +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/setup.cfg +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/setup.py +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/src/PyCBA.egg-info/dependency_links.txt +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/src/PyCBA.egg-info/requires.txt +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/src/PyCBA.egg-info/top_level.txt +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/beam.py +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/pattern.py +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/types.py +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/utils.py +0 -0
- {pycba-0.6.0 → pycba-0.7.0}/src/pycba/vehicle.py +0 -0
|
@@ -91,13 +91,15 @@ class BeamAnalysis:
|
|
|
91
91
|
* ``+k`` — elastic spring stiffness in consistent units.
|
|
92
92
|
|
|
93
93
|
LM : list of list, optional
|
|
94
|
-
Load matrix: a list of load descriptors
|
|
95
|
-
|
|
94
|
+
Load matrix: a list of load descriptors. The number of columns
|
|
95
|
+
per entry depends on the load type:
|
|
96
96
|
|
|
97
|
-
1. UDL — ``
|
|
98
|
-
2. Point load — ``
|
|
99
|
-
3. Partial UDL — ``
|
|
100
|
-
4. Moment load — ``
|
|
97
|
+
1. UDL — ``[span, 1, w]``.
|
|
98
|
+
2. Point load — ``[span, 2, P, a]``.
|
|
99
|
+
3. Partial UDL — ``[span, 3, w, a, c]``.
|
|
100
|
+
4. Moment load — ``[span, 4, M, a]``.
|
|
101
|
+
5. Trapezoidal — ``[span, 5, w1, w2]`` (full span) or
|
|
102
|
+
``[span, 5, w1, w2, a, c]`` (partial).
|
|
101
103
|
|
|
102
104
|
eletype : array_like of int, optional
|
|
103
105
|
Element type for each span, controlling which end(s) carry moment:
|
|
@@ -241,6 +243,44 @@ class BeamAnalysis:
|
|
|
241
243
|
load = [i_span, 4, m, a]
|
|
242
244
|
self._beam.add_load(load)
|
|
243
245
|
|
|
246
|
+
def add_trap(
|
|
247
|
+
self,
|
|
248
|
+
i_span: int,
|
|
249
|
+
w1: float,
|
|
250
|
+
w2: float,
|
|
251
|
+
a: Optional[float] = None,
|
|
252
|
+
c: Optional[float] = None,
|
|
253
|
+
):
|
|
254
|
+
"""
|
|
255
|
+
Append a trapezoidal (linearly varying) distributed load.
|
|
256
|
+
|
|
257
|
+
When *a* and *c* are omitted the load covers the full span, varying
|
|
258
|
+
from *w1* at the left end to *w2* at the right end. When *a* and *c*
|
|
259
|
+
are given the load covers the region from *a* to *a + c*, varying from
|
|
260
|
+
*w1* to *w2* over that length.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
i_span : int
|
|
265
|
+
1-based span index.
|
|
266
|
+
w1 : float
|
|
267
|
+
Load intensity at the start of the load. Positive values act downward.
|
|
268
|
+
w2 : float
|
|
269
|
+
Load intensity at the end of the load. Positive values act downward.
|
|
270
|
+
a : float, optional
|
|
271
|
+
Distance from the left end of the span to the start of the load.
|
|
272
|
+
If given, *c* must also be provided.
|
|
273
|
+
c : float, optional
|
|
274
|
+
Length (cover) of the load. Required when *a* is provided.
|
|
275
|
+
"""
|
|
276
|
+
if a is not None and c is None:
|
|
277
|
+
raise ValueError("If 'a' is specified, 'c' must also be provided")
|
|
278
|
+
if a is not None:
|
|
279
|
+
load = [i_span, 5, w1, w2, a, c]
|
|
280
|
+
else:
|
|
281
|
+
load = [i_span, 5, w1, w2]
|
|
282
|
+
self._beam.add_load(load)
|
|
283
|
+
|
|
244
284
|
def analyze(self, npts: Optional[int] = None) -> int:
|
|
245
285
|
"""
|
|
246
286
|
Execute the direct-stiffness analysis.
|
|
@@ -209,7 +209,12 @@ class BridgeAnalysis:
|
|
|
209
209
|
return self.ba.analyze()
|
|
210
210
|
|
|
211
211
|
def run_vehicle(
|
|
212
|
-
self,
|
|
212
|
+
self,
|
|
213
|
+
step: float,
|
|
214
|
+
plot_env: bool = False,
|
|
215
|
+
plot_all: bool = False,
|
|
216
|
+
pos_start: Optional[float] = None,
|
|
217
|
+
pos_end: Optional[float] = None,
|
|
213
218
|
) -> Envelopes:
|
|
214
219
|
"""
|
|
215
220
|
Runs the vehicle over the bridge performing a static analysis at each point
|
|
@@ -223,6 +228,12 @@ class BridgeAnalysis:
|
|
|
223
228
|
plot_all : bool, optional
|
|
224
229
|
Whether or not to plot the results for each position as an animation.
|
|
225
230
|
The default is False.
|
|
231
|
+
pos_start : Optional[float], optional
|
|
232
|
+
The starting position of the front axle. Defaults to 0 (front axle at
|
|
233
|
+
the left end of the beam).
|
|
234
|
+
pos_end : Optional[float], optional
|
|
235
|
+
The ending position of the front axle. Defaults to beam length plus
|
|
236
|
+
vehicle length (front axle past the right end of the beam).
|
|
226
237
|
|
|
227
238
|
Raises
|
|
228
239
|
------
|
|
@@ -239,14 +250,20 @@ class BridgeAnalysis:
|
|
|
239
250
|
self._check_objects()
|
|
240
251
|
self.pos = []
|
|
241
252
|
self.vResults = []
|
|
242
|
-
|
|
253
|
+
|
|
254
|
+
if pos_start is None:
|
|
255
|
+
pos_start = 0.0
|
|
256
|
+
if pos_end is None:
|
|
257
|
+
pos_end = self.ba.beam.length + self.veh.L
|
|
258
|
+
|
|
259
|
+
npts = round((pos_end - pos_start) / step) + 1
|
|
243
260
|
|
|
244
261
|
if plot_all:
|
|
245
262
|
fig, axs = plt.subplots(2, 1, sharex=True)
|
|
246
263
|
|
|
247
264
|
for i in range(npts):
|
|
248
265
|
# load position
|
|
249
|
-
pos = i * step
|
|
266
|
+
pos = pos_start + i * step
|
|
250
267
|
self.pos.append(pos)
|
|
251
268
|
out = self._single_analysis(pos)
|
|
252
269
|
if out != 0:
|
|
@@ -269,7 +286,11 @@ class BridgeAnalysis:
|
|
|
269
286
|
) -> Dict[str, Dict[str, Union[float, np.ndarray]]]:
|
|
270
287
|
"""
|
|
271
288
|
From the envelopes output, returns the extreme values, their locations,
|
|
272
|
-
and the position of the vehicle for each in a dictionary of dictionaries
|
|
289
|
+
and the position of the vehicle for each in a dictionary of dictionaries.
|
|
290
|
+
|
|
291
|
+
Each moment entry (``Mmax``, ``Mmin``) also contains a ``"Vco"`` key with
|
|
292
|
+
the coincident shear at the critical location. Each shear entry (``Vmax``,
|
|
293
|
+
``Vmin``) contains a ``"Mco"`` key with the coincident moment.
|
|
273
294
|
|
|
274
295
|
Parameters
|
|
275
296
|
----------
|
|
@@ -287,7 +308,8 @@ class BridgeAnalysis:
|
|
|
287
308
|
-------
|
|
288
309
|
crit_values : Dict[str, Dict[str, Union[float, np.ndarray]]]
|
|
289
310
|
A dictionary of dictionaries containing the critical values (i.e. extremes)
|
|
290
|
-
of each of the load effects, both maximum and minimum
|
|
311
|
+
of each of the load effects, both maximum and minimum, along with
|
|
312
|
+
coincident values of the other effect.
|
|
291
313
|
"""
|
|
292
314
|
|
|
293
315
|
crit_values = {}
|
|
@@ -329,21 +351,25 @@ class BridgeAnalysis:
|
|
|
329
351
|
"val": Mmax,
|
|
330
352
|
"at": env.x[env.Mmax.argmax()],
|
|
331
353
|
"pos": [self.pos[i] for i in indx["Mmax"]],
|
|
354
|
+
"Vco": env.Vco_Mmax[env.Mmax.argmax()],
|
|
332
355
|
}
|
|
333
356
|
crit_values["Mmin"] = {
|
|
334
357
|
"val": Mmin,
|
|
335
358
|
"at": env.x[env.Mmin.argmin()],
|
|
336
359
|
"pos": [self.pos[i] for i in indx["Mmin"]],
|
|
360
|
+
"Vco": env.Vco_Mmin[env.Mmin.argmin()],
|
|
337
361
|
}
|
|
338
362
|
crit_values["Vmax"] = {
|
|
339
363
|
"val": Vmax,
|
|
340
364
|
"at": env.x[env.Vmax.argmax()],
|
|
341
365
|
"pos": [self.pos[i] for i in indx["Vmax"]],
|
|
366
|
+
"Mco": env.Mco_Vmax[env.Vmax.argmax()],
|
|
342
367
|
}
|
|
343
368
|
crit_values["Vmin"] = {
|
|
344
369
|
"val": Vmin,
|
|
345
370
|
"at": env.x[env.Vmin.argmin()],
|
|
346
371
|
"pos": [self.pos[i] for i in indx["Vmin"]],
|
|
372
|
+
"Mco": env.Mco_Vmin[env.Vmin.argmin()],
|
|
347
373
|
}
|
|
348
374
|
crit_values["nsup"] = env.nsup
|
|
349
375
|
for i in range(env.nsup):
|
|
@@ -132,13 +132,19 @@ class InfluenceLines:
|
|
|
132
132
|
self.ba.beam.no_fixed_restraints
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
-
#
|
|
135
|
+
# Find the span containing the poi and the closest non-padding index
|
|
136
|
+
# within that span. Padding indices (first/last per span) have M/V
|
|
137
|
+
# forced to zero, so we must avoid them.
|
|
138
|
+
ispan, _ = self.ba.beam.get_local_span_coords(poi)
|
|
139
|
+
if ispan == -1:
|
|
140
|
+
ispan = self.ba.beam.no_spans - 1
|
|
141
|
+
span_x = self.vResults[0].vRes[ispan].x
|
|
142
|
+
# Skip padding at indices 0 and -1; real data is at 1:-1
|
|
143
|
+
idx_in_span = np.abs(span_x[1:-1] - poi).argmin() + 1
|
|
136
144
|
|
|
137
145
|
if load_effect.upper() == "V":
|
|
138
|
-
dx = x[2] - x[1]
|
|
139
|
-
idx = np.where(np.abs(x - poi) <= dx * 1e-6)[0][0]
|
|
140
146
|
for i, res in enumerate(self.vResults):
|
|
141
|
-
eta[i] = res.
|
|
147
|
+
eta[i] = res.vRes[ispan].V[idx_in_span]
|
|
142
148
|
|
|
143
149
|
elif load_effect.upper() == "R":
|
|
144
150
|
#
|
|
@@ -182,10 +188,8 @@ class InfluenceLines:
|
|
|
182
188
|
eta[i] = res.R[mt_sup_idx]
|
|
183
189
|
|
|
184
190
|
else:
|
|
185
|
-
dx = x[2] - x[1]
|
|
186
|
-
idx = np.where(np.abs(x - poi) <= dx * 1e-6)[0][0]
|
|
187
191
|
for i, res in enumerate(self.vResults):
|
|
188
|
-
eta[i] = res.
|
|
192
|
+
eta[i] = res.vRes[ispan].M[idx_in_span]
|
|
189
193
|
|
|
190
194
|
return (np.array(self.pos), eta)
|
|
191
195
|
|
|
@@ -1,37 +1,34 @@
|
|
|
1
|
-
"""
|
|
2
|
-
PyCBA - Load module
|
|
3
|
-
|
|
4
|
-
The load matrix
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
from
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class Load:
|
|
1
|
+
"""
|
|
2
|
+
PyCBA - Load module
|
|
3
|
+
|
|
4
|
+
The load matrix is a ``List[List]`` of load descriptors. Each entry
|
|
5
|
+
describes one load; the number of columns varies by load type:
|
|
6
|
+
|
|
7
|
+
===== ==================== ================================ ====
|
|
8
|
+
Type Name Format Cols
|
|
9
|
+
===== ==================== ================================ ====
|
|
10
|
+
1 UDL ``[span, 1, w]`` 3
|
|
11
|
+
2 Point Load ``[span, 2, P, a]`` 4
|
|
12
|
+
3 Partial UDL ``[span, 3, w, a, c]`` 5
|
|
13
|
+
4 Moment Load ``[span, 4, M, a]`` 4
|
|
14
|
+
5 Trapezoidal (full) ``[span, 5, w1, w2]`` 4
|
|
15
|
+
5 Trapezoidal (partial) ``[span, 5, w1, w2, a, c]`` 6
|
|
16
|
+
===== ==================== ================================ ====
|
|
17
|
+
|
|
18
|
+
The type alias `LoadMatrix` is defined as
|
|
19
|
+
|
|
20
|
+
.. autodata:: LoadMatrix
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
from typing import Union, List, NamedTuple, Tuple, Optional
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
from .types import LoadType, LoadMatrix, LoadCNL, MemberResults
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Load:
|
|
35
32
|
"""
|
|
36
33
|
Beam load container and processor
|
|
37
34
|
"""
|
|
@@ -430,6 +427,244 @@ class LoadPUDL(Load):
|
|
|
430
427
|
return res
|
|
431
428
|
|
|
432
429
|
|
|
430
|
+
class LoadTrapez(Load):
|
|
431
|
+
"""
|
|
432
|
+
Trapezoidal (linearly varying) distributed load, optionally partial.
|
|
433
|
+
|
|
434
|
+
The load varies linearly from intensity *w1* at position *a* to *w2* at
|
|
435
|
+
position *a + c*. When *a* = 0 and *c* = span length the load covers
|
|
436
|
+
the full span (the default).
|
|
437
|
+
"""
|
|
438
|
+
|
|
439
|
+
def __init__(
|
|
440
|
+
self,
|
|
441
|
+
i_span: int,
|
|
442
|
+
w1: float,
|
|
443
|
+
w2: float,
|
|
444
|
+
a: float = 0.0,
|
|
445
|
+
c: Optional[float] = None,
|
|
446
|
+
):
|
|
447
|
+
"""
|
|
448
|
+
Creates a trapezoidal load for the member.
|
|
449
|
+
|
|
450
|
+
Parameters
|
|
451
|
+
----------
|
|
452
|
+
i_span : int
|
|
453
|
+
The member index to which the load is applied.
|
|
454
|
+
w1 : float
|
|
455
|
+
The load intensity at position *a* (left edge of the load).
|
|
456
|
+
w2 : float
|
|
457
|
+
The load intensity at position *a + c* (right edge of the load).
|
|
458
|
+
a : float, optional
|
|
459
|
+
Distance from the left end of the span to the start of the load.
|
|
460
|
+
Default is 0 (load starts at the left end).
|
|
461
|
+
c : float or None, optional
|
|
462
|
+
Length (cover) of the load. ``None`` (default) means full span
|
|
463
|
+
from *a* to the right end.
|
|
464
|
+
"""
|
|
465
|
+
super().__init__(i_span)
|
|
466
|
+
self.w1 = w1
|
|
467
|
+
self.w2 = w2
|
|
468
|
+
self.a = a
|
|
469
|
+
self._c = c # None ⇒ full span from a, resolved when L is known
|
|
470
|
+
|
|
471
|
+
def _resolve(self, L: float):
|
|
472
|
+
"""Resolve c and clip to span boundaries.
|
|
473
|
+
|
|
474
|
+
Returns (w1, w2, dw, a, c) with c > 0, or c = 0 when the load
|
|
475
|
+
falls outside the span.
|
|
476
|
+
"""
|
|
477
|
+
a = self.a
|
|
478
|
+
c = self._c if self._c is not None else L - a
|
|
479
|
+
w1 = self.w1
|
|
480
|
+
w2 = self.w2
|
|
481
|
+
|
|
482
|
+
if a >= L or c <= 0:
|
|
483
|
+
return w1, w2, 0.0, a, 0.0
|
|
484
|
+
|
|
485
|
+
# Clip overhang
|
|
486
|
+
if a + c > L:
|
|
487
|
+
c_orig = c
|
|
488
|
+
c = L - a
|
|
489
|
+
w2 = w1 + (w2 - w1) * c / c_orig # interpolated at clipped end
|
|
490
|
+
|
|
491
|
+
return w1, w2, w2 - w1, a, c
|
|
492
|
+
|
|
493
|
+
def get_cnl(self, L: float, eType: int) -> LoadCNL:
|
|
494
|
+
"""
|
|
495
|
+
Consistent Nodal Loads for the trapezoidal load on a fixed-fixed span.
|
|
496
|
+
|
|
497
|
+
Derived from the fixed-end force influence integrals:
|
|
498
|
+
|
|
499
|
+
.. math::
|
|
500
|
+
M_A = \\frac{1}{L^2}\\int_a^b w(x)\\,x\\,(L-x)^2\\,dx
|
|
501
|
+
|
|
502
|
+
Parameters
|
|
503
|
+
----------
|
|
504
|
+
L : float
|
|
505
|
+
The length of the member
|
|
506
|
+
eType : int
|
|
507
|
+
The member element type
|
|
508
|
+
|
|
509
|
+
Returns
|
|
510
|
+
-------
|
|
511
|
+
LoadCNL
|
|
512
|
+
Consistent Nodal Loads for this load type
|
|
513
|
+
"""
|
|
514
|
+
w1, w2, dw, a, c = self._resolve(L)
|
|
515
|
+
|
|
516
|
+
if c <= 0:
|
|
517
|
+
return LoadCNL(Va=0.0, Vb=0.0, Ma=0.0, Mb=0.0)
|
|
518
|
+
|
|
519
|
+
alpha = L - a # distance: load start → right beam end
|
|
520
|
+
# delta = L - a - c # distance: load end → right beam end (not used directly)
|
|
521
|
+
|
|
522
|
+
# --- Ma: ∫ w(x) · x · (L−x)² dx / L² ---
|
|
523
|
+
# Split into UDL(w1) integral I1 and triangular(dw) integral I2.
|
|
524
|
+
I1 = (
|
|
525
|
+
c**4 / 4
|
|
526
|
+
+ (a - 2 * alpha) * c**3 / 3
|
|
527
|
+
+ (alpha**2 - 2 * a * alpha) * c**2 / 2
|
|
528
|
+
+ a * alpha**2 * c
|
|
529
|
+
)
|
|
530
|
+
I2 = (
|
|
531
|
+
c**5 / 5
|
|
532
|
+
+ (a - 2 * alpha) * c**4 / 4
|
|
533
|
+
+ (alpha**2 - 2 * a * alpha) * c**3 / 3
|
|
534
|
+
+ a * alpha**2 * c**2 / 2
|
|
535
|
+
)
|
|
536
|
+
Ma = (w1 / L**2) * I1 + (dw / (c * L**2)) * I2
|
|
537
|
+
|
|
538
|
+
# --- Mb: −∫ w(x) · x² · (L−x) dx / L² ---
|
|
539
|
+
J1 = (
|
|
540
|
+
-(c**4) / 4
|
|
541
|
+
+ (alpha - 2 * a) * c**3 / 3
|
|
542
|
+
+ (2 * a * alpha - a**2) * c**2 / 2
|
|
543
|
+
+ a**2 * alpha * c
|
|
544
|
+
)
|
|
545
|
+
J2 = (
|
|
546
|
+
-(c**5) / 5
|
|
547
|
+
+ (alpha - 2 * a) * c**4 / 4
|
|
548
|
+
+ (2 * a * alpha - a**2) * c**3 / 3
|
|
549
|
+
+ a**2 * alpha * c**2 / 2
|
|
550
|
+
)
|
|
551
|
+
Mb = -((w1 / L**2) * J1 + (dw / (c * L**2)) * J2)
|
|
552
|
+
|
|
553
|
+
# --- Va: ∫ w(x) · (L−x)² · (2x+L) dx / L³ ---
|
|
554
|
+
K1 = (
|
|
555
|
+
c**4 / 2
|
|
556
|
+
+ (a - alpha) * c**3
|
|
557
|
+
- 3 * a * alpha * c**2
|
|
558
|
+
+ (3 * a + alpha) * alpha**2 * c
|
|
559
|
+
)
|
|
560
|
+
K2 = (
|
|
561
|
+
2 * c**5 / 5
|
|
562
|
+
+ 3 * (a - alpha) * c**4 / 4
|
|
563
|
+
- 2 * a * alpha * c**3
|
|
564
|
+
+ (3 * a + alpha) * alpha**2 * c**2 / 2
|
|
565
|
+
)
|
|
566
|
+
Va = (w1 / L**3) * K1 + (dw / (c * L**3)) * K2
|
|
567
|
+
|
|
568
|
+
Vb = (w1 + w2) * c / 2 - Va
|
|
569
|
+
|
|
570
|
+
return LoadCNL(Va=Va, Vb=Vb, Ma=Ma, Mb=Mb)
|
|
571
|
+
|
|
572
|
+
def get_mbr_results(self, x: np.ndarray, L: float) -> MemberResults:
|
|
573
|
+
"""
|
|
574
|
+
Simply-supported member results using Macaulay bracket integration.
|
|
575
|
+
|
|
576
|
+
The load ``w(x) = w1 + (w2−w1)·(x−a)/c`` for ``a ≤ x ≤ a+c`` is
|
|
577
|
+
integrated using Macaulay brackets at positions *a* and *b = a+c*.
|
|
578
|
+
|
|
579
|
+
Parameters
|
|
580
|
+
----------
|
|
581
|
+
x : np.ndarray
|
|
582
|
+
Vector of points along the length of the member
|
|
583
|
+
L : float
|
|
584
|
+
The length of the member
|
|
585
|
+
|
|
586
|
+
Returns
|
|
587
|
+
-------
|
|
588
|
+
res : MemberResults
|
|
589
|
+
A populated :class:`pycba.load.MemberResults` object
|
|
590
|
+
"""
|
|
591
|
+
npts = len(x)
|
|
592
|
+
res = MemberResults(vals=None, n=npts)
|
|
593
|
+
res.x = x
|
|
594
|
+
|
|
595
|
+
w1, w2, dw, a, c = self._resolve(L)
|
|
596
|
+
|
|
597
|
+
if c <= 0:
|
|
598
|
+
res.V = np.zeros(npts)
|
|
599
|
+
res.M = np.zeros(npts)
|
|
600
|
+
res.R = np.zeros(npts)
|
|
601
|
+
res.D = np.zeros(npts)
|
|
602
|
+
return res
|
|
603
|
+
|
|
604
|
+
b = a + c
|
|
605
|
+
alpha = L - a
|
|
606
|
+
delta = L - b
|
|
607
|
+
|
|
608
|
+
# Simply-supported reaction at left end (moment equilibrium about B)
|
|
609
|
+
Va = (
|
|
610
|
+
(w1 / 2) * alpha**2
|
|
611
|
+
+ (dw / (6 * c)) * alpha**3
|
|
612
|
+
- (w2 / 2) * delta**2
|
|
613
|
+
- (dw / (6 * c)) * delta**3
|
|
614
|
+
) / L
|
|
615
|
+
|
|
616
|
+
# Rotation integration constant (from D(0) = D(L) = 0)
|
|
617
|
+
Ra = (
|
|
618
|
+
-(Va / 6) * L**3
|
|
619
|
+
+ (w1 / 24) * alpha**4
|
|
620
|
+
+ (dw / (120 * c)) * alpha**5
|
|
621
|
+
- (w2 / 24) * delta**4
|
|
622
|
+
- (dw / (120 * c)) * delta**5
|
|
623
|
+
) / L
|
|
624
|
+
|
|
625
|
+
# Macaulay brackets at load start and end
|
|
626
|
+
MBa = self.MB(x - a)
|
|
627
|
+
MBb = self.MB(x - b)
|
|
628
|
+
|
|
629
|
+
res.V = (
|
|
630
|
+
Va
|
|
631
|
+
- w1 * MBa
|
|
632
|
+
- (dw / (2 * c)) * MBa**2
|
|
633
|
+
+ w2 * MBb
|
|
634
|
+
+ (dw / (2 * c)) * MBb**2
|
|
635
|
+
)
|
|
636
|
+
res.M = (
|
|
637
|
+
Va * x
|
|
638
|
+
- (w1 / 2) * MBa**2
|
|
639
|
+
- (dw / (6 * c)) * MBa**3
|
|
640
|
+
+ (w2 / 2) * MBb**2
|
|
641
|
+
+ (dw / (6 * c)) * MBb**3
|
|
642
|
+
)
|
|
643
|
+
res.R = (
|
|
644
|
+
(Va / 2) * x**2
|
|
645
|
+
- (w1 / 6) * MBa**3
|
|
646
|
+
- (dw / (24 * c)) * MBa**4
|
|
647
|
+
+ (w2 / 6) * MBb**3
|
|
648
|
+
+ (dw / (24 * c)) * MBb**4
|
|
649
|
+
+ Ra
|
|
650
|
+
)
|
|
651
|
+
res.D = (
|
|
652
|
+
(Va / 6) * x**3
|
|
653
|
+
- (w1 / 24) * MBa**4
|
|
654
|
+
- (dw / (120 * c)) * MBa**5
|
|
655
|
+
+ (w2 / 24) * MBb**4
|
|
656
|
+
+ (dw / (120 * c)) * MBb**5
|
|
657
|
+
+ Ra * x
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
res.V[0] = 0.0
|
|
661
|
+
res.V[npts - 1] = 0.0
|
|
662
|
+
res.M[0] = 0.0
|
|
663
|
+
res.M[npts - 1] = 0.0
|
|
664
|
+
|
|
665
|
+
return res
|
|
666
|
+
|
|
667
|
+
|
|
433
668
|
class LoadMaMb(Load):
|
|
434
669
|
"""
|
|
435
670
|
Member end moment loads
|
|
@@ -654,6 +889,13 @@ def parse_LM(LM: LoadMatrix) -> List[Load]:
|
|
|
654
889
|
m = load[2]
|
|
655
890
|
a = load[3]
|
|
656
891
|
loads.append(LoadML(span, m, a))
|
|
892
|
+
# Trapezoidal Load
|
|
893
|
+
elif ltype == 5:
|
|
894
|
+
w1 = load[2]
|
|
895
|
+
w2 = load[3]
|
|
896
|
+
a = load[4] if len(load) > 4 else 0.0
|
|
897
|
+
c = load[5] if len(load) > 5 else None
|
|
898
|
+
loads.append(LoadTrapez(span, w1, w2, a, c))
|
|
657
899
|
return loads
|
|
658
900
|
|
|
659
901
|
|
|
@@ -710,6 +952,11 @@ def factor_LM(LM: LoadMatrix, gamma: float) -> LoadMatrix:
|
|
|
710
952
|
LMnew.append([i_span, l_type, mag])
|
|
711
953
|
elif l_type == 2 or l_type == 4: # PL or ML
|
|
712
954
|
LMnew.append([i_span, l_type, mag, load[3]])
|
|
955
|
+
elif l_type == 5: # Trapezoidal
|
|
956
|
+
new_load = [i_span, l_type, mag, gamma * load[3]]
|
|
957
|
+
if len(load) > 4:
|
|
958
|
+
new_load.extend(load[4:]) # a, c are not factored
|
|
959
|
+
LMnew.append(new_load)
|
|
713
960
|
else: # PUDL
|
|
714
961
|
LMnew.append([i_span, l_type, mag, load[3], load[4]])
|
|
715
962
|
|