"""
Copyright (c) 2019, Matt Pewsey
"""
import weakref
import numpy as np
from functools import lru_cache
from .element import transformation_matrix
__all__ = [
'load_distances',
'force_local_reactions',
'moment_local_reactions',
'local_reactions',
'clear_element_load_cache',
'ElementLoad',
]
[docs]def load_distances(dx, dy, dz, ix, delx):
"""
Returns the load distances to where an element load is applied.
Parameters
----------
dx, dy, dz : float
The element distance vector.
ix, : float
The distance from the i node of the element to where the beginning
of the loads are applied.
dx : float
The distance from the ix position toward the j node over which
the loads are applied.
"""
l = (dx**2 + dy**2 + dz**2)**0.5
l1 = l * abs(ix) if ix < 0 else ix
l2 = l * abs(delx) if delx < 0 else delx
l2 = l - l1 - l2
if l1 > l or l1 < 0 or l2 > l or l2 < 0:
raise ValueError('Load applied beyond element bounds.')
return l, l1, l2
[docs]def force_local_reactions(fx, fy, fz, dx, dy, dz, roll, ix, delx):
"""
Returns the local force reaction vector for an element.
Parameters
----------
fx, fy, fz : float
The force vector.
dx, dy, dz : float
The element distance vector.
roll : float
The roll of the element.
ix, : float
The distance from the i node of the element to where the beginning
of the loads are applied.
dx : float
The distance from the ix position toward the j node over which
the loads are applied.
"""
l, l1, l2 = load_distances(dx, dy, dz, ix, delx)
t = transformation_matrix(dx, dy, dz, roll)
if delx == 0:
# Point load
fsi = (l2**2 / l**3) * (3*l1 + l2)
fmi = l1*l2**2 / l**2
fsj = (l1**2 / l**3) * (l1 + 3*l2)
fmj = -fmi
fti = ftj = 0
fai = l2 / l
faj = l1 / l
else:
# Uniform load
fsi = (l / 2) * (1 - (2*l**3 - 2*l1**2*l + l1**3)*l1/l**4 - (2*l - l2)*l2**3/l**4)
fmi = (l**2 / 12) * (1 - (6*l**2 - 8*l1*l + 3*l1**2)*l1**2/l**4 - (4*l - 3*l2)*l2**3/l**4)
fsj = (l / 2) * (1 - (2*l - l1)*l1**3/l**4 - (2*l**3 - 2*l2**2*l + l2**3)*l2/l**4)
fmj = -(l**2 / 12) * (1 - (4*l - 3*l1)*l1**3/l**4 - (6*l**2 - 8*l2*l + 3*l2**2)*l2**2/l**4)
fti = ftj = 0
fai = (l / 2) * (l - l1 - l2) * (l - l1 + l2)
faj = -fai
fx, fy, fz = t[:3,:3].dot([fx, fy, fz])
r = [-fx*fai, -fy*fsi, -fz*fsi, -fti, -fz*fmi, -fy*fmi,
-fx*faj, -fy*fsj, -fz*fsj, -ftj, -fz*fmj, -fy*fmj]
return np.array(r, dtype='float')
[docs]def moment_local_reactions(mx, my, mz, dx, dy, dz, roll, ix, delx):
"""
Returns the local moment reaction vector for an element.
Parameters
----------
mx, my, mz : float
The moment vector.
dx, dy, dz : float
The element distance vector.
roll : float
The roll of the element.
ix, : float
The distance from the i node of the element to where the beginning
of the loads are applied.
dx : float
The distance from the ix position toward the j node over which
the loads are applied.
"""
l, l1, l2 = load_distances(dx, dy, dz, ix, delx)
t = transformation_matrix(dx, dy, dz, roll)
if delx == 0:
# Point load
fsi = -6*l1*l2 / l**3
fmi = (l2 / l**2) * (l2 - 2*l1)
fsj = -fsi
fmj = (l1 / l**2) * (l1 - 2*l2)
fti = l2 / l
ftj = l1 / l
fai = faj = 0
else:
# Uniform load
fsi = 2*((l-l2)**3 - l1**3)/l**3 - 3*((l-l2)**2 - l1**2)/l**2
fmi = ((l-l2) - l1) - 2*((l-l2)**2 - l1**2)/l + ((l-l2)**3 - l1**3)/l**2
fsj = -fsi
fmj = ((l-l2)**3 - l1**3)/l**2 - ((l-l2)**2 - l1**2)/l
fti = ((l-l2) - l1) - ((l-l2)**2 - l1**2)/(2*l)
ftj = ((l-l2)**2 - l1**2)/(2*l)
fai = faj = 0
mx, my, mz = t[:3,:3].dot([mx, my, mz])
r = [-fai, -my*fsi, -mx*fsi, -mx*fti, -my*fmi, -mz*fmi,
-faj, -my*fsj, -mx*fsj, -mx*ftj, -my*fmj, -mz*fmj]
return np.array(r, dtype='float')
@lru_cache(maxsize=1000)
def local_reactions(fx, fy, fz, mx, my, mz, dx, dy, dz, roll, ix, delx,
imx_free, imy_free, imz_free, jmx_free, jmy_free, jmz_free):
"""
Returns the local reaction vector for an element.
Parameters
----------
fx, fy, fz : float
The force vector.
mx, my, mz : float
The moment vector.
dx, dy, dz : float
The element distance vector.
roll : float
The roll of the element.
ix, : float
The distance from the i node of the element to where the beginning
of the loads are applied.
dx : float
The distance from the ix position toward the j node over which
the loads are applied.
imx_free, imy_free, imz_free : bool
The fixities at the i end of the element.
jmx_free, jmy_free, jmz_free : bool
The fixities at the j end of the element.
"""
r = force_local_reactions(fx, fy, fz, dx, dy, dz, roll, ix, delx)
r += moment_local_reactions(mx, my, mz, dx, dy, dz, roll, ix, delx)
# Adjust reactions for element end fixities
if imz_free:
if not jmz_free:
# Free-Fixed
r[9] += r[3]
r[3] = 0
else:
# Free-Free
r[3] = r[9] = 0
elif jmz_free:
# Fixed-Free
r[3] += r[9]
r[9] = 0
if imx_free:
if not jmx_free:
# Free-Fixed
r[1] -= 1.5 * r[5] / l
r[7] += 1.5 * r[5] / l
r[11] -= 0.5 * r[5]
r[5] = 0
else:
# Free-Free
r[1] -= (r[5] + r[11]) / l
r[7] += (r[5] + r[11]) / l
r[5] = r[11] = 0
elif jmx_free:
# Fixed-Free
r[1] -= 1.5 * r[11] / l
r[5] -= 0.5 * r[11]
r[7] += 1.5 * r[11] / l
r[11] = 0
if imy_free:
if not jmy_free:
# Free-Fixed
r[2] += 1.5 * r[4] / l
r[8] -= 1.5 * r[4] / l
r[10] -= 0.5 * r[4] / l
r[4] = 0
else:
# Free-Free
r[2] += (r[4] + r[10]) / l
r[8] -= (r[4] + r[10]) / l
r[4] = r[10] = 0
elif jmy_free:
# Fixed-Free
r[2] += 1.5 * r[10] / l
r[4] -= 0.5 * r[10]
r[8] -= 1.5 * r[10] / l
r[10] = 0
return r
[docs]def clear_element_load_cache():
"""Clears the element load function cache."""
local_reactions.cache_clear()
[docs]class ElementLoad(np.ndarray):
"""
A class representing an element load.
Parameters
----------
element : str
The name of the element to which the loads are applied.
fx, fy, fz : float
The global forces applied to the element.
mx, my, mz : float
The global moments applied to the element.
ix, : float
The distance from the i node at where the loads are applied.
dx : float
The distance from the ix position toward the j node over which
the loads are applied.
"""
def __new__(cls, element, fx=0, fy=0, fz=0, mx=0, my=0, mz=0, ix=0, dx=-1):
obj = np.array([fx, fy, fz, mx, my, mz], dtype='float').view(cls)
obj.element = element
obj.ix = ix
obj.dx = dx
return obj
def __array_finalize__(self, obj):
if obj is None: return
self.element = getattr(obj, 'element', '')
self.ix = getattr(obj, 'ix', 0)
self.dx = getattr(obj, 'dx', 0)
self.element_ref = None
def element():
def fget(self):
return self._element
def fset(self, value):
if not isinstance(value, str):
value = str(value)
self._element = value
def fdel(self):
del self._element
return locals()
element = property(**element())
def element_ref():
def fget(self):
value = self._element_ref
if value is None:
return value
return value()
def fset(self, value):
if value is not None:
value = weakref.ref(value)
self._element_ref = value
def fdel(self):
del self._element_ref
return locals()
element_ref = property(**element_ref())
def fx():
def fget(self):
return self[0]
def fset(self, value):
self[0] = value
return locals()
fx = property(**fx())
def fy():
def fget(self):
return self[1]
def fset(self, value):
self[1] = value
return locals()
fy = property(**fy())
def fz():
def fget(self):
return self[2]
def fset(self, value):
self[2] = value
return locals()
fz = property(**fz())
def mx():
def fget(self):
return self[3]
def fset(self, value):
self[3] = value
return locals()
mx = property(**mx())
def my():
def fget(self):
return self[4]
def fset(self, value):
self[4] = value
return locals()
my = property(**my())
def mz():
def fget(self):
return self[5]
def fset(self, value):
self[5] = value
return locals()
mz = property(**mz())
def __repr__(self):
s = [
'element={!r}'.format(self.element),
'forces={!r}'.format((self.fx, self.fy, self.fz)),
'moments={!r}'.format((self.mx, self.my, self.mz)),
'ix={!r}'.format(self.ix),
'dx={!r}'.format(self.dx),
]
return '{}({})'.format(type(self).__name__, ', '.join(s))
[docs] def forces(self):
"""Returns the force vector."""
return self[:3]
[docs] def moments(self):
"""Returns the moment vector."""
return self[3:6]
[docs] def get_element(self):
"""Gets the referenced element."""
if self._element_ref is None:
raise ValueError('Element has not been set.')
return self._element_ref
[docs] def set_element(self, edict):
"""
Sets the element reference.
Parameters
----------
edict : dict
A dictionary mapping node names to node objects.
"""
self._element_ref = edict[self.element]
[docs] def local_reactions(self, di=(0, 0, 0), dj=(0, 0, 0)):
"""
Returns the local end reactions for the element.
Parameters
----------
di, dj : array
The deflections at the i and j ends of the element.
"""
di, dj = np.asarray(di), np.asarray(dj)
e = self.get_element()
xi, xj = e.get_nodes()
dx, dy, dz = (xj - xi) + (dj - di)
fx, fy, fz = self.forces()
mx, my, mz = self.moments()
r = local_reactions(
fx, fy, fz, mx, my, mz, dx, dy, dz,
e.roll, self.ix, self.dx,
e.imx_free, e.imy_free, e.imz_free,
e.jmx_free, e.jmy_free, e.jmz_free
)
return r
[docs] def global_reactions(self, di=(0, 0, 0), dj=(0, 0, 0)):
"""
Returns the global end reactions for the element.
Parameters
----------
di, dj : array
The deflections at the i and j ends of the element.
"""
di, dj = np.asarray(di), np.asarray(dj)
e = self.get_element()
t = e.transformation_matrix(di, dj)
q = self.local_reactions(di, dj)
return t.T.dot(q)