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.
Files changed (58) hide show
  1. {pycba-1.0.1/src/PyCBA.egg-info → pycba-1.0.2}/PKG-INFO +1 -1
  2. {pycba-1.0.1 → pycba-1.0.2/src/PyCBA.egg-info}/PKG-INFO +1 -1
  3. {pycba-1.0.1 → pycba-1.0.2}/src/PyCBA.egg-info/SOURCES.txt +1 -0
  4. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/__init__.py +1 -1
  5. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/beam.py +8 -11
  6. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/load.py +11 -10
  7. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/results.py +12 -0
  8. pycba-1.0.2/tests/test_double_release.py +80 -0
  9. {pycba-1.0.1 → pycba-1.0.2}/tests/test_results.py +25 -0
  10. {pycba-1.0.1 → pycba-1.0.2}/LICENSE +0 -0
  11. {pycba-1.0.1 → pycba-1.0.2}/README.md +0 -0
  12. {pycba-1.0.1 → pycba-1.0.2}/pyproject.toml +0 -0
  13. {pycba-1.0.1 → pycba-1.0.2}/setup.cfg +0 -0
  14. {pycba-1.0.1 → pycba-1.0.2}/setup.py +0 -0
  15. {pycba-1.0.1 → pycba-1.0.2}/src/PyCBA.egg-info/dependency_links.txt +0 -0
  16. {pycba-1.0.1 → pycba-1.0.2}/src/PyCBA.egg-info/requires.txt +0 -0
  17. {pycba-1.0.1 → pycba-1.0.2}/src/PyCBA.egg-info/top_level.txt +0 -0
  18. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/analysis.py +0 -0
  19. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/bridge.py +0 -0
  20. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/foundation.py +0 -0
  21. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/inf_lines.py +0 -0
  22. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/load_cases.py +0 -0
  23. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/modal.py +0 -0
  24. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/nonlinear.py +0 -0
  25. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/pattern.py +0 -0
  26. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/plotting.py +0 -0
  27. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/prestress.py +0 -0
  28. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/render.py +0 -0
  29. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/section.py +0 -0
  30. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/types.py +0 -0
  31. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/units.py +0 -0
  32. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/utils.py +0 -0
  33. {pycba-1.0.1 → pycba-1.0.2}/src/pycba/vehicle.py +0 -0
  34. {pycba-1.0.1 → pycba-1.0.2}/tests/test_additive_pattern.py +0 -0
  35. {pycba-1.0.1 → pycba-1.0.2}/tests/test_basic.py +0 -0
  36. {pycba-1.0.1 → pycba-1.0.2}/tests/test_bridge.py +0 -0
  37. {pycba-1.0.1 → pycba-1.0.2}/tests/test_code_load_models.py +0 -0
  38. {pycba-1.0.1 → pycba-1.0.2}/tests/test_foundation.py +0 -0
  39. {pycba-1.0.1 → pycba-1.0.2}/tests/test_inf_lines.py +0 -0
  40. {pycba-1.0.1 → pycba-1.0.2}/tests/test_loadpattern_ergonomics.py +0 -0
  41. {pycba-1.0.1 → pycba-1.0.2}/tests/test_member_naming.py +0 -0
  42. {pycba-1.0.1 → pycba-1.0.2}/tests/test_member_type.py +0 -0
  43. {pycba-1.0.1 → pycba-1.0.2}/tests/test_modal.py +0 -0
  44. {pycba-1.0.1 → pycba-1.0.2}/tests/test_moving_load.py +0 -0
  45. {pycba-1.0.1 → pycba-1.0.2}/tests/test_moving_udl.py +0 -0
  46. {pycba-1.0.1 → pycba-1.0.2}/tests/test_nonlinear.py +0 -0
  47. {pycba-1.0.1 → pycba-1.0.2}/tests/test_nonprismatic.py +0 -0
  48. {pycba-1.0.1 → pycba-1.0.2}/tests/test_plot_helpers.py +0 -0
  49. {pycba-1.0.1 → pycba-1.0.2}/tests/test_plotly.py +0 -0
  50. {pycba-1.0.1 → pycba-1.0.2}/tests/test_prestress.py +0 -0
  51. {pycba-1.0.1 → pycba-1.0.2}/tests/test_reaction_envelope.py +0 -0
  52. {pycba-1.0.1 → pycba-1.0.2}/tests/test_reactions.py +0 -0
  53. {pycba-1.0.1 → pycba-1.0.2}/tests/test_render.py +0 -0
  54. {pycba-1.0.1 → pycba-1.0.2}/tests/test_results_export.py +0 -0
  55. {pycba-1.0.1 → pycba-1.0.2}/tests/test_shear_points.py +0 -0
  56. {pycba-1.0.1 → pycba-1.0.2}/tests/test_supports.py +0 -0
  57. {pycba-1.0.1 → pycba-1.0.2}/tests/test_timoshenko.py +0 -0
  58. {pycba-1.0.1 → pycba-1.0.2}/tests/test_units.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyCBA
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Python Continuous Beam Analysis
5
5
  Author-email: Colin Caprani <colin.caprani@monash.edu>
6
6
  License: AGPL-3.0-or-later
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyCBA
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Python Continuous Beam Analysis
5
5
  Author-email: Colin Caprani <colin.caprani@monash.edu>
6
6
  License: AGPL-3.0-or-later
@@ -31,6 +31,7 @@ tests/test_additive_pattern.py
31
31
  tests/test_basic.py
32
32
  tests/test_bridge.py
33
33
  tests/test_code_load_models.py
34
+ tests/test_double_release.py
34
35
  tests/test_foundation.py
35
36
  tests/test_inf_lines.py
36
37
  tests/test_loadpattern_ergonomics.py
@@ -2,7 +2,7 @@
2
2
  PyCBA - Continuous Beam Analysis in Python
3
3
  """
4
4
 
5
- __version__ = "1.0.1"
5
+ __version__ = "1.0.2"
6
6
 
7
7
  from .analysis import BeamAnalysis
8
8
  from .beam import Beam
@@ -847,14 +847,11 @@ class Beam:
847
847
  )
848
848
 
849
849
  if eType == 4:
850
- # Pinned-pinned: release both end moments. Mirror the prismatic
851
- # :meth:`pycba.load.Load.get_ref` convention exactly (the vertical
852
- # correction is ``(Ma + Mb)/L`` applied to the fixed-fixed CNL),
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] = Vb_ff - (Ma + Mb) / L
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
- # Pinned-pinned: release both end moments (mirror _ref_nonprismatic).
1609
- Va_ff, Vb_ff = ref[0], ref[2]
1610
- ref[0] = Va_ff + (Ma + Mb) / L
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] = Vb_ff - (Ma + Mb) / L
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
- ref[0] = -(cnl.Ma + cnl.Mb) / L
120
- ref[1] = 1.0 * cnl.Ma
121
- ref[2] = (cnl.Ma + cnl.Mb) / L
122
- ref[3] = 1.0 * cnl.Mb
123
- else:
124
- # no nothing if it is FF
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