mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
New "Monoids" benchmark
This commit is contained in:
@@ -220,12 +220,24 @@ set(SWIFT_BENCH_MODULES
|
||||
|
||||
set(SWIFT_MULTISOURCE_SWIFT_BENCHES
|
||||
multi-source/PrimsSplit
|
||||
multi-source/Monoids
|
||||
)
|
||||
|
||||
set(PrimsSplit_sources
|
||||
multi-source/PrimsSplit/Prims.swift
|
||||
multi-source/PrimsSplit/Prims_main.swift)
|
||||
|
||||
set(Monoids_sources
|
||||
multi-source/Monoids/Automaton.swift
|
||||
multi-source/Monoids/Benchmark.swift
|
||||
multi-source/Monoids/Enumeration.swift
|
||||
multi-source/Monoids/Monoids.swift
|
||||
multi-source/Monoids/Presentation.swift
|
||||
multi-source/Monoids/RewritingSystem.swift
|
||||
multi-source/Monoids/Solver.swift
|
||||
multi-source/Monoids/Strategy.swift
|
||||
multi-source/Monoids/Trie.swift)
|
||||
|
||||
set(BENCH_DRIVER_LIBRARY_FLAGS)
|
||||
if (SWIFT_RUNTIME_ENABLE_LEAK_CHECKER)
|
||||
set(BENCH_DRIVER_LIBRARY_FLAGS -DSWIFT_RUNTIME_ENABLE_LEAK_CHECKER)
|
||||
|
||||
169
benchmark/multi-source/Monoids/Automaton.swift
Normal file
169
benchmark/multi-source/Monoids/Automaton.swift
Normal file
@@ -0,0 +1,169 @@
|
||||
/// This file implements an algorithm to compute the number of elements in a
|
||||
/// monoid (or determine it is infinite), given a complete presentation.
|
||||
|
||||
/// A finite state automaton, given by a set of vertices and edges.
|
||||
struct Automaton {
|
||||
var states: [Word] = []
|
||||
var transitions: [(Word, Symbol, Word)] = []
|
||||
}
|
||||
|
||||
extension Automaton {
|
||||
var hasStar: Bool {
|
||||
for state in states {
|
||||
var visited = Set<Word>()
|
||||
|
||||
func rec(_ state: Word) -> Bool {
|
||||
for (from, _, to) in transitions {
|
||||
if from == state {
|
||||
if visited.contains(to) {
|
||||
return true
|
||||
} else {
|
||||
visited.insert(to)
|
||||
if rec(to) { return true }
|
||||
visited.remove(to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
visited.insert(state)
|
||||
if rec(state) { return true }
|
||||
visited.remove(state)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/// If this automaton is star-free, count the number of unique words accepted.
|
||||
var countWords: Int {
|
||||
func R(_ q: Word) -> [Word] {
|
||||
var result: [Word] = []
|
||||
|
||||
for (from, _, to) in transitions {
|
||||
if to == q {
|
||||
result.append(from)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func T(_ q: Word, _ p: Word) -> Int {
|
||||
var letters = Set<Symbol>()
|
||||
for (from, x, to) in transitions {
|
||||
if from == q && to == p {
|
||||
letters.insert(x)
|
||||
}
|
||||
}
|
||||
return letters.count
|
||||
}
|
||||
|
||||
func N(_ q: Word) -> Int {
|
||||
if q == [] {
|
||||
return 1
|
||||
}
|
||||
|
||||
var result = 0
|
||||
for p in R(q) {
|
||||
result += N(p) * T(p, q)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var result = 0
|
||||
|
||||
for q in states {
|
||||
result += N(q)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs an automaton to recognize the complement of this regular set:
|
||||
///
|
||||
/// .*(x1|x2|...).*
|
||||
///
|
||||
/// where 'words' is [x1, x2, ...].
|
||||
///
|
||||
/// This is Lemma 2.1.3 in:
|
||||
///
|
||||
/// String Rewriting Systems, R.V. Book, F. Otto 1993. Springer New York.
|
||||
func buildAutomaton(_ words: [Word], _ alphabet: Int) -> Automaton {
|
||||
// Proper prefixes of each word.
|
||||
var prefixes = Set<Word>()
|
||||
|
||||
var result = Automaton()
|
||||
|
||||
func isIrreducible(_ word: Word) -> Bool {
|
||||
for i in 0 ..< word.count {
|
||||
for other in words {
|
||||
if i + other.count <= word.count {
|
||||
if Word(word[i ..< (i + other.count)]) == other {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
prefixes.insert([])
|
||||
for word in words {
|
||||
for i in 0 ..< word.count {
|
||||
let prefix = Word(word[0 ..< i])
|
||||
prefixes.insert(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
result.states = prefixes.sorted { compare($0, $1, order: .shortlex) == .lessThan }
|
||||
|
||||
for prefix in prefixes {
|
||||
for x in 0 ..< UInt8(alphabet) {
|
||||
let word = prefix + [x]
|
||||
|
||||
if prefixes.contains(word) {
|
||||
result.transitions.append((prefix, x, word))
|
||||
continue
|
||||
}
|
||||
|
||||
if !isIrreducible(word) {
|
||||
continue
|
||||
}
|
||||
|
||||
for i in 1 ... word.count {
|
||||
let suffix = Word(word[i...])
|
||||
|
||||
if prefixes.contains(suffix) {
|
||||
result.transitions.append((prefix, x, suffix))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
extension Presentation {
|
||||
/// The Irr(R) automaton.
|
||||
func automaton(alphabet: Int) -> Automaton {
|
||||
return buildAutomaton(rules.map { $0.lhs }, alphabet)
|
||||
}
|
||||
|
||||
/// Returns the number of irreducible words in this monoid presentation, or
|
||||
/// nil if this set is infinite.
|
||||
///
|
||||
/// If the presentation is complete, this is the cardinality of the
|
||||
/// presented monoid. Otherwise, it is an upper bound.
|
||||
func cardinality(alphabet: Int) -> Int? {
|
||||
let automaton = automaton(alphabet: alphabet)
|
||||
if automaton.hasStar {
|
||||
return nil
|
||||
}
|
||||
return automaton.countWords
|
||||
}
|
||||
}
|
||||
20
benchmark/multi-source/Monoids/Benchmark.swift
Normal file
20
benchmark/multi-source/Monoids/Benchmark.swift
Normal file
@@ -0,0 +1,20 @@
|
||||
import TestsUtils
|
||||
import Dispatch
|
||||
|
||||
public let benchmarks = [
|
||||
BenchmarkInfo(
|
||||
name: "Monoids",
|
||||
runFunction: run_Monoids,
|
||||
tags: [.algorithm])
|
||||
]
|
||||
|
||||
func run_Monoids(_ n: Int) {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
for _ in 0 ... n {
|
||||
Task {
|
||||
await run(output: false)
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
}
|
||||
167
benchmark/multi-source/Monoids/Enumeration.swift
Normal file
167
benchmark/multi-source/Monoids/Enumeration.swift
Normal file
@@ -0,0 +1,167 @@
|
||||
/// This file implements algorithms for enumerating all monoid presentations
|
||||
/// up to a given length.
|
||||
|
||||
func nextSymbol(_ s: inout Symbol, alphabet: Int) -> Bool {
|
||||
precondition(alphabet > 0)
|
||||
if s == alphabet - 1 {
|
||||
s = 0
|
||||
return true
|
||||
}
|
||||
s += 1
|
||||
return false
|
||||
}
|
||||
|
||||
func nextWord(_ word: inout Word, alphabet: Int) -> Bool {
|
||||
var carry = true
|
||||
for i in word.indices.reversed() {
|
||||
carry = nextSymbol(&word[i], alphabet: alphabet)
|
||||
if !carry { break }
|
||||
}
|
||||
return carry
|
||||
}
|
||||
|
||||
func nextRule(_ rule: inout Rule, alphabet: Int) -> Bool {
|
||||
if nextWord(&rule.rhs, alphabet: alphabet) {
|
||||
rule.rhs = Word(repeating: 0, count: rule.rhs.count)
|
||||
if nextWord(&rule.lhs, alphabet: alphabet) {
|
||||
rule.lhs = Word(repeating: 0, count: rule.lhs.count)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func nextPresentation(_ p: inout Presentation, alphabet: Int) -> Bool {
|
||||
precondition(!p.rules.isEmpty)
|
||||
var carry = true
|
||||
for i in p.rules.indices.reversed() {
|
||||
carry = nextRule(&p.rules[i], alphabet: alphabet)
|
||||
if !carry { break }
|
||||
}
|
||||
return carry
|
||||
}
|
||||
|
||||
struct RuleShape {
|
||||
var lhs: Int
|
||||
var rhs: Int
|
||||
|
||||
var rule: Rule {
|
||||
return Rule(lhs: Word(repeating: 0, count: lhs),
|
||||
rhs: Word(repeating: 0, count: rhs))
|
||||
}
|
||||
}
|
||||
|
||||
struct PresentationShape {
|
||||
var rules: [RuleShape]
|
||||
|
||||
var presentation: Presentation {
|
||||
return Presentation(rules: rules.map { $0.rule })
|
||||
}
|
||||
}
|
||||
|
||||
func enumerateAll(alphabet: Int, shapes: [PresentationShape], output: Bool)
|
||||
-> [Presentation] {
|
||||
var filteredLHS = 0
|
||||
var filteredRHS = 0
|
||||
var filteredSymmetry = 0
|
||||
var total = 0
|
||||
|
||||
var instances: [Presentation] = []
|
||||
var unique: Set<Presentation> = []
|
||||
|
||||
let perms = allPermutations(alphabet)
|
||||
|
||||
for shape in shapes {
|
||||
var p = shape.presentation
|
||||
var done = false
|
||||
|
||||
loop: while !done {
|
||||
defer {
|
||||
done = nextPresentation(&p, alphabet: alphabet)
|
||||
}
|
||||
|
||||
total += 1
|
||||
|
||||
for n in 0 ..< p.rules.count - 1 {
|
||||
if compare(p.rules[n].lhs, p.rules[n + 1].lhs, order: .shortlex) != .lessThan {
|
||||
filteredLHS += 1
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
|
||||
for rule in p.rules {
|
||||
if compare(rule.rhs, rule.lhs, order: .shortlex) != .lessThan {
|
||||
filteredRHS += 1
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
|
||||
if unique.contains(p.sorted(order: .shortlex)) {
|
||||
filteredSymmetry += 1
|
||||
continue
|
||||
}
|
||||
|
||||
for perm in perms {
|
||||
let permuted = p.permuted(perm)
|
||||
unique.insert(permuted.sorted(order: .shortlex))
|
||||
}
|
||||
|
||||
precondition(p == p.sorted(order: .shortlex))
|
||||
instances.append(p)
|
||||
}
|
||||
}
|
||||
|
||||
if output {
|
||||
print("# Total \(total)")
|
||||
print("# Discarded lhs:\(filteredLHS),rhs:\(filteredRHS),"
|
||||
+ "symmetry:\(filteredSymmetry)")
|
||||
}
|
||||
|
||||
return instances
|
||||
}
|
||||
|
||||
func ruleShapes(_ n: Int) -> [RuleShape] {
|
||||
precondition(n > 0)
|
||||
var result: [RuleShape] = []
|
||||
for i in 0 ..< n {
|
||||
let j = n - i
|
||||
|
||||
// Don't generate rules with shorter left-hand side.
|
||||
if j < i { continue }
|
||||
|
||||
result.append(RuleShape(lhs: j, rhs: i))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func presentationShapes(rules: Int, ofLength n: Int) -> [PresentationShape] {
|
||||
precondition(n > 0)
|
||||
precondition(rules > 0)
|
||||
|
||||
if rules == 1 {
|
||||
return ruleShapes(n).map {
|
||||
PresentationShape(rules: [$0])
|
||||
}
|
||||
}
|
||||
|
||||
var result: [PresentationShape] = []
|
||||
for i in 1 ..< n {
|
||||
let next = presentationShapes(rules: rules - 1, ofLength: i)
|
||||
for x in ruleShapes(n - i) {
|
||||
for y in next {
|
||||
if x.lhs <= y.rules.first!.lhs {
|
||||
result.append(PresentationShape(rules: [x] + y.rules))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func presentationShapes(rules: Int, upToLength n: Int) -> [PresentationShape] {
|
||||
var shapes: [PresentationShape] = []
|
||||
for i in 1 ... n {
|
||||
shapes.append(contentsOf: presentationShapes(rules: rules, ofLength: i))
|
||||
}
|
||||
return shapes
|
||||
}
|
||||
26
benchmark/multi-source/Monoids/Monoids.swift
Normal file
26
benchmark/multi-source/Monoids/Monoids.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
/// This is the main entry point.
|
||||
///
|
||||
/// We enumerate all 2-generator, 2-relation monoids of length up to 10:
|
||||
///
|
||||
/// <a, b | u=v, w=x> where |u| + |v| + |w| + |x| <= 10
|
||||
///
|
||||
/// We attempt to build an FCRS for each one by trying various strategies,
|
||||
/// which ultimately succeeds for all but three instances.
|
||||
func run(output: Bool) async {
|
||||
let shapes = presentationShapes(rules: 2, upToLength: 10)
|
||||
|
||||
let alphabet = 2
|
||||
let instances = enumerateAll(alphabet: alphabet, shapes: shapes, output: output)
|
||||
|
||||
var solver = Solver(alphabet: alphabet, instances: instances, output: output)
|
||||
await solver.solve()
|
||||
|
||||
// These we know we can't solve.
|
||||
let expect: [Presentation] = ["bab=aaa,bbbb=1", "aaaa=1,abbba=b", "aaa=a,abba=bb"]
|
||||
|
||||
// Make sure everything else was solved.
|
||||
let unsolved = solver.subset.map { instances[$0] }
|
||||
if unsolved != expect {
|
||||
fatalError("Expected \(expect), but got \(unsolved)")
|
||||
}
|
||||
}
|
||||
372
benchmark/multi-source/Monoids/Presentation.swift
Normal file
372
benchmark/multi-source/Monoids/Presentation.swift
Normal file
@@ -0,0 +1,372 @@
|
||||
/// This file defines the data types we use for words, rules, and
|
||||
/// monoid presentations. Also, a few other fundamental algorithms
|
||||
/// for working with permutations and reduction orders.
|
||||
|
||||
typealias Symbol = UInt8
|
||||
|
||||
let symbols: [Character] = ["a", "b", "c"]
|
||||
|
||||
func printSymbol(_ s: Symbol) -> Character {
|
||||
return symbols[Int(s)]
|
||||
}
|
||||
|
||||
func parseSymbol(_ c: Character) -> Symbol {
|
||||
return Symbol(symbols.firstIndex(of: c)!)
|
||||
}
|
||||
|
||||
typealias Word = [Symbol]
|
||||
|
||||
func printWord(_ word: Word) -> String {
|
||||
if word.isEmpty { return "1" }
|
||||
return String(word.map { printSymbol($0) })
|
||||
}
|
||||
|
||||
func parseWord(_ str: String) -> Word {
|
||||
if str == "" || str == "1" { return [] }
|
||||
return str.map { parseSymbol($0) }
|
||||
}
|
||||
|
||||
struct Rule: Hashable {
|
||||
var lhs: Word
|
||||
var rhs: Word
|
||||
}
|
||||
|
||||
extension Rule: ExpressibleByStringLiteral, CustomStringConvertible {
|
||||
init(_ str: String) {
|
||||
let pair = str.split(separator: "=")
|
||||
precondition(pair.count == 2)
|
||||
self.lhs = parseWord(String(pair[0]))
|
||||
self.rhs = parseWord(String(pair[1]))
|
||||
}
|
||||
|
||||
init(stringLiteral: String) {
|
||||
self.init(stringLiteral)
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "\(printWord(lhs))=\(printWord(rhs))"
|
||||
}
|
||||
}
|
||||
|
||||
struct Presentation: Hashable {
|
||||
var rules: [Rule]
|
||||
|
||||
var alphabet: Int {
|
||||
var result: Int = 0
|
||||
for rule in rules {
|
||||
for s in rule.lhs { result = max(result, Int(s)) }
|
||||
for s in rule.rhs { result = max(result, Int(s)) }
|
||||
}
|
||||
return result + 1
|
||||
}
|
||||
|
||||
var longestRule: Int {
|
||||
var result = 0
|
||||
for rule in rules {
|
||||
result = max(result, rule.lhs.count)
|
||||
result = max(result, rule.rhs.count)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension Presentation: ExpressibleByStringLiteral, CustomStringConvertible {
|
||||
init(_ str: String) {
|
||||
self.rules = str.split(separator: ",").map { Rule(String($0)) }
|
||||
}
|
||||
|
||||
init(stringLiteral: String) {
|
||||
self.init(stringLiteral)
|
||||
}
|
||||
|
||||
var description: String {
|
||||
if rules.isEmpty { return "," }
|
||||
return rules.map { $0.description }.joined(separator: ",")
|
||||
}
|
||||
}
|
||||
|
||||
typealias Permutation = [Int]
|
||||
|
||||
func identityPermutation(_ n: Int) -> Permutation {
|
||||
return Permutation(0 ..< n)
|
||||
}
|
||||
|
||||
func inversePermutation(_ perm: Permutation) -> Permutation {
|
||||
var result = Permutation(repeating: 0, count: perm.count)
|
||||
for (i, j) in perm.enumerated() {
|
||||
result[j] = i
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// TAOCP 4A 7.2.1.2 Algorithm L
|
||||
func nextPermutation(_ perm: inout Permutation) -> Bool {
|
||||
var j = perm.count - 2
|
||||
while j >= 0 && perm[j] >= perm[j + 1] {
|
||||
j -= 1
|
||||
}
|
||||
if j < 0 { return true }
|
||||
|
||||
var l = perm.count - 1
|
||||
while perm[j] >= perm[l] { l -= 1 }
|
||||
perm.swapAt(l, j)
|
||||
|
||||
l = perm.count - 1
|
||||
var k = j + 1
|
||||
while k < l {
|
||||
perm.swapAt(k, l)
|
||||
k += 1
|
||||
l -= 1
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func allPermutations(_ alphabet: Int) -> [Permutation] {
|
||||
var perm = identityPermutation(alphabet)
|
||||
var perms: [Permutation] = []
|
||||
repeat {
|
||||
perms.append(perm)
|
||||
} while !nextPermutation(&perm)
|
||||
return perms
|
||||
}
|
||||
|
||||
extension Word {
|
||||
func permuted(_ perm: Permutation) -> Word {
|
||||
return map { Symbol(perm[Int($0)]) }
|
||||
}
|
||||
}
|
||||
|
||||
extension Rule {
|
||||
func permuted(_ perm: Permutation) -> Rule {
|
||||
return Rule(lhs: lhs.permuted(perm), rhs: rhs.permuted(perm))
|
||||
}
|
||||
}
|
||||
|
||||
extension Presentation {
|
||||
func permuted(_ perm: Permutation) -> Presentation {
|
||||
return Presentation(rules: rules.map { $0.permuted(perm) })
|
||||
}
|
||||
}
|
||||
|
||||
enum CompareResult {
|
||||
case lessThan
|
||||
case equal
|
||||
case greaterThan
|
||||
}
|
||||
|
||||
enum Order: Hashable {
|
||||
case shortlex
|
||||
case permutation(Permutation)
|
||||
case wreath([Int], Permutation)
|
||||
|
||||
var simplified: Order {
|
||||
switch self {
|
||||
case .shortlex:
|
||||
return self
|
||||
case .permutation(let perm):
|
||||
// shortlex with identity permutation avoids some indirection
|
||||
if perm == identityPermutation(perm.count) {
|
||||
return .shortlex
|
||||
}
|
||||
return self
|
||||
case .wreath(_, _):
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
func removeGenerator(_ a: Symbol) -> Order {
|
||||
func updatePermutation(_ perm: Permutation, removing i: Int) -> Permutation {
|
||||
return Permutation(perm[0 ..< i] + perm[(i + 1)...]).map {
|
||||
return $0 > perm[i] ? $0 - 1 : $0
|
||||
}
|
||||
}
|
||||
|
||||
switch self {
|
||||
case .shortlex:
|
||||
return self
|
||||
|
||||
case .permutation(let perm):
|
||||
return .permutation(updatePermutation(perm, removing: Int(a)))
|
||||
|
||||
case .wreath(let degrees, let perm):
|
||||
var newDegrees = Array(degrees[0 ..< Int(a)] + degrees[(Int(a) + 1)...])
|
||||
let oldDegree = degrees[Int(a)]
|
||||
if newDegrees.firstIndex(of: oldDegree) == nil {
|
||||
newDegrees = newDegrees.map { $0 > oldDegree ? $0 - 1 : $0 }
|
||||
}
|
||||
let newPerm = updatePermutation(perm, removing: Int(a))
|
||||
if newDegrees.max()! == 0 { return .permutation(newPerm) }
|
||||
return .wreath(newDegrees, newPerm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shortlex(_ lhs: Word, _ lhsFrom: Int, _ lhsTo: Int,
|
||||
_ rhs: Word, _ rhsFrom: Int, _ rhsTo: Int,
|
||||
perm: Permutation) -> CompareResult {
|
||||
let lhsCount = (lhsTo - lhsFrom)
|
||||
let rhsCount = (rhsTo - rhsFrom)
|
||||
if lhsCount != rhsCount {
|
||||
return lhsCount < rhsCount ? .lessThan : .greaterThan
|
||||
}
|
||||
|
||||
for i in 0 ..< lhsCount {
|
||||
let x = lhs[lhsFrom + i]
|
||||
let y = rhs[rhsFrom + i]
|
||||
if x != y {
|
||||
return perm[Int(x)] < perm[Int(y)] ? .lessThan : .greaterThan
|
||||
}
|
||||
}
|
||||
|
||||
return .equal
|
||||
}
|
||||
|
||||
// The "wreath product" or "recursive path" order:
|
||||
//
|
||||
// Sims, C. C. (1994). Computation with Finitely Presented Groups.
|
||||
// Cambridge: Cambridge University Press.
|
||||
//
|
||||
func wreath(_ lhs: Word, _ lhsFrom: Int, _ lhsTo: Int,
|
||||
_ rhs: Word, _ rhsFrom: Int, _ rhsTo: Int,
|
||||
degrees: [Int], perm: Permutation) -> CompareResult {
|
||||
var i = lhsFrom
|
||||
var j = rhsFrom
|
||||
|
||||
while true {
|
||||
if i == lhsTo {
|
||||
if j == rhsTo { return .equal }
|
||||
return .lessThan
|
||||
} else if j == rhsTo {
|
||||
return .greaterThan
|
||||
}
|
||||
|
||||
if lhs[i] != rhs[j] { break }
|
||||
i += 1
|
||||
j += 1
|
||||
}
|
||||
|
||||
func maxDegree(_ word: Word, _ from: Int, _ to: Int)
|
||||
-> (degree: Int, count: Int, symbol: Symbol?) {
|
||||
var degree = -1, count = 0
|
||||
var symbol: Symbol? = nil
|
||||
|
||||
for s in word[from ..< to] {
|
||||
if degrees[Int(s)] > degree {
|
||||
degree = degrees[Int(s)]
|
||||
count = 1
|
||||
symbol = s
|
||||
} else if degrees[Int(s)] == degree {
|
||||
count += 1
|
||||
if symbol != s { symbol = nil }
|
||||
}
|
||||
}
|
||||
|
||||
return (degree, count, symbol)
|
||||
}
|
||||
|
||||
let (lhsMaxDegree, lhsCount, lhsHeadSymbol) = maxDegree(lhs, i, lhsTo)
|
||||
let (rhsMaxDegree, rhsCount, rhsHeadSymbol) = maxDegree(rhs, j, rhsTo)
|
||||
if lhsMaxDegree < rhsMaxDegree {
|
||||
return .lessThan
|
||||
} else if lhsMaxDegree > rhsMaxDegree {
|
||||
return .greaterThan
|
||||
} else if lhsCount < rhsCount {
|
||||
return .lessThan
|
||||
} else if lhsCount > rhsCount {
|
||||
return .greaterThan
|
||||
}
|
||||
|
||||
if lhsHeadSymbol != nil && rhsHeadSymbol != nil {
|
||||
if lhsHeadSymbol != rhsHeadSymbol {
|
||||
return perm[Int(lhsHeadSymbol!)] < perm[Int(rhsHeadSymbol!)]
|
||||
? .lessThan : .greaterThan
|
||||
}
|
||||
} else {
|
||||
if lhsMaxDegree == 0 {
|
||||
return shortlex(lhs, i, lhsTo, rhs, j, rhsTo, perm: perm)
|
||||
} else {
|
||||
let lhsHeadWord = lhs[i ..< lhsTo].filter { degrees[Int($0)] == lhsMaxDegree }
|
||||
let rhsHeadWord = rhs[j ..< rhsTo].filter { degrees[Int($0)] == rhsMaxDegree }
|
||||
|
||||
let result = shortlex(lhsHeadWord, 0, lhsHeadWord.count,
|
||||
rhsHeadWord, 0, rhsHeadWord.count,
|
||||
perm: perm)
|
||||
if result != .equal { return result }
|
||||
}
|
||||
}
|
||||
|
||||
if lhsMaxDegree == 0 { return .equal }
|
||||
|
||||
var ii = i, jj = j
|
||||
while i < lhsTo {
|
||||
while i < lhsTo && degrees[Int(lhs[i])] != lhsMaxDegree { i += 1 }
|
||||
while j < rhsTo && degrees[Int(rhs[j])] != rhsMaxDegree { j += 1 }
|
||||
|
||||
let result = wreath(lhs, ii, i, rhs, jj, j, degrees: degrees, perm: perm)
|
||||
if result != .equal { return result }
|
||||
|
||||
i += 1; ii = i
|
||||
j += 1; jj = j
|
||||
}
|
||||
|
||||
precondition(j == rhsTo)
|
||||
return .equal
|
||||
}
|
||||
|
||||
func compare(_ lhs: Word, _ rhs: Word, order: Order) -> CompareResult {
|
||||
switch order {
|
||||
case .shortlex:
|
||||
if lhs.count != rhs.count {
|
||||
return lhs.count < rhs.count ? .lessThan : .greaterThan
|
||||
}
|
||||
|
||||
for i in lhs.indices {
|
||||
let x = lhs[i]
|
||||
let y = rhs[i]
|
||||
if x != y {
|
||||
return x < y ? .lessThan : .greaterThan
|
||||
}
|
||||
}
|
||||
|
||||
return .equal
|
||||
|
||||
case .permutation(let perm):
|
||||
return shortlex(lhs, 0, lhs.count, rhs, 0, rhs.count, perm: perm)
|
||||
|
||||
case .wreath(let degrees, let perm):
|
||||
return wreath(lhs, 0, lhs.count, rhs, 0, rhs.count,
|
||||
degrees: degrees, perm: perm)
|
||||
}
|
||||
}
|
||||
|
||||
func compare(_ lhs: Rule, _ rhs: Rule, order: Order) -> CompareResult {
|
||||
let result = compare(lhs.lhs, rhs.lhs, order: order)
|
||||
if result != .equal {
|
||||
return result
|
||||
}
|
||||
|
||||
return compare(lhs.rhs, rhs.rhs, order: order)
|
||||
}
|
||||
|
||||
extension Rule {
|
||||
func oriented(order: Order) -> Rule? {
|
||||
switch compare(lhs, rhs, order: order) {
|
||||
case .equal:
|
||||
return nil
|
||||
case .lessThan:
|
||||
return Rule(lhs: rhs, rhs: lhs)
|
||||
case .greaterThan:
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Presentation {
|
||||
func sorted(order: Order) -> Presentation {
|
||||
let sortedRules =
|
||||
rules.map { $0.oriented(order: order)! }
|
||||
.sorted { compare($0, $1, order: order) == .lessThan }
|
||||
return Presentation(rules: sortedRules)
|
||||
}
|
||||
}
|
||||
24
benchmark/multi-source/Monoids/README.md
Normal file
24
benchmark/multi-source/Monoids/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Monoids Benchmark
|
||||
|
||||
This benchmark solves the "word problem" in a bunch of monoids simultaneously, using Swift concurrency (or really, just `Task`). It exercises the standard library data structures heavily. You can also run "sh compile.sh" inside the source directory to build a standalone binary separately from the benchmark harness. The standalone binary prints results to standard output.
|
||||
|
||||
More specifically, this program enumerates two-generator two-relation monoid presentations up to length 10, and then applies the Knuth-Bendix algorithm to each one:
|
||||
|
||||
<a,b|u=v,w=x> where |u| + |v| + |w| + |x| <= 10
|
||||
|
||||
This takes a few seconds to finish and solves all but three instances. The three it cannot solve are:
|
||||
|
||||
<a,b|aaa=a,abba=bb>
|
||||
<a,b|bab=aaa,bbbb=1>
|
||||
<a,b|aaaa=1,abbba=b>
|
||||
|
||||
In addition to Knuth-Bendix completion, there are some other interesting algorithms here as well:
|
||||
- Shortlex order with arbitrary permutation of alphabet
|
||||
- Wreath product order (also known as recursive path order) with arbitrary degree mapping
|
||||
- Enumerating all words, permutations, monoid presentations
|
||||
- Inverse of a permutation
|
||||
- Computing number of irreducible words in complete presentation (= cardinality of presented monoid) with finite state automata
|
||||
|
||||
The Knuth-Bendix implementation is pretty fast. It uses a trie to speed up reduction and finding overlaps.
|
||||
|
||||
The main "entry point" is `func run()` in Monoids.swift.
|
||||
293
benchmark/multi-source/Monoids/RewritingSystem.swift
Normal file
293
benchmark/multi-source/Monoids/RewritingSystem.swift
Normal file
@@ -0,0 +1,293 @@
|
||||
/// This file implements Knuth-Bendix completion and the normal form algorithm.
|
||||
|
||||
enum RewritingError: Error {
|
||||
case tooManyRounds
|
||||
case tooManyRules
|
||||
case tooManyNodes
|
||||
case ruleTooLong
|
||||
case tooManySteps
|
||||
case reducedWordTooLong
|
||||
}
|
||||
|
||||
let debug = false
|
||||
|
||||
func log(_ str: @autoclosure () -> String) {
|
||||
if debug {
|
||||
print(str())
|
||||
}
|
||||
}
|
||||
|
||||
struct RewritingSystem {
|
||||
var state: State = .initial
|
||||
|
||||
enum State {
|
||||
case initial
|
||||
case complete
|
||||
case failed
|
||||
}
|
||||
|
||||
var alphabet: Int
|
||||
var rules: [Rule] = []
|
||||
var trie: Trie
|
||||
|
||||
// Limits for completion
|
||||
struct Limits: Hashable {
|
||||
var maxRounds = 100
|
||||
var maxRules = 180
|
||||
var maxLength = 100
|
||||
var maxReductionLength = 100
|
||||
var maxReductionSteps = 1 << 24
|
||||
}
|
||||
|
||||
var limits: Limits
|
||||
|
||||
var checkedRulesUpTo = 0 // Completion progress
|
||||
var reducedRules: [UInt32] = [] // Bitmap of reduced rules
|
||||
|
||||
typealias CriticalPair = (i: Int, from: Int, j: Int)
|
||||
var criticalPairs: [CriticalPair] = [] // Temporary array for completion
|
||||
|
||||
var stats = Stats()
|
||||
|
||||
struct Stats {
|
||||
var numRounds = 0
|
||||
var numRulesRemaining = 0 // Number of rules that were not reduced away
|
||||
var numReductionSteps = 0
|
||||
}
|
||||
|
||||
init(alphabet: Int, limits: Limits) {
|
||||
self.alphabet = alphabet
|
||||
self.trie = Trie(alphabet: self.alphabet)
|
||||
self.limits = limits
|
||||
|
||||
criticalPairs.reserveCapacity(128)
|
||||
}
|
||||
|
||||
mutating func addRules(_ rules: [Rule], order: Order)
|
||||
throws(RewritingError) {
|
||||
for var rule in rules {
|
||||
_ = try addRule(&rule, order: order)
|
||||
}
|
||||
}
|
||||
|
||||
func reduceOne(_ word: Word, excluding: Int? = nil) -> (Int, Int)? {
|
||||
var from = 0
|
||||
|
||||
while from < word.count {
|
||||
if let n = trie.lookup(word, from) {
|
||||
if n != excluding { return (from, n) }
|
||||
}
|
||||
|
||||
from += 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func reduce(_ word: inout Word, stats: inout Stats) throws(RewritingError) {
|
||||
var count = 0
|
||||
|
||||
repeat {
|
||||
guard let (from, n) = reduceOne(word) else { return }
|
||||
|
||||
let index = word.startIndex + from
|
||||
word.replaceSubrange(index ..< index + rules[n].lhs.count,
|
||||
with: rules[n].rhs)
|
||||
stats.numReductionSteps += (from + rules[n].lhs.count)
|
||||
if stats.numReductionSteps > limits.maxReductionSteps { throw .tooManySteps }
|
||||
|
||||
if count > limits.maxReductionLength { throw .tooManySteps }
|
||||
|
||||
// FIXME: Load bearing
|
||||
if word.count > limits.maxLength { throw .reducedWordTooLong }
|
||||
|
||||
count += 1
|
||||
} while true
|
||||
}
|
||||
|
||||
mutating func addOrientedRule(_ rule: Rule) throws(RewritingError) {
|
||||
let longestSide = max(rule.lhs.count, rule.rhs.count)
|
||||
if longestSide > limits.maxLength { throw .ruleTooLong }
|
||||
|
||||
if stats.numRulesRemaining == limits.maxRules { throw .tooManyRules }
|
||||
|
||||
log("Adding rule \(rules.count) = \(rule)")
|
||||
try trie.insert(rule.lhs, rules.count)
|
||||
|
||||
rules.append(rule)
|
||||
stats.numRulesRemaining += 1
|
||||
}
|
||||
|
||||
mutating func addRule(_ rule: inout Rule, order: Order)
|
||||
throws(RewritingError) -> Bool {
|
||||
try reduce(&rule.lhs, stats: &stats)
|
||||
try reduce(&rule.rhs, stats: &stats)
|
||||
|
||||
switch compare(rule.lhs, rule.rhs, order: order) {
|
||||
case .equal:
|
||||
return false
|
||||
|
||||
case .lessThan:
|
||||
swap(&rule.lhs, &rule.rhs)
|
||||
fallthrough
|
||||
|
||||
case .greaterThan:
|
||||
try addOrientedRule(rule)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
mutating func resolveOverlap(i: Int, from: Int, j: Int, order: Order)
|
||||
throws(RewritingError) -> Bool {
|
||||
let lhs = rules[i]
|
||||
let rhs = rules[j]
|
||||
|
||||
log("Critical pair: \(i) vs \(j) at \(from)")
|
||||
log("\(printWord(rules[i].lhs))")
|
||||
log("\(String(repeating: " ", count: from))\(printWord(rules[j].lhs))")
|
||||
|
||||
var rule = Rule(lhs: [], rhs: [])
|
||||
|
||||
let end = lhs.lhs.count
|
||||
if from + rhs.lhs.count < end {
|
||||
rule.lhs = lhs.rhs
|
||||
|
||||
rule.rhs.reserveCapacity(lhs.lhs.count - rhs.lhs.count + rhs.rhs.count)
|
||||
|
||||
rule.rhs.append(contentsOf: lhs.lhs[0 ..< from])
|
||||
rule.rhs.append(contentsOf: rhs.rhs)
|
||||
rule.rhs.append(contentsOf: lhs.lhs[(from + rhs.lhs.count)...])
|
||||
} else {
|
||||
rule.lhs.reserveCapacity(lhs.rhs.count + rhs.lhs.count - lhs.lhs.count + from)
|
||||
rule.lhs.append(contentsOf: lhs.rhs)
|
||||
rule.lhs.append(contentsOf: rhs.lhs[(lhs.lhs.count - from)...])
|
||||
|
||||
rule.rhs.reserveCapacity(from + rhs.rhs.count)
|
||||
rule.rhs.append(contentsOf: lhs.lhs[..<from])
|
||||
rule.rhs.append(contentsOf: rhs.rhs)
|
||||
}
|
||||
|
||||
return try addRule(&rule, order: order)
|
||||
}
|
||||
|
||||
mutating func processRule(_ i: Int) {
|
||||
if isReduced(i) { return }
|
||||
|
||||
let lhs = rules[i]
|
||||
var from = 0
|
||||
while from < lhs.lhs.count {
|
||||
trie.visitOverlaps(lhs.lhs, from) { j in
|
||||
precondition(!isReduced(j))
|
||||
|
||||
if i < checkedRulesUpTo && j < checkedRulesUpTo { return }
|
||||
|
||||
if from == 0 {
|
||||
if i == j { return }
|
||||
if rules[j].lhs.count > lhs.lhs.count { return }
|
||||
}
|
||||
|
||||
criticalPairs.append((i: i, from: from, j: j))
|
||||
}
|
||||
|
||||
from += 1
|
||||
}
|
||||
}
|
||||
|
||||
mutating func completeOne(order: Order) throws(RewritingError) -> Bool {
|
||||
precondition(state == .initial)
|
||||
|
||||
precondition(criticalPairs.isEmpty)
|
||||
|
||||
for i in rules.indices {
|
||||
processRule(i)
|
||||
}
|
||||
|
||||
checkedRulesUpTo = rules.count
|
||||
stats.numRounds += 1
|
||||
|
||||
reduceLeft()
|
||||
|
||||
var confluent = true
|
||||
|
||||
do {
|
||||
log("Resolving critical pairs...")
|
||||
for (i, from, j) in criticalPairs {
|
||||
if try resolveOverlap(i: i, from: from, j: j, order: order) {
|
||||
confluent = false
|
||||
}
|
||||
}
|
||||
criticalPairs.removeAll(keepingCapacity: true)
|
||||
log("All critical pairs resolved")
|
||||
|
||||
try reduceRight()
|
||||
} catch let e {
|
||||
state = .failed
|
||||
throw e
|
||||
}
|
||||
|
||||
if confluent {
|
||||
state = .complete
|
||||
return true
|
||||
}
|
||||
|
||||
if stats.numRounds > limits.maxRounds {
|
||||
state = .failed
|
||||
throw .tooManyRounds
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
mutating func complete(order: Order) throws(RewritingError) {
|
||||
while try !completeOne(order: order) {}
|
||||
}
|
||||
|
||||
func isReduced(_ rule: Int) -> Bool {
|
||||
let i = (rule >> 5)
|
||||
let j = (rule & 31)
|
||||
if i >= reducedRules.count { return false }
|
||||
return (reducedRules[i] & (1 << j)) != 0
|
||||
}
|
||||
|
||||
mutating func setReduced(_ rule: Int) {
|
||||
let i = (rule >> 5)
|
||||
let j = (rule & 31)
|
||||
while i >= reducedRules.count { reducedRules.append(0) }
|
||||
reducedRules[i] |= (1 << j)
|
||||
}
|
||||
|
||||
mutating func reduceLeft() {
|
||||
if rules.isEmpty { return }
|
||||
log("Reducing left-hand sides...")
|
||||
for (n, rule) in rules.enumerated() {
|
||||
if !isReduced(n) && reduceOne(rule.lhs, excluding: n) != nil {
|
||||
log("Reduced \(n) = \(rule)")
|
||||
setReduced(n)
|
||||
trie.remove(rule.lhs, n)
|
||||
stats.numRulesRemaining -= 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
precondition(stats.numRulesRemaining > 0)
|
||||
}
|
||||
|
||||
mutating func reduceRight() throws(RewritingError) {
|
||||
for n in rules.indices {
|
||||
if !isReduced(n) {
|
||||
try reduce(&rules[n].rhs, stats: &stats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a complete presentation once the rewriting system is complete.
|
||||
var presentation: Presentation {
|
||||
var result: [Rule] = []
|
||||
for (n, rule) in rules.enumerated() {
|
||||
if !isReduced(n) {
|
||||
result.append(rule)
|
||||
}
|
||||
}
|
||||
return Presentation(rules: result)
|
||||
}
|
||||
}
|
||||
283
benchmark/multi-source/Monoids/Solver.swift
Normal file
283
benchmark/multi-source/Monoids/Solver.swift
Normal file
@@ -0,0 +1,283 @@
|
||||
/// This file implements the driver loop which attempts Knuth-Bendix completion
|
||||
/// with various strategies on all instances in parallel.
|
||||
|
||||
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]
|
||||
|
||||
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 presentation:\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 frequency in [0, 1] {
|
||||
for factorLength in 2 ..< maxFactors.count {
|
||||
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)\t\(instance)\thard")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 2 is a magic number
|
||||
let words = p.collectFactors(order: order, upToLength: p.longestRule + 2)
|
||||
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 }
|
||||
|
||||
let solved = 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] {
|
||||
if oldStrategyIndex < strategyIndex {
|
||||
betterStrategy = false
|
||||
}
|
||||
}
|
||||
|
||||
if betterStrategy {
|
||||
solved[instance] = (strategyIndex, solution)
|
||||
}
|
||||
}
|
||||
|
||||
retireStrategies()
|
||||
}
|
||||
|
||||
func retireStrategies() {
|
||||
var retired: [Int: [Int]] = [:]
|
||||
let pendingInOrder = pending.sorted { $0.key < $1.key }
|
||||
for (strategyIndex, numTasks) in pendingInOrder {
|
||||
precondition(strategyIndex <= dispatcher.currentStrategy)
|
||||
if dispatcher.currentStrategy == strategyIndex { break }
|
||||
if numTasks > 0 { break }
|
||||
|
||||
pending[strategyIndex] = nil
|
||||
retired[strategyIndex] = []
|
||||
}
|
||||
|
||||
if retired.isEmpty { return }
|
||||
|
||||
// If we are done dispatching a strategy, look at all instances solved
|
||||
// by that strategy and print them out.
|
||||
for n in subset {
|
||||
if let (strategyIndex, solution) = solved[n] {
|
||||
if retired[strategyIndex] != nil {
|
||||
retired[strategyIndex]!.append(n)
|
||||
|
||||
// Print the instance and solution.
|
||||
var str = "\(n)\t\(instances[n])\t"
|
||||
|
||||
if let cardinality = solution.cardinality {
|
||||
str += "finite:\(cardinality)"
|
||||
} else {
|
||||
str += "infinite"
|
||||
}
|
||||
str += "\tfcrs:\(solution.presentation)"
|
||||
|
||||
// Print the extra generators that were added, if any.
|
||||
if !solution.extra.isEmpty {
|
||||
str += "\t\(Presentation(rules: solution.extra))"
|
||||
}
|
||||
|
||||
if output {
|
||||
print(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We run 32 tasks at a time.
|
||||
for _ in 0 ..< 32 {
|
||||
startTask()
|
||||
}
|
||||
|
||||
for await (instance, strategyIndex, solution) in group {
|
||||
startTask()
|
||||
completeTask(instance, strategyIndex, solution)
|
||||
}
|
||||
|
||||
return solved
|
||||
}
|
||||
|
||||
subset = subset.filter { solved[$0] == nil }
|
||||
}
|
||||
}
|
||||
13
benchmark/multi-source/Monoids/Standalone.swift
Normal file
13
benchmark/multi-source/Monoids/Standalone.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
// Only generate main entry point if we're not being built as part of the
|
||||
// benchmark harness. In this case you get a binary that runs the same
|
||||
// workload, except it also prints results to standard output.
|
||||
|
||||
#if !canImport(TestsUtils)
|
||||
|
||||
@main struct Main {
|
||||
static func main() async {
|
||||
await run(output: true)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
117
benchmark/multi-source/Monoids/Strategy.swift
Normal file
117
benchmark/multi-source/Monoids/Strategy.swift
Normal file
@@ -0,0 +1,117 @@
|
||||
/// Generate a bunch of reduction orders.
|
||||
func getExhaustiveOrderMix(_ alphabet: Int, _ extraAlphabet: Int) -> [Order] {
|
||||
var result: [Order] = []
|
||||
|
||||
let permutations = allPermutations(alphabet + extraAlphabet)
|
||||
|
||||
for perm in permutations {
|
||||
result.append(.permutation(perm))
|
||||
}
|
||||
|
||||
for perm in permutations {
|
||||
let degrees = perm.map { Int($0) }
|
||||
result.append(.wreath(degrees, perm))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/// Parameters for completion.
|
||||
struct Strategy: Sendable, Hashable {
|
||||
var extra: [Rule] = []
|
||||
var factorLength: Int? = nil
|
||||
var frequency: Int = 0
|
||||
var order: Order = .shortlex
|
||||
|
||||
var limits = RewritingSystem.Limits()
|
||||
|
||||
func withOrder(_ order: Order) -> Self {
|
||||
var result = self
|
||||
result.order = order
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension Presentation {
|
||||
func collectFactors(order: Order, upToLength: Int) -> [Word] {
|
||||
let strategy = Strategy()
|
||||
var rws = RewritingSystem(alphabet: alphabet,
|
||||
limits: strategy.limits)
|
||||
|
||||
do {
|
||||
try rws.addRules(rules, order: strategy.order)
|
||||
|
||||
for _ in [0, 1] {
|
||||
if try rws.completeOne(order: strategy.order) { break }
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return rws.collectFactors(upToLength: upToLength, order: strategy.order)
|
||||
}
|
||||
}
|
||||
|
||||
extension Word {
|
||||
func collectFactors(_ table: inout [Word: Int], length: Int) {
|
||||
precondition(length >= 2)
|
||||
|
||||
if length > count { return }
|
||||
|
||||
for i in 0 ... count - length {
|
||||
table[Word(self[i ..< i + length]), default: 0] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension RewritingSystem {
|
||||
func collectFactors(upToLength: Int, order: Order) -> [Word] {
|
||||
var table: [Word: Int] = [:]
|
||||
for n in rules.indices {
|
||||
if isReduced(n) { continue }
|
||||
let lhs = rules[n].lhs
|
||||
for length in 2 ... upToLength {
|
||||
lhs.collectFactors(&table, length: length)
|
||||
}
|
||||
}
|
||||
|
||||
return table.sorted {
|
||||
$0.1 > $1.1 || ($0.1 == $1.1 &&
|
||||
compare($0.0, $1.0, order: order) == .greaterThan)
|
||||
}.map { $0.key }
|
||||
}
|
||||
}
|
||||
|
||||
struct Solution {
|
||||
/// Total number of generators.
|
||||
let alphabet: Int
|
||||
|
||||
/// Extra generators added by morphocompletion.
|
||||
let extra: [Rule]
|
||||
|
||||
/// The cardinality of the presented monoid.
|
||||
let cardinality: Int?
|
||||
|
||||
/// A complete presentation.
|
||||
let presentation: Presentation
|
||||
}
|
||||
|
||||
extension RewritingSystem {
|
||||
func formSolution(_ strategy: Strategy) -> Solution {
|
||||
let p = presentation.sorted(order: strategy.order)
|
||||
let cardinality = p.cardinality(alphabet: alphabet)
|
||||
return Solution(alphabet: alphabet, extra: strategy.extra,
|
||||
cardinality: cardinality, presentation: p)
|
||||
}
|
||||
}
|
||||
|
||||
extension Presentation {
|
||||
func complete(_ strategy: Strategy) throws(RewritingError) -> Solution {
|
||||
let alphabet = 2 + strategy.extra.count
|
||||
var rws = RewritingSystem(alphabet: alphabet,
|
||||
limits: strategy.limits)
|
||||
try rws.addRules(rules, order: strategy.order)
|
||||
try rws.addRules(strategy.extra, order: strategy.order)
|
||||
|
||||
try rws.complete(order: strategy.order)
|
||||
return rws.formSolution(strategy)
|
||||
}
|
||||
}
|
||||
136
benchmark/multi-source/Monoids/Trie.swift
Normal file
136
benchmark/multi-source/Monoids/Trie.swift
Normal file
@@ -0,0 +1,136 @@
|
||||
struct Trie {
|
||||
typealias Node = Int16
|
||||
|
||||
var values: [Node] = []
|
||||
var children: [Node] = []
|
||||
var freeList: [Node] = []
|
||||
|
||||
let emptyNode: [Node]
|
||||
|
||||
init(alphabet: Int) {
|
||||
self.emptyNode = Array(repeating: -1, count: alphabet)
|
||||
_ = try! createNode() // The root node
|
||||
}
|
||||
|
||||
mutating func createNode() throws(RewritingError) -> Node {
|
||||
if !freeList.isEmpty {
|
||||
let result = Int(freeList.removeLast())
|
||||
values.replaceSubrange(result ..< result + emptyNode.count, with: emptyNode)
|
||||
children.replaceSubrange(result ..< result + emptyNode.count, with: emptyNode)
|
||||
return Node(result)
|
||||
}
|
||||
|
||||
let result = values.count
|
||||
if result + emptyNode.count >= 32000 {
|
||||
throw RewritingError.tooManyNodes
|
||||
}
|
||||
values.append(contentsOf: emptyNode)
|
||||
children.append(contentsOf: emptyNode)
|
||||
precondition(values.count == children.count)
|
||||
precondition(values.count % emptyNode.count == 0)
|
||||
return Node(result)
|
||||
}
|
||||
|
||||
mutating func reclaimNode(_ node: Node) {
|
||||
freeList.append(node)
|
||||
}
|
||||
|
||||
mutating func insert(_ key: Word, _ value: Int) throws(RewritingError) {
|
||||
var node = 0
|
||||
for i in 0 ..< key.count - 1 {
|
||||
let s = key[i]
|
||||
if children[node + Int(s)] == -1 {
|
||||
children[node + Int(s)] = try createNode()
|
||||
}
|
||||
node = Int(children[node + Int(s)])
|
||||
}
|
||||
values[node + Int(key.last!)] = Node(value)
|
||||
}
|
||||
|
||||
func lookup(_ key: Word, _ i: Int) -> Int? {
|
||||
var node = 0
|
||||
for s in key[i ..< key.count] {
|
||||
let n = Int(values[node + Int(s)])
|
||||
if n != -1 { return n }
|
||||
node = Int(children[node + Int(s)])
|
||||
if node == -1 { return nil }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Visits all keys that are equal to a prefix of key[i ...], as well as
|
||||
// all keys whose prefix is equal to key[i ...].
|
||||
func visitOverlaps(_ key: Word, _ from: Int, callback: (Int) -> ()) {
|
||||
var node = 0
|
||||
for s in key[from...] {
|
||||
let n = Int(values[node + Int(s)])
|
||||
if n != -1 { callback(n) }
|
||||
node = Int(children[node + Int(s)])
|
||||
if node == -1 { return }
|
||||
}
|
||||
|
||||
if node == 0 { return }
|
||||
|
||||
var stack: [Int] = [node]
|
||||
|
||||
repeat {
|
||||
let node = stack.removeLast()
|
||||
|
||||
for s in (0 ..< emptyNode.count) {
|
||||
let n = Int(values[node + s])
|
||||
if n != -1 { callback(n) }
|
||||
let child = Int(children[node + s])
|
||||
if child != -1 { stack.append(child) }
|
||||
}
|
||||
} while !stack.isEmpty
|
||||
}
|
||||
|
||||
func isEmptyNode(_ node: Int) -> Bool {
|
||||
for i in 0 ..< emptyNode.count {
|
||||
if values[node + i] != -1 ||
|
||||
children[node + i] != -1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
mutating func remove(_ key: Word, _ value: Int) {
|
||||
var node = 0
|
||||
var stack: [Int] = [] // path to current node from root
|
||||
|
||||
for i in 0 ..< key.count - 1 {
|
||||
let s = key[i]
|
||||
precondition(children[node + Int(s)] != -1)
|
||||
|
||||
stack.append(node)
|
||||
node = Int(children[node + Int(s)])
|
||||
}
|
||||
|
||||
let j = node + Int(key.last!)
|
||||
precondition(values[j] == value)
|
||||
values[j] = -1
|
||||
|
||||
// Remove any newly-empty nodes, up to the root.
|
||||
repeat {
|
||||
if !isEmptyNode(node) { return }
|
||||
|
||||
reclaimNode(Node(node))
|
||||
|
||||
let parent = stack.removeLast()
|
||||
|
||||
var sawThis = false
|
||||
for i in 0 ..< emptyNode.count {
|
||||
if Int(children[parent + i]) == node {
|
||||
children[parent + i] = -1
|
||||
sawThis = true
|
||||
break
|
||||
}
|
||||
}
|
||||
precondition(sawThis)
|
||||
|
||||
node = parent
|
||||
} while !stack.isEmpty
|
||||
}
|
||||
}
|
||||
1
benchmark/multi-source/Monoids/compile.sh
Normal file
1
benchmark/multi-source/Monoids/compile.sh
Normal file
@@ -0,0 +1 @@
|
||||
xcrun swiftc -O Automaton.swift Enumeration.swift Monoids.swift Presentation.swift RewritingSystem.swift Solver.swift Standalone.swift Strategy.swift Trie.swift -O -swift-version 6 -g -wmo -parse-as-library -o Monoids $@
|
||||
@@ -117,6 +117,7 @@ import LuhnAlgoLazy
|
||||
import MapReduce
|
||||
import Memset
|
||||
import MirrorTest
|
||||
import Monoids
|
||||
import MonteCarloE
|
||||
import MonteCarloPi
|
||||
import NaiveRangeReplaceableCollectionConformance
|
||||
@@ -317,6 +318,7 @@ register(LuhnAlgoLazy.benchmarks)
|
||||
register(MapReduce.benchmarks)
|
||||
register(Memset.benchmarks)
|
||||
register(MirrorTest.benchmarks)
|
||||
register(Monoids.benchmarks)
|
||||
register(MonteCarloE.benchmarks)
|
||||
register(MonteCarloPi.benchmarks)
|
||||
register(NaiveRangeReplaceableCollectionConformance.benchmarks)
|
||||
|
||||
Reference in New Issue
Block a user