• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kaidokert / heapless-graphs-rs / 15792950674

21 Jun 2025 06:21AM UTC coverage: 95.559% (+1.0%) from 94.579%
15792950674

Pull #13

github

kaidokert
More edge flex graphs
Pull Request #13: Add edge mutability

1575 of 1597 new or added lines in 9 files covered. (98.62%)

2 existing lines in 1 file now uncovered.

8134 of 8512 relevant lines covered (95.56%)

37.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.17
/src/matrix/bit_map_matrix.rs
1
use crate::{
2
    containers::maps::MapTrait,
3
    graph::{Graph, GraphError, GraphWithMutableEdges, GraphWithMutableNodes, NodeIndex},
4
};
5

6
/// A bit-packed adjacency matrix with arbitrary node indices
7
///
8
/// This struct combines a [`crate::matrix::bit_matrix::BitMatrix`] for efficient edge storage with an index map
9
/// that allows arbitrary node indices. It provides the same memory efficiency as
10
/// BitMatrix while supporting non-contiguous node identifiers.
11
pub struct BitMapMatrix<const N: usize, const R: usize, NI, M>
12
where
13
    NI: NodeIndex,
14
    M: MapTrait<NI, usize>,
15
{
16
    bitmap: super::bit_matrix::BitMatrix<N, R>,
17
    index_map: M,
18
    phantom: core::marker::PhantomData<NI>,
19
}
20

21
impl<const N: usize, const R: usize, NI, M> BitMapMatrix<N, R, NI, M>
22
where
23
    NI: NodeIndex,
24
    M: MapTrait<NI, usize>,
25
{
26
    /// Creates a new BitMapMatrix with the given bitmap and index mapping
27
    ///
28
    /// Validates that all indices in the index_map are within valid bounds for the BitMatrix.
29
    /// BitMatrix supports node indices in the range 0..8*N.
30
    pub fn new(
19✔
31
        bitmap: super::bit_matrix::BitMatrix<N, R>,
19✔
32
        index_map: M,
19✔
33
    ) -> Result<Self, GraphError<NI>> {
19✔
34
        // BitMatrix supports indices in range 0..8*N
19✔
35
        let max_valid_index = 8 * N;
19✔
36
        for (node, idx) in index_map.iter() {
47✔
37
            if *idx >= max_valid_index {
47✔
38
                return Err(GraphError::IndexOutOfBounds(*idx, *node));
×
39
            }
47✔
40
        }
41
        Ok(Self::new_unchecked(bitmap, index_map))
19✔
42
    }
19✔
43

44
    /// Creates a new BitMapMatrix with the given bitmap and index mapping without bounds checking
45
    ///
46
    /// # Safety
47
    /// The caller must ensure that all indices in the index_map are within valid bounds
48
    /// for the BitMatrix (0..8*N).
49
    pub fn new_unchecked(bitmap: super::bit_matrix::BitMatrix<N, R>, index_map: M) -> Self {
19✔
50
        Self {
19✔
51
            bitmap,
19✔
52
            index_map,
19✔
53
            phantom: core::marker::PhantomData,
19✔
54
        }
19✔
55
    }
19✔
56
}
57

58
impl<const N: usize, const R: usize, NI, M> Graph<NI> for BitMapMatrix<N, R, NI, M>
59
where
60
    NI: NodeIndex,
61
    M: MapTrait<NI, usize>,
