now use rational numbers as probabilities

This commit is contained in:
didier amyot committed 2015-06-16 09:20:19 -04:00
1 parent dbbfaf99b6
commit fb9084794c
4 files changed
+94 -82

No files matched your search

+64 -50
View File
@@ -2,8 +2,10 @@ package doorman
import (
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"net/http"
"net/http/httptest"
"reflect"
@@ -13,41 +15,36 @@ import (
"gopkg.in/mgo.v2/bson"
)
func assertIsAbout(t *testing.T, expected, received float64) {
if math.Abs(expected-received) > epsilon {
var epsilon = 0.0
var ZERO = big.NewRat(0, 1)
func assertIsAbout(t *testing.T, expected, received *big.Rat) {
if expected.Cmp(received) != 0 {
t.Error("received", received, "but expected", expected)
}
}
func assertIsNotAbout(t *testing.T, expected, received float64) {
if math.Abs(expected-received) < epsilon {
func assertIsNotAbout(t *testing.T, expected, received *big.Rat) {
if expected.Cmp(received) != 0 {
t.Error("received", received, "but expected something different than", expected)
}
}
var oid = "507f1f77bcf86cd799439011"
func TestIsZero(t *testing.T) {
if !IsZero(epsilon / 2) {
t.Error()
}
if !IsZero(-epsilon / 2) {
t.Error()
}
if IsZero(epsilon * 1.5) {
t.Error()
}
if IsZero(-epsilon * 1.5) {
t.Error()
func getProbs(probs ...float64) []*big.Rat {
ret := make([]*big.Rat, len(probs))
for i, p := range probs {
ret[i], _ = new(big.Rat).SetString(fmt.Sprint(p))
}
return ret
}
var oid = "507f1f77bcf86cd799439011"
func TestIsEqual(t *testing.T) {
if !IsEqual(1.0, 1.0+epsilon/2) {
if !IsEqual(big.NewRat(2, 2), big.NewRat(1, 1)) {
t.Error()
}
if IsEqual(1.0, 1.0+epsilon*1.5) {
if IsEqual(big.NewRat(2, 2), big.NewRat(2, 1)) {
t.Error()
}
}
@@ -58,42 +55,42 @@ func TestValidate(t *testing.T) {
t.Error()
}
w = &Doorman{Probabilities: []float64{0.5, 0.75}}
w = &Doorman{Probabilities: getProbs(0.5, 0.75)}
if w.Validate().Error() != "The sum of probabilities is not one" {
t.Error()
}
w = &Doorman{Probabilities: []float64{0.25, 0.75}}
w = &Doorman{Probabilities: getProbs(0.25, 0.75)}
if w.Validate() != nil {
t.Error()
}
w = &Doorman{Probabilities: []float64{0.2500000000000002, 0.75}}
if w.Validate() != nil {
t.Error()
w = &Doorman{Probabilities: getProbs(0.2500000000002, 0.75)}
if w.Validate() == nil {
t.Error("even very small diff should be significative")
}
}
func TestGetCase(t *testing.T) {
w := NewDoorman(bson.NewObjectId(), []float64{0.25, 0.5, 0.25})
if c := w.GetCase(0); c != 0 {
w := NewDoorman(bson.NewObjectId(), getProbs(0.25, 0.5, 0.25))
if c := w.GetCase(ZERO); c != 0 {
t.Error("expected 0 but received", c)
}
if c := w.GetCase(1); c != 2 {
if c := w.GetCase(ONE); c != 2 {
t.Error("expected 2 but received", c)
}
}
func TestGetCaseCoroutineSafety(t *testing.T) {
w := NewDoorman(bson.NewObjectId(), []float64{0.25, 0.5, 0.25})
w := NewDoorman(bson.NewObjectId(), getProbs(0.25, 0.5, 0.25))
i := 0
w.wg.Add(1)
go func() {
i++
w.wg.Done()
}()
w.GetCase(0)
w.GetCase(ZERO)
if i != 1 {
t.Error("error with goroutine safety")
}
@@ -101,11 +98,11 @@ func TestGetCaseCoroutineSafety(t *testing.T) {
func TestGenerateRandomProbabilityFromBitSlice(t *testing.T) {
w := new(Doorman)
assertIsAbout(t, 0.5, w.GenerateRandomProbabilityFromInteger(1))
assertIsAbout(t, big.NewRat(1, 2), w.GenerateRandomProbabilityFromInteger(1))
assertIsAbout(t, 0.5+0*0.25+1*0.125, w.GenerateRandomProbabilityFromInteger(5))
// assertIsAbout(t, 0.5+0*0.25+1*0.125, w.GenerateRandomProbabilityFromInteger(5))
assertIsAbout(t, 1, w.GenerateRandomProbabilityFromInteger(math.MaxUint64))
assertIsAbout(t, ONE, w.GenerateRandomProbabilityFromInteger(math.MaxUint64))
}
func TestHash(t *testing.T) {
@@ -128,7 +125,7 @@ func TestUpdateTimestamp(t *testing.T) {
t.Error()
}
m = &shared.DoormanUpdater{Timestamp: 11, Probabilities: []float64{}, Id: oid}
m = &shared.DoormanUpdater{Timestamp: 11, Probabilities: getProbs(), Id: oid}
w.Update(m)
if w.LastChangeTimestamp != 11 {
t.Error()
@@ -137,23 +134,23 @@ func TestUpdateTimestamp(t *testing.T) {
func TestUpdateProbabilities(t *testing.T) {
w := &Doorman{LastChangeTimestamp: 0, Id: bson.ObjectIdHex(oid)}
m := &shared.DoormanUpdater{Timestamp: 0, Probabilities: []float64{0.5, 0.5}, Id: oid}
m := &shared.DoormanUpdater{Timestamp: 0, Probabilities: getProbs(0.5, 0.5), Id: oid}
w.Update(m)
if len(w.Probabilities) != 0 {
t.Error()
}
m = &shared.DoormanUpdater{Timestamp: 2, Probabilities: []float64{0.5, 0.5}, Id: oid}
m = &shared.DoormanUpdater{Timestamp: 2, Probabilities: getProbs(0.5, 0.5), Id: oid}
if err := w.Update(m); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(w.Probabilities, []float64{0.5, 0.5}) {
} else if !reflect.DeepEqual(w.Probabilities, getProbs(0.5, 0.5)) {
t.Error("bad prob")
}
m = &shared.DoormanUpdater{Timestamp: 3, Probabilities: []float64{0.5, 0.25}, Id: oid}
m = &shared.DoormanUpdater{Timestamp: 3, Probabilities: getProbs(0.5, 0.25), Id: oid}
if err := w.Update(m); err == nil {
t.Error("should received an error")
} else if !reflect.DeepEqual(w.Probabilities, []float64{0.5, 0.5}) {
} else if !reflect.DeepEqual(w.Probabilities, getProbs(0.5, 0.5)) {
t.Error("bad prob")
}
@@ -169,9 +166,9 @@ func randomBytes(n int) []byte {
func TestUpdateHard(t *testing.T) {
wab := NewDoorman(bson.ObjectIdHex(oid), []float64{})
wab := NewDoorman(bson.ObjectIdHex(oid), getProbs())
createMessage := func() string {
msg := &shared.DoormanUpdater{Timestamp: 2, Probabilities: []float64{0.5, 0.25, 0.25}, Id: oid}
msg := &shared.DoormanUpdater{Timestamp: 2, Probabilities: getProbs(0.5, 0.25, 0.25), Id: oid}
if ret, err := json.Marshal(msg); err != nil {
panic(err)
} else {
@@ -195,20 +192,37 @@ func TestUpdateHard(t *testing.T) {
}
}
func TestGetRandomCase(t *testing.T) {
wab := NewDoorman(bson.NewObjectId(), []float64{0.5, 0.5})
var ret uint = 0
for i := 0; i < 10000; i++ {
ret += wab.GetRandomCase()
func IsExtremeBinomialResult(x int, n, p float64) error {
std := math.Sqrt(n * p * (1 - p))
mean := n * p
xf := float64(x)
if minVal := mean - 3.5*std; xf < minVal {
return errors.New(fmt.Sprintf("value too small of %v when minimal threshold is %v", x, minVal))
}
if ret < 4900 || ret > 5100 {
t.Error("very improbable result of ", ret)
if maxVal := mean + 3.5*std; xf > maxVal {
return errors.New(fmt.Sprintf("value too big of %v when maximum threshold is %v", x, maxVal))
}
return nil
}
func TestGetRandomCase(t *testing.T) {
p := 0.5
n := 10000
wab := NewDoorman(bson.NewObjectId(), getProbs(p, 1-p))
var sum = 0
for i := 0; i < n; i++ {
sum += int(wab.GetRandomCase())
}
if err := IsExtremeBinomialResult(sum, float64(n), p); err != nil {
t.Error(err)
}
}
func BenchmarkGetCaseFromData(b *testing.B) {
var data = randomBytes(1024 * 1024)
w := NewDoorman(bson.NewObjectId(), []float64{0.1, 0.4, 0.4, 0.05, 0.05})
w := NewDoorman(bson.NewObjectId(), getProbs(0.1, 0.4, 0.4, 0.05, 0.05))
for i := 0; i < b.N; i++ {
w.GetCaseFromData(data)
}