     internal func log(_ msg: @autoclosure () -> String) {
         Debug.log(msg, debug)
     }
+    
+    internal func logNodes(_ name: String, _ nodes: [S : Node]) {
+        if debug {
+            let sorted = nodes.values.sorted{ $0.cell <= $1.cell }
+            log("\t\(name): {\n\t\t\(sorted.map{ $0.detailDescription }.joined(separator: ",\n\t\t"))\n\t}\n")
+        }
+    }
 }
+
+public extension ChainContractor {
+    public var contractedChainComplex: ChainComplex<Simplex, R> {
+        typealias CC = ChainComplex<S, R>
+        let chain = K.validDims.map{ (i) -> (CC.ChainBasis, CC.BoundaryMap) in
+            
+            let from = generators.filter { s in s.dim == i }
+            let map  = CC.BoundaryMap { s in self.diff[s] ?? C.zero }
+            return (from, map)
+        }
+        
+        return CC(name: K.name, chain)
+    }
+    
+    public var contractedCochainComplex: CochainComplex<Dual<Simplex>, R> {
+        typealias CC = CochainComplex<Dual<S>, R>
+        let chain = K.validDims.map{ (i) -> (CC.ChainBasis, CC.BoundaryMap) in
+            let from = generators.filter { s in s.dim == i }
+            let map  = CC.BoundaryMap { d in
+                let s = d.base
+                let e = R(intValue: (-1).pow(d.degree + 1))
+                let to = self.generators.filter { s in s.dim == i + 1 }
+                let vals = to.flatMap { t -> (Dual<S>, R)? in
+                    let a = self.diff[t]?[s] ?? R.zero
+                    return (a != R.zero) ? (Dual(t), e * a) : nil
+                }
+                return SimplicialCochain(vals)
+            }
+            return (from.map{ Dual($0) }, map)
+        }
+        
+        return CC(name: K.name, chain)
+    }
+    
+    public var homologyGroup: Homology<Simplex, R> {
+        let H = Homology(contractedChainComplex)
+        let map = ChainMap { s in self.g(s) }
+        
+        // factorize by: z -> f(z) -> [r]
+        
+        let factorizer = { (z: C) -> [R] in
+            if z == C.zero {
+                return []
+            }
+            let i = z.anyElement!.0.degree
+            return H[i].structure.factorize( self.f(z) )
+        }
+        return Homology(name: K.name, H, map, factorizer)
+    }
+    
+    public var cohomologyGroup: Cohomology<Dual<Simplex>, R> {
+        let H = Cohomology(contractedCochainComplex)
+        let C = ChainComplex(K, R.self)
+        let map = ChainMap { s in self.f(s) }.dualMap(domainChainComplex: C)
+        
+        // factorize by: c -> g^*(c) -> [r]
+        
+        let factorizer = { (c: SimplicialCochain<R>) -> [R] in
+            if c == .zero {
+                return []
+            }
+            let i = c.anyElement!.0.degree
+            let basis = self.generators.filter{ $0.degree == i }
+            
+            // express cocycle as: (g^*)c = Σ c(g(s)) s^*
+            
+            let values = basis.flatMap { s -> (Dual<S>, R)? in
+                let a = c.evaluate(self.g(s))
+                return (a != .zero) ? (Dual(s), a) : nil
+            }
+            let cocycle = SimplicialCochain<R>(values)
+            
+            return H[i].structure.factorize( cocycle )
+        }
+        return Cohomology(name: K.name, H, map, factorizer)
+    }
+}

        
        doneCells.insert(s)
        
        log("\tgenerators: \(generators)")
        if !relations.isEmpty {
            log("relations: \(relations)")
        }
        log("")
    }
    
    public func assertChainContraction() {
        for s in doneCells {
            let a1 = g(f(s)) - C(s)
            let a2 = h(C(s).boundary()) + h(s).boundary()

        }
        
        func collect(flatten: Bool = false) -> C {
            if refs.isEmpty {
                return self.value
            }
            
            var basket = [Node : R]()
            let value = _collect(1, &basket)
            
            assert(basket.forAll{ $0.value == R.zero }, "invalid: \(basket)")
            
            if flatten {
                self.value = value
