/// This file implements the driver loop which attempts Knuth-Bendix completion /// with various strategies on all instances in parallel. let numTasks = 32 struct Dispatcher { let subset: [Int] let strategies: [Strategy] var currentStrategy = 0 var currentInstance = 0 mutating func next() -> (instance: Int, strategy: Int)? { if subset.isEmpty || currentStrategy == strategies.count { return nil } defer { currentInstance += 1 if currentInstance == subset.count { currentInstance = 0 currentStrategy += 1 } } return (instance: subset[currentInstance], strategy: currentStrategy) } } struct Solver { let alphabet: Int let instances: [Presentation] // This is a list of indices of all remaining unsolved instances. var subset: [Int] // Histogram of finite monoid cardinality. var finite: [Int: Int] = [:] var factors: [Order: [Int: [Word]]] = [:] var maxFactors: [Int] = [] var output: Bool init(alphabet: Int, instances: [Presentation], output: Bool) { self.alphabet = alphabet self.instances = instances self.subset = Array(instances.indices) self.output = output } mutating func solve() async { if output { print("# Remaining \(subset.count)") print("# n:\tpresentation:\tcardinality:\tcomplete:\tstrategy:") } // The shortlex order with identity permutation of generators solves // almost everything. await attempt([Strategy()]) if output { print("# Remaining \(subset.count)") print("# Attempting more reduction orders") } var orderMix: [Int: [Order]] = [:] for i in [0, 1] { orderMix[alphabet + i] = getExhaustiveOrderMix(alphabet, i) } do { var strategies: [Strategy] = [] let strategy = Strategy() let orders = orderMix[alphabet]! // We already did the first one. for order in orders[1...] { strategies.append(strategy.withOrder(order)) } await attempt(strategies) } if output { print("# Remaining \(subset.count)") print("# Attempting to add a generator") } do { collectFactors(orderMix[alphabet]!) var strategies: [Strategy] = [] for factorLength in 2 ..< maxFactors.count { for frequency in [0, 1] { let strategy = Strategy(factorLength: factorLength, frequency: frequency) for order in orderMix[alphabet + 1]! { strategies.append(strategy.withOrder(order)) } } } await attempt(strategies) } if output { print("# Remaining \(subset.count)") for n in subset { let instance = instances[n] print("\(n + 1)\t\(instance)\thard") } print("# Finite monoids: ", terminator: "") print(finite.sorted { $0.0 < $1.0 } .map { "\($0): \($1)" } .joined(separator: ", ")) } } mutating func collectFactors(_ orders: [Order]) { for order in orders { for n in subset { factors[order, default: [:]][n] = collectFactors(n, instances[n], order: order.simplified) } } } mutating func collectFactors(_ n: Int, _ p: Presentation, order: Order) -> [Word] { // FIXME: The 6 is a magic number. let words = p.collectFactors(order: order, upToLength: 6) if !words.isEmpty { let longestFactor = words.map { $0.count }.max()! for i in 2 ... longestFactor { let factorsOfLength = words.filter { $0.count == i } while maxFactors.count <= i { maxFactors.append(0) } maxFactors[i] = max(maxFactors[i], factorsOfLength.count) } } return words } func prepare(_ instance: Int, _ strategy: Strategy) -> Strategy? { var strategy = strategy var factorsOfLength: [Word] = [] if let length = strategy.factorLength { let order = strategy.order.removeGenerator(Symbol(alphabet)) factorsOfLength = (factors[order]!)[instance]! let factorsOfLength = factorsOfLength.filter { $0.count == length } if strategy.frequency >= factorsOfLength.count { return nil } // Add a new generator 'c' and a rule 'c=x' for a magic factor 'x'. let newFactor = factorsOfLength[strategy.frequency] let newGenerator = [Symbol(alphabet)] // If 'c' is just going to reduce to 'x' there's no point in // considering it further. if compare(newFactor, newGenerator, order: strategy.order) == .lessThan { return nil } strategy.extra = [Rule(lhs: newFactor, rhs: newGenerator)] } strategy.order = strategy.order.simplified return strategy } mutating func attempt(_ strategies: [Strategy]) async { if subset.isEmpty { return } await withTaskGroup(of: (Int, Int, Solution?).self) { group in var dispatcher = Dispatcher(subset: subset, strategies: strategies) var solved: [Int: (Int, Solution)] = [:] var pending: [Int: Int] = [:] func startTask() { while true { guard let (instance, strategyIndex) = dispatcher.next() else { return } if solved[instance] != nil { continue } guard let strategy = prepare(instance, strategies[strategyIndex]) else { continue } pending[strategyIndex, default: 0] += 1 let p = instances[instance] group.addTask { () -> (Int, Int, Solution?) in if let solution = try? p.complete(strategy) { return (instance, strategyIndex, solution) } return (instance, strategyIndex, nil) } return } } func completeTask(_ instance: Int, _ strategyIndex: Int, _ solution: Solution?) { pending[strategyIndex, default: 0] -= 1 precondition(pending[strategyIndex]! >= 0) if let solution { // The lowest-numbered strategy is the 'official' solution for the instance. var betterStrategy = true if let (oldStrategyIndex, _) = solved[instance] { precondition(oldStrategyIndex != strategyIndex) if oldStrategyIndex < strategyIndex { betterStrategy = false } } if betterStrategy { solved[instance] = (strategyIndex, solution) } } while retireStrategy() {} } func retireStrategy() -> Bool { // Check if nothing remains. guard let minPending = pending.keys.min() else { return false } // Check if we're going to dispatch more instances with this strategy. precondition(minPending <= dispatcher.currentStrategy) if minPending == dispatcher.currentStrategy { return false } // Check if we're still waiting for instances to finish with this // strategy. if pending[minPending]! > 0 { return false } // Otherwise, retire this strategy. pending[minPending] = nil // Print out all instances solved by this strategy and remove them // from the list. subset = subset.filter { n in guard let (strategyIndex, solution) = solved[n] else { return true } guard strategyIndex == minPending else { return true } // Print the instance and solution. var str = "\(n + 1)\t\(instances[n])\t" if let cardinality = solution.cardinality { str += "finite:\(cardinality)" finite[cardinality, default: 0] += 1 } else { str += "infinite" } str += "\tfcrs:\(solution.presentation)" // Print the extra generators that were added, if any. if !solution.extra.isEmpty { str += "\t\(printRules(solution.extra))" } if output { print(str) } return false } return true } for _ in 0 ..< numTasks { startTask() } for await (instance, strategyIndex, solution) in group { startTask() completeTask(instance, strategyIndex, solution) } } } }