158 lines
6.0 KiB
Python
158 lines
6.0 KiB
Python
# DiscreteMethod.py
|
|
|
|
import numpy as np
|
|
|
|
from Bar import Bar
|
|
|
|
class DiscreteMethod():
|
|
'''Provides the class for solving via FDM or FEM.
|
|
The bar is provided at initiation, as well as the desired order.
|
|
The user can change the forcing frequency and num_sections as desired'''
|
|
def __init__(self, bar: 'Bar', desired_order:str="2"):
|
|
self.bar: 'Bar' = bar
|
|
self.desired_order:str = desired_order
|
|
|
|
# Finite method solving
|
|
self.num_sections: int = None
|
|
self.dx: float = None
|
|
self.alpha: float = None
|
|
self.stiffness_matrix: np.ndarray = None
|
|
self.constant_vector: np.ndarray = None
|
|
|
|
# Result vectors
|
|
self.x_vals: np.ndarray = None
|
|
self.forcing_freq: float = None
|
|
self.disp_amp: np.ndarray = None
|
|
self.strain_amp: np.ndarray = None
|
|
|
|
def set_boundary_conditions(self, U_0:float=0, U_L:float=100):
|
|
'''Set the left and right amplitude boundary conditions'''
|
|
self.U_0 = U_0
|
|
self.U_L = U_L
|
|
|
|
def nodal_strain(self, node_idx: int):
|
|
'''Calculates the strain (U') at a given node using the 4th-order scheme.'''
|
|
dx = self.dx
|
|
alpha = self.alpha
|
|
if node_idx == 0: # Left boundary
|
|
# Use forward difference at the boundary
|
|
return (
|
|
(1 - dx**2 * alpha**2 / 2 + dx**4 * alpha**4 / 24) / (-dx + (dx**3*alpha**2/6)) * self.disp_amp[node_idx]
|
|
- (1 / (-dx + dx**3 * alpha**2 / 6)) * self.disp_amp[node_idx + 1]
|
|
)
|
|
elif node_idx == len(self.disp_amp) - 1: # Right boundary, use backward difference
|
|
return (
|
|
(1 - dx**2 * alpha**2 / 2 + dx**4 * alpha**4 / 24) / (dx - (dx**3*alpha**2/6)) * self.disp_amp[node_idx]
|
|
- (1 / (dx - (dx**3*alpha**2/6))) * self.disp_amp[node_idx - 1]
|
|
)
|
|
else: # Return the average of the backward and forward derivative
|
|
return (
|
|
((1 - dx**2 * alpha**2 / 2 + dx**4 * alpha**4 / 24) / (dx - (dx**3*alpha**2/6)) * self.disp_amp[node_idx]
|
|
- (1 / (dx - (dx**3*alpha**2/6))) * self.disp_amp[node_idx - 1]
|
|
+
|
|
(1 - dx**2 * alpha**2 / 2 + dx**4 * alpha**4 / 24) / (-dx + (dx**3*alpha**2/6)) * self.disp_amp[node_idx]
|
|
- (1 / (-dx + dx**3 * alpha**2 / 6)) * self.disp_amp[node_idx + 1])
|
|
/ 2.0
|
|
)
|
|
|
|
def calc_all_nodal_strain(self):
|
|
'''Calculates all of the nodal strains after running the method'''
|
|
nodal_strains = np.array([])
|
|
for i in range(self.num_sections+1):
|
|
nodal_strains = np.append(nodal_strains, self.nodal_strain(i))
|
|
self.strain_amp = nodal_strains
|
|
|
|
def get_interpolated_disp_amp(self, x_val: float):
|
|
"""Returns the interpolated displacement amplitude for a given x_val."""
|
|
# Find the closest node to x_val
|
|
idx = np.argmin(np.abs(self.x_vals - x_val)) # Closest node index
|
|
alpha = self.alpha
|
|
|
|
# Calculate h (distance from the nearest node)
|
|
h = x_val - self.x_vals[idx]
|
|
|
|
U_i = self.disp_amp[idx]
|
|
|
|
if h == 0:
|
|
return U_i
|
|
|
|
# Calculate U' at the closest node
|
|
U_prime = self.strain_amp[idx]
|
|
|
|
# Apply the fourth-order Taylor series
|
|
|
|
interpolated_value = (
|
|
(1 - h**2 * alpha**2 / 2 + h**4 * alpha**4 / 24) * U_i
|
|
+ (h - h**3 * alpha**2 / 6) * U_prime
|
|
)
|
|
|
|
return interpolated_value
|
|
|
|
def get_interpolated_strain_amp(self, x_val: float):
|
|
'''Returns the interpolated strain (U') amplitude for a given x_val.'''
|
|
# Find the closest node to x_val
|
|
idx = np.argmin(np.abs(self.x_vals - x_val)) # Closest node index
|
|
dx = 1 / (len(self.x_vals) - 1) # Spacing between nodes
|
|
alpha = self.forcing_freq * (self.bar.density / self.bar.E)**0.5
|
|
|
|
# Calculate h (distance from the nearest node)
|
|
h = x_val - self.x_vals[idx]
|
|
|
|
# Get U at the closest node
|
|
U_at_node = self.disp_amp[idx]
|
|
|
|
# Calculate U' at the closest node
|
|
U_prime_at_node = self.strain_amp[idx]
|
|
|
|
if h == 0:
|
|
return U_prime_at_node
|
|
|
|
# Apply the fourth-order Taylor series
|
|
# Taylor series expansion for strain
|
|
U_prime_interpolated = (
|
|
(-h*alpha**2 + h**3 * alpha**4 / 6) * U_at_node
|
|
+ (1 - h**2 * alpha**2 / 2 + h**4 * alpha**4 / 24) * U_prime_at_node
|
|
)
|
|
|
|
return U_prime_interpolated
|
|
|
|
def build_stiffness_matrix(self):
|
|
pass
|
|
|
|
def build_constant_vector(self):
|
|
pass
|
|
|
|
def run(self, forcing_freq:float, num_sections:int=4):
|
|
'''Runs the method for the given forcing frequency and num_sections.
|
|
Stores displacment ampltitude data in self.disp_amp
|
|
Stores strain amplitude data in self.strain_amp'''
|
|
# Save the appropriate values
|
|
self.num_sections = num_sections
|
|
self.dx = 1 / num_sections
|
|
self.alpha = forcing_freq*(self.bar.density / self.bar.E)**0.5
|
|
self.forcing_freq = forcing_freq
|
|
|
|
self.build_stiffness_matrix()
|
|
self.build_constant_vector()
|
|
|
|
# Solve the system
|
|
self.disp_amp = np.linalg.solve(self.stiffness_matrix, self.constant_vector)
|
|
|
|
# Calculate all of the nodal strains
|
|
self.calc_all_nodal_strain()
|
|
|
|
# Save the x values for the nodal values
|
|
self.x_vals = np.linspace(0, 1, num_sections+1) # sections + endpoints
|
|
|
|
def get_disp_amp(self, x):
|
|
'''Returns the displacement amplitude at x'''
|
|
return self.get_interpolated_disp_amp(x)
|
|
|
|
def get_strain_amp(self, x):
|
|
'''Returns the strain amplitude at x'''
|
|
return self.get_interpolated_strain_amp(x)
|
|
|
|
def get_force_amp(self, x):
|
|
'''Returns the force amplitude at x.
|
|
Units are in MN'''
|
|
return self.bar.area * self.bar.E* self.get_interpolated_strain_amp(x) / (1e9) |