diff --git a/Icfpc2023/Geometry.fs b/Icfpc2023/Geometry.fs index 55a906e..de18b94 100644 --- a/Icfpc2023/Geometry.fs +++ b/Icfpc2023/Geometry.fs @@ -5,7 +5,13 @@ type PointD = | PointD of double * double member this.X = match this with PointD(x, _) -> x member this.Y = match this with PointD(_, y) -> y + member this.SquaredDistanceTo(p: PointD): double = + let (PointD(x1, y1)) = this + let (PointD(x2, y2)) = p + (x1 - x2) ** 2.0 + (y1 - y2) ** 2.0 + member this.DistanceTo(p: PointD): double = + sqrt <| this.SquaredDistanceTo p [] type Stadium = diff --git a/Icfpc2023/Icfpc2023.fsproj b/Icfpc2023/Icfpc2023.fsproj index 852eea6..a1ae9a1 100644 --- a/Icfpc2023/Icfpc2023.fsproj +++ b/Icfpc2023/Icfpc2023.fsproj @@ -12,6 +12,7 @@ + diff --git a/Icfpc2023/LambdaScore.fs b/Icfpc2023/LambdaScore.fs new file mode 100644 index 0000000..dc1e8a1 --- /dev/null +++ b/Icfpc2023/LambdaScore.fs @@ -0,0 +1,99 @@ +module Icfpc2023.LambdaScore + +open System +open System.Linq + +let OVERLAP_DISTANCE = 5.0 +let MUSICAL_MIN_DISTANCE = 10.0 + +let distance_point(A: PointD, M) = + A.DistanceTo(M) + +// square distance between A and M +let distance2_point(A: PointD, M) = + A.SquaredDistanceTo(M) + +// compute parameters of line a*x + b*y + c = 0 which pass through A and M points +let line_parameter(A: PointD, M: PointD) = + let a = A.Y - M.Y + let b = M.X - A.X + let c = -a*M.X - b*M.Y + a, b, c + +// compute distance between line (a*x + b*y + c = 0) and point +// base points (B1, B2) are using to determine if point is between them, otherwise we can set distance to infinity (1000 is enough) +let distance_point_line(line, point, B1: PointD, B2: PointD) = + let a, b, c = line + let (PointD(x0, y0)) = point + let x_line = (b*( b*x0 - a*y0) - a*c) / (a*a + b*b) + let y_line = (a*(-b*x0 + a*y0) - b*c) / (a*a + b*b) + if B1.X > x_line && B2.X > x_line then + 1000.0 + else if B1.X < x_line && B2.X < x_line then + 1000.0 + else if B1.Y > y_line && B2.Y > y_line then + 1000.0 + else if B1.Y < y_line && B2.Y < y_line then + 1000.0 + else distance_point(PointD(x0, y0), PointD(x_line, y_line)) + +// lambda factor (jt-th Musician between i-th Attendee and j-th Musician) +// when overlapping is, return 0 +// when no overlapping, return 1 +// the idea is to replace step function with continuous function with some parameter for easier gradient-based optimization algorithms +// lambda changes behavior of gate +let lambda_factor(labda, Ai, Mj: PointD, Mjt: PointD) = + let line_Ai_Mj = line_parameter(Ai, Mj) // (a, b, c) typle + let point_Mjt = (Mjt.X, Mjt.Y) // (x0, y0) typle + 2. / Math.PI * atan(labda*(distance_point_line(line_Ai_Mj, Mjt, Ai, Mj) - OVERLAP_DISTANCE)) + 1.0 + +// lambda score between A_i and M_j +// +// A - Attendee, i-th +// M - Musician, j-th +// T - taste matrix +// lambda - parameter; exact solution when -> infty +let lambda_score_AiMj(A: PointD[], M: PointD[], i, j, T: double[,], labda) = + let mutable res = T[i,j] // distance2_point(A[i], M[j]) + for jt in Enumerable.Range(0, M.Length) do + if jt <> j then + res <- res * lambda_factor(labda, A[i], M[j], M[jt]) + res + +// lambda score between M_i and M_j +// +// M - Musician, i-th +// M - Musician, j-th +// lambda - parameter; exact solution when -> infty +let lambda_score_MiMj(M: PointD[], i, j, labda) = + (2. / Math.PI * atan(labda * (distance_point(M[i], M[j]) - MUSICAL_MIN_DISTANCE)) + 1.0) * 100.0 + +let DoTest() = + let p1 = PointD(2,3) + let p2 = PointD(2,4) + let a,b,c = line_parameter(p1, p2) + printfn "%A" (a,b,c) + printfn "%A" (a*p1.X + b*p1.Y + c) + printfn "%A" (a*p2.X + b*p2.Y + c) + + let p1 = PointD(3,2) + let p2 = PointD(4,2) + let a,b,c = line_parameter(p1, p2) + printfn "%A" (a,b,c) + printfn "%A" (a*p1.X + b*p1.Y + c) + printfn "%A" (a*p2.X + b*p2.Y + c) + + let p1 = PointD(3,3) + let p2 = PointD(4,4) + let a,b,c = line_parameter(p1, p2) + printfn "%A" (a,b,c) + printfn "%A" (a*p1.X + b*p1.Y + c) + printfn "%A" (a*p2.X + b*p2.Y + c) + + let p1 = PointD(3,3) + let p2 = PointD(6,0) + let a,b,c = line_parameter(p1, p2) + printfn "%A" (a,b,c) + printfn "%A" (a*p1.X + b*p1.Y + c) + printfn "%A" (a*p2.X + b*p2.Y + c) + diff --git a/Icfpc2023/Program.fs b/Icfpc2023/Program.fs index 290c3aa..e42bfa5 100644 --- a/Icfpc2023/Program.fs +++ b/Icfpc2023/Program.fs @@ -80,6 +80,9 @@ let main(args: string[]): int = let submission = { ProblemId = problemNumber; Contents = File.ReadAllText(solution) } runSynchronouslyV <| Upload(submission, token) + | [| "lambdaScore" |] -> + LambdaScore.DoTest() + | _ -> printfn "Command unrecognized." 0 diff --git a/README.md b/README.md index eeb5374..121415a 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,12 @@ Calculate score for solution 1 on problem 1: $ dotnet run --project Icfpc2023 -- score 1 ``` +#### Oddities +Lambda score showcase (I dunno, see [PR #4](https://github.com/codingteam/icfpc-2023/pull/4/) for details): +```console +$ dotnet run --project Icfpc2023 -- lambdaScore +``` + #### Legacy Download first 3 problems: diff --git a/lambda_score.py b/lambda_score.py new file mode 100755 index 0000000..b43952c --- /dev/null +++ b/lambda_score.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +import math + +OVERLAP_DISTANCE = 5 + +# distance between A and M +def distance_point(A, M): + return math.sqrt((A.x - M.x)**2 + (A.y - M.y)**2) + +# square distance between A and M +def distance2_point(A, M): + return (A.x - M.x)**2 + (A.y - M.y)**2 + +# compute parameters of line a*x + b*y + c = 0 which pass through A and M points +def line_parameter(A, M): + a = A.y - M.y + b = M.x - A.x + c = -a*M.x - b*M.y + return (a, b, c) + +# compute distance between line (a*x + b*y + c = 0) and point +# base points (B1, B2) are using to determine if point is between them, otherwise we can set distance to infinity (1000 is enough) +def distance_point_line(line, point, B1, B2): + a, b, c = line + x0, y0 = point + x_line = (b*( b*x0 - a*y0) - a*c) / (a*a + b*b) + y_line = (a*(-b*x0 + a*y0) - b*c) / (a*a + b*b) + if B1.x > x_line and B2.x > x_line: + return 1000.0 + if B1.x < x_line and B2.x < x_line: + return 1000.0 + if B1.y > y_line and B2.y > y_line: + return 1000.0 + if B1.y < y_line and B2.y < y_line: + return 1000.0 + return distance_point(Point(x0, y0), Point(x_line, y_line)) + +# lambda factor (jt-th Musician between i-th Attendee and j-th Musician) +# when overlapping is, return 0 +# when no overlapping, return 1 +# the idea is to replace step function with continuous function with some parameter for easier gradient-based optimization algorithms +# lambda changes behavior of gate +def lambda_factor(labda, Ai, Mj, Mjt): + line_Ai_Mj = line_parameter(Ai, Mj) # (a, b, c) typle + point_Mjt = (Mjt.x, Mjt.y) # (x0, y0) typle + return 2. / math.pi * math.atan(labda*(distance_point_line(line_Ai_Mj, Mjt) - OVERLAP_DISTANCE)) + 1 + +# lambda score between A_i and M_j +# +# A - Attendee, i-th +# M - Musician, j-th +# T - taste matrix +# lambda - parameter; exact solution when -> infty +def lambda_score_AiMj(A, M, i, j, T, labda): + res = T[i,j] // distance2_point(A[i], M[j]) + for jt in range(0, len(M)): + if jt != j: + res *= lambda_factor(labda, A[i], M[j], M[jt]) + return res + +# lambda score between M_i and M_j +# +# M - Musician, i-th +# M - Musician, j-th +# lambda - parameter; exact solution when -> infty +def lambda_score_MiMj(M, i, j, labda): + return (2. / math.pi * math.atan(labda * (distance_point(M[i], M[j]) - MUSICAL_MIN_DISTANCE)) + 1) * 100 + +# to test +class Point: + def __init__(self): + self.x = 0 + self.y = 0 + def __init__(self, x, y): + self.x = x + self.y = y + +p1 = Point(2,3) +p2 = Point(2,4) +a,b,c = line_parameter(p1, p2) +print(a,b,c) +print(a*p1.x + b*p1.y + c) +print(a*p2.x + b*p2.y + c) + +p1 = Point(3,2) +p2 = Point(4,2) +a,b,c = line_parameter(p1, p2) +print(a,b,c) +print(a*p1.x + b*p1.y + c) +print(a*p2.x + b*p2.y + c) + +p1 = Point(3,3) +p2 = Point(4,4) +a,b,c = line_parameter(p1, p2) +print(a,b,c) +print(a*p1.x + b*p1.y + c) +print(a*p2.x + b*p2.y + c) + +p1 = Point(3,3) +p2 = Point(6,0) +a,b,c = line_parameter(p1, p2) +print(a,b,c) +print(a*p1.x + b*p1.y + c) +print(a*p2.x + b*p2.y + c) +