62
{
63
    type Error = GraphError<NI>;
64

65
    fn iter_nodes(&self) -> Result<impl Iterator<Item = NI>, Self::Error> {
8✔
66
        Ok(self.index_map.iter().map(|(&k, _)| k))
21✔
67
    }
8✔
68

69
    fn contains_node(&self, node: NI) -> Result<bool, Self::Error> {
22✔
70
        Ok(self.index_map.contains_key(&node))
22✔
71
    }
22✔
72

73
    /// Note: Uses linear search for backindexing matrix indices to NI, this is slow.
74
    ///
75
    /// TODO: Store back-index ?
76
    fn iter_edges(&self) -> Result<impl Iterator<Item = (NI, NI)>, Self::Error> {
18✔
77
        Ok(self
18✔
78
            .index_map
18✔
79
            .iter()
18✔
80
            .flat_map(move |(&from_node, &from_idx)| {
51✔
81
                self.index_map
51✔
82
                    .iter()
51✔
83
                    .filter_map(move |(&to_node, &to_idx)| {
163✔
84
                        if self.bitmap.get(from_idx, to_idx) {
163✔
85
                            Some((from_node, to_node))
38✔
86
                        } else {
87
                            None
125✔
88
                        }
89
                    })
163✔
90
            }))
51✔
91
    }
18✔
92

93
    /// Note: Uses linear search for backindexing matrix indices to NI, this is slow.
94
    ///
95
    /// TODO: Store back-index ?
96
    fn outgoing_edges(&self, node: NI) -> Result<impl Iterator<Item = NI>, Self::Error> {
53✔
97
        // Fast direct lookup of matrix index for this node
53✔
98
        let matrix_idx = self.index_map.get(&node).copied();
53✔
99

100
        // Get outgoing edges from bitmap, using usize::MAX as fallback (will be filtered out)
101
        // Note: BitMatrix::get handles out-of-bounds indices safely by returning false,
102
        // and the filter below ensures we don't return edges for non-existent nodes.
103
        // The map_err is needed to convert GraphError<usize> to GraphError<NI> for the trait implementation.
104
        let outgoing = self
53✔
105
            .bitmap
53✔
106
            .outgoing_edges(matrix_idx.unwrap_or(usize::MAX))
53✔
107
            .map_err(|_| GraphError::NodeNotFound(node))?;
53✔
108

109
        // Map matrix indices back to node indices by checking all nodes
110
        Ok(outgoing
53✔
111
            .filter(move |_| matrix_idx.is_some()) // Filter out everything if node doesn't exist
53✔
112
            .filter_map(move |target_idx| {
53✔
113
                self.index_map
43✔
114
                    .iter()
43✔
115
                    .find(|(_, &idx)| idx == target_idx)
138✔
116
                    .map(|(&node, _)| node)
43✔
117
            }))
53✔
118
    }
53✔
119

120
    /// Note: Uses linear search for backindexing matrix indices to NI, this is slow.
121
    ///
122
    /// TODO: Store back-index ?
123
    fn incoming_edges(&self, node: NI) -> Result<impl Iterator<Item = NI>, Self::Error> {
3✔
124
        // Fast direct lookup of matrix index for this node
3✔
125
        let matrix_idx = self.index_map.get(&node).copied();
3✔
126

127
        // Get incoming edges from bitmap, using 0 as fallback (will be filtered out)
128
        let incoming = self
3✔
129
            .bitmap
3✔
130
            .incoming_edges(matrix_idx.unwrap_or(usize::MAX))
3✔
131
            .map_err(|_| GraphError::NodeNotFound(node))?;
3✔
132

133
        // Map matrix indices back to node indices by checking all nodes
134
        Ok(incoming
3✔
135
            .filter(move |_| matrix_idx.is_some()) // Filter out everything if node doesn't exist
3✔
136
            .filter_map(move |source_idx| {
3✔
137
                self.index_map
3✔
138
                    .iter()
3✔
139
                    .find(|(_, &idx)| idx == source_idx)
4✔
140
                    .map(|(&node, _)| node)
3✔
141
            }))
3✔
142
    }
3✔
143
}
144

145
impl<const N: usize, const R: usize, NI, M> GraphWithMutableNodes<NI> for BitMapMatrix<N, R, NI, M>
146
where
147
    NI: NodeIndex,
148
    M: MapTrait<NI, usize>,
149
{
150
    fn add_node(&mut self, node: NI) -> Result<(), Self::Error> {
8✔
151
        // Check if node already exists
8✔
152
        if self.index_map.contains_key(&node) {
8✔
153
            return Err(GraphError::DuplicateNode(node));
1✔
154
        }
7✔
155

7✔
156
        // Find an unused matrix index (BitMatrix supports 0..8*N)
7✔
157
        let max_index = 8 * N;
7✔
158
        let unused_index = (0..max_index)
7✔
159
            .find(|&idx| !self.index_map.iter().any(|(_, &used_idx)| used_idx == idx))
49✔
160
            .ok_or(GraphError::OutOfCapacity)?;
7✔
161

162
        // Insert the new node mapping
163
        self.index_map
6✔
164
            .insert(node, unused_index)
6✔
165
            .map_err(|_| GraphError::OutOfCapacity)?;
6✔
166

167
        Ok(())
6✔
168
    }
8✔
169

170
    fn remove_node(&mut self, node: NI) -> Result<(), Self::Error> {
×
171
        // Check if node exists
×
172
        if !self.index_map.contains_key(&node) {
×
173
            return Err(GraphError::NodeNotFound(node));
×
174
        }
×
175

×
176
        // Check if node has incoming edges
×
177
        if self.incoming_edges(node)?.next().is_some() {
×
178
            return Err(GraphError::NodeHasIncomingEdges(node));
×
179
        }
×
180

×
181
        // Remove the node mapping (bit matrix position becomes available for reuse)
×
182
        self.index_map.remove(&node);
×
183
        Ok(())
×
184
    }
×
185
}
186

