honeycomb_core/cmap/dim2/
serialize.rs

1use std::{any::TypeId, collections::BTreeMap};
2
3use vtkio::{
4    model::{
5        ByteOrder, CellType, DataSet, Piece, UnstructuredGridPiece, Version, VertexNumbers, Vtk,
6    },
7    IOBuffer,
8};
9
10use crate::cmap::{
11    CMap2, DartIdType, EdgeIdType, FaceIdType, Orbit2, OrbitPolicy, VertexIdType, NULL_DART_ID,
12};
13use crate::geometry::CoordsFloat;
14
15/// **Serialization methods**
16impl<T: CoordsFloat + 'static> CMap2<T> {
17    // --- Custom
18
19    /// Serialize the map under a custom format.
20    ///
21    /// The format specification is described in the [user guide]().
22    ///
23    /// # Panics
24    ///
25    /// This method may panic if writing to the file fails.
26    pub fn serialize(&self, mut writer: impl std::fmt::Write) {
27        let n_darts = self.n_darts();
28        writeln!(writer, "[META]").expect("E: couldn't write to file");
29        writeln!(
30            writer,
31            "{} {} {}",
32            env!("CARGO_PKG_VERSION"), // indicates which version was used to generate the file
33            2,
34            n_darts - 1
35        )
36        .expect("E: couldn't write to file");
37        writeln!(writer).expect("E: couldn't write to file"); // not required, but nice
38
39        writeln!(writer, "[BETAS]").expect("E: couldn't write to file");
40        let width = n_darts.to_string().len();
41        let mut b0 = String::with_capacity(n_darts * 2);
42        let mut b1 = String::with_capacity(n_darts * 2);
43        let mut b2 = String::with_capacity(n_darts * 2);
44        std::thread::scope(|s| {
45            s.spawn(|| {
46                // convoluted bc this prevents ephemeral allocs
47                use std::fmt::Write;
48                let mut buf = String::new();
49                (0..n_darts as DartIdType).for_each(|d| {
50                    write!(&mut buf, "{:>width$} ", self.beta::<0>(d))
51                        .expect("E: couldn't write to file");
52                    b0.push_str(buf.as_str());
53                    buf.clear();
54                });
55            });
56            s.spawn(|| {
57                // convoluted bc this prevents ephemeral allocs
58                use std::fmt::Write;
59                let mut buf = String::new();
60                (0..n_darts as DartIdType).for_each(|d| {
61                    write!(&mut buf, "{:>width$} ", self.beta::<1>(d))
62                        .expect("E: couldn't write to file");
63                    b1.push_str(buf.as_str());
64                    buf.clear();
65                });
66            });
67            s.spawn(|| {
68                // convoluted bc this prevents ephemeral allocs
69                use std::fmt::Write;
70                let mut buf = String::new();
71                (0..n_darts as DartIdType).for_each(|d| {
72                    write!(&mut buf, "{:>width$} ", self.beta::<2>(d))
73                        .expect("E: couldn't write to file");
74                    b2.push_str(buf.as_str());
75                    buf.clear();
76                });
77            });
78        });
79        writeln!(writer, "{}", b0.trim()).expect("E: couldn't write to file");
80        writeln!(writer, "{}", b1.trim()).expect("E: couldn't write to file");
81        writeln!(writer, "{}", b2.trim()).expect("E: couldn't write to file");
82        writeln!(writer).expect("E: couldn't write to file"); // not required, but nice
83
84        writeln!(writer, "[UNUSED]").expect("E: couldn't write to file");
85        self.unused_darts
86            .iter()
87            .enumerate()
88            .filter(|(_, v)| v.read_atomic())
89            .for_each(|(i, _)| {
90                write!(writer, "{i} ").unwrap();
91            });
92        writeln!(writer).expect("E: couldn't write to file"); // required
93        writeln!(writer).expect("E: couldn't write to file"); // not required, but nice
94
95        writeln!(writer, "[VERTICES]").expect("E: couldn't write to file");
96        self.iter_vertices().for_each(|v| {
97            if let Some(val) = self.force_read_vertex(v) {
98                writeln!(
99                    writer,
100                    "{v} {} {}",
101                    val.0.to_f64().unwrap(),
102                    val.1.to_f64().unwrap(),
103                )
104                .expect("E: couldn't write to file");
105            }
106        });
107    }
108
109    // --- VTK
110
111    /// Generate a legacy VTK file from the map.
112    ///
113    /// # Panics
114    ///
115    /// This function may panic if the internal writing routine fails, i.e.:
116    /// - vertex coordinates cannot be cast to `f32` or `f64`,
117    /// - a vertex cannot be found.
118    pub fn to_vtk_binary(&self, writer: impl std::io::Write) {
119        // build a Vtk structure
120        let vtk_struct = Vtk {
121            version: Version::Legacy { major: 2, minor: 0 },
122            title: "cmap".to_string(),
123            byte_order: ByteOrder::BigEndian,
124            data: DataSet::UnstructuredGrid {
125                meta: None,
126                pieces: vec![Piece::Inline(Box::new(build_unstructured_piece(self)))],
127            },
128            file_path: None,
129        };
130
131        // write data to the created file
132        vtk_struct
133            .write_legacy(writer)
134            .expect("E: could not write data to writer");
135    }
136
137    /// Generate a legacy VTK file from the map.
138    ///
139    /// # Panics
140    ///
141    /// This function may panic if the internal writing routine fails, i.e.:
142    /// - vertex coordinates cannot be cast to `f32` or `f64`,
143    /// - a vertex cannot be found.
144    pub fn to_vtk_ascii(&self, writer: impl std::fmt::Write) {
145        // build a Vtk structure
146        let vtk_struct = Vtk {
147            version: Version::Legacy { major: 2, minor: 0 },
148            title: "cmap".to_string(),
149            byte_order: ByteOrder::BigEndian,
150            data: DataSet::UnstructuredGrid {
151                meta: None,
152                pieces: vec![Piece::Inline(Box::new(build_unstructured_piece(self)))],
153            },
154            file_path: None,
155        };
156
157        // write data to the created file
158        vtk_struct
159            .write_legacy_ascii(writer)
160            .expect("E: could not write data to writer");
161    }
162}
163
164/// Internal building routine for VTK serialization.
165fn build_unstructured_piece<T>(map: &CMap2<T>) -> UnstructuredGridPiece
166where
167    T: CoordsFloat + 'static,
168{
169    // common data
170    let vertex_ids: Vec<VertexIdType> = map.iter_vertices().collect();
171    let mut id_map: BTreeMap<VertexIdType, usize> = BTreeMap::new();
172    vertex_ids.iter().enumerate().for_each(|(id, vid)| {
173        id_map.insert(*vid, id);
174    });
175    // ------ points data
176    let vertices = vertex_ids
177        .iter()
178        .map(|vid| {
179            map.force_read_vertex(*vid)
180                .expect("E: found a topological vertex with no associated coordinates")
181        })
182        .flat_map(|v| [v.x(), v.y(), T::zero()].into_iter());
183    // ------ cells data
184    let mut n_cells = 0;
185    // --- faces
186    let face_ids: Vec<FaceIdType> = map.iter_faces().collect();
187    let face_data = face_ids.into_iter().map(|id| {
188        let mut count: u32 = 0;
189        // VecDeque will be useful later
190        let orbit: Vec<u32> = Orbit2::new(map, OrbitPolicy::Custom(&[1]), id as DartIdType)
191            .map(|dart_id| {
192                count += 1;
193                id_map[&map.vertex_id(dart_id)] as u32
194            })
195            .collect();
196        (count, orbit)
197    });
198
199    // --- borders
200    let edge_ids: Vec<EdgeIdType> = map.iter_edges().collect();
201    // because we do not model boundaries, we can get edges
202    // from filtering isolated darts making up edges
203    let edge_data = edge_ids
204        .into_iter()
205        .filter(|id| map.beta::<2>(*id as DartIdType) == NULL_DART_ID)
206        .map(|id| {
207            let dart_id = id as DartIdType;
208            let ndart_id = map.beta::<1>(dart_id);
209            (
210                id_map[&map.vertex_id(dart_id)] as u32,
211                id_map[&map.vertex_id(ndart_id)] as u32,
212            )
213        });
214
215    // --- corners
216
217    // ------ build VTK data
218    let mut cell_vertices: Vec<u32> = Vec::new();
219    let mut cell_types: Vec<CellType> = Vec::new();
220
221    edge_data.for_each(|(v1, v2)| {
222        cell_types.push(CellType::Line);
223        cell_vertices.extend([2_u32, v1, v2]);
224        n_cells += 1;
225    });
226
227    face_data.for_each(|(count, mut elements)| {
228        cell_types.push(match count {
229            0..=2 => return, // silent ignore
230            3 => CellType::Triangle,
231            4 => CellType::Quad,
232            5.. => CellType::Polygon,
233        });
234        cell_vertices.push(count);
235        cell_vertices.append(&mut elements);
236        n_cells += 1;
237    });
238
239    UnstructuredGridPiece {
240        points: if TypeId::of::<T>() == TypeId::of::<f32>() {
241            IOBuffer::F32(
242                vertices
243                    .map(|t| t.to_f32().expect("E: unreachable"))
244                    .collect(),
245            )
246        } else if TypeId::of::<T>() == TypeId::of::<f64>() {
247            IOBuffer::F64(
248                vertices
249                    .map(|t| t.to_f64().expect("E: unreachable"))
250                    .collect(),
251            )
252        } else {
253            println!("W: unrecognized coordinate type -- cast to f64 might fail");
254            IOBuffer::F64(
255                vertices
256                    .map(|t| t.to_f64().expect("E: unreachable"))
257                    .collect(),
258            )
259        },
260        cells: vtkio::model::Cells {
261            cell_verts: VertexNumbers::Legacy {
262                num_cells: n_cells,
263                vertices: cell_vertices,
264            },
265            types: cell_types,
266        },
267        data: vtkio::model::Attributes::default(),
268    }
269}