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  }