A Swift library for graph visualization and efficient force simulation.
## Examples
### Force Directed Graph
This is a force directed graph visualizing [the network of character co-occurence in _Les Misérables_](https://observablehq.com/@d3/force-directed-graph-component). Take a closer look at the animation:
https://github.com/swiftgraphs/Grape/assets/45376537/d80dc797-1980-4755-85b9-18ee26e2a7ff
Source code: [Miserables.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Miserables.swift).
### Force Directed Graph in visionOS
This is the same graph as the first example, rendered in `RealityView`:
https://github.com/swiftgraphs/Grape/assets/45376537/4585471e-2339-4aee-8f39-0c11fdfb6901
Source code: [ForceDirectedGraph3D/ContentView.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/ContentView.swift).
### Mermaid Visualization
Dynamical graph structure based on your input, with tap and drag gesture supports, all within 100 lines of view body.
https://github.com/swiftgraphs/Grape/assets/45376537/7c75d367-d5a8-4316-813b-288b375f513b
Source code: [MermaidVisualization.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MermaidVisualization.swift)
### Lattice Simulation
A 36x36 force directed lattice.
https://github.com/swiftgraphs/Grape/assets/45376537/5b76fddc-dd5c-4d35-bced-29c01269dd2b
Source code: [Lattice.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Lattice.swift)
### Dragging Gesture
An example rendering a ring with 60 vertices, with dragging gesture enabled.
https://github.com/swiftgraphs/Grape/assets/45376537/73213e7f-73ee-44f3-9b3e-7e58355045d2
Source code: [MyRing.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MyRing.swift)
## Installation
To use Grape in an Xcode project by adding it to your project as a package:
```
https://github.com/swiftgraphs/Grape
```
To use Grape in a [SwiftPM](https://swift.org/package-manager/) project, add this to your `Package.swift`:
``` swift
dependencies: [
.package(url: "https://github.com/swiftgraphs/Grape", from: "1.1.0")
]
```
```swift
.product(name: "Grape", package: "Grape"),
```
> [!NOTE]
> The `Grape` module relies on the [`Observation` framework](https://developer.apple.com/documentation/observation). It’s possible to backdeploy with community shims like [`swift-perception`](https://github.com/pointfreeco/swift-perception).
>
> The `Grape` module may introduce breaking API changes in minor version changes before 1.0 release.
>
> The `ForceSimulation` module is stable in terms of public API now.
## Get started
Grape ships 2 modules:
- The `Grape` module allows you to create force-directed graphs in SwiftUI Views.
- The `ForceSimulation` module is the underlying mechanism of `Grape`, and it helps you to create more complicated or customized force simulations. It also contains a `KDTree` data structure built with performance in mind, which can be useful for spatial partitioning tasks.
### The `Grape` module
For detailed usage, please refer to [documentation](https://swiftgraphs.github.io/Grape/Grape/documentation/grape). A quick example here:
```swift
import Grape
struct MyGraph: View {
// States including running status, transformation, etc.
// Gives you a handle to control the states.
@State var graphStates = ForceDirectedGraphState()
var body: some View {
ForceDirectedGraph(states: graphStates) {
// Declare nodes and links like you would do in Swift Charts.
NodeMark(id: 0).foregroundStyle(.green)
NodeMark(id: 1).foregroundStyle(.blue)
NodeMark(id: 2).foregroundStyle(.yellow)
Series(0..<2) { i in
LinkMark(from: i, to: i+1)
}
} force: {
.link()
.center()
.manyBody()
}
}
}
```
### The `ForceSimulation` module
Refer to the documentation or expand this section to find more about this module.
`ForceSimulation` module mainly contains 3 concepts, `Kinetics`, `ForceProtocol` and `Simulation`.
- `Kinetics` describes all kinetic states of your system, i.e. position, velocity, link connections, and the variable `alpha` that describes how "active" your system is.
- Forces are any types that conforms to `ForceProtocol`. This module provides most of the forces you will use in force directed graphs. And you can also create your own forces. They should be responsible for 2 tasks:
- `bindKinetics(_ kinetics: Kinetics)`: binding to a `Kinetics`. In most cases the force should keep a reference of the `Kinetics` so they know what to mutate when `apply` is called.
- `apply()`: Mutating the states of `Kinetics`. For example, a gravity force should add velocities on each node in this function.
- `Simulation` is a shell class you interact with, which enables you to create any dimensional simulation with velocity Verlet integration. It manages a `Kinetics` and a force conforming to `ForceProtocol`. Since `Simulation` only stores one force, you are responsible for compositing multiple forces into one.
- Another data structure `KDTree` is used to accelerate the force simulation with [Barnes-Hut Approximation](https://jheer.github.io/barnes-hut/).
The basic concepts of simulations and forces can be found here: [Force simulations - D3](https://d3js.org/d3-force/simulation). You can simply create simulations by using `Simulation` like this:
```swift
import simd
import ForceSimulation
// assuming you’re simulating 4 nodes
let nodeCount = 4
// Connect them
let links = [(0, 1), (1, 2), (2, 3), (3, 0)]
/// Create a 2D force composited with 4 primitive forces.
let myForce = SealedForce2D {
// Forces are namespaced under `Kinetics`
// here we only use `Kinetics>`, i.e. `Kinetics2D`
Kinetics2D.ManyBodyForce(strength: -30)
Kinetics2D.LinkForce(
stiffness: .weightedByDegree(k: { _, _ in 1.0 }),
originalLength: .constant(35)
)
Kinetics2D.CenterForce(center: .zero, strength: 1)
Kinetics2D.CollideForce(radius: .constant(3))
}
/// Create a simulation, the dimension is inferred from the force.
let mySimulation = Simulation(
nodeCount: nodeCount,
links: links.map { EdgeID(source: $0.0, target: $0.1) },
forceField: myForce
)
/// Force is ready to start! run `tick` to iterate the simulation.
for mySimulation in 0..<120 {
mySimulation.tick()
let positions = mySimulation.kinetics.position.asArray()
/// Do something with the positions.
}
```
See [Example](https://github.com/swiftgraphs/Grape/tree/main/Examples/ForceDirectedGraphExample) for more details.
## Performance
#### Simulation
Grape uses simd to calculate position and velocity. Currently it takes **~0.005** seconds to iterate 120 times over the example graph(2D). (77 vertices, 254 edges, with manybody, center, collide and link forces. Release build on a M1 Max, [tested](https://github.com/swiftgraphs/Grape/blob/main/Tests/ForceSimulationTests/MiserableGraphTest.swift) with command `swift test -c release`)
For 3D simulation, it takes **~0.008** seconds for the same graph and same configs.
> [!IMPORTANT]
> Due to heavy use of generics (some are not specialized in Debug mode), the performance in Debug build is ~100x slower than Release build.
#### KDTree
The `BufferedKDTree` from this package is **~22x** faster than `GKQuadtree` from Apple’s GameKit, according to this [test case](https://github.com/swiftgraphs/Grape/blob/main/Tests/ForceSimulationTests/GKTreeCompareTest.swift). However, please note that comparing Swift structs with NSObjects is unfair, and their behaviors are different.
## Credits
This library has been greatly influenced by the outstanding work done by [D3.js (Data-Driven Documents)](https://d3js.org).
================================================
FILE: Sources/ForceSimulation/ForceProtocol.swift
================================================
/// A protocol that represents a force.
///
/// A force takes a simulation state and modifies its node positions and velocities.
public protocol ForceProtocol {
associatedtype Vector where Vector: SimulatableVector & L2NormCalculatable
/// Takes a simulation state and modifies its node positions and velocities.
/// This is executed in each tick of the simulation.
@inlinable func apply(to kinetics: inout Kinetics)
/// Bind to a kinetic system that describes the state of all nodes in your simulation.
/// This has to be called before `apply` is called.
@inlinable mutating func bindKinetics(_ kinetics: Kinetics)
/// Deinitialize the tree and deallocate the memory.
/// Called when `Simulation` is deinitialized.
@inlinable func dispose()
}
public protocol Force2D: ForceProtocol where Vector == SIMD2 {}
public protocol Force3D: ForceProtocol where Vector == SIMD3 {}
extension Kinetics2D.LinkForce: Force2D {}
extension Kinetics2D.ManyBodyForce: Force2D {}
extension Kinetics2D.CenterForce: Force2D {}
extension Kinetics2D.CollideForce: Force2D {}
extension Kinetics2D.PositionForce: Force2D {}
extension Kinetics2D.RadialForce: Force2D {}
extension Kinetics2D.EmptyForce: Force2D {}
extension CompositedForce: Force2D where Vector == SIMD2 {}
extension Kinetics3D.LinkForce: Force3D {}
extension Kinetics3D.ManyBodyForce: Force3D {}
extension Kinetics3D.CenterForce: Force3D {}
extension Kinetics3D.CollideForce: Force3D {}
extension Kinetics3D.PositionForce: Force3D {}
extension Kinetics3D.RadialForce: Force3D {}
extension Kinetics3D.EmptyForce: Force3D {}
extension CompositedForce: Force3D where Vector == SIMD3 {}
public protocol ForceDescriptor: Equatable {
associatedtype ConcreteForce: ForceProtocol
func createForce() -> ConcreteForce
}
/// A helper force for hiding long type signatures you composed with `CompositedForce`.
/// You can easily build a force with a result builder.
public protocol ForceField: ForceProtocol
where Vector: SimulatableVector & L2NormCalculatable {
associatedtype F: ForceProtocol where F.Vector == Vector
@inlinable
@ForceBuilder
var force: F { get set }
}
extension ForceField {
// @inlinable
// public func apply() {
// self.force.apply()
// }
@inlinable
public func apply(to kinetics: inout Kinetics) {
self.force.apply(to: &kinetics)
}
@inlinable
public func dispose() {
self.force.dispose()
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
self.force.bindKinetics(kinetics)
}
}
public protocol ForceField2D: ForceField & Force2D {}
public protocol ForceField3D: ForceField & Force3D {}
================================================
FILE: Sources/ForceSimulation/ForceSimulation.docc/CreatingASimulationWithBuiltinForces.md
================================================
# Creating a Simulation with Built-in Forces
## Overview
You can simply create simulations by using Simulation like this:
```swift
import simd
import ForceSimulation
// assuming you’re simulating 4 nodes
let nodeCount = 4
// Connect them
let links = [(0, 1), (1, 2), (2, 3), (3, 0)]
/// Create a 2D force composited with 4 primitive forces.
let myForce = SealedForce2D {
// Forces are namespaced under `Kinetics`
// here we only use `Kinetics>`, i.e. `Kinetics2D`
Kinetics2D.ManyBodyForce(strength: -30)
Kinetics2D.LinkForce(
stiffness: .weightedByDegree(k: { _, _ in 1.0 }),
originalLength: .constant(35)
)
Kinetics2D.CenterForce(center: .zero, strength: 1)
Kinetics2D.CollideForce(radius: .constant(3))
}
/// Create a simulation, the dimension is inferred from the force.
let mySimulation = Simulation(
nodeCount: nodeCount,
links: links.map { EdgeID(source: $0.0, target: $0.1) },
forceField: myForce
)
/// Force is ready to start! run `tick` to iterate the simulation.
for mySimulation in 0..<120 {
mySimulation.tick()
let positions = mySimulation.kinetics.position.asArray()
/// Do something with the positions.
}
```
ForceSimulation module mainly contains 3 concepts, `Kinetics`, `ForceProtocol` and `Simulation`.
@Image(source: "SimulationDiagram.svg", alt: "A diagram showing the relationships of `Kinetics`, `ForceProtocol` and `Simulation`. A `Simulation` contains a `Kinetics` and a `ForceProtocol`.")
A diagram showing the relationships of `Kinetics`, `ForceProtocol` and `Simulation`. A `Simulation` contains a `Kinetics` and a `ForceProtocol`.
- `Kinetics` describes all kinetic states of your system, i.e. position, velocity, link connections, and the variable alpha that describes how "active" your system is. `Vector` tells simulation how you decribe a coordinate in this space, it can be `SIMD2` or `SIMD3` or any other types conforming to `SimulatableVector`.
- Forces are any types that conforms to `ForceProtocol`. This module provides most of the forces you will use in force directed graphs. And you can also create your own forces. They should be responsible for 2 tasks:
- `bindKinetics(_ kinetics: Kinetics)`: binding to a Kinetics. In most cases the force should keep a reference of the Kinetics so they know what to mutate when apply is called.
- `apply()`: Mutating the states of Kinetics. For example, a gravity force should add velocities on each node in this function.
- Simulation is a shell class you interact with, which enables you to create any dimensional simulation with velocity Verlet integration. It manages a Kinetics and a force conforming to ``ForceProtocol``. Since Simulation only stores one force, you are responsible for compositing multiple forces into one.
- Another data structure ``KDTree`` is used to accelerate the force simulation with Barnes-Hut Approximation.
In this example, we run our simulation in a 2D space (`SIMD2`). We explicitly create a ``SealedForce2D`` to make sure the force is in the same dimension as the Kinetics. The `Vector` in `Simulation` is inferred from the force we pass.
See [Examples](https://github.com/swiftgraphs/Grape/tree/main/Examples) for example Xcode projects.
================================================
FILE: Sources/ForceSimulation/ForceSimulation.docc/Documentation.md
================================================
# ``ForceSimulation``
Run force simulation within any number of dimensions.
## Overview
The `ForceSimulation` library enables you to create any dimensional simulation that uses velocity Verlet integration.
If you’re looking for an out-of-the-box SwiftUI View to render force-directed graphs, please refer to [Grape | Documentation](https://swiftgraphs.github.io/Grape/Grape/documentation/grape/).
@Image(source: "ForceDirectedGraph.png", alt: "An example of 2D force directied graph.")
For more information on force simulations, read: [Force simulations - D3](https://d3js.org/d3-force/simulation).
## Topics
### Creating a simulation
*
* ``Simulation``
* ``Kinetics``
### Built-in forces
* ``Kinetics/LinkForce``
* ``Kinetics/ManyBodyForce``
* ``Kinetics/CenterForce``
* ``Kinetics/CollideForce``
* ``Kinetics/PositionForce``
* ``Kinetics/RadialForce``
* ``Kinetics/EmptyForce``
### Utility forces for compositing a force field
* ``ForceField``
* ``CompositedForce``
* ``SealedForce2D``
* ``SealedForce3D``
### Spatial partitioning data structures
- ``KDTree``
- ``KDBox``
- ``BufferedKDTree``
- ``KDTreeDelegate``
### Deterministic randomness
- ``SimulatableFloatingPoint``
- ``DeterministicRandomGenerator``
- ``HasDeterministicRandomGenerator``
- ``DoubleLinearCongruentialGenerator``
- ``FloatLinearCongruentialGenerator``
### Supporting protocols
- ``ForceProtocol``
- ``SimulatableVector``
### Utilities
- ``UnsafeArray``
- ``EdgeID``
- ``AttributeDescriptor``
================================================
FILE: Sources/ForceSimulation/ForceSimulation.docc/theme-settings.json
================================================
{
"theme": {
"typography": {
"html-font": "system-ui, -apple-system, \"InterVar\"",
"html-font-mono": "ui-monospace, \"JetBrains Mono\", \"IBM Plex Mono\", monospace"
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/CenterForce.swift
================================================
extension Kinetics {
/// A force that drives nodes towards the center.
///
/// Center force is relatively fast, the complexity is `O(n)`,
/// where `n` is the number of nodes.
/// See [Collide Force - D3](https://d3js.org/d3-force/collide).
public struct CenterForce: ForceProtocol {
@inlinable
public func apply(to kinetics: inout Kinetics) {
var meanPosition = Vector.zero
let positionBufferPointer = kinetics.position.mutablePointer
for i in 0..: KDTreeDelegate
where Vector: SimulatableVector {
@usableFromInline
var radiusBufferPointer: UnsafeMutablePointer
public var maxNodeRadius: Vector.Scalar = .zero
@inlinable
public mutating func didAddNode(_ node: Int, at position: Vector) {
maxNodeRadius = max(maxNodeRadius, radiusBufferPointer[node])
}
@inlinable
public mutating func didRemoveNode(_ node: Int, at position: Vector) {
if radiusBufferPointer[node] >= maxNodeRadius {
// 🤯 for Collide force, set to 0 is fine
// Otherwise you need to traverse the delegate again
maxNodeRadius = 0
}
}
@inlinable
public func spawn() -> MaxRadiusNDTreeDelegate {
return Self(radiusBufferPointer: radiusBufferPointer)
}
@inlinable
init(maxNodeRadius: Vector.Scalar = 0, radiusBufferPointer: UnsafeMutablePointer)
{
self.maxNodeRadius = maxNodeRadius
self.radiusBufferPointer = radiusBufferPointer
}
}
extension Kinetics {
public typealias CollideRadius = AttributeDescriptor
/// A force that prevents nodes from overlapping.
///
/// This is a very expensive force, the complexity is `O(n log(n))`,
/// where `n` is the number of nodes.
/// See [Collide Force - D3](https://d3js.org/d3-force/collide).
public struct CollideForce: ForceProtocol {
// @usableFromInline
// var kinetics: Kinetics! = nil
public var radius: CollideRadius
public let iterationsPerTick: UInt
public var strength: Vector.Scalar
@inlinable
public init(
radius: CollideRadius,
strength: Vector.Scalar = 1.0,
iterationsPerTick: UInt = 1
) {
self.radius = radius
self.iterationsPerTick = iterationsPerTick
self.strength = strength
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.calculatedRadius = self.radius.calculateUnsafe(for: kinetics.validCount)
self.tree = .allocate(capacity: 1)
self.tree.initialize(
to:
BufferedKDTree(
rootBox: .init(
p0: .init(repeating: 0),
p1: .init(repeating: 1)
),
nodeCapacity: kinetics.validCount,
rootDelegate: .init(
radiusBufferPointer: self.calculatedRadius.mutablePointer)
)
)
}
@usableFromInline
var calculatedRadius: UnsafeArray! = nil
@usableFromInline
internal var tree:
UnsafeMutablePointer>>! = nil
@inlinable
public func apply(to kinetics: inout Kinetics) {
let strength = self.strength
let calculatedRadius = self.calculatedRadius!.mutablePointer
let positionBufferPointer = kinetics.position.mutablePointer
let velocityBufferPointer = kinetics.velocity.mutablePointer
let tree = self.tree!
for _ in 0...cover(of: kinetics.position)
tree.pointee.reset(
rootBox: coveringBox,
rootDelegate: .init(radiusBufferPointer: calculatedRadius)
)
assert(tree.pointee.validCount == 1)
for p in kinetics.range {
tree.pointee.add(nodeIndex: p, at: positionBufferPointer[p])
}
for i in kinetics.range {
let iOriginalPosition = positionBufferPointer[i]
let iOriginalVelocity = velocityBufferPointer[i]
let iR = calculatedRadius[i]
let iR2 = iR * iR
let iPosition = iOriginalPosition + iOriginalVelocity
tree.pointee.visit { t in
let maxRadiusOfQuad = t.delegate.maxNodeRadius
let deltaR = maxRadiusOfQuad + iR
if var jNode = t.nodeIndices {
while true {
let j = jNode.index
// print("\(i)<=>\(j)")
// is leaf, make sure every collision happens once.
if j > i {
let jR = calculatedRadius[j]
let jOriginalPosition = positionBufferPointer[j]
let jOriginalVelocity = velocityBufferPointer[j]
var deltaPosition =
iPosition - (jOriginalPosition + jOriginalVelocity)
let l = (deltaPosition).lengthSquared()
let deltaR = iR + jR
if l < deltaR * deltaR {
var l = /*simd_length*/ (deltaPosition.jiggled(by: &kinetics.randomGenerator))
.length()
l = (deltaR - l) / l * strength
let jR2 = jR * jR
let k = jR2 / (iR2 + jR2)
deltaPosition *= l
velocityBufferPointer[i] += deltaPosition * k
velocityBufferPointer[j] -= deltaPosition * (1 - k)
}
}
if jNode.next == nil {
break
} else {
jNode = jNode.next!.pointee
}
}
return false
}
// TODO: SIMD mask
// for laneIndex in t.box.p0.indices {
// let _v = t.box.p0[laneIndex]
// if _v > iPosition[laneIndex] + deltaR /* True if no overlap */ {
// return false
// }
// }
// for laneIndex in t.box.p1.indices {
// let _v = t.box.p1[laneIndex]
// if _v < iPosition[laneIndex] - deltaR /* True if no overlap */ {
// return false
// }
// }
let p0Flag = t.box.p0 .> (iPosition + deltaR)
let p1Flag = t.box.p1 .< (iPosition - deltaR)
let flag = p0Flag .| p1Flag
for laneIndex in t.box.p0.indices {
if flag[laneIndex] {
return false
}
// let _v = t.box.p1[laneIndex]
// if (t.box.p0[laneIndex] > iPosition[laneIndex] + deltaR)
// || (t.box.p1[laneIndex] < iPosition[laneIndex]
// - deltaR) /* True if no overlap */
// {
// return false
// }
}
return true
}
}
}
}
/// Deinitialize the tree and deallocate the memory.
/// Called when `Simulation` is deinitialized.
@inlinable
public func dispose() {
self.tree.dispose()
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/CompositedForce.swift
================================================
/// Workaround for yet unsupported packed generic pack types & same type requirements
public struct CompositedForce: ForceProtocol
where
F1: ForceProtocol, F2: ForceProtocol,
Vector: SimulatableVector & L2NormCalculatable,
F1.Vector == Vector, F2.Vector == Vector, F1.Vector == Vector
{
@usableFromInline var force1: F1?
@usableFromInline var force2: F2
@inlinable
public init(force1: F1? = nil, force2: F2) {
self.force1 = force1
self.force2 = force2
}
@inlinable
public func apply(to kinetics: inout Kinetics) {
self.force1?.apply(to: &kinetics)
self.force2.apply(to: &kinetics)
}
@inlinable
public func dispose() {
self.force1?.dispose()
self.force2.dispose()
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
self.force1?.bindKinetics(kinetics)
self.force2.bindKinetics(kinetics)
}
@inlinable
public init(@ForceBuilder _ builder: () -> CompositedForce) {
self = builder()
}
}
// public typealias CompositedForce2D = CompositedForce, F1, F2>
// where F1: ForceProtocol>, F2: ForceProtocol>
// public typealias CompositedForce3D = CompositedForce, F1, F2>
// where F1: ForceProtocol>, F2: ForceProtocol>
@resultBuilder
public struct ForceBuilder
where Vector: SimulatableVector & L2NormCalculatable {
public static func buildPartialBlock(first: F) -> F// CompositedForce.EmptyForce, F>
where F: ForceProtocol, Vector: SimulatableVector & L2NormCalculatable {
return first //.init(force2: first)
}
public static func buildPartialBlock(
accumulated: F1,
next: F2
) -> CompositedForce
where
F1: ForceProtocol, F2: ForceProtocol,
Vector: SimulatableVector & L2NormCalculatable,
F1.Vector == Vector, F2.Vector == Vector
{
return CompositedForce(force1: accumulated, force2: next)
}
public static func buildBlock(
_ force1: F1? = nil,
_ force2: F2
) -> CompositedForce
where
F1: ForceProtocol, F2: ForceProtocol,
Vector: SimulatableVector & L2NormCalculatable,
F1.Vector == Vector, F2.Vector == Vector
{
return CompositedForce(force1: force1, force2: force2)
}
public static func buildExpression(
_ expression: Descriptor
) -> Descriptor.ConcreteForce {
expression.createForce()
}
public static func buildExpression(
_ expression: F
) -> F {
expression
}
}
================================================
FILE: Sources/ForceSimulation/Forces/EmptyForce.swift
================================================
extension Kinetics {
public struct EmptyForce: ForceProtocol {
@inlinable
public func apply(to kinetics: inout Kinetics) {}
@inlinable
public func bindKinetics(_ kinetics: Kinetics) {}
@inlinable
public func dispose() {}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/KDTreeForce.swift
================================================
// public protocol KDTreeForce: ForceProtocol
// where
// Vector: SimulatableVector & L2NormCalculatable
// {
// associatedtype Delegate: KDTreeDelegate where Delegate.Vector == Vector, Delegate.NodeID == Int
// var kinetics: Kinetics! { get set }
// func epilogue()
// func buildDelegate() -> Delegate
// func visitForeignTree(
// tree: inout KDTree, getDelegate: (D) -> Delegate)
// }
// public struct CompositedKDTreeDelegate: KDTreeDelegate
// where
// V: SimulatableVector & L2NormCalculatable,
// D1: KDTreeDelegate, D2: KDTreeDelegate
// {
// var d1: D1
// var d2: D2
// mutating public func didAddNode(_ node: Int, at position: V) {
// d1.didAddNode(node, at: position)
// d2.didAddNode(node, at: position)
// }
// mutating public func didRemoveNode(_ node: Int, at position: V) {
// d1.didRemoveNode(node, at: position)
// d2.didRemoveNode(node, at: position)
// }
// public func spawn() -> CompositedKDTreeDelegate {
// return .init(d1: d1.spawn(), d2: d2.spawn())
// }
// }
// extension Kinetics.ManyBodyForce: KDTreeForce {
// public typealias Delegate = MassCentroidKDTreeDelegate
// public func epilogue() {
// }
// public func buildDelegate() -> MassCentroidKDTreeDelegate {
// return .init(massProvider: { self.precalculatedMass[$0] })
// }
// public func visitForeignTree(
// tree: inout KDTree, getDelegate: (D) -> MassCentroidKDTreeDelegate
// ) {
// }
// }
// extension Kinetics.CollideForce: KDTreeForce {
// public typealias Delegate = MaxRadiusNDTreeDelegate
// public func epilogue() {
// }
// public func buildDelegate() -> MaxRadiusNDTreeDelegate {
// return .init(radiusProvider: { self.calculatedRadius[$0] })
// }
// public func visitForeignTree(
// tree: inout KDTree, getDelegate: (D) -> MaxRadiusNDTreeDelegate
// ) where D: KDTreeDelegate, Vector == D.Vector, D.NodeID == Int {
// }
// }
// public struct CompositedKDTreeForce: ForceProtocol
// where
// KF1: KDTreeForce, KF2: KDTreeForce,
// Vector: SimulatableVector & L2NormCalculatable,
// KF1.Vector == Vector, KF2.Vector == Vector, KF1.Vector == Vector
// {
// var force1: KF1
// var force2: KF2
// public func apply() {
// force1.epilogue()
// force2.epilogue()
// var tree = KDTree>(
// covering: force1.kinetics!.position,
// rootDelegate: CompositedKDTreeDelegate(
// d1: force1.buildDelegate(),
// d2: force2.buildDelegate()
// )
// )
// force1.visitForeignTree(tree: &tree, getDelegate: \.d1)
// force2.visitForeignTree(tree: &tree, getDelegate: \.d2)
// }
// public mutating func bindKinetics(_ kinetics: Kinetics) {
// }
// }
================================================
FILE: Sources/ForceSimulation/Forces/LinkForce.swift
================================================
import Darwin
extension Kinetics {
public enum LinkStiffness {
case constant(Vector.Scalar)
case varied((EdgeID, LinkLookup) -> Vector.Scalar)
case weightedByDegree(k: (EdgeID, LinkLookup) -> Vector.Scalar)
@inlinable
func calculate(for link: [EdgeID], in lookup: LinkLookup) -> [Vector.Scalar] {
switch self {
case .constant(let m):
return Array(repeating: m, count: link.count)
case .varied(let f):
return link.map { l in f(l, lookup) }
case .weightedByDegree(let k):
return link.map { l in
k(l, lookup)
/ (Vector.Scalar(
min(
lookup.count[l.source, default: 0],
lookup.count[l.target, default: 0]
)
))
}
}
}
}
public enum LinkLength {
case constant(Vector.Scalar)
case varied((EdgeID, LinkLookup) -> Vector.Scalar)
@inlinable
func calculate(for link: [EdgeID], in lookup: LinkLookup) -> [Vector.Scalar] {
switch self {
case .constant(let m):
return Array(repeating: m, count: link.count)
case .varied(let f):
return link.map { l in f(l, lookup) }
}
}
}
/// A force that represents links between nodes.
///
/// The complexity is `O(e)`, where `e` is the number of links.
/// See [Link Force - D3](https://d3js.org/d3-force/link).
public struct LinkForce: ForceProtocol {
// @usableFromInline
// internal var kinetics: Kinetics! = nil
@usableFromInline
var linkStiffness: LinkStiffness
@usableFromInline
var calculatedStiffness: [Vector.Scalar] = []
@usableFromInline
var linkLength: LinkLength
@usableFromInline
var calculatedLength: [Vector.Scalar] = []
/// Bias
@usableFromInline
var calculatedBias: [Vector.Scalar] = []
@inlinable
public func apply(to kinetics: inout Kinetics) {
let positionBufferPointer = kinetics.position.mutablePointer
let velocityBufferPointer = kinetics.velocity.mutablePointer
for _ in 0..]! = nil
@usableFromInline
internal var linkLookup: LinkLookup = .init(links: [])
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.links = kinetics.links
self.links = self.links.filter {
$0.source < kinetics.validCount && $0.target < kinetics.validCount
}
self.linkLookup = .init(links: self.links)
self.calculatedBias = self.links.map { l in
Vector.Scalar(self.linkLookup.count[l.source, default: 0])
/ Vector.Scalar(
linkLookup.count[l.target, default: 0]
+ linkLookup.count[l.source, default: 0])
}
self.calculatedStiffness = self.linkStiffness.calculate(
for: self.links, in: self.linkLookup)
self.calculatedLength = self.linkLength.calculate(for: self.links, in: self.linkLookup)
}
@usableFromInline var iterationsPerTick: UInt
@inlinable
public init(
// _ links: [EdgeID],
stiffness: LinkStiffness,
originalLength: LinkLength = .constant(30),
iterationsPerTick: UInt = 1
) {
// self.links = links
self.iterationsPerTick = iterationsPerTick
self.linkStiffness = stiffness
self.linkLength = originalLength
}
@inlinable
public func dispose() {}
}
}
public struct LinkLookup {
public let sourceToTarget: [NodeID: [NodeID]]
public let targetToSource: [NodeID: [NodeID]]
public let count: [NodeID: Int]
@inlinable
public init(links: [EdgeID]) {
var sourceToTarget: [NodeID: [NodeID]] = [:]
var targetToSource: [NodeID: [NodeID]] = [:]
var count: [NodeID: Int] = [:]
for link in links {
sourceToTarget[link.source, default: []].append(link.target)
targetToSource[link.target, default: []].append(link.source)
count[link.source, default: 0] += 1
count[link.target, default: 0] += 1
}
self.sourceToTarget = sourceToTarget
self.targetToSource = targetToSource
self.count = count
}
}
extension Kinetics.LinkStiffness: Equatable {
@inlinable
public static func == (lhs: Kinetics.LinkStiffness, rhs: Kinetics.LinkStiffness) -> Bool {
switch (lhs, rhs) {
case (.constant(let l), .constant(let r)):
return l == r
default:
return false
}
}
}
extension Kinetics.LinkLength: Equatable {
@inlinable
public static func == (lhs: Kinetics.LinkLength, rhs: Kinetics.LinkLength) -> Bool {
switch (lhs, rhs) {
case (.constant(let l), .constant(let r)):
return l == r
default:
return false
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/ManyBodyForce.swift
================================================
import simd
public struct MassCentroidKDTreeDelegate: KDTreeDelegate
where Vector: SimulatableVector {
public var accumulatedMass: Vector.Scalar = .zero
public var accumulatedCount: Int = 0
public var accumulatedMassWeightedPositions: Vector = .zero
@usableFromInline let massArray: UnsafeMutablePointer //(NodeID) -> Vector.Scalar
@inlinable
init(massProvider: UnsafeMutablePointer) {
self.massArray = massProvider
}
@inlinable
init(
initialAccumulatedProperty: Vector.Scalar,
initialAccumulatedCount: Int,
initialWeightedAccumulatedNodePositions: Vector,
massProvider: UnsafeMutablePointer //@escaping (Int) -> Vector.Scalar
) {
self.accumulatedMass = initialAccumulatedProperty
self.accumulatedCount = initialAccumulatedCount
self.accumulatedMassWeightedPositions = initialWeightedAccumulatedNodePositions
self.massArray = massProvider
}
@inlinable public mutating func didAddNode(_ node: Int, at position: Vector) {
let p = massArray[node]
#if DEBUG
assert(p > 0)
#endif
accumulatedCount += 1
accumulatedMass += p
accumulatedMassWeightedPositions += position * p
}
@inlinable public mutating func didRemoveNode(_ node: Int, at position: Vector) {
let p = massArray[node]
accumulatedCount -= 1
accumulatedMass -= p
accumulatedMassWeightedPositions -= position * p
// TODO: parent removal?
}
// @inlinable public func copy() -> Self {
// return Self(
// initialAccumulatedProperty: self.accumulatedMass,
// initialAccumulatedCount: self.accumulatedCount,
// initialWeightedAccumulatedNodePositions: self.accumulatedMassWeightedPositions,
// massProvider: self.massProvider
// )
// }
@inlinable public func spawn() -> Self {
return Self(massProvider: self.massArray)
}
}
extension Kinetics {
public typealias NodeMass = AttributeDescriptor
/// A force that simulate the many-body force.
///
/// This is a very expensive force, the complexity is `O(n log(n))`,
/// where `n` is the number of nodes. The complexity might degrade to `O(n^2)` if the nodes are too close to each other.
/// See [Manybody Force - D3](https://d3js.org/d3-force/many-body).
public struct ManyBodyForce: ForceProtocol {
@usableFromInline let strength: Vector.Scalar
@usableFromInline var theta2: Vector.Scalar
@usableFromInline var theta: Vector.Scalar {
didSet {
theta2 = theta * theta
}
}
@usableFromInline let distanceMin: Vector.Scalar = 1
@usableFromInline let distanceMin2: Vector.Scalar = 1
@usableFromInline let distanceMax2: Vector.Scalar = .infinity
@usableFromInline let distanceMax: Vector.Scalar = .infinity
public let mass: NodeMass
@usableFromInline var precalculatedMass: UnsafeArray! = nil
@inlinable
public init(
strength: Vector.Scalar,
nodeMass: NodeMass = .constant(1.0),
theta: Vector.Scalar = 0.9
) {
self.strength = strength
self.mass = nodeMass
self.theta = theta
self.theta2 = theta * theta
}
@inlinable
public func apply(to kinetics: inout Kinetics) {
// Avoid capturing self
let alpha = kinetics.alpha
let theta2 = self.theta2
let distanceMin2 = self.distanceMin2
let distanceMax2 = self.distanceMax2
let strength = self.strength
let precalculatedMass = self.precalculatedMass.mutablePointer
let positionBufferPointer = kinetics.position.mutablePointer
// let random = kinetics.randomGenerator
let tree = self.tree!
let coveringBox = KDBox.cover(of: kinetics.position)
tree.pointee.reset(rootBox: coveringBox, rootDelegate: .init(massProvider: precalculatedMass))
for p in kinetics.range {
tree.pointee.add(nodeIndex: p, at: positionBufferPointer[p])
}
for i in kinetics.range {
let pos = positionBufferPointer[i]
var f = Vector.zero
tree.pointee.visit { t in
guard t.delegate.accumulatedCount > 0 else { return false }
let centroid =
t.delegate.accumulatedMassWeightedPositions / t.delegate.accumulatedMass
let vec = centroid - pos
let boxWidth = (t.box.p1 - t.box.p0)[0]
var distanceSquared =
(vec
.jiggled(by: &kinetics.randomGenerator)).lengthSquared()
let farEnough: Bool =
(distanceSquared * theta2) > (boxWidth * boxWidth)
if distanceSquared < distanceMin2 {
distanceSquared = (distanceMin2 * distanceSquared).squareRoot()
}
if farEnough {
guard distanceSquared < distanceMax2 else { return true }
/// Workaround for "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
let k: Vector.Scalar =
strength * alpha * t.delegate.accumulatedMass
/ distanceSquared // distanceSquared.squareRoot()
f += vec * k
return false
} else if t.childrenBufferPointer != nil {
return true
}
if t.isFilledLeaf {
if t.nodeIndices!.contains(i) { return false }
let massAcc = t.delegate.accumulatedMass
let k: Vector.Scalar = strength * alpha * massAcc / distanceSquared // distanceSquared.squareRoot()
f += vec * k
return false
} else {
return true
}
}
positionBufferPointer[i] += f / precalculatedMass[i]
}
}
// public var kinetics: Kinetics! = nil
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.precalculatedMass = self.mass.calculateUnsafe(for: (kinetics.validCount))
self.tree = .allocate(capacity: 1)
self.tree.initialize(
to:
BufferedKDTree(
rootBox: .init(
p0: .init(repeating: 0),
p1: .init(repeating: 1)
),
nodeCapacity: kinetics.validCount,
rootDelegate: MassCentroidKDTreeDelegate(
massProvider: precalculatedMass.mutablePointer)
)
)
}
/// The buffered KDTree used across all ticks.
@usableFromInline
internal var tree:
UnsafeMutablePointer>>! = nil
/// Deinitialize the tree and deallocate the memory.
/// Called when `Simulation` is deinitialized.
@inlinable
public func dispose() {
self.tree.deinitialize(count: 1)
self.tree.deallocate()
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/PackedForce.swift
================================================
// struct PackedForce: ForceProtocol where Vector: SIMD, Vector.Scalar: FloatingPoint, repeat each Force: ForceProtocol {
// let forces: (repeat each Force)
// // var kinetics: Kinetics?
// init(forces: repeat each Force) {
// self.forces = (repeat each forces)
// }
// func apply() {
// repeat (each forces).apply()
// }
// func bindKinetics(_ kinetics: Kinetics) {
// repeat (each forces).bindKinetics(kinetics)
// }
// }
================================================
FILE: Sources/ForceSimulation/Forces/PositionForce.swift
================================================
extension Kinetics {
public typealias TargetOnDirection = AttributeDescriptor
public enum DirectionOfPositionForce: Equatable {
case x
case y
case z
case entryOfVector(Int)
}
public typealias PositionStrength = AttributeDescriptor
/// A force that moves nodes to a target position.
///
/// Center force is relatively fast, the complexity is `O(n)`,
/// where `n` is the number of nodes.
/// See [Position Force - D3](https://d3js.org/d3-force/position).
public struct PositionForce: ForceProtocol {
// @usableFromInline var kinetics: Kinetics! = nil
public var strength: PositionStrength
public var direction: Int
public var calculatedStrength: UnsafeArray! = nil
public var targetOnDirection: TargetOnDirection
public var calculatedTargetOnDirection: UnsafeArray! = nil
@inlinable
public func apply(to kinetics: inout Kinetics) {
let alpha = kinetics.alpha
let lane = self.direction
for i in kinetics.range {
kinetics.velocity[i][lane] +=
(self.calculatedTargetOnDirection[i] - kinetics.position[i][lane])
* self.calculatedStrength[i] * alpha
}
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.calculatedTargetOnDirection = self.targetOnDirection.calculateUnsafe(
for: kinetics.validCount)
self.calculatedStrength = self.strength.calculateUnsafe(for: kinetics.validCount)
}
@inlinable
public init(
direction: DirectionOfPositionForce,
targetOnDirection: TargetOnDirection,
strength: PositionStrength = .constant(1.0)
) {
self.strength = strength
self.direction = direction.lane
self.targetOnDirection = targetOnDirection
}
@inlinable
public func dispose() {}
}
}
extension Kinetics.DirectionOfPositionForce {
@inlinable
var lane: Int {
switch self {
case .x: return 0
case .y: return 1
case .z: return 2
case .entryOfVector(let i): return i
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/RadialForce.swift
================================================
extension Kinetics {
public typealias RadialBound = AttributeDescriptor
public typealias RadialStrength = AttributeDescriptor
/// A force that applies a radial force to all nodes.
///
/// Center force is relatively fast, the complexity is `O(n)`,
/// where `n` is the number of nodes.
/// See [Position Force - D3](https://d3js.org/d3-force/position).
public struct RadialForce: ForceProtocol {
// @usableFromInline var kinetics: Kinetics! = nil
public var radius: RadialBound
public var strength: RadialStrength
public var center: Vector
@usableFromInline
var calculatedRadius: UnsafeArray! = nil
@usableFromInline
var calculatedStrength: UnsafeArray! = nil
@inlinable
public func apply(to kinetics: inout Kinetics) {
let alpha = kinetics.alpha
for i in kinetics.range {
let deltaPosition = (kinetics.position[i] - self.center).jiggled(
by: &kinetics.randomGenerator) //.jiggled()
let r = deltaPosition.length()
let k =
(self.calculatedRadius[i]
* self.calculatedStrength[i] * alpha) / r
kinetics.velocity[i] += deltaPosition * k
}
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.calculatedRadius = self.radius.calculateUnsafe(for: kinetics.validCount)
self.calculatedStrength = self.strength.calculateUnsafe(for: kinetics.validCount)
}
@inlinable
public init(center: Vector, radius: RadialBound, strength: RadialStrength) {
self.center = center
self.radius = radius
self.strength = strength
}
@inlinable
public func dispose() {}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/SealedForce2D.swift
================================================
/// A force that can be composed of one or multiple forces. The forces you can add
/// here include:
/// - `Kinetics2D.CenterForce`
/// - `Kinetics2D.RadialForce`
/// - `Kinetics2D.ManyBodyForce`
/// - `Kinetics2D.LinkForce`
/// - `Kinetics2D.CollideForce`
/// - `Kinetics2D.PositionForce`
/// - `Kinetics2D.EmptyForce`
///
/// If you want to add a custom force, checkout `CompositedForce`.
public struct SealedForce2D: Force2D {
public var entries: [ForceEntry] = []
@inlinable
public func apply(to kinetics: inout Kinetics>) {
for force in self.entries {
force.apply(to: &kinetics)
}
}
@inlinable
public func dispose() {
for force in self.entries {
force.dispose()
}
}
@inlinable
public init(
_ entries: [ForceEntry]
) {
self.entries = entries
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics>) {
self.entries = self.entries.map { entry in
switch entry {
case .center(var force):
force.bindKinetics(kinetics)
return .center(force)
case .radial(var force):
force.bindKinetics(kinetics)
return .radial(force)
case .manyBody(var force):
force.bindKinetics(kinetics)
return .manyBody(force)
case .link(var force):
force.bindKinetics(kinetics)
return .link(force)
case .collide(var force):
force.bindKinetics(kinetics)
return .collide(force)
case .position(var force):
force.bindKinetics(kinetics)
return .position(force)
default:
return entry
}
}
}
public enum ForceEntry {
case center(Kinetics2D.CenterForce)
case radial(Kinetics2D.RadialForce)
case manyBody(Kinetics2D.ManyBodyForce)
case link(Kinetics2D.LinkForce)
case collide(Kinetics2D.CollideForce)
case position(Kinetics2D.PositionForce)
case empty
@inlinable
public func dispose() {
switch self {
case .center(let force):
force.dispose()
case .radial(let force):
force.dispose()
case .manyBody(let force):
force.dispose()
case .link(let force):
force.dispose()
case .collide(let force):
force.dispose()
case .position(let force):
force.dispose()
default:
break
}
}
@inlinable
public func apply(to kinetics: inout Kinetics>) {
switch self {
case .center(let force):
force.apply(to: &kinetics)
case .radial(let force):
force.apply(to: &kinetics)
case .manyBody(let force):
force.apply(to: &kinetics)
case .link(let force):
force.apply(to: &kinetics)
case .collide(let force):
force.apply(to: &kinetics)
case .position(let force):
force.apply(to: &kinetics)
default:
break
}
}
}
@inlinable
public init(@SealedForce2DBuilder _ builder: () -> [ForceEntry]) {
self.entries = builder()
}
}
@resultBuilder
public struct SealedForce2DBuilder {
public static func buildBlock(_ components: SealedForce2D.ForceEntry...) -> [SealedForce2D.ForceEntry] {
components
}
public static func buildExpression(_ expression: FD) -> SealedForce2D.ForceEntry where FD: ForceDescriptor, FD.ConcreteForce: Force2D {
let f = expression.createForce()
switch f {
case let f as Kinetics2D.CenterForce:
return .center(f)
case let f as Kinetics2D.RadialForce:
return .radial(f)
case let f as Kinetics2D.ManyBodyForce:
return .manyBody(f)
case let f as Kinetics2D.LinkForce:
return .link(f)
case let f as Kinetics2D.CollideForce:
return .collide(f)
case let f as Kinetics2D.PositionForce:
return .position(f)
default:
return .empty
}
}
public static func buildExpression(_ f: F) -> SealedForce2D.ForceEntry where F:Force2D {
switch f {
case let f as Kinetics2D.CenterForce:
return .center(f)
case let f as Kinetics2D.RadialForce:
return .radial(f)
case let f as Kinetics2D.ManyBodyForce:
return .manyBody(f)
case let f as Kinetics2D.LinkForce:
return .link(f)
case let f as Kinetics2D.CollideForce:
return .collide(f)
case let f as Kinetics2D.PositionForce:
return .position(f)
default:
return .empty
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/SealedForce3D.swift
================================================
/// A force that can be composed of one or multiple forces. The forces you can add
/// here include:
/// - `Kinetics3D.CenterForce`
/// - `Kinetics3D.RadialForce`
/// - `Kinetics3D.ManyBodyForce`
/// - `Kinetics3D.LinkForce`
/// - `Kinetics3D.CollideForce`
/// - `Kinetics3D.PositionForce`
/// - `Kinetics3D.EmptyForce`
///
/// If you want to add a custom force, checkout `CompositedForce`.
public struct SealedForce3D: Force3D {
public var entries: [ForceEntry] = []
@inlinable
public func apply(to kinetics: inout Kinetics>) {
for force in self.entries {
force.apply(to: &kinetics)
}
}
@inlinable
public func dispose() {
for force in self.entries {
force.dispose()
}
}
@inlinable
public init(
_ entries: [ForceEntry]
) {
self.entries = entries
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics>) {
self.entries = self.entries.map { entry in
switch entry {
case .center(var force):
force.bindKinetics(kinetics)
return .center(force)
case .radial(var force):
force.bindKinetics(kinetics)
return .radial(force)
case .manyBody(var force):
force.bindKinetics(kinetics)
return .manyBody(force)
case .link(var force):
force.bindKinetics(kinetics)
return .link(force)
case .collide(var force):
force.bindKinetics(kinetics)
return .collide(force)
case .position(var force):
force.bindKinetics(kinetics)
return .position(force)
default:
return entry
}
}
}
public enum ForceEntry {
case center(Kinetics3D.CenterForce)
case radial(Kinetics3D.RadialForce)
case manyBody(Kinetics3D.ManyBodyForce)
case link(Kinetics3D.LinkForce)
case collide(Kinetics3D.CollideForce)
case position(Kinetics3D.PositionForce)
case empty
@inlinable
public func dispose() {
switch self {
case .center(let force):
force.dispose()
case .radial(let force):
force.dispose()
case .manyBody(let force):
force.dispose()
case .link(let force):
force.dispose()
case .collide(let force):
force.dispose()
case .position(let force):
force.dispose()
default:
break
}
}
@inlinable
public func apply(to kinetics: inout Kinetics>) {
switch self {
case .center(let force):
force.apply(to: &kinetics)
case .radial(let force):
force.apply(to: &kinetics)
case .manyBody(let force):
force.apply(to: &kinetics)
case .link(let force):
force.apply(to: &kinetics)
case .collide(let force):
force.apply(to: &kinetics)
case .position(let force):
force.apply(to: &kinetics)
default:
break
}
}
}
@inlinable
public init(@SealedForce3DBuilder _ builder: () -> [ForceEntry]) {
self.entries = builder()
}
}
@resultBuilder
public struct SealedForce3DBuilder {
public static func buildBlock(_ components: SealedForce3D.ForceEntry...) -> [SealedForce3D.ForceEntry] {
components
}
public static func buildExpression(_ expression: FD) -> SealedForce3D.ForceEntry where FD: ForceDescriptor, FD.ConcreteForce: Force3D {
let f = expression.createForce()
switch f {
case let f as Kinetics3D.CenterForce:
return .center(f)
case let f as Kinetics3D.RadialForce:
return .radial(f)
case let f as Kinetics3D.ManyBodyForce:
return .manyBody(f)
case let f as Kinetics3D.LinkForce:
return .link(f)
case let f as Kinetics3D.CollideForce:
return .collide(f)
case let f as Kinetics3D.PositionForce:
return .position(f)
default:
return .empty
}
}
public static func buildExpression(_ f: F) -> SealedForce3D.ForceEntry where F:Force3D {
switch f {
case let f as Kinetics3D.CenterForce:
return .center(f)
case let f as Kinetics3D.RadialForce:
return .radial(f)
case let f as Kinetics3D.ManyBodyForce:
return .manyBody(f)
case let f as Kinetics3D.LinkForce:
return .link(f)
case let f as Kinetics3D.CollideForce:
return .collide(f)
case let f as Kinetics3D.PositionForce:
return .position(f)
default:
return .empty
}
}
}
================================================
FILE: Sources/ForceSimulation/KDTree/BufferedKDTree.swift
================================================
public struct BufferedKDTree: Disposable
where
Vector: SimulatableVector & L2NormCalculatable,
Delegate: KDTreeDelegate
{
public typealias Box = KDBox
public typealias TreeNode = KDTreeNode
@usableFromInline
internal var rootPointer: UnsafeMutablePointer {
treeNodeBuffer.mutablePointer
}
@usableFromInline
internal var validCount: Int = 0
@usableFromInline
internal var treeNodeBuffer: UnsafeArray
@inlinable
static internal var clusterDistanceSquared: Vector.Scalar {
return Vector.clusterDistanceSquared
}
@inlinable
public var root: TreeNode { rootPointer.pointee }
@inlinable
public init(
rootBox: Box,
nodeCapacity: Int,
rootDelegate: @autoclosure () -> Delegate
) {
// Assuming each add creates 2^Vector.scalarCount nodes
// In most situations this is sufficient (for example the miserable graph)
// But It's possible to exceed this limit:
// 2 additions very close but not clustered in the same box
// In this case there's no upperbound for addition so `resize` is needed
let maxBufferCount = (nodeCapacity << Vector.scalarCount) + 1
self.rootDelegate = rootDelegate()
treeNodeBuffer = .createBuffer(
withHeader: maxBufferCount,
count: maxBufferCount,
initialValue: .zeroWithDelegate(self.rootDelegate)
)
rootPointer.pointee = TreeNode(
nodeIndices: nil,
childrenBufferPointer: nil,
delegate: self.rootDelegate,
box: rootBox
)
self.validCount = 1
}
@usableFromInline
internal var rootDelegate: Delegate
@inlinable
public mutating func reset(
rootBox: Box,
rootDelegate: @autoclosure () -> Delegate
) {
self.rootDelegate = rootDelegate()
treeNodeBuffer.withUnsafeMutablePointerToElements {
for i in 0.. treeNodeBuffer.header)
let rootCopy = root
#endif
let oldRootPointer = rootPointer
let newTreeNodeBuffer = UnsafeArray.createBuffer(
withHeader: newTreeNodeBufferSize,
count: newTreeNodeBufferSize,
moving: treeNodeBuffer.mutablePointer,
movingCount: validCount,
fillingExcessiveBufferWith: .zeroWithDelegate(self.rootDelegate)
)
let newRootPointer = newTreeNodeBuffer.withUnsafeMutablePointerToElements { $0 }
for i in 0.. Bool {
if validCount + count > treeNodeBuffer.count {
let factor = (count / self.treeNodeBuffer.count) + 2
resize(to: treeNodeBuffer.count * factor)
assert(treeNodeBuffer.count >= validCount + count)
return true
}
return false
}
@inlinable
public mutating func add(
nodeIndex: Int,
at point: Vector
) {
assert(validCount > 0)
cover(point: point)
addWithoutCover(
onTreeNode: rootPointer,
nodeOf: nodeIndex,
at: point
)
}
@inlinable
internal mutating func addWithoutCover(
onTreeNode treeNode: UnsafeMutablePointer,
nodeOf nodeIndex: Int,
at point: Vector
) {
if treeNode.pointee.childrenBufferPointer != nil {
let directionOfNewNode = getIndexInChildren(
point, relativeTo: treeNode.pointee.box.center)
let treeNodeOffset = (treeNode) - rootPointer
self.addWithoutCover(
onTreeNode: treeNode.pointee.childrenBufferPointer! + directionOfNewNode,
nodeOf: nodeIndex,
at: point
)
rootPointer[treeNodeOffset].delegate.didAddNode(nodeIndex, at: point)
return
} else if treeNode.pointee.nodeIndices == nil {
// empty leaf
treeNode.pointee.nodeIndices = TreeNode.NodeIndex(nodeIndex)
treeNode.pointee.nodePosition = point
treeNode.pointee.delegate.didAddNode(nodeIndex, at: point)
return
} else if treeNode.pointee.nodePosition.distanceSquared(to: point)
> Self.clusterDistanceSquared
{
// filled leaf
let treeNodeOffset = (treeNode) - rootPointer
resizeIfNeededBeforeAllocation(for: Self.directionCount)
let newTreeNode = self.rootPointer + treeNodeOffset
let spawnedDelegate = newTreeNode.pointee.delegate.spawn()
let center = newTreeNode.pointee.box.center
let _box = newTreeNode.pointee.box
for j in 0..> i) & 0b1
if isOnTheHigherRange != 0 {
__box.p0[i] = center[i]
} else {
__box.p1[i] = center[i]
}
}
let obsoletePtr = self.rootPointer + validCount + j
obsoletePtr.pointee.disposeNodeIndices()
obsoletePtr.pointee = TreeNode(
nodeIndices: nil,
childrenBufferPointer: nil,
delegate: spawnedDelegate,
box: __box
)
}
newTreeNode.pointee.childrenBufferPointer = rootPointer + validCount
validCount += Self.directionCount
if let childrenBufferPointer = newTreeNode.pointee.childrenBufferPointer {
let direction = getIndexInChildren(
newTreeNode.pointee.nodePosition,
relativeTo: center
)
// newly created, no need to dispose
// childrenBufferPointer[direction].disposeNodeIndices()
childrenBufferPointer[direction] = .init(
nodeIndices: newTreeNode.pointee.nodeIndices,
childrenBufferPointer: childrenBufferPointer[direction]
.childrenBufferPointer,
delegate: newTreeNode.pointee.delegate,
box: childrenBufferPointer[direction].box,
nodePosition: newTreeNode.pointee.nodePosition
)
newTreeNode.pointee.nodeIndices = nil
newTreeNode.pointee.nodePosition = .zero
}
let directionOfNewNode = getIndexInChildren(point, relativeTo: center)
// This add might also resize this buffer!
addWithoutCover(
onTreeNode: newTreeNode.pointee.childrenBufferPointer! + directionOfNewNode,
nodeOf: nodeIndex,
at: point
)
rootPointer[treeNodeOffset].delegate.didAddNode(nodeIndex, at: point)
return
} else {
// filled leaf and within cluster distance
treeNode.pointee.nodeIndices!.append(nodeIndex: nodeIndex)
treeNode.pointee.delegate.didAddNode(nodeIndex, at: point)
return
}
}
@inlinable
internal mutating func cover(point: Vector) {
if self.root.box.contains(point) { return }
repeat {
let direction = self.getIndexInChildren(point, relativeTo: self.root.box.p0)
self.expand(towards: direction)
} while !self.root.box.contains(point)
}
@inlinable
internal mutating func expand(towards direction: Int) {
let nailedDirection = (Self.directionCount - 1) - direction
let nailedCorner = self.root.box.getCorner(of: nailedDirection)
let _corner = self.root.box.getCorner(of: direction)
let expandedCorner = (_corner + _corner) - nailedCorner
let newRootBox = Box(nailedCorner, expandedCorner)
let _rootValue = self.root
// spawn the delegate with the same internal values
// for the children, use implicit copy of spawned
let spawned = _rootValue.delegate.spawn()
let newChildrenPointer = self.rootPointer + validCount
resizeIfNeededBeforeAllocation(for: Self.directionCount)
for j in 0..> i) & 0b1
// TODO: use simd mask
if isOnTheHigherRange != 0 {
__box.p0[i] = _corner[i]
} else {
__box.p1[i] = _corner[i]
}
}
// newly allocated, no need to dispose
if j != nailedDirection {
self.treeNodeBuffer[validCount + j] = TreeNode(
nodeIndices: nil,
childrenBufferPointer: nil,
delegate: spawned,
box: __box,
nodePosition: .zero
)
} else {
self.treeNodeBuffer[validCount + j] = TreeNode(
nodeIndices: _rootValue.nodeIndices,
childrenBufferPointer: _rootValue.childrenBufferPointer,
delegate: _rootValue.delegate,
box: __box,
nodePosition: _rootValue.nodePosition
)
}
}
self.validCount += Self.directionCount
// don't dispose, they are used in treeNodeBuffer[validCount + j]
self.rootPointer.pointee = .init(
nodeIndices: nil,
childrenBufferPointer: newChildrenPointer,
delegate: _rootValue.delegate,
box: newRootBox
)
}
@inlinable
static internal var directionCount: Int { 1 << Vector.scalarCount }
@inlinable
public func dispose() {
treeNodeBuffer.withUnsafeMutablePointerToElements {
for i in 0.. Int {
var index = 0
let mask = point .>= originalPoint
for i in 0..) -> Bool
) {
rootPointer.pointee.visit(shouldVisitChildren: shouldVisitChildren)
}
}
================================================
FILE: Sources/ForceSimulation/KDTree/KDBox.swift
================================================
//
// KDBox.swift
//
//
// Created by li3zhen1 on 10/14/23.
//
import simd
/// A box in N-dimensional space.
///
/// - Note: `p0` is the minimum point of the box, `p1` is the maximum point of the box.
public struct KDBox: Equatable
where V: SIMD, V.Scalar: FloatingPoint & ExpressibleByFloatLiteral {
/// the minimum anchor of the box
public var p0: V
/// the maximum anchor of the box
public var p1: V
/// Create a box with 2 anchors.
///
/// - Parameters:
/// - p0: anchor
/// - p1: another anchor in the diagonal position of `p0`
/// - Note: `p0` you pass does not have to be minimum point of the box.
/// `p1` does not have to be maximum point of the box. The initializer will
/// automatically adjust the order of `p0` and `p1` to make sure `p0` is the
/// minimum point of the box and `p1` is the maximum point of the box.
@inlinable
public init(p0: V, p1: V) {
// #if DEBUG
assert(p0 != p1, "NdBox was initialized with 2 same anchor")
// #endif
// var p0 = p0
// var p1 = p1
// for i in p0.indices {
// if p1[i] < p0[i] {
// swap(&p0[i], &p1[i])
// }
// }
// let mask = p0 .< p1
// self.p0 = p1.replacing(with: p0, where: mask)
// self.p1 = p0.replacing(with: p1, where: mask)
self.p0 = pointwiseMin(p0, p1)
self.p1 = pointwiseMax(p0, p1)
// TODO: use Mask
}
/// Create a box with 2 anchors.
///
/// - Parameters:
/// - pMin: minimum anchor of the box
/// - pMax: maximum anchor of the box
/// - Note: Please make sure `pMin` is the minimum point of the box and `pMax` is the
/// maximum point of the box.
@inlinable
@available(*, deprecated, renamed: "init(uncheckedP0:uncheckedP1:)")
internal init(pMin: V, pMax: V) {
assert(pMin != pMax, "NdBox was initialized with 2 same anchor")
self.p0 = pMin
self.p1 = pMax
}
@inlinable
internal init(uncheckedP0: V, uncheckedP1: V) {
self.p0 = uncheckedP0
self.p1 = uncheckedP1
}
/// Create a box with 2 zero anchors.
@inlinable
@available(*, deprecated, renamed: "zero")
public init() {
p0 = .zero
p1 = .zero
}
/// Create a box with 2 anchors.
/// - Parameters:
/// - p0: anchor
/// - p1: another anchor in the diagonal position of `p0`
/// - Note: `p0` you pass does not have to be minimum point of the box.
/// `p1` does not have to be maximum point of the box. The initializer will
/// automatically adjust the order of `p0` and `p1` to make sure `p0` is the
/// minimum point of the box and `p1` is the maximum point of the box.
@inlinable
public init(_ p0: V, _ p1: V) {
self.init(p0: p0, p1: p1)
}
}
extension KDBox {
@inlinable
@inline(__always)
var diagnalVector: V {
return p1 - p0
}
@inlinable
static var zero: Self {
return Self(uncheckedP0: .zero, uncheckedP1: .zero)
}
@inlinable
@inline(__always)
var center: V { (p1 + p0) / 2.0 }
/// Test if the box contains a point.
/// - Parameter point: N dimensional point
/// - Returns: `true` if the box contains the point, `false` otherwise.
/// The boundary test is similar to ..< operator.
@inlinable
@inline(__always)
func contains(_ point: V) -> Bool {
// let mask =
return !any((p0 .> point) .| (point .>= p1))
//mask == .init(repeating: false)
// equivalent to:
// for i in point.indices {
// if p0[i] > point[i] || point[i] >= p1[i] {
// return false
// }
// }
// return true
}
}
extension KDBox {
@inlinable func getCorner(of direction: Int) -> V {
var mask = SIMDMask()
for i in 0..> i) & 0b1) == 1
}
return p0.replacing(with: p1, where: mask)
// equivalent to:
// var corner = V.zero
// for i in 0..> i) & 0b1) == 1 ? p1[i] : p0[i]
// }
// return p0.replacing(with: p1, where: mask) //corner
}
// @inlinable public var debugDescription: String {
// return "[\(p0), \(p1)]"
// }
}
extension KDBox {
/// Get the small box that contains a list points and guarantees the box's size is at least 1x..x1.
///
/// - Parameter points: The points to be covered.
/// - Returns: The box that contains all the points.
@inlinable public static func cover(of points: [V]) -> Self {
var _p0 = points[0]
var _p1 = points[0]
for p in points {
// let mask1 = p .< _p0
// let mask2 = p .>= _p1
// _p0 = _p0.replacing(with: p, where: mask1)
// _p1 = _p1.replacing(with: p + 1, where: mask2)
_p0 = pointwiseMin(p, _p0)
_p1 = pointwiseMax(p, _p1)
// for i in p.indices {
// if p[i] < _p0[i] {
// _p0[i] = p[i]
// }
// if p[i] >= _p1[i] {
// _p1[i] = p[i] + 1
// }
// }
}
#if DEBUG
let _box = Self(_p0, _p1)
assert(
points.allSatisfy { p in
_box.contains(p)
})
#endif
return Self(_p0, _p1)
}
@inlinable public static func cover(of points: UnsafeArray) -> Self {
var _p0 = points[0]
var _p1 = points[0]
for pi in 0.. _p1[i] {
// _p1[i] = p[i] + 1
// }
// }
}
#if DEBUG
let testBox = Self(_p0, _p1 + 1)
for i in points.range {
assert(testBox.contains(points[i]))
}
#endif
return Self(_p0, _p1 + 1)
}
}
================================================
FILE: Sources/ForceSimulation/KDTree/KDTree.swift
================================================
/// A node in NDTree
/// - Note: `NDTree` is a generic type that can be used in any dimension.
/// `NDTree` is a value type.
public struct KDTree
where
Vector: SimulatableVector & L2NormCalculatable,
Delegate: KDTreeDelegate
{
public typealias NodeIndex = Delegate.NodeID
public typealias Direction = Int
public typealias Box = KDBox
public var box: Box
public var children: [KDTree]?
public var nodePosition: Vector?
public var nodeIndices: [NodeIndex]
@inlinable var clusterDistanceSquared: Vector.Scalar {
return Vector.clusterDistanceSquared
}
public var delegate: Delegate
@inlinable
public init(
box: Box,
// clusterDistanceSquared: Vector.Scalar,
spawnedDelegateBeingConsumed: Delegate
) {
self.box = box
self.nodeIndices = []
self.delegate = spawnedDelegateBeingConsumed
}
@inlinable
init(
box: Box,
spawnedDelegateBeingConsumed: Delegate,
childrenBeingConsumed: [KDTree]
) {
self.box = box
self.nodeIndices = []
self.delegate = spawnedDelegateBeingConsumed
self.children = childrenBeingConsumed
}
@inlinable
static var directionCount: Int { 1 << Vector.scalarCount }
@inlinable
mutating func cover(_ point: Vector) {
if box.contains(point) { return }
repeat {
let direction = getIndexInChildren(point, relativeTo: box.p0)
expand(towards: direction)
} while !box.contains(point)
}
/// Get the index of the child that contains the point.
///
/// **Complexity**: `O(n*(2^n))`, where `n` is the dimension of the vector.
@inlinable
func getIndexInChildren(_ point: Vector, relativeTo originalPoint: Vector) -> Int {
var index = 0
let mask = point .>= originalPoint
for i in 0..]()
result.reserveCapacity(Self.directionCount)
// let center = newRootBox.center
for j in 0..> i) & 0b1
// TODO: use simd mask
if isOnTheHigherRange != 0 {
__box.p0[i] = _corner[i]
} else {
__box.p1[i] = _corner[i]
}
}
result.append(
Self(
box: __box,
// clusterDistanceSquared: clusterDistanceSquared,
spawnedDelegateBeingConsumed: j != nailedDirection ? self.delegate : spawned
)
)
}
// result[nailedDirection] = tempSelf
self = Self(
box: newRootBox,
spawnedDelegateBeingConsumed: self.delegate,
childrenBeingConsumed: result
)
}
@inlinable
public mutating func add(_ nodeIndex: NodeIndex, at point: Vector) {
cover(point)
addWithoutCover(nodeIndex, at: point)
}
@inlinable
public mutating func addWithoutCover(_ nodeIndex: NodeIndex, at point: Vector) {
defer {
delegate.didAddNode(nodeIndex, at: point)
}
guard children != nil else {
if nodePosition == nil {
nodeIndices.append(nodeIndex)
nodePosition = point
return
} else if nodePosition!.distanceSquared(to: point) < clusterDistanceSquared {
// the condition (nodePosition == point) is mostly only true when the tree is initialized
// hence omitted
nodeIndices.append(nodeIndex)
return
} else {
var spawnedChildren = [KDTree]()
spawnedChildren.reserveCapacity(Self.directionCount)
let spawendDelegate = self.delegate.spawn()
let center = box.center
for j in 0..> i) & 0b1
// TODO: use simd mask
if isOnTheHigherRange != 0 {
__box.p0[i] = center[i]
} else {
__box.p1[i] = center[i]
}
}
spawnedChildren.append(
Self(
box: __box,
// clusterDistanceSquared: clusterDistanceSquared,
spawnedDelegateBeingConsumed: spawendDelegate
)
)
}
if let nodePosition {
let direction = getIndexInChildren(nodePosition, relativeTo: box.center)
spawnedChildren[direction].nodeIndices = self.nodeIndices
spawnedChildren[direction].nodePosition = self.nodePosition
spawnedChildren[direction].delegate = self.delegate
self.nodeIndices = []
self.nodePosition = nil
// TODO: Consume
}
let directionOfNewNode = getIndexInChildren(point, relativeTo: box.center)
spawnedChildren[directionOfNewNode].addWithoutCover(nodeIndex, at: point)
self.children = spawnedChildren
return
}
}
let directionOfNewNode = getIndexInChildren(point, relativeTo: box.center)
self.children![directionOfNewNode].addWithoutCover(nodeIndex, at: point)
return
}
}
extension KDTree where Delegate.NodeID == Int {
/// Initialize a KDTree with a list of points and a key path to the vector.
///
/// - Parameters:
/// - points: A list of points. The points are only used to calculate the covering box. You should still call `add` to add the points to the tree.
/// - clusterDistance: If 2 points are close enough, they will be clustered into the same leaf node.
/// - buildRootDelegate: A closure that tells the tree how to initialize the data you want to store in the rootPointer.
/// The closure is called only once. The `NDTreeDelegate` will then be created in children tree nods by calling `spawn` on the rootPointer delegate.
@inlinable
public init(
covering points: [Vector],
buildRootDelegate: () -> Delegate
) {
let coveringBox = Box.cover(of: points)
self.init(
box: coveringBox, spawnedDelegateBeingConsumed: buildRootDelegate()
)
for i in points.indices {
add(i, at: points[i])
}
}
@inlinable
public init(
covering points: UnsafeArray,
buildRootDelegate: () -> Delegate
) {
let coveringBox = Box.cover(of: points)
self.init(
box: coveringBox, spawnedDelegateBeingConsumed: buildRootDelegate()
)
for i in 0..,
rootDelegate: @autoclosure () -> Delegate
) {
let coveringBox = Box.cover(of: points)
self.init(
box: coveringBox, spawnedDelegateBeingConsumed: rootDelegate()
)
for i in 0..(
// covering points: [T],
// keyPath: KeyPath,
// buildRootDelegate: () -> Delegate
// ) {
// let coveringBox = Box.cover(of: points, keyPath: keyPath)
// self.init(
// box: coveringBox, clusterDistance: clusterDistance, buildRootDelegate: buildRootDelegate
// )
// for i in points.indices {
// add(i, at: points[i][keyPath: keyPath])
// }
// }
}
extension KDTree {
/// The bounding box of the current node
@inlinable public var extent: Box { box }
/// Returns true is the current tree node is leaf.
///
/// Does not guarantee that the tree node has point in it.
@inlinable public var isLeaf: Bool { children == nil }
/// Returns true is the current tree node is internal.
///
/// Internal tree node are always empty and do not contain any points.
@inlinable public var isInternalNode: Bool { children != nil }
/// Returns true is the current tree node is leaf and has point in it.
@inlinable public var isFilledLeaf: Bool { nodePosition != nil }
/// Returns true is the current tree node is leaf and does not have point in it.
@inlinable public var isEmptyLeaf: Bool { nodePosition == nil }
/// Visit the tree in pre-order.
///
/// - Parameter shouldVisitChildren: a closure that returns a boolean value indicating whether should continue to visit children.
@inlinable public mutating func visit(
shouldVisitChildren: (inout KDTree) -> Bool
) {
if shouldVisitChildren(&self) && children != nil {
// this is an internal node
for i in children!.indices {
children![i].visit(shouldVisitChildren: shouldVisitChildren)
}
}
}
}
================================================
FILE: Sources/ForceSimulation/KDTree/KDTreeDelegate.swift
================================================
//
// NDTree.swift
//
//
// Created by li3zhen1 on 10/14/23.
//
/// The data structure carried by a node of NDTree.
///
/// It receives notifications when a node is added or removed on a node,
/// regardless of whether the node is internal or leaf.
/// It is designed to calculate properties like a box's center of mass.
///
/// When implementing your delegates, ensure they
/// are value types to enable memberwise copy.
public protocol KDTreeDelegate {
associatedtype NodeID: Hashable
associatedtype Vector: SIMD where Vector.Scalar: FloatingPoint & ExpressibleByFloatLiteral
/// Called when a node is added on a node, regardless of whether the node is internal or leaf.
///
/// If you add `n` points to the root, this method will be called `n` times in the root delegate,
/// although it is probably not containing points now.
/// - Parameters:
/// - node: The nodeID of the node that is added.
/// - position: The position of the node that is added.
@inlinable mutating func didAddNode(_ node: NodeID, at position: Vector)
/// Called when a node is removed on a node, regardless of whether the node is internal or leaf.
@inlinable mutating func didRemoveNode(_ node: NodeID, at position: Vector)
/// Copy object.
///
/// This method is called when the root box is not large enough to cover the new nodes.
// @inlinable func copy() -> Self
/// Create new object with properties set to initial value as if the box is empty.
///
/// However, you can still carry something like a closure to get information from outside.
/// This method is called when a leaf box is splited due to the insertion of a new node in this box.
@inlinable func spawn() -> Self
}
================================================
FILE: Sources/ForceSimulation/KDTree/KDTreeNode.swift
================================================
public struct KDTreeNode
where
Vector: SimulatableVector & L2NormCalculatable,
Delegate: KDTreeDelegate
{
public var box: KDBox
public var nodePosition: Vector
public var childrenBufferPointer: UnsafeMutablePointer?
@usableFromInline
internal var nodeIndices: NodeIndex?
public var delegate: Delegate
@inlinable
init(
nodeIndices: NodeIndex?,
childrenBufferPointer: UnsafeMutablePointer?,
delegate: Delegate,
box: KDBox,
nodePosition: Vector = .zero
) {
self.childrenBufferPointer = childrenBufferPointer
self.nodeIndices = nodeIndices
self.delegate = delegate
self.box = box
self.nodePosition = nodePosition
}
@inlinable
mutating public func disposeNodeIndices() {
nodeIndices?.dispose()
nodeIndices = nil
}
}
extension KDTreeNode {
@usableFromInline
struct NodeIndex: Disposable {
@usableFromInline
var index: Int
@usableFromInline
var next: UnsafeMutablePointer?
}
}
extension KDTreeNode.NodeIndex {
@inlinable
internal init(
nodeIndex: Int
) {
self.index = nodeIndex
self.next = nil
}
@inlinable
internal init(
_ nodeIndex: Int
) {
self.index = nodeIndex
self.next = nil
}
@inlinable
internal mutating func append(nodeIndex: Int) {
if let next {
next.pointee.append(nodeIndex: nodeIndex)
} else {
next = .allocate(capacity: 1)
next!.initialize(to: .init(nodeIndex: nodeIndex))
// next!.pointee = .init(nodeIndex: nodeIndex)
}
}
@inlinable
internal func dispose() {
if let next {
next.pointee.dispose()
next.deallocate()
}
}
@inlinable
internal func contains(_ nodeIndex: Int) -> Bool {
if index == nodeIndex { return true }
if let next {
return next.pointee.contains(nodeIndex)
} else {
return false
}
}
@inlinable
internal func forEach(_ body: (Int) -> Void) {
body(index)
if let next {
next.pointee.forEach(body)
}
}
}
extension KDTreeNode {
/// Returns true is the current tree node is leaf.
///
/// Does not guarantee that the tree node has point in it.
@inlinable public var isLeaf: Bool { childrenBufferPointer == nil }
/// Returns true is the current tree node is internal.
///
/// Internal tree node are always empty and do not contain any points.
@inlinable public var isInternalNode: Bool { childrenBufferPointer != nil }
/// Returns true is the current tree node is leaf and has point in it.
@inlinable public var isFilledLeaf: Bool { nodeIndices != nil }
/// Returns true is the current tree node is leaf and does not have point in it.
@inlinable public var isEmptyLeaf: Bool { nodeIndices == nil }
/// Visit the tree in pre-order.
///
/// - Parameter shouldVisitChildren: a closure that returns a boolean value indicating whether should continue to visit children.
@inlinable public mutating func visit(
shouldVisitChildren: (inout KDTreeNode) -> Bool
) {
if shouldVisitChildren(&self) && childrenBufferPointer != nil {
// this is an internal node
for i in 0...directionCount {
childrenBufferPointer![i].visit(shouldVisitChildren: shouldVisitChildren)
}
}
}
/// Returns an array of point indices in the tree node.
@inlinable
public var containedIndices: [Int] {
guard isFilledLeaf else { return [] }
var result: [Int] = []
nodeIndices!.forEach { result.append($0) }
return result
}
@inlinable
static func zeroWithDelegate(_ delegate: Delegate) -> Self {
return Self(
nodeIndices: nil,
childrenBufferPointer: nil,
delegate: delegate,
box: .zero,
nodePosition: .zero
)
}
}
================================================
FILE: Sources/ForceSimulation/Kinetics.swift
================================================
/// A class that holds the state of the simulation, which
/// includes the positions, velocities of the nodes.
public struct Kinetics
where Vector: SimulatableVector & L2NormCalculatable {
/// The position of points stored in simulation.
///
/// Ordered as the nodeIds you passed in when initializing simulation.
/// They are always updated.
/// Exposed publicly so examples & clients can read out the latest positions.
public var position: UnsafeArray
// public var positionBufferPointer: UnsafeMutablePointer
/// The velocities of points stored in simulation.
///
/// Ordered as the nodeIds you passed in when initializing simulation.
/// They are always updated.
@usableFromInline
package var velocity: UnsafeArray
// public var velocityBufferPointer: UnsafeMutablePointer
/// The fixed positions of points stored in simulation.
///
/// Ordered as the nodeIds you passed in when initializing simulation.
/// They are always updated.
@usableFromInline
package var fixation: UnsafeArray
public var validCount: Int
public var alpha: Vector.Scalar
public let alphaMin: Vector.Scalar
public let alphaDecay: Vector.Scalar
public let alphaTarget: Vector.Scalar
public let velocityDecay: Vector.Scalar
@usableFromInline
var randomGenerator: Vector.Scalar.Generator
@usableFromInline
package let links: [EdgeID]
// public var validRanges: [Range]
// public var validRanges: Range
@inlinable
package var range: Range {
return 0..],
initialAlpha: Vector.Scalar,
alphaMin: Vector.Scalar,
alphaDecay: Vector.Scalar,
alphaTarget: Vector.Scalar,
velocityDecay: Vector.Scalar,
position: [Vector],
velocity: [Vector],
fixation: [Vector?]
) {
self.links = links
// self.initializedAlpha = initialAlpha
self.alpha = initialAlpha
self.alphaMin = alphaMin
self.alphaDecay = alphaDecay
self.alphaTarget = alphaTarget
self.velocityDecay = velocityDecay
let count = position.count
self.validCount = count
self.position = .createBuffer(moving: position, fillingWithIfFailed: .zero)
self.velocity = .createBuffer(moving: velocity, fillingWithIfFailed: .zero)
self.fixation = .createBuffer(moving: fixation, fillingWithIfFailed: nil)
self.randomGenerator = .init()
}
@inlinable
package static var empty: Kinetics {
Kinetics(
links: [],
initialAlpha: 0,
alphaMin: 0,
alphaDecay: 0,
alphaTarget: 0,
velocityDecay: 0,
position: [],
velocity: [],
fixation: []
)
}
}
extension Kinetics {
@inlinable
@inline(__always)
func updatePositions() {
for i in range {
if let fix = fixation[i] {
position[i] = fix
} else {
velocity[i] *= velocityDecay
position[i] += velocity[i]
}
}
}
@inlinable
@inline(__always)
mutating func updateAlpha() {
alpha += alphaTarget - alpha * alphaDecay
}
}
public typealias Kinetics2D = Kinetics>
public typealias Kinetics3D = Kinetics>
================================================
FILE: Sources/ForceSimulation/Simulation.swift
================================================
@usableFromInline
package enum Ticks: Sendable {
case untilReachingAlpha(Scalar?)
case iteration(Int)
@inlinable
public static var zero: Self {
.iteration(0)
}
@inlinable
public static var untilStable: Self {
.untilReachingAlpha(nil)
}
}
/// An any-dimensional force simulation.
/// The points are placed in a space where you use a SIMD data structure
/// to describe their coordinates.
public final class Simulation: @unchecked Sendable
where Vector: SimulatableVector & L2NormCalculatable, ForceField: ForceProtocol {
@usableFromInline
var forceField: ForceField
public var kinetics: Kinetics
/// Create a new simulation.
///
/// - Parameters:
/// - nodeCount: Count of the nodes. Force simulation calculate them by order once created.
/// - links: The links between nodes.
/// - forceField: The force field that drives the simulation. The simulation takes ownership of the force field.
/// - alpha: Initial alpha value, determines how "active" the simulation is.
/// - alphaMin: The minimum alpha value. The simulation stops when alpha is less than this value.
/// - alphaDecay: The larger the value, the faster the simulation converges to the final result.
/// - alphaTarget: The alpha value the simulation converges to.
/// - velocityDecay: A multiplier for the velocity of the nodes in Velocity Verlet integration. The position of the nodes is updated by the formula `x += v * velocityDecay`.
// @inlinable
// public init(
// nodeCount: Int,
// links: [EdgeID],
// forceField: ForceField,
// initialAlpha: Vector.Scalar = 1,
// alphaMin: Vector.Scalar = 1e-2,
// alphaDecay: Vector.Scalar = 2e-3,
// alphaTarget: Vector.Scalar = 0.0,
// velocityDecay: Vector.Scalar = 0.6
// ) {
// self.kinetics = .createZeros(
// links: links,
// initialAlpha: initialAlpha,
// alphaMin: alphaMin,
// alphaDecay: alphaDecay,
// alphaTarget: alphaTarget,
// velocityDecay: velocityDecay,
// count: nodeCount
// )
// // self.kinetics.jigglePosition()
// forceField.bindKinetics(self.kinetics)
// self.forceField = forceField
// }
/// Create a new simulation.
///
/// - Parameters:
/// - nodeCount: Count of the nodes. Force simulation calculate them by order once created.
/// - links: The links between nodes.
/// - forceField: The force field that drives the simulation. The simulation takes ownership of the force field.
/// - alpha: Initial alpha value, determines how "active" the simulation is.
/// - alphaMin: The minimum alpha value. The simulation stops when alpha is less than this value.
/// - alphaDecay: The larger the value, the faster the simulation converges to the final result.
/// - alphaTarget: The alpha value the simulation converges to.
/// - velocityDecay: A multiplier for the velocity of the nodes in Velocity Verlet integration. The position of the nodes is updated by the formula `x += v * velocityDecay`.
@inlinable
public init(
nodeCount: Int,
links: [EdgeID],
forceField: consuming ForceField,
initialAlpha: Vector.Scalar = 1,
alphaMin: Vector.Scalar = 1e-3,
alphaDecay: Vector.Scalar = 1e-2,
alphaTarget: Vector.Scalar = 0.0,
velocityDecay: Vector.Scalar = 0.6,
position: [Vector]? = nil,
velocity: [Vector]? = nil,
fixation: [Vector?]? = nil
) {
self.kinetics = Kinetics(
links: links,
initialAlpha: initialAlpha,
alphaMin: alphaMin,
alphaDecay: alphaDecay,
alphaTarget: alphaTarget,
velocityDecay: velocityDecay,
position: position ?? Array(repeating: .zero, count: nodeCount),
velocity: velocity ?? Array(repeating: .zero, count: nodeCount),
fixation: fixation ?? Array(repeating: nil, count: nodeCount)
)
// self.kinetics.jigglePosition()
forceField.bindKinetics(self.kinetics)
self.forceField = forceField
}
/// Run a number of iterations of ticks.
@inlinable
@inline(__always)
public func tick(iterations: UInt = 1) {
// print(self.kinetics.alpha, self.kinetics.alphaMin)
guard self.kinetics.alpha >= self.kinetics.alphaMin else { return }
for _ in 0..) {
switch ticks {
case .iteration(let count):
self.tick(iterations: UInt(count))
case .untilReachingAlpha(let alpha):
let alpha = alpha ?? self.kinetics.alphaMin
while self.kinetics.alpha >= alpha {
self.kinetics.updateAlpha()
self.forceField.apply(to: &self.kinetics)
self.kinetics.updatePositions()
}
}
}
@inlinable
deinit {
self.forceField.dispose()
}
}
public typealias Simulation2D = Simulation, ForceField>
where ForceField: ForceProtocol>
public typealias Simulation3D = Simulation, ForceField>
where ForceField: ForceProtocol>
================================================
FILE: Sources/ForceSimulation/Utils/AttributeDescriptor.swift
================================================
public enum AttributeDescriptor {
case varied((Int) -> T)
case constant(T)
}
extension AttributeDescriptor: Equatable where T: Equatable {
@inlinable
public static func == (lhs: AttributeDescriptor, rhs: AttributeDescriptor) -> Bool {
switch (lhs, rhs) {
case (.constant(let l), .constant(let r)):
return l == r
default:
return false
}
}
}
extension AttributeDescriptor {
@inlinable
func calculate(for count: Int) -> [T] {
switch self {
case .constant(let m):
return [T](repeating: m, count: count)
case .varied(let radiusProvider):
return (0.. UnsafeArray where T: Numeric {
switch self {
case .constant(let m):
return UnsafeArray.createBuffer(
withHeader: count,
count: count,
initialValue: m
)
case .varied(let valueProvider):
let array = UnsafeArray.createBuffer(withHeader: count, count: count) {
valueProvider($0)
}
return array
}
}
}
================================================
FILE: Sources/ForceSimulation/Utils/Disposable.swift
================================================
public protocol Disposable {
@inlinable
mutating func dispose()
}
extension UnsafeMutablePointer where Pointee: Disposable {
/// Disposes the underlying memory block and
/// deallocates the memory block previously allocated at this pointer.
///
/// This pointer must be a pointer to the start of a previously allocated memory
/// block. The memory must not be initialized or `Pointee` must be a trivial type.
@inlinable
public func dispose() {
self.pointee.dispose()
self.deallocate()
}
}
================================================
FILE: Sources/ForceSimulation/Utils/EdgeID.swift
================================================
/// A Hashable identifier for an edge.
///
/// It’s a utility type for preserving `Hashable` conformance.
public struct EdgeID: Hashable {
public var source: NodeID
public var target: NodeID
public init(source: NodeID, target: NodeID) {
self.source = source
self.target = target
}
}
extension EdgeID {
public init(_ source: NodeID, _ target: NodeID) {
self.source = source
self.target = target
}
}
================================================
FILE: Sources/ForceSimulation/Utils/LinearCongruentialGenerator.swift
================================================
// TODO: https://forums.swift.org/t/deterministic-randomness-in-swift/20835/5
/// A random number generator that generates deterministic random numbers.
public protocol DeterministicRandomGenerator {
associatedtype Scalar where Scalar: FloatingPoint & ExpressibleByFloatLiteral
associatedtype OverflowingInteger: FixedWidthInteger & UnsignedInteger
@inlinable mutating func next() -> Scalar
@inlinable init(seed: OverflowingInteger)
@inlinable init()
}
/// A random number generator that generates deterministic random numbers for `Double`.
public struct DoubleLinearCongruentialGenerator: DeterministicRandomGenerator {
public typealias OverflowingInteger = UInt32
@usableFromInline internal static let a: UInt32 = 1_664_525
@usableFromInline internal static let c: UInt32 = 1_013_904_223
@usableFromInline internal var s: UInt32 = 1
@usableFromInline internal static let m: Double = 4_294_967_296
@inlinable public mutating func next() -> Double {
// Perform the linear congruential generation with integer arithmetic.
// The overflow addition and multiplication automatically wrap around,
// thus imitating the modulo operation.
s = (Self.a &* s) &+ Self.c
// Convert the result to Double and divide by m to normalize it.
return Double(s) / Self.m
}
@inlinable public init(seed: OverflowingInteger) {
self.s = 1 //seed
}
@inlinable public init() {
self.init(seed: 1)
}
}
/// A random number generator that generates deterministic random numbers for `Float`.
public struct FloatLinearCongruentialGenerator: DeterministicRandomGenerator {
public typealias OverflowingInteger = UInt16
@usableFromInline internal static let a: UInt16 = 75
@usableFromInline internal static let c: UInt16 = 74
@usableFromInline internal var s: UInt16 = 1
@usableFromInline internal static let m: Float = 65537.0
@inlinable public mutating func next() -> Float {
// Perform the linear congruential generation with integer arithmetic.
// The overflow addition and multiplication automatically wrap around.
s = (Self.a &* s) &+ Self.c
// Convert the result to Float and divide by m to normalize it.
return Float(s) / Self.m
}
@inlinable public init(seed: OverflowingInteger) {
self.s = seed
}
@inlinable public init() {
self.init(seed: 1)
}
}
/// A floating point type that can be generated with a deterministic random number generator ``DeterministicRandomGenerator``.
public protocol HasDeterministicRandomGenerator: FloatingPoint & ExpressibleByFloatLiteral {
associatedtype Generator: DeterministicRandomGenerator where Generator.Scalar == Self
}
extension Double: HasDeterministicRandomGenerator {
public typealias Generator = DoubleLinearCongruentialGenerator
}
extension Float: HasDeterministicRandomGenerator {
public typealias Generator = FloatLinearCongruentialGenerator
}
extension HasDeterministicRandomGenerator {
@inlinable
static var jigglingScale: Self {
return 1e-5
}
@inlinable
public func jiggled(by: UnsafeMutablePointer) -> Self {
if self == .zero || self.isNaN {
return (by.pointee.next() - 0.5) * Self.jigglingScale
}
return self
}
}
================================================
FILE: Sources/ForceSimulation/Utils/SimulatableVector.swift
================================================
/// A protocol for vectors that can be jiggled, and has a certain precision for
/// simulation — so zero vectors could be altered
/// into a small random non-zero vector, and then the force simulation could be
/// could be numerically stable.
public protocol SimulatableVector: SIMD
where Scalar: FloatingPoint & HasDeterministicRandomGenerator {
@inlinable
static var clusterDistance: Scalar { get }
@inlinable
static var clusterDistanceSquared: Scalar { get }
@inlinable
func jiggled(by: UnsafeMutablePointer) -> Self
}
// extension SimulatableVector {
// /// If the vector is zero, returns a vector with the same magnitude as `self` but pointing in a random direction,
// /// otherwise returns `self`.
// @inlinable
// public func jiggled() -> Self {
// var result = Self.zero
// for i in indices {
// result[i] = self[i].jiggled()
// }
// return result
// }
// }
/// A protocol for vectors that can be calculated with L2 norms, i.e. Euclidean distance.
public protocol L2NormCalculatable: SIMD where Scalar: FloatingPoint {
@inlinable
func distanceSquared(to point: Self) -> Scalar
@inlinable
func distance(to point: Self) -> Scalar
@inlinable
func lengthSquared() -> Scalar
@inlinable
func length() -> Scalar
}
extension SIMD2: SimulatableVector where Scalar: FloatingPoint & HasDeterministicRandomGenerator {
@inlinable
public static var clusterDistance: Scalar {
return 1e-5
}
@inlinable
public static var clusterDistanceSquared: Scalar {
return clusterDistance * clusterDistance
}
@inlinable
public func jiggled(by: UnsafeMutablePointer) -> Self {
return .init(x: self.x.jiggled(by: by), y: self.y.jiggled(by: by))
}
}
extension SIMD3: SimulatableVector where Scalar: FloatingPoint & HasDeterministicRandomGenerator {
@inlinable
public static var clusterDistance: Scalar {
return 1e-5
}
@inlinable
public static var clusterDistanceSquared: Scalar {
return clusterDistance * clusterDistance
}
@inlinable
public func jiggled(by: UnsafeMutablePointer) -> Self {
return .init(
x: self.x.jiggled(by: by),
y: self.y.jiggled(by: by),
z: self.z.jiggled(by: by)
)
}
}
#if canImport(simd)
import simd
extension SIMD2: L2NormCalculatable where Scalar == Double {
@inlinable
public func distanceSquared(to point: SIMD2) -> Scalar {
return simd_distance_squared(self, point)
}
@inlinable
public func distance(to point: SIMD2) -> Scalar {
return simd_distance(self, point)
}
@inlinable
public func lengthSquared() -> Scalar {
return simd_length_squared(self)
}
@inlinable
public func length() -> Scalar {
return simd_fast_length(self)
}
}
extension SIMD3: L2NormCalculatable where Scalar == Float {
@inlinable
public func distanceSquared(to point: SIMD3) -> Scalar {
return simd_distance_squared(self, point)
}
@inlinable
public func distance(to point: SIMD3) -> Scalar {
return simd_distance(self, point)
}
@inlinable
public func lengthSquared() -> Scalar {
return simd_length_squared(self)
}
@inlinable
public func length() -> Scalar {
return simd_fast_length(self)
}
}
#endif
================================================
FILE: Sources/ForceSimulation/Utils/UnsafeArray.swift
================================================
/// A wrapper of managed buffer that stores an array of elements.
@_eagerMove
public final class UnsafeArray: ManagedBuffer {
@inlinable
class func createBuffer(withHeader header: Int, count: Int, initialValue: Element)
-> UnsafeArray
{
let buffer = self.create(minimumCapacity: count) { _ in header }
buffer.withUnsafeMutablePointerToElements {
$0.initialize(repeating: initialValue, count: count)
}
return unsafeDowncast(buffer, to: UnsafeArray.self)
}
@inlinable
class func createBuffer(withHeader header: Int, count: Int, initializer: (Int) -> Element)
-> UnsafeArray
{
let buffer = self.create(minimumCapacity: count) { _ in header }
buffer.withUnsafeMutablePointerToElements {
for i in 0..,
movingCount: Int,
fillingExcessiveBufferWith initialValue: Element
) -> UnsafeArray {
let buffer = self.create(minimumCapacity: count) { _ in header }
buffer.withUnsafeMutablePointerToElements {
$0.moveInitialize(from: moving, count: movingCount)
$0.advanced(by: movingCount).initialize(
repeating: initialValue,
count: count - movingCount
)
}
return unsafeDowncast(buffer, to: UnsafeArray.self)
}
@inlinable
class func createBuffer(
moving array: [Element],
fillingWithIfFailed element: Element
) -> UnsafeArray {
let buffer = self.create(minimumCapacity: array.count) { _ in array.count }
array.withUnsafeBufferPointer { bufferPtr in
if let baseAddr = bufferPtr.baseAddress {
buffer.withUnsafeMutablePointerToElements {
$0.moveInitialize(from: .init(mutating: baseAddr), count: array.count)
}
}
else {
buffer.withUnsafeMutablePointerToElements {
for i in 0..