187
impl<const N: usize, const R: usize, NI, M> GraphWithMutableEdges<NI> for BitMapMatrix<N, R, NI, M>
188
where
189
    NI: NodeIndex,
190
    M: MapTrait<NI, usize>,
191
{
192
    fn add_edge(&mut self, source: NI, destination: NI) -> Result<(), Self::Error> {
16✔
193
        // Translate node indices to matrix indices
194
        let source_idx = self
16✔
195
            .index_map
16✔
196
            .get(&source)
16✔
197
            .copied()
16✔
198
            .ok_or(GraphError::EdgeHasInvalidNode(source))?;
16✔
199

200
        let destination_idx = self
14✔
201
            .index_map
14✔
202
            .get(&destination)
14✔
203
            .copied()
14✔
204
            .ok_or(GraphError::EdgeHasInvalidNode(destination))?;
14✔
205

206
        // Delegate to underlying BitMatrix implementation
207
        self.bitmap
13✔
208
            .add_edge(source_idx, destination_idx)
13✔
209
            .map_err(|err| match err {
13✔
210
                GraphError::EdgeHasInvalidNode(_) => {
211
                    // This shouldn't happen since we validated indices above,
212
                    // but if it does, convert to source node error
NEW
213
                    GraphError::EdgeHasInvalidNode(source)
×
214
                }
NEW
215
                GraphError::OutOfCapacity => GraphError::OutOfCapacity,
×
NEW
216
                _ => GraphError::EdgeHasInvalidNode(source), // Fallback
×
217
            })
13✔
218
    }
16✔
219

220
    fn remove_edge(&mut self, source: NI, destination: NI) -> Result<(), Self::Error> {
6✔
221
        // Translate node indices to matrix indices
222
        let source_idx = self
6✔
223
            .index_map
6✔
224
            .get(&source)
6✔
225
            .copied()
6✔
226
            .ok_or(GraphError::EdgeNotFound(source, destination))?;
6✔
227

228
        let destination_idx = self
5✔
229
            .index_map
5✔
230
            .get(&destination)
5✔
231
            .copied()
5✔
232
            .ok_or(GraphError::EdgeNotFound(source, destination))?;
5✔
233

234
        // Delegate to underlying BitMatrix implementation
235
        self.bitmap
5✔
236
            .remove_edge(source_idx, destination_idx)
5✔
237
            .map_err(|err| match err {
5✔
238
                GraphError::EdgeNotFound(_, _) => {
239
                    // Convert matrix indices back to node indices for error
240
                    GraphError::EdgeNotFound(source, destination)
2✔
241
                }
NEW
242
                _ => GraphError::EdgeNotFound(source, destination), // Fallback
×
243
            })
5✔
244
    }
6✔
245
}
246

