Skip to content

Commit

Permalink
2023-24 - Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
jurisk committed Dec 26, 2023
1 parent 4d4b6ba commit 1fd2450
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 28 deletions.
20 changes: 5 additions & 15 deletions scala2/src/main/scala/jurisk/adventofcode/y2018/Advent23.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,24 +85,14 @@ object Advent23 {
distanceFromOrigin === sum(abs(x), abs(y), abs(z)),
)

// Objective - maximize nanobotsInRange and minimize distanceFromOrigin
val objective1 = maximize(nanobotsInRange)
val objective2 = minimize(distanceFromOrigin)
// Objectives - maximize nanobotsInRange and minimize distanceFromOrigin
val _ = maximize(nanobotsInRange)
val _ = minimize(distanceFromOrigin)

@nowarn("cat=deprecation")
val model = checkAndGetModel()

println(model)

val List(xc, yc, zc, nir, dor) =
List(x, y, z, nanobotsInRange, distanceFromOrigin).map { v =>
@nowarn("cat=deprecation")
val result = extractInt(v)
result
}
val List(xc, yc, zc) = runExternal("x", "y", "z").map(resultToInt)

val found = Coords3D(xc, yc, zc)
println(s"$found: nanobots in range: $nir, distance from origin: $dor")

found.manhattanDistance(Coords3D.Zero)
}

Expand Down
31 changes: 22 additions & 9 deletions scala2/src/main/scala/jurisk/adventofcode/y2023/Advent24.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import jurisk.geometry.Coordinates2D
import jurisk.geometry.Coords3D
import jurisk.geometry.Coords3D.Axis
import jurisk.geometry.lineLineIntersectionGivenTwoPointsOnEachLine
import jurisk.math.GaussianElimination
import jurisk.math.positiveAndNegativeDivisors
import jurisk.optimization.ImplicitConversions.RichArithExprIntSort
import jurisk.optimization.ImplicitConversions.RichExpr
Expand Down Expand Up @@ -68,8 +69,8 @@ object Advent24 {
}

// We can assume that rv is rather small, e.g. -1000 to +1000.
// Then we can solve CRT for various assumed `rv` and IF they are `coprime` (requirement for CRT)
// then the solution of CRT will be a solution for all the equations and thus the answer
// Then we can solve CRT for various assumed `coprime` (requirement for CRT!) `rv` values.
// Solution of the CRT is the `p` which is the solution we are looking for.

"Not implemented".fail
}
Expand Down Expand Up @@ -100,15 +101,26 @@ object Advent24 {
}
}

// TODO: Implement Gaussian reduction (just first 3 equations should be enough)
// Gaussian reduction (just the first 5 equations are enough, the rest are redundant)

// This is linear now so you can use Gaussian reduction to get:
// t1 = 94255352940 and t2 = 810431007754 and t3 = 857431055888
val h = data.head
val g = data(1)

// Now just plug it in:
// 191146615936494 + 342596108503183 + 131079628110881 = 664822352550558
// Columns: px, py, pz, t1, t2
// Rows: Equations, 3 from h, 2 from g
val A = Array(
Array(1, 0, 0, v.x - h.v.x, 0),
Array(0, 1, 0, v.y - h.v.y, 0),
Array(0, 0, 1, v.z - h.v.z, 0),
Array(1, 0, 0, 0, v.x - g.v.x),
Array(0, 1, 0, 0, v.y - g.v.y),
).map(_.map(_.toDouble))

???
val b = Array(h.p.x, h.p.y, h.p.z, g.p.x, g.p.y).map(_.toDouble)

val Array(px, py, pz, t1 @ _, t2 @ _) = GaussianElimination.solve(A, b)

Coords3D[Long](px.toLong, py.toLong, pz.toLong)
}

val position = solveAssumingV(data, velocity)
Expand Down Expand Up @@ -381,7 +393,8 @@ object Advent24 {
}

def part2(data: InputPart2): Long = {
// TODO: Consider also trying Newton-Raphson and/or gradient descent
// TODO: Consider also trying Newton-Raphson (see https://github.com/rzikm/advent-of-code/blob/master/2023/24.fs,
// or https://pastebin.com/s6nvy0jA) and/or gradient descent
val result = solve2InferringVelocity(data)
result.position.x + result.position.y + result.position.z
}
Expand Down
39 changes: 39 additions & 0 deletions scala2/src/main/scala/jurisk/math/GaussianElimination.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package jurisk.math

