# Path: feemodel/util.py
# def cumsum_gen(iterable, base=0, mapfn=lambda x: x):
#     """Cumulative sum generator.
# 
#     Returns a generator that yields the cumulative sum of a given iterable.
# 
#     base is the object that you begin summing from.
# 
#     mapfn is a function that is applied to each element of the iterable prior
#     to the summation.
#     """
#     cumsum = base
#     for item in iterable:
#         cumsum += mapfn(item)
#         yield cumsum
# 
# class StepFunction(Function):
#     """A non-negative, monotone step function.
# 
#     Points always represent the upper part of a discontinuity.
#     As of now it works for the intended purpose, but it's a bit of a mess,
#     sorry.
#     """
# 
#     def __call__(self, x0):
#         # TODO: need better specifications about the function.
#         #       e.g. values outside of the defined domain, inc / dec etc.
#         if len(self) < 2:
#             raise ValueError("Function must have at least 2 points.")
#         if self[-1][1] > self[0][1]:
#             # Function is strictly increasing
#             idx = bisect(self._x, x0) - 1
#             if idx < 0:
#                 return 0
#         else:
#             # Function is weakly decreasing
#             idx = bisect_left(self._x, x0)
#             if idx == len(self._x):
#                 return 0
#         return self._y[idx]
# 
#     def approx(self, percenterror=0.05, percentstep=0.05):
#         """Approximate by a piecewise linear function.
# 
#         Use as few segments as possible to stay below a given allowable error.
#         For convenience, errors are only defined on integer values of the
#         domain (also because feerates are integer valued).
#         """
#         if len(self) < 2:
#             raise ValueError("Function must have at least 2 points.")
#         # Make a copy to mutate
#         selfcopy = copy(self)
#         reverse = False
#         if selfcopy[-1][1] < selfcopy[0][1]:
#             reverse = True
#             # If self is decreasing, normalize it to be increasing
#             selfcopy._x = map(operator.neg, reversed(selfcopy._x))
#             selfcopy._y.reverse()
# 
#         selfmax = selfcopy._y[-1]
#         error_thresh = percenterror*selfmax
#         step = percentstep*selfmax
#         f = Function([selfcopy._x[0]], [selfcopy._y[0]])
#         previdx = 0
#         prev_cand_idx = None
#         idx = 0
#         while idx < len(selfcopy):
#             cand = selfcopy[idx]
#             _f = copy(f)
#             _f.addpoint(*cand)
#             maxerror = selfcopy._get_maxerror(previdx, idx, _f)
#             if maxerror > error_thresh:
#                 if prev_cand_idx is not None:
#                     f.addpoint(*selfcopy[prev_cand_idx])
#                     previdx = prev_cand_idx
#                 else:
#                     x, y = selfcopy[idx]
#                     f.addpoint(x-1, selfcopy(x-1))
#                 prev_cand_idx = None
#             elif cand[1] - selfcopy[previdx][1] > step:
#                 f.addpoint(*cand)
#                 previdx = idx
#                 prev_cand_idx = None
#                 idx += 1
#             else:
#                 prev_cand_idx = idx
#                 idx += 1
#         if prev_cand_idx:
#             f.addpoint(*selfcopy[prev_cand_idx])
#         if reverse:
#             f._x = map(operator.neg, reversed(f._x))
#             f._y = list(reversed(f._y))
#         return f
# 
#     def _get_maxerror(self, previdx, curridx, f):
#         maxerror = 0
#         for idx in range(previdx, curridx+1):
#             x, y = self[idx]
#             curr_error = abs(y - f(x))
#             curr_error_back = abs(self(x-1) - f(x-1, use_lower=True))
#             maxerror = max(curr_error, curr_error_back, maxerror)
#         return maxerror
# 
#     def __copy__(self):
#         return StepFunction(list(self._x), list(self._y))


from random import random, expovariate, choice
from bisect import bisect_left
from itertools import groupby
from operator import attrgetter
from collections import Counter
from tabulate import tabulate
from feemodel.util import cumsum_gen, StepFunction
from feemodel.simul.simul import SimBlock

    def __eq__(self, other):
        return all([
            getattr(self, attr) == getattr(other, attr)
            for attr in ['maxblocksizes', 'minfeerates', 'blockrate']])


