Commit 132050c2 authored by Heiko Becker's avatar Heiko Becker
Browse files

Merge with public master

parents 17650bfc 6f1eb15a
...@@ -14,7 +14,20 @@ import Sampler._ ...@@ -14,7 +14,20 @@ import Sampler._
/** /**
Phase for dynamic analysis of errors. Phase for dynamic analysis of errors.
Currently supports only estimation of roundoff errors for double floating-point
computations. Work by running the double float computation side-by-side with
one performed in rationals or arbitrary precision (mpfr) and by comparing the
results.
When using rationals, only min/max absolute and relative errors can be tracked.
Also, square root and transcendental functions are not supported.
MPFR supports also ulp errors, and average errors.
Roundoff errors considered:
When using rationals, input roundoff errors are not considered, i.e. the inputs
are considered to be exact. Roundoff errors from constants are taken into account.
MPFR does not consider input errors either.
Prerequisites: Prerequisites:
- SpecsProcessingPhase - SpecsProcessingPhase
...@@ -22,17 +35,16 @@ import Sampler._ ...@@ -22,17 +35,16 @@ import Sampler._
object DynamicPhase extends DaisyPhase { object DynamicPhase extends DaisyPhase {
var numSamples = 100000 var numSamples = 100000
var filePrefix = "dynamic"
var inputRangeFactor = Rational.one var inputRangeFactor = Rational.one
override val name = "dynamic phase" override val name = "dynamic phase"
override val description = "dynamic evaluation of errors" override val description = "dynamic evaluation of errors"
override val definedOptions: Set[CmdLineOptionDef[Any]] = Set( override val definedOptions: Set[CmdLineOptionDef[Any]] = Set(
ParamOptionDef("sampleSize", "number of inputs for dynamic evaluation", numSamples.toString), ParamOptionDef("sampleSize", "number of inputs for dynamic evaluation", numSamples.toString),
ChoiceOptionDef("dataType", "which higher precision data type to use for comparison", ParamOptionDef("inputRangeFactor", "factor for scaling input ranges", inputRangeFactor.toString),
Set("rational", "mpfr"), "mpfr"), FlagOptionDef("mpfr", "Use MPFR as the higher precision"),
ParamOptionDef("filePrefix", "prefix to use for output/log file", filePrefix), FlagOptionDef("log", "log results to file"),
ParamOptionDef("inputRangeFactor", "factor for scaling input ranges", inputRangeFactor.toString) ParamOptionDef("seed", "seed to use for random number generator", "System.currentTimeMillis")
) )
implicit val debugSection = DebugSectionAnalysis implicit val debugSection = DebugSectionAnalysis
...@@ -44,41 +56,50 @@ object DynamicPhase extends DaisyPhase { ...@@ -44,41 +56,50 @@ object DynamicPhase extends DaisyPhase {
reporter.info(s"\nStarting $name") reporter.info(s"\nStarting $name")
val timer = ctx.timers.dynamic.start val timer = ctx.timers.dynamic.start
var useMpfr = true // make rationals default as they are a bit more reliable
var useRationals = false var useRationals = true
var logToFile = false
var seed = System.currentTimeMillis // 4783
/* Process relevant options */ /* Process relevant options */
for (opt <- ctx.options) opt match { for (opt <- ctx.options) opt match {
case ParamOption("sampleSize", value) => numSamples = value.toInt case ParamOption("sampleSize", value) => numSamples = value.toInt
case ChoiceOption("dataType", "rational") => useMpfr = false; useRationals = true case ParamOption("inputRangeFactor", value) =>
case ChoiceOption("dataType", "mpfr") => useMpfr = true; useRationals = false inputRangeFactor = Rational.fromString(value)
case ChoiceOption("dataType", _) =>
reporter.warning("Unknown data type, reverting to default.") case FlagOption("mpfr") => useRationals = false
case ParamOption("filePrefix", value) => filePrefix = value case FlagOption("log") => logToFile = true
case ParamOption("inputRangeFactor", value) => inputRangeFactor = Rational.fromString(value) case ParamOption("seed", value) => seed = value.toLong
case _ => case _ =>
} }
if (useMpfr) reporter.info("using MPFR") if (useRationals) reporter.info("using Rational")
else reporter.info("using rationals") else reporter.info("using MPFR")
reporter.info("seed: " + seed)
//val timestamp: Long = System.currentTimeMillis / 1000 //val timestamp: Long = System.currentTimeMillis / 1000
//val fstream = new FileWriter(s"rawdata/${filePrefix}_${prg.id}_$timestamp.txt") //val fstream = new FileWriter(s"rawdata/${filePrefix}_${prg.id}_$timestamp.txt")
val fstream = new FileWriter(s"rawdata/${filePrefix}.txt", true) // append val logFile = if (logToFile) {
val out = new BufferedWriter(fstream) val fstream = new FileWriter(s"rawdata/dynamic_${prg.id}.txt", true) // append
out.write(s"sampleSize: $numSamples, ") val out = new BufferedWriter(fstream)
out.write(s"inputRangeFactor: $inputRangeFactor, ") out.write(s"# sampleSize: $numSamples, ")
if (useMpfr) { out.write(s"inputRangeFactor: $inputRangeFactor, ")
out.write("mpfr\n") if (useRationals) {
out.write("rationals\n")
// ulp and average errors are only tracked for mpfr
out.write(s"# benchmark maxAbsError maxRelError minAbsError minRelError\n\n")
} else {
out.write("mpfr\n")
out.write(s"# benchmark maxAbsError maxRelError maxUlpError minAbsError " +
"minRelError minUlpError avrgAbsError avrgRelError avrgUlpError\n\n")
}
out
} else { } else {
out.write("rationals\n") null
} }
out.write(s"benchmark maxAbsError maxRelError maxUlpError minAbsError " +
"minRelError minUlpError avrgAbsError avrgRelError avrgUlpError\n\n")
// TODO: this condition will have to be relaxed if we want to have unbounded inputs
for (fnc <- prg.defs) if (!fnc.precondition.isEmpty && !fnc.body.isEmpty) { for (fnc <- prg.defs) if (!fnc.precondition.isEmpty && !fnc.body.isEmpty) {
val id = fnc.id val id = fnc.id
...@@ -91,11 +112,43 @@ object DynamicPhase extends DaisyPhase { ...@@ -91,11 +112,43 @@ object DynamicPhase extends DaisyPhase {
i.mid + inputRangeFactor * i.radius)) i.mid + inputRangeFactor * i.radius))
}) })
if (useMpfr) { if (useRationals) {
val sampler = new Uniform(inputRanges, seed)
val measurer = new ErrorMeasurerRational()
var i = 0
while (i < numSamples) {
i = i + 1
//if (i % 10000 == 0) println (s"i: $i") // showing progress
// does not consider input roundoff errors
val dblInputs: Map[Identifier, Double] = sampler.next
val ratInputs: Map[Identifier, Rational] = dblInputs.map({
case (x, d) => (x -> Rational.fromDouble(d))
})
val dblOutput: Double = evalDouble(body, dblInputs)
val ratOutput: Rational = evalRational(body, ratInputs)
measurer.nextValues(dblOutput, ratOutput)
}
if (logToFile) {
logFile.write(s"${prg.id}-$id" +
s" ${measurer.maxAbsError}"+
s" ${measurer.maxRelError}" +
s" ${measurer.minAbsError}" +
s" ${measurer.minRelError}\n")
}
reporter.info(s"$id maxAbsError: ${measurer.maxAbsError}" +
s" maxRelError: ${measurer.maxRelError}")
} else {
// this functionality is duplicated in ErrorFunctions.errorDynamic, // this functionality is duplicated in ErrorFunctions.errorDynamic,
// but we are printing here all sorts of stats... // but we are printing here all sorts of stats...
val sampler = new Uniform(inputRanges, 485793) //no seed = System millis val sampler = new Uniform(inputRanges, seed) //no seed = System millis 485793
val measurer = new ErrorMeasurerMPFR() val measurer = new ErrorMeasurerMPFR()
var currentMaxAbsMPFR = measurer.maxAbsError var currentMaxAbsMPFR = measurer.maxAbsError
var currentMaxAbs: Double = measurer.maxAbsError.doubleValue var currentMaxAbs: Double = measurer.maxAbsError.doubleValue
...@@ -124,57 +177,31 @@ object DynamicPhase extends DaisyPhase { ...@@ -124,57 +177,31 @@ object DynamicPhase extends DaisyPhase {
currentMaxAbs = measurer.maxAbsError.doubleValue currentMaxAbs = measurer.maxAbsError.doubleValue
} }
out.write(s"${prg.id}-$id" + if (logToFile) {
s" ${measurer.maxAbsError.toDoubleString}"+ logFile.write(s"${prg.id}-$id" +
s" ${measurer.maxRelError.toDoubleString}" + s" ${measurer.maxAbsError.toDoubleString}"+
s" ${measurer.maxUlpError}"+ s" ${measurer.maxRelError.toDoubleString}" +
s" ${measurer.minAbsError.toDoubleString}" + s" ${measurer.maxUlpError}"+
s" ${measurer.minRelError.toDoubleString} " + s" ${measurer.minAbsError.toDoubleString}" +
s" ${measurer.minUlpError}" + s" ${measurer.minRelError.toDoubleString} " +
s" ${measurer.avrgAbsError.toDoubleString}" + s" ${measurer.minUlpError}" +
s" ${measurer.avrgRelError.toDoubleString}"+ s" ${measurer.avrgAbsError.toDoubleString}" +
s" ${measurer.avrgUlpError}\n") s" ${measurer.avrgRelError.toDoubleString}"+
s" ${measurer.avrgUlpError}\n")
}
reporter.info(s"$id maxAbsError: ${measurer.maxAbsError.toDoubleString}" + reporter.info(s"$id maxAbsError: ${measurer.maxAbsError.toDoubleString}" +
s" maxRelError: ${measurer.maxRelError.toDoubleString}") s" maxRelError: ${measurer.maxRelError.toDoubleString}")
} else if (useRationals) {
val sampler = new Uniform(inputRanges, 4783)
val measurer = new ErrorMeasurerRational()
var i = 0 }
while (i < numSamples) {
i = i + 1
//if (i % 10000 == 0) println (s"i: $i")
val dblInputs: Map[Identifier, Double] = sampler.next
val ratInputs: Map[Identifier, Rational] = dblInputs.map({
case (x, d) => (x -> Rational.fromDouble(d))
})
val dblOutput: Double = evalDouble(body, dblInputs)
val ratOutput: Rational = evalRational(body, ratInputs)
measurer.nextValues(dblOutput, ratOutput)
}
out.write(s"${prg.id}-$id" +
s" ${measurer.maxAbsError}"+
s" ${measurer.maxRelError}" +
//s" ${measurer.maxUlpError}"+
s" ${measurer.minAbsError}" +
s" ${measurer.minRelError} " +
//s" ${measurer.minUlpError}" +
s" ${measurer.avrgAbsError}" +
s" ${measurer.avrgRelError}")
//s" ${measurer.avrgUlpError}\n")
reporter.info(s"$id maxAbsError: ${measurer.maxAbsError}" +
s" maxRelError: ${measurer.maxRelError}")
}
} }
timer.stop timer.stop
out.write(s"\ntime: ${timer}\n\n") if (logToFile) {
out.close() logFile.write(s"\ntime: ${timer}\n\n")
logFile.close()
}
ctx.reporter.info(s"Finished $name") ctx.reporter.info(s"Finished $name")
(ctx, prg) (ctx, prg)
......
...@@ -7,7 +7,10 @@ import MPFRFloat.{abs => mpfr_abs, max => mpfr_max, min => mpfr_min} ...@@ -7,7 +7,10 @@ import MPFRFloat.{abs => mpfr_abs, max => mpfr_max, min => mpfr_min}
import Rational.{abs => rat_abs, max => rat_max, min => rat_min} import Rational.{abs => rat_abs, max => rat_max, min => rat_min}
/**
Keeps track of absolute, relative and ulp errors seen so far.
This class is used by the DynamicPhase.
*/
class ErrorMeasurerMPFR { class ErrorMeasurerMPFR {
var n = 0 var n = 0
...@@ -73,7 +76,7 @@ class ErrorMeasurerMPFR { ...@@ -73,7 +76,7 @@ class ErrorMeasurerMPFR {
// from Zach // from Zach Tatlock
private def ulpd(_x: Double, _y: Double): Long = { private def ulpd(_x: Double, _y: Double): Long = {
var x = _x var x = _x
var y = _y var y = _y
...@@ -99,19 +102,20 @@ class ErrorMeasurerMPFR { ...@@ -99,19 +102,20 @@ class ErrorMeasurerMPFR {
} }
class ErrorMeasurerRational { class ErrorMeasurerRational {
var n = 0 private var n = 0
var currentAbsError_Min = Rational(100000) // TODO: this should be MaxValue or some such var currentAbsError_Min = Rational(100000) // TODO: this should be MaxValue or some such
var currentAbsError_Max = Rational.zero var currentAbsError_Max = Rational.zero
var currentRelError_Min = Rational(100000) // this should be MaxValue or some such var currentRelError_Min = Rational(100000) // this should be MaxValue or some such
var currentRelError_Max = Rational.zero var currentRelError_Max = Rational.zero
var currentAbsError_Sum = Rational.zero // This does not work right now, probably because Rationals get too large
var currentRelError_Sum = Rational.zero //var currentAbsError_Sum = Rational.zero
//var currentRelError_Sum = Rational.zero
// TODO: ulp measure and average error // TODO: ulp measure and average error
/* /*
@return (abs, rel error) @return new resp. current (abs error, rel error)
*/ */
def nextValues(lowPrec: Double, highPrec: Rational): (Rational, Rational) = { def nextValues(lowPrec: Double, highPrec: Rational): (Rational, Rational) = {
n = n + 1 n = n + 1
...@@ -123,8 +127,8 @@ class ErrorMeasurerRational { ...@@ -123,8 +127,8 @@ class ErrorMeasurerRational {
currentRelError_Min = rat_min(currentRelError_Min, relError) currentRelError_Min = rat_min(currentRelError_Min, relError)
currentRelError_Max = rat_max(currentRelError_Max, relError) currentRelError_Max = rat_max(currentRelError_Max, relError)
currentAbsError_Sum = currentAbsError_Sum + Rational.zero//absError //currentAbsError_Sum = currentAbsError_Sum + Rational.zero //absError
currentRelError_Sum = currentRelError_Sum + Rational.zero//relError //currentRelError_Sum = currentRelError_Sum + Rational.zero //relError
(absError, relError) (absError, relError)
} }
...@@ -133,6 +137,6 @@ class ErrorMeasurerRational { ...@@ -133,6 +137,6 @@ class ErrorMeasurerRational {
def maxRelError: Rational = currentRelError_Max def maxRelError: Rational = currentRelError_Max
def minRelError: Rational = currentRelError_Min def minRelError: Rational = currentRelError_Min
def avrgAbsError: Rational = (currentAbsError_Sum / Rational(n)) //def avrgAbsError: Rational = (currentAbsError_Sum / Rational(n))
def avrgRelError: Rational = (currentRelError_Sum / Rational(n)) //def avrgRelError: Rational = (currentRelError_Sum / Rational(n))
} }
\ No newline at end of file
...@@ -11,9 +11,9 @@ import collection.immutable.Map ...@@ -11,9 +11,9 @@ import collection.immutable.Map
object Sampler { object Sampler {
trait Sample[T] { // trait Sample[T] {
def next: Map[Identifier, T] // def next: Map[Identifier, T]
} // }
class Uniform(ranges: Map[Identifier, Interval], seed: Long = System.currentTimeMillis) { class Uniform(ranges: Map[Identifier, Interval], seed: Long = System.currentTimeMillis) {
...@@ -23,14 +23,15 @@ object Sampler { ...@@ -23,14 +23,15 @@ object Sampler {
val lowerBounds: Map[Identifier, Double] = ranges.map({ val lowerBounds: Map[Identifier, Double] = ranges.map({
case (x, Interval(a, b)) => (x -> a.toDouble) case (x, Interval(a, b)) => (x -> a.toDouble)
}) })
val random = new Random(seed) // TODO: System.millis val random = new Random(seed) // TODO: System.millis
def next: Map[Identifier, Double] = { def next: Map[Identifier, Double] = {
ranges.map({ ranges.map({
case (x, Interval(a, b)) => (x -> (lowerBounds(x) + random.nextDouble * diameter(x))) case (x, Interval(a, b)) =>
}) (x -> (lowerBounds(x) + random.nextDouble * diameter(x)))
})
} }
} }
......
...@@ -10,7 +10,9 @@ import Definitions.UnitDef ...@@ -10,7 +10,9 @@ import Definitions.UnitDef
import daisy.lang.Trees import daisy.lang.Trees
import daisy.utils.Rational import daisy.utils.Rational
/**
This is the old frontend using Leon.
*/
object ScalaExtraction { object ScalaExtraction {
case class UnsupportedFragmentException(msg: String) extends Exception(msg) case class UnsupportedFragmentException(msg: String) extends Exception(msg)
......
...@@ -27,7 +27,6 @@ object SSATransformerPhase extends DaisyPhase { ...@@ -27,7 +27,6 @@ object SSATransformerPhase extends DaisyPhase {
override val name = "SSA transformer" override val name = "SSA transformer"
override val description = "Transforms the function bodies into SSA form." override val description = "Transforms the function bodies into SSA form."
override val definedOptions: Set[CmdLineOptionDef[Any]] = Set() override val definedOptions: Set[CmdLineOptionDef[Any]] = Set()
//implicit val debugSection = DebugSectionRanges
var reporter: Reporter = null var reporter: Reporter = null
...@@ -37,9 +36,7 @@ object SSATransformerPhase extends DaisyPhase { ...@@ -37,9 +36,7 @@ object SSATransformerPhase extends DaisyPhase {
reporter.info("\nStarting SSA transformation phase") reporter.info("\nStarting SSA transformation phase")
val timer = ctx.timers.ssa.start val timer = ctx.timers.ssa.start
// we need to actually replace the function bodies, // need to replace function bodies, create a copy of the whole program
// so we need to create a copy of the whole program
val newDefs = prg.defs.map(fnc => val newDefs = prg.defs.map(fnc =>
if (!fnc.body.isEmpty) { if (!fnc.body.isEmpty) {
...@@ -51,14 +48,6 @@ object SSATransformerPhase extends DaisyPhase { ...@@ -51,14 +48,6 @@ object SSATransformerPhase extends DaisyPhase {
fnc fnc
}) })
// println("Original program:")
// println(prg)
// println("\nNew program:")
// println(lang.PrettyPrinter.withIDs(Program(prg.id, newDefs)))
timer.stop timer.stop
reporter.info("Finished SSA transformation phase") reporter.info("Finished SSA transformation phase")
...@@ -81,11 +70,29 @@ object SSATransformerPhase extends DaisyPhase { ...@@ -81,11 +70,29 @@ object SSATransformerPhase extends DaisyPhase {
merge(lhs, rhs, recons) merge(lhs, rhs, recons)
// This is not ideal, because we are repeating computation
case Let(id, value, body) => case Let(id, value, body) =>
val inlined = replace(Map((Variable(id) -> value)), body) val ssaValue = toSSA(value)
toSSA(inlined)
val ssaBody = toSSA(body)
val tmp = ssaValue match {
case l: Let => replaceBody(ssaValue, id, ssaBody)
case _ =>
Let(id, ssaValue, ssaBody)
}
tmp
}
private def replaceBody(expr: Expr, nextId: Identifier, nextBody: Expr): Expr = expr match {
case Let(id, value, x @ Let(id2, value2, body)) =>
Let(id, value, replaceBody(x, id, nextBody))
case Let(id, value, body) =>
Let(id, value,
Let(nextId, body, nextBody))
} }
def merge(lhs: Expr, rhs: Expr, recons: (Seq[Expr]) => Expr): Expr = (lhs, rhs) match { def merge(lhs: Expr, rhs: Expr, recons: (Seq[Expr]) => Expr): Expr = (lhs, rhs) match {
......
...@@ -174,6 +174,7 @@ trait ErrorFunctions { ...@@ -174,6 +174,7 @@ trait ErrorFunctions {
val rand = new util.Random(3691285) val rand = new util.Random(3691285)
val sampler = new Uniform(inputRanges, 485793) //no seed = System millis val sampler = new Uniform(inputRanges, 485793) //no seed = System millis
// TODO: this should not be using the ErrorMeasurer as it only needs the abs error
val measurer = new ErrorMeasurerMPFR() val measurer = new ErrorMeasurerMPFR()
var currentMaxAbsMPFR = measurer.maxAbsError var currentMaxAbsMPFR = measurer.maxAbsError
var currentMaxAbs: Double = measurer.maxAbsError.doubleValue var currentMaxAbs: Double = measurer.maxAbsError.doubleValue
...@@ -236,6 +237,7 @@ trait ErrorFunctions { ...@@ -236,6 +237,7 @@ trait ErrorFunctions {
}) })
val sampler = new Uniform(inputRanges, 485793) //no seed = System millis val sampler = new Uniform(inputRanges, 485793) //no seed = System millis
// TODO: no need for the ErrorMeasurer
val measurer = new ErrorMeasurerMPFR() val measurer = new ErrorMeasurerMPFR()
var currentMaxAbsMPFR = measurer.maxAbsError var currentMaxAbsMPFR = measurer.maxAbsError
var currentMaxAbs: Double = measurer.maxAbsError.doubleValue var currentMaxAbs: Double = measurer.maxAbsError.doubleValue
......
...@@ -11,8 +11,8 @@ object MPFRFloat { ...@@ -11,8 +11,8 @@ object MPFRFloat {
// if this is changed in some way, make sure you check all the places // if this is changed in some way, make sure you check all the places
// where MPFRFloats have been used! // where MPFRFloats have been used!
//val context = BinaryMathContext.BINARY128 // TODO, BinaryMathContext seems immutable val context = BinaryMathContext.BINARY128 // TODO, BinaryMathContext seems immutable
val context = new BinaryMathContext(219, 15) //val context = new BinaryMathContext(500, 24)
//val context = BinaryMathContext.BINARY128 //val context = BinaryMathContext.BINARY128
def fromString(s: String): MPFRFloat = new MPFRFloat(new BigFloat(s, context)) def fromString(s: String): MPFRFloat = new MPFRFloat(new BigFloat(s, context))
......
...@@ -143,11 +143,22 @@ object Rational { ...@@ -143,11 +143,22 @@ object Rational {
} }
// decimal notation // decimal notation
else if (value.contains('.')) { else if (value.contains('.')) {
// nominator as simply all digits without the decimal sign //but not things like '3.' (those are integers and we don't like this notation)
assert(!value.endsWith("."))
// nominator is simply all digits without the decimal sign
val nom = new BigInt(new BigInteger(value.replace(".", ""))) val nom = new BigInt(new BigInteger(value.replace(".", "")))
val parts = value.split('.') val parts = value.split('.')
// can this overflow?
val den = new BigInt(new BigInteger(math.pow(baseTen, parts(1).length).toLong.toString)) // the denominator is 10^(number of decimal places)
// Eva: I can't think of a better way to construct this, we cannot use
// Longs or Ints as they may overflow
val numDecPlaces = parts(1).length
val den = new BigInt(new BigInteger(
// a '1' with as many zeroes as there are decimal places
"1" + Array.fill[String](numDecPlaces)("0").mkString("")
))
(nom, den) (nom, den)
} }
// integer // integer
...@@ -414,6 +425,12 @@ class Rational private(val n: BigInt, val d: BigInt) extends ScalaNumber with Sc ...@@ -414,6 +425,12 @@ class Rational private(val n: BigInt, val d: BigInt) extends ScalaNumber with Sc