Repository navigation
Conversation
|
@Ayberkrk - Nice change. Inverting the whole block makes sense to me for finite springs. One thing that might be worth a look while you're in here: for a hinge ( A quick check: adding Example and possible fixss = SystemElements(EI=1000, EA=1e9)
ss.add_element([[0, 0], [4, 0]])
ss.add_element([[4, 0], [8, 0]], spring={2: 0}) # try with and without the spring
ss.add_support_fixed(1); ss.add_support_roll(2, direction="x"); ss.add_support_hinged(3)
ss.moment_load(2, Ty=10); ss.solve()
print(ss.element_map[1].node_map[2].Tz, ss.element_map[2].node_map[2].Tz)Condensing before the zeroing in - if node_1_hinge or (spring is not None and 1 in spring and spring[1] == 0):
+ hinge_1 = node_1_hinge or (spring is not None and 1 in spring and spring[1] == 0)
+ hinge_2 = node_2_hinge or (spring is not None and 2 in spring and spring[2] == 0)
+ if hinge_1 and not hinge_2:
+ matrix[2][2] = 3 * EI / l
+ if hinge_2 and not hinge_1:
+ matrix[1][1] = 3 * EI / l
+ if hinge_1:
matrix[1][1] = matrix[1][2] = matrix[2][1] = 0
-
- if node_2_hinge or (spring is not None and 2 in spring and spring[2] == 0):
+ if hinge_2:
matrix[1][2] = matrix[2][1] = matrix[2][2] = 0 |
|
@mitchell-tesch thanks, great catch! You're right: releasing one end should condense its rotation out, so the other end gets 3EI/l, and my test had baked in the 4EI/l. I've pushed your suggested change. Your example now gives 5.714 / 4.286 with and without One knock-on effect: the stored solution for example 4 in |
Follow-up to #470.
Root cause
constitutive_matrix()describes the springs correctly in its comment: each rotational spring adds1/kto the diagonal of the element's rotational flexibility matrix. The code then inverts each stiffness term on its own (k_ii = 1 / (1/k_ii + 1/k)) instead of inverting the flexibility matrix. That is only exact when the off-diagonal coupling is zero. #470 fixed which term was touched and restored symmetry, but the term-by-term inversion remains, and for a spring softer than the element's own end stiffness the rotational block becomes indefinite. For EI = 15000, l = 4 and k = 2000 at one end its eigenvalues are about -1620 and 18385, sosolve()raises a stability error. For stiffer springs it solves but gives wrong displacements.Reproduction
A cantilever (EI = 15000, L = 4, P = 10) with a rotational spring k between the clamped support and the beam has the closed-form tip deflection P L³ / (3 EI) + P L² / k. A second case puts the spring at the end of a first element, followed by a second element of the same length and a tip load.
I also checked these cases and
examples/ex_7_rotational_spring.pyagainst an independent beam solver that models each spring as a separate rotational degree of freedom. All agree to about 1e-15 relative error.Fix
The rotational 2x2 block is inverted to a flexibility matrix,
1/kis added on the diagonal for each spring, and the result is inverted back. A hinge at the other end keeps its existing zeroed row and column; the spring then acts on the remaining scalar term. Axial stiffness is unchanged.Tests
tests/test_constitutive_matrix.pynow checks the block against the inverted flexibility matrix and checks it is symmetric positive definite for soft springs. It also has two structure-level tests against the closed-form deflections above. Four of these five tests fail on current master and all pass with this change. I replaced the assertions I added in Fix node 2 rotational spring corrupting the constitutive matrix #470, because they encoded the term-by-term behaviour.tests/test_e2e.pyis updated to the verified values (node 2 deflection 0.25070634 instead of 0.23558645).Side note: the
it_*checks inside thecontext_*blocks oftests/test_e2e.pyare not collected by pytest-describe with the defaultdescribe_prefixes, so they do not run today. That is why the outdated example 7 values went unnoticed. With-o describe_prefixes="describe context", all 45 of those checks pass on this branch except one unrelatedsystemfixture error. I left the configuration alone to keep this PR focused, and I'm happy to open a separate PR for it.black and pylint (10/10) pass and mypy reports the same errors as master. The full pytest suite passes apart from the six
test_plotter.pytests, which fail locally in the same way on master.