Skip to content

Commit

Permalink
Merge pull request #57 from valeriyvan/feature/multithreading
Browse files Browse the repository at this point in the history
Add multithreading
  • Loading branch information
valeriyvan authored Oct 8, 2023
2 parents 80a0ebf + 4f97d12 commit 9bf8200
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 28 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ You could try swift-geometrize in action in Telegram bot [Geometrizebot](https:/

## TODO:
* add stroke width for line, polyline and bezier curve;
* multithreading;
multithreading;
* solve dealing with randomness in tests;
* geometrize with predefined or user supplied brush strokes;
* geometrize with characters (on output will be something which could be called ascii art or art produced by [James Cook Type Writer Artist](https://jamescookartwork.com)).
Expand Down
16 changes: 13 additions & 3 deletions Sources/geometrize/Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ func differencePartial(
/// - buffer: The buffer bitmap.
/// - lastScore: The last score.
/// - energyFunction: A function to calculate the energy.
/// - using: The Random number generator to use.
/// - callback: The closure to call with resulting state.
/// - queue: The queue to call callback
/// - Returns: The best state acquired from hill climbing i.e. the one with the lowest energy.
func bestHillClimbState( // swiftlint:disable:this function_parameter_count
shapeCreator: ShapeCreator,
Expand All @@ -207,8 +210,10 @@ func bestHillClimbState( // swiftlint:disable:this function_parameter_count
buffer: inout Bitmap,
lastScore: Double,
energyFunction: EnergyFunction = defaultEnergyFunction,
using generator: inout SplitMix64
) -> State {
using generator: inout SplitMix64,
callback: @escaping (State) -> Void,
queue: DispatchQueue
) {
let state: State = bestRandomState(
shapeCreator: shapeCreator,
alpha: alpha,
Expand All @@ -220,7 +225,7 @@ func bestHillClimbState( // swiftlint:disable:this function_parameter_count
energyFunction: energyFunction,
using: &generator
)
return hillClimb(
let resState = hillClimb(
state: state,
maxAge: age,
target: target,
Expand All @@ -230,6 +235,9 @@ func bestHillClimbState( // swiftlint:disable:this function_parameter_count
energyFunction: energyFunction,
using: &generator
)
queue.async {
callback(resState)
}
}

/// Hill climbing optimization algorithm, attempts to minimize energy (the error/difference).
Expand All @@ -242,6 +250,7 @@ func bestHillClimbState( // swiftlint:disable:this function_parameter_count
/// - buffer: The buffer bitmap.
/// - lastScore: The last score.
/// - energyFunction: An energy function to be used.
/// - using: The Random number generator to use.
/// - Returns: The best state found from hillclimbing.
func hillClimb( // swiftlint:disable:this function_parameter_count
state: State,
Expand Down Expand Up @@ -294,6 +303,7 @@ func hillClimb( // swiftlint:disable:this function_parameter_count
/// - buffer: The buffer bitmap.
/// - lastScore: The last score.
/// - energyFunction: An energy function to be used.
/// - using: The Random number generator to use.
/// - Returns: The best random state i.e. the one with the lowest energy.
private func bestRandomState( // swiftlint:disable:this function_parameter_count
shapeCreator: ShapeCreator,
Expand Down
52 changes: 33 additions & 19 deletions Sources/geometrize/GeometrizeModelHillClimb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,43 @@ class GeometrizeModelHillClimb: GeometrizeModelBase {
// Ensure that the results of the random generation are the same between tasks with identical settings
// The RNG is thread-local and std::async may use a thread pool (which is why this is necessary)
// Note this implementation requires maxThreads to be the same between tasks for each task to produce the same results.
let seed = baseRandomSeed + randomSeedOffset
randomSeedOffset += 1
//seedRandomGenerator(UInt64(seed))

var generator = SplitMix64(seed: UInt64(seed))

let lastScore = lastScore

var buffer: Bitmap = currentBitmap
let state = bestHillClimbState(
shapeCreator: shapeCreator,
alpha: alpha,
n: shapeCount,
age: maxShapeMutations,
target: targetBitmap,
current: currentBitmap,
buffer: &buffer,
lastScore: lastScore,
energyFunction: energyFunction,
using: &generator
)
let concurrentQueue = DispatchQueue(label: "geometrize.concurrent.queue", attributes: .concurrent)
let group = DispatchGroup()
let serialQueue = DispatchQueue(label: "geometrize.serial.queue")
var states: [State] = []

for _ in 0..<maxThreads {
let seed = baseRandomSeed + randomSeedOffset
randomSeedOffset += 1
var generator = SplitMix64(seed: UInt64(seed))
group.enter()
concurrentQueue.async { [targetBitmap, currentBitmap] in
var buffer: Bitmap = currentBitmap
bestHillClimbState(
shapeCreator: shapeCreator,
alpha: alpha,
n: shapeCount,
age: maxShapeMutations,
target: targetBitmap,
current: currentBitmap,
buffer: &buffer,
lastScore: lastScore,
energyFunction: energyFunction,
using: &generator,
callback: { state in
states.append(state)
group.leave()
},
queue: serialQueue
)
}
}
group.wait()

return [state]
return states
}

/// Concurrently runs several optimization sessions tying improving image geometrization by adding a shape to it
Expand Down
6 changes: 5 additions & 1 deletion Sources/geometrize/Int_random.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation

var _randomImplementationReference = _randomImplementation // swiftlint:disable:this identifier_name
// Normally Int._random(in:using) just calls Int._random(in:using).
// Tests could change default implementation e.g. to read pre-generated random numbers from file.

// swiftlint:disable:this identifier_name
var _randomImplementationReference = _randomImplementation

private func _randomImplementation(in range: ClosedRange<Int>, using generator: inout SplitMix64) -> Int {
Int.random(in: range, using: &generator)
Expand Down
4 changes: 0 additions & 4 deletions Sources/geometrize/ShapeCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ public typealias ShapeCreator = (inout SplitMix64) -> any Shape
/// Creates a function for creating instances of Shape. Returned instances should be set up!
/// - Parameters:
/// - types: The types of shapes to create.
/// - xMin: The minimum x coordinate of the shapes created.
/// - yMin: The minimum y coordinate of the shapes created.
/// - xMax: The maximum x coordinate of the shapes created.
/// - yMax: The maximum y coordinate of the shapes created.
/// - Returns: The default shape creator.
public func makeDefaultShapeCreator(types: Set<ShapeType>) -> ShapeCreator {
return { generator in
Expand Down

0 comments on commit 9bf8200

Please sign in to comment.