|
|
|
|
|
|
|
|
|
|
|
|
|
|
| import numpy as np
|
| import matplotlib.pyplot as plt
|
| from dataclasses import dataclass
|
| from pathlib import Path
|
| import warnings
|
| from numpy.lib._iotools import ConversionWarning
|
| from itertools import combinations_with_replacement
|
| from math import comb
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| MATERIAL_TYPES = [ "CPP",]
|
| VOL_FRACTIONS = [ "0.2000",]
|
|
|
|
|
|
|
| UPPER_LAYER_COUNTS = [9, ]
|
|
|
|
|
|
|
|
|
|
|
|
|
| FIXED_STACK_ANGLES = [-8,8,8,-8]
|
|
|
|
|
|
|
|
|
| CURVE_DIR = Path(r"C:\Users\shahria\OneDrive - Clemson University\PhD\AIM Project\Post Meeting\Unstability_in_e33\RVE_datasets")
|
|
|
|
|
|
|
|
|
|
|
|
|
| OUT_DIR_ALL = Path(r"C:\Users\shahria\OneDrive - Clemson University\PhD\AIM Project\Post Meeting\Unstability_in_e33\Output_directory")
|
|
|
|
|
|
|
| NU12 = 0.36
|
| NU13 = 0.36
|
| NU23 = 0.86
|
|
|
| COND_MAX = 1e12
|
|
|
|
|
| @dataclass
|
| class Curve1D:
|
| eps: np.ndarray
|
| sig: np.ndarray
|
|
|
| def __post_init__(self):
|
| idx = np.argsort(self.eps)
|
| self.eps = np.asarray(self.eps, float)[idx]
|
| self.sig = np.asarray(self.sig, float)[idx]
|
|
|
| def stress(self, e):
|
| ea = np.clip(abs(e), self.eps[0], self.eps[-1])
|
| sa = np.interp(ea, self.eps, self.sig)
|
| return np.sign(e) * sa
|
|
|
| def tangent(self, e):
|
| ea = np.clip(abs(e), self.eps[0], self.eps[-1])
|
| i = np.searchsorted(self.eps, ea) - 1
|
| i = np.clip(i, 0, len(self.eps)-2)
|
| de = self.eps[i+1] - self.eps[i]
|
| ds = self.sig[i+1] - self.sig[i]
|
| return (ds/de)
|
|
|
| def smallest_positive_interval_slope(curve: Curve1D) -> float:
|
| slopes = np.diff(curve.sig) / np.diff(curve.eps)
|
| pos = slopes[slopes > 0.0]
|
|
|
| if len(pos) == 0:
|
| raise ValueError("No positive interval slopes found in the compression curve.")
|
|
|
| return float(np.min(pos))
|
| class BiCurve1D:
|
| """
|
| Branch-aware 1D constitutive law:
|
| - tension_curve -> used when e >= 0
|
| - compression_curve -> used when e < 0
|
|
|
| Both curves should be stored with positive strain magnitude
|
| and positive stress magnitude.
|
| """
|
|
|
| def __init__(self, tension_curve: Curve1D, compression_curve: Curve1D):
|
| self.tension_curve = tension_curve
|
| self.compression_curve = compression_curve
|
|
|
| def stress(self, e):
|
| if e >= 0.0:
|
| return self.tension_curve.stress(e)
|
| else:
|
|
|
| return -self.compression_curve.stress(abs(e))
|
|
|
| def tangent(self, e):
|
| if e >= 0.0:
|
| return self.tension_curve.tangent(e)
|
| else:
|
|
|
|
|
| return self.compression_curve.tangent(abs(e))
|
| def read_dataset_metadata(prefix: str):
|
|
|
| meta_file = CURVE_DIR / f"{prefix}_11.txt"
|
| vol_frac = None
|
| centers_str = None
|
|
|
| with open(meta_file, "r") as f:
|
| for line in f:
|
| line = line.strip()
|
| if line.startswith("volume fraction="):
|
| vol_frac = float(line.split("=", 1)[1])
|
| elif line.startswith("fiber_centers_YZ="):
|
| centers_str = line.split("=", 1)[1].strip()
|
|
|
| if vol_frac is None:
|
| raise ValueError(f"volume fraction not found in {meta_file}")
|
|
|
| if centers_str is None:
|
| n_fibers = None
|
| else:
|
| n_fibers = centers_str.count("(")
|
|
|
| return vol_frac, centers_str, n_fibers
|
|
|
|
|
|
|
|
|
|
|
| def load_curve_from_file(filename, strain_col=1, stress_col=2, skiprows=1, is_shear=False):
|
| """
|
| Load a 2-col curve file.
|
| If is_shear=True, strain column is assumed 'tensorial' and converted to engineering γ by *2.0*.
|
|
|
| """
|
| p = Path(filename)
|
| if not p.exists():
|
| raise FileNotFoundError(f"Missing curve file: {p.resolve()}")
|
|
|
|
|
|
|
|
|
| with warnings.catch_warnings():
|
| warnings.simplefilter("ignore", ConversionWarning)
|
| arr = np.genfromtxt(
|
| p,
|
| dtype=float,
|
| delimiter=None,
|
| skip_header=skiprows,
|
| invalid_raise=False
|
| )
|
|
|
|
|
| eps = arr[:, strain_col]
|
| if is_shear:
|
| eps = 2.0 * eps
|
| sig = 1e6 * arr[:, stress_col]
|
| return Curve1D(eps, sig)
|
|
|
|
|
| def load_ud_material_from_files(prefix: str) -> "PlyMaterial":
|
| """
|
| Build a PlyMaterial from three curve files with a common prefix, e.g.
|
| prefix='CPP_0.1000' -> CPP_0.1000_11.txt, CPP_0.1000_22.txt, CPP_0.1000_12.txt
|
| """
|
|
|
| c11 = load_curve_from_file(
|
| CURVE_DIR / f"{prefix}_11.txt",
|
| strain_col=0,
|
| stress_col=1,
|
| skiprows=1
|
| )
|
|
|
|
|
|
|
| c22_t = load_curve_from_file(
|
| CURVE_DIR / f"{prefix}_22.txt",
|
| strain_col=0,
|
| stress_col=1,
|
| skiprows=1
|
| )
|
|
|
|
|
| c22_c = load_curve_from_signed_compression_file(
|
| CURVE_DIR / f"{prefix}_22c.txt",
|
| strain_col=0,
|
| stress_col=1,
|
| skiprows=1
|
| )
|
|
|
|
|
| c22 = BiCurve1D(c22_t, c22_c)
|
|
|
|
|
| c33 = c22
|
| E2_comp_min_pos = smallest_positive_interval_slope(c22_c)
|
|
|
| c12 = load_curve_from_file(
|
| CURVE_DIR / f"{prefix}_12.txt",
|
| strain_col=0,
|
| stress_col=1,
|
| skiprows=1,
|
| is_shear=True
|
| )
|
|
|
|
|
| c13 = c12
|
| c23 = c12
|
|
|
| return PlyMaterial(
|
| E1_curve=c11, E2_curve=c22, E3_curve=c33,
|
| G12_curve=c12, G13_curve=c13, G23_curve=c23,
|
| nu12=NU12, nu13=NU13, nu23=NU23,
|
| E2_comp_min_pos=E2_comp_min_pos
|
| )
|
|
|
| def load_curve_from_signed_compression_file(filename, strain_col=0, stress_col=1, skiprows=1):
|
| p = Path(filename)
|
| if not p.exists():
|
| raise FileNotFoundError(f"Missing curve file: {p.resolve()}")
|
|
|
| with warnings.catch_warnings():
|
| warnings.simplefilter("ignore", ConversionWarning)
|
| arr = np.genfromtxt(
|
| p,
|
| dtype=float,
|
| delimiter=None,
|
| skip_header=skiprows,
|
| invalid_raise=False
|
| )
|
|
|
| eps = np.abs(arr[:, strain_col])
|
| sig = 1e6 * np.abs(arr[:, stress_col])
|
|
|
| return Curve1D(eps, sig)
|
|
|
|
|
|
|
|
|
| def orthotropic_C_prime(E1,E2,E3,G12,G13,G23, nu12,nu13,nu23):
|
| V = (1.0
|
| - (E3/E2)*(nu23**2)
|
| - (E3/E1)*(nu13**2)
|
| - (E2/E1)*(nu12**2)
|
| - 2*(E3/E1)*(nu12*nu13*nu23))
|
|
|
| C11 = ((1.0 - (E3/E2)*nu23**2) * E1) / V
|
| C22 = ((1.0 - (E3/E1)*nu13**2) * E2) / V
|
| C33 = ((1.0 - (E2/E1)*nu12**2) * E3) / V
|
| C12 = ((nu12 + (E3/E2)*nu13*nu23) * E2) / V
|
| C13 = ((nu13 + nu12*nu23) * E3) / V
|
| C23 = ((nu23 + (E2/E1)*nu12*nu13) * E3) / V
|
|
|
| C = np.zeros((6,6))
|
| C[0,0] = C11; C[1,1] = C22; C[2,2] = C33
|
| C[0,1] = C12; C[1,0] = C12
|
| C[0,2] = C13; C[2,0] = C13
|
| C[1,2] = C23; C[2,1] = C23
|
| C[3,3] = G23; C[4,4] = G13; C[5,5] = G12
|
| return C
|
|
|
|
|
|
|
|
|
| def T_sigma(theta_deg):
|
| th = np.radians(theta_deg); m, n = np.cos(th), np.sin(th)
|
| return np.array([
|
| [ m*m, n*n, 0, 0, 0, 2*m*n],
|
| [ n*n, m*m, 0, 0, 0, -2*m*n],
|
| [ 0, 0, 1, 0, 0, 0],
|
| [ 0, 0, 0, m,-n, 0],
|
| [ 0, 0, 0, n, m, 0],
|
| [-m*n, m*n, 0, 0, 0, m*m-n*n]
|
| ], float)
|
|
|
| def T_eps(theta_deg):
|
| th = np.radians(theta_deg); m, n = np.cos(th), np.sin(th)
|
| return np.array([
|
| [ m*m, n*n, 0, 0, 0, m*n],
|
| [ n*n, m*m, 0, 0, 0, -m*n],
|
| [ 0, 0, 1, 0, 0, 0],
|
| [ 0, 0, 0, m,-n, 0],
|
| [ 0, 0, 0, n, m, 0],
|
| [-2*m*n,2*m*n,0, 0, 0, m*m-n*n]
|
| ], float)
|
|
|
|
|
| def T(theta_deg):
|
| th = np.radians(theta_deg); m, n = np.cos(th), np.sin(th)
|
| return np.array([
|
| [ m*m, n*n, 0, 0, 0, 2*m*n],
|
| [ n*n, m*m, 0, 0, 0, -2*m*n],
|
| [ 0, 0, 1, 0, 0, 0],
|
| [ 0, 0, 0, m,-n, 0],
|
| [ 0, 0, 0, n, m, 0],
|
| [-1*m*n,1*m*n,0, 0, 0, m*m-n*n]
|
| ], float)
|
|
|
|
|
|
|
|
|
|
|
|
|
| @dataclass
|
| class PlyMaterial:
|
| E1_curve: Curve1D
|
| E2_curve: Curve1D
|
| E3_curve: Curve1D
|
| G12_curve: Curve1D
|
| G13_curve: Curve1D
|
| G23_curve: Curve1D
|
| nu12: float
|
| nu13: float
|
| nu23: float
|
| E2_comp_min_pos: float
|
|
|
| def tangents_from_local_strain(self, e_local):
|
| e11, e22, e33, g23, g13, g12 = e_local
|
| floor = -1e12
|
|
|
| E1 = max(self.E1_curve.tangent(e11), floor)
|
|
|
| E2_raw = self.E2_curve.tangent(e22)
|
| if e22 < 0.0:
|
| E2 = max(E2_raw, self.E2_comp_min_pos)
|
| else:
|
| E2 = max(E2_raw, floor)
|
|
|
| E3 = max(self.E3_curve.tangent(e33), floor)
|
| G12 = max(self.G12_curve.tangent(g12), floor)
|
| G13 = max(self.G13_curve.tangent(g13), floor)
|
| G23 = max(self.G23_curve.tangent(g23), floor)
|
|
|
| return E1, E2, E3, G12, G13, G23
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| @dataclass
|
| class Ply:
|
| theta_deg: float
|
| thickness: float
|
| mat: PlyMaterial
|
| e_prev: np.ndarray = None
|
| s_prev: np.ndarray = None
|
| theta_curr_deg: float = None
|
| Cglob_prev: np.ndarray = None
|
|
|
|
|
|
|
|
|
|
|
| def init_state(self):
|
| self.e_prev = np.zeros(6)
|
| self.s_prev = np.zeros(6)
|
| self.theta_curr_deg = float(self.theta_deg)
|
|
|
|
|
| theta = self.theta_curr_deg
|
| T_e = T_eps(theta)
|
| e_local = T_e @ self.e_prev
|
|
|
| E1, E2, E3, G12, G13, G23 = self.mat.tangents_from_local_strain(e_local)
|
| Cprime = orthotropic_C_prime(E1, E2, E3, G12, G13, G23,
|
| self.mat.nu12, self.mat.nu13, self.mat.nu23)
|
|
|
| Tt = T(theta)
|
| self.Cglob_prev = np.linalg.inv(Tt) @ Cprime @ np.linalg.inv(Tt).T
|
| class Laminate:
|
| def __init__(self, plies, tol=1e-12):
|
| self.plies = plies
|
| for p in self.plies:
|
| p.init_state()
|
| t0 = self.plies[0].thickness
|
| for i,p in enumerate(self.plies, 1):
|
| if abs(p.thickness - t0) > tol:
|
| raise ValueError(f"Equal-thickness assumption violated at ply {i}: {p.thickness} vs {t0}")
|
| self.N = len(self.plies)
|
| self.t = t0
|
| self.total_t = self.N * self.t
|
|
|
|
|
| def update_fiber_angles_incremental(self, d_ex, d_ey, d_gxy):
|
| """
|
| a_{n+1} = ΔF a_n / ||ΔF a_n|| with ΔF = [[1+Δex, Δgxy/2],[Δgxy/2, 1+Δey]]
|
| Updates each ply's theta_curr_deg in-place.
|
| """
|
| Fd = np.array([[1.0 + d_ex, 0.5*d_gxy],
|
| [0.5*d_gxy, 1.0 + d_ey]], dtype=float)
|
|
|
| for p in self.plies:
|
| th = np.radians(p.theta_curr_deg)
|
| a = np.array([np.cos(th), np.sin(th)])
|
| a_new = Fd @ a
|
| nrm = np.linalg.norm(a_new)
|
| if nrm > 1e-14:
|
| a_new /= nrm
|
| p.theta_curr_deg = np.degrees(np.arctan2(a_new[1], a_new[0]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def effective_C_from_previous_strains(self, ex_prev, ey_prev, ezz_prev, g23_prev, g13_prev, gxy_prev):
|
| """
|
| Thickness-average of per-ply global tangential stiffness matrices,
|
| each built from tangents evaluated at the previous-step local strains.
|
| """
|
|
|
| u = [0, 1, 5]
|
| w = [2, 3, 4]
|
|
|
| eps_u = np.array([ex_prev, ey_prev, gxy_prev], dtype=float)
|
|
|
|
|
| I = [0, 1, 2, 5]
|
| z = 2
|
|
|
|
|
| Vk_list = []
|
| Ck_list = []
|
| Ck_next_list = []
|
|
|
| for p in self.plies:
|
| theta = p.theta_curr_deg
|
|
|
|
|
| Cprev = p.Cglob_prev
|
| if Cprev is None:
|
| raise RuntimeError("Ply.Cglob_prev is not initialized. Check Ply.init_state().")
|
|
|
| Ck_list.append(Cprev)
|
| Vk_list.append(1.0 / self.N)
|
|
|
|
|
|
|
| Cww = Cprev[np.ix_(w, w)]
|
| Cwu = Cprev[np.ix_(w, u)]
|
| eps_w = -np.linalg.solve(Cww, Cwu @ eps_u)
|
|
|
|
|
| e_global_k = np.array([ex_prev, ey_prev, eps_w[0], eps_w[1], eps_w[2], gxy_prev], dtype=float)
|
|
|
|
|
| T_e = T_eps(theta)
|
| e_local_k = T_e @ e_global_k
|
|
|
| E1, E2, E3, G12, G13, G23 = p.mat.tangents_from_local_strain(e_local_k)
|
| Cprime = orthotropic_C_prime(E1, E2, E3, G12, G13, G23,
|
| p.mat.nu12, p.mat.nu13, p.mat.nu23)
|
|
|
| Tt = T(theta)
|
| Cglob_next = np.linalg.inv(Tt) @ Cprime @ np.linalg.inv(Tt).T
|
| Ck_next_list.append(Cglob_next)
|
|
|
|
|
| Vk = np.asarray(Vk_list, float)
|
|
|
|
|
| den = 0.0
|
|
|
| num = np.zeros(len(I), float)
|
|
|
| for Ck, vk in zip(Ck_list, Vk):
|
| C33 = Ck[z, z]
|
| if abs(C33) < 1e-30:
|
| raise ZeroDivisionError("Eq.(7) needs C33^k != 0; found near-zero Ck[2,2].")
|
| den += vk * (1.0 / C33)
|
| num += vk * (Ck[z, I] / C33)
|
|
|
| if abs(den) < 1e-30:
|
| raise ZeroDivisionError("Eq.(7) denominator sum_l V_l*(1/C33^l) is near zero.")
|
|
|
| R = num / den
|
|
|
|
|
| Cstar = np.zeros((6, 6), float)
|
|
|
| for Ck, vk in zip(Ck_list, Vk):
|
| C33 = Ck[z, z]
|
|
|
| CIJ = Ck[np.ix_(I, I)]
|
| CI3 = Ck[np.ix_(I, [z])]
|
| C3J = Ck[np.ix_([z], I)]
|
|
|
|
|
| term12 = CIJ - (CI3 @ C3J) / C33
|
|
|
|
|
| term3 = (CI3 / C33) @ R.reshape(1, -1)
|
|
|
| Cstar[np.ix_(I, I)] += vk * (term12 + term3)
|
|
|
|
|
| J = [3, 4]
|
|
|
| Cstar[np.ix_(I, J)] = 0.0
|
| Cstar[np.ix_(J, I)] = 0.0
|
|
|
|
|
|
|
| Dk_list = []
|
| for Ck in Ck_list:
|
| C44 = Ck[3, 3]
|
| C45 = Ck[3, 4]
|
| C54 = Ck[4, 3]
|
| C55 = Ck[4, 4]
|
|
|
| Dk = C44 * C55 - C45 * C54
|
| if abs(Dk) < 1e-30:
|
| raise ZeroDivisionError(
|
| "Eq.(10) gave near-zero D_k = C44*C55 - C45*C54."
|
| )
|
| Dk_list.append(Dk)
|
|
|
|
|
| numJJ = np.zeros((2, 2), float)
|
| for Ck, vk, Dk in zip(Ck_list, Vk, Dk_list):
|
| numJJ += (vk / Dk) * Ck[np.ix_(J, J)]
|
|
|
|
|
|
|
| denJJ = 0.0
|
| for Ck, vk, Dk in zip(Ck_list, Vk, Dk_list):
|
| C44k = Ck[3, 3]
|
| C45k = Ck[3, 4]
|
| for Cl, vl, Dl in zip(Ck_list, Vk, Dk_list):
|
| C55l = Cl[4, 4]
|
| C54l = Cl[4, 3]
|
| denJJ += (vk * vl / (Dk * Dl)) * (C44k * C55l - C45k * C54l)
|
|
|
| if abs(denJJ) < 1e-30:
|
| raise ZeroDivisionError("Eq.(9) denominator is near zero.")
|
|
|
| Cstar[np.ix_(J, J)] = numJJ / denJJ
|
|
|
|
|
| for p, Cnew in zip(self.plies, Ck_next_list):
|
| p.Cglob_prev = Cnew
|
|
|
|
|
|
|
| return Cstar
|
|
|
|
|
|
|
|
|
| def build_full_symmetric_stack(half_angles, center_angle=None):
|
| """
|
| Given an ordered list of angles for the *first half* of the laminate,
|
| build the full symmetric stack with the same angles mirrored in the second half.
|
|
|
| Examples:
|
| [30, -30] -> [30, -30, -30, 30]
|
| [30, -30], 0 -> [30, -30, 0, -30, 30]
|
| """
|
| half = list(half_angles)
|
| mirrored_half = list(reversed(half))
|
|
|
| if center_angle is None:
|
| return half + mirrored_half
|
|
|
| return half + [center_angle] + mirrored_half
|
|
|
| def _format_angle_for_label(a):
|
| """
|
| Format a ply angle for use in filenames:
|
| - integer degrees
|
| - explicit sign (+ or -)
|
| Examples:
|
| 45.0 -> '+45'
|
| -30.0 -> '-30'
|
| 0.0 -> '+0'
|
| """
|
| a_int = int(round(float(a)))
|
| sign = "+" if a_int >= 0 else "-"
|
| return f"{sign}{abs(a_int)}"
|
|
|
|
|
|
|
|
|
|
|
| def stack_label_from_upper(upper_angles):
|
| """
|
| Return a human-readable description of the *upper-half* stacking sequence,
|
| with comma separation, e.g. [0, 45, -45] -> '0, 45, -45'.
|
| """
|
| parts = []
|
| for a in upper_angles:
|
|
|
| if float(a).is_integer():
|
| parts.append(f"{int(a)}")
|
| else:
|
| parts.append(f"{a}")
|
| return ", ".join(parts)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def run_uniaxial_test_from_files_5d(prefix: str, stack_angles, mode="11"):
|
|
|
|
|
|
|
| tply = 0.05
|
|
|
|
|
|
|
|
|
|
|
| plies = [Ply(angle_deg, tply, mat) for angle_deg in stack_angles]
|
| lam = Laminate(plies)
|
|
|
|
|
|
|
|
|
|
|
| if mode == "11":
|
| main_index = 0
|
| eps_max = 0.10
|
| elif mode == "22":
|
| main_index = 1
|
| eps_max = 0.10
|
| elif mode == "12":
|
| main_index = 5
|
| eps_max = 0.20
|
| else:
|
| raise ValueError("mode must be '11', '22' or '12'")
|
|
|
| main_steps = np.linspace(0.0, eps_max, 3500)
|
|
|
|
|
|
|
|
|
|
|
| ex_hist, sx_hist = [], []
|
| ey_hist, gxy_hist = [], []
|
| ezz_hist, g23_hist, g13_hist = [], [], []
|
| e11_hist = []
|
|
|
|
|
|
|
|
|
| ex_prev = 0.0
|
| ey_prev = 0.0
|
| gxy_prev = 0.0
|
| ezz_prev = 0.0
|
| g23_prev = 0.0
|
| g13_prev = 0.0
|
| s1_prev = 0.0
|
|
|
| for i in range(1, len(main_steps)):
|
| main_target = main_steps[i]
|
|
|
|
|
| if main_index == 0:
|
| main_prev = ex_prev
|
| elif main_index == 1:
|
| main_prev = ey_prev
|
| else:
|
| main_prev = gxy_prev
|
|
|
|
|
| dmain = main_target - main_prev
|
|
|
|
|
| Ceff_prev = lam.effective_C_from_previous_strains(
|
| ex_prev, ey_prev, ezz_prev, g23_prev, g13_prev, gxy_prev
|
| )
|
|
|
|
|
| cond = np.linalg.cond(Ceff_prev)
|
| if not np.isfinite(cond) or cond > COND_MAX:
|
| raise np.linalg.LinAlgError(
|
| f"Effective C is ill-conditioned (cond={cond:.3e}) at step {i}"
|
| )
|
|
|
|
|
|
|
| e_j = np.zeros(6)
|
| e_j[main_index] = 1.0
|
|
|
| try:
|
|
|
| S_col = np.linalg.solve(Ceff_prev, e_j)
|
| except np.linalg.LinAlgError as err:
|
| raise np.linalg.LinAlgError(
|
| f"Failed to solve for compliance column at step {i}: {err}"
|
| )
|
|
|
| Sjj = S_col[main_index]
|
| if abs(Sjj) < 1e-20:
|
| raise ZeroDivisionError(
|
| f"Sjj is zero or too small at step {i} (Sjj={Sjj:.3e})."
|
| )
|
|
|
|
|
| ds1 = dmain / Sjj
|
| de_vec = S_col * ds1
|
|
|
| de_vec[main_index] = dmain
|
|
|
|
|
|
|
|
|
|
|
|
|
| de1, de2, de3, de4, de5, de6 = de_vec
|
| de4 = 0.0
|
| de5 = 0.0
|
|
|
|
|
| s1 = s1_prev + ds1
|
| ex = ex_prev + de1
|
| ey = ey_prev + de2
|
| ezz = ezz_prev + de3
|
| g23 = g23_prev + de4
|
| g13 = g13_prev + de5
|
| gxy = gxy_prev + de6
|
|
|
|
|
| lam.update_fiber_angles_incremental(de1, de2, de6)
|
|
|
|
|
| ex_prev, ey_prev, ezz_prev = ex, ey, ezz
|
| g23_prev, g13_prev, gxy_prev = g23, g13, gxy
|
| s1_prev = s1
|
|
|
|
|
|
|
|
|
|
|
| if main_index == 0:
|
| main_strain = ex
|
| elif main_index == 1:
|
| main_strain = ey
|
| else:
|
| main_strain = 0.5 * gxy
|
|
|
|
|
| ex_hist.append(main_strain)
|
| sx_hist.append(s1)
|
| ey_hist.append(ey); gxy_hist.append(gxy)
|
| ezz_hist.append(ezz); g23_hist.append(g23); g13_hist.append(g13)
|
| e11_hist.append(ex)
|
|
|
|
|
|
|
|
|
| angles_str = ", ".join(f"{p.theta_curr_deg:.2f}°" for p in lam.plies)
|
|
|
|
|
|
|
| return (np.array(ex_hist), np.array(sx_hist),
|
| np.array(ey_hist), np.array(gxy_hist),
|
| np.array(ezz_hist), np.array(g23_hist), np.array(g13_hist),
|
| np.array(e11_hist))
|
|
|
|
|
|
|
|
|
| if __name__ == "__main__":
|
|
|
|
|
| n_mat = len(MATERIAL_TYPES)
|
| n_vf = len(VOL_FRACTIONS)
|
|
|
| expected_files = n_mat * n_vf
|
| print(f"Expected number of laminate output files: {expected_files}")
|
|
|
| file_count = 0
|
| for mat_type in MATERIAL_TYPES:
|
| for vf in VOL_FRACTIONS:
|
| vf_str = vf
|
|
|
| prefix = f"{mat_type}_{vf_str}"
|
| vf_meta, centers_meta, n_fibers = read_dataset_metadata(prefix)
|
| mat = load_ud_material_from_files(prefix)
|
|
|
|
|
| full_angles = FIXED_STACK_ANGLES
|
|
|
|
|
| stack_label_human = stack_label_from_upper(full_angles)
|
| stack_label_file = "_".join(
|
| s.strip().replace("+", "p").replace("-", "m")
|
| for s in stack_label_human.split(",")
|
| )
|
|
|
| combined_blocks = {}
|
|
|
| for mode in ("11", "22", "12"):
|
| try:
|
|
|
| ex, sx, ey, gxy, ezz, g23, g13, e11 = run_uniaxial_test_from_files_5d(
|
| prefix,
|
| full_angles,
|
| mode=mode,
|
| )
|
| except Exception as e:
|
| print(f"Skipping {prefix}, stack={stack_label_human} due to error: {e}")
|
| continue
|
|
|
|
|
| N = 20
|
| x_out = np.linspace(ex[0], ex[-1], N)
|
| sx_out_MPa = np.interp(x_out, ex, sx / 1e6)
|
|
|
|
|
| if mode == "11":
|
| strain_label = "eps_11"
|
| stress_label = "sig_11"
|
| lateral_label = "eps_22"
|
| lateral_series = ey
|
| e33_label = "eps_33"
|
| e33_series = ezz
|
|
|
| elif mode == "22":
|
| strain_label = "eps_22"
|
| stress_label = "sig_22"
|
| lateral_label = "eps_11"
|
| lateral_series = e11
|
| e33_label = "eps_33"
|
| e33_series = ezz
|
|
|
| elif mode == "12":
|
| strain_label = "eps_12"
|
| stress_label = "sig_12"
|
| lateral_label = None
|
| lateral_series = None
|
| e33_label = None
|
| e33_series = None
|
| else:
|
| raise ValueError(f"Unknown mode {mode}")
|
|
|
|
|
| if lateral_series is not None:
|
| lateral_out = np.interp(x_out, ex, lateral_series)
|
| else:
|
| lateral_out = None
|
|
|
| if e33_series is not None:
|
| e33_out = np.interp(x_out, ex, e33_series)
|
| else:
|
| e33_out = None
|
|
|
|
|
| if lateral_label is None:
|
|
|
| header_line = f"{strain_label:<8} {stress_label:<8}"
|
| else:
|
|
|
| header_line = f"{strain_label:<8} {stress_label:<8} {lateral_label:<8} {e33_label:<8}"
|
|
|
| rows = []
|
| if lateral_label is None:
|
|
|
| for eps_val, sig_val in zip(x_out, sx_out_MPa):
|
| line = f"{eps_val:8.6f} {sig_val:8.3f}"
|
| rows.append(line)
|
| else:
|
|
|
| for eps_val, sig_val, lat_val, e33_val in zip(x_out, sx_out_MPa, lateral_out, e33_out):
|
| line = f"{eps_val:8.6f} {sig_val:8.3f} {lat_val:8.6f} {e33_val:8.6f}"
|
| rows.append(line)
|
|
|
|
|
| combined_blocks[mode] = (header_line, rows)
|
|
|
|
|
| OUT_DIR_ALL.mkdir(parents=True, exist_ok=True)
|
| combined_file = OUT_DIR_ALL / (
|
| f"{mat_type}_{vf_str}_{stack_label_file}.txt"
|
| )
|
|
|
| with open(combined_file, "w") as fc:
|
|
|
| for m in ("11", "22", "12"):
|
| header_line, rows = combined_blocks[m]
|
| fc.write(header_line + "\n")
|
| for line in rows:
|
| fc.write(line + "\n")
|
| fc.write("\n")
|
|
|
|
|
| fc.write(f"volume fraction= {vf_meta:.6f}\n")
|
| fc.write(f"material type= {mat_type}\n")
|
| fc.write("loading modes= 11, 22, 12\n")
|
| fc.write(f"stacking sequence= {stack_label_human}\n")
|
|
|
| if n_fibers is not None:
|
| fc.write(f"number of fibers= {n_fibers}\n")
|
| if centers_meta is not None:
|
| fc.write(f"fiber_centers_YZ={centers_meta}\n")
|
|
|
| file_count += 1
|
| print(f"Generated {file_count}/{expected_files} files")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|