class SimPools(object):

    def __init__(self, pools, blockrate=DEFAULT_BLOCKRATE):
        self.pools = pools
        self.blockrate = blockrate

    def check(self):
        if not self.pools:
            raise ValueError("No pools.")
        if any([pool.hashrate <= 0 or
                pool.maxblocksize <= 0 or
                pool.minfeerate < 0
                for pool in self.pools.values()]):
            raise ValueError("Bad pool stats.")
        if not any([pool.minfeerate < float("inf")
                    for pool in self.pools.values()]):
            raise ValueError("Zero pools capacity.")

    def blockgen(self):
        self.check()
        poolitems = sorted(self.pools.items(),
                           key=lambda item: item[1].hashrate)
        cumhashrates = list(

# Path: feemodel/util.py
# def cumsum_gen(iterable, base=0, mapfn=lambda x: x):
#     """Cumulative sum generator.
# 
#     Returns a generator that yields the cumulative sum of a given iterable.
# 
#     base is the object that you begin summing from.
# 
#     mapfn is a function that is applied to each element of the iterable prior
#     to the summation.
#     """
#     cumsum = base
#     for item in iterable:
#         cumsum += mapfn(item)
#         yield cumsum
# 
# class StepFunction(Function):
#     """A non-negative, monotone step function.
# 
#     Points always represent the upper part of a discontinuity.
#     As of now it works for the intended purpose, but it's a bit of a mess,
#     sorry.
#     """
# 
#     def __call__(self, x0):
#         # TODO: need better specifications about the function.
#         #       e.g. values outside of the defined domain, inc / dec etc.
#         if len(self) < 2:
#             raise ValueError("Function must have at least 2 points.")
#         if self[-1][1] > self[0][1]:
#             # Function is strictly increasing
#             idx = bisect(self._x, x0) - 1
#             if idx < 0:
#                 return 0
#         else:
#             # Function is weakly decreasing
#             idx = bisect_left(self._x, x0)
#             if idx == len(self._x):
#                 return 0
#         return self._y[idx]
# 
#     def approx(self, percenterror=0.05, percentstep=0.05):
#         """Approximate by a piecewise linear function.
# 
#         Use as few segments as possible to stay below a given allowable error.
#         For convenience, errors are only defined on integer values of the
#         domain (also because feerates are integer valued).
#         """
#         if len(self) < 2:
#             raise ValueError("Function must have at least 2 points.")
#         # Make a copy to mutate
#         selfcopy = copy(self)
#         reverse = False
#         if selfcopy[-1][1] < selfcopy[0][1]:
#             reverse = True
#             # If self is decreasing, normalize it to be increasing
#             selfcopy._x = map(operator.neg, reversed(selfcopy._x))
#             selfcopy._y.reverse()
# 
#         selfmax = selfcopy._y[-1]
#         error_thresh = percenterror*selfmax
#         step = percentstep*selfmax
#         f = Function([selfcopy._x[0]], [selfcopy._y[0]])
#         previdx = 0
#         prev_cand_idx = None
#         idx = 0
#         while idx < len(selfcopy):
#             cand = selfcopy[idx]
#             _f = copy(f)
#             _f.addpoint(*cand)
#             maxerror = selfcopy._get_maxerror(previdx, idx, _f)
#             if maxerror > error_thresh:
#                 if prev_cand_idx is not None:
#                     f.addpoint(*selfcopy[prev_cand_idx])
#                     previdx = prev_cand_idx
#                 else:
#                     x, y = selfcopy[idx]
#                     f.addpoint(x-1, selfcopy(x-1))
#                 prev_cand_idx = None
#             elif cand[1] - selfcopy[previdx][1] > step:
#                 f.addpoint(*cand)
#                 previdx = idx
#                 prev_cand_idx = None
#                 idx += 1
#             else:
#                 prev_cand_idx = idx
#                 idx += 1
#         if prev_cand_idx:
#             f.addpoint(*selfcopy[prev_cand_idx])
#         if reverse:
#             f._x = map(operator.neg, reversed(f._x))
#             f._y = list(reversed(f._y))
#         return f
# 
#     def _get_maxerror(self, previdx, curridx, f):
#         maxerror = 0
#         for idx in range(previdx, curridx+1):
#             x, y = self[idx]
#             curr_error = abs(y - f(x))
#             curr_error_back = abs(self(x-1) - f(x-1, use_lower=True))
#             maxerror = max(curr_error, curr_error_back, maxerror)
#         return maxerror
# 
#     def __copy__(self):
#         return StepFunction(list(self._x), list(self._y))


# Path: feemodel/simul/pools.py
from random import random, expovariate, choice
from bisect import bisect_left
from itertools import groupby
from operator import attrgetter
from collections import Counter
from tabulate import tabulate
from feemodel.util import cumsum_gen, StepFunction
from feemodel.simul.simul import SimBlock


    def __eq__(self, other):
        return all([
            getattr(self, attr) == getattr(other, attr)
            for attr in ['maxblocksizes', 'minfeerates', 'blockrate']])


class SimPools(object):

    def __init__(self, pools, blockrate=DEFAULT_BLOCKRATE):
        self.pools = pools
        self.blockrate = blockrate

    def check(self):
        if not self.pools:
            raise ValueError("No pools.")
        if any([pool.hashrate <= 0 or
                pool.maxblocksize <= 0 or
                pool.minfeerate < 0
                for pool in self.pools.values()]):
            raise ValueError("Bad pool stats.")
        if not any([pool.minfeerate < float("inf")
                    for pool in self.pools.values()]):
            raise ValueError("Zero pools capacity.")

    def blockgen(self):
        self.check()
        poolitems = sorted(self.pools.items(),
                           key=lambda item: item[1].hashrate)
        cumhashrates = list(

            cumsum_gen([pool.hashrate for name, pool in poolitems]))