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

Wavelet solver #2

Draft
wants to merge 11 commits into
base: graphical-model-solver
Choose a base branch
from
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ libraryDependencies ++= Seq(
"com.fasterxml.jackson.core" % "jackson-databind" % "2.5.3",
"com.github.tototoshi" %% "scala-csv" % "1.3.10",
"org.apache.xmlgraphics" % "batik-all" % "1.16",
"com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.8.8")
"com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.8.8",
"de.sciss" % "jwave" % "1.0.3",
)

lazy val originalCBackend = (project in file("src") / "native" / "Original")
.settings(nativeCompile / sourceDirectory := baseDirectory.value)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package core.solver.wavelet

import util.Profiler

class CoefficientAveragingWaveletSolver(override val querySize: Int, override val debug: Boolean = false)
extends
WaveletSolver[Double]("CoefficientAveragingWaveletSolver", querySize, debug) {


override def solve(): Array[Double] = {
val finalWavelet: Array[Double] = new Array(N)

// target order is ascending variables
val finalCuboidVariables = (0 until querySize).sorted(Ordering[Int])

// multiple cuboids can have the same variable,
// each corresponding wavelet will have coefficients for that variable
// we need to combine or select the coefficients from all the wavelets

// here we will average the coefficients for each variable,
// after extrapolating and permuting the marginal wavelets into the target order

// for each variable, find the number of cuboids that it appears in
val variableCounts: Map[Int, Int] = Profiler(s"$solverName Coefficient Averaging Preparation") {
cuboids.keys.flatten.groupBy(identity).mapValues(_.size)
}

for (((variables, cuboid), index) <- cuboids.zipWithIndex) {
val wavelet: Array[Double] = Profiler(s"$solverName Forward Transform") {
// transform cuboid to wavelet
transformer.forward(cuboid)
}

val extrapolatedWavelet: Array[Double] = Profiler(s"$solverName Wavelet Extrapolation") {
// extrapolate wavelet to the correct size, by filling the missing values with 0
val extrapolatedWavelet = Array.fill(N)(0.0)
System.arraycopy(wavelet, 0, extrapolatedWavelet, 0, wavelet.length)

extrapolatedWavelet
}

// copy the coefficients to the final wavelet
Profiler(s"$solverName Wavelet Permutation & Combination") {
// copy the sum to the final wavelet
if (index == 0) {
finalWavelet(0) = extrapolatedWavelet(0)
}

// fill the wavelet values into the right positions in the final wavelet

/* eg:
querySize = 3
extrapolatedWavelet (0, 2) :: (a, b, c, d)

for variable = 2 (index 0): variablePos = 2^0 = 1, numCoefficients = 2^0 = 1, variableCoefficients = (b)
for variable = 0 (index 1): variablePos = 2^1 = 2, numCoefficients = 2^1 = 2, variableCoefficients = (c, d)

in final wavelet (0, 1, 2) :: (t, 1, 2, 3, 4, 5, 6, 7)

for variable = 2: variablePos = 2^0 = 1, numCoefficients = 2^0 = 1, finalWavelet(1) = (b)
for variable = 0: variablePos = 2^2 = 4, numCoefficients = 2^2 = 4, finalWavelet([4...7]) = (c/2, c/2, d/2, d/2)
*/
// for each variable
variables.reverse.zipWithIndex.foreach { case (variable, i) =>
val variablePos = Math.pow(2, i).toInt
val numCoefficients = variablePos

// get all the coefficients of the variable
val variableCoefficients = extrapolatedWavelet.slice(variablePos, variablePos + numCoefficients)

// set the coefficients of the variable in the final wavelet
val finalVariablePos = Math.pow(2, (querySize - 1) - variable).toInt
val finalNumCoefficients = finalVariablePos

// average the coefficients for each variable
var adjustedCoefficients = variableCoefficients.map { c => c / variableCounts(variable) }

if (numCoefficients < finalNumCoefficients) {
// we need to equally split the coefficients, giving us double the number of coefficients
val divisor = finalNumCoefficients / numCoefficients
adjustedCoefficients = variableCoefficients.flatMap(c => Array.fill(divisor)(c / divisor))

} /* else if (numCoefficient > finalNumCoefficients)
we need to sum the coefficients pairwise, giving us half the number of coefficients

eg. given source cuboid (2, 4, 5) :: [total, 5, 4_0, 4_1, 2_0, 2_1, 2_2, 2_3]
in the final cuboid (..., 2, ..., 4, 5, ...)
there will always be equal or more variables succeeding variable 2
hence, 2 can never acquire an earlier position that requires less coefficients than it has
*/

System.arraycopy(adjustedCoefficients, 0, finalWavelet, finalVariablePos, finalNumCoefficients)
}
}

if (debug) {
println(s"Source Cuboid: $variables :: ${cuboid.mkString(", ")}")
println(s"Source Wavelet: $variables :: ${extrapolatedWavelet.mkString(", ")}")
println(s"Combined Wavelet: $finalCuboidVariables :: ${finalWavelet.mkString(", ")}")
println()
}
}

val finalCuboid: Array[Double] = Profiler(s"$solverName Reverse Transform") {
transformer.reverse(finalWavelet)
}

if (debug) {
println(s"Final Wavelet: $finalCuboidVariables :: ${finalWavelet.mkString(", ")}")
println(s"Final Cuboid: $finalCuboidVariables :: ${finalCuboid.mkString(", ")}")
println()
}

solution = finalCuboid
finalCuboid
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package core.solver.wavelet

import util.Profiler

import scala.math.Ordering.Implicits.seqDerivedOrdering

class CoefficientSelectionWaveletSolver(override val querySize: Int,
override val debug: Boolean = false)
extends
WaveletSolver[Double]("CoefficientSelectionWaveletSolver", querySize, debug) {


override def solve(): Array[Double] = {
val finalWavelet: Array[Double] = new Array(N)

// target order is ascending variables
val finalCuboidVariables = (0 until querySize).sorted(Ordering[Int])

// multiple cuboids can have the same variable,
// each corresponding wavelet will have coefficients for that variable
// we need to combine or select the coefficients from all the wavelets

// here we sort the stored cuboids by the variable seq in ascending order
// eg: if we have [[0, 1, 4], [0, 1, 2], [1], [2, 3, 4], [2, 4], [1, 2]]
// we will sort them to [[0, 1, 2], [0, 1, 4], [1], [1, 2], [2, 3, 4], [2, 4]]
// then do a stable sort on the length of the element seq
// we will finally get [[1], [1, 2], [2, 4], [0, 1, 2], [0, 1, 4], [2, 3, 4]]
// thus the lower dimensional marginals will be overwritten by the higher dimensional marginals
val sortedCuboids = Profiler(s"$solverName Coefficient Priority Selection") {
cuboids.toSeq
.sortBy(_._1)(seqDerivedOrdering)
.sortBy(_._1.length)(Ordering[Int])
}

if (debug) {
println(s"Coefficient priority: ")
println(s"\t${
sortedCuboids.reverse.zipWithIndex.map {
case ((variables, _), index) => s"$index : (${variables.mkString(",")})"
}.mkString("\n\t")
}")
println()
}

for (((variables, cuboid), index) <- sortedCuboids.zipWithIndex) {
val wavelet: Array[Double] = Profiler(s"$solverName Forward Transform") {
// transform cuboid to wavelet
transformer.forward(cuboid)
}

val extrapolatedWavelet: Array[Double] = Profiler(s"$solverName Wavelet Extrapolation") {
// extrapolate wavelet to the correct size, by filling the missing values with 0
val extrapolatedWavelet = Array.fill(N)(0.0)
System.arraycopy(wavelet, 0, extrapolatedWavelet, 0, wavelet.length)

extrapolatedWavelet
}



// copy the coefficients to the final wavelet
Profiler(s"$solverName Wavelet Permutation & Combination") {
// copy the sum to the final wavelet
if (index == 0) {
finalWavelet(0) = extrapolatedWavelet(0)
}

// fill the wavelet values into the right positions in the final wavelet

/* eg:
querySize = 3
extrapolatedWavelet (0, 2) :: (a, b, c, d)

for variable = 2 (index 0): variablePos = 2^0 = 1, numCoefficients = 2^0 = 1, variableCoefficients = (b)
for variable = 0 (index 1): variablePos = 2^1 = 2, numCoefficients = 2^1 = 2, variableCoefficients = (c, d)

in final wavelet (0, 1, 2) :: (t, 1, 2, 3, 4, 5, 6, 7)

for variable = 2: variablePos = 2^0 = 1, numCoefficients = 2^0 = 1, finalWavelet(1) = (b)
for variable = 0: variablePos = 2^2 = 4, numCoefficients = 2^2 = 4, finalWavelet([4...7]) = (c/2, c/2, d/2, d/2)
*/
// for each variable
variables.reverse.zipWithIndex.foreach { case (variable, i) =>
val variablePos = Math.pow(2, i).toInt
val numCoefficients = variablePos

// get all the coefficients of the variable
val variableCoefficients = extrapolatedWavelet.slice(variablePos, variablePos + numCoefficients)

// set the coefficients of the variable in the final wavelet
val finalVariablePos = Math.pow(2, (querySize - 1) - variable).toInt
val finalNumCoefficients = finalVariablePos

var adjustedCoefficients = Array.empty[Double]

if (numCoefficients == finalNumCoefficients) {
// we can just copy the coefficients
adjustedCoefficients = variableCoefficients

} else if (numCoefficients < finalNumCoefficients) {
// we need to equally split the coefficients, giving us double the number of coefficients
val divisor = finalNumCoefficients / numCoefficients
adjustedCoefficients = variableCoefficients.flatMap(c => Array.fill(divisor)(c / divisor))

} else /* if numCoefficient > finalNumCoefficients */ {
// we need to sum the coefficients pairwise, giving us half the number of coefficients

/*
eg. given marginal source cuboid (2, 4, 5) :: [total, 5, 4_0, 4_1, 2_0, 2_1, 2_2, 2_3]
the final cuboid will be (..., 2, ..., 4, 5, ...)
In the final cuboid, 2 cannot have less number of variables succeeding it than it has in the marginal cuboid.
Hence, 2 will never require less number of coefficients than it has in the marginal cuboid.
*/
assert(assertion = false,
"Unexpected condition: numCoefficients > finalNumCoefficients for \n" +
s"MarginalCuboid: ${variables.mkString(", ")}")
}

System.arraycopy(adjustedCoefficients, 0, finalWavelet, finalVariablePos, finalNumCoefficients)
}
}

if (debug) {
println(s"Source Cuboid: $variables :: ${cuboid.mkString(", ")}")
println(s"Source Wavelet: $variables :: ${extrapolatedWavelet.mkString(", ")}")
println(s"Combined Wavelet: $finalCuboidVariables :: ${finalWavelet.mkString(", ")}")
println()
}
}

val finalCuboid: Array[Double] = Profiler(s"$solverName Reverse Transform") {
transformer.reverse(finalWavelet)
}

if (debug) {
println(s"Final Wavelet: $finalCuboidVariables :: ${finalWavelet.mkString(", ")}")
println(s"Final Cuboid: $finalCuboidVariables :: ${finalCuboid.mkString(", ")}")
println()
}

solution = finalCuboid
finalCuboid
}
}
120 changes: 120 additions & 0 deletions src/main/scala/core/solver/wavelet/Demo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package core.solver.wavelet

import core.solver.SolverTools
import core.solver.iterativeProportionalFittingSolver.IPFUtils
import util.BitUtils

import scala.util.Random

object Demo {

/**
* Run the solver with the given parameters, and print the actual data, calculated result and error
*
* @param cuboidIndices list of cuboids to provide to the solver written as Seq(marginal_variables)
* @param solver solver to use
*/
def run(cuboidIndices: Seq[Seq[Int]], solver: WaveletSolver[Double]): Unit = {
val rng = new Random(42)

val querySize = solver.querySize

// Generate random result
val actual: Array[Double] = Array.fill(1 << querySize)(rng.nextInt(100))

implicit def seqToInt: Seq[Int] => Int = BitUtils.SetToInt

// List of cuboids represented as (column indices -> flattened array of values)
val cuboids: Map[Seq[Int], Array[Double]] =
cuboidIndices.map { marginalVariables =>
marginalVariables -> IPFUtils.getMarginalDistribution(querySize, actual, marginalVariables.size, marginalVariables)
}.toMap

// Add cuboids to solver
cuboids.foreach { case (columnIndices, values) => solver.addCuboid(columnIndices, values) }

// Solve
val result: Array[Double] = solver.solve()

// print list of doubles with 2 decimal places
println(s"Actual: ${PrintUtils.toString(actual)}")
println(s"Result: ${PrintUtils.toString(result)}")
println(s"Error: ${SolverTools.error(actual, result)}")
}
}

object TransformDemo {

def main(args: Array[String]): Unit = {
val t = new HaarTransformer[Double]()

val a = Array(1.0, 5.0, 11.0, 2.0)

val w = t.forward(a)

val a_ = t.reverse(w)

println(s"Original: ${PrintUtils.toString(a)}")
println(s"Wavelet: ${PrintUtils.toString(w)}")
println(s"Reversed: ${PrintUtils.toString(a_)}")
}
}


object Demo1 {
def main(args: Array[String]): Unit = {
Demo.run(
Seq(Seq(0, 1, 4), Seq(1, 5), Seq(2, 3, 5), Seq(0, 2), Seq(3, 4), Seq(1)),
new CoefficientSelectionWaveletSolver(6, debug = true))
}
}

object Demo2 {
def main(args: Array[String]): Unit = {
Demo.run(
Seq(Seq(0, 1, 4), Seq(1, 5), Seq(2, 3, 5), Seq(0, 2), Seq(3, 4), Seq(1)),
new CoefficientAveragingWaveletSolver(6, debug = true))
}
}

object Demo3 {

def main(args: Array[String]): Unit = {

val runs: Seq[Run] = Seq(
// Run(3, Seq((Seq(0, 1, 2), Array(1.0, 19.0, 13.0, 1.0, 4.0, 7.0, 1.0, 13.0))), new SingleCuboidWaveletSolver(_, _)),
// Run(3, Seq((Seq(0, 1), Array(5.0, 26.0, 14.0, 14.0))), new SingleCuboidWaveletSolver(_, _)),
// Run(3, Seq((Seq(0, 2), Array(14.0, 20.0, 5.0, 20.0))), new SingleCuboidWaveletSolver(_, _)),
// Run(3, Seq((Seq(1), Array(31.0, 28.0))), new SingleCuboidWaveletSolver(_, _)),
// Run(3, Seq((Seq(0), Array(19.0, 40.0))), new SingleCuboidWaveletSolver(_, _)),
// Run(3,
// Seq(
// (Seq(1), Array(31.0, 28.0)),
// (Seq(0, 2), Array(14.0, 20.0, 5.0, 20.0))
// ),
// new MutuallyExclusiveWaveletSolver(_, _)
// ),
// Run(3,
// Seq(
// (Seq(0, 1, 2), Array(1.0, 19.0, 13.0, 1.0, 4.0, 7.0, 1.0, 13.0)),
// ),
// new MutuallyExclusiveWaveletSolver(_, _)
// ),
Run(3, Seq((Seq(0, 1, 2), Array(1.0, 5.0, 11.0, 7.0, 12.0, 4.0, 6.0, 3.0))), new SingleCuboidWaveletSolver(_, _)),


)

runs.foreach { case Run(querySize, cuboids, createSolver) =>
val solver = createSolver(querySize, true)
cuboids.foreach { case (columnIndices, values) =>
solver.addCuboid(columnIndices, values)
}
solver.solve()

println("================================")
}
}

private case class Run(querySize: Int, cuboids: Seq[(Seq[Int], Array[Double])], solver: (Int, Boolean) => WaveletSolver[Double])
}
Loading