now use rational numbers as probabilities
This commit is contained in:
1 parent
dbbfaf99b6
commit
fb9084794c
4 files changed
+92
-80
No files matched your search
+24
-28
@@ -3,8 +3,10 @@ package doorman
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
|
"math/big"
|
||||||
mathrand "math/rand"
|
mathrand "math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -15,11 +17,9 @@ import (
|
|||||||
"gopkg.in/mgo.v2/bson"
|
"gopkg.in/mgo.v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// the smallest value that makes two floats the same. Consequently x == y
|
var _ = fmt.Print
|
||||||
// if |x - y| < epsilon. It is a known issue that one can find three numbers
|
|
||||||
// a,b,c such that a == b, b == c and a != c. Hence, it is the programmer role
|
var ONE *big.Rat = big.NewRat(1, 1)
|
||||||
// to be very careful about equalities and inequalities.
|
|
||||||
const epsilon float64 = 0.000000001
|
|
||||||
|
|
||||||
var rand *mathrand.Rand
|
var rand *mathrand.Rand
|
||||||
|
|
||||||
@@ -32,25 +32,18 @@ func init() {
|
|||||||
initRandomSeed()
|
initRandomSeed()
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsZero(f float64) bool {
|
func IsEqual(f1, f2 *big.Rat) bool {
|
||||||
if math.Abs(f) > epsilon {
|
return f1.Cmp(f2) == 0
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsEqual(f1, f2 float64) bool {
|
|
||||||
return IsZero(f1 - f2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Doorman struct {
|
type Doorman struct {
|
||||||
Id bson.ObjectId // the id of the doorman
|
Id bson.ObjectId // the id of the doorman
|
||||||
LastChangeTimestamp int64 // an always increasing int that represent the last time the doorman has beed updated
|
LastChangeTimestamp int64 // an always increasing int that represent the last time the doorman has beed updated
|
||||||
Probabilities []float64 // The probability of each cases. The sum of probabilities needs to be one
|
Probabilities []*big.Rat // The probability of each cases. The sum of probabilities needs to be one
|
||||||
wg sync.WaitGroup // waitgroup for goroutine safety
|
wg sync.WaitGroup // waitgroup for goroutine safety
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDoorman(id bson.ObjectId, Probabilities []float64) *Doorman {
|
func NewDoorman(id bson.ObjectId, Probabilities []*big.Rat) *Doorman {
|
||||||
wab := &Doorman{}
|
wab := &Doorman{}
|
||||||
wab.Id = id
|
wab.Id = id
|
||||||
wab.Probabilities = Probabilities
|
wab.Probabilities = Probabilities
|
||||||
@@ -86,7 +79,7 @@ func (w *Doorman) Update(wu *shared.DoormanUpdater) error {
|
|||||||
defer w.wg.Done()
|
defer w.wg.Done()
|
||||||
w.LastChangeTimestamp = wu.Timestamp
|
w.LastChangeTimestamp = wu.Timestamp
|
||||||
|
|
||||||
if !IsEqual(w.sum(wu.Probabilities), 1) {
|
if !IsEqual(w.sum(wu.Probabilities), ONE) {
|
||||||
return errors.New("the sum of probabilities cannot be different than 1")
|
return errors.New("the sum of probabilities cannot be different than 1")
|
||||||
}
|
}
|
||||||
w.Probabilities = wu.Probabilities
|
w.Probabilities = wu.Probabilities
|
||||||
@@ -94,10 +87,10 @@ func (w *Doorman) Update(wu *shared.DoormanUpdater) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Doorman) sum(prob []float64) float64 {
|
func (w *Doorman) sum(prob []*big.Rat) *big.Rat {
|
||||||
var ret float64 = 0
|
ret := big.NewRat(0, 1)
|
||||||
for _, p := range prob {
|
for _, p := range prob {
|
||||||
ret += p
|
ret = new(big.Rat).Add(ret, p)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -106,25 +99,26 @@ func (w *Doorman) Validate() error {
|
|||||||
if len(w.Probabilities) == 0 {
|
if len(w.Probabilities) == 0 {
|
||||||
return errors.New("not initiated")
|
return errors.New("not initiated")
|
||||||
}
|
}
|
||||||
if !IsZero(w.sum(w.Probabilities) - 1.0) {
|
s := w.sum(w.Probabilities)
|
||||||
|
if !IsEqual(s, ONE) {
|
||||||
return errors.New("The sum of probabilities is not one")
|
return errors.New("The sum of probabilities is not one")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Doorman) GetCase(choosenRandomPosition float64) uint {
|
func (w *Doorman) GetCase(choosenRandomPosition *big.Rat) uint {
|
||||||
w.wg.Wait()
|
w.wg.Wait()
|
||||||
var prob float64 = 0
|
var prob = big.NewRat(0, 1)
|
||||||
for i, p := range w.Probabilities {
|
for i, p := range w.Probabilities {
|
||||||
prob += p
|
prob = new(big.Rat).Add(prob, p)
|
||||||
if choosenRandomPosition-epsilon < prob {
|
if choosenRandomPosition.Cmp(prob) <= 0 {
|
||||||
return uint(i)
|
return uint(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic("cannot have a probability above 1")
|
panic("cannot have a probability above 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Doorman) GenerateRandomProbabilityFromInteger(data uint64) float64 {
|
func (w *Doorman) GenerateRandomProbabilityFromInteger(data uint64) *big.Rat {
|
||||||
var ret float64 = 0
|
var ret float64 = 0
|
||||||
for i := 0; data > 0; i++ {
|
for i := 0; data > 0; i++ {
|
||||||
if data&1 == 1 {
|
if data&1 == 1 {
|
||||||
@@ -132,7 +126,8 @@ func (w *Doorman) GenerateRandomProbabilityFromInteger(data uint64) float64 {
|
|||||||
}
|
}
|
||||||
data >>= 1
|
data >>= 1
|
||||||
}
|
}
|
||||||
return ret
|
rat := new(big.Rat)
|
||||||
|
return rat.SetFloat64(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Doorman) Hash(data ...[]byte) uint64 {
|
func (w *Doorman) Hash(data ...[]byte) uint64 {
|
||||||
@@ -149,5 +144,6 @@ func (w *Doorman) GetCaseFromData(data ...[]byte) uint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *Doorman) GetRandomCase() uint {
|
func (w *Doorman) GetRandomCase() uint {
|
||||||
return w.GetCase(rand.Float64())
|
r := rand.Float64()
|
||||||
|
return w.GetCase(new(big.Rat).SetFloat64(r))
|
||||||
}
|
}
|
||||||
+64
-50
@@ -2,8 +2,10 @@ package doorman
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -13,41 +15,36 @@ import (
|
|||||||
"gopkg.in/mgo.v2/bson"
|
"gopkg.in/mgo.v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertIsAbout(t *testing.T, expected, received float64) {
|
var epsilon = 0.0
|
||||||
if math.Abs(expected-received) > epsilon {
|
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)
|
t.Error("received", received, "but expected", expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertIsNotAbout(t *testing.T, expected, received float64) {
|
func assertIsNotAbout(t *testing.T, expected, received *big.Rat) {
|
||||||
if math.Abs(expected-received) < epsilon {
|
if expected.Cmp(received) != 0 {
|
||||||
t.Error("received", received, "but expected something different than", expected)
|
t.Error("received", received, "but expected something different than", expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var oid = "507f1f77bcf86cd799439011"
|
func getProbs(probs ...float64) []*big.Rat {
|
||||||
|
ret := make([]*big.Rat, len(probs))
|
||||||
func TestIsZero(t *testing.T) {
|
for i, p := range probs {
|
||||||
if !IsZero(epsilon / 2) {
|
ret[i], _ = new(big.Rat).SetString(fmt.Sprint(p))
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !IsZero(-epsilon / 2) {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
if IsZero(epsilon * 1.5) {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
if IsZero(-epsilon * 1.5) {
|
|
||||||
t.Error()
|
|
||||||
}
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var oid = "507f1f77bcf86cd799439011"
|
||||||
|
|
||||||
func TestIsEqual(t *testing.T) {
|
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()
|
t.Error()
|
||||||
}
|
}
|
||||||
if IsEqual(1.0, 1.0+epsilon*1.5) {
|
if IsEqual(big.NewRat(2, 2), big.NewRat(2, 1)) {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,42 +55,42 @@ func TestValidate(t *testing.T) {
|
|||||||
t.Error()
|
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" {
|
if w.Validate().Error() != "The sum of probabilities is not one" {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
w = &Doorman{Probabilities: []float64{0.25, 0.75}}
|
w = &Doorman{Probabilities: getProbs(0.25, 0.75)}
|
||||||
if w.Validate() != nil {
|
if w.Validate() != nil {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
w = &Doorman{Probabilities: []float64{0.2500000000000002, 0.75}}
|
w = &Doorman{Probabilities: getProbs(0.2500000000002, 0.75)}
|
||||||
if w.Validate() != nil {
|
if w.Validate() == nil {
|
||||||
t.Error()
|
t.Error("even very small diff should be significative")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCase(t *testing.T) {
|
func TestGetCase(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))
|
||||||
if c := w.GetCase(0); c != 0 {
|
if c := w.GetCase(ZERO); c != 0 {
|
||||||
t.Error("expected 0 but received", c)
|
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)
|
t.Error("expected 2 but received", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCaseCoroutineSafety(t *testing.T) {
|
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
|
i := 0
|
||||||
w.wg.Add(1)
|
w.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
i++
|
i++
|
||||||
w.wg.Done()
|
w.wg.Done()
|
||||||
}()
|
}()
|
||||||
w.GetCase(0)
|
w.GetCase(ZERO)
|
||||||
if i != 1 {
|
if i != 1 {
|
||||||
t.Error("error with goroutine safety")
|
t.Error("error with goroutine safety")
|
||||||
}
|
}
|
||||||
@@ -101,11 +98,11 @@ func TestGetCaseCoroutineSafety(t *testing.T) {
|
|||||||
|
|
||||||
func TestGenerateRandomProbabilityFromBitSlice(t *testing.T) {
|
func TestGenerateRandomProbabilityFromBitSlice(t *testing.T) {
|
||||||
w := new(Doorman)
|
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) {
|
func TestHash(t *testing.T) {
|
||||||
@@ -128,7 +125,7 @@ func TestUpdateTimestamp(t *testing.T) {
|
|||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
m = &shared.DoormanUpdater{Timestamp: 11, Probabilities: []float64{}, Id: oid}
|
m = &shared.DoormanUpdater{Timestamp: 11, Probabilities: getProbs(), Id: oid}
|
||||||
w.Update(m)
|
w.Update(m)
|
||||||
if w.LastChangeTimestamp != 11 {
|
if w.LastChangeTimestamp != 11 {
|
||||||
t.Error()
|
t.Error()
|
||||||
@@ -137,23 +134,23 @@ func TestUpdateTimestamp(t *testing.T) {
|
|||||||
|
|
||||||
func TestUpdateProbabilities(t *testing.T) {
|
func TestUpdateProbabilities(t *testing.T) {
|
||||||
w := &Doorman{LastChangeTimestamp: 0, Id: bson.ObjectIdHex(oid)}
|
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)
|
w.Update(m)
|
||||||
if len(w.Probabilities) != 0 {
|
if len(w.Probabilities) != 0 {
|
||||||
t.Error()
|
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 {
|
if err := w.Update(m); err != nil {
|
||||||
t.Error(err)
|
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")
|
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 {
|
if err := w.Update(m); err == nil {
|
||||||
t.Error("should received an error")
|
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")
|
t.Error("bad prob")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,9 +166,9 @@ func randomBytes(n int) []byte {
|
|||||||
|
|
||||||
func TestUpdateHard(t *testing.T) {
|
func TestUpdateHard(t *testing.T) {
|
||||||
|
|
||||||
wab := NewDoorman(bson.ObjectIdHex(oid), []float64{})
|
wab := NewDoorman(bson.ObjectIdHex(oid), getProbs())
|
||||||
createMessage := func() string {
|
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 {
|
if ret, err := json.Marshal(msg); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
} else {
|
} else {
|
||||||
@@ -195,20 +192,37 @@ func TestUpdateHard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRandomCase(t *testing.T) {
|
func IsExtremeBinomialResult(x int, n, p float64) error {
|
||||||
wab := NewDoorman(bson.NewObjectId(), []float64{0.5, 0.5})
|
std := math.Sqrt(n * p * (1 - p))
|
||||||
var ret uint = 0
|
mean := n * p
|
||||||
for i := 0; i < 10000; i++ {
|
xf := float64(x)
|
||||||
ret += wab.GetRandomCase()
|
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 {
|
if maxVal := mean + 3.5*std; xf > maxVal {
|
||||||
t.Error("very improbable result of ", ret)
|
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) {
|
func BenchmarkGetCaseFromData(b *testing.B) {
|
||||||
var data = randomBytes(1024 * 1024)
|
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++ {
|
for i := 0; i < b.N; i++ {
|
||||||
w.GetCaseFromData(data)
|
w.GetCaseFromData(data)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
|||||||
"gopkg.in/mgo.v2/bson"
|
"gopkg.in/mgo.v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
var wab = doorman.NewDoorman(bson.ObjectIdHex("54b33d5ef6fb5e3b77000001"), []float64{0.25, 0.5, 0.10, 0.05, 0.1})
|
var wab = doorman.NewDoorman(bson.ObjectIdHex("55048bfff6fb5e213d000001"), []float64{0.25, 0.5, 0.10, 0.05, 0.1})
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := wab.NanoMsgSubscriber("tcp://127.0.0.1:40899"); err != nil {
|
if err := wab.NanoMsgSubscriber("tcp://127.0.0.1:40899"); err != nil {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package shared
|
package shared
|
||||||
|
|
||||||
|
import "math/big"
|
||||||
|
|
||||||
type DoormanUpdater struct {
|
type DoormanUpdater struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Probabilities []float64 `json:"probabilities"`
|
Probabilities []*big.Rat `json:"probabilities"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateHandlerFunc func(m *DoormanUpdater) error
|
type UpdateHandlerFunc func(m *DoormanUpdater) error
|
||||||
Reference in new issue
Block a user