use super::functions::*;
use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct XfemFemCouplingZone {
pub blend_nodes: Vec<usize>,
pub ramp_weights: Vec<f64>,
}
impl XfemFemCouplingZone {
pub fn linear_ramp(start_node_id: usize, n_layers: usize) -> Self {
let blend_nodes: Vec<usize> = (start_node_id..start_node_id + n_layers).collect();
let ramp_weights: Vec<f64> = (0..n_layers)
.map(|i| i as f64 / (n_layers - 1).max(1) as f64)
.collect();
Self {
blend_nodes,
ramp_weights,
}
}
pub fn ramp_at(&self, local_idx: usize) -> f64 {
self.ramp_weights.get(local_idx).copied().unwrap_or(1.0)
}
pub fn blend(&self, local_idx: usize, xfem_val: f64, fem_val: f64) -> f64 {
let w = self.ramp_at(local_idx);
w * xfem_val + (1.0 - w) * fem_val
}
}
pub struct BlendingFunction;
impl BlendingFunction {
pub fn ramp(shape_functions: &[f64], enriched_flags: &[bool]) -> f64 {
assert_eq!(shape_functions.len(), enriched_flags.len());
let mut r = 0.0;
for (n, &is_enriched) in shape_functions.iter().zip(enriched_flags.iter()) {
if is_enriched {
r += n;
}
}
r
}
pub fn shifted_enrichment(enrichment_values: &[f64], enrichment_at_nodes: &[f64]) -> Vec<f64> {
assert_eq!(enrichment_values.len(), enrichment_at_nodes.len());
enrichment_values
.iter()
.zip(enrichment_at_nodes.iter())
.map(|(&f, &f_node)| f - f_node)
.collect()
}
}
pub struct EnrichedNode {
pub node_id: usize,
pub enrichment: EnrichmentType,
pub dofs: Vec<f64>,
}
pub struct SifCalculator {
pub e_modulus: f64,
pub poisson: f64,
}
impl SifCalculator {
pub fn new(e: f64, nu: f64) -> Self {
SifCalculator {
e_modulus: e,
poisson: nu,
}
}
pub fn ki_from_displacement(crack: &Crack, u_y: f64, r: f64) -> f64 {
let _ = crack;
u_y * (2.0 * PI / r).sqrt()
}
pub fn plastic_zone_size(&self, k1: f64, yield_stress: f64) -> f64 {
let denom = yield_stress * (2.0 * PI).sqrt();
(k1 / denom).powi(2)
}
}
pub struct LevelSet {
pub phi: Vec<f64>,
pub psi: Vec<f64>,
}
impl LevelSet {
pub fn from_crack(nodes: &[[f64; 3]], crack: &Crack) -> Self {
let last = crack.segments.last().expect("crack must have segments");
let n = nodes.len();
let mut phi = vec![0.0; n];
let mut psi = vec![0.0; n];
for (i, node) in nodes.iter().enumerate() {
phi[i] = signed_distance_to_line(*node, last.start, last.end);
let tip = crack.tip;
let dir = crack.growth_direction;
let dx = node[0] - tip[0];
let dy = node[1] - tip[1];
let dz = node[2] - tip[2];
psi[i] = dx * dir[0] + dy * dir[1] + dz * dir[2];
}
LevelSet { phi, psi }
}
pub fn needs_heaviside(&self, node_idx: usize) -> bool {
self.psi[node_idx] < 0.0
}
pub fn needs_tip_enrichment(&self, node_idx: usize, enrichment_radius: f64) -> bool {
self.psi[node_idx].abs() < enrichment_radius && self.phi[node_idx].abs() < enrichment_radius
}
pub fn update_after_propagation(
&mut self,
nodes: &[[f64; 3]],
new_tip: [f64; 3],
new_direction: [f64; 3],
) {
for (i, node) in nodes.iter().enumerate() {
let dx = node[0] - new_tip[0];
let dy = node[1] - new_tip[1];
let dz = node[2] - new_tip[2];
self.psi[i] = dx * new_direction[0] + dy * new_direction[1] + dz * new_direction[2];
}
}
pub fn interpolate_phi(&self, node_a: usize, node_b: usize, xi: f64) -> f64 {
(1.0 - xi) * self.phi[node_a] + xi * self.phi[node_b]
}
pub fn zero_crossing(&self, node_a: usize, node_b: usize) -> Option<f64> {
let pa = self.phi[node_a];
let pb = self.phi[node_b];
if pa * pb > 0.0 {
return None;
}
let denom = pa - pb;
if denom.abs() < 1e-60 {
return Some(0.5);
}
Some(pa / denom)
}
}
pub enum EnrichmentType {
HeavisideJump,
CrackTip,
}
#[derive(Debug, Clone, Default)]
pub struct PhantomNodeRegistry {
pub phantoms: Vec<PhantomNode>,
}
impl PhantomNodeRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, real_node_id: usize) -> usize {
let idx = self.phantoms.len();
self.phantoms.push(PhantomNode::new(real_node_id));
idx
}
pub fn activate(&mut self, phantom_idx: usize) {
if let Some(p) = self.phantoms.get_mut(phantom_idx) {
p.activate();
}
}
pub fn active_count(&self) -> usize {
self.phantoms.iter().filter(|p| p.active).count()
}
pub fn jump_energy(&self) -> f64 {
self.phantoms
.iter()
.filter(|p| p.active)
.map(|p| {
let j = p.displacement_jump();
0.5 * (j[0] * j[0] + j[1] * j[1] + j[2] * j[2])
})
.sum()
}
}
pub struct CrackFront3D {
pub front_points: Vec<[f64; 3]>,
pub normals: Vec<[f64; 3]>,
pub tangents: Vec<[f64; 3]>,
}
impl CrackFront3D {
pub fn new_straight(start: [f64; 3], end: [f64; 3], n_points: usize) -> Self {
let n = n_points.max(2);
let mut front_points = Vec::with_capacity(n);
let mut normals = Vec::with_capacity(n);
let mut tangents = Vec::with_capacity(n);
let dx = end[0] - start[0];
let dy = end[1] - start[1];
let dz = end[2] - start[2];
let len = (dx * dx + dy * dy + dz * dz).sqrt().max(1e-30);
let tang = [dx / len, dy / len, dz / len];
for i in 0..n {
let t = i as f64 / (n - 1) as f64;
front_points.push([start[0] + t * dx, start[1] + t * dy, start[2] + t * dz]);
normals.push([1.0, 0.0, 0.0]);
tangents.push(tang);
}
Self {
front_points,
normals,
tangents,
}
}
pub fn arc_length(&self) -> f64 {
let mut len = 0.0;
for i in 1..self.front_points.len() {
let dx = self.front_points[i][0] - self.front_points[i - 1][0];
let dy = self.front_points[i][1] - self.front_points[i - 1][1];
let dz = self.front_points[i][2] - self.front_points[i - 1][2];
len += (dx * dx + dy * dy + dz * dz).sqrt();
}
len
}
pub fn n_points(&self) -> usize {
self.front_points.len()
}
pub fn propagate_uniform(&mut self, da: f64) {
for (p, n) in self.front_points.iter_mut().zip(self.normals.iter()) {
p[0] += da * n[0];
p[1] += da * n[1];
p[2] += da * n[2];
}
}
}
pub struct MultiCrackSystem {
pub cracks: Vec<Crack>,
}
impl MultiCrackSystem {
pub fn new() -> Self {
Self { cracks: Vec::new() }
}
pub fn add_crack(&mut self, crack: Crack) {
self.cracks.push(crack);
}
pub fn min_tip_distance(&self) -> f64 {
let n = self.cracks.len();
if n < 2 {
return f64::INFINITY;
}
let mut min_d = f64::MAX;
for i in 0..n {
for j in i + 1..n {
let dx = self.cracks[i].tip[0] - self.cracks[j].tip[0];
let dy = self.cracks[i].tip[1] - self.cracks[j].tip[1];
let dz = self.cracks[i].tip[2] - self.cracks[j].tip[2];
let d = (dx * dx + dy * dy + dz * dz).sqrt();
if d < min_d {
min_d = d;
}
}
}
min_d
}
pub fn are_interacting(&self, interaction_radius: f64) -> bool {
self.min_tip_distance() < interaction_radius
}
pub fn total_length(&self) -> f64 {
self.cracks.iter().map(|c| c.length()).sum()
}
}
pub struct XfemSifExtractor {
pub e_modulus: f64,
pub poisson: f64,
}
impl XfemSifExtractor {
pub fn new(e: f64, nu: f64) -> Self {
Self {
e_modulus: e,
poisson: nu,
}
}
pub fn sif_from_interaction_integral(
&self,
interaction_integral_mode1: f64,
interaction_integral_mode2: f64,
) -> (f64, f64) {
let e_prime = self.e_modulus / (1.0 - self.poisson * self.poisson);
let ki = interaction_integral_mode1 * e_prime / 2.0;
let kii = interaction_integral_mode2 * e_prime / 2.0;
(ki, kii)
}
pub fn ki_from_cod(&self, cod: f64, r: f64) -> f64 {
let mu = self.e_modulus / (2.0 * (1.0 + self.poisson));
let kappa = 3.0 - 4.0 * self.poisson;
let factor = (kappa + 1.0) / mu * (r / (2.0 * PI)).sqrt();
if factor.abs() < 1e-60 {
return 0.0;
}
cod / factor
}
pub fn j_from_sif(&self, ki: f64, kii: f64) -> f64 {
let e_prime = self.e_modulus / (1.0 - self.poisson * self.poisson);
(ki * ki + kii * kii) / e_prime
}
}
pub struct Crack {
pub segments: Vec<CrackSegment>,
pub tip: [f64; 3],
pub growth_direction: [f64; 3],
}
impl Crack {
pub fn new(start: [f64; 3], tip: [f64; 3]) -> Self {
let dx = tip[0] - start[0];
let dy = tip[1] - start[1];
let dz = tip[2] - start[2];
let len = (dx * dx + dy * dy + dz * dz).sqrt().max(f64::EPSILON);
let growth_direction = [dx / len, dy / len, dz / len];
let segment = CrackSegment { start, end: tip };
Crack {
segments: vec![segment],
tip,
growth_direction,
}
}
pub fn propagate(&mut self, da: f64, direction: [f64; 3]) {
let dx = direction[0];
let dy = direction[1];
let dz = direction[2];
let len = (dx * dx + dy * dy + dz * dz).sqrt().max(f64::EPSILON);
let unit = [dx / len, dy / len, dz / len];
let new_tip = [
self.tip[0] + da * unit[0],
self.tip[1] + da * unit[1],
self.tip[2] + da * unit[2],
];
self.segments.push(CrackSegment {
start: self.tip,
end: new_tip,
});
self.tip = new_tip;
self.growth_direction = unit;
}
pub fn length(&self) -> f64 {
self.segments.iter().map(|s| s.length()).sum()
}
pub fn tip_distance(&self, point: [f64; 3]) -> f64 {
let dx = point[0] - self.tip[0];
let dy = point[1] - self.tip[1];
let dz = point[2] - self.tip[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BranchingDecision {
Continue,
Bifurcate {
angle1: f64,
angle2: f64,
},
}
#[derive(Debug, Clone, Copy)]
pub struct MixedModeSif {
pub k1: f64,
pub k2: f64,
pub k3: f64,
}
impl MixedModeSif {
pub fn effective(&self, poisson: f64) -> f64 {
let k3_eff = self.k3 / (1.0 - poisson).max(f64::EPSILON);
(self.k1 * self.k1 + self.k2 * self.k2 + k3_eff * k3_eff).sqrt()
}
pub fn energy_release_rate(&self, e_prime: f64, shear_modulus: f64) -> f64 {
(self.k1 * self.k1 + self.k2 * self.k2) / e_prime.max(f64::EPSILON)
+ self.k3 * self.k3 / (2.0 * shear_modulus.max(f64::EPSILON))
}
}
#[derive(Debug, Clone)]
pub struct SubTriangle {
pub vertices: [[f64; 2]; 3],
pub side: i8,
}
impl SubTriangle {
pub fn new(vertices: [[f64; 2]; 3], side: i8) -> Self {
Self { vertices, side }
}
pub fn area(&self) -> f64 {
let [v0, v1, v2] = &self.vertices;
let a = [v1[0] - v0[0], v1[1] - v0[1]];
let b = [v2[0] - v0[0], v2[1] - v0[1]];
0.5 * (a[0] * b[1] - a[1] * b[0]).abs()
}
pub fn centroid(&self) -> [f64; 2] {
let [v0, v1, v2] = &self.vertices;
[(v0[0] + v1[0] + v2[0]) / 3.0, (v0[1] + v1[1] + v2[1]) / 3.0]
}
pub fn gauss_points(&self) -> [(f64, f64, f64); 3] {
let area = self.area();
let w = area / 3.0;
let [v0, v1, v2] = &self.vertices;
let pts = [
[(v0[0] + v1[0]) / 2.0, (v0[1] + v1[1]) / 2.0],
[(v1[0] + v2[0]) / 2.0, (v1[1] + v2[1]) / 2.0],
[(v2[0] + v0[0]) / 2.0, (v2[1] + v0[1]) / 2.0],
];
[
(pts[0][0], pts[0][1], w),
(pts[1][0], pts[1][1], w),
(pts[2][0], pts[2][1], w),
]
}
}
#[derive(Debug, Clone)]
pub struct CrackFrontNode {
pub position: [f64; 3],
pub tangent: [f64; 3],
pub branch_dofs: [f64; 4],
pub arc_length: f64,
}
impl CrackFrontNode {
pub fn new(position: [f64; 3], tangent: [f64; 3], arc_length: f64) -> Self {
let t_norm = vec3_norm(tangent);
let t_unit = if t_norm > f64::EPSILON {
[
tangent[0] / t_norm,
tangent[1] / t_norm,
tangent[2] / t_norm,
]
} else {
[1.0, 0.0, 0.0]
};
Self {
position,
tangent: t_unit,
branch_dofs: [0.0; 4],
arc_length,
}
}
pub fn branch_functions(r: f64, theta: f64) -> [f64; 4] {
let sr = r.sqrt();
let ht = theta / 2.0;
[
sr * ht.sin(),
sr * ht.cos(),
sr * ht.sin() * theta.sin(),
sr * ht.cos() * theta.sin(),
]
}
pub fn enriched_displacement(&self, r: f64, theta: f64) -> [f64; 4] {
let bf = Self::branch_functions(r, theta);
[
self.branch_dofs[0] * bf[0],
self.branch_dofs[1] * bf[1],
self.branch_dofs[2] * bf[2],
self.branch_dofs[3] * bf[3],
]
}
}
#[derive(Debug, Clone, Default)]
pub struct CrackFrontEnrichment {
pub nodes: Vec<CrackFrontNode>,
}
impl CrackFrontEnrichment {
pub fn new() -> Self {
Self::default()
}
pub fn push_node(&mut self, position: [f64; 3], tangent: [f64; 3]) {
let arc = if let Some(last) = self.nodes.last() {
let d = [
position[0] - last.position[0],
position[1] - last.position[1],
position[2] - last.position[2],
];
last.arc_length + vec3_norm(d)
} else {
0.0
};
self.nodes.push(CrackFrontNode::new(position, tangent, arc));
}
pub fn total_arc_length(&self) -> f64 {
self.nodes.last().map(|n| n.arc_length).unwrap_or(0.0)
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn position_at(&self, s: f64) -> Option<[f64; 3]> {
if self.nodes.is_empty() {
return None;
}
let s = s.clamp(0.0, self.total_arc_length());
for i in 1..self.nodes.len() {
let n0 = &self.nodes[i - 1];
let n1 = &self.nodes[i];
if s <= n1.arc_length {
let t = (s - n0.arc_length) / (n1.arc_length - n0.arc_length).max(f64::EPSILON);
return Some([
n0.position[0] + t * (n1.position[0] - n0.position[0]),
n0.position[1] + t * (n1.position[1] - n0.position[1]),
n0.position[2] + t * (n1.position[2] - n0.position[2]),
]);
}
}
self.nodes.last().map(|n| n.position)
}
}
pub struct CrackSegment {
pub start: [f64; 3],
pub end: [f64; 3],
}
impl CrackSegment {
pub fn length(&self) -> f64 {
let dx = self.end[0] - self.start[0];
let dy = self.end[1] - self.start[1];
let dz = self.end[2] - self.start[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct PhantomNode {
pub real_node_id: usize,
pub dofs: [f64; 3],
pub active: bool,
}
impl PhantomNode {
pub fn new(real_node_id: usize) -> Self {
Self {
real_node_id,
dofs: [0.0; 3],
active: false,
}
}
pub fn activate(&mut self) {
self.active = true;
}
pub fn displacement_jump(&self) -> [f64; 3] {
[self.dofs[0] * 2.0, self.dofs[1] * 2.0, self.dofs[2] * 2.0]
}
}