Source code for rolland.deflection
"""Defines deflection classes for FDM simulation.
.. autosummary::
:toctree: deflection
Deflection
DeflectionEBBVertic
"""
import abc
from numpy import empty, linspace, zeros
from scipy.sparse.linalg import splu
from traitlets import Instance
from .abstract_traits import ABCHasTraits
from .discretization import Discretization
from .excitation import Excitation
[docs]
class Deflection(ABCHasTraits):
r"""Abstract base class for deflection classes.
Attributes
----------
excit : Excitation
Excitation instance.
discr : Discretization
Discretization instance.
"""
discr = Instance(Discretization)
excit = Instance(Excitation)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.track = self.discr.track
[docs]
@abc.abstractmethod
def validate_deflection(self):
"""Validate deflection."""
[docs]
class DeflectionEBBVertic(Deflection):
r"""Calculate deflection according to :cite:t:`stampka2022a`.
Attributes
----------
track : Track
Track instance.
excit : Excitation
Excitation instance.
discr : Discretization
Discretization instance.
deflection : numpy.ndarray
Deflection array :math:`[m]`.
ind_excit : int
Index of excitation point :math:`[-]`.
"""
[docs]
def validate_deflection(self):
"""Validate deflection."""
[docs]
def initialize_start_values(self):
"""Set starting values of deflections to zero.
Returns
-------
defl : numpy.ndarray
Array of deflections initialized to zero with shape (2 * nx, nt + 1).
"""
defl = empty((2 * self.discr.nx, self.discr.nt + 1))
# Set starting values to zero for two time steps
defl[:, 0:2] = zeros((2 * self.discr.nx, 2))
return defl
[docs]
def calc_force(self):
"""Calculate force array."""
t = linspace(0, self.discr.sim_t, self.discr.nt)
self.force = self.excit.force(t)
[docs]
def calc_rightside_crank_nicolson(self, u1, u0, ind_excit, t):
"""Calculate the right-hand side of the equation according to :cite:t:`stampka2022a`.
Parameters
----------
u1 : numpy.ndarray
Deflection array at the current time step.
u0 : numpy.ndarray
Deflection array at the previous time step.
ind_excit : int
Index of the excitation point.
t : int
Current time step.
Returns
-------
numpy.ndarray
Right-hand side of the equation.
"""
# Write excitation force for time step t into force array
f = zeros(2 * self.discr.nx)
if isinstance(ind_excit, list):
for idx in ind_excit:
f[idx] = self.force[t]
else:
f[ind_excit] = self.force[t]
return (self.discr.B.dot(u1) + self.discr.C.dot(u0) + self.discr.dt ** 2 /
(self.track.rail.mr * self.discr.dx) * f)
[docs]
def calc_deflection(self, defl):
"""
Calculate deflection.
Parameters
----------
defl : numpy.ndarray
Array of deflections initialized to zero with shape (2 * nx, nt + 1).
Returns
-------
defl : numpy.ndarray
Array of calculated deflections with shape (2 * nx, nt + 1).
"""
# Index of excitation point/points
if isinstance(self.excit.x_excit, list):
self.ind_excit = [int(x / self.discr.dx) for x in self.excit.x_excit]
else:
self.ind_excit = int(self.excit.x_excit / self.discr.dx)
# Factorization of matrix A (LU decomposition)
factoriz = splu(self.discr.A)
for t in range(1, self.discr.nt):
# Calculate right hand side of equation
b = self.calc_rightside_crank_nicolson(u1=defl[:, t], u0=defl[:, t - 1], ind_excit=self.ind_excit, t=t)
# Calculate deflection for time step t
u = factoriz.solve(b)
defl[:, t + 1] = u[0:2 * self.discr.nx]
return defl
def __init__(self, *args, **kwargs):
"""
Initialize the DeflectionFDMStampka class.
Parameters
----------
*args : tuple
Variable length argument list.
**kwargs : dict
Arbitrary keyword arguments.
Attributes
----------
deflection : numpy.ndarray
Array of calculated deflections with shape (2 * nx, nt + 1).
"""
super().__init__(*args, **kwargs)
# Initialize starting values
self.calc_force()
defl = self.initialize_start_values()
# Calculate deflection
self.deflection = self.calc_deflection(defl)