         doneCells.insert(s)
         
         if debug {
+            log("")
             log("\tgenerators: \(generators)")
-            if !relations.isEmpty {
-                log("relations: \(relations)")
-            }
-            log("")
-            
-            logNodes("f", fNodes)
-            logNodes("h", fNodes)
-            log("")
-            
-            assertChainContraction()
+            
+//            logNodes("f", fNodes)
+//            logNodes("h", hNodes)
+//            log("")
+
+//            assertChainContraction()
         }
     }
     
     internal func assertChainContraction() {
+        // assert chain complex
+        for s in generators {
+            let dds = d(d(s))
+            assert(dds == C.zero, "∂∂\(s) = \(dds), should be zero")
+        }
+        
+        // assert gf - 1 = h∂ + ∂h
         for s in doneCells {
             let a1 = g(f(s)) - C(s)
             let a2 = h(C(s).boundary()) + h(s).boundary()
             assert(a1 == a2, "(gf - 1)(\(s)) = \(a1),\n(h∂ + ∂h)(\(s)) = \(a2)\n")
         }
         
+        // assert fg = 1
         for s in generators {
-            let b1 = f(g(s))
-            assert(b1 == C(s), "fg(\(s)) = \(b1)\n")
-        }
-        
-        for s in generators.filter({ diff[$0] == nil}) {
-            let b = g(s).boundary()
-            assert(b == C.zero, "∂s = \(b), should be 0.\n")
-        }
-        
-        log("assertion complete.")
+            let fgs = f(g(s))
+            assert(fgs == C(s), "fg(\(s)) != \(fgs)\n")
+        }
+        
+        log("\tassertion complete.")
         log("")
     }
     

            hNodes[s] = Node(s, C.zero)
            
        } else {
            let candidates = f_bs
                .filter{ (t1, a) in a.isInvertible && diff[t1] == nil }
                .sorted{ (v1, v2) in v1.0 <= v2.0 } // TODO
            
            if let (t1, a) = candidates.last {
                let e = a.inverse!
                log("\tremove: \(t1) = \(-e * (f_bs - a * C(t1)))")
                
                generators.remove(at: generators.index(of: t1)!)
                
                for (i, r) in relations.enumerated().filter({ (_, r) in r[t1] != R.zero}) {
                    let e = r[t1] * a.inverse!
                    relations[i] = r - e * f_bs
                }
                
                for (s, b) in diff.filter({ (_, b) in b[t1] != R.zero }) {
                    let e = b[t1] * a.inverse!
                    diff[s] = b - e * f_bs
                }
                
                fNodes[s] = Node(s, C.zero)
                hNodes[s] = Node(s, C.zero)
                
                fNodes[t1]!.value = C.zero
                fNodes[t1]!.refs = (C(t1) - e * f_bs).map{ (t, a) in (fNodes[t]!, a) }
                
                hNodes[t1]!.value = -e * C(s)
                hNodes[t1]!.refs = (C(t1) - e * bs).map{ (t, a) in (hNodes[t]!, a) }
                
            } else {
                log("\tadd: \(s) with diff: \(f_bs)")
                
                generators.append(s)
                relations.append(f_bs)
                diff[s] = f_bs
                
                fNodes[s] = Node(s, C(s))

    }
    
    internal func logNodes(_ name: String, _ nodes: [S : Node]) {
        let sorted = nodes.values.filter{ $0.value != C.zero || !$0.refs.isEmpty }.sorted{ $0.cell <= $1.cell }
        log("\t\(name): {\n\t\t\(sorted.map{ $0.detailDescription }.joined(separator: ",\n\t\t"))\n\t}\n")
    }
    
    internal func logG() {
        let sorted = generators.sorted()
        log("\tg: {\n\t\t\(sorted.map{ "\($0) : \(g($0))" }.joined(separator: ",\n\t\t"))\n\t}\n")
    }
    
    internal func logGF() {
        let sorted = doneCells.sorted()
        log("\tg: {\n\t\t\(sorted.map{ "\($0) : \(g(f($0)))" }.joined(separator: ",\n\t\t"))\n\t}\n")
    }
}

