     }
 }
 
-final class SpectralSequence {
+final class SerreSS {
     var name: String? = nil
     let size: (width: Int, height: Int)
     

                    fatalError("conflict at E_\(r)[\((p, q))], \(group!) != \(newValue!)")
                }
            } didSet {
                self.update()
                target?.update()
                cotarget?.update()
            }
        }
        
        func update() {
            if let above = above, !above.isDetermined && isIsomorphicToAbove {
                above.group = self.group
            }
            if let below = below, !below.isDetermined && below.isIsomorphicToAbove {
                below.group = self.group
            }
        }
        
        weak var target: Component? = nil
        weak var cotarget: Component? = nil
        weak var above: Component? = nil

        
        var isIsomorphicToAbove: Bool {
            return isZeroMap && (cotarget?.isZeroMap ?? true)
        }
        
        var isInjectiveToTarget: Bool {
            return (above?.isZero ?? false) && (cotarget?.isZeroMap ?? true)
        }
        
        var isSurjectiveToTarget: Bool {
            return (target?.above?.isZero ?? false) && (target?.isZeroMap ?? true)
        }
        
        var isIsomorphicToTarget: Bool {
            return isInjectiveToTarget && isSurjectiveToTarget
        }
        
        func update() {
            if r == 2 {
                let E2 = sheet!
                
                if p == 0 { // copy rows →
                    for p in (1 ..< E2.width) {
                        if E2[0, q].isZero {
                            E2[p, q].group = 0
                        } else if E2[p, 0].isDetermined && E2[0, q].isDetermined {
                            E2[p, q].group = E2[p, 0].group! * E2[0, q].group!
                        }
                    }
                }
                if q == 0 { // copy cols ↑
                    for q in (1 ..< E2.height) {
                        if E2[p, 0].isZero {
                            E2[p, q].group = 0
                        } else if E2[p, 0].isDetermined && E2[0, q].isDetermined {
                            E2[p, q].group = E2[p, 0].group! * E2[0, q].group!
                        }
                    }
                }
            }
            
            if let above = above, !above.isDetermined && isIsomorphicToAbove {
                above.group = self.group
            }
            if let target = target, !target.isDetermined && isIsomorphicToTarget {
                target.group = self.group
            }
            if let cotarget = cotarget, !cotarget.isDetermined && cotarget.isIsomorphicToTarget {
                cotarget.group = self.group
            }
            if let below = below {
                if !below.isDetermined && below.isIsomorphicToAbove {
                    below.group = self.group
                }
                if let target = below.target, below.isIsomorphicToTarget {
                    if !target.isDetermined && below.isDetermined {
                        target.group = below.group
                    }
                    if !below.isDetermined && target.isDetermined {
                        below.group = target.group
                    }
                }
                if let cotarget = below.cotarget, cotarget.isIsomorphicToTarget {
                    if !cotarget.isDetermined && below.isDetermined {
                        cotarget.group = below.group
                    }
                    if !below.isDetermined && cotarget.isDetermined {
                        below.group = cotarget.group
                    }
                }
            }
        }
        
        static func zero(_ r: Int, _ p: Int, _ q: Int) -> Component {
