github.com/filecoin-project/specs-actors/v4@v4.0.2/support/agent/math.go (about) 1 package agent 2 3 import ( 4 "math" 5 big2 "math/big" 6 "math/rand" 7 8 "github.com/filecoin-project/go-state-types/abi" 9 "github.com/filecoin-project/go-state-types/big" 10 ) 11 12 var DisbursedAmount = big.Mul(big.NewInt(41e6), big.NewInt(1e18)) 13 14 // RateIterator can be used to model poisson process (a process with discreet events occurring at 15 // arbitrary times with a specified average rate). It's Tick function must be called at regular 16 // intervals with a function that will be called zero or more times to produce the event distribution 17 // at the correct rate. 18 type RateIterator struct { 19 rnd *rand.Rand 20 rate float64 21 nextOccurrence float64 22 } 23 24 func NewRateIterator(rate float64, seed int64) *RateIterator { 25 rnd := rand.New(rand.NewSource(seed)) 26 next := 1.0 27 if rate > 0.0 { 28 next += poissonDelay(rnd.Float64(), rate) 29 } 30 31 return &RateIterator{ 32 rnd: rnd, 33 rate: rate, 34 35 // choose first event in next tick 36 nextOccurrence: next, 37 } 38 } 39 40 // simulate random occurrences by calling the given function once for each event that would land in this epoch. 41 // The function will be called `rate` times on average, but may be called zero or many times in any Tick. 42 func (ri *RateIterator) Tick(f func() error) error { 43 // wait until we have a positive rate before doing anything 44 if ri.rate <= 0.0 { 45 return nil 46 } 47 48 // next tick becomes this tick 49 ri.nextOccurrence -= 1.0 50 51 // choose events can call function until event occurs in next tick 52 for ri.nextOccurrence < 1.0 { 53 err := f() 54 if err != nil { 55 return err 56 } 57 58 // Choose next event 59 // Note the argument to Log is <= 1, so the right side is always negative and nextOccurrence increases 60 ri.nextOccurrence += poissonDelay(ri.rnd.Float64(), ri.rate) 61 } 62 return nil 63 } 64 65 // TickWithRate permits a variable rate. 66 // If the rate has changed, it will compute a new next occurrence before running tick. 67 // This prevents having to wait a long time to recognize a change from a very slow rate to a higher one. 68 func (ri *RateIterator) TickWithRate(rate float64, f func() error) error { 69 // recompute next occurrence if rate has changed 70 if ri.rate != rate && rate > 0.0 { 71 ri.nextOccurrence = 1.0 + poissonDelay(ri.rnd.Float64(), rate) 72 } 73 ri.rate = rate 74 75 return ri.Tick(f) 76 } 77 78 // Compute a poisson distributed delay that produces (on average) a given rate. 79 // The uniformRnd is a real number uniformly distributed in [0, 1). 80 // The rate is the average number of events expected per epoch and may be greater or less than 1 but not zero. 81 func poissonDelay(uniformRnd float64, rate float64) float64 { 82 return -math.Log(1.0-uniformRnd) / rate 83 } 84 85 /////////////////////////////////////// 86 // 87 // Win Count 88 // 89 /////////////////////////////////////// 90 91 // This is the Filecoin algorithm for winning a ticket within a block with the tickets replaced 92 // with random numbers. It lets miners win according to a Poisson distribution with rate 93 // proportional to the miner's fraction of network power. 94 func WinCount(minerPower abi.StoragePower, totalPower abi.StoragePower, random float64) uint64 { 95 E := big2.NewRat(5, 1) 96 lambdaR := new(big2.Rat) 97 lambdaR.SetFrac(minerPower.Int, totalPower.Int) 98 lambdaR.Mul(lambdaR, E) 99 lambda, _ := lambdaR.Float64() 100 101 rhs := 1 - poissonPMF(lambda, 0) 102 103 winCount := uint64(0) 104 for rhs > random { 105 winCount++ 106 rhs -= poissonPMF(lambda, winCount) 107 } 108 return winCount 109 } 110 111 ////////////////////////////////////////// 112 // 113 // Misc 114 // 115 ////////////////////////////////////////// 116 117 // this panics if list is empty. 118 func PopRandom(list []uint64, rnd *rand.Rand) (uint64, []uint64) { 119 idx := rnd.Int63n(int64(len(list))) 120 result := list[idx] 121 list[idx] = list[len(list)-1] 122 return result, list[:len(list)-1] 123 } 124 125 func poissonPMF(lambda float64, k uint64) float64 { 126 fk := float64(k) 127 return (math.Exp(-lambda) * math.Pow(lambda, fk)) / fact(fk) 128 } 129 130 func fact(k float64) float64 { 131 fact := 1.0 132 for i := 2.0; i <= k; i += 1.0 { 133 fact *= i 134 } 135 return fact 136 }