Skip to content
Snippets Groups Projects
Commit 2064e78b authored by Mingchung Xia's avatar Mingchung Xia
Browse files

Added sample visualizer

parent 6e091f98
No related branches found
No related tags found
1 merge request!13HNSW Implementation with Testcases
Pipeline #114229 passed with warnings
......@@ -79,7 +79,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
......
......@@ -6,15 +6,17 @@ import PackageDescription
let package = Package(
name: "SwiftNLP",
platforms: [
.macOS(.v13),
.macOS(.v13),
],
products: [
.library(
name: "SwiftNLP",
targets: ["SwiftNLP"]),
.executable(
name: "SwiftNLPVisualizer",
targets: ["SwiftNLPVisualizer"]),
],
dependencies: [
//.package(url: "https://github.com/jbadger3/SwiftAnnoy", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/L1MeN9Yu/Elva", .upToNextMajor(from: "2.1.3")),
.package(url: "https://github.com/JadenGeller/similarity-topology", .upToNextMajor(from: "0.1.14")),
.package(url: "https://github.com/Jounce/Surge.git", .upToNextMajor(from: "2.0.0")),
......@@ -23,10 +25,10 @@ let package = Package(
.target(
name: "SwiftNLP",
dependencies: [
//"SwiftAnnoy",
.product(name: "HNSWAlgorithm", package: "similarity-topology"),
.product(name: "HNSWEphemeral", package: "similarity-topology"),
.product(name: "HNSWDurable", package: "similarity-topology"),
.product(name: "HNSWSample", package: "similarity-topology"),
.product(name: "ZSTD", package: "Elva"),
.product(name: "Surge", package: "Surge"),
],
......@@ -35,6 +37,11 @@ let package = Package(
.testTarget(
name: "SwiftNLPTests",
dependencies: ["SwiftNLP"],
resources: [.process("Resources")]),
resources: [.process("Resources")]
),
.executableTarget(
name: "SwiftNLPVisualizer",
dependencies: ["SwiftNLP"]
),
]
)
......@@ -38,12 +38,14 @@ public struct DeterministicEphemeralVectorIndex<Vector: Collection & Codable> wh
public var base: Index
public var typicalNeighborhoodSize: Int
private var rng: RandomNumberGenerator
private var vectorRNG: RandomNumberGenerator
private var graphRNG: RandomNumberGenerator
public init(typicalNeighborhoodSize: Int = 20) {
base = .init(metric: CartesianDistanceMetric<Vector>(), config: .unstableDefault(typicalNeighborhoodSize: typicalNeighborhoodSize))
self.typicalNeighborhoodSize = typicalNeighborhoodSize
self.rng = SeedableRNG(seed: 1)
self.vectorRNG = SeedableRNG(seed: 0)
self.graphRNG = SeedableRNG(seed: 1)
}
public func find(near query: Vector, limit: Int, exact: Bool = false) throws -> [Index.Neighbor] {
......@@ -57,12 +59,19 @@ public struct DeterministicEphemeralVectorIndex<Vector: Collection & Codable> wh
}
}
public mutating func generateRandom(range: ClosedRange<Double>) -> CGPoint {
CGPoint(
x: .random(in: range, using: &vectorRNG),
y: .random(in: range, using: &vectorRNG)
)
}
@discardableResult
public mutating func insert(_ vector: Vector) -> Int {
let convertedVector: [Double] = vector.map{ Double($0) }
if let metricVector = convertedVector as? CartesianDistanceMetric<Vector>.Vector {
/// base.insert will returns a key and inserts the vector into the index
let key = base.insert(metricVector, using: &rng)
let key = base.insert(metricVector, using: &graphRNG)
return key
} else {
fatalError("Unable to get metric vector")
......
import SwiftUI
import HNSWAlgorithm
import HNSWSample
// MARK: go to Product -> Scheme -> SwiftNLPVisualizer then run
// TODO: Support this for SwiftNLP data structures instead of the sample
struct GraphView: View {
let points: [(Int, CGPoint)]
let edges: [(CGPoint, CGPoint)]
var body: some View {
Canvas { context, size in
for (startPoint, endPoint) in edges {
var path = Path()
path.move(to: startPoint)
path.addLine(to: endPoint)
context.stroke(path, with: .color(.black), lineWidth: 1)
}
for (id, point) in points {
context.fill(
Circle().path(in: CGRect(x: point.x - 5, y: point.y - 5, width: 10, height: 10)),
with: .color(.blue)
)
context.draw(Text("\(id)").bold().foregroundColor(.red), in: CGRect(x: point.x, y: point.y, width: 20, height: 20))
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
extension DeterministicSampleVectorIndex {
func points(for level: Int) -> [(Int, CGPoint)] {
base.graph.keys(on: level).map { id in
(id, base.vectors[id])
}
}
func edges(for level: Int) -> [(CGPoint, CGPoint)] {
base.graph.keys(on: level).flatMap { id in
base.graph.neighborhood(on: level, around: id).map { neighbor in
return (base.vectors[id], base.vectors[neighbor])
}
}
}
}
struct VisualizerView: View {
@State var index = DeterministicSampleVectorIndex(typicalNeighborhoodSize: 6)
@State var angle: Angle = .zero
@State var updateCount = 0 // since index isn't observable!
var body: some View {
VStack {
HStack {
Button("Add Data") {
index.insertRandom(range: 0...500)
updateCount += 1
}
Slider(value: $angle.degrees, in: 0...89)
.frame(width: 100)
}
.padding()
ScrollView {
VStack {
let graph = index.base.graph
ForEach(Array(sequence(state: graph.entry?.level, next: graph.descend)), id: \.self) { level in
let _ = updateCount // to force an update
Text("Level \(String(level))")
GraphView(
points: index.points(for: level),
edges: index.edges(for: level)
)
.rotation3DEffect(angle, axis: (1, 0, 0), perspective: 0)
.frame(width: 600, height: 600, alignment: .top)
.frame(width: 600, height: 600 * cos(angle.radians))
Divider()
}
}
}
}
}
}
@main
struct HNSWVisualizerApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
VisualizerView()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.setActivationPolicy(.regular)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment