Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/spatialindex #9

Merged
merged 4 commits into from
Jul 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions Icfpc2023.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module Tests
open System.IO

open Icfpc2023
open Icfpc2023.Scoring
open Xunit

[<Fact>]
Expand Down Expand Up @@ -79,7 +78,7 @@ let ``Scoring yields the expected score for the sample problem from the spec`` (
Volumes = [| 1.0; 1.0; 1.0 |]
}

Assert.Equal(5343.0, CalculateScore problem solution)
Assert.Equal(5343.0, Scoring.CalculateScore problem solution)

[<Fact>]
let ``Stadium detects intersection when blocking musician is right on the line`` () =
Expand All @@ -91,3 +90,19 @@ let ``Stadium detects intersection when blocking musician is right on the line``
}
let blockingMusician = PointD(1100.0, 150.0)
Assert.True(stadium.Contains blockingMusician)

[<Fact>]
let ``CalculateScore for problem 1``(): unit =
let problem = JsonDefs.ReadProblemFromFile(Path.Combine(DirectoryLookup.problemsDir, "1.json"))
let solution = JsonDefs.ReadSolutionFromFile(Path.Combine(DirectoryLookup.solutionsDir, "1.json"))
let score = Scoring.CalculateScore problem solution
let meta = JsonDefs.ReadSolutionMetadataFromFile(Path.Combine(DirectoryLookup.solutionsDir, "1.meta.json"))
Assert.Equal(meta.Score, score)

[<Fact>]
let ``CalculateScore for problem 30``(): unit =
let problem = JsonDefs.ReadProblemFromFile(Path.Combine(DirectoryLookup.problemsDir, "30.json"))
let solution = JsonDefs.ReadSolutionFromFile(Path.Combine(DirectoryLookup.solutionsDir, "30.json"))
let score = Scoring.CalculateScore problem solution
let meta = JsonDefs.ReadSolutionMetadataFromFile(Path.Combine(DirectoryLookup.solutionsDir, "30.meta.json"))
Assert.Equal(meta.Score, score)
52 changes: 52 additions & 0 deletions Icfpc2023/Geometry.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace Icfpc2023

open System

[<Struct>]
type PointD =
| PointD of double * double
Expand Down Expand Up @@ -31,6 +33,8 @@ type Line =

abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) / sqrt((x2 - x1) ** 2.0 + (y2 - y1) ** 2)

#nowarn "3391"

[<Struct>]
type Rectangle =
{
Expand All @@ -40,6 +44,7 @@ type Rectangle =
}
member private this.UpperRight: PointD =
this.BottomLeft + PointD(this.Width, this.Height)

member this.Contains(p: PointD): bool =
let (PointD(x, y)) = p
let (PointD(x0, y0)) = this.BottomLeft
Expand Down Expand Up @@ -101,3 +106,50 @@ type Stadium =
// doesn't exceed the length of the segment
let segment_length = this.Center1.DistanceTo(this.Center2)
closest.DistanceTo(this.Center1) < segment_length && closest.DistanceTo(this.Center2) < segment_length

static member NormalizeVector(v: PointD): PointD =
let (PointD(x, y)) = v
let length = sqrt(x ** 2.0 + y ** 2.0)
PointD(x / length, y / length)

static member RotateVector (radians: double) (v: PointD): PointD =
let (PointD(x, y)) = v
let cos = cos radians
let sin = sin radians
PointD(x * cos - y * sin, x * sin + y * cos)

// https://stackoverflow.com/a/18292964/2684760
static member LineIntersectsRect(struct(a, b), rect: Rectangle): bool =
let (PointD(x1, y1)) = a
let (PointD(x2, y2)) = b

let minX = rect.BottomLeft.X
let maxX = rect.BottomLeft.X + rect.Width
let minY = rect.BottomLeft.Y
let maxY = rect.BottomLeft.Y + rect.Height

let k = (y2 - y1) / (x2 - x1)
let y = k * (minX - x1) + y1
if y > minY && y < maxY then true
else
let y = k * (maxX - x1) + y1
if y > minY && y < maxY then true
else
let x = (minY - y1) / k + x1
if x > minX && x < maxX then true
else
let x = (maxY - y1) / k + x1
if x > minX && x < maxX then true
else false

member this.RectangularPartIntersectsWith(rect: Rectangle): bool =
let radiusVector = Stadium.NormalizeVector(this.Center2 - this.Center1) * this.Radius
let line1 = struct (
this.Center1 + Stadium.RotateVector(Math.PI / 2.0) radiusVector,
this.Center2 + Stadium.RotateVector(Math.PI / 2.0) radiusVector
)
let line2 = struct (
this.Center1 - Stadium.RotateVector(Math.PI / 2.0) radiusVector,
this.Center2 - Stadium.RotateVector(Math.PI / 2.0) radiusVector
)
Stadium.LineIntersectsRect(line1, rect) || Stadium.LineIntersectsRect(line2, rect)
7 changes: 6 additions & 1 deletion Icfpc2023/Program.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
open System
open System.IO
open System.Text
open System.Threading
open System.Threading.Tasks

open Icfpc2023
Expand Down Expand Up @@ -207,9 +208,13 @@ let recalculateScoreCommand (problemId: int) =

