Source code for anastruct.fem.system_components.util

from typing import TYPE_CHECKING, Optional, Sequence, Tuple, Union

import numpy as np

from anastruct.basic import FEMException, angle_x_axis
from anastruct.fem.node import Node
from anastruct.vertex import Vertex

if TYPE_CHECKING:
    from anastruct._types import VertexLike
    from anastruct.fem.system import MpType, Spring, SystemElements


[docs] def check_internal_hinges(system: "SystemElements", node_id: int) -> None: """Identify internal hinges, set their hinge status Args: system (SystemElements): System in which the node is located node_id (int): Id of the node in the system """ node = system.node_map[node_id] hinges = [] for el_id in node.elements: el = system.element_map[el_id] assert el.springs is not None if node_id == el.node_id1 and 1 in el.springs and el.springs[1] == 0: hinges.append(1) elif node_id == el.node_id2 and 2 in el.springs and el.springs[2] == 0: hinges.append(1) else: hinges.append(0) # If at least one element is connected # and no more than one element is rigidly connected if (1 < len(hinges) <= 1 + sum(hinges)) or (len(hinges) == 1 and sum(hinges) == 1): system.internal_hinges.append(node) if node in system.internal_hinges: node.hinge = True # If the elements aren't already all hinged, then set them to be if len(hinges) - sum(hinges) >= 1: for el_id in node.elements: el = system.element_map[el_id] assert el.springs is not None if node_id == el.node_id1 and not ( 1 in el.springs and el.springs[1] == 0 ): el.springs.update({1: 0}) if node_id == el.node_id2 and not ( 2 in el.springs and el.springs[2] == 0 ): el.springs.update({2: 0}) system.element_map[el_id].springs = el.springs
[docs] def append_node_id( system: "SystemElements", point_1: Vertex, point_2: Vertex, node_id1: int, node_id2: int, ) -> None: """Append the node id to the system if it is not already in the system Args: system (SystemElements): System in which the node is located point_1 (Vertex): Vertex of the first point point_2 (Vertex): Vertex of the second point node_id1 (int): Node id of the first point node_id2 (int): Node id of the second point """ if node_id1 not in system.node_map: system.node_map[node_id1] = Node(node_id1, vertex=point_1) if node_id2 not in system.node_map: system.node_map[node_id2] = Node(node_id2, vertex=point_2)
[docs] def remove_node_id( system: "SystemElements", node_id: int, ) -> None: """Remove the node id from the system Note that this function does NOT check if the node is still used in the system. It is intended to be used as a helper function to `remove_element()` and others, not to be used directly by users. Args: system (SystemElements): System in which the node is located node_id (int): Node id of the node """ node = system.node_map[node_id] system._vertices.pop(system.node_map[node_id].vertex) system.node_map.pop(node_id) if node_id in system.loads_point: system.loads_point.pop(node_id) if node_id in system.loads_moment: system.loads_moment.pop(node_id) if node in system.supports_fixed: system.supports_fixed.remove(node) if node in system.supports_hinged: system.supports_hinged.remove(node) if node in system.supports_rotational: system.supports_rotational.remove(node) if node in system.internal_hinges: system.internal_hinges.remove(node) if node in system.supports_roll: ind = system.supports_roll.index(node) system.supports_roll.remove(node) system.supports_roll_direction.pop(ind) system.supports_roll_rotate.pop(ind) if node_id in system.inclined_roll: system.inclined_roll.pop(node_id) if node in [item[0] for item in system.supports_spring_x]: ind = [item[0] for item in system.supports_spring_x].index(node) system.supports_spring_x.pop(ind) if node in [item[0] for item in system.supports_spring_y]: ind = [item[0] for item in system.supports_spring_y].index(node) system.supports_spring_y.pop(ind) if node in [item[0] for item in system.supports_spring_z]: ind = [item[0] for item in system.supports_spring_z].index(node) system.supports_spring_z.pop(ind) if node_id in [item[0] for item in system.supports_spring_args]: ind = [item[0] for item in system.supports_spring_args].index(node_id) system.supports_spring_args.pop(ind)
[docs] def det_vertices( system: "SystemElements", location_list: Union[ "VertexLike", Sequence["VertexLike"], ], ) -> Tuple[Vertex, Vertex]: """Determine the vertices of a location list Args: system (SystemElements): System in which the nodes are located location_list (Union[ Vertex, Sequence[Vertex], Sequence[int], Sequence[float], Sequence[Sequence[float]], ]): List of one or two locations Raises: FEMException: Raised when the location list is not a list of two points, a list of two coordinates, or a list of two lists of two coordinates. Returns: Tuple[Vertex, Vertex]: Tuple of two vertices """ if isinstance(location_list, Vertex): point_1 = system._previous_point point_2 = location_list elif isinstance(location_list, Sequence) and len(location_list) == 1: point_1 = system._previous_point point_2 = Vertex(location_list[0]) elif ( isinstance(location_list, Sequence) and len(location_list) == 2 and isinstance(location_list[0], (float, int, np.number)) and isinstance(location_list[1], (float, int, np.number)) ): point_1 = system._previous_point point_2 = Vertex(location_list[0], location_list[1]) elif isinstance(location_list, Sequence) and len(location_list) == 2: point_1 = Vertex(location_list[0]) point_2 = Vertex(location_list[1]) else: raise FEMException( "Flawed inputs", "The location_list should be a list of two points, a list of two coordinates, " + "or a list of two lists of two coordinates.", ) system._previous_point = point_2 return point_1, point_2
[docs] def det_node_ids( system: "SystemElements", point_1: Vertex, point_2: Vertex ) -> Tuple[int, int]: """Determine the node ids of two points Args: system (SystemElements): System in which the nodes are located point_1 (Vertex): First point point_2 (Vertex): Second point Returns: Tuple[int, int]: Tuple of two node ids """ node_ids = [] for p in (point_1, point_2): # k = str(p) k = p if k in system._vertices: node_id = system._vertices[k] else: node_id = len(system._vertices) + 1 system._vertices[k] = node_id node_ids.append(node_id) return node_ids[0], node_ids[1]
[docs] def add_node( system: "SystemElements", point: Vertex, node_id: Optional[int] = None ) -> int: """Add a node, optionally with a specific ID, without adding an element Args: system (SystemElements): System in which the nodes are located point (Vertex): Location of the node node_id (Optional[int], optional): node_id to assign to the node. Defaults to None, which means to use the first available node_id automatically. Raises: FEMException: Raised when the location is already assigned to a different node id. FEMException: Raised when the node id is already assigned to a different location. Returns: int: The node id of the added (or existing) node """ if point in system._vertices: if node_id is not None: existing_node_id = system._vertices[point] if existing_node_id != node_id: raise FEMException( "Flawed inputs", f"Location {point} is already assigned to node id {existing_node_id}, " f"cannot assign to node id {node_id}.", ) return existing_node_id if node_id is None: node_id = max(system.node_map.keys(), default=0) + 1 elif node_id in system.node_map and system.node_map[node_id].vertex != point: raise FEMException( "Flawed inputs", f"Node id {node_id} is already assigned to a different location.", ) system._vertices[point] = node_id system.node_map[node_id] = Node(node_id, vertex=point) return node_id
[docs] def support_check(system: "SystemElements", node_id: int) -> None: """Check if the node is a hinge Args: system (SystemElements): System in which the node is located node_id (int): Node id of the node Raises: FEMException: Raised when the node is a hinge """ if system.node_map[node_id].hinge: raise FEMException( "Flawed inputs", "You cannot add a rotation-restraining support to a hinged node.", )
[docs] def force_elements_orientation( point_1: Vertex, point_2: Vertex, node_id1: int, node_id2: int, spring: Optional["Spring"], mp: Optional["MpType"], ) -> Tuple[ Vertex, Vertex, int, int, Optional["Spring"], Optional["MpType"], float, ]: """Force the elements to be in the first and the last quadrant of the unity circle. Meaning the first node is always left and the last node is always right. Or they are both on one vertical line. The angle of the element will thus always be between -90 till +90 degrees. Args: point_1 (Vertex): First point of the element point_2 (Vertex): Second point of the element node_id1 (int): Node id of the first point node_id2 (int): Node id of the second point spring (Optional[Spring]): Any spring releases of the element mp (Optional[MpType]): Any maximum plastic moments of the element Returns: Tuple[ Vertex, Vertex, int, int, Optional[Spring], Optional[MpType], float ]: Tuple of the first point, second point, first node id, second node id, spring releases, maximum plastic moments, and the angle of the element """ # determine the angle of the element with the global x-axis delta_x = point_2.x - point_1.x delta_y = point_2.y - point_1.y # minus sign to work with an opposite y-axis angle = angle_x_axis(delta_x, delta_y) if delta_x < 0: # switch points point_1, point_2 = point_2, point_1 node_id1, node_id2 = node_id2, node_id1 angle = angle_x_axis(-delta_x, -delta_y) if spring is not None: assert isinstance( spring, dict ), "The spring parameter should be a dictionary." if 1 in spring and 2 in spring: spring[1], spring[2] = spring[2], spring[1] elif 1 in spring: spring[2] = spring.pop(1) elif 2 in spring: spring[1] = spring.pop(2) if mp is not None: if 1 in mp and 2 in mp: mp[1], mp[2] = mp[2], mp[1] elif 1 in mp: mp[2] = mp.pop(1) elif 2 in mp: mp[1] = mp.pop(2) return point_1, point_2, node_id1, node_id2, spring, mp, angle