247
#[cfg(test)]
248
mod tests {
249
    use super::*;
250
    use crate::containers::maps::staticdict::Dictionary;
251
    use crate::tests::{collect, collect_sorted};
252

253
    #[test]
254
    fn test_bit_map_matrix_basic() {
1✔
255
        // Create a simple 2x2 bit matrix with edges: 0->0, 0->1, 1->0
1✔
256
        let bits = [
1✔
257
            [0b00000011u8], // Row 0: edges to nodes 0 and 1
1✔
258
            [0b00000001u8], // Row 1: edge to node 0
1✔
259
            [0b00000000u8], // Row 2: no edges
1✔
260
            [0b00000000u8], // Row 3: no edges
1✔
261
            [0b00000000u8], // Row 4: no edges
1✔
262
            [0b00000000u8], // Row 5: no edges
1✔
263
            [0b00000000u8], // Row 6: no edges
1✔
264
            [0b00000000u8], // Row 7: no edges
1✔
265
        ];
1✔
266
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
267

1✔
268
        // Map custom node IDs 'A','B' to matrix indices 0,1
1✔
269
        let mut index_map = Dictionary::<char, usize, 8>::new();
1✔
270
        index_map.insert('A', 0).unwrap();
1✔
271
        index_map.insert('B', 1).unwrap();
1✔
272

1✔
273
        let bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
274

1✔
275
        // Test node iteration
1✔
276
        let mut nodes = ['\0'; 8];
1✔
277
        let nodes_slice = collect(bit_map_matrix.iter_nodes().unwrap(), &mut nodes);
1✔
278
        assert_eq!(nodes_slice.len(), 2);
1✔
279

280
        // Check both nodes are present (order may vary)
281
        assert!(nodes_slice.contains(&'A'));
1✔
282
        assert!(nodes_slice.contains(&'B'));
1✔
283

284
        // Test contains_node
285
        assert!(bit_map_matrix.contains_node('A').unwrap());
1✔
286
        assert!(bit_map_matrix.contains_node('B').unwrap());
1✔
287
        assert!(!bit_map_matrix.contains_node('C').unwrap());
1✔
288

289
        // Test outgoing edges
290
        let mut outgoing_a = ['\0'; 8];
1✔
291
        let outgoing_slice = collect(bit_map_matrix.outgoing_edges('A').unwrap(), &mut outgoing_a);
1✔
292
        assert_eq!(outgoing_slice.len(), 2); // A->A, A->B
1✔
293

294
        let mut outgoing_b = ['\0'; 8];
1✔
295
        let outgoing_slice = collect(bit_map_matrix.outgoing_edges('B').unwrap(), &mut outgoing_b);
1✔
296
        assert_eq!(outgoing_slice, &['A']); // B->A
1✔
297
    }
1✔
298

299
    #[test]
300
    fn test_bit_map_matrix_empty() {
1✔
301
        // Empty bitmap
1✔
302
        let bits = [[0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8]];
1✔
303
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
304

1✔
305
        // Empty index map
1✔
306
        let index_map = Dictionary::<u32, usize, 8>::new();
1✔
307
        let bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
308

1✔
309
        // Should have no nodes
1✔
310
        assert_eq!(bit_map_matrix.iter_nodes().unwrap().count(), 0);
1✔
311

312
        // Should have no edges
313
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 0);
1✔
314

315
        // Should not contain any nodes
316
        assert!(!bit_map_matrix.contains_node(42).unwrap());
1✔
317
    }
1✔
318

319
    #[test]
320
    fn test_bit_map_matrix_nonexistent_node() {
1✔
321
        let bits = [
1✔
322
            [0b00000001u8], // Row 0: edge to node 0
1✔
323
            [0b00000000u8],
1✔
324
            [0b00000000u8],
1✔
325
            [0b00000000u8],
1✔
326
            [0b00000000u8],
1✔
327
            [0b00000000u8],
1✔
328
            [0b00000000u8],
1✔
329
            [0b00000000u8],
1✔
330
        ];
1✔
331
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
332

1✔
333
        let mut index_map = Dictionary::<u32, usize, 8>::new();
1✔
334
        index_map.insert(100, 0).unwrap();
1✔
335

1✔
336
        let bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
337

1✔
338
        // Test outgoing edges for non-existent node should return empty iterator
1✔
339
        assert_eq!(bit_map_matrix.outgoing_edges(999).unwrap().count(), 0);
1✔
340

341
        // Test incoming edges for non-existent node should return empty iterator
342
        assert_eq!(bit_map_matrix.incoming_edges(999).unwrap().count(), 0);
1✔
343
    }
1✔
344

345
    #[test]
346
    fn test_bit_map_matrix_incoming_edges() {
1✔
347
        // Create a matrix with edges: A->A, A->B, B->A
1✔
348
        let bits = [
1✔
349
            [0b00000011u8], // Row 0: edges to nodes 0 and 1 (A->A, A->B)
1✔
350
            [0b00000001u8], // Row 1: edge to node 0 (B->A)
1✔
351
            [0b00000000u8], // Row 2: no edges
1✔
352
            [0b00000000u8], // Row 3: no edges
1✔
353
            [0b00000000u8], // Row 4: no edges
1✔
354
            [0b00000000u8], // Row 5: no edges
1✔
355
            [0b00000000u8], // Row 6: no edges
1✔
356
            [0b00000000u8], // Row 7: no edges
1✔
357
        ];
1✔
358
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
359

1✔
360
        // Map custom node IDs 'A','B' to matrix indices 0,1
1✔
361
        let mut index_map = Dictionary::<char, usize, 8>::new();
1✔
362
        index_map.insert('A', 0).unwrap();
1✔
363
        index_map.insert('B', 1).unwrap();
1✔
364

1✔
365
        let bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
366

1✔
367
        // Test incoming edges to A (should be from A and B)
1✔
368
        let mut incoming_a = ['\0'; 8];
1✔
369
        let incoming_slice = collect(bit_map_matrix.incoming_edges('A').unwrap(), &mut incoming_a);
1✔
370
        assert_eq!(incoming_slice.len(), 2); // A->A, B->A
1✔
371
        assert!(incoming_slice.contains(&'A'));
1✔
372
        assert!(incoming_slice.contains(&'B'));
1✔
373

374
        // Test incoming edges to B (should be from A only)
375
        let mut incoming_b = ['\0'; 8];
1✔
376
        let incoming_slice = collect(bit_map_matrix.incoming_edges('B').unwrap(), &mut incoming_b);
1✔
377
        assert_eq!(incoming_slice, &['A']); // A->B
1✔
378
    }
