Numerical-Simulation/HW5/DiscreteMethod.py

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)