// https://en.wikipedia.org/wiki/Gaussian_elimination
object GaussianElimination {
def solve(A: Array[Array[Double]], b: Array[Double]): Array[Double] = {
val n = A.length
val augmented = A.zip(b).map { case (row, bi) => row :+ bi }

for (col <- 0 until n) {
// Find pivot row
val pivotRow = (col until n).maxBy(row => math.abs(augmented(row)(col)))
val temp = augmented(col)
augmented(col) = augmented(pivotRow)
augmented(pivotRow) = temp

// Make leading coefficient of pivot row 1
val pivotElement = augmented(col)(col)
for (j <- col until n + 1)
augmented(col)(j) /= pivotElement

// Eliminate below pivot
for (i <- col + 1 until n) {
val factor = augmented(i)(col)
for (j <- col until n + 1)
augmented(i)(j) -= factor * augmented(col)(j)
}
}

// Back substitution
val x = new Array[Double](n)
for (i <- n - 1 to 0 by -1) {
x(i) = augmented(i)(n)
for (j <- i + 1 until n)
x(i) -= augmented(i)(j) * x(j)
}

x
}
}
13 changes: 11 additions & 2 deletions scala2/src/main/scala/jurisk/optimization/Optimizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ import jurisk.process.Runner
import jurisk.utils.Parsing.StringOps
import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper

// The https://github.com/tudo-aqua/z3-turnkey distribution did not work on task 2023-24 while the same program
// The https://github.com/tudo-aqua/z3-turnkey distribution did not work on task 2023-24 while the same SMT-LIB program
// worked from the command line. Thus, some methods have ended up being deprecated, and this class is mostly
// a way to generate SMT-LIB programs (see https://smtlib.cs.uiowa.edu/language.shtml).
// TODO: Consider removing the Z3 dependency and just generating SMT-LIB format directly.
//
// You could consider removing the Z3 dependency and just generating SMT-LIB format directly, but it's probably not
// worth it.
trait Optimizer {
val context: Context
val optimize: Optimize
Expand All @@ -46,6 +48,7 @@ trait Optimizer {

// TODO: Make type-safe?
def runExternal(evaluate: String*): List[String]
def resultToInt(result: String): Int
def resultToLong(result: String): Long

def debugPrint(): Unit
Expand Down Expand Up @@ -192,6 +195,12 @@ private class Z3Optimizer(val context: Context, val optimize: Optimize)
lines.tail
}

def resultToInt(result: String): Int =
result.trim match {
case s"(- $n)" => -n.toInt
case other => other.toInt
}

def resultToLong(result: String): Long =
result.trim match {
case s"(- $n)" => -n.toLong
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class Advent24Spec extends AnyFreeSpec {
"part 2" - {
val expectedResult = 664822352550558L

"real inferring velocity" in {
// This works but is not fast enough to leave enabled
"real inferring velocity" ignore {
solve2InferringVelocity(realData) shouldEqual expectedRealAnswer
}

Expand All @@ -67,7 +68,8 @@ class Advent24Spec extends AnyFreeSpec {
solve2UsingChineseRemainderTheorem(realData) shouldEqual expectedResult
}

"real" in {
// This works but is not fast enough to leave enabled
"real" ignore {
part2(realData) shouldEqual expectedResult
}
}
Expand Down
57 changes: 57 additions & 0 deletions scala2/src/test/scala/jurisk/math/GaussianEliminationSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package jurisk.math

import jurisk.math.GaussianElimination.solve
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers._

class GaussianEliminationSpec extends AnyFreeSpec {
private def compare(
solution: Array[Double],
expected: Array[Double],
): Unit = {
val Eps = 1e-5

solution.length shouldEqual expected.length
for ((v, i) <- solution.zipWithIndex)
assert(
math.abs(v - expected(i)) < Eps,
s"Value at index $i should be close to ${expected(i)}",
)
}

"GaussianElimination" - {
"test 1" in {
// 2x + y -z = 8
// -3x -y + 2z = -11
// -2x +y + 2z = -3
val A = Array(
Array(2.0, 1.0, -1.0),
Array(-3.0, -1.0, 2.0),
Array(-2.0, 1.0, 2.0),
)
val b = Array(8.0, -11.0, -3.0)
val expected = Array(2.0, 3.0, -1.0)
val solution = solve(A, b)

compare(solution, expected)
}

// https://en.wikipedia.org/wiki/Gaussian_elimination#Example_of_the_algorithm
"test from Wikipedia" in {
// 2x + y - z = 8
// -3x -y + 2z = -11
// -2x + y + 2z = -3
val A = Array(
Array(2.0, 1.0, -1.0),
Array(-3.0, -1.0, 2.0),
Array(-2.0, 1.0, 2.0),
)

val b = Array(8.0, -11.0, -3.0)
val expected = Array(2.0, 3.0, -1.0)
val solution = solve(A, b)

compare(solution, expected)
}
}
}

0 comments on commit 1fd2450

Please sign in to comment.