1✔
379

380
    #[test]
381
    fn test_bit_map_matrix_outgoing_edges_bounds() {
1✔
382
        // Create a matrix with edges: A->A, A->B
1✔
383
        let bits = [
1✔
384
            [0b00000011u8], // Row 0: edges to nodes 0 and 1 (A->A, A->B)
1✔
385
            [0b00000000u8], // Row 1: no edges
1✔
386
            [0b00000000u8], // Row 2: no edges
1✔
387
            [0b00000000u8], // Row 3: no edges
1✔
388
            [0b00000000u8], // Row 4: no edges
1✔
389
            [0b00000000u8], // Row 5: no edges
1✔
390
            [0b00000000u8], // Row 6: no edges
1✔
391
            [0b00000000u8], // Row 7: no edges
1✔
392
        ];
1✔
393
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
394

1✔
395
        // Map custom node IDs 'A','B' to matrix indices 0,1
1✔
396
        let mut index_map = Dictionary::<char, usize, 8>::new();
1✔
397
        index_map.insert('A', 0).unwrap();
1✔
398
        index_map.insert('B', 1).unwrap();
1✔
399

1✔
400
        let bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
401

1✔
402
        // Test outgoing edges for non-existent node with usize::MAX fallback
1✔
403
        // This should not panic and should return an empty iterator
1✔
404
        let mut outgoing = ['\0'; 8];
1✔
405
        let outgoing_slice = collect(bit_map_matrix.outgoing_edges('C').unwrap(), &mut outgoing);
1✔
406
        assert_eq!(outgoing_slice.len(), 0);
1✔
407

408
        // Test outgoing edges for non-existent node with 0 fallback
409
        // This should also not panic and should return an empty iterator
410
        let mut outgoing = ['\0'; 8];
1✔
411
        let outgoing_slice = collect(bit_map_matrix.outgoing_edges('D').unwrap(), &mut outgoing);
1✔
412
        assert_eq!(outgoing_slice.len(), 0);
1✔
413
    }
1✔
414

415
    #[test]
416
    fn test_add_node_to_empty_bit_matrix() {
1✔
417
        let bits = [[0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8]];
1✔
418
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
419
        let index_map = Dictionary::<u32, usize, 10>::new();
1✔
420

1✔
421
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
422
        bit_map_matrix.add_node(42).unwrap();
1✔
423

1✔
424
        let mut nodes = [0u32; 2];
1✔
425
        let nodes_slice = collect(bit_map_matrix.iter_nodes().unwrap(), &mut nodes);
1✔
426
        assert_eq!(nodes_slice, &[42]);
1✔
427
        assert_eq!(bit_map_matrix.outgoing_edges(42).unwrap().count(), 0);
1✔
428
    }
1✔
429

430
    #[test]
431
    fn test_add_node_to_existing_bit_matrix() {
1✔
432
        let bits = [
1✔
433
            [0b00000001u8], // Row 0: edge to node 0 (self-loop)
1✔
434
            [0b00000000u8],
1✔
435
            [0b00000000u8],
1✔
436
            [0b00000000u8],
1✔
437
            [0b00000000u8],
1✔
438
            [0b00000000u8],
1✔
439
            [0b00000000u8],
1✔
440
            [0b00000000u8],
1✔
441
        ];
1✔
442
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
443
        let mut index_map = Dictionary::<char, usize, 10>::new();
1✔
444
        index_map.insert('A', 0).unwrap();
1✔
445

1✔
446
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
447
        bit_map_matrix.add_node('B').unwrap();
1✔
448

1✔
449
        let mut nodes = ['\0'; 4];
1✔
450
        let nodes_slice = collect_sorted(bit_map_matrix.iter_nodes().unwrap(), &mut nodes);
1✔
451
        assert_eq!(nodes_slice, &['A', 'B']);
1✔
452
        assert_eq!(bit_map_matrix.outgoing_edges('B').unwrap().count(), 0);
1✔
453
        assert_eq!(bit_map_matrix.outgoing_edges('A').unwrap().count(), 1);
1✔
454
    }
