def __add__(self, other: Element) -> Element: if self.id != other.id: raise FEMException("Wrong element:", "only elements with the same id can be added.") el = copy.deepcopy(self) for unit in [ "bending_moment", "shear_force", "deflection", "extension", "N_1", "N_2", ]: if getattr(el, unit) is None: setattr(el, unit, getattr(other, unit)) else: setattr(el, unit, getattr(el, unit) + getattr(other, unit)) el.max_deflection = (other.max_deflection if el.max_deflection is None else max(el.max_deflection, other.max_deflection)) el.node_map[self.node_id1] = el.node_1 + other.node_1 el.node_map[self.node_id2] = el.node_2 + other.node_2 el.node_map[self.node_id1].ux = el.node_1.ux + other.node_1.ux el.node_map[self.node_id1].uz = el.node_1.uz + other.node_1.uz el.node_map[self.node_id1].phi_y = el.node_1.phi_y + other.node_1.phi_y el.node_map[self.node_id2].ux = el.node_2.ux + other.node_2.ux el.node_map[self.node_id2].uz = el.node_2.uz + other.node_2.uz el.node_map[self.node_id2].phi_y = el.node_2.phi_y + other.node_2.phi_y return el
def __add__(self, other): if self.id != other.id: raise FEMException('Wrong element:', 'only elements with the same id can be added.') el = copy.deepcopy(self) for unit in [ 'bending_moment', 'shear_force', 'deflection', 'extension', 'N_1', 'N_2' ]: if getattr(el, unit) is None: setattr(el, unit, getattr(other, unit)) else: setattr(el, unit, getattr(el, unit) + getattr(other, unit)) el.max_deflection = other.max_deflection if el.max_deflection is None else \ max(el.max_deflection, other.max_deflection) el.node_map[self.node_id1] = el.node_1 + other.node_1 el.node_map[self.node_id2] = el.node_2 + other.node_2 el.node_map[self.node_id1].ux = el.node_1.ux + other.node_1.ux el.node_map[self.node_id1].uz = el.node_1.uz + other.node_1.uz el.node_map[self.node_id1].phi_y = el.node_1.phi_y + other.node_1.phi_y el.node_map[self.node_id2].ux = el.node_2.ux + other.node_2.ux el.node_map[self.node_id2].uz = el.node_2.uz + other.node_2.uz el.node_map[self.node_id2].phi_y = el.node_2.phi_y + other.node_2.phi_y return el
def all_q_load(self): if self.q_load is None: q = 0 else: if self.q_direction == "x": q_factor = -sin(self.angle) elif self.q_direction == "y": q_factor = cos(self.angle) elif self.q_direction == "element" or self.q_direction is None: q_factor = 1 elif self.q_direction is not None: raise FEMException('Wrong parameters', "q-loads direction is not set property. Please choose 'x', 'y', or 'element'") q = self.q_load * q_factor return q + self.dead_load * cos(self.angle)
def all_q_load(self) -> float: if self.q_load is None: q = (0, 0) else: if self.q_direction == "x": q_factor = -sin(self.angle) elif self.q_direction == "y": q_factor = cos(self.angle) elif self.q_direction == "element" or self.q_direction is None: q_factor = 1 elif self.q_direction is not None: raise FEMException( "Wrong parameters", "q-loads direction is not set property. Please choose 'x', 'y', or 'element'", ) q = [i * q_factor for i in self.q_load] return [i + self.dead_load * cos(self.angle) for i in q]
def all_q_load(self) -> Union[float, Sequence[float]]: q_factor = 0 if self.q_load is None: q = self.dead_load * cos(self.angle) else: if self.q_direction == "x": q_factor = -sin(self.angle) elif self.q_direction == "y": q_factor = cos(self.angle) elif self.q_direction == "element" or self.q_direction is None: q_factor = 1 elif self.q_direction is not None: raise FEMException( "Wrong parameters", "q-loads direction is not set property. Please choose 'x', 'y', or 'element'", ) if isinstance(self.q_load, (float, int)): q = self.q_load * q_factor + self.dead_load * cos(self.angle) elif isinstance(self.q_load, list): q = [ q_l * q_factor + self.dead_load * cos(self.angle) for q_l in self.q_load ] return q
def solve( self, force_linear=False, verbosity=0, max_iter=200, geometrical_non_linear=False, **kwargs ): """ Compute the results of current model. :param force_linear: (bool) Force a linear calculation. Even when the system has non linear nodes. :param verbosity: (int) 0. Log calculation outputs. 1. silence. :param max_iter: (int) Maximum allowed iterations. :param geometrical_non_linear: (bool) Calculate second order effects and determine the buckling factor. :return: (array) Displacements vector. Development **kwargs: :param naked: (bool) Whether or not to run the solve function without doing post processing. :param discretize_kwargs: When doing a geometric non linear analysis you can reduce or increase the number of elements created that are used for determining the buckling_factor """ # kwargs: arguments for the iterative solver callers such as the _stiffness_adaptation method. # naked (bool) Default = False, if True force lines won't be computed. if self.system_displacement_vector is None: system_components.assembly.process_supports(self) naked = kwargs.get("naked", False) if not naked: if not self.validate(): if all( ["general" in element.type for element in self.element_map.values()] ): raise FEMException( "StabilityError", "The eigenvalues of the stiffness matrix are non zero, " "which indicates a instable structure. " "Check your support conditions", ) # (Re)set force vectors for el in self.element_map.values(): el.reset() system_components.assembly.prep_matrix_forces(self) assert ( self.system_force_vector is not None ), "There are no forces on the structure" if self.non_linear and not force_linear: return system_components.solver.stiffness_adaptation( self, verbosity, max_iter ) system_components.assembly.assemble_system_matrix(self) if geometrical_non_linear: discretize_kwargs = kwargs.get("discretize_kwargs", None) self.buckling_factor = system_components.solver.geometrically_non_linear( self, verbosity, discretize_kwargs=discretize_kwargs ) return self.system_displacement_vector system_components.assembly.process_conditions(self) # solution of the reduced system (reduced due to support conditions) reduced_displacement_vector = np.linalg.solve( self.reduced_system_matrix, self.reduced_force_vector ) # add the solution of the reduced system in the complete system displacement vector self.system_displacement_vector = np.zeros(self.shape_system_matrix) np.put( self.system_displacement_vector, self._remainder_indexes, reduced_displacement_vector, ) # determine the displacement vector of the elements for el in self.element_map.values(): index_node_1 = (el.node_1.id - 1) * 3 index_node_2 = (el.node_2.id - 1) * 3 # node 1 ux, uz, phi el.element_displacement_vector[:3] = self.system_displacement_vector[ index_node_1 : index_node_1 + 3 ] # node 2 ux, uz, phi el.element_displacement_vector[3:] = self.system_displacement_vector[ index_node_2 : index_node_2 + 3 ] el.determine_force_vector() if not naked: # determining the node results in post processing class self.post_processor.node_results_elements() self.post_processor.node_results_system() self.post_processor.reaction_forces() self.post_processor.element_results() # check the values in the displacement vector for extreme values, indicating a flawed calculation assert np.any(self.system_displacement_vector < 1e6), ( "The displacements of the structure exceed 1e6. " "Check your support conditions," "or your elements Young's modulus" ) return self.system_displacement_vector
def add_multiple_elements( self, location, n=None, dl=None, EA=None, EI=None, g=0, mp=None, spring=None, **kwargs ): """ Add multiple elements defined by the first and the last point. :param location: See 'add_element' method :param n: (int) Number of elements. :param dl: (flt) Distance between the elements nodes. :param EA: See 'add_element' method :param EI: See 'add_element' method :param g: See 'add_element' method :param mp: See 'add_element' method :param spring: See 'add_element' method **Keyword Args:** :param element_type: (str) See 'add_element' method :param first: (dict) Different arguments for the first element :param last: (dict) Different arguments for the last element :param steelsection: (str) Steel section name like IPE 300 :param orient: (str) Steel section axis for moment of inertia - 'y' and 'z' possible :param b: (flt) Width of generic rectangle section :param h: (flt) Height of generic rectangle section :param d: (flt) Diameter of generic circle section :param sw: (boolean) If true self weight of section is considered as dead load :param E: (flt) Modulus of elasticity for section material :param gamma: (flt) Weight of section material per volume unit. [kN/m3] / [N/m3]s :Example: .. code-block:: python last={'EA': 1e3, 'mp': 290} :return: (list) Element IDs """ first = kwargs.get("first", {}) last = kwargs.get("last", {}) element_type = kwargs.get("element_type", "general") for el in (first, last): if "EA" not in el: el["EA"] = EA if "EI" not in el: el["EI"] = EI if "g" not in el: el["g"] = g if "mp" not in el: el["mp"] = mp if "spring" not in el: el["spring"] = spring if "element_type" not in el: el["element_type"] = element_type point_1, point_2 = system_components.util.det_vertices(self, location) length = (point_2 - point_1).modulus() direction = (point_2 - point_1).unit() if type(n) == type(dl): raise FEMException( "Wrong parameters", "One, and only one, of n and dl should be passed as argument.", ) elif n: dl = length / n point = point_1 + direction * dl elements = [ self.add_element( (point_1, point), first["EA"], first["EI"], first["g"], first["mp"], first["spring"], element_type=first["element_type"], **kwargs ) ] l = 2 * dl while l < length: point += direction * dl elements.append( self.add_element( point, EA, EI, g, mp, spring, element_type=element_type, **kwargs ) ) l += dl elements.append( self.add_element( point_2, last["EA"], last["EI"], last["g"], last["mp"], last["spring"], element_type=last["element_type"], **kwargs ) ) return elements
def support_check(system, node_id): if system.node_map[node_id].hinge: raise FEMException( "Flawed inputs", "You cannot add a rotation-restraining support to a hinged node.", )
def _support_check(self, node_id): if self.node_map[node_id].hinge: raise FEMException("Flawed inputs", "You cannot add a support to a hinged node.")
def add_multiple_elements(self, location, n=None, dl=None, EA=None, EI=None, g=0, mp=None, spring=None, **kwargs): """ Add multiple elements defined by the first and the last point. :param location: See 'add_element' method :param n: (int) Number of elements. :param dl: (flt) Distance between the elements nodes. :param EA: See 'add_element' method :param EI: See 'add_element' method :param g: See 'add_element' method :param mp: See 'add_element' method :param spring: See 'add_element' method **Keyword Args:** :param element_type: (str) See 'add_element' method :param first: (dict) Different arguments for the first element :param last: (dict) Different arguments for the last element :Example: .. code-block:: python last={'EA': 1e3, 'mp': 290} :return: (list) Element IDs """ first = kwargs.get("first", {}) last = kwargs.get("last", {}) element_type = kwargs.get("element_type", "general") for el in (first, last): if "EA" not in el: el["EA"] = EA if "EI" not in el: el["EI"] = EI if "g" not in el: el["g"] = g if "mp" not in el: el["mp"] = mp if "spring" not in el: el["spring"] = spring if "element_type" not in el: el["element_type"] = element_type point_1, point_2 = self._det_vertices(location) length = (point_2 - point_1).modulus() direction = (point_2 - point_1).unit() if type(n) == type(dl): raise FEMException( "Wrong parameters", "One, and only one, of n and dl should be passed as argument.") elif n: dl = length / n point = point_1 + direction * dl elements = [ self.add_element((point_1, point), first["EA"], first["EI"], first["g"], first["mp"], first["spring"], element_type=first["element_type"]) ] l = 2 * dl while l < length: point += direction * dl elements.append( self.add_element(point, EA, EI, g, mp, spring, element_type=element_type)) l += dl elements.append( self.add_element(point_2, last["EA"], last["EI"], last["g"], last["mp"], last["spring"], element_type=last["element_type"])) return elements