polyquad_parse/
lib.rs

1//! Tiny parser for the output of `polyquad` quadrature files.
2//!
3//! This crate provides parsers for 2D and 3D quadrature rules formatted in a simple text file
4//! format and bundled with the paper
5//!
6//! ```text
7//! Witherden, Freddie D., and Peter E. Vincent.
8//! "On the identification of symmetric quadrature rules for finite element methods."
9//! Computers & Mathematics with Applications 69, no. 10 (2015): 1232-1241.
10//! ```
11//!
12//! It is used as a (build) dependency of `fenris-quadrature`.
13//!
14//! It is only minimally tested, because the resulting quadrature rules are further tested in
15//! `fenris-quadrature`.
16
17use core::fmt;
18use core::fmt::{Display, Formatter};
19use std::error::Error;
20
21pub type Point2 = [f64; 2];
22pub type Point3 = [f64; 3];
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct ParseError {
26    error: String,
27}
28
29impl ParseError {
30    fn from_string(error: String) -> Self {
31        Self { error }
32    }
33
34    fn float_parse_error(str: &str, label: &str, error: impl Error) -> Self {
35        ParseError::from_string(format!("Failed to parse {} ({}) as f64: {}", str, label, error))
36    }
37
38    fn unexpected_entries(num_entries_found: usize, num_entries_expected: usize) -> Self {
39        ParseError::from_string(format!(
40            "Found {} entries in line, but expected {}",
41            num_entries_found, num_entries_expected
42        ))
43    }
44}
45
46fn try_parse_f64(str: &str, label: &str) -> Result<f64, ParseError> {
47    str.parse::<f64>()
48        .map_err(|err| ParseError::float_parse_error(str, label, err))
49}
50
51impl Display for ParseError {
52    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
53        write!(f, "{}", self.error)
54    }
55}
56
57impl std::error::Error for ParseError {}
58
59#[derive(Clone, Default, PartialEq)]
60pub struct Rule2d {
61    pub points: Vec<Point2>,
62    pub weights: Vec<f64>,
63}
64
65#[derive(Clone, Default, PartialEq)]
66pub struct Rule3d {
67    pub points: Vec<Point3>,
68    pub weights: Vec<f64>,
69}
70
71fn parse_helper(
72    data: &str,
73    labels: &[&str],
74    mut handler: impl FnMut(&[f64]) -> Result<(), ParseError>,
75) -> Result<(), ParseError> {
76    let mut line_numbers = Vec::new();
77
78    for line in data.lines() {
79        let mut iter = line.split_ascii_whitespace().peekable();
80
81        // Skip empty lines
82        if iter.peek().is_some() {
83            line_numbers.clear();
84
85            for (i, entry) in iter.enumerate() {
86                let label = labels.get(i).unwrap_or_else(|| &"unlabeled entry");
87                let coord_entry = try_parse_f64(entry, label)?;
88                line_numbers.push(coord_entry);
89            }
90
91            handler(&line_numbers)?;
92        }
93    }
94    Ok(())
95}
96
97/// Attempts to parse a text file in the `expanded` format as a 2D quadrature rule.
98///
99/// The text file should contain lines with the format
100/// ```text
101///    <x>     <y>     <weight>
102/// ```
103/// where `x`, `y` and `weights` are floating-point numbers.
104pub fn parse2d(data: &str) -> Result<Rule2d, ParseError> {
105    let mut rule = Rule2d::default();
106    parse_helper(
107        data,
108        &["x coordinate", "y coordinate", "weight"],
109        |entries| match entries {
110            [x, y, w] => {
111                rule.points.push([*x, *y]);
112                rule.weights.push(*w);
113                Ok(())
114            }
115            _ => Err(ParseError::unexpected_entries(entries.len(), 3)),
116        },
117    )?;
118    Ok(rule)
119}
120
121/// Attempts to parse a text file in the `expanded` format as a 3D quadrature rule.
122///
123/// The text file should contain lines with the format
124/// ```text
125///    <x>     <y>     <z>     <weight>
126/// ```
127/// where `x`, `y`, `z` and `weights` are floating-point numbers delimited by whitespace.
128pub fn parse3d(data: &str) -> Result<Rule3d, ParseError> {
129    let mut rule = Rule3d::default();
130    let labels = &["x coordinate", "y coordinate", "z coordinate", "weight"];
131    parse_helper(data, labels, |entries| match entries {
132        [x, y, z, w] => {
133            rule.points.push([*x, *y, *z]);
134            rule.weights.push(*w);
135            Ok(())
136        }
137        _ => Err(ParseError::unexpected_entries(entries.len(), 4)),
138    })?;
139    Ok(rule)
140}