Skip to content

Commit

Permalink
Adjust callbacks. Removed ifpossible and ifplace
Browse files Browse the repository at this point in the history
  • Loading branch information
James Zingel committed Oct 15, 2023
1 parent beaa194 commit 6b83164
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 80 deletions.
5 changes: 4 additions & 1 deletion go.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ using Lonpos
prob = loadproblem("./problems/simple.toml")
live(prob)

println(solve(prob))
println(solve(prob, threaded=true))

prob = loadproblem("./problems/original.toml")
live(prob)
#live(prob)
1 change: 1 addition & 0 deletions src/Lonpos.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const INVALID_BOARD = 13

# Includes
include("structs.jl")
include("common.jl")
include("core.jl")
include("run.jl")

Expand Down
63 changes: 63 additions & 0 deletions src/common.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Methods using the structures defined in `structs.jl`

using TOML

# Merge all the results into one
function merge(results::Vector{Result})::Result
combined = Result()
combined.total_placements = sum([r.total_placements for r in results])
combined.successful_placements = sum([r.successful_placements for r in results])
combined.dead_ends = sum([r.dead_ends for r in results])
combined.best_times = sum([r.best_times for r in results])
combined.solutions = vcat([r.solutions for r in results]...)
return combined
end

# If a given problem is consistent might have a solution
function consistent(prob::Problem)::Bool
boardgaps = prod(size(prob.board.shape)) - (sum(prob.board.shape)÷13)
piecegaps = sum([sum(ifelse.(p.shape .!= 0, 1, 0)) for p in prob.pieces])
return boardgaps == piecegaps
end

# Load a problem from disk
function loadproblem(fname::AbstractString)::Problem
desc = TOML.parse(read(fname, String))

if (!haskey(desc, "board")) || (!haskey(desc, "pieces"))
return throw(ArgumentError("Missing board or piece description"))
end

board = newboard(desc["board"])
pieces = [newpiece(map, i) for (i, map) in enumerate(desc["pieces"])]

# check that empty space of the board corresponds to the size of the pieces
prob = Problem(pieces, board)
if !consistent(prob)
@warn "Problem is inconsistent. The number of gaps in the board ($(prod(size(prob.board.shape)) - (sum(prob.board.shape)÷13))) is different to the total size of all pieces ($(sum([sum(ifelse.(p.shape .!= 0, 1, 0)) for p in prob.pieces])))."
end
return prob
end

# Process the :from callback
function advance!(c::Callback, from::Symbol, vars)
if time() > c.dt + c.lasttime # update
lock(c.reentractlocker) do
c.lasttime = time()
if from == :ifbest
c.ifbest(vars...)
elseif from == :ifsolution
c.ifsolution(vars...)
else
@error "Unknown symbol!" from
end
end
end
end

# Default callback
function defaultcallback()::Callback
# Threadsafe default
return Callback()
end

