Spaces:
Running
on
CPU Upgrade
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:
- Generate random point sets on the sphere (uniform distribution via Marsaglia's method)
- Compute the convex hull and extract the 1-skeleton (edge graph)
- Use pynauty to compute a canonical graph labeling
- Hash the canonical form to efficiently detect isomorphic graphs
- 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
- Higher vertex counts: Test N=25, 30, 40, ... (requires more samples or adaptive sampling)
- Face lattice enumeration: Use full face structure instead of just edges
- Symmetry analysis: For each type, compute automorphism group size
- Volume statistics: For each combinatorial type, compute the distribution of hyperbolic volumes
- Comparison with OEIS: Check if the sequence matches known combinatorial sequences
Dependencies
numpy: Random sampling and numerical operationsscipy: Convex hull computationpynauty: 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)