let recalculateScoreAllCommand () =
let problems = Directory.GetFiles(problemsDir, "*.json")
for problem in problems do
let mutable finished = 0
Parallel.ForEach(problems, fun (problem: string) ->
let problemId = Path.GetFileNameWithoutExtension problem |> int
recalculateScoreCommand problemId
let res = Interlocked.Increment(&finished)
printfn $"Finished {res} / {problems.Length} problems"
) |> ignore

let convertIni problemFile =
let problem = JsonDefs.ReadProblemFromFile problemFile |> postProcessProblem
Expand Down
67 changes: 59 additions & 8 deletions Icfpc2023/Scoring.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,61 @@ type private Musician = {
Volume: double
}

type SectoredIndex(problem: Problem) =
let sectorSize = 100.0
let sectors =
let w = int <| ceil(problem.StageWidth / sectorSize)
let h = int <| ceil(problem.StageHeight / sectorSize)
Array2D.init w h (fun _ _ -> ResizeArray<PointD>())

let getSectorSquares(stadium: Stadium) =
let stadium' =
{ stadium with
Center1 = stadium.Center1 - problem.StageBottomLeft
Center2 = stadium.Center2 - problem.StageBottomLeft
}

let w = Array2D.length1 sectors
let h = Array2D.length2 sectors
seq {
for x in 0..w - 1 do
for y in 0..h - 1 do
if sectors[x, y].Count > 0 then
let square = {
BottomLeft = PointD(float x * sectorSize, float y * sectorSize)
Width = sectorSize
Height = sectorSize
}
if stadium'.RectangularPartIntersectsWith square then
yield struct(x, y)
}

member _.Add(point: PointD): unit =
let sector = sectors[int ((point.X - problem.StageBottomLeft.X) / sectorSize), int ((point.Y- problem.StageBottomLeft.Y) / sectorSize)]
sector.Add point

member _.GetPointsIn(s: Stadium): PointD seq =
let squares = getSectorSquares s
squares
|> Seq.collect(fun (struct(x, y)) -> sectors[x, y])
|> Seq.filter(s.Contains)

let private CalculateAttendeeMusicianScore(attendee: Attendee, musician: Musician, closeness: ClosenessFactor): Score =
let d_squared = (attendee.X - musician.Location.X) ** 2.0 + (attendee.Y - musician.Location.Y) ** 2.0
musician.Volume * ceil(closeness * 1_000_000.0 * attendee.Tastes[musician.Instrument] / d_squared)

let private AnyOtherMusicianBlocksSound (musicians: Musician[]) (attendee: Attendee) (mIndex: int): bool =
let musician = PointD(musicians[mIndex].Location.X, musicians[mIndex].Location.Y)
let private AnyOtherMusicianBlocksSound(index: SectoredIndex,
attendee: Attendee,
musician: Musician): bool =
let musician = musician.Location
let attendee = PointD(attendee.X, attendee.Y)
let blockZone = { Center1 = musician; Center2 = attendee; Radius = 5.0 }
let candidates = index.GetPointsIn blockZone

Seq.indexed musicians
|> Seq.filter(fun (i, _) -> i <> mIndex)
|> Seq.exists(fun (_, m) -> blockZone.Contains(m.Location))
candidates
|> Seq.filter(fun p -> p <> musician)
|> Seq.tryHead
|> Option.isSome

let private AnyPillarBlocksSound (pillars: Pillar[]) (musician: Musician) (attendee: Attendee): bool =
let musician = PointD(musician.Location.X, musician.Location.Y)
Expand All @@ -34,10 +77,14 @@ let private AnyPillarBlocksSound (pillars: Pillar[]) (musician: Musician) (atten
}
blockZone.Contains(p.Center))

let private CalculateAttendeeScore (pillars: Pillar[]) (musicians: Musician[]) (closenessFactors: ClosenessFactor[]) (attendee: Attendee): Score =
let private CalculateAttendeeScore(pillars: Pillar[],
musicians: Musician[],
index: SectoredIndex,
closenessFactors: ClosenessFactor[],
attendee: Attendee): Score =
Seq.indexed musicians
|> Seq.sumBy(fun (i, musician) ->
if AnyOtherMusicianBlocksSound musicians attendee i then 0.0
if AnyOtherMusicianBlocksSound(index, attendee, musicians[i]) then 0.0
else if AnyPillarBlocksSound pillars musicians.[i] attendee then 0.0
else CalculateAttendeeMusicianScore(attendee, musician, closenessFactors.[i])
)
Expand Down Expand Up @@ -105,7 +152,11 @@ let CalculateScore(problem: Problem) (solution: Solution): Score =
if tooClose
then BadScore
else
problem.Attendees |> Array.sumBy(CalculateAttendeeScore problem.Pillars musicians closeness_factors)
let index = SectoredIndex problem
musicians |> Seq.iter(fun m -> index.Add m.Location)

problem.Attendees
|> Array.sumBy(fun a -> CalculateAttendeeScore(problem.Pillars, musicians, index, closeness_factors, a))

let CalculateNoBlockingScore(problem: Problem) (solution: IPartialSolution): Score =
let musicians =
Expand Down