1✔
455

456
    #[test]
457
    fn test_add_duplicate_node_bit_matrix() {
1✔
458
        let bits = [[0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8]];
1✔
459
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
460
        let mut index_map = Dictionary::<u32, usize, 8>::new();
1✔
461
        index_map.insert(100, 0).unwrap();
1✔
462

1✔
463
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
464

1✔
465
        let result = bit_map_matrix.add_node(100);
1✔
466
        assert!(matches!(result, Err(GraphError::DuplicateNode(100))));
1✔
467

468
        let mut nodes = [0u32; 2];
1✔
469
        let nodes_slice = collect(bit_map_matrix.iter_nodes().unwrap(), &mut nodes);
1✔
470
        assert_eq!(nodes_slice, &[100]);
1✔
471
    }
1✔
472

473
    #[test]
474
    fn test_add_node_capacity_exceeded_bit_matrix() {
1✔
475
        let bits = [[0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8]];
1✔
476
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
477
        let mut index_map = Dictionary::<u32, usize, 8>::new();
1✔
478
        for i in 0..8 {
9✔
479
            index_map.insert(i as u32, i).unwrap();
8✔
480
        }
8✔
481

482
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
483

1✔
484
        let result = bit_map_matrix.add_node(999);
1✔
485
        assert!(matches!(result, Err(GraphError::OutOfCapacity)));
1✔
486

487
        let mut nodes = [0u32; 10];
1✔
488
        let nodes_slice = collect_sorted(bit_map_matrix.iter_nodes().unwrap(), &mut nodes);
1✔
489
        assert_eq!(nodes_slice, &[0, 1, 2, 3, 4, 5, 6, 7]);
1✔
490
    }
1✔
491

492
    #[test]
493
    fn test_add_multiple_nodes_bit_matrix() {
1✔
494
        let bits = [[0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8]];
1✔
495
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
496
        let index_map = Dictionary::<char, usize, 10>::new();
1✔
497

1✔
498
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
499
        bit_map_matrix.add_node('X').unwrap();
1✔
500
        bit_map_matrix.add_node('Y').unwrap();
1✔
501
        bit_map_matrix.add_node('Z').unwrap();
1✔
502

1✔
503
        let mut nodes = ['\0'; 4];
1✔
504
        let nodes_slice = collect_sorted(bit_map_matrix.iter_nodes().unwrap(), &mut nodes);
1✔
505
        assert_eq!(nodes_slice, &['X', 'Y', 'Z']);
1✔
506
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 0);
1✔
507
    }
1✔
508

509
    #[test]
510
    fn test_add_node_fills_gaps_bit_matrix() {
1✔
511
        let bits = [[0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8], [0u8]];
1✔
512
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
513
        let mut index_map = Dictionary::<u32, usize, 10>::new();
1✔
514
        index_map.insert(10, 0).unwrap();
1✔
515
        index_map.insert(30, 2).unwrap();
1✔
516
        index_map.insert(50, 4).unwrap();
1✔
517

1✔
518
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
519
        bit_map_matrix.add_node(20).unwrap();
1✔
520

1✔
521
        let mut nodes = [0u32; 6];
1✔
522
        let nodes_slice = collect_sorted(bit_map_matrix.iter_nodes().unwrap(), &mut nodes);
1✔
523
        assert_eq!(nodes_slice, &[10, 20, 30, 50]);
1✔
524
    }
1✔
525

526
    #[test]
527
    fn test_add_edge_success() {
1✔
528
        use crate::graph::GraphWithMutableEdges;
529

530
        let bits = [[0u8]; 8];
1✔
531
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
532
        let mut index_map = Dictionary::<char, usize, 10>::new();
1✔
533
        index_map.insert('A', 0).unwrap();
1✔
534
        index_map.insert('B', 1).unwrap();
1✔
535
        index_map.insert('C', 2).unwrap();
1✔
536

1✔
537
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
538

1✔
539
        // Add edges between existing nodes
1✔
540
        assert!(bit_map_matrix.add_edge('A', 'B').is_ok());
1✔
541
        assert!(bit_map_matrix.add_edge('B', 'C').is_ok());
1✔
542
        assert!(bit_map_matrix.add_edge('A', 'C').is_ok());
1✔
543

544
        // Verify edges were added
545
        let edge_count = bit_map_matrix.iter_edges().unwrap().count();
1✔
546
        assert_eq!(edge_count, 3);
1✔
547

548
        // Verify specific edges exist
549
        let mut edges = [('\0', '\0'); 8];
1✔
550
        let edges_slice = collect_sorted(bit_map_matrix.iter_edges().unwrap(), &mut edges);
1✔
551
        assert_eq!(edges_slice, &[('A', 'B'), ('A', 'C'), ('B', 'C')]);
1✔
552
    }
