import numpy as np
from feastruct.fea.node import Node
[docs]class FiniteElementAnalysis:
"""Parent class for a finite element analysis.
Establishes a template for each different type of finite element analysis, e.g. frame,
membrane, plate etc. and provides a number of generic methods which are useful for all types of
analyses.
:cvar nodes: Nodes used in the finite element analysis
:vartype nodes: list[:class:`~feastruct.fea.node.Node`]
:cvar elements: Elements used in the finite element analysis
:vartype elements: list[:class:`~feastruct.fea.fea.FiniteElement`]
:cvar nfa: Node freedom arrangement
:vartype nfa: list[bool]
"""
[docs] def __init__(self, nodes, elements, nfa):
"""Inits the FiniteElementAnalysis class.
:param nodes: List of nodes with which to initialise the class
:type nodes: list[:class:`~feastruct.fea.node.Node`]
:param elements: List of elements with which to initialise the class
:type elements: list[:class:`~feastruct.fea.fea.FiniteElement`]
:param nfa: Node freedom arrangement
:type nfa: list[bool]
"""
if nodes is None:
self.nodes = []
else:
self.nodes = nodes
if elements is None:
self.elements = []
else:
self.elements = elements
self.nfa = nfa
[docs] def create_node(self, coords):
"""Creates a node and adds it to the Fea object.
Creates and returns a :class:`~feastruct.fea.node.Node` object and adds it to the
:class:`~feastruct.fea.fea.Fea` object.
:param coords: Cartesian coordinates of the node *([x], [x, y] or [x, y, z])*
:type coords: list[float]
:returns: Node object
:rtype: :class:`~feastruct.fea.node.Node`
"""
# TODO:catch any errors thrown by node creation
new_node = Node(coords)
self.nodes.append(new_node)
return new_node
[docs] def create_element(self, element):
"""Creates a finite element and adds it to the Fea object.
Creates and returns a :class:`~feastruct.fea.fea.FiniteElement` object ands adds it to the
:class:`~feastruct.fea.fea.Fea` object.
:param element: Element to be added to the analysis object
:type element: :class:`~feastruct.fea.fea.FiniteElement`
:returns: Element object
:rtype: :class:`~feastruct.fea.fea.FiniteElement`
"""
# TODO:catch any errors thrown by element creation
self.elements.append(element)
return element
[docs] def get_node_lims(self):
"""Finds and returns the minimum and maximum x, y and z values within the current analysis.
:returns: (xmin, xmax, ymin, ymax, zmin, zmax)
:rtype: tuple(float)
"""
for (i, node) in enumerate(self.nodes):
if i == 0:
xmin = node.x
xmax = node.x
ymin = node.y
ymax = node.y
zmin = node.z
zmax = node.z
xmin = min(xmin, node.x)
xmax = max(xmax, node.x)
ymin = min(ymin, node.y)
ymax = max(ymax, node.y)
zmin = min(zmin, node.z)
zmax = max(zmax, node.z)
return (xmin, xmax, ymin, ymax, zmin, zmax)
[docs]class FiniteElement:
"""Parent class for a finite element.
Establishes a template for each different type of finite element, e.g. frame element, membrane
element, plate element etc. and provides a number of generic methods which are useful for all
types of elements.
:cvar nodes: List of node objects that define the geometry of the finite element
:vartype nodes: list[:class:`~feastruct.fea.node.Node`]
:cvar material: Material object for the element
:vartype material: :class:`~feastruct.pre.material.Material`
:cvar efs: Element freedom signature
:vartype efs: list[bool]
:cvar f_int: List of internal force vector results stored for each analysis case
:vartype f_int: list[:class:`~feastruct.fea.fea.ForceVector`]
"""
[docs] def __init__(self, nodes, material, efs):
"""Inits the FiniteElement class.
:param nodes: List of node objects that define the geometry of the finite element
:type nodes: list[:class:`~feastruct.fea.node.Node`]
:param material: Material object for the element
:type material: :class:`~feastruct.pre.material.Material`
:param efs: Element freedom signature
:type efs: list[bool]
"""
self.nodes = nodes
self.material = material
self.efs = efs
self.f_int = []
[docs] def get_node_coords(self):
"""Returns a NumPy array of the cartesian coordinates defining the geometry of the finite
element.
:returns: An *(n x 3)* array of node coordinates, where *n* is the number of nodes for the
given finite element
:rtype: :class:`numpy.ndarray`
"""
coord_list = []
# loop through all nodes of the element
for node in self.nodes:
# append the node coordinates to the list
coord_list.append(node.coords)
return np.array(coord_list)
[docs] def get_ndof(self):
"""Returns the number of active degrees of freedom for the element.
:returns: Number of active degrees of freedom for the element
:rtype: int
"""
return len(self.nodes) * sum(self.efs)
[docs] def get_dofs(self):
"""Finds and returns a list of DoF objects corresponding to the degrees of freedom of the
finite element.
:returns: A list of DoF objects, with a length of *(n_nodes x n_dof)*
:rtype: list[list[:class:`~feastruct.fea.node.DoF`]]
"""
dof_list = []
# loop through all nodes of the element
for node in self.nodes:
# append the node dofs to the list
dof_list.append(node.get_dofs(freedom_signature=self.efs))
return dof_list
[docs] def get_gdof_nums(self):
"""Returns an array of global degree of freedom numbers corresponding to the degrees of
freedom of the finite element.
:returns: A integer array of global degrees of freedom, with a length of *(1 x n)*, where n
is *n_nodes x n_dof*
:rtype: :class:`numpy.ndarray`
"""
# get a list of dof objects for all nodes
dof_list = self.get_dofs()
# allocate a list of ints for the global
gdof_num_list = []
# loop through nodes in the dof list
for node in dof_list:
# loop through dofs in the current node
for dof in node:
gdof_num_list.append(dof.global_dof_num)
return np.array(gdof_num_list, dtype=np.int32)
[docs] def apply_nfa(self):
"""Applies the element freedom signature to all nodes in the element to generate a node
freedom allocation.
"""
# loop through all the nodes in the element
for node in self.nodes:
# loop through all the freedoms in the current node signature
for (i, nf) in enumerate(node.nfs):
# apply the element freedom signature
node.nfs[i] = nf or self.efs[i]
[docs] def get_nodal_displacements(self, analysis_case):
"""Returns an array of the nodal displacements for each degree of freedom in the finite
element for the analysis_case.
:param analysis_case: Analysis case relating to the displacement
:type analysis_case: :class:`~feastruct.fea.cases.AnalysisCase`
:returns: An *(n_nodes x n_dof)* array of degree of freedom displacements
:rtype: :class:`numpy.ndarray`
"""
disp_list = [] # allocate list of displacements
# get all the dof objects for the element
dof_list = self.get_dofs()
# loop through each node's dofs
for node_dofs in dof_list:
# list of displacements for the current node
node_list = []
# loop through each dof in the node
for dof in node_dofs:
# add the dof displacement to the node list
node_list.append(dof.get_displacement(analysis_case))
disp_list.append(node_list)
return np.array(disp_list)
[docs] def get_buckling_results(self, analysis_case, buckling_mode=0):
"""Returns the eigenvalue corresponding to the buckling analysis defined by the
analysis_case and the buckling_mode. Also returns an array of eigenvector values
corresponding to each degree of freedom in the finite element.
:param analysis_case: Analysis case relating to the buckling analysis
:type analysis_case: :class:`~feastruct.fea.cases.AnalysisCase`
:param int buckling_mode: Buckling mode number
:returns: Eigenvalue and eigenvectors *(w, v)*. A tuple containing the eigenvalue *w* and
an *(n_nodes x n_dof)* array of eigenvector values *v*.
:rtype: tuple(float, :class:`numpy.ndarray`)
"""
v_list = [] # allocate list of eigenvectors
# get all the dof objects for the element
dof_list = self.get_dofs()
# loop through each node's dofs
for node_dofs in dof_list:
# list of eigenvectors for the current node
node_list = []
# loop through each dof in the node
for dof in node_dofs:
(w, v) = dof.get_buckling_mode(
analysis_case=analysis_case, buckling_mode=buckling_mode)
# add the dof eigenvector to the node list
node_list.append(v)
v_list.append(node_list)
return (w, np.array(v_list))
[docs] def get_frequency_results(self, analysis_case, frequency_mode=0):
"""Returns the eigenvalue corresponding to the frequency analysis defined by the
analysis_case and the frequency_mode. Also returns an array of eigenvector values
corresponding to each degree of freedom in the finite element.
:param analysis_case: Analysis case relating to the frequency analysis
:type analysis_case: :class:`~feastruct.fea.cases.AnalysisCase`
:param int frequency_mode: Frequency mode number
:returns: Eigenvalue and eigenvectors *(w, v)*. A tuple containing the eigenvalue *w* and
an *(n_nodes x n_dof)* array of eigenvector values *v*.
:rtype: tuple(float, :class:`numpy.ndarray`)
"""
v_list = [] # allocate list of eigenvectors
# get all the dof objects for the element
dof_list = self.get_dofs()
# loop through each node's dofs
for node_dofs in dof_list:
# list of eigenvectors for the current node
node_list = []
# loop through each dof in the node
for dof in node_dofs:
(w, v) = dof.get_frequency_mode(
analysis_case=analysis_case, frequency_mode=frequency_mode)
# add the dof eigenvector to the node list
node_list.append(v)
v_list.append(node_list)
return (w, np.array(v_list))
[docs] def get_fint(self, analysis_case):
"""Returns the internal force vector relating to an
:class:`~feastruct.fea.cases.AnalysisCase`.
:param analysis_case: Analysis case relating to the internal force vector
:type analysis_case: :class:`~feastruct.fea.cases.AnalysisCase`
:returns: Internal force vector
:rtype: :class:`numpy.ndarray`
:raises Exception: If the force vector cannot be found for the analysis_case
"""
for f_int in self.f_int:
if analysis_case == f_int.analysis_case:
return f_int.f
# if nothing is found
str = 'Force vector corresponding to element {0}'.format(self)
str += ' could not be found for analysis case {0}'.format(analysis_case)
raise Exception(str)
[docs] def save_fint(self, f, analysis_case):
"""Adds an internal force vector result to the :class:`~feastruct.fea.fea.FiniteElement`.
:param f: Internal force vector
:type f: :class:`numpy.ndarray`
:param analysis_case: Analysis case relating to the internal force vector
:type analysis_case: :class:`~feastruct.fea.cases.AnalysisCase`
"""
self.f_int.append(ForceVector(f, analysis_case))
[docs] def get_shape_function(self, xi):
"""Placeholder for the get_shape_function method.
Returns the value of the shape functions at *xi*.
:param float xi: Position along the element
:returns: Value of the shape functions at *xi*
:rtype: :class:`numpy.ndarray`
"""
pass
[docs] def get_stiffness_matrix(self):
"""Placeholder for the get_stiffness_matrix method.
Gets the stiffness matrix for a FiniteElement.
:returns: 6 x 6 element stiffness matrix
:rtype: :class:`numpy.ndarray`
"""
pass
[docs] def get_geometric_stiff_matrix(self, analysis_case):
"""Placeholder for the get_geometric_stiff_matrix method.
Gets the geometric stiffness matrix for a FiniteElement.
:param analysis_case: Analysis case from which to extract the axial force
:type analysis_case: :class:`~feastruct.fea.cases.AnalysisCase`
:returns: Element geometric stiffness matrix
:rtype: :class:`numpy.ndarray`
"""
pass
[docs] def get_mass_matrix(self):
"""Placeholder for the get_mass_matrix method.
Gets the mass matrix for a for a FiniteElement.
:returns: Element mass matrix
:rtype: :class:`numpy.ndarray`
"""
pass
[docs] def get_internal_actions(self, analysis_case):
"""Returns the internal actions for a FiniteElement.
:param analysis_case: Analysis case
:type analysis_case: :class:`~feastruct.fea.cases.AnalysisCase`
:returns: An array containing the internal actions for the element
:rtype: :class:`numpy.ndarray`
"""
pass
[docs]class ForceVector:
"""Class for storing a force vector for a finite element.
:cvar f: Force vector *(n x 1)*, where *n* is the number of dofs within the element
:vartype: :class:`numpy.ndarray`
:cvar analysis_case: Analysis case relating to the displacement
:vartype analysis_case: :class:`~feastruct.fea.cases.AnalysisCase`
"""
[docs] def __init__(self, f, analysis_case):
"""Inits the ForceVector class.
:cvar f: Force vector *(n x 1)*, where *n* is the number of dofs within the element
:vartype: :class:`numpy.ndarray`
:param analysis_case: Analysis case relating to the displacement
:type analysis_case: :class:`~feastruct.fea.cases.AnalysisCase`
"""
self.f = f
self.analysis_case = analysis_case