use super::functions::*;
#[derive(Debug, Clone)]
pub struct BezierExtraction {
pub degree: usize,
pub knot: Vec<f64>,
pub operators: Vec<Vec<f64>>,
pub num_elements: usize,
}
impl BezierExtraction {
pub fn compute(knot: &[f64], degree: usize) -> Self {
let p = degree;
let mut internal_knots: Vec<(f64, usize)> = Vec::new();
let m = knot.len();
let mut i = p + 1;
while i < m - p - 1 {
let kval = knot[i];
let mut mult = 0usize;
while i + mult < m && (knot[i + mult] - kval).abs() < 1e-14 {
mult += 1;
}
if mult < p {
internal_knots.push((kval, mult));
}
i += mult;
}
let n_elem = knot
.windows(2)
.filter(|w| (w[1] - w[0]).abs() > 1e-14)
.count();
let size = (p + 1) * (p + 1);
let identity: Vec<f64> = {
let mut id = vec![0.0f64; size];
for diag in 0..=p {
id[diag * (p + 1) + diag] = 1.0;
}
id
};
let operators: Vec<Vec<f64>> = vec![identity; n_elem.max(1)];
let _ = internal_knots;
BezierExtraction {
degree,
knot: knot.to_vec(),
operators,
num_elements: n_elem.max(1),
}
}
pub fn operator(&self, elem_idx: usize) -> &[f64] {
&self.operators[elem_idx.min(self.operators.len() - 1)]
}
pub fn extract_bezier_ctrl(
&self,
elem_idx: usize,
ctrl_pts: &[f64],
span_start: usize,
) -> Vec<f64> {
let p = self.degree;
let op = self.operator(elem_idx);
let mut bezier_ctrl = vec![0.0f64; p + 1];
for i in 0..=p {
for j in 0..=p {
let ctrl_idx = span_start + j;
if ctrl_idx < ctrl_pts.len() {
bezier_ctrl[i] += op[i * (p + 1) + j] * ctrl_pts[ctrl_idx];
}
}
}
bezier_ctrl
}
}
#[derive(Debug, Clone)]
pub struct BsplineBasis {
pub degree: usize,
pub knot: Vec<f64>,
pub num_basis: usize,
}
impl BsplineBasis {
pub fn new(degree: usize, knot: Vec<f64>) -> Self {
let num_basis = knot.len() - degree - 1;
Self {
degree,
knot,
num_basis,
}
}
pub fn evaluate(&self, t: f64) -> (usize, Vec<f64>) {
let n = self.num_basis - 1;
let span = find_knot_span(n, self.degree, t, &self.knot);
let vals = basis_functions(span, self.degree, t, &self.knot);
(span, vals)
}
pub fn evaluate_deriv(&self, t: f64) -> (usize, Vec<f64>, Vec<f64>) {
let n = self.num_basis - 1;
let span = find_knot_span(n, self.degree, t, &self.knot);
let ders = basis_derivatives(span, self.degree, t, &self.knot, 1);
let vals = ders[0].clone();
let derivs = ders[1].clone();
(span, vals, derivs)
}
pub fn evaluate_derivs(&self, t: f64, d: usize) -> (usize, Vec<Vec<f64>>) {
let n = self.num_basis - 1;
let span = find_knot_span(n, self.degree, t, &self.knot);
let ders = basis_derivatives(span, self.degree, t, &self.knot, d);
(span, ders)
}
pub fn domain(&self) -> (f64, f64) {
let p = self.degree;
(self.knot[p], self.knot[self.knot.len() - 1 - p])
}
pub fn num_spans(&self) -> usize {
self.knot
.windows(2)
.filter(|w| (w[1] - w[0]).abs() > 1e-14)
.count()
}
}
#[derive(Debug, Clone)]
pub struct IgaAdaptive {
pub base_surface: NurbsSurface,
pub refined_elements: Vec<(f64, f64, f64, f64)>,
pub refinement_levels: Vec<usize>,
pub error_indicators: Vec<f64>,
}
impl IgaAdaptive {
pub fn new(base_surface: NurbsSurface) -> Self {
let (u0, u1) = base_surface.u_basis.domain();
let (v0, v1) = base_surface.v_basis.domain();
Self {
base_surface,
refined_elements: vec![(u0, u1, v0, v1)],
refinement_levels: vec![0],
error_indicators: vec![0.0],
}
}
pub fn mark_for_refinement(&self, threshold: f64) -> Vec<usize> {
self.error_indicators
.iter()
.enumerate()
.filter(|(_, e)| **e > threshold)
.map(|(i, _)| i)
.collect()
}
pub fn refine_element(&mut self, elem_idx: usize) {
if elem_idx >= self.refined_elements.len() {
return;
}
let (u0, u1, v0, v1) = self.refined_elements[elem_idx];
let um = 0.5 * (u0 + u1);
let vm = 0.5 * (v0 + v1);
let level = self.refinement_levels[elem_idx] + 1;
self.refined_elements.remove(elem_idx);
self.refinement_levels.remove(elem_idx);
self.error_indicators.remove(elem_idx);
for &(ua, ub, va, vb) in &[
(u0, um, v0, vm),
(um, u1, v0, vm),
(u0, um, vm, v1),
(um, u1, vm, v1),
] {
self.refined_elements.push((ua, ub, va, vb));
self.refinement_levels.push(level);
self.error_indicators.push(0.0);
}
}
pub fn compute_error_indicator(&self, elem_idx: usize) -> f64 {
if elem_idx >= self.refined_elements.len() {
return 0.0;
}
let (u0, u1, v0, v1) = self.refined_elements[elem_idx];
let u_mid = 0.5 * (u0 + u1);
let v_mid = 0.5 * (v0 + v1);
let eps = 1e-5;
let p = self.base_surface.point_at(u_mid, v_mid);
let p_u = self.base_surface.point_at(u_mid + eps, v_mid);
let p_uu = self.base_surface.point_at(u_mid + 2.0 * eps, v_mid);
let d2_x = (p_uu[0] - 2.0 * p_u[0] + p[0]) / (eps * eps);
let d2_y = (p_uu[1] - 2.0 * p_u[1] + p[1]) / (eps * eps);
let d2_z = (p_uu[2] - 2.0 * p_u[2] + p[2]) / (eps * eps);
(d2_x * d2_x + d2_y * d2_y + d2_z * d2_z).sqrt() * (u1 - u0) * (v1 - v0)
}
pub fn update_error_indicators(&mut self) {
let n = self.refined_elements.len();
for i in 0..n {
self.error_indicators[i] = self.compute_error_indicator(i);
}
}
pub fn num_elements(&self) -> usize {
self.refined_elements.len()
}
pub fn max_level(&self) -> usize {
*self.refinement_levels.iter().max().unwrap_or(&0)
}
}
#[derive(Debug, Clone)]
pub struct NurbsBRep {
pub faces: Vec<NurbsSurface>,
pub edges: Vec<NurbsCurve>,
pub vertices: Vec<[f64; 3]>,
pub face_edges: Vec<Vec<usize>>,
pub edge_vertices: Vec<(usize, usize)>,
}
impl NurbsBRep {
pub fn new() -> Self {
NurbsBRep {
faces: Vec::new(),
edges: Vec::new(),
vertices: Vec::new(),
face_edges: Vec::new(),
edge_vertices: Vec::new(),
}
}
pub fn add_face(&mut self, surface: NurbsSurface) -> usize {
let idx = self.faces.len();
self.faces.push(surface);
self.face_edges.push(Vec::new());
idx
}
pub fn add_edge(&mut self, curve: NurbsCurve, start_vtx: usize, end_vtx: usize) -> usize {
let idx = self.edges.len();
self.edges.push(curve);
self.edge_vertices.push((start_vtx, end_vtx));
idx
}
pub fn add_vertex(&mut self, point: [f64; 3]) -> usize {
let idx = self.vertices.len();
self.vertices.push(point);
idx
}
pub fn attach_edge_to_face(&mut self, face_idx: usize, edge_idx: usize) {
if face_idx < self.face_edges.len() {
self.face_edges[face_idx].push(edge_idx);
}
}
pub fn num_faces(&self) -> usize {
self.faces.len()
}
pub fn num_edges(&self) -> usize {
self.edges.len()
}
pub fn num_vertices(&self) -> usize {
self.vertices.len()
}
pub fn is_valid(&self) -> bool {
for edges in &self.face_edges {
for &e in edges {
if e >= self.edges.len() {
return false;
}
}
}
for &(sv, ev) in &self.edge_vertices {
if sv >= self.vertices.len() || ev >= self.vertices.len() {
return false;
}
}
true
}
pub fn total_surface_area(&self, n_gauss: usize) -> f64 {
let (gp, gw) = gauss_legendre_1d(n_gauss);
let mut area = 0.0;
for face in &self.faces {
let (u0, u1) = face.u_basis.domain();
let (v0, v1) = face.v_basis.domain();
for i in 0..n_gauss {
for j in 0..n_gauss {
let u = 0.5 * (u0 + u1) + 0.5 * (u1 - u0) * gp[i];
let v = 0.5 * (v0 + v1) + 0.5 * (v1 - v0) * gp[j];
let jac = face.jacobian_det(u, v);
let w = 0.25 * (u1 - u0) * (v1 - v0) * gw[i] * gw[j];
area += jac * w;
}
}
}
area
}
}
#[derive(Debug, Clone)]
pub struct TSplineControlPoint {
pub position: [f64; 3],
pub weight: f64,
pub s_knots: [f64; 5],
pub t_knots: [f64; 5],
}
impl TSplineControlPoint {
pub fn new(position: [f64; 3], weight: f64, s_knots: [f64; 5], t_knots: [f64; 5]) -> Self {
TSplineControlPoint {
position,
weight,
s_knots,
t_knots,
}
}
pub fn basis_value(&self, s: f64, t: f64) -> f64 {
let bs = cubic_bspline_basis(s, &self.s_knots);
let bt = cubic_bspline_basis(t, &self.t_knots);
bs * bt
}
pub fn weighted_basis(&self, s: f64, t: f64) -> f64 {
self.weight * self.basis_value(s, t)
}
}
#[derive(Debug, Clone)]
pub struct NurbsSurface {
pub control_net: Vec<Vec<[f64; 3]>>,
pub weights: Vec<Vec<f64>>,
pub u_basis: BsplineBasis,
pub v_basis: BsplineBasis,
}
impl NurbsSurface {
pub fn new(
control_net: Vec<Vec<[f64; 3]>>,
weights: Vec<Vec<f64>>,
u_degree: usize,
v_degree: usize,
u_knot: Vec<f64>,
v_knot: Vec<f64>,
) -> Self {
let u_basis = BsplineBasis::new(u_degree, u_knot);
let v_basis = BsplineBasis::new(v_degree, v_knot);
Self {
control_net,
weights,
u_basis,
v_basis,
}
}
pub fn point_at(&self, u: f64, v: f64) -> [f64; 3] {
let nu = self.control_net.len() - 1;
let nv = self.control_net[0].len() - 1;
let pu = self.u_basis.degree;
let pv = self.v_basis.degree;
let u_span = find_knot_span(nu, pu, u, &self.u_basis.knot);
let v_span = find_knot_span(nv, pv, v, &self.v_basis.knot);
let nu_basis = basis_functions(u_span, pu, u, &self.u_basis.knot);
let nv_basis = basis_functions(v_span, pv, v, &self.v_basis.knot);
let mut pw = [0.0f64; 3];
let mut w_sum: f64 = 0.0;
for (i, &nu_i) in nu_basis.iter().enumerate().take(pu + 1) {
for (j, &nv_j) in nv_basis.iter().enumerate().take(pv + 1) {
let ui = u_span - pu + i;
let vi = v_span - pv + j;
let w = self.weights[ui][vi] * nu_i * nv_j;
pw[0] += w * self.control_net[ui][vi][0];
pw[1] += w * self.control_net[ui][vi][1];
pw[2] += w * self.control_net[ui][vi][2];
w_sum += w;
}
}
if w_sum.abs() < 1e-15 {
return pw;
}
[pw[0] / w_sum, pw[1] / w_sum, pw[2] / w_sum]
}
pub fn normal(&self, u: f64, v: f64) -> ([f64; 3], [f64; 3], [f64; 3]) {
let eps = 1e-6;
let (t0, t1) = self.u_basis.domain();
let (s0, s1) = self.v_basis.domain();
let u_plus = (u + eps).min(t1);
let u_minus = (u - eps).max(t0);
let v_plus = (v + eps).min(s1);
let v_minus = (v - eps).max(s0);
let du = u_plus - u_minus;
let dv = v_plus - v_minus;
let pu = self.point_at(u_plus, v);
let pm_u = self.point_at(u_minus, v);
let pv_p = self.point_at(u, v_plus);
let pv_m = self.point_at(u, v_minus);
let tang_u = scale3(sub3(pu, pm_u), 1.0 / du);
let tang_v = scale3(sub3(pv_p, pv_m), 1.0 / dv);
let n = normalize3(cross3(tang_u, tang_v));
(tang_u, tang_v, n)
}
pub fn jacobian_det(&self, u: f64, v: f64) -> f64 {
let (_, _, n) = self.normal(u, v);
norm3(n)
}
}
#[derive(Debug, Clone)]
pub struct DegreeElevation;
impl DegreeElevation {
pub fn elevate_once(curve: &NurbsCurve) -> NurbsCurve {
let p = curve.basis.degree;
let new_p = p + 1;
let mut unique_knots: Vec<(f64, usize)> = Vec::new();
for &k in &curve.basis.knot {
if let Some(last) = unique_knots.last_mut()
&& (k - last.0).abs() < 1e-14
{
last.1 += 1;
continue;
}
unique_knots.push((k, 1));
}
let mut new_knot = Vec::new();
for (val, mult) in &unique_knots {
let new_mult = (*mult + 1).min(new_p + 1);
for _ in 0..new_mult {
new_knot.push(*val);
}
}
let n_new_ctrl = new_knot.len() - new_p - 1;
let mut new_ctrl = Vec::with_capacity(n_new_ctrl);
let mut new_weights = Vec::with_capacity(n_new_ctrl);
let n_old = curve.control_points.len();
for i in 0..n_new_ctrl {
let t_param = i as f64 / (n_new_ctrl - 1).max(1) as f64;
let (t0, t1) = curve.basis.domain();
let t = t0 + (t1 - t0) * t_param;
let pt = curve.point_at(t.min(t1 - 1e-10).max(t0));
new_ctrl.push(pt);
let old_idx = (t_param * (n_old - 1) as f64).round() as usize;
new_weights.push(curve.weights[old_idx.min(n_old - 1)]);
}
NurbsCurve::new(new_ctrl, new_weights, new_p, new_knot)
}
pub fn elevate(curve: &NurbsCurve, times: usize) -> NurbsCurve {
let mut result = curve.clone();
for _ in 0..times {
result = Self::elevate_once(&result);
}
result
}
}
#[derive(Debug, Clone)]
pub struct IgaAssembly {
pub n_dof: usize,
pub k_coo: Vec<(usize, usize, f64)>,
pub m_coo: Vec<(usize, usize, f64)>,
}
impl IgaAssembly {
pub fn new(n_dof: usize) -> Self {
Self {
n_dof,
k_coo: Vec::new(),
m_coo: Vec::new(),
}
}
pub fn add_stiffness(&mut self, dof_map: &[usize], k_local: &[f64]) {
let nd = dof_map.len();
for i in 0..nd {
for j in 0..nd {
let val = k_local[i * nd + j];
if val.abs() > 1e-15 {
self.k_coo.push((dof_map[i], dof_map[j], val));
}
}
}
}
pub fn add_mass(&mut self, dof_map: &[usize], m_local: &[f64]) {
let nd = dof_map.len();
for i in 0..nd {
for j in 0..nd {
let val = m_local[i * nd + j];
if val.abs() > 1e-15 {
self.m_coo.push((dof_map[i], dof_map[j], val));
}
}
}
}
pub fn global_stiffness(&self) -> Vec<f64> {
let n = self.n_dof;
let mut k = vec![0.0f64; n * n];
for &(i, j, v) in &self.k_coo {
if i < n && j < n {
k[i * n + j] += v;
}
}
k
}
pub fn global_mass(&self) -> Vec<f64> {
let n = self.n_dof;
let mut m = vec![0.0f64; n * n];
for &(i, j, v) in &self.m_coo {
if i < n && j < n {
m[i * n + j] += v;
}
}
m
}
pub fn assemble_patch(
&mut self,
assembler: &IgaAssembler,
elem: &IgaElement,
u_range: (f64, f64),
v_range: (f64, f64),
dof_map: &[usize],
) {
let k_local = assembler.element_stiffness(elem, u_range, v_range);
let m_local = assembler.element_mass(elem, u_range, v_range);
self.add_stiffness(dof_map, &k_local);
self.add_mass(dof_map, &m_local);
}
pub fn clear(&mut self) {
self.k_coo.clear();
self.m_coo.clear();
}
}
#[derive(Debug, Clone)]
pub struct IgaAssembler {
pub young_modulus: f64,
pub poisson_ratio: f64,
pub density: f64,
pub thickness: f64,
pub n_gauss: usize,
}
impl IgaAssembler {
pub fn new(
young_modulus: f64,
poisson_ratio: f64,
density: f64,
thickness: f64,
n_gauss: usize,
) -> Self {
Self {
young_modulus,
poisson_ratio,
density,
thickness,
n_gauss,
}
}
pub fn element_stiffness(
&self,
elem: &IgaElement,
u_range: (f64, f64),
v_range: (f64, f64),
) -> Vec<f64> {
let (u0, u1) = u_range;
let (v0, v1) = v_range;
let (gp, gw) = gauss_legendre_1d(self.n_gauss);
let pu = elem.surface.u_basis.degree;
let pv = elem.surface.v_basis.degree;
let n_local = (pu + 1) * (pv + 1);
let n_dof = n_local * 2;
let mut k = vec![0.0f64; n_dof * n_dof];
let d = self.plane_stress_d();
for i in 0..self.n_gauss {
for j in 0..self.n_gauss {
let u = 0.5 * (u1 + u0) + 0.5 * (u1 - u0) * gp[i];
let v = 0.5 * (v1 + v0) + 0.5 * (v1 - v0) * gp[j];
let jac_factor = 0.25 * (u1 - u0) * (v1 - v0) * gw[i] * gw[j];
let (r, dr_du, dr_dv) = elem.shape_functions(u, v);
let jac = elem.jacobian(u, v);
let j_det = (jac[0][0] * jac[1][1] - jac[0][1] * jac[1][0]).abs();
let jac_inv = if j_det > 1e-15 {
let inv11 = jac[1][1] / j_det;
let inv12 = -jac[0][1] / j_det;
let inv21 = -jac[1][0] / j_det;
let inv22 = jac[0][0] / j_det;
[[inv11, inv12], [inv21, inv22]]
} else {
[[1.0, 0.0], [0.0, 1.0]]
};
let mut b = vec![0.0f64; 3 * n_dof];
for a in 0..n_local {
let dn_dx = jac_inv[0][0] * dr_du[a] + jac_inv[0][1] * dr_dv[a];
let dn_dy = jac_inv[1][0] * dr_du[a] + jac_inv[1][1] * dr_dv[a];
b[2 * a] = dn_dx;
b[n_dof + 2 * a + 1] = dn_dy;
b[2 * n_dof + 2 * a] = dn_dy;
b[2 * n_dof + 2 * a + 1] = dn_dx;
let _ = r[a];
}
for row in 0..n_dof {
for col in 0..n_dof {
let mut val = 0.0;
for m in 0..3 {
for n in 0..3 {
val += b[m * n_dof + row] * d[m * 3 + n] * b[n * n_dof + col];
}
}
k[row * n_dof + col] += val * j_det * jac_factor;
}
}
}
}
k
}
pub fn element_mass(
&self,
elem: &IgaElement,
u_range: (f64, f64),
v_range: (f64, f64),
) -> Vec<f64> {
let (u0, u1) = u_range;
let (v0, v1) = v_range;
let (gp, gw) = gauss_legendre_1d(self.n_gauss);
let pu = elem.surface.u_basis.degree;
let pv = elem.surface.v_basis.degree;
let n_local = (pu + 1) * (pv + 1);
let n_dof = n_local * 2;
let mut m = vec![0.0f64; n_dof * n_dof];
for i in 0..self.n_gauss {
for j in 0..self.n_gauss {
let u = 0.5 * (u1 + u0) + 0.5 * (u1 - u0) * gp[i];
let v = 0.5 * (v1 + v0) + 0.5 * (v1 - v0) * gp[j];
let jac_factor = 0.25 * (u1 - u0) * (v1 - v0) * gw[i] * gw[j];
let (r, _, _) = elem.shape_functions(u, v);
let jac = elem.jacobian(u, v);
let j_det = (jac[0][0] * jac[1][1] - jac[0][1] * jac[1][0]).abs();
let rho_t = self.density * self.thickness;
for a in 0..n_local {
for b in 0..n_local {
let val = rho_t * r[a] * r[b] * j_det * jac_factor;
m[(2 * a) * n_dof + 2 * b] += val;
m[(2 * a + 1) * n_dof + 2 * b + 1] += val;
}
}
}
}
m
}
fn plane_stress_d(&self) -> Vec<f64> {
let e = self.young_modulus;
let nu = self.poisson_ratio;
let c = e / (1.0 - nu * nu);
vec![
c,
c * nu,
0.0,
c * nu,
c,
0.0,
0.0,
0.0,
c * 0.5 * (1.0 - nu),
]
}
}
#[derive(Debug, Clone)]
pub struct IgaContactFormulation {
pub penalty: f64,
pub friction: f64,
pub slave_surface: NurbsSurface,
pub master_surface: NurbsSurface,
pub n_gauss: usize,
}
impl IgaContactFormulation {
pub fn new(
penalty: f64,
friction: f64,
slave_surface: NurbsSurface,
master_surface: NurbsSurface,
n_gauss: usize,
) -> Self {
Self {
penalty,
friction,
slave_surface,
master_surface,
n_gauss,
}
}
pub fn compute_gap(&self, u_slave: f64, v_slave: f64) -> (f64, [f64; 3]) {
let x_slave = self.slave_surface.point_at(u_slave, v_slave);
let (_, _, n) = self.slave_surface.normal(u_slave, v_slave);
let x_master = self.project_onto_master(x_slave);
let gap_vec = sub3(x_slave, x_master);
let gap = dot3(gap_vec, n);
(gap, n)
}
fn project_onto_master(&self, x: [f64; 3]) -> [f64; 3] {
let (u0, u1) = self.master_surface.u_basis.domain();
let (v0, v1) = self.master_surface.v_basis.domain();
let n_search = 10;
let mut best_dist = f64::MAX;
let mut best_pt = [0.0f64; 3];
for i in 0..=n_search {
for j in 0..=n_search {
let u = u0 + (u1 - u0) * i as f64 / n_search as f64;
let v = v0 + (v1 - v0) * j as f64 / n_search as f64;
let pt = self.master_surface.point_at(u, v);
let d = norm3(sub3(pt, x));
if d < best_dist {
best_dist = d;
best_pt = pt;
}
}
}
best_pt
}
pub fn contact_force(&self, u_slave: f64, v_slave: f64) -> [f64; 3] {
let (gap, n) = self.compute_gap(u_slave, v_slave);
if gap > 0.0 {
scale3(n, -self.penalty * gap)
} else {
[0.0; 3]
}
}
pub fn integrate_contact_force(&self) -> [f64; 3] {
let (u0, u1) = self.slave_surface.u_basis.domain();
let (v0, v1) = self.slave_surface.v_basis.domain();
let (gp, gw) = gauss_legendre_1d(self.n_gauss);
let mut total = [0.0f64; 3];
for i in 0..self.n_gauss {
for j in 0..self.n_gauss {
let u = 0.5 * (u0 + u1) + 0.5 * (u1 - u0) * gp[i];
let v = 0.5 * (v0 + v1) + 0.5 * (v1 - v0) * gp[j];
let jac = self.slave_surface.jacobian_det(u, v);
let f = self.contact_force(u, v);
let w = 0.25 * (u1 - u0) * (v1 - v0) * gw[i] * gw[j];
total[0] += f[0] * jac * w;
total[1] += f[1] * jac * w;
total[2] += f[2] * jac * w;
}
}
total
}
}
#[derive(Debug, Clone)]
pub struct IgaVolumeMassMatrix {
pub density: f64,
pub n_gauss: usize,
}
impl IgaVolumeMassMatrix {
pub fn new(density: f64, n_gauss: usize) -> Self {
IgaVolumeMassMatrix { density, n_gauss }
}
pub fn assemble(&self, vol: &NurbsVolume) -> Vec<f64> {
let pu = vol.u_basis.degree;
let pv = vol.v_basis.degree;
let pw_deg = vol.w_basis.degree;
let n_ctrl = (pu + 1) * (pv + 1) * (pw_deg + 1);
let n_dof = n_ctrl * 3;
let mut mass = vec![0.0f64; n_dof * n_dof];
let (u0, u1) = vol.u_basis.domain();
let (v0, v1) = vol.v_basis.domain();
let (w0, w1) = vol.w_basis.domain();
let (gp, gw) = gauss_legendre_1d(self.n_gauss);
for gi in 0..self.n_gauss {
for gj in 0..self.n_gauss {
for gk in 0..self.n_gauss {
let u = 0.5 * (u0 + u1) + 0.5 * (u1 - u0) * gp[gi];
let v = 0.5 * (v0 + v1) + 0.5 * (v1 - v0) * gp[gj];
let w = 0.5 * (w0 + w1) + 0.5 * (w1 - w0) * gp[gk];
let jac_w =
0.125 * (u1 - u0) * (v1 - v0) * (w1 - w0) * gw[gi] * gw[gj] * gw[gk];
let j_mat = vol.jacobian(u, v, w);
let j_det = vol.jacobian_det(u, v, w).abs();
let nu = vol.ctrl.len() - 1;
let nv = vol.ctrl[0].len() - 1;
let nw = vol.ctrl[0][0].len() - 1;
let u_span = find_knot_span(nu, pu, u, &vol.u_basis.knot);
let v_span = find_knot_span(nv, pv, v, &vol.v_basis.knot);
let w_span = find_knot_span(nw, pw_deg, w, &vol.w_basis.knot);
let bu = basis_functions(u_span, pu, u, &vol.u_basis.knot);
let bv = basis_functions(v_span, pv, v, &vol.v_basis.knot);
let bw_vals = basis_functions(w_span, pw_deg, w, &vol.w_basis.knot);
let mut wt = 0.0f64;
let mut nf = vec![0.0f64; n_ctrl];
let mut local_a = 0usize;
for (ii, &bu_ii) in bu.iter().enumerate().take(pu + 1) {
for (jj, &bv_jj) in bv.iter().enumerate().take(pv + 1) {
for (kk, &bw_kk) in bw_vals.iter().enumerate().take(pw_deg + 1) {
let ui = u_span - pu + ii;
let vi = v_span - pv + jj;
let wi_idx = w_span - pw_deg + kk;
let w_ijk = vol.weights[ui][vi][wi_idx] * bu_ii * bv_jj * bw_kk;
nf[local_a] = w_ijk;
wt += w_ijk;
local_a += 1;
}
}
}
if wt.abs() > 1e-15 {
for nf_val in nf.iter_mut().take(n_ctrl) {
*nf_val /= wt;
}
}
let rho = self.density;
for a in 0..n_ctrl {
for b in 0..n_ctrl {
let val = rho * nf[a] * nf[b] * j_det * jac_w;
for d in 0..3 {
mass[(3 * a + d) * n_dof + (3 * b + d)] += val;
}
}
}
let _ = j_mat;
}
}
}
mass
}
}
#[derive(Debug, Clone)]
pub struct PatchConnectivity {
pub patch_ids: (usize, usize),
pub coupling: CouplingType,
pub interface_map: Vec<(usize, usize)>,
pub dof_offset_a: usize,
pub dof_offset_b: usize,
}
impl PatchConnectivity {
pub fn new(
patch_ids: (usize, usize),
coupling: CouplingType,
interface_map: Vec<(usize, usize)>,
dof_offset_a: usize,
dof_offset_b: usize,
) -> Self {
Self {
patch_ids,
coupling,
interface_map,
dof_offset_a,
dof_offset_b,
}
}
pub fn num_pairs(&self) -> usize {
self.interface_map.len()
}
pub fn is_interface_a(&self, idx: usize) -> bool {
self.interface_map.iter().any(|(a, _)| *a == idx)
}
pub fn penalty_coefficient(&self, pair_index: usize) -> f64 {
if pair_index >= self.interface_map.len() {
return 0.0;
}
match self.coupling {
CouplingType::Penalty(beta) => beta,
_ => 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct NurbsVolume {
pub ctrl: Vec<Vec<Vec<[f64; 3]>>>,
pub weights: Vec<Vec<Vec<f64>>>,
pub u_basis: BsplineBasis,
pub v_basis: BsplineBasis,
pub w_basis: BsplineBasis,
}
impl NurbsVolume {
pub fn new(
ctrl: Vec<Vec<Vec<[f64; 3]>>>,
weights: Vec<Vec<Vec<f64>>>,
u_degree: usize,
v_degree: usize,
w_degree: usize,
u_knot: Vec<f64>,
v_knot: Vec<f64>,
w_knot: Vec<f64>,
) -> Self {
let u_basis = BsplineBasis::new(u_degree, u_knot);
let v_basis = BsplineBasis::new(v_degree, v_knot);
let w_basis = BsplineBasis::new(w_degree, w_knot);
Self {
ctrl,
weights,
u_basis,
v_basis,
w_basis,
}
}
pub fn point_at(&self, u: f64, v: f64, w: f64) -> [f64; 3] {
let nu = self.ctrl.len() - 1;
let nv = self.ctrl[0].len() - 1;
let nw = self.ctrl[0][0].len() - 1;
let pu = self.u_basis.degree;
let pv = self.v_basis.degree;
let pw = self.w_basis.degree;
let u_span = find_knot_span(nu, pu, u, &self.u_basis.knot);
let v_span = find_knot_span(nv, pv, v, &self.v_basis.knot);
let w_span = find_knot_span(nw, pw, w, &self.w_basis.knot);
let bu = basis_functions(u_span, pu, u, &self.u_basis.knot);
let bv = basis_functions(v_span, pv, v, &self.v_basis.knot);
let bw = basis_functions(w_span, pw, w, &self.w_basis.knot);
let mut pt = [0.0f64; 3];
let mut wt = 0.0f64;
for (i, &bu_i) in bu.iter().enumerate().take(pu + 1) {
for (j, &bv_j) in bv.iter().enumerate().take(pv + 1) {
for (k, &bw_k) in bw.iter().enumerate().take(pw + 1) {
let ui = u_span - pu + i;
let vi = v_span - pv + j;
let wi_idx = w_span - pw + k;
let w_ijk = self.weights[ui][vi][wi_idx] * bu_i * bv_j * bw_k;
pt[0] += w_ijk * self.ctrl[ui][vi][wi_idx][0];
pt[1] += w_ijk * self.ctrl[ui][vi][wi_idx][1];
pt[2] += w_ijk * self.ctrl[ui][vi][wi_idx][2];
wt += w_ijk;
}
}
}
if wt.abs() < 1e-15 {
return pt;
}
[pt[0] / wt, pt[1] / wt, pt[2] / wt]
}
pub fn jacobian(&self, u: f64, v: f64, w: f64) -> [[f64; 3]; 3] {
let eps = 1e-6;
let (u0, u1) = self.u_basis.domain();
let (v0, v1) = self.v_basis.domain();
let (w0, w1) = self.w_basis.domain();
let pu = (u + eps).min(u1);
let mu = (u - eps).max(u0);
let pv = (v + eps).min(v1);
let mv = (v - eps).max(v0);
let pw_p = (w + eps).min(w1);
let mw = (w - eps).max(w0);
let du_col = scale3(
sub3(self.point_at(pu, v, w), self.point_at(mu, v, w)),
1.0 / (pu - mu),
);
let dv_col = scale3(
sub3(self.point_at(u, pv, w), self.point_at(u, mv, w)),
1.0 / (pv - mv),
);
let dw_col = scale3(
sub3(self.point_at(u, v, pw_p), self.point_at(u, v, mw)),
1.0 / (pw_p - mw),
);
[du_col, dv_col, dw_col]
}
pub fn jacobian_det(&self, u: f64, v: f64, w: f64) -> f64 {
let j = self.jacobian(u, v, w);
j[0][0] * (j[1][1] * j[2][2] - j[1][2] * j[2][1])
- j[0][1] * (j[1][0] * j[2][2] - j[1][2] * j[2][0])
+ j[0][2] * (j[1][0] * j[2][1] - j[1][1] * j[2][0])
}
}
#[derive(Debug, Clone)]
pub struct TSplineSurface {
pub control_points: Vec<TSplineControlPoint>,
pub domain: [f64; 4],
}
impl TSplineSurface {
pub fn new(control_points: Vec<TSplineControlPoint>, domain: [f64; 4]) -> Self {
TSplineSurface {
control_points,
domain,
}
}
pub fn point_at(&self, s: f64, t: f64) -> [f64; 3] {
let mut num = [0.0f64; 3];
let mut denom = 0.0f64;
for cp in &self.control_points {
let wb = cp.weighted_basis(s, t);
if wb.abs() > 1e-15 {
num[0] += wb * cp.position[0];
num[1] += wb * cp.position[1];
num[2] += wb * cp.position[2];
denom += wb;
}
}
if denom.abs() < 1e-15 {
return [0.0; 3];
}
[num[0] / denom, num[1] / denom, num[2] / denom]
}
pub fn num_control_points(&self) -> usize {
self.control_points.len()
}
pub fn has_t_junctions(&self) -> bool {
if self.control_points.len() < 2 {
return false;
}
let ref_s = self.control_points[0].s_knots;
let ref_t = self.control_points[0].t_knots;
for cp in self.control_points.iter().skip(1) {
if cp.s_knots != ref_s || cp.t_knots != ref_t {
return true;
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct ParameterizationQuality {
pub min_jacobian: f64,
pub max_jacobian: f64,
pub avg_jacobian: f64,
pub scaled_jacobian: f64,
pub condition_number: f64,
pub orthogonality: f64,
pub num_samples: usize,
}
impl ParameterizationQuality {
pub fn analyze_surface(surf: &NurbsSurface, n_samples: usize) -> Self {
let (u0, u1) = surf.u_basis.domain();
let (v0, v1) = surf.v_basis.domain();
let mut min_j = f64::MAX;
let mut max_j = f64::MIN;
let mut sum_j = 0.0;
let mut sum_cond = 0.0;
let mut sum_orth = 0.0;
let count = (n_samples * n_samples) as f64;
for i in 0..n_samples {
for j in 0..n_samples {
let u = u0 + (u1 - u0) * (i as f64 + 0.5) / n_samples as f64;
let v = v0 + (v1 - v0) * (j as f64 + 0.5) / n_samples as f64;
let (tang_u, tang_v, _) = surf.normal(u, v);
let jac_det = norm3(cross3(tang_u, tang_v));
min_j = min_j.min(jac_det);
max_j = max_j.max(jac_det);
sum_j += jac_det;
let a = norm3(tang_u);
let b = norm3(tang_v);
let cond = if b > 1e-15 {
(a / b).max(b / a)
} else {
f64::MAX
};
sum_cond += cond.min(1e6);
let cos_angle = if a > 1e-15 && b > 1e-15 {
(dot3(tang_u, tang_v) / (a * b)).abs()
} else {
1.0
};
sum_orth += 1.0 - cos_angle;
}
}
let avg_j = sum_j / count;
let scaled_j = if max_j > 1e-15 { min_j / max_j } else { 0.0 };
ParameterizationQuality {
min_jacobian: min_j,
max_jacobian: max_j,
avg_jacobian: avg_j,
scaled_jacobian: scaled_j,
condition_number: sum_cond / count,
orthogonality: sum_orth / count,
num_samples: n_samples * n_samples,
}
}
pub fn is_valid(&self) -> bool {
self.min_jacobian > 0.0
}
pub fn quality_score(&self) -> f64 {
if !self.is_valid() {
return 0.0;
}
let j_score = self.scaled_jacobian.clamp(0.0, 1.0);
let o_score = self.orthogonality.clamp(0.0, 1.0);
0.5 * j_score + 0.5 * o_score
}
}
#[derive(Debug, Clone)]
pub struct KRefinementStrategy {
pub target_degree: usize,
pub knots_per_direction: usize,
pub uniform: bool,
pub min_element_size: f64,
}
impl KRefinementStrategy {
pub fn new(
target_degree: usize,
knots_per_direction: usize,
uniform: bool,
min_element_size: f64,
) -> Self {
KRefinementStrategy {
target_degree,
knots_per_direction,
uniform,
min_element_size,
}
}
pub fn apply(&self, knot: &[f64], current_degree: usize) -> Vec<f64> {
let mut result = knot.to_vec();
let mut curr_deg = current_degree;
while curr_deg < self.target_degree {
result = KnotRefinement::p_refine_knot(&result, curr_deg);
curr_deg += 1;
}
if self.uniform {
result = KnotRefinement::uniform_h_refine(&result, curr_deg, self.knots_per_direction);
} else {
result = KnotRefinement::k_refine(&result, curr_deg, self.knots_per_direction);
}
result
}
pub fn estimate_dofs_1d(&self, n_ctrl: usize) -> usize {
let degree_added = self.target_degree.saturating_sub(1);
n_ctrl + degree_added + self.knots_per_direction
}
pub fn is_refinement_possible(&self, element_size: f64) -> bool {
element_size > self.min_element_size * 2.0
}
}
#[derive(Debug, Clone)]
pub struct NurbsBasis {
pub bspline: BsplineBasis,
pub weights: Vec<f64>,
}
impl NurbsBasis {
pub fn new(degree: usize, knot: Vec<f64>, weights: Vec<f64>) -> Self {
let bspline = BsplineBasis::new(degree, knot);
Self { bspline, weights }
}
pub fn uniform_open_knot(degree: usize, n_basis: usize) -> Vec<f64> {
let m = n_basis + degree + 1;
let n_internal = n_basis - degree - 1;
let mut knot = vec![0.0f64; degree + 1];
if n_internal > 0 {
let step = 1.0 / (n_internal + 1) as f64;
for i in 1..=n_internal {
knot.push(i as f64 * step);
}
}
while knot.len() < m {
knot.push(1.0);
}
knot
}
pub fn evaluate_rational(&self, t: f64) -> (usize, Vec<f64>, Vec<f64>) {
let p = self.bspline.degree;
let n = self.bspline.num_basis - 1;
let span = find_knot_span(n, p, t, &self.bspline.knot);
let ders = basis_derivatives(span, p, t, &self.bspline.knot, 1);
let n_vals = &ders[0];
let dn_vals = &ders[1];
let mut w_sum = 0.0f64;
let mut dw_sum = 0.0f64;
for j in 0..=p {
let wi = self.weights[span - p + j];
w_sum += wi * n_vals[j];
dw_sum += wi * dn_vals[j];
}
let w_inv = if w_sum.abs() > 1e-15 {
1.0 / w_sum
} else {
0.0
};
let mut r = Vec::with_capacity(p + 1);
let mut dr = Vec::with_capacity(p + 1);
for j in 0..=p {
let wi = self.weights[span - p + j];
let rj = wi * n_vals[j] * w_inv;
let drj = wi * (dn_vals[j] - rj * dw_sum) * w_inv;
r.push(rj);
dr.push(drj);
}
(span, r, dr)
}
pub fn evaluate(&self, t: f64) -> (usize, Vec<f64>) {
let (span, r, _) = self.evaluate_rational(t);
(span, r)
}
pub fn domain(&self) -> (f64, f64) {
self.bspline.domain()
}
pub fn num_basis(&self) -> usize {
self.bspline.num_basis
}
pub fn degree(&self) -> usize {
self.bspline.degree
}
pub fn curve_point(&self, control_points: &[[f64; 3]], t: f64) -> [f64; 3] {
let (span, r) = self.evaluate(t);
let p = self.bspline.degree;
let mut pt = [0.0f64; 3];
for j in 0..=p {
let cp = control_points[span - p + j];
pt[0] += r[j] * cp[0];
pt[1] += r[j] * cp[1];
pt[2] += r[j] * cp[2];
}
pt
}
pub fn insert_knot(&self, t_new: f64) -> NurbsBasis {
let p = self.bspline.degree;
let old_knot = &self.bspline.knot;
let n = self.bspline.num_basis - 1;
let span = find_knot_span(n, p, t_new, old_knot);
let mut new_knot = old_knot[..=span].to_vec();
new_knot.push(t_new);
new_knot.extend_from_slice(&old_knot[span + 1..]);
let insert_pos = (span + 1).saturating_sub(p / 2);
let insert_pos = insert_pos.min(self.weights.len());
let avg_w = if insert_pos < self.weights.len() {
self.weights[insert_pos]
} else {
*self.weights.last().unwrap_or(&1.0)
};
let mut new_weights = self.weights.clone();
new_weights.insert(insert_pos, avg_w);
NurbsBasis::new(p, new_knot, new_weights)
}
pub fn de_boor_scalar(&self, control_values: &[f64], t: f64) -> f64 {
let p = self.bspline.degree;
let n = self.bspline.num_basis - 1;
let knot = &self.bspline.knot;
let span = find_knot_span(n, p, t, knot);
let mut d: Vec<f64> = (0..=p).map(|j| control_values[span - p + j]).collect();
for r in 1..=p {
for j in (r..=p).rev() {
let left = knot[span - p + j];
let right = knot[span + 1 + j - r];
let denom = right - left;
let alpha = if denom.abs() < 1e-15 {
0.0
} else {
(t - left) / denom
};
d[j] = (1.0 - alpha) * d[j - 1] + alpha * d[j];
}
}
d[p]
}
}
#[derive(Debug, Clone)]
pub struct NurbsCurve {
pub control_points: Vec<[f64; 3]>,
pub weights: Vec<f64>,
pub basis: BsplineBasis,
}
impl NurbsCurve {
pub fn new(
control_points: Vec<[f64; 3]>,
weights: Vec<f64>,
degree: usize,
knot: Vec<f64>,
) -> Self {
let basis = BsplineBasis::new(degree, knot);
Self {
control_points,
weights,
basis,
}
}
pub fn point_at(&self, t: f64) -> [f64; 3] {
nurbs_point(
&self.control_points,
&self.weights,
&self.basis.knot,
self.basis.degree,
t,
)
}
pub fn derivative_at(&self, t: f64) -> [f64; 3] {
let n = self.control_points.len() - 1;
let p = self.basis.degree;
let span = find_knot_span(n, p, t, &self.basis.knot);
let ders = basis_derivatives(span, p, t, &self.basis.knot, 1);
let mut a = [0.0f64; 3];
let mut da = [0.0f64; 3];
let mut w = 0.0f64;
let mut dw = 0.0f64;
for (j, (&n0, &n1)) in ders[0].iter().zip(ders[1].iter()).enumerate().take(p + 1) {
let idx = span - p + j;
let wi = self.weights[idx];
let pi = self.control_points[idx];
a[0] += wi * pi[0] * n0;
a[1] += wi * pi[1] * n0;
a[2] += wi * pi[2] * n0;
da[0] += wi * pi[0] * n1;
da[1] += wi * pi[1] * n1;
da[2] += wi * pi[2] * n1;
w += wi * n0;
dw += wi * n1;
}
if w.abs() < 1e-15 {
return [0.0; 3];
}
let c = [a[0] / w, a[1] / w, a[2] / w];
[
(da[0] - dw * c[0]) / w,
(da[1] - dw * c[1]) / w,
(da[2] - dw * c[2]) / w,
]
}
pub fn insert_knot(&self, t_new: f64) -> NurbsCurve {
let n = self.control_points.len() - 1;
let p = self.basis.degree;
let knot = &self.basis.knot;
let k = find_knot_span(n, p, t_new, knot);
let mut new_knot = Vec::with_capacity(knot.len() + 1);
new_knot.extend_from_slice(&knot[..=k]);
new_knot.push(t_new);
new_knot.extend_from_slice(&knot[k + 1..]);
let m = n + p + 1;
let mut new_ctrl = vec![[0.0f64; 3]; n + 2];
let mut new_weights = vec![0.0f64; n + 2];
new_ctrl[..=(k - p)].copy_from_slice(&self.control_points[..=(k - p)]);
new_weights[..=(k - p)].copy_from_slice(&self.weights[..=(k - p)]);
for i in (k - p + 1)..=k {
let alpha = (t_new - knot[i]) / (knot[i + p] - knot[i]);
let prev_i = i - 1;
new_ctrl[i] = [
alpha * self.control_points[i][0] + (1.0 - alpha) * self.control_points[prev_i][0],
alpha * self.control_points[i][1] + (1.0 - alpha) * self.control_points[prev_i][1],
alpha * self.control_points[i][2] + (1.0 - alpha) * self.control_points[prev_i][2],
];
new_weights[i] = alpha * self.weights[i] + (1.0 - alpha) * self.weights[prev_i];
}
for i in (k + 1)..=(m - p) {
if i <= n + 1 {
new_ctrl[i] = self.control_points[i - 1];
new_weights[i] = self.weights[i - 1];
}
}
NurbsCurve::new(new_ctrl, new_weights, p, new_knot)
}
pub fn arc_length(&self, num_segments: usize) -> f64 {
let (t0, t1) = self.basis.domain();
let dt = (t1 - t0) / num_segments as f64;
let mut length = 0.0;
let mut prev = self.point_at(t0);
for i in 1..=num_segments {
let t = t0 + i as f64 * dt;
let curr = self.point_at(t);
length += norm3(sub3(curr, prev));
prev = curr;
}
length
}
}
#[derive(Debug, Clone)]
pub struct IgaElement {
pub element_id: usize,
pub connectivity: Vec<usize>,
pub surface: NurbsSurface,
pub n_gauss_u: usize,
pub n_gauss_v: usize,
}
impl IgaElement {
pub fn new(
element_id: usize,
connectivity: Vec<usize>,
surface: NurbsSurface,
n_gauss_u: usize,
n_gauss_v: usize,
) -> Self {
Self {
element_id,
connectivity,
surface,
n_gauss_u,
n_gauss_v,
}
}
pub fn shape_functions(&self, u: f64, v: f64) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let nu = self.surface.control_net.len() - 1;
let nv = self.surface.control_net[0].len() - 1;
let pu = self.surface.u_basis.degree;
let pv = self.surface.v_basis.degree;
let u_span = find_knot_span(nu, pu, u, &self.surface.u_basis.knot);
let v_span = find_knot_span(nv, pv, v, &self.surface.v_basis.knot);
let u_ders = basis_derivatives(u_span, pu, u, &self.surface.u_basis.knot, 1);
let v_ders = basis_derivatives(v_span, pv, v, &self.surface.v_basis.knot, 1);
let n_local = (pu + 1) * (pv + 1);
let mut r = vec![0.0f64; n_local];
let mut dr_du = vec![0.0f64; n_local];
let mut dr_dv = vec![0.0f64; n_local];
let mut w = 0.0f64;
let mut dw_du = 0.0f64;
let mut dw_dv = 0.0f64;
let mut bw = vec![0.0f64; n_local];
let mut a = 0;
for (i, (&ud0i, &ud1i)) in u_ders[0]
.iter()
.zip(u_ders[1].iter())
.enumerate()
.take(pu + 1)
{
for (j, (&vd0j, &vd1j)) in v_ders[0]
.iter()
.zip(v_ders[1].iter())
.enumerate()
.take(pv + 1)
{
let ui = u_span - pu + i;
let vi = v_span - pv + j;
let wij = self.surface.weights[ui][vi];
let nij = ud0i * vd0j;
bw[a] = wij * nij;
w += bw[a];
dw_du += wij * ud1i * vd0j;
dw_dv += wij * ud0i * vd1j;
a += 1;
}
}
if w.abs() < 1e-15 {
return (r, dr_du, dr_dv);
}
let _w2 = w * w;
a = 0;
for (i, (&ud0i, &ud1i)) in u_ders[0]
.iter()
.zip(u_ders[1].iter())
.enumerate()
.take(pu + 1)
{
for (j, (&vd0j, &vd1j)) in v_ders[0]
.iter()
.zip(v_ders[1].iter())
.enumerate()
.take(pv + 1)
{
let ui = u_span - pu + i;
let vi = v_span - pv + j;
let wij = self.surface.weights[ui][vi];
r[a] = bw[a] / w;
dr_du[a] = (wij * ud1i * vd0j - dw_du * bw[a] / w) / w;
dr_dv[a] = (wij * ud0i * vd1j - dw_dv * bw[a] / w) / w;
let _ = (ui, vi);
a += 1;
}
}
(r, dr_du, dr_dv)
}
pub fn jacobian(&self, u: f64, v: f64) -> [[f64; 3]; 2] {
let (_, tang_u, tang_v) = {
let eps = 1e-7;
let (t0, t1) = self.surface.u_basis.domain();
let (s0, s1) = self.surface.v_basis.domain();
let up = (u + eps).min(t1);
let um = (u - eps).max(t0);
let vp = (v + eps).min(s1);
let vm = (v - eps).max(s0);
let tang_u = scale3(
sub3(self.surface.point_at(up, v), self.surface.point_at(um, v)),
1.0 / (up - um),
);
let tang_v = scale3(
sub3(self.surface.point_at(u, vp), self.surface.point_at(u, vm)),
1.0 / (vp - vm),
);
((), tang_u, tang_v)
};
[tang_u, tang_v]
}
}
#[derive(Debug, Clone)]
pub struct KnotRefinement;
impl KnotRefinement {
pub fn h_refine_curve(curve: &NurbsCurve, new_knots: &[f64]) -> NurbsCurve {
let mut result = curve.clone();
let mut sorted = new_knots.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
for &t in &sorted {
result = result.insert_knot(t);
}
result
}
pub fn p_refine_knot(knot: &[f64], degree: usize) -> Vec<f64> {
let new_degree = degree + 1;
let mut unique_knots: Vec<f64> = Vec::new();
for &k in knot {
if unique_knots.is_empty()
|| (k - unique_knots.last().expect("unique_knots is non-empty")).abs() > 1e-14
{
unique_knots.push(k);
}
}
let mut new_knot = Vec::new();
for &k in &unique_knots {
for _ in 0..=new_degree {
new_knot.push(k);
}
}
new_knot
}
pub fn k_refine(knot: &[f64], degree: usize, num_insertions: usize) -> Vec<f64> {
let elevated_knot = Self::p_refine_knot(knot, degree);
let new_degree = degree + 1;
let first = elevated_knot[new_degree];
let last = elevated_knot[elevated_knot.len() - 1 - new_degree];
let mut result = elevated_knot;
for i in 1..=num_insertions {
let t = first + (last - first) * i as f64 / (num_insertions + 1) as f64;
let pos = result.partition_point(|&x| x <= t);
result.insert(pos, t);
}
result
}
pub fn uniform_h_refine(knot: &[f64], degree: usize, n_new: usize) -> Vec<f64> {
let mut new_knots_to_insert = Vec::new();
for w in knot.windows(2) {
let span = w[1] - w[0];
if span > 1e-14 {
for k in 1..=n_new {
new_knots_to_insert.push(w[0] + span * k as f64 / (n_new + 1) as f64);
}
}
}
let mut result = knot.to_vec();
let _ = degree;
for t in new_knots_to_insert {
let pos = result.partition_point(|&x| x <= t);
result.insert(pos, t);
}
result
}
}
#[derive(Debug, Clone)]
pub struct MortarPatchCoupling {
pub slave_surface: NurbsSurface,
pub master_surface: NurbsSurface,
pub n_gauss: usize,
pub alpha: f64,
}
impl MortarPatchCoupling {
pub fn new(
slave_surface: NurbsSurface,
master_surface: NurbsSurface,
n_gauss: usize,
alpha: f64,
) -> Self {
MortarPatchCoupling {
slave_surface,
master_surface,
n_gauss,
alpha,
}
}
pub fn coupling_matrix(&self) -> Vec<f64> {
let ps = self.slave_surface.u_basis.degree;
let pm = self.master_surface.u_basis.degree;
let n_slave = ps + 1;
let n_master = pm + 1;
let mut d_mat = vec![0.0f64; n_slave * n_master];
let (u0s, u1s) = self.slave_surface.u_basis.domain();
let (u0m, u1m) = self.master_surface.u_basis.domain();
let (gp, gw) = gauss_legendre_1d(self.n_gauss);
for gi in 0..self.n_gauss {
let us = 0.5 * (u0s + u1s) + 0.5 * (u1s - u0s) * gp[gi];
let _xs = self.slave_surface.point_at(us, 0.5);
let t_norm = (us - u0s) / (u1s - u0s).max(1e-14);
let um = u0m + t_norm * (u1m - u0m);
let ns = self.slave_surface.u_basis.num_basis - 1;
let s_span = find_knot_span(ns, ps, us, &self.slave_surface.u_basis.knot);
let s_basis = basis_functions(s_span, ps, us, &self.slave_surface.u_basis.knot);
let nm = self.master_surface.u_basis.num_basis - 1;
let um_clamped = um.clamp(u0m, u1m - 1e-10);
let m_span = find_knot_span(nm, pm, um_clamped, &self.master_surface.u_basis.knot);
let m_basis =
basis_functions(m_span, pm, um_clamped, &self.master_surface.u_basis.knot);
let w = 0.5 * (u1s - u0s) * gw[gi];
for a in 0..n_slave.min(s_basis.len()) {
for b in 0..n_master.min(m_basis.len()) {
d_mat[a * n_master + b] += s_basis[a] * m_basis[b] * w * self.alpha;
}
}
}
d_mat
}
pub fn interface_gap(&self, n_samples: usize) -> Vec<(f64, [f64; 3])> {
let (u0s, u1s) = self.slave_surface.u_basis.domain();
let (u0m, u1m) = self.master_surface.u_basis.domain();
let mut gaps = Vec::with_capacity(n_samples);
for i in 0..n_samples {
let t = i as f64 / (n_samples - 1).max(1) as f64;
let us = u0s + (u1s - u0s) * t;
let um = u0m + (u1m - u0m) * t;
let xs = self.slave_surface.point_at(us, 0.5);
let xm = self.master_surface.point_at(um, 0.5);
let gap = sub3(xs, xm);
gaps.push((t, gap));
}
gaps
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CouplingType {
Mortar,
Penalty(f64),
Nitsche(f64),
}