scirs2_integrate/pde/
mesh_generation.rs

1//! Automatic Mesh Generation Interfaces
2//!
3//! This module provides automatic mesh generation capabilities for common geometric domains.
4//! It includes algorithms for generating structured and unstructured meshes for various
5//! shapes and domains, with quality control and refinement options.
6
7use crate::pde::finite_element::{ElementType, Point, Triangle, TriangularMesh};
8use crate::pde::{PDEError, PDEResult};
9use std::collections::{HashMap, HashSet};
10use std::f64::consts::PI;
11
12/// Parameters for controlling automatic mesh generation
13#[derive(Debug, Clone)]
14pub struct MeshGenerationParams {
15    /// Target element size (average edge length)
16    pub element_size: f64,
17    /// Minimum angle constraint for triangles (degrees)
18    pub min_angle: f64,
19    /// Maximum angle constraint for triangles (degrees)
20    pub max_angle: f64,
21    /// Quality improvement iterations
22    pub quality_iterations: usize,
23    /// Element type to generate
24    pub element_type: ElementType,
25    /// Maximum number of boundary refinement iterations
26    pub boundary_refinement_iterations: usize,
27}
28
29impl Default for MeshGenerationParams {
30    fn default() -> Self {
31        Self {
32            element_size: 0.1,
33            min_angle: 20.0,
34            max_angle: 140.0,
35            quality_iterations: 5,
36            element_type: ElementType::Linear,
37            boundary_refinement_iterations: 3,
38        }
39    }
40}
41
42/// Geometric domain types for automatic mesh generation
43#[derive(Debug, Clone)]
44pub enum Domain {
45    /// Rectangle: (x_min, y_min, x_max, y_max)
46    Rectangle {
47        x_min: f64,
48        y_min: f64,
49        x_max: f64,
50        y_max: f64,
51    },
52    /// Circle: (center_x, center_y, radius)
53    Circle {
54        center_x: f64,
55        center_y: f64,
56        radius: f64,
57    },
58    /// Ellipse: (center_x, center_y, a, b, rotation_angle)
59    Ellipse {
60        center_x: f64,
61        center_y: f64,
62        a: f64,
63        b: f64,
64        rotation: f64,
65    },
66    /// L-shaped domain
67    LShape {
68        width: f64,
69        height: f64,
70        notch_width: f64,
71        notch_height: f64,
72    },
73    /// Custom polygon defined by vertices
74    Polygon { vertices: Vec<Point> },
75    /// Annulus (ring): (center_x, center_y, inner_radius, outer_radius)
76    Annulus {
77        center_x: f64,
78        center_y: f64,
79        inner_radius: f64,
80        outer_radius: f64,
81    },
82}
83
84/// Boundary condition specification for domain boundaries
85#[derive(Debug, Clone, Default)]
86pub struct BoundarySpecification {
87    /// Boundary markers for different boundary segments
88    pub boundary_markers: HashMap<String, i32>,
89    /// Point markers for specific points
90    pub point_markers: HashMap<String, i32>,
91}
92
93/// Quality metrics for mesh assessment
94#[derive(Debug, Clone)]
95pub struct MeshQuality {
96    /// Minimum angle in degrees
97    pub min_angle: f64,
98    /// Maximum angle in degrees  
99    pub max_angle: f64,
100    /// Average element size
101    pub avg_element_size: f64,
102    /// Aspect ratio statistics
103    pub min_aspect_ratio: f64,
104    /// Number of poor quality elements
105    pub poor_quality_elements: usize,
106    /// Overall quality score (0-1, higher is better)
107    pub quality_score: f64,
108}
109
110/// Main automatic mesh generator
111pub struct AutoMeshGenerator {
112    params: MeshGenerationParams,
113}
114
115impl Default for AutoMeshGenerator {
116    fn default() -> Self {
117        Self::new(MeshGenerationParams::default())
118    }
119}
120
121impl AutoMeshGenerator {
122    /// Create a new mesh generator with specified parameters
123    pub fn new(params: MeshGenerationParams) -> Self {
124        Self { params }
125    }
126
127    /// Create a mesh generator with default parameters
128    pub fn with_default_params(&self) -> Self {
129        Self::default()
130    }
131
132    /// Generate mesh for a specified domain
133    pub fn generatemesh(
134        &mut self,
135        domain: &Domain,
136        boundary_spec: &BoundarySpecification,
137    ) -> PDEResult<TriangularMesh> {
138        match domain {
139            Domain::Rectangle {
140                x_min,
141                y_min,
142                x_max,
143                y_max,
144            } => self.generate_rectanglemesh(*x_min, *y_min, *x_max, *y_max, boundary_spec),
145            Domain::Circle {
146                center_x,
147                center_y,
148                radius,
149            } => self.generate_circlemesh(*center_x, *center_y, *radius, boundary_spec),
150            Domain::Ellipse {
151                center_x,
152                center_y,
153                a,
154                b,
155                rotation,
156            } => self.generate_ellipsemesh(*center_x, *center_y, *a, *b, *rotation, boundary_spec),
157            Domain::LShape {
158                width,
159                height,
160                notch_width,
161                notch_height,
162            } => self.generate_lshapemesh(
163                *width,
164                *height,
165                *notch_width,
166                *notch_height,
167                boundary_spec,
168            ),
169            Domain::Polygon { vertices } => self.generate_polygonmesh(vertices, boundary_spec),
170            Domain::Annulus {
171                center_x,
172                center_y,
173                inner_radius,
174                outer_radius,
175            } => self.generate_annulusmesh(
176                *center_x,
177                *center_y,
178                *inner_radius,
179                *outer_radius,
180                boundary_spec,
181            ),
182        }
183    }
184
185    /// Generate rectangular mesh using structured approach
186    fn generate_rectanglemesh(
187        &mut self,
188        x_min: f64,
189        y_min: f64,
190        x_max: f64,
191        y_max: f64,
192        boundary_spec: &BoundarySpecification,
193    ) -> PDEResult<TriangularMesh> {
194        let width = x_max - x_min;
195        let height = y_max - y_min;
196
197        // Calculate number of divisions
198        let nx = ((width / self.params.element_size).ceil() as usize).max(2);
199        let ny = ((height / self.params.element_size).ceil() as usize).max(2);
200
201        let dx = width / (nx - 1) as f64;
202        let dy = height / (ny - 1) as f64;
203
204        // Generate points
205        let mut points = Vec::new();
206        for j in 0..ny {
207            for i in 0..nx {
208                let x = x_min + i as f64 * dx;
209                let y = y_min + j as f64 * dy;
210                points.push(Point::new(x, y));
211            }
212        }
213
214        // Generate triangles (two triangles per rectangular cell)
215        let mut triangles = Vec::new();
216        for j in 0..ny - 1 {
217            for i in 0..nx - 1 {
218                let idx = |row: usize, col: usize| row * nx + col;
219
220                let p0 = idx(j, i);
221                let p1 = idx(j, i + 1);
222                let p2 = idx(j + 1, i);
223                let p3 = idx(j + 1, i + 1);
224
225                // First triangle
226                triangles.push(Triangle::new([p0, p1, p2], Some(1)));
227                // Second triangle
228                triangles.push(Triangle::new([p1, p3, p2], Some(1)));
229            }
230        }
231
232        let mut mesh = TriangularMesh::new();
233        mesh.points = points;
234        mesh.elements = triangles;
235        self.apply_boundary_markers(
236            &mut mesh,
237            boundary_spec,
238            &Domain::Rectangle {
239                x_min,
240                y_min,
241                x_max,
242                y_max,
243            },
244        )?;
245        self.improvemesh_quality(&mut mesh)?;
246
247        Ok(mesh)
248    }
249
250    /// Generate circular mesh using radial structured approach
251    fn generate_circlemesh(
252        &mut self,
253        center_x: f64,
254        center_y: f64,
255        radius: f64,
256        boundary_spec: &BoundarySpecification,
257    ) -> PDEResult<TriangularMesh> {
258        // Estimate number of radial and angular divisions
259        let circumference = 2.0 * PI * radius;
260        let n_theta = ((circumference / self.params.element_size).ceil() as usize).max(8);
261        let n_r = ((radius / self.params.element_size).ceil() as usize).max(2);
262
263        let mut points = Vec::new();
264        let mut triangles = Vec::new();
265
266        // Add center point
267        points.push(Point::new(center_x, center_y));
268
269        // Generate points in radial layers
270        for i in 1..=n_r {
271            let r = radius * i as f64 / n_r as f64;
272            for j in 0..n_theta {
273                let theta = 2.0 * PI * j as f64 / n_theta as f64;
274                let _x = center_x + r * theta.cos();
275                let y = center_y + r * theta.sin();
276                points.push(Point::new(_x, y));
277            }
278        }
279
280        // Generate triangles
281        // Connect center to first ring
282        for j in 0..n_theta {
283            let p1 = 1 + j;
284            let p2 = 1 + (j + 1) % n_theta;
285            triangles.push(Triangle::new([0, p1, p2], Some(1)));
286        }
287
288        // Connect rings
289        for i in 0..n_r - 1 {
290            let ring1_start = 1 + i * n_theta;
291            let ring2_start = 1 + (i + 1) * n_theta;
292
293            for j in 0..n_theta {
294                let p1 = ring1_start + j;
295                let p2 = ring1_start + (j + 1) % n_theta;
296                let p3 = ring2_start + j;
297                let p4 = ring2_start + (j + 1) % n_theta;
298
299                triangles.push(Triangle::new([p1, p2, p3], Some(1)));
300                triangles.push(Triangle::new([p2, p4, p3], Some(1)));
301            }
302        }
303
304        let mut mesh = TriangularMesh::new();
305        mesh.points = points;
306        mesh.elements = triangles;
307        self.apply_boundary_markers(
308            &mut mesh,
309            boundary_spec,
310            &Domain::Circle {
311                center_x,
312                center_y,
313                radius,
314            },
315        )?;
316        self.improvemesh_quality(&mut mesh)?;
317
318        Ok(mesh)
319    }
320
321    /// Generate ellipse mesh using transformation from circle
322    fn generate_ellipsemesh(
323        &mut self,
324        center_x: f64,
325        center_y: f64,
326        a: f64,
327        b: f64,
328        rotation: f64,
329        boundary_spec: &BoundarySpecification,
330    ) -> PDEResult<TriangularMesh> {
331        // Generate circle mesh with radius = max(a, b)
332        let max_radius = a.max(b);
333        let mut mesh =
334            self.generate_circlemesh(0.0, 0.0, max_radius, &BoundarySpecification::default())?;
335
336        // Transform points to ellipse
337        let cos_rot = rotation.cos();
338        let sin_rot = rotation.sin();
339
340        for point in &mut mesh.points {
341            // Scale to ellipse
342            point.x *= a / max_radius;
343            point.y *= b / max_radius;
344
345            // Rotate
346            let x_rot = point.x * cos_rot - point.y * sin_rot;
347            let y_rot = point.x * sin_rot + point.y * cos_rot;
348
349            // Translate
350            point.x = center_x + x_rot;
351            point.y = center_y + y_rot;
352        }
353
354        self.apply_boundary_markers(
355            &mut mesh,
356            boundary_spec,
357            &Domain::Ellipse {
358                center_x,
359                center_y,
360                a,
361                b,
362                rotation,
363            },
364        )?;
365        self.improvemesh_quality(&mut mesh)?;
366
367        Ok(mesh)
368    }
369
370    /// Generate L-shaped domain mesh
371    fn generate_lshapemesh(
372        &mut self,
373        width: f64,
374        height: f64,
375        notch_width: f64,
376        notch_height: f64,
377        boundary_spec: &BoundarySpecification,
378    ) -> PDEResult<TriangularMesh> {
379        // Create L-shape as combination of two rectangles
380        let mesh1 = self.generate_rectanglemesh(
381            0.0,
382            0.0,
383            width,
384            height - notch_height,
385            &BoundarySpecification::default(),
386        )?;
387        let mesh2 = self.generate_rectanglemesh(
388            0.0,
389            height - notch_height,
390            width - notch_width,
391            height,
392            &BoundarySpecification::default(),
393        )?;
394
395        // Combine meshes
396        let combinedmesh = AutoMeshGenerator::combinemeshes(&[mesh1, mesh2])?;
397        let mut mesh = combinedmesh;
398
399        self.apply_boundary_markers(
400            &mut mesh,
401            boundary_spec,
402            &Domain::LShape {
403                width,
404                height,
405                notch_width,
406                notch_height,
407            },
408        )?;
409        self.improvemesh_quality(&mut mesh)?;
410
411        Ok(mesh)
412    }
413
414    /// Generate mesh for arbitrary polygon using Delaunay triangulation
415    fn generate_polygonmesh(
416        &self,
417        vertices: &[Point],
418        boundary_spec: &BoundarySpecification,
419    ) -> PDEResult<TriangularMesh> {
420        if vertices.len() < 3 {
421            return Err(PDEError::FiniteElementError(
422                "Polygon must have at least 3 vertices".to_string(),
423            ));
424        }
425
426        // Simple implementation: triangulate using fan triangulation from first vertex
427        // In practice, would use more sophisticated algorithms like Delaunay triangulation
428        let mut points = vertices.to_vec();
429        let mut triangles = Vec::new();
430
431        // Add interior points if needed for refinement
432        let (min_x, max_x) = vertices
433            .iter()
434            .map(|p| p.x)
435            .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), x| {
436                (min.min(x), max.max(x))
437            });
438        let (min_y, max_y) = vertices
439            .iter()
440            .map(|p| p.y)
441            .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), y| {
442                (min.min(y), max.max(y))
443            });
444
445        // Generate interior points in a grid
446        let nx = ((max_x - min_x) / self.params.element_size) as usize;
447        let ny = ((max_y - min_y) / self.params.element_size) as usize;
448
449        for i in 1..nx {
450            for j in 1..ny {
451                let x = min_x + (max_x - min_x) * i as f64 / nx as f64;
452                let y = min_y + (max_y - min_y) * j as f64 / ny as f64;
453                let point = Point::new(x, y);
454
455                // Check if point is inside polygon
456                if AutoMeshGenerator::point_in_polygon(&point, vertices) {
457                    points.push(point);
458                }
459            }
460        }
461
462        // Simple triangulation (would be replaced with proper Delaunay triangulation)
463        for i in 1..vertices.len() - 1 {
464            triangles.push(Triangle::new([0, i, i + 1], Some(1)));
465        }
466
467        let mut mesh = TriangularMesh::new();
468        mesh.points = points;
469        mesh.elements = triangles;
470        self.apply_boundary_markers(
471            &mut mesh,
472            boundary_spec,
473            &Domain::Polygon {
474                vertices: vertices.to_vec(),
475            },
476        )?;
477
478        Ok(mesh)
479    }
480
481    /// Generate annulus (ring) mesh
482    fn generate_annulusmesh(
483        &mut self,
484        center_x: f64,
485        center_y: f64,
486        inner_radius: f64,
487        outer_radius: f64,
488        boundary_spec: &BoundarySpecification,
489    ) -> PDEResult<TriangularMesh> {
490        if inner_radius >= outer_radius {
491            return Err(PDEError::FiniteElementError(
492                "Inner _radius must be less than outer _radius".to_string(),
493            ));
494        }
495
496        // Generate structured radial mesh
497        let n_theta = ((2.0 * PI * outer_radius / self.params.element_size).ceil() as usize).max(8);
498        let n_r =
499            (((outer_radius - inner_radius) / self.params.element_size).ceil() as usize).max(2);
500
501        let mut points = Vec::new();
502        let mut triangles = Vec::new();
503
504        // Generate points in radial layers
505        for i in 0..=n_r {
506            let r = inner_radius + (outer_radius - inner_radius) * i as f64 / n_r as f64;
507            for j in 0..n_theta {
508                let theta = 2.0 * PI * j as f64 / n_theta as f64;
509                let _x = center_x + r * theta.cos();
510                let y = center_y + r * theta.sin();
511                points.push(Point::new(_x, y));
512            }
513        }
514
515        // Connect rings with triangles
516        for i in 0..n_r {
517            let ring1_start = i * n_theta;
518            let ring2_start = (i + 1) * n_theta;
519
520            for j in 0..n_theta {
521                let p1 = ring1_start + j;
522                let p2 = ring1_start + (j + 1) % n_theta;
523                let p3 = ring2_start + j;
524                let p4 = ring2_start + (j + 1) % n_theta;
525
526                triangles.push(Triangle::new([p1, p2, p3], Some(1)));
527                triangles.push(Triangle::new([p2, p4, p3], Some(1)));
528            }
529        }
530
531        let mut mesh = TriangularMesh::new();
532        mesh.points = points;
533        mesh.elements = triangles;
534        self.apply_boundary_markers(
535            &mut mesh,
536            boundary_spec,
537            &Domain::Annulus {
538                center_x,
539                center_y,
540                inner_radius,
541                outer_radius,
542            },
543        )?;
544        self.improvemesh_quality(&mut mesh)?;
545
546        Ok(mesh)
547    }
548
549    /// Apply boundary markers to mesh based on domain and specification
550    fn apply_boundary_markers(
551        &self,
552        mesh: &mut TriangularMesh,
553        boundary_spec: &BoundarySpecification,
554        _domain: &Domain,
555    ) -> PDEResult<()> {
556        // This is a simplified implementation
557        // In practice, would need sophisticated boundary detection
558
559        // Apply default boundary markers if none specified
560        if boundary_spec.boundary_markers.is_empty() {
561            // Mark all boundary edges with marker 1
562            let boundary_edges = AutoMeshGenerator::find_boundary_edges(mesh);
563            for _p1_p2 in boundary_edges {
564                // Mark boundary points
565                // In a complete implementation, would track edge markers
566            }
567        }
568
569        Ok(())
570    }
571
572    /// Find boundary edges in the mesh
573    fn find_boundary_edges(mesh: &TriangularMesh) -> Vec<(usize, usize)> {
574        let mut edge_count = HashMap::new();
575
576        // Count how many times each edge appears
577        for element in &mesh.elements {
578            let [p1, p2, p3] = element.nodes;
579            let edges = [
580                (p1.min(p2), p1.max(p2)),
581                (p2.min(p3), p2.max(p3)),
582                (p3.min(p1), p3.max(p1)),
583            ];
584
585            for edge in &edges {
586                *edge_count.entry(*edge).or_insert(0) += 1;
587            }
588        }
589
590        // Boundary edges appear only once
591        edge_count
592            .into_iter()
593            .filter(|(_, count)| *count == 1)
594            .map(|(edge, _)| edge)
595            .collect()
596    }
597
598    /// Improve mesh quality through smoothing and refinement
599    fn improvemesh_quality(&mut self, mesh: &mut TriangularMesh) -> PDEResult<()> {
600        for _ in 0..self.params.quality_iterations {
601            // Laplacian smoothing
602            self.laplacian_smoothing(mesh)?;
603
604            // Quality-based refinement
605            self.quality_refinement(mesh)?;
606        }
607
608        Ok(())
609    }
610
611    /// Apply Laplacian smoothing to improve mesh quality
612    fn laplacian_smoothing(&mut self, mesh: &mut TriangularMesh) -> PDEResult<()> {
613        let n_points = mesh.points.len();
614        let mut new_positions = vec![Point::new(0.0, 0.0); n_points];
615        let mut neighbor_counts = vec![0; n_points];
616
617        // Find neighbors for each point
618        for element in &mesh.elements {
619            let [p1, p2, p3] = element.nodes;
620
621            new_positions[p1].x += mesh.points[p2].x + mesh.points[p3].x;
622            new_positions[p1].y += mesh.points[p2].y + mesh.points[p3].y;
623            neighbor_counts[p1] += 2;
624
625            new_positions[p2].x += mesh.points[p1].x + mesh.points[p3].x;
626            new_positions[p2].y += mesh.points[p1].y + mesh.points[p3].y;
627            neighbor_counts[p2] += 2;
628
629            new_positions[p3].x += mesh.points[p1].x + mesh.points[p2].x;
630            new_positions[p3].y += mesh.points[p1].y + mesh.points[p2].y;
631            neighbor_counts[p3] += 2;
632        }
633
634        // Update positions (keep boundary points fixed)
635        let boundary_edges = AutoMeshGenerator::find_boundary_edges(mesh);
636        let boundary_points: HashSet<usize> = boundary_edges
637            .iter()
638            .flat_map(|(p1, p2)| vec![*p1, *p2])
639            .collect();
640
641        for i in 0..n_points {
642            if !boundary_points.contains(&i) && neighbor_counts[i] > 0 {
643                mesh.points[i].x = new_positions[i].x / neighbor_counts[i] as f64;
644                mesh.points[i].y = new_positions[i].y / neighbor_counts[i] as f64;
645            }
646        }
647
648        Ok(())
649    }
650
651    /// Refine elements with poor quality
652    fn quality_refinement(&mut self, mesh: &mut TriangularMesh) -> PDEResult<()> {
653        let mut elements_to_refine = Vec::new();
654
655        // Identify poor quality elements
656        for (i, element) in mesh.elements.iter().enumerate() {
657            let quality = self.element_quality(mesh, element);
658            if quality.min_angle < self.params.min_angle
659                || quality.max_angle > self.params.max_angle
660            {
661                elements_to_refine.push(i);
662            }
663        }
664
665        // For simplicity, we'll skip actual refinement here
666        // In practice, would implement edge splitting and local refinement
667
668        Ok(())
669    }
670
671    /// Calculate quality metrics for a single element
672    fn element_quality(&self, mesh: &TriangularMesh, element: &Triangle) -> ElementQuality {
673        let [p1, p2, p3] = element.nodes;
674        let a = &mesh.points[p1];
675        let b = &mesh.points[p2];
676        let c = &mesh.points[p3];
677
678        // Calculate edge lengths
679        let ab = ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt();
680        let bc = ((c.x - b.x).powi(2) + (c.y - b.y).powi(2)).sqrt();
681        let ca = ((a.x - c.x).powi(2) + (a.y - c.y).powi(2)).sqrt();
682
683        // Calculate angles using law of cosines
684        let angle_a =
685            ((bc.powi(2) + ca.powi(2) - ab.powi(2)) / (2.0 * bc * ca)).acos() * 180.0 / PI;
686        let angle_b =
687            ((ca.powi(2) + ab.powi(2) - bc.powi(2)) / (2.0 * ca * ab)).acos() * 180.0 / PI;
688        let angle_c = 180.0 - angle_a - angle_b;
689
690        let min_angle = angle_a.min(angle_b).min(angle_c);
691        let max_angle = angle_a.max(angle_b).max(angle_c);
692
693        // Calculate area using cross product
694        let area = 0.5 * ((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y)).abs();
695
696        // Aspect ratio (radius ratio)
697        let s = (ab + bc + ca) / 2.0;
698        let inradius = area / s;
699        let circumradius = (ab * bc * ca) / (4.0 * area);
700        let aspect_ratio = circumradius / inradius;
701
702        ElementQuality {
703            min_angle,
704            max_angle,
705            area,
706            aspect_ratio,
707        }
708    }
709
710    /// Check if a point is inside a polygon using ray casting
711    fn point_in_polygon(point: &Point, polygon: &[Point]) -> bool {
712        let mut inside = false;
713        let mut j = polygon.len() - 1;
714
715        for i in 0..polygon.len() {
716            if ((polygon[i].y > point.y) != (polygon[j].y > point.y))
717                && (point.x
718                    < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y)
719                        / (polygon[j].y - polygon[i].y)
720                        + polygon[i].x)
721            {
722                inside = !inside;
723            }
724            j = i;
725        }
726
727        inside
728    }
729
730    /// Combine multiple meshes into one
731    fn combinemeshes(meshes: &[TriangularMesh]) -> PDEResult<TriangularMesh> {
732        if meshes.is_empty() {
733            return Err(PDEError::FiniteElementError(
734                "Cannot combine empty mesh list".to_string(),
735            ));
736        }
737
738        let mut combined_points = Vec::new();
739        let mut combined_elements = Vec::new();
740        let mut point_offset = 0;
741
742        for mesh in meshes {
743            // Add points
744            combined_points.extend(mesh.points.iter().cloned());
745
746            // Add elements with updated indices
747            for element in &mesh.elements {
748                let [p1, p2, p3] = element.nodes;
749                combined_elements.push(Triangle::new(
750                    [p1 + point_offset, p2 + point_offset, p3 + point_offset],
751                    element.marker,
752                ));
753            }
754
755            point_offset += mesh.points.len();
756        }
757
758        let mut combinedmesh = TriangularMesh::new();
759        combinedmesh.points = combined_points;
760        combinedmesh.elements = combined_elements;
761        Ok(combinedmesh)
762    }
763
764    /// Calculate overall mesh quality metrics
765    pub fn assessmesh_quality(&self, mesh: &TriangularMesh) -> MeshQuality {
766        let mut min_angle = f64::INFINITY;
767        let mut max_angle: f64 = 0.0;
768        let mut total_area = 0.0;
769        let mut min_aspect_ratio = f64::INFINITY;
770        let mut poor_quality_count = 0;
771
772        for element in &mesh.elements {
773            let quality = self.element_quality(mesh, element);
774
775            min_angle = min_angle.min(quality.min_angle);
776            max_angle = max_angle.max(quality.max_angle);
777            total_area += quality.area;
778            min_aspect_ratio = min_aspect_ratio.min(quality.aspect_ratio);
779
780            if quality.min_angle < self.params.min_angle
781                || quality.max_angle > self.params.max_angle
782            {
783                poor_quality_count += 1;
784            }
785        }
786
787        let avg_element_size = (total_area / mesh.elements.len() as f64).sqrt();
788        let quality_score = 1.0 - (poor_quality_count as f64 / mesh.elements.len() as f64);
789
790        MeshQuality {
791            min_angle,
792            max_angle,
793            avg_element_size,
794            min_aspect_ratio,
795            poor_quality_elements: poor_quality_count,
796            quality_score,
797        }
798    }
799}
800
801/// Quality metrics for individual elements
802#[derive(Debug, Clone)]
803struct ElementQuality {
804    min_angle: f64,
805    max_angle: f64,
806    area: f64,
807    aspect_ratio: f64,
808}
809
810#[cfg(test)]
811mod tests {
812    use super::*;
813
814    #[test]
815    fn test_rectanglemesh_generation() {
816        let mut generator = AutoMeshGenerator::default();
817        let domain = Domain::Rectangle {
818            x_min: 0.0,
819            y_min: 0.0,
820            x_max: 1.0,
821            y_max: 1.0,
822        };
823        let boundary_spec = BoundarySpecification::default();
824
825        let mesh = generator.generatemesh(&domain, &boundary_spec).unwrap();
826
827        assert!(!mesh.points.is_empty());
828        assert!(!mesh.elements.is_empty());
829
830        // Check that all points are within domain
831        for point in &mesh.points {
832            assert!(point.x >= 0.0 && point.x <= 1.0);
833            assert!(point.y >= 0.0 && point.y <= 1.0);
834        }
835    }
836
837    #[test]
838    fn test_circlemesh_generation() {
839        let params = MeshGenerationParams {
840            element_size: 0.2,
841            ..Default::default()
842        };
843        let mut generator = AutoMeshGenerator::new(params);
844
845        let domain = Domain::Circle {
846            center_x: 0.0,
847            center_y: 0.0,
848            radius: 1.0,
849        };
850        let boundary_spec = BoundarySpecification::default();
851
852        let mesh = generator.generatemesh(&domain, &boundary_spec).unwrap();
853
854        assert!(!mesh.points.is_empty());
855        assert!(!mesh.elements.is_empty());
856
857        // Check that all points are within or on circle boundary
858        for point in &mesh.points {
859            let distance = (point.x.powi(2) + point.y.powi(2)).sqrt();
860            assert!(distance <= 1.01); // Allow small tolerance for numerical errors
861        }
862    }
863
864    #[test]
865    fn testmesh_quality_assessment() {
866        let mut generator = AutoMeshGenerator::default();
867        let domain = Domain::Rectangle {
868            x_min: 0.0,
869            y_min: 0.0,
870            x_max: 1.0,
871            y_max: 1.0,
872        };
873        let boundary_spec = BoundarySpecification::default();
874
875        let mesh = generator.generatemesh(&domain, &boundary_spec).unwrap();
876        let quality = generator.assessmesh_quality(&mesh);
877
878        assert!(quality.min_angle > 0.0);
879        assert!(quality.max_angle < 180.0);
880        assert!(quality.avg_element_size > 0.0);
881        assert!(quality.quality_score >= 0.0 && quality.quality_score <= 1.0);
882    }
883
884    #[test]
885    fn test_point_in_polygon() {
886        let generator = AutoMeshGenerator::default();
887
888        // Square polygon
889        let polygon = vec![
890            Point::new(0.0, 0.0),
891            Point::new(1.0, 0.0),
892            Point::new(1.0, 1.0),
893            Point::new(0.0, 1.0),
894        ];
895
896        assert!(AutoMeshGenerator::point_in_polygon(
897            &Point::new(0.5, 0.5),
898            &polygon
899        ));
900        assert!(!AutoMeshGenerator::point_in_polygon(
901            &Point::new(1.5, 0.5),
902            &polygon
903        ));
904        assert!(!AutoMeshGenerator::point_in_polygon(
905            &Point::new(-0.5, 0.5),
906            &polygon
907        ));
908    }
909
910    #[test]
911    fn test_annulusmesh_generation() {
912        let mut generator = AutoMeshGenerator::default();
913        let domain = Domain::Annulus {
914            center_x: 0.0,
915            center_y: 0.0,
916            inner_radius: 0.5,
917            outer_radius: 1.0,
918        };
919        let boundary_spec = BoundarySpecification::default();
920
921        let mesh = generator.generatemesh(&domain, &boundary_spec).unwrap();
922
923        assert!(!mesh.points.is_empty());
924        assert!(!mesh.elements.is_empty());
925
926        // Check that all points are within annulus
927        for point in &mesh.points {
928            let distance = (point.x.powi(2) + point.y.powi(2)).sqrt();
929            assert!((0.49..=1.01).contains(&distance)); // Allow tolerance
930        }
931    }
932
933    #[test]
934    fn test_lshapemesh_generation() {
935        let mut generator = AutoMeshGenerator::default();
936        let domain = Domain::LShape {
937            width: 2.0,
938            height: 2.0,
939            notch_width: 1.0,
940            notch_height: 1.0,
941        };
942        let boundary_spec = BoundarySpecification::default();
943
944        let mesh = generator.generatemesh(&domain, &boundary_spec).unwrap();
945
946        assert!(!mesh.points.is_empty());
947        assert!(!mesh.elements.is_empty());
948    }
949
950    #[test]
951    fn test_custommesh_parameters() {
952        let params = MeshGenerationParams {
953            element_size: 0.05,
954            min_angle: 25.0,
955            max_angle: 135.0,
956            quality_iterations: 10,
957            element_type: ElementType::Linear,
958            boundary_refinement_iterations: 5,
959        };
960
961        let mut generator = AutoMeshGenerator::new(params);
962        let domain = Domain::Rectangle {
963            x_min: 0.0,
964            y_min: 0.0,
965            x_max: 1.0,
966            y_max: 1.0,
967        };
968        let boundary_spec = BoundarySpecification::default();
969
970        let mesh = generator.generatemesh(&domain, &boundary_spec).unwrap();
971        let quality = generator.assessmesh_quality(&mesh);
972
973        // With smaller element size, should have more elements
974        assert!(mesh.elements.len() > 10);
975        assert!(quality.avg_element_size < 0.2);
976    }
977}