download
raw
6.34 kB
import numpy as np
from random import choice
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def deviatoric(tensor):
"""
Calculates the deviatoric part of a tensor.
This removes the hydrostatic (volume-changing) component.
"""
return tensor - np.trace(tensor)/3 * np.eye(3)
def tresca_norm(tensor):
"""
Computes the Tresca norm of a tensor, defined as the difference
between the maximum and minimum eigenvalues. This is related to the
maximum shear stress in a material.
"""
eigs = np.linalg.eigvalsh(tensor)
return max(eigs) - min(eigs)
def distance(eps1, eps2):
"""
Calculates the 'distance' between two tensors using the Tresca norm
of their difference.
"""
return tresca_norm(eps1 - eps2)
def compute_diameter(E):
"""
Finds the diameter of a set of tensors E, which is the maximum
Tresca distance between any two tensors in the set. This function uses
an iterative algorithm to find the pair(s) of tensors that define this diameter.
"""
if len(E) < 2:
return None, 0
Ecurr = list(E)
eps_i = choice(Ecurr)
max_dist = 0
diameter = None
stop = False
while not stop:
dists = [distance(eps_i, eps) for eps in Ecurr]
k = np.argmax(dists)
eps_k = Ecurr[k]
dist_ik = dists[k]
if dist_ik > max_dist:
diameter = (eps_i, eps_k)
max_dist = dist_ik
if diameter is None:
break
p, q = diameter
m = (p + q) / 2
r = max_dist / 2
to_keep = []
for eps in Ecurr:
d = distance(eps, m)
if d >= r:
to_keep.append(eps)
Ecurr = to_keep
remaining = [eps for eps in Ecurr if not np.allclose(eps, p) and not np.allclose(eps, q)]
if len(remaining) > 0:
dists_m = [distance(eps, m) for eps in Ecurr]
new_k = np.argmax(dists_m)
eps_i = Ecurr[new_k]
else:
stop = True
p, q = diameter
m = (p + q)/2
r = max_dist / 2
Eout = [eps for eps in E if distance(eps, m) >= r]
Eout_without_diam = [eps for eps in Eout if not np.allclose(eps, p) and not np.allclose(eps, q)]
if len(Eout_without_diam) == 0:
return [(p, q)], max_dist
else:
list_diameters = []
current_max = max_dist
for eps_k in Eout:
for eps_l in E:
if np.allclose(eps_k, eps_l):
continue
d_kl = distance(eps_k, eps_l)
if d_kl > current_max:
current_max = d_kl
list_diameters = [(eps_k, eps_l)]
elif np.isclose(d_kl, current_max):
list_diameters.append((eps_k, eps_l))
return list_diameters, current_max
def get_critical_planes(eps1, eps2):
"""
Calculates the normal vectors of the critical planes.
These planes correspond to the planes of maximum shear stress range,
determined from the eigenvectors of the difference tensor between
the two most distant strain states.
"""
delta = eps1 - eps2
eigs, vecs = np.linalg.eigh(delta)
idx = np.argsort(eigs)
v3 = vecs[:, idx[0]]
v1 = vecs[:, idx[2]]
n1 = (v1 + v3) / np.linalg.norm(v1 + v3)
n2 = (v1 - v3) / np.linalg.norm(v1 - v3)
return n1, n2
def plot_plane(ax, n, color='b', alpha=0.2, size=1):
"""Helper function to plot a 3D plane given its normal vector 'n'."""
if np.abs(n[0]) > 0.1:
v1 = np.array([-n[1], n[0], 0])
else:
v1 = np.array([0, -n[2], n[1]])
v1 = v1 / np.linalg.norm(v1) if np.linalg.norm(v1) > 0 else v1
v2 = np.cross(n, v1)
v2 = v2 / np.linalg.norm(v2) if np.linalg.norm(v2) > 0 else v2
u, v = np.meshgrid(np.linspace(-size, size, 10), np.linspace(-size, size, 10))
points = u[..., np.newaxis] * v1 + v[..., np.newaxis] * v2
x = points[..., 0]
y = points[..., 1]
z = points[..., 2]
ax.plot_surface(x, y, z, color=color, alpha=alpha)
def plot_critical_planes(n1, n2):
"""
Creates and saves a 3D plot visualizing the two critical planes
and their normal vectors.
"""
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Plot planes
plot_plane(ax, n1, 'blue', 0.3, 1)
plot_plane(ax, n2, 'red', 0.3, 1)
# Plot normals
ax.quiver(0, 0, 0, n1[0], n1[1], n1[2], color='blue', label='n1', length=1.5, arrow_length_ratio=0.2)
ax.quiver(0, 0, 0, n2[0], n2[1], n2[2], color='red', label='n2', length=1.5, arrow_length_ratio=0.2)
ax.set_xlim([-1.5, 1.5])
ax.set_ylim([-1.5, 1.5])
ax.set_zlim([-1.5, 1.5])
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.legend()
plt.savefig('critical_planes.png')
print("Saved plot to 'critical_planes.png'")
plt.close()
def generate_strains(num_points=1000):
"""
Generates a sample non-proportional, multiaxial strain history
for demonstration purposes.
"""
t = np.linspace(0, 1, num_points, endpoint=False)
eps11 = 0.005 * np.cos(3 * np.pi * t)
eps22 = -0.001 * np.cos(2 * np.pi * t)
eps33 = 0.0075 * np.cos(4 * np.pi * t)
eps12 = -0.002 * np.cos(5 * np.pi * t)
eps = []
for i in range(num_points):
mat = np.array([[eps11[i], eps12[i], 0],
[eps12[i], eps22[i], 0],
[0, 0, eps33[i]]])
dev = deviatoric(mat)
eps.append(dev)
return eps
# --- Main execution block ---
# 1. Generate a simulated history of strain tensors
E = generate_strains()
# 2. Find the diameter of the strain path, which corresponds to the
# maximum shear strain range experienced by the material.
diameters, max_d = compute_diameter(E)
# 3. Calculate a fatigue parameter (half of the max shear strain range)
delta_gamma_over_2_percent = (max_d / 2) * 100
# 4. For each pair of points that form the diameter, calculate and
# visualize the critical planes.
for pair in diameters:
n1, n2 = get_critical_planes(pair[0], pair[1])
print("Critical planes normals:")
print(np.round(n1, 4))
print(np.round(n2, 4))
plot_critical_planes(n1, n2)
# 5. Print the calculated fatigue parameter.
print("Delta gamma / 2 (%):", round(delta_gamma_over_2_percent, 3))

Xet Storage Details

Size:
6.34 kB
·
Xet hash:
2891b890671a284245f5801e25a26a885e7efbdb26acc437d4d86af2a216806a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.