Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions anastruct/fem/system_components/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,11 @@ def assemble_system_matrix(
#
# thus with appending numbers in the system matrix: column = row

for i in range(len(system.element_map)):
element = system.element_map[i + 1]
# Iterate over the actual elements rather than assuming the element ids are
# a contiguous 1..N sequence: remove_element (e.g. via insert_node) can leave
# gaps in element_map, and assembly is an order-independent accumulation into
# the system matrix indexed by node id (see #308).
for element in system.element_map.values():
element_matrix = element.stiffness_matrix

# n1 and n2 are starting indexes of the rows and the columns for node 1 and node 2
Expand Down
45 changes: 45 additions & 0 deletions tests/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,51 @@ def it_retains_supports_and_loads(SS_20):
assert SS_20.element_map[4].q_load == approx((1.6, 2.205258))
assert SS_20.element_map[5].q_load == approx((2.205258, 3))

def context_insert_node_then_solve():
# insert_node deletes the split element and appends two new ones, leaving
# element_map with non-contiguous ids. The structure must still assemble
# and solve, giving the same result as the equivalent model built with
# that node defined up front (#308).
inserted = SystemElements(EA=15000, EI=5000, mesh=101)
inserted.add_element(location=[[0, 0], [10, 4]])
inserted.add_element(location=[[10, 4], [10, 0]])
inserted.add_support_hinged(node_id=1)
inserted.add_support_hinged(node_id=3)
inserted.q_load(q=5, element_id=1, direction="element")
inserted.point_load(node_id=2, Fx=4, Fy=0)
inserted.insert_node(element_id=2, factor=0.25)
inserted.point_load(node_id=4, Fx=8, Fy=0)
inserted.solve()

unsplit = SystemElements(EA=15000, EI=5000, mesh=101)
unsplit.add_element(location=[[0, 0], [10, 4]])
unsplit.add_element(location=[[10, 4], [10, 3]])
unsplit.add_element(location=[[10, 3], [10, 0]])
unsplit.add_support_hinged(node_id=1)
unsplit.add_support_hinged(node_id=4)
unsplit.q_load(q=5, element_id=1, direction="element")
unsplit.point_load(node_id=2, Fx=4, Fy=0)
unsplit.point_load(node_id=3, Fx=8, Fy=0)
unsplit.solve()

# the element ids are non-contiguous after the split
assert list(inserted.element_map.keys()) == [1, 3, 4]

def displacements_by_coordinate(system):
return {
(round(node.vertex.x, 6), round(node.vertex.y, 6)): (
system.get_node_displacements(node_id)["ux"],
system.get_node_displacements(node_id)["uy"],
)
for node_id, node in system.node_map.items()
}

inserted_disp = displacements_by_coordinate(inserted)
unsplit_disp = displacements_by_coordinate(unsplit)
assert set(inserted_disp) == set(unsplit_disp)
for coordinate, displacement in inserted_disp.items():
assert displacement == approx(unsplit_disp[coordinate])

def context_find_node_id():
# find_node_id() function using Example 8

Expand Down
Loading