harness_space/complexes/
cubical.rs

1//! # Cubical Complexes and Cubical Topology
2//!
3//! This module provides data structures for representing **cubical complexes**, which are
4//! fundamental objects in algebraic topology and computational topology. Cubical complexes
5//! are topological spaces built by attaching $k$-dimensional cubes (hypercubes) together.
6//!
7//! ## Core Components
8//!
9//! - **[`Cube`]**: Represents an individual $k$-cube. Each cube has a dimension and is defined by
10//!   its vertices in a discrete grid structure.
11//!
12//! ## Mathematical Background
13//!
14//! A $k$-dimensional cube (or $k$-cube) in this implementation is represented as a collection
15//! of $2^k$ vertices that form the corners of a hypercube. For example:
16//! - A $0$-cube is a point (1 vertex)
17//! - A $1$-cube is a unit edge (2 vertices)
18//! - A $2$-cube is a unit square (4 vertices)
19//! - A $3$-cube is a unit cube in 3D space (8 vertices)
20//!
21//! The faces of a $k$-cube are obtained by systematic selection of vertex subsets that
22//! correspond to $(k-1)$-dimensional faces of the hypercube.
23
24use std::fmt;
25
26use super::ComplexElement;
27
28/// Represents an individual cube in a cubical complex.
29///
30/// A $k$-cube is represented by its $2^k$ vertices and its dimension.
31/// The vertices are ordered according to a binary coordinate system where
32/// each vertex corresponds to a corner of the hypercube.
33///
34/// # Fields
35///
36/// * `vertices`: A `Vec<usize>` containing the vertex indices that define the cube. For a $k$-cube,
37///   this contains $2^k$ vertices ordered systematically.
38/// * `dimension`: The intrinsic dimension of the cube (e.g., 0 for a point, 1 for an edge).
39/// * `id`: An optional unique identifier assigned when the cube is added to a complex.
40#[derive(Debug, Clone, PartialEq, Eq, Hash)]
41pub struct Cube {
42  /// The vertex indices that define this cube.
43  pub vertices:  Vec<usize>,
44  /// The dimension of this cube (e.g., 0 for a point, 1 for an edge, 2 for a face).
45  pub dimension: usize,
46  /// An optional unique identifier assigned when the cube is added to a complex.
47  pub id:        Option<usize>,
48}
49
50impl Cube {
51  /// Creates a new cube with the given dimension and vertices.
52  ///
53  /// # Arguments
54  ///
55  /// * `dimension`: The dimension of the cube.
56  /// * `vertices`: The vertex indices that define the cube. For a k-cube, this should contain
57  ///   exactly 2^k vertices.
58  ///
59  /// # Panics
60  ///
61  /// Panics if `vertices.len()` is not equal to `2^dimension`.
62  pub fn new(dimension: usize, vertices: Vec<usize>) -> Self {
63    let expected_vertex_count = 1 << dimension; // 2^dimension
64    assert_eq!(
65      vertices.len(),
66      expected_vertex_count,
67      "A {}-cube must have exactly {} vertices, got {}",
68      dimension,
69      expected_vertex_count,
70      vertices.len()
71    );
72    Self { vertices, dimension, id: None }
73  }
74
75  /// Creates a new 0-cube (vertex) from a single vertex index.
76  pub fn vertex(vertex: usize) -> Self { Self::new(0, vec![vertex]) }
77
78  /// Creates a new 1-cube (edge) from two vertex indices.
79  pub fn edge(v0: usize, v1: usize) -> Self { Self::new(1, vec![v0, v1]) }
80
81  /// Creates a new 2-cube (square) from four vertex indices.
82  /// The vertices should be ordered as: [bottom-left, bottom-right, top-left, top-right]
83  /// corresponding to binary coordinates (0,0), (1,0), (0,1), (1,1).
84  pub fn square(vertices: [usize; 4]) -> Self { Self::new(2, vertices.to_vec()) }
85
86  /// Creates a new cube with a specific ID.
87  const fn with_id(mut self, new_id: usize) -> Self {
88    self.id = Some(new_id);
89    self
90  }
91
92  /// Returns a slice reference to the vertex indices of the cube.
93  pub fn vertices(&self) -> &[usize] { &self.vertices }
94
95  /// Returns the dimension of the cube.
96  pub const fn dimension(&self) -> usize { self.dimension }
97
98  /// Returns the ID of the cube if it has been assigned to a complex.
99  pub const fn id(&self) -> Option<usize> { self.id }
100
101  /// Checks if this cube has the same mathematical content as another.
102  pub fn same_content(&self, other: &Self) -> bool {
103    self.dimension == other.dimension && self.vertices == other.vertices
104  }
105}
106
107impl PartialOrd for Cube {
108  fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
109}
110
111impl Ord for Cube {
112  fn cmp(&self, other: &Self) -> std::cmp::Ordering {
113    // First compare by dimension, then by vertices
114    self.dimension.cmp(&other.dimension).then_with(|| self.vertices.cmp(&other.vertices))
115  }
116}
117
118impl fmt::Display for Cube {
119  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120    write!(f, "Cube{}(vertices:{:?})", self.dimension, self.vertices)
121  }
122}
123
124impl ComplexElement for Cube {
125  fn dimension(&self) -> usize { self.dimension }
126
127  fn faces(&self) -> Vec<Self> {
128    if self.dimension == 0 {
129      return Vec::new(); // 0-cubes have no faces
130    }
131
132    let mut faces = Vec::new();
133    let k = self.dimension;
134
135    // For a k-cube, faces are (k-1)-cubes obtained by fixing one coordinate
136    // In the binary representation, this means fixing one bit position
137    for coord_idx in 0..k {
138      // For each coordinate direction, create faces by fixing that coordinate
139      // to both 0 and 1
140      for bit_value in [0, 1] {
141        let mut face_vertices = Vec::new();
142
143        // Collect vertices where the coord_idx-th bit matches bit_value
144        for (vertex_idx, &vertex) in self.vertices.iter().enumerate() {
145          if ((vertex_idx >> coord_idx) & 1) == bit_value {
146            face_vertices.push(vertex);
147          }
148        }
149
150        // Create the (k-1)-dimensional face
151        if !face_vertices.is_empty() && face_vertices.len() == (1 << (k - 1)) {
152          let face = Self::new(k - 1, face_vertices);
153          faces.push(face);
154        }
155      }
156    }
157
158    faces
159  }
160
161  fn boundary_with_orientations(&self) -> Vec<(Self, i32)> {
162    if self.dimension == 0 {
163      return Vec::new();
164    }
165
166    let mut faces_with_orientations = Vec::new();
167    let k = self.dimension;
168
169    // Compute cubical boundary: ∂_k σ = Σ_{i=0}^{k-1} (-1)^i (σ|_{x_i=1} - σ|_{x_i=0})
170    for coord_idx in 0..k {
171      // Get the two faces by fixing coordinate coord_idx to 0 and 1
172      for (bit_value, base_sign) in [(0, -1), (1, 1)] {
173        let mut face_vertices = Vec::new();
174
175        // Collect vertices where the coord_idx-th bit matches bit_value
176        for (vertex_idx, &vertex) in self.vertices.iter().enumerate() {
177          if ((vertex_idx >> coord_idx) & 1) == bit_value {
178            face_vertices.push(vertex);
179          }
180        }
181
182        // Create the face with proper orientation
183        if !face_vertices.is_empty() && face_vertices.len() == (1 << (k - 1)) {
184          let face = Self::new(k - 1, face_vertices);
185          // Cubical boundary orientation: (-1)^i * (face_1 - face_0)
186          let orientation = base_sign * if coord_idx % 2 == 0 { 1 } else { -1 };
187          faces_with_orientations.push((face, orientation));
188        }
189      }
190    }
191
192    faces_with_orientations
193  }
194
195  fn id(&self) -> Option<usize> { self.id }
196
197  fn same_content(&self, other: &Self) -> bool { self.same_content(other) }
198
199  fn with_id(&self, new_id: usize) -> Self { self.clone().with_id(new_id) }
200}
201
202#[cfg(test)]
203mod tests {
204  use harness_algebra::algebras::boolean::Boolean;
205
206  use super::*;
207  use crate::{complexes::Complex, homology::Chain};
208
209  #[test]
210  fn test_cube_creation() {
211    let vertex = Cube::vertex(42);
212    assert_eq!(vertex.dimension(), 0);
213    assert_eq!(vertex.vertices(), &[42]);
214    assert_eq!(vertex.id(), None);
215
216    let edge = Cube::edge(10, 11);
217    assert_eq!(edge.dimension(), 1);
218    assert_eq!(edge.vertices(), &[10, 11]);
219    assert_eq!(edge.id(), None);
220
221    let square = Cube::square([0, 1, 2, 3]);
222    assert_eq!(square.dimension(), 2);
223    assert_eq!(square.vertices(), &[0, 1, 2, 3]);
224    assert_eq!(square.id(), None);
225  }
226
227  #[test]
228  fn test_cube_with_id() {
229    let cube = Cube::edge(10, 11);
230    assert_eq!(cube.id(), None);
231
232    let cube_with_id = cube.with_id(42);
233    assert_eq!(cube_with_id.id(), Some(42));
234    assert_eq!(cube_with_id.vertices(), &[10, 11]); // Content unchanged
235  }
236
237  #[test]
238  fn test_cube_same_content() {
239    let cube1 = Cube::edge(10, 11);
240    let cube2 = Cube::edge(10, 11); // Same content
241    let cube3 = Cube::edge(10, 12); // Different vertices
242    let cube4 = cube1.clone().with_id(42); // Same content, different ID
243
244    assert!(cube1.same_content(&cube2));
245    assert!(!cube1.same_content(&cube3));
246    assert!(cube1.same_content(&cube4)); // Content equality ignores ID
247  }
248
249  #[test]
250  fn test_cube_ordering() {
251    let v1 = Cube::vertex(0);
252    let v2 = Cube::vertex(1);
253    let e1 = Cube::edge(0, 1);
254
255    assert!(v1 < v2); // Same dimension, different vertices
256    assert!(v1 < e1); // Different dimension
257  }
258
259  #[test]
260  fn test_cube_faces() {
261    // Test 0-cube (vertex) faces
262    let vertex = Cube::vertex(42);
263    let vertex_faces = vertex.faces();
264    assert_eq!(vertex_faces.len(), 0); // No faces for 0-cubes
265
266    // Test 1-cube (edge) faces
267    let edge = Cube::edge(10, 11);
268    let edge_faces = edge.faces();
269    assert_eq!(edge_faces.len(), 2); // Two vertices
270
271    // Both faces should be 0-cubes
272    for face in &edge_faces {
273      assert_eq!(face.dimension(), 0);
274      assert_eq!(face.vertices().len(), 1);
275    }
276
277    // The faces should contain the edge's vertices
278    let face_vertices: Vec<usize> = edge_faces.iter().flat_map(Cube::vertices).copied().collect();
279    assert!(face_vertices.contains(&10));
280    assert!(face_vertices.contains(&11));
281
282    // Test 2-cube (square) faces
283    let square = Cube::square([0, 1, 2, 3]);
284    let square_faces = square.faces();
285    assert_eq!(square_faces.len(), 4); // Four edges
286
287    // All faces should be 1-cubes
288    for face in &square_faces {
289      assert_eq!(face.dimension(), 1);
290      assert_eq!(face.vertices().len(), 2);
291    }
292  }
293
294  #[test]
295  #[should_panic = "A 1-cube must have exactly 2 vertices, got 3"]
296  fn test_invalid_vertex_count() {
297    // A 1-cube should have exactly 2 vertices, not 3
298    Cube::new(1, vec![0, 1, 2]);
299  }
300
301  #[test]
302  fn test_cubical_complex_basic() {
303    let mut complex: Complex<Cube> = Complex::new();
304    let square = Cube::square([0, 1, 2, 3]);
305    complex.join_element(square);
306
307    assert_eq!(complex.elements_of_dimension(2).len(), 1); // 1 square
308    assert_eq!(complex.elements_of_dimension(1).len(), 4); // 4 edges
309    assert_eq!(complex.elements_of_dimension(0).len(), 4); // 4 vertices
310  }
311
312  #[test]
313  fn test_cubical_chain_operations() {
314    let mut complex: Complex<Cube> = Complex::new();
315
316    // Create two edges
317    let edge1 = Cube::edge(0, 1);
318    let edge2 = Cube::edge(1, 2);
319    let added_edge1 = complex.join_element(edge1);
320    let added_edge2 = complex.join_element(edge2);
321
322    let chain1 = Chain::from_item_and_coeff(&complex, added_edge1, 1_i32);
323    let chain2 = Chain::from_item_and_coeff(&complex, added_edge2, 2_i32);
324
325    let result = chain1 + chain2;
326
327    assert_eq!(result.items.len(), 2);
328    assert_eq!(result.coefficients, vec![1, 2]);
329  }
330
331  #[test]
332  fn test_cubical_boundary_operations() {
333    let mut complex: Complex<Cube> = Complex::new();
334
335    // Test edge boundary
336    let edge = Cube::edge(0, 1);
337    let added_edge = complex.join_element(edge);
338    let chain = Chain::from_item_and_coeff(&complex, added_edge, 1);
339
340    let boundary = chain.boundary();
341    assert_eq!(boundary.items.len(), 2); // Two vertices
342
343    // Test square boundary
344    let square = Cube::square([0, 1, 2, 3]);
345    let added_square = complex.join_element(square);
346    let square_chain = Chain::from_item_and_coeff(&complex, added_square, 1);
347
348    let square_boundary = square_chain.boundary();
349    assert_eq!(square_boundary.items.len(), 4); // Four edges
350  }
351
352  #[test]
353  fn test_cubical_boundary_squared_is_zero() {
354    let mut complex: Complex<Cube> = Complex::new();
355
356    let square = Cube::square([0, 1, 2, 3]);
357    let added_square = complex.join_element(square);
358    let chain = Chain::from_item_and_coeff(&complex, added_square, 1);
359
360    let boundary = chain.boundary();
361    let boundary_squared = boundary.boundary();
362
363    // Boundary of boundary should be empty (∂² = 0)
364    assert_eq!(boundary_squared.items.len(), 0);
365    assert_eq!(boundary_squared.coefficients.len(), 0);
366  }
367
368  #[test]
369  fn test_cubical_incidence_poset_condition_1() {
370    // Condition 1: If [σ : τ] ≠ 0, then σ ⊲ τ and there are no cells between σ and τ
371    let mut complex: Complex<Cube> = Complex::new();
372
373    let square = Cube::square([0, 1, 2, 3]);
374    let added_square = complex.join_element(square);
375
376    let vertices = complex.elements_of_dimension(0);
377    let edges = complex.elements_of_dimension(1);
378
379    let boundary_with_orientations = added_square.boundary_with_orientations();
380    for (face, _orientation) in boundary_with_orientations {
381      assert_eq!(face.dimension(), 1);
382      assert!(edges.iter().any(|e| e.same_content(&face)));
383    }
384
385    for edge in &edges {
386      let edge_boundary = edge.boundary_with_orientations();
387      for (vertex_face, _orientation) in edge_boundary {
388        assert_eq!(vertex_face.dimension(), 0);
389        assert!(vertices.iter().any(|v| v.same_content(&vertex_face)));
390      }
391    }
392  }
393
394  #[test]
395  fn test_cubical_incidence_poset_condition_2() {
396    // Condition 2: For any σ ⊲ τ, Σ_γ∈P_X [σ : γ][γ : τ] = 0 (∂² = 0)
397    let mut complex: Complex<Cube> = Complex::new();
398
399    // Test with a square
400    let square = Cube::square([0, 1, 2, 3]);
401    let added_square = complex.join_element(square);
402
403    // Create a chain from the square
404    let square_chain = Chain::from_item_and_coeff(&complex, added_square, 1);
405
406    // Compute boundary of the square (should give edges)
407    let boundary_1 = square_chain.boundary();
408
409    // Compute boundary of the boundary (should be zero)
410    let boundary_2 = boundary_1.boundary();
411
412    // ∂² should be zero
413    assert_eq!(boundary_2.items.len(), 0, "∂² should be zero for cubical complex");
414    assert_eq!(boundary_2.coefficients.len(), 0, "∂² should have no coefficients");
415
416    // Also test with a more complex example - a cube
417    let cube = Cube::new(3, vec![0, 1, 2, 3, 4, 5, 6, 7]);
418    let mut cube_complex: Complex<Cube> = Complex::new();
419    let added_cube = cube_complex.join_element(cube);
420
421    let cube_chain = Chain::from_item_and_coeff(&cube_complex, added_cube, 1);
422    let cube_boundary_1 = cube_chain.boundary();
423    let cube_boundary_2 = cube_boundary_1.boundary();
424
425    assert_eq!(cube_boundary_2.items.len(), 0, "∂² should be zero for 3D cube");
426  }
427
428  #[test]
429  fn test_cubical_homology_point() {
430    let mut complex: Complex<Cube> = Complex::new();
431    let vertex = Cube::vertex(0);
432    complex.join_element(vertex);
433
434    let h0 = complex.homology::<Boolean>(0);
435    let h1 = complex.homology::<Boolean>(1);
436
437    assert_eq!(h0.betti_number, 1); // One connected component
438    assert_eq!(h1.betti_number, 0); // No 1D holes
439  }
440
441  #[test]
442  fn test_cubical_homology_edge() {
443    let mut complex: Complex<Cube> = Complex::new();
444    let edge = Cube::edge(0, 1);
445    complex.join_element(edge);
446
447    let h0 = complex.homology::<Boolean>(0);
448    let h1 = complex.homology::<Boolean>(1);
449
450    assert_eq!(h0.betti_number, 1); // One connected component
451    assert_eq!(h1.betti_number, 0); // No 1D holes (contractible)
452  }
453
454  #[test]
455  fn test_cubical_homology_square_boundary() {
456    let mut complex: Complex<Cube> = Complex::new();
457
458    // Create a square boundary (4 edges forming a cycle)
459    let edge1 = Cube::edge(0, 1);
460    let edge2 = Cube::edge(1, 2);
461    let edge3 = Cube::edge(2, 3);
462    let edge4 = Cube::edge(3, 0);
463
464    complex.join_element(edge1);
465    complex.join_element(edge2);
466    complex.join_element(edge3);
467    complex.join_element(edge4);
468
469    let h0 = complex.homology::<Boolean>(0);
470    let h1 = complex.homology::<Boolean>(1);
471
472    assert_eq!(h0.betti_number, 1); // One connected component
473    assert_eq!(h1.betti_number, 1); // One 1-dimensional hole
474  }
475
476  #[test]
477  fn test_cubical_homology_filled_square() {
478    let mut complex: Complex<Cube> = Complex::new();
479    let square = Cube::square([0, 1, 2, 3]);
480    complex.join_element(square);
481
482    let h0 = complex.homology::<Boolean>(0);
483    let h1 = complex.homology::<Boolean>(1);
484    let h2 = complex.homology::<Boolean>(2);
485
486    assert_eq!(h0.betti_number, 1); // One connected component
487    assert_eq!(h1.betti_number, 0); // No 1D holes (filled)
488    assert_eq!(h2.betti_number, 0); // No 2D holes
489  }
490
491  #[test]
492  fn test_cubical_homology_two_disjoint_squares() {
493    let mut complex: Complex<Cube> = Complex::new();
494
495    let square1 = Cube::square([0, 1, 2, 3]);
496    let square2 = Cube::square([4, 5, 6, 7]);
497    complex.join_element(square1);
498    complex.join_element(square2);
499
500    let h0 = complex.homology::<Boolean>(0);
501    let h1 = complex.homology::<Boolean>(1);
502
503    assert_eq!(h0.betti_number, 2); // Two connected components
504    assert_eq!(h1.betti_number, 0); // No 1D holes (both filled)
505  }
506}