1✔
553

554
    #[test]
555
    fn test_add_edge_invalid_nodes() {
1✔
556
        use crate::graph::GraphWithMutableEdges;
557

558
        let bits = [[0u8]; 8];
1✔
559
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
560
        let mut index_map = Dictionary::<char, usize, 10>::new();
1✔
561
        index_map.insert('A', 0).unwrap();
1✔
562
        index_map.insert('B', 1).unwrap();
1✔
563

1✔
564
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
565

1✔
566
        // Try to add edge with non-existent source node
1✔
567
        let result = bit_map_matrix.add_edge('X', 'B');
1✔
568
        assert!(matches!(result, Err(GraphError::EdgeHasInvalidNode('X'))));
1✔
569

570
        // Try to add edge with non-existent destination node
571
        let result = bit_map_matrix.add_edge('A', 'Y');
1✔
572
        assert!(matches!(result, Err(GraphError::EdgeHasInvalidNode('Y'))));
1✔
573

574
        // Try to add edge with both nodes non-existent
575
        let result = bit_map_matrix.add_edge('X', 'Y');
1✔
576
        assert!(matches!(result, Err(GraphError::EdgeHasInvalidNode('X'))));
1✔
577
    }
1✔
578

579
    #[test]
580
    fn test_remove_edge_success() {
1✔
581
        use crate::graph::GraphWithMutableEdges;
582

583
        // Set up matrix with some initial edges
584
        let bits = [
1✔
585
            [0b00000110u8], // A->B, A->C
1✔
586
            [0b00000100u8], // B->C
1✔
587
            [0b00000000u8], // C->nothing
1✔
588
            [0b00000000u8],
1✔
589
            [0b00000000u8],
1✔
590
            [0b00000000u8],
1✔
591
            [0b00000000u8],
1✔
592
            [0b00000000u8],
1✔
593
        ];
1✔
594
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
595
        let mut index_map = Dictionary::<char, usize, 10>::new();
1✔
596
        index_map.insert('A', 0).unwrap();
1✔
597
        index_map.insert('B', 1).unwrap();
1✔
598
        index_map.insert('C', 2).unwrap();
1✔
599

1✔
600
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
601

1✔
602
        // Verify initial state
1✔
603
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 3);
1✔
604

605
        // Remove an edge
606
        assert!(bit_map_matrix.remove_edge('A', 'B').is_ok());
1✔
607

608
        // Verify edge was removed
609
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 2);
1✔
610
        let mut edges = [('\0', '\0'); 8];
1✔
611
        let edges_slice = collect_sorted(bit_map_matrix.iter_edges().unwrap(), &mut edges);
1✔
612
        assert_eq!(edges_slice, &[('A', 'C'), ('B', 'C')]);
1✔
613
    }
1✔
614

615
    #[test]
616
    fn test_remove_edge_not_found() {
1✔
617
        use crate::graph::GraphWithMutableEdges;
618

619
        let bits = [
1✔
620
            [0b00000010u8], // A->B
1✔
621
            [0b00000000u8],
1✔
622
            [0b00000000u8],
1✔
623
            [0b00000000u8],
1✔
624
            [0b00000000u8],
1✔
625
            [0b00000000u8],
1✔
626
            [0b00000000u8],
1✔
627
            [0b00000000u8],
1✔
628
        ];
1✔
629
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
630
        let mut index_map = Dictionary::<char, usize, 10>::new();
1✔
631
        index_map.insert('A', 0).unwrap();
1✔
632
        index_map.insert('B', 1).unwrap();
1✔
633
        index_map.insert('C', 2).unwrap();
1✔
634

1✔
635
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
636

1✔
637
        // Try to remove edge that doesn't exist
1✔
638
        let result = bit_map_matrix.remove_edge('A', 'C');
1✔
639
        assert!(matches!(result, Err(GraphError::EdgeNotFound('A', 'C'))));
1✔
640

641
        // Try to remove edge with non-existent nodes
642
        let result = bit_map_matrix.remove_edge('X', 'Y');
1✔
643
        assert!(matches!(result, Err(GraphError::EdgeNotFound('X', 'Y'))));
1✔
644

645
        // Verify original edge is still there
646
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 1);
1✔
647
    }
