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
```bash
# 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:
```bash
# 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
```bash
# 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.
```python
# 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:
```python
# 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)