52 changes: 26 additions & 26 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,8 @@ function compute(i::Integer, j::Integer, board::Board, perms::Vector{Vector{Piec

res.total_placements += 1
res.successful_placements += poss
callbacks.forallpieces(poss, res) # callback for if the piece is placed

if poss
callbacks.ifpossible(b) # callback for if the piece is possible
remaining = copy(perms)
deleteat!(remaining, p_inx)

Expand All @@ -128,13 +126,13 @@ function compute(i::Integer, j::Integer, board::Board, perms::Vector{Vector{Piec
res.best_times = 0
end
res.best_times += 1
callbacks.ifbest(b, res, remaining) # callback for if the piece is the best
advance!(callbacks, :ifbest, (b, res, remaining)) # callback for if the piece is the best
end

if length(remaining) == 0 # We're done!
@debug "Found solution" num=res.best_times b
push!(res.solutions, b)
callbacks.ifsolution(b, res)
advance!(callbacks, :ifsolution, (b, res))
else
compute(i, j, b, remaining, res, callbacks)
# next loop will increment i,j for us
Expand All @@ -154,11 +152,6 @@ function compute(i::Integer, j::Integer, board::Board, perms::Vector{Vector{Piec
return res
end

solve(prob::Problem) = solve(prob, Callback())
function solve(problem::Problem, c::Callback)::Result
perms = create_permutations(problem.pieces)
return compute(1, 1, problem.board, perms, newresult(), c)
end

function distribute(problem::Problem)::Vector{Problem}
# Turn one problem in a group of subproblems which can then be solved
Expand All @@ -184,26 +177,33 @@ function distribute(problem::Problem)::Vector{Problem}
return subproblems
end

function solveparallel(prob::Problem)::Result
# Solve the problem using multithreading
if nthreads() < 2
@warn "Multithreading arguement badly set. Only using 1 thread. Start julia with --threads=n to add more threads or --threads=auto to set automatically."
end

subprobs = distribute(prob)
nprobs = length(subprobs)
results = Vector{Result}(undef, nprobs) # stats for each subproblem to mutate
solve(prob::Problem; threaded=false) = solve(prob, defaultcallback(), threaded=threaded)
function solve(problem::Problem, c::Callback; threaded=false)::Result
if threaded
# Solve the problem using multithreading
if nthreads() < 2
@warn "Multithreading arguement badly set. Only using 1 thread. Start julia with --threads=n to add more threads or --threads=auto to set automatically."
end

subprobs = distribute(problem)
nprobs = length(subprobs)
results = Vector{Result}(undef, nprobs) # stats for each subproblem to mutate

@info "Multithreading with $(nthreads()) threads chugging through $(length(subprobs)) subproblems"

@info "Multithreading with $(nthreads()) threads chugging through $(length(subprobs)) subproblems"
@threads for i = 1:nprobs
subprob = subprobs[i]
results[i] = compute(1, 1, subprob.board, create_permutations(subprob.pieces), Result(), c)
print("Worker $(threadid()) has finished subproblem $i finding $(length(results[i].solutions)) results.\n")
end

@threads for i = 1:nprobs
subprob = subprobs[i]
results[i] = compute(1, 1, subprob.board, create_permutations(subprob.pieces), newresult(), [(x,y)->nothing, (x)->nothing, (x,y,z)->nothing])
print("Worker $(threadid()) has finished subproblem $i finding $(length(results[i].solutions)) results.\n")
# merge results
merged = merge(results)
@info "Found $(length(merged.solutions)) solutions."
return merged
end

# merge results
merged = merge(results)
@info "Found $(length(merged.solutions)) solutions."
return merged
perms = create_permutations(problem.pieces)
return compute(1, 1, problem.board, perms, Result(), c)
end
4 changes: 2 additions & 2 deletions src/run.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ function update_screen(b, res, remain)
end
end

# Solve the board live. Single threaded and slow.
function live(prob::Problem)
"""Solve the board live. Clone of the python version"""

callbacks = Callback(ifbest=update_screen)
callbacks = Callback(; ifbest=update_screen)

println(BOLD("Lonpos Solver v1.1"))
println(prob.board)
Expand Down
72 changes: 21 additions & 51 deletions src/structs.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# d7844

using Crayons
using TOML
using Dates

# update to static in the future
Expand All @@ -20,31 +19,34 @@ struct Problem{T<:Integer}
end

# Contain all the solutions and stats. This is passed between branches
mutable struct Result{T<:Integer}
total_placements::T
successful_placements::T
dead_ends::T
best_fit::T # best number of pieces fitted in the board
best_times::T # number of times the best fit was achieved
mutable struct Result
total_placements::Int
successful_placements::Int
dead_ends::Int
best_fit::Int # best number of pieces fitted in the board
best_times::Int # number of times the best fit was achieved
solutions:: Vector{Board}
tic:: DateTime # problem start time

function Result()
new(0, 0, 0, 1000, 0, Board[], now())
end
end

# Contains our code to callback during solving
struct Callback
# Contains our code to callback printing during solving
mutable struct Callback
reentractlocker::Threads.ReentrantLock # To halt the threads while processing the callback
forallpieces::Any # (possible::Bool, result::Result)
ifpossible::Any # (board::Board)
ifbest::Any # (board::Board, result::Result, remaining:Vector{Piece})
ifsolution::Any # (board::Board, result::Result)
lasttime::Float64 # Last update
dt::Float64 # Min time between updates

function Callback(;
forallpieces=(x,y)->nothing,
ifpossible=(x)->nothing,
ifbest=(x,y,z)->nothing,
ifsolution=(x,y)->nothing,
dt=0.1
)
new(Threads.ReentrantLock(), forallpieces, ifpossible, ifbest, ifsolution)
new(Threads.ReentrantLock(), ifbest, ifsolution, 0.0, dt)
end
end

Expand Down Expand Up @@ -83,43 +85,6 @@ newboard(shp::Matrix{T}) where {T<:Integer} = Board(shp)
newboard(b::Board) = Board(copy(b.shape)) # is a copy
newboard(map::String) = newboard(string_map_to_matrix(map) * INVALID_BOARD)

newresult() = Result(0, 0, 0, 1000, 0, Board[], now())

function merge(results::Vector{Result{T}})::Result{T} where {T<:Integer}
# Merge all the results into one
combined = newresult()
combined.total_placements = sum([r.total_placements for r in results])
combined.successful_placements = sum([r.successful_placements for r in results])
combined.dead_ends = sum([r.dead_ends for r in results])
combined.best_times = sum([r.best_times for r in results])
combined.solutions = vcat([r.solutions for r in results]...)
return combined
end

function consistent(prob::Problem)::Bool
boardgaps = prod(size(prob.board.shape)) - (sum(prob.board.shape)÷13)
piecegaps = sum([sum(ifelse.(p.shape .!= 0, 1, 0)) for p in prob.pieces])
return boardgaps == piecegaps
end

function loadproblem(fname::AbstractString)::Problem
desc = TOML.parse(read(fname, String))

if (!haskey(desc, "board")) || (!haskey(desc, "pieces"))
return throw(ArgumentError("Missing board or piece description"))
end

board = newboard(desc["board"])
pieces = [newpiece(map, i) for (i, map) in enumerate(desc["pieces"])]

# check that empty space of the board corresponds to the size of the pieces
prob = Problem(pieces, board)
if !consistent(prob)
@warn "Problem is inconsistent. The number of gaps in the board ($(prod(size(prob.board.shape)) - (sum(prob.board.shape)÷13))) is different to the total size of all pieces ($(sum([sum(ifelse.(p.shape .!= 0, 1, 0)) for p in prob.pieces])))."
end
return prob
end

Base.:(==)(p::Piece, q::Piece) = p.shape == q.shape
Base.:(==)(b::Board, bb::Board) = b.shape == bb.shape

Expand All @@ -128,6 +93,7 @@ Base.size(p::Piece) = size(p.shape)
Base.size(b::Board) = size(b.shape)
Base.size(b::Board, d::T) where {T<:Integer} = size(b)[d]

# Overload printing methods
const colormap = repeat(
[crayon"bg:(0,0,0)",
crayon"bg:(230,25,75)",
Expand Down Expand Up @@ -202,3 +168,7 @@ function Base.show(io::IO, prob::Problem)
end
print_color_matrix(io, M)
end

function Base.show(io::IO, result::Result)
print(io, "Result with $(length(result.solutions)) solutions. Placed $(result.total_placements) pieces total, with $(result.successful_placements) being successful")
end

0 comments on commit 6b83164

Please sign in to comment.