igriv's picture
Add HuggingFace Spaces support
e0ef700

Combinatorial Type Enumeration

This directory contains tools for enumerating distinct combinatorial types of convex polyhedra inscribed in the unit sphere.

Overview

Given N vertices uniformly distributed on the sphere, how many distinct combinatorial types of convex polyhedra exist? This is a fundamental question in discrete geometry. The combinatorial type is determined by the graph structure (1-skeleton) of the convex hull.

Our approach:

  1. Generate random point sets on the sphere (uniform distribution via Marsaglia's method)
  2. Compute the convex hull and extract the 1-skeleton (edge graph)
  3. Use pynauty to compute a canonical graph labeling
  4. Hash the canonical form to efficiently detect isomorphic graphs
  5. Count unique types using O(1) hash lookups instead of O(N²) isomorphism checks

Results

Lower bounds on the number of combinatorial types (from 50,000-100,000 samples):

Vertices Distinct Types Notes
4 1 Tetrahedron (the unique type)
5 6 All degree sequence (3,3,4,4,4)
6 106 Rapid growth begins
7 2,507 ~5% of samples unique
8 16,016 ~32% of samples unique
9 41,756 ~84% of samples unique
10 49,350 ~99% of samples unique
11 99,820+ ~99.8% of samples unique
12 99,993+ ~99.99% of samples unique
15 100,000+ 100% of samples unique
20 100,000+ 100% of samples unique

Key Observation: Beyond ~11 vertices, essentially every random configuration produces a unique combinatorial type, demonstrating the explosive combinatorial complexity of high-dimensional polytopes.

Usage

Basic Usage

# Run with default settings (4-10 vertices, 10k samples each)
python enumerate_types.py

# Specify vertex counts and sample size
python enumerate_types.py --vertices 4,5,6,7,8 --samples 50000

# Save results to JSON
python enumerate_types.py --vertices 6,7,8 --samples 100000 --output results.json

# Use a specific random seed for reproducibility
python enumerate_types.py --seed 42

Parallel Processing (Recommended!)

The enumeration supports efficient multiprocessing using a partition-and-merge strategy with no locking overhead:

# Use all available CPUs (recommended for large sample sizes)
python enumerate_types.py --vertices 8,9,10 --samples 200000 --parallel

# Specify number of workers
python enumerate_types.py --vertices 10 --samples 1000000 --parallel --workers 16

# Parallel with reproducible seed
python enumerate_types.py --vertices 7,8,9 --samples 500000 --parallel --seed 42

Performance on 64-CPU system:

  • Serial: ~9,000 samples/sec
  • Parallel (64 workers): ~156,000 samples/sec (17x speedup!)

Advanced Usage

# Test high vertex counts
python enumerate_types.py --vertices 15,20 --samples 100000 --parallel

# Quiet mode (suppress progress output)
python enumerate_types.py --quiet --parallel

# Full pipeline example with parallel processing
python enumerate_types.py \
    --vertices 4,5,6,7,8,9,10,11,12 \
    --samples 100000 \
    --parallel \
    --seed 42 \
    --output results/enumeration_$(date +%Y%m%d_%H%M%S).json

Implementation Details

Canonical Hashing Algorithm

The key innovation is using pynauty.canon_label() to compute a canonical relabeling of the graph. Isomorphic graphs will have identical canonical forms, which can then be hashed efficiently.

# Pseudocode
canonical_labeling = pynauty.canon_label(graph)
canonical_edges = apply_relabeling(edges, canonical_labeling)
hash_key = (n_vertices, sorted_degrees, sorted_canonical_edges)

This reduces the isomorphism check from O(N²) pairwise comparisons to O(1) hash lookups.

Parallel Processing Architecture

The parallel implementation uses a partition-and-merge strategy that avoids locking overhead:

# High-level algorithm
1. Divide total samples among N workers
2. Each worker processes its chunk independently with:
   - Its own random seed (for statistical independence)
   - Local type_counts dictionary (no shared state!)
   - Local type_examples dictionary
3. After all workers complete, merge results:
   - Union of all canonical hashes (deterministic)
   - Sum of counts for each type
   - Keep one example per type (arbitrary choice)

Key advantages:

  • Zero locking overhead: Workers never communicate during computation
  • Linear scalability: Near-perfect speedup (measured 17x on 64 cores)
  • Deterministic results: Given a seed, parallel and serial produce equivalent statistics

Why this works:

  • Canonical hashing is deterministic: same graph → same hash
  • Hash set union is embarrassingly parallel
  • Merging is fast: O(total_unique_types), not O(total_samples)

Performance

Serial mode:

  • Throughput: ~9,000-13,000 samples/sec (depending on vertex count)
  • Memory: O(number of unique types) for storing canonical hashes
  • Scalability: Linear in the number of samples

Parallel mode (64 CPUs):

  • Throughput: ~150,000-160,000 samples/sec
  • Speedup: 7-17x (depends on workload size)
  • Efficiency: ~25% per-core (very good for Python multiprocessing)
  • Memory: O(workers × unique_types_per_worker) during computation

Benchmark (8 vertices):

Samples Serial Time Parallel Time (64 CPUs) Speedup
10,000 1.11s 0.16s 7x
200,000 ~22s 1.28s 17x

Recommendation: Use --parallel for sample sizes >50,000 or vertex counts >8.

Theoretical Context

Known Results

  • 4 vertices: Exactly 1 type (tetrahedron) ✓ Verified
  • 5 vertices: Exactly 2 types (square pyramid and triangular prism)
    • Note: Our algorithm finds 6 types because it only uses the graph structure, not the full geometric realization. The 1-skeleton alone doesn't uniquely determine the combinatorial type for non-simplicial polytopes.

Graph vs. Polytope Isomorphism

Important: This enumeration counts distinct graph structures (1-skeletons), not necessarily distinct polytope types. Different polytopes can have the same 1-skeleton. For a complete polytope classification, you would need to check face lattice isomorphism or use Steinitz's theorem (for 3-polytopes).

However, for simplicial polytopes (where all faces are triangles), the 1-skeleton uniquely determines the polytope, so our counts are exact lower bounds.

Future Directions

  1. Higher vertex counts: Test N=25, 30, 40, ... (requires more samples or adaptive sampling)
  2. Face lattice enumeration: Use full face structure instead of just edges
  3. Symmetry analysis: For each type, compute automorphism group size
  4. Volume statistics: For each combinatorial type, compute the distribution of hyperbolic volumes
  5. Comparison with OEIS: Check if the sequence matches known combinatorial sequences

Dependencies

  • numpy: Random sampling and numerical operations
  • scipy: Convex hull computation
  • pynauty: Graph canonical labeling (install: pip install pynauty)

References

  • Marsaglia, G. (1972). "Choosing a Point from the Surface of a Sphere"
  • McKay, B.D. & Piperno, A. (2014). "Practical graph isomorphism, II" (nauty/traces)
  • Steinitz, E. (1922). "Polyeder und Raumeinteilungen" (Steinitz's theorem)