Skip to content

Commit 63d2cab

Browse files
authored
Merge pull request #83214 from slavapestov/monoids
New "Monoids" benchmark
2 parents 9376bdc + 68b044a commit 63d2cab

15 files changed

+1642
-2
lines changed

benchmark/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,24 @@ set(SWIFT_BENCH_MODULES
220220

221221
set(SWIFT_MULTISOURCE_SWIFT_BENCHES
222222
multi-source/PrimsSplit
223+
multi-source/Monoids
223224
)
224225

225226
set(PrimsSplit_sources
226227
multi-source/PrimsSplit/Prims.swift
227228
multi-source/PrimsSplit/Prims_main.swift)
228229

230+
set(Monoids_sources
231+
multi-source/Monoids/Automaton.swift
232+
multi-source/Monoids/Benchmark.swift
233+
multi-source/Monoids/Enumeration.swift
234+
multi-source/Monoids/Monoids.swift
235+
multi-source/Monoids/Presentation.swift
236+
multi-source/Monoids/RewritingSystem.swift
237+
multi-source/Monoids/Solver.swift
238+
multi-source/Monoids/Strategy.swift
239+
multi-source/Monoids/Trie.swift)
240+
229241
set(BENCH_DRIVER_LIBRARY_FLAGS)
230242
if (SWIFT_RUNTIME_ENABLE_LEAK_CHECKER)
231243
set(BENCH_DRIVER_LIBRARY_FLAGS -DSWIFT_RUNTIME_ENABLE_LEAK_CHECKER)

benchmark/Package.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,17 +178,22 @@ targets += multiSourceLibraries.map { lib in
178178
dependencies: [
179179
.target(name: "TestsUtils")
180180
],
181-
path: lib.parentSubDir)
181+
path: "\(lib.parentSubDir)/\(lib.name)")
182182
}
183183

184184
//===---
185185
// Top Level Definition
186186
//
187187

188-
let p = Package(
188+
var p = Package(
189189
name: "swiftbench",
190190
products: products,
191191
targets: targets,
192192
swiftLanguageVersions: [.v4],
193193
cxxLanguageStandard: .cxx20
194194
)
195+
196+
// Let's build for Swift 5.5-aligned runtimes.
197+
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
198+
p.platforms = [.macOS(.v12), .iOS(.v15), .watchOS(.v8), .tvOS(.v15)]
199+
#endif
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/// This file implements an algorithm to compute the number of elements in a
2+
/// monoid (or determine it is infinite), given a complete presentation.
3+
4+
/// A finite state automaton, given by a set of vertices and edges.
5+
struct Automaton {
6+
var states: [Word] = []
7+
var transitions: [(Word, Symbol, Word)] = []
8+
}
9+
10+
extension Automaton {
11+
var hasStar: Bool {
12+
for state in states {
13+
var visited = Set<Word>()
14+
15+
func rec(_ state: Word) -> Bool {
16+
for (from, _, to) in transitions {
17+
if from == state {
18+
if visited.contains(to) {
19+
return true
20+
} else {
21+
visited.insert(to)
22+
if rec(to) { return true }
23+
visited.remove(to)
24+
}
25+
}
26+
}
27+
28+
return false
29+
}
30+
31+
visited.insert(state)
32+
if rec(state) { return true }
33+
visited.remove(state)
34+
}
35+
36+
return false
37+
}
38+
39+
/// If this automaton is star-free, count the number of unique words accepted.
40+
var countWords: Int {
41+
func R(_ q: Word) -> [Word] {
42+
var result: [Word] = []
43+
44+
for (from, _, to) in transitions {
45+
if to == q {
46+
result.append(from)
47+
}
48+
}
49+
50+
return result
51+
}
52+
53+
func T(_ q: Word, _ p: Word) -> Int {
54+
var letters = Set<Symbol>()
55+
for (from, x, to) in transitions {
56+
if from == q && to == p {
57+
letters.insert(x)
58+
}
59+
}
60+
return letters.count
61+
}
62+
63+
func N(_ q: Word) -> Int {
64+
if q == [] {
65+
return 1
66+
}
67+
68+
var result = 0
69+
for p in R(q) {
70+
result += N(p) * T(p, q)
71+
}
72+
return result
73+
}
74+
75+
var result = 0
76+
77+
for q in states {
78+
result += N(q)
79+
}
80+
81+
return result
82+
}
83+
}
84+
85+
/// Constructs an automaton to recognize the complement of this regular set:
86+
///
87+
/// .*(x1|x2|...).*
88+
///
89+
/// where 'words' is [x1, x2, ...].
90+
///
91+
/// This is Lemma 2.1.3 in:
92+
///
93+
/// String Rewriting Systems, R.V. Book, F. Otto 1993. Springer New York.
94+
func buildAutomaton(_ words: [Word], _ alphabet: Int) -> Automaton {
95+
// Proper prefixes of each word.
96+
var prefixes = Set<Word>()
97+
98+
var result = Automaton()
99+
100+
func isIrreducible(_ word: Word) -> Bool {
101+
for i in 0 ..< word.count {
102+
for other in words {
103+
if i + other.count <= word.count {
104+
if Word(word[i ..< (i + other.count)]) == other {
105+
return false
106+
}
107+
}
108+
}
109+
}
110+
111+
return true
112+
}
113+
114+
prefixes.insert([])
115+
for word in words {
116+
for i in 0 ..< word.count {
117+
let prefix = Word(word[0 ..< i])
118+
prefixes.insert(prefix)
119+
}
120+
}
121+
122+
result.states = prefixes.sorted { compare($0, $1, order: .shortlex) == .lessThan }
123+
124+
for prefix in prefixes {
125+
for x in 0 ..< UInt8(alphabet) {
126+
let word = prefix + [x]
127+
128+
if prefixes.contains(word) {
129+
result.transitions.append((prefix, x, word))
130+
continue
131+
}
132+
133+
if !isIrreducible(word) {
134+
continue
135+
}
136+
137+
for i in 1 ... word.count {
138+
let suffix = Word(word[i...])
139+
140+
if prefixes.contains(suffix) {
141+
result.transitions.append((prefix, x, suffix))
142+
break
143+
}
144+
}
145+
}
146+
}
147+
148+
return result
149+
}
150+
151+
extension Presentation {
152+
/// The Irr(R) automaton.
153+
func automaton(alphabet: Int) -> Automaton {
154+
return buildAutomaton(rules.map { $0.lhs }, alphabet)
155+
}
156+
157+
/// Returns the number of irreducible words in this monoid presentation, or
158+
/// nil if this set is infinite.
159+
///
160+
/// If the presentation is complete, this is the cardinality of the
161+
/// presented monoid. Otherwise, it is an upper bound.
162+
func cardinality(alphabet: Int) -> Int? {
163+
let automaton = automaton(alphabet: alphabet)
164+
if automaton.hasStar {
165+
return nil
166+
}
167+
return automaton.countWords
168+
}
169+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import TestsUtils
2+
import Dispatch
3+
4+
public let benchmarks = [
5+
BenchmarkInfo(
6+
name: "Monoids",
7+
runFunction: run_Monoids,
8+
tags: [.algorithm])
9+
]
10+
11+
func run_Monoids(_ n: Int) {
12+
let semaphore = DispatchSemaphore(value: 0)
13+
for _ in 0 ... n {
14+
Task {
15+
await run(output: false)
16+
semaphore.signal()
17+
}
18+
semaphore.wait()
19+
}
20+
}

0 commit comments

Comments
 (0)