1✔
648

649
    #[test]
650
    fn test_add_remove_edge_comprehensive() {
1✔
651
        use crate::graph::GraphWithMutableEdges;
652

653
        let bits = [[0u8]; 8];
1✔
654
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
655
        let mut index_map = Dictionary::<u32, usize, 10>::new();
1✔
656
        index_map.insert(10, 0).unwrap();
1✔
657
        index_map.insert(20, 1).unwrap();
1✔
658
        index_map.insert(30, 2).unwrap();
1✔
659
        index_map.insert(40, 3).unwrap();
1✔
660

1✔
661
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
662

1✔
663
        // Start with empty graph
1✔
664
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 0);
1✔
665

666
        // Add several edges
667
        assert!(bit_map_matrix.add_edge(10, 20).is_ok());
1✔
668
        assert!(bit_map_matrix.add_edge(20, 30).is_ok());
1✔
669
        assert!(bit_map_matrix.add_edge(30, 40).is_ok());
1✔
670
        assert!(bit_map_matrix.add_edge(10, 40).is_ok());
1✔
671
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 4);
1✔
672

673
        // Remove some edges
674
        assert!(bit_map_matrix.remove_edge(20, 30).is_ok());
1✔
675
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 3);
1✔
676

677
        // Try to remove the same edge again (should fail)
678
        let result = bit_map_matrix.remove_edge(20, 30);
1✔
679
        assert!(matches!(result, Err(GraphError::EdgeNotFound(20, 30))));
1✔
680

681
        // Add the edge back
682
        assert!(bit_map_matrix.add_edge(20, 30).is_ok());
1✔
683
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 4);
1✔
684

685
        // Verify final edge set
686
        let mut edges = [(0u32, 0u32); 8];
1✔
687
        let edges_slice = collect_sorted(bit_map_matrix.iter_edges().unwrap(), &mut edges);
1✔
688
        assert_eq!(edges_slice, &[(10, 20), (10, 40), (20, 30), (30, 40)]);
1✔
689
    }
1✔
690

691
    #[test]
692
    fn test_self_loops() {
1✔
693
        use crate::graph::GraphWithMutableEdges;
694

695
        let bits = [[0u8]; 8];
1✔
696
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
697
        let mut index_map = Dictionary::<char, usize, 10>::new();
1✔
698
        index_map.insert('A', 0).unwrap();
1✔
699
        index_map.insert('B', 1).unwrap();
1✔
700

1✔
701
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
702

1✔
703
        // Add self-loops and regular edges
1✔
704
        assert!(bit_map_matrix.add_edge('A', 'A').is_ok()); // Self-loop
1✔
705
        assert!(bit_map_matrix.add_edge('A', 'B').is_ok());
1✔
706
        assert!(bit_map_matrix.add_edge('B', 'B').is_ok()); // Self-loop
1✔
707

708
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 3);
1✔
709

710
        // Remove self-loop
711
        assert!(bit_map_matrix.remove_edge('A', 'A').is_ok());
1✔
712
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 2);
1✔
713

714
        // Verify remaining edges
715
        let mut edges = [('\0', '\0'); 8];
1✔
716
        let edges_slice = collect_sorted(bit_map_matrix.iter_edges().unwrap(), &mut edges);
1✔
717
        assert_eq!(edges_slice, &[('A', 'B'), ('B', 'B')]);
1✔
718
    }
1✔
719

720
    #[test]
721
    fn test_edge_idempotency() {
1✔
722
        use crate::graph::GraphWithMutableEdges;
723

724
        let bits = [[0u8]; 8];
1✔
725
        let bitmap = super::super::bit_matrix::BitMatrix::new_unchecked(bits);
1✔
726
        let mut index_map = Dictionary::<u32, usize, 10>::new();
1✔
727
        index_map.insert(1, 0).unwrap();
1✔
728
        index_map.insert(2, 1).unwrap();
1✔
729

1✔
730
        let mut bit_map_matrix = BitMapMatrix::new(bitmap, index_map).unwrap();
1✔
731

1✔
732
        // Add edge
1✔
733
        assert!(bit_map_matrix.add_edge(1, 2).is_ok());
1✔
734
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 1);
1✔
735

736
        // Add same edge again (should be idempotent)
737
        assert!(bit_map_matrix.add_edge(1, 2).is_ok());
1✔
738
        assert_eq!(bit_map_matrix.iter_edges().unwrap().count(), 1);
1✔
739
    }
1✔
740
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc