PyCBA 1.0.1__tar.gz → 1.0.2__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-1.0.1/src/PyCBA.egg-info → pycba-1.0.2}/PKG-INFO +1 -1
- {pycba-1.0.1 → pycba-1.0.2/src/PyCBA.egg-info}/PKG-INFO +1 -1
- {pycba-1.0.1 → pycba-1.0.2}/src/PyCBA.egg-info/SOURCES.txt +1 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/__init__.py +1 -1
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/beam.py +8 -11
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/load.py +11 -10
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/results.py +12 -0
- pycba-1.0.2/tests/test_double_release.py +80 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_results.py +25 -0
- {pycba-1.0.1 → pycba-1.0.2}/LICENSE +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/README.md +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/pyproject.toml +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/setup.cfg +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/setup.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/PyCBA.egg-info/dependency_links.txt +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/PyCBA.egg-info/requires.txt +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/PyCBA.egg-info/top_level.txt +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/analysis.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/bridge.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/foundation.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/inf_lines.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/load_cases.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/modal.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/nonlinear.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/pattern.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/plotting.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/prestress.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/render.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/section.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/types.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/units.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/utils.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/src/pycba/vehicle.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_additive_pattern.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_basic.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_bridge.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_code_load_models.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_foundation.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_inf_lines.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_loadpattern_ergonomics.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_member_naming.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_member_type.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_modal.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_moving_load.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_moving_udl.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_nonlinear.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_nonprismatic.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_plot_helpers.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_plotly.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_prestress.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_reaction_envelope.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_reactions.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_render.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_results_export.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_shear_points.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_supports.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_timoshenko.py +0 -0
- {pycba-1.0.1 → pycba-1.0.2}/tests/test_units.py +0 -0
|
@@ -847,14 +847,11 @@ class Beam:
|
|
|
847
847
|
)
|
|
848
848
|
|
|
849
849
|
if eType == 4:
|
|
850
|
-
#
|
|
851
|
-
#
|
|
852
|
-
|
|
853
|
-
# so the constant-EI limit is reproduced.
|
|
854
|
-
Va_ff, Vb_ff = ref[0], ref[2]
|
|
855
|
-
ref[0] = Va_ff + (Ma + Mb) / L
|
|
850
|
+
# Both rotations are free: reactions follow statics, independent
|
|
851
|
+
# of the stiffness distribution and its fixed-end moments.
|
|
852
|
+
ref[0] = Va_ss
|
|
856
853
|
ref[1] = 0.0
|
|
857
|
-
ref[2] =
|
|
854
|
+
ref[2] = Vb_ss
|
|
858
855
|
ref[3] = 0.0
|
|
859
856
|
elif eType in (2, 3):
|
|
860
857
|
# Single moment release: condense the released rotational DOF so
|
|
@@ -1605,11 +1602,11 @@ class Beam:
|
|
|
1605
1602
|
)
|
|
1606
1603
|
|
|
1607
1604
|
if eType == 4:
|
|
1608
|
-
#
|
|
1609
|
-
|
|
1610
|
-
ref[0] =
|
|
1605
|
+
# Both-end releases leave the simply-supported reactions;
|
|
1606
|
+
# shear flexibility must not change this statically determinate case.
|
|
1607
|
+
ref[0] = Va_ss
|
|
1611
1608
|
ref[1] = 0.0
|
|
1612
|
-
ref[2] =
|
|
1609
|
+
ref[2] = Vb_ss
|
|
1613
1610
|
ref[3] = 0.0
|
|
1614
1611
|
elif eType in (2, 3):
|
|
1615
1612
|
# Single moment release: condense the released rotational DOF using
|
|
@@ -112,16 +112,17 @@ class Load:
|
|
|
112
112
|
ref[3] = 1.0 * cnl.Mb
|
|
113
113
|
elif eType == 3: # DOF = moment at i node
|
|
114
114
|
ref[0] = fm * cnl.Ma
|
|
115
|
-
ref[1] = 1.0 * cnl.Ma
|
|
116
|
-
ref[2] = -fm * cnl.Ma
|
|
117
|
-
ref[3] = 0.5 * cnl.Ma
|
|
118
|
-
elif eType == 4: # keep only vertical, remove moments
|
|
119
|
-
|
|
120
|
-
ref[
|
|
121
|
-
ref[
|
|
122
|
-
ref[
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
ref[1] = 1.0 * cnl.Ma
|
|
116
|
+
ref[2] = -fm * cnl.Ma
|
|
117
|
+
ref[3] = 0.5 * cnl.Ma
|
|
118
|
+
elif eType == 4: # keep only vertical, remove moments
|
|
119
|
+
# Static condensation transfers the end-moment couple to shear.
|
|
120
|
+
ref[0] = (cnl.Ma + cnl.Mb) / L
|
|
121
|
+
ref[1] = 1.0 * cnl.Ma
|
|
122
|
+
ref[2] = -(cnl.Ma + cnl.Mb) / L
|
|
123
|
+
ref[3] = 1.0 * cnl.Mb
|
|
124
|
+
else:
|
|
125
|
+
# no nothing if it is FF
|
|
125
126
|
pass
|
|
126
127
|
# now superimpose the released forces
|
|
127
128
|
return LoadCNL(
|
|
@@ -291,6 +291,18 @@ class BeamResults:
|
|
|
291
291
|
res.R[1:-1] = psi
|
|
292
292
|
res.D[1:-1] = D
|
|
293
293
|
|
|
294
|
+
# The padding stations at index 0 and -1 duplicate the true end
|
|
295
|
+
# stations (x[1] and x[-2]) so that M and V can show their
|
|
296
|
+
# discontinuity at a support/point load. Rotation and deflection
|
|
297
|
+
# have no such discontinuity, so the padding should carry the same
|
|
298
|
+
# (properly computed) value as the true end station, not the raw
|
|
299
|
+
# per-load superposition value left over from before ``psi``/``D``
|
|
300
|
+
# were computed above.
|
|
301
|
+
res.R[0] = psi[0]
|
|
302
|
+
res.R[-1] = psi[-1]
|
|
303
|
+
res.D[0] = D[0]
|
|
304
|
+
res.D[-1] = D[-1]
|
|
305
|
+
|
|
294
306
|
return res
|
|
295
307
|
|
|
296
308
|
def at(self, x: float, attrs: Tuple[str, ...] = ("M", "V", "R", "D")) -> Dict:
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Release regressions anchored to statics, not agreement between code paths."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pytest
|
|
4
|
+
import pycba as cba
|
|
5
|
+
from pycba.load import LoadPL, LoadUDL, LoadPUDL
|
|
6
|
+
from pycba.section import SectionEI
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.parametrize("length", [3.0, 10.0, 27.0])
|
|
10
|
+
@pytest.mark.parametrize("fraction", [0.2, 0.5, 0.8])
|
|
11
|
+
@pytest.mark.parametrize("magnitude", [100.0, -40.0])
|
|
12
|
+
@pytest.mark.parametrize("path", ["prismatic", "nonprismatic", "timoshenko"])
|
|
13
|
+
def test_double_release_point_load_statics(length, fraction, magnitude, path):
|
|
14
|
+
beam = cba.Beam()
|
|
15
|
+
load = LoadPL(0, magnitude, fraction * length)
|
|
16
|
+
if path == "prismatic":
|
|
17
|
+
actual = load.get_ref(length, 4)
|
|
18
|
+
elif path == "nonprismatic":
|
|
19
|
+
section = SectionEI([("linear", [0.0, length], [1e5, 3e5])])
|
|
20
|
+
actual = beam._ref_nonprismatic(load, section, length, 4)
|
|
21
|
+
else:
|
|
22
|
+
actual = beam._ref_timoshenko(load, 1e5, 2.5e4, length, 4)
|
|
23
|
+
expected = [magnitude * (1 - fraction), 0.0, magnitude * fraction, 0.0]
|
|
24
|
+
np.testing.assert_allclose(actual, expected, rtol=1e-10, atol=1e-8)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize("etype", [1, 2, 3, 4])
|
|
28
|
+
@pytest.mark.parametrize(
|
|
29
|
+
"load",
|
|
30
|
+
[
|
|
31
|
+
LoadPL(0, 100.0, 2.0),
|
|
32
|
+
LoadPL(0, 100.0, 8.0),
|
|
33
|
+
LoadUDL(0, 20.0),
|
|
34
|
+
LoadPUDL(0, 20.0, 1.0, 3.0),
|
|
35
|
+
],
|
|
36
|
+
)
|
|
37
|
+
def test_release_vector_matches_matrix_condensation(etype, load):
|
|
38
|
+
# Independent test-only elimination of rotations from the unreleased matrix.
|
|
39
|
+
k = cba.Beam().k_FF(1e5, 10.0)
|
|
40
|
+
f = np.array(load.get_cnl(10.0, 1))
|
|
41
|
+
released = {1: [], 2: [3], 3: [1], 4: [1, 3]}[etype]
|
|
42
|
+
expected = f.copy()
|
|
43
|
+
if released:
|
|
44
|
+
retained = [i for i in range(4) if i not in released]
|
|
45
|
+
expected[retained] -= k[np.ix_(retained, released)] @ np.linalg.solve(
|
|
46
|
+
k[np.ix_(released, released)], f[released]
|
|
47
|
+
)
|
|
48
|
+
expected[released] = 0.0
|
|
49
|
+
np.testing.assert_allclose(
|
|
50
|
+
load.get_ref(10.0, etype), expected, rtol=1e-10, atol=1e-8
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.parametrize("path", ["prismatic", "nonprismatic", "timoshenko"])
|
|
55
|
+
@pytest.mark.parametrize("a", [2.0, 5.0, 8.0])
|
|
56
|
+
def test_suspended_span_global_equilibrium(path, a):
|
|
57
|
+
lengths = [3.0, 10.0, 7.0]
|
|
58
|
+
ei = [2e5, 1e5, 4e5]
|
|
59
|
+
kwargs = {}
|
|
60
|
+
if path == "nonprismatic":
|
|
61
|
+
ei[1] = SectionEI([("linear", [0.0, 10.0], [1e5, 3e5])])
|
|
62
|
+
elif path == "timoshenko":
|
|
63
|
+
kwargs["GAv"] = 2.5e4
|
|
64
|
+
analysis = cba.BeamAnalysis(
|
|
65
|
+
lengths,
|
|
66
|
+
ei,
|
|
67
|
+
[-1, -1, 0, 0, 0, 0, -1, -1],
|
|
68
|
+
[[2, 2, 100.0, a, 0]],
|
|
69
|
+
eletype=[1, 4, 1],
|
|
70
|
+
**kwargs,
|
|
71
|
+
)
|
|
72
|
+
analysis.analyze()
|
|
73
|
+
va, vb = 100.0 * (1 - a / 10.0), 100.0 * a / 10.0
|
|
74
|
+
actual = analysis.beam_results.R
|
|
75
|
+
np.testing.assert_allclose(
|
|
76
|
+
actual, [va, 3.0 * va, vb, -7.0 * vb], rtol=1e-10, atol=1e-8
|
|
77
|
+
)
|
|
78
|
+
ra, ma, rb, mb = actual
|
|
79
|
+
assert ra + rb == pytest.approx(100.0, abs=1e-8)
|
|
80
|
+
assert ma + mb + 20.0 * rb == pytest.approx(100.0 * (3.0 + a), abs=1e-8)
|
|
@@ -81,3 +81,28 @@ def test_sum_incompatible_envelopes():
|
|
|
81
81
|
env_sum = cba.Envelopes.zero_like(env1)
|
|
82
82
|
with pytest.raises(ValueError, match="Cannot sum with an inconsistent envelope"):
|
|
83
83
|
env_sum.sum(env2)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_at_rotation_at_supports_matches_analytical():
|
|
87
|
+
"""Regression test for at() returning the wrong rotation at a member's
|
|
88
|
+
end stations. A simply supported beam with a central point load has a
|
|
89
|
+
well known closed form support rotation of P*L**2 / (16*EI).
|
|
90
|
+
"""
|
|
91
|
+
L = 10.0
|
|
92
|
+
P = 100.0
|
|
93
|
+
EI = 30 * 10e9 * 1e-6
|
|
94
|
+
|
|
95
|
+
beam_analysis = cba.BeamAnalysis([L], EI, supports=["pinned", "roller"])
|
|
96
|
+
beam_analysis.set_loads([[1, 2, P, L / 2, 0]])
|
|
97
|
+
beam_analysis.analyze()
|
|
98
|
+
|
|
99
|
+
theta = P * L**2 / (16 * EI)
|
|
100
|
+
|
|
101
|
+
assert beam_analysis.at(0.0)["R"] == pytest.approx(-theta)
|
|
102
|
+
assert beam_analysis.at(L)["R"] == pytest.approx(theta)
|
|
103
|
+
|
|
104
|
+
# The padding stations at a member's ends duplicate the true end
|
|
105
|
+
# stations, so both copies should agree.
|
|
106
|
+
r = beam_analysis.beam_results.results
|
|
107
|
+
assert r.R[0] == pytest.approx(r.R[1])
|
|
108
|
+
assert r.R[-2] == pytest.approx(r.R[-1])
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|