# Path: feemodel/util.py
# class Function(object):
#     '''A (math) function object with interpolation methods.'''
# 
#     def __init__(self, x, y):
#         '''y and x are lists s.t. y_i = f(x_i) and x is sorted.'''
#         self._x = x
#         self._y = y
# 
#     def __call__(self, x0, use_upper=False, use_lower=False):
#         '''Evaluate the function at x0, by linear interpolation.
# 
#         use_upper and use_lower specifies what to do if x is outside
#         the domain of the function [min(self._x), max(self._x)).
# 
#         if use_upper is True, then if all([x >= xi for xi in self._x]),
#         return f(max(self._x)).
# 
#         if use_lower is True, then if all([x < xi for xi in self._x]),
#         return f(min(self._x)).
# 
#         Otherwise return None, if x is outside the domain.
#         '''
#         y0, xidx = interpolate(x0, self._x, self._y)
#         if xidx == 0:
#             return y0 if use_lower else None
#         if xidx == len(self._x):
#             return y0 if use_upper or x0 == self._x[-1] else None
#         return y0
# 
#     def inv(self, y0, use_upper=False, use_lower=False):
#         '''Evaluate the inverse function at y0 by linear interpolation.
# 
#         use_upper and use_lower have the same meaning as in self.__call__.
# 
#         We don't check that the function is 1-to-1: if you call this method,
#         it is assumed so.
#         '''
#         _y = self._y[:]
#         _x = self._x[:]
#         if _y[-1] < _y[0]:
#             _y.reverse()
#             _x.reverse()
#         x0, yidx = interpolate(y0, _y, _x)
#         if yidx == 0:
#             return x0 if use_lower else None
#         if yidx == len(_y):
#             return x0 if use_upper or y0 == _y[-1] else None
#         return x0
# 
#     def addpoint(self, xi, yi):
#         if xi in self._x:
#             return
#         self._x, self._y = map(list, zip(*sorted(list(self) + [(xi, yi)])))
# 
#     def __getitem__(self, idx):
#         return self._x[idx], self._y[idx]
# 
#     def __str__(self):
#         headers = ['x', 'y']
#         table = list(self)
#         return tabulate(table, headers=headers)
# 
#     def __len__(self):
#         return len(self._y)
# 
#     def __iter__(self):
#         return izip(self._x, self._y)
# 
#     def __copy__(self):
#         return Function(list(self._x), list(self._y))
# 
# class DiscreteFunction(Function):
#     """Like Function but without the interpolation.
# 
#     Only defined on the specified x points.
#     """
# 
#     def __call__(self, x0):
#         try:
#             idx = self._x.index(x0)
#         except ValueError:
#             raise ValueError("Not defined at this point")
#         return self._y[idx]
# 
#     def inv(self, y0):
#         try:
#             idx = self._y.index(y0)
#         except ValueError:
#             raise ValueError("Not defined at this point")
#         return self._x[idx]


from tabulate import tabulate
from feemodel.util import Function, DiscreteFunction

        caprate = self.capfn(feerate)
        next_caprate = self.capfn(feerate - 1)
        caprate_delta = caprate - next_caprate
        caprate_prop = caprate_delta / caprate

        prop_min = min(hashrate_prop, caprate_prop)
        return prop_min*txbyterate

    def inv_util(self, utilization_target):
        """Inverse utilization.

        Returns the lowest feerate at which self.txbyteratefn(feerate) /
        self.capfn(feerate) <= utilization_target.
        """
        feerates = sorted(set(self.capfn._x + self.txbyteratefn._x))
        for feerate in feerates:
            try:
                utilization = self.txbyteratefn(feerate) / self.capfn(feerate)
            except ZeroDivisionError:
                continue
            if utilization <= utilization_target:
                return feerate

        # This won't normally happen, because we ensure txbyteratefn extends
        # to 0 byterate, and cap is nonzero as feerate -> inf.
        raise ValueError("utilization_target must be > 0.")


# TODO: Deprecate this.

# Path: feemodel/util.py
# class Function(object):
#     '''A (math) function object with interpolation methods.'''
# 
#     def __init__(self, x, y):
#         '''y and x are lists s.t. y_i = f(x_i) and x is sorted.'''
#         self._x = x
#         self._y = y
# 
#     def __call__(self, x0, use_upper=False, use_lower=False):
#         '''Evaluate the function at x0, by linear interpolation.
# 
#         use_upper and use_lower specifies what to do if x is outside
#         the domain of the function [min(self._x), max(self._x)).
# 
#         if use_upper is True, then if all([x >= xi for xi in self._x]),
#         return f(max(self._x)).
# 
#         if use_lower is True, then if all([x < xi for xi in self._x]),
#         return f(min(self._x)).
# 
#         Otherwise return None, if x is outside the domain.
#         '''
#         y0, xidx = interpolate(x0, self._x, self._y)
#         if xidx == 0:
#             return y0 if use_lower else None
#         if xidx == len(self._x):
#             return y0 if use_upper or x0 == self._x[-1] else None
#         return y0
# 
#     def inv(self, y0, use_upper=False, use_lower=False):
#         '''Evaluate the inverse function at y0 by linear interpolation.
# 
#         use_upper and use_lower have the same meaning as in self.__call__.
# 
#         We don't check that the function is 1-to-1: if you call this method,
#         it is assumed so.
#         '''
#         _y = self._y[:]
#         _x = self._x[:]
#         if _y[-1] < _y[0]:
#             _y.reverse()
#             _x.reverse()
#         x0, yidx = interpolate(y0, _y, _x)
#         if yidx == 0:
#             return x0 if use_lower else None
#         if yidx == len(_y):
#             return x0 if use_upper or y0 == _y[-1] else None
#         return x0
# 
#     def addpoint(self, xi, yi):
#         if xi in self._x:
#             return
#         self._x, self._y = map(list, zip(*sorted(list(self) + [(xi, yi)])))
# 
#     def __getitem__(self, idx):
#         return self._x[idx], self._y[idx]
# 
#     def __str__(self):
#         headers = ['x', 'y']
#         table = list(self)
#         return tabulate(table, headers=headers)
# 
#     def __len__(self):
#         return len(self._y)
# 
#     def __iter__(self):
#         return izip(self._x, self._y)
# 
#     def __copy__(self):
#         return Function(list(self._x), list(self._y))
# 
# class DiscreteFunction(Function):
#     """Like Function but without the interpolation.
# 
#     Only defined on the specified x points.
#     """
# 
#     def __call__(self, x0):
#         try:
#             idx = self._x.index(x0)
#         except ValueError:
#             raise ValueError("Not defined at this point")
#         return self._y[idx]
# 
#     def inv(self, y0):
#         try:
#             idx = self._y.index(y0)
#         except ValueError:
#             raise ValueError("Not defined at this point")
#         return self._x[idx]


# Path: feemodel/simul/stats.py
from tabulate import tabulate
from feemodel.util import Function, DiscreteFunction


        caprate = self.capfn(feerate)
        next_caprate = self.capfn(feerate - 1)
        caprate_delta = caprate - next_caprate
        caprate_prop = caprate_delta / caprate

        prop_min = min(hashrate_prop, caprate_prop)
        return prop_min*txbyterate

    def inv_util(self, utilization_target):
        """Inverse utilization.

        Returns the lowest feerate at which self.txbyteratefn(feerate) /
        self.capfn(feerate) <= utilization_target.
        """
        feerates = sorted(set(self.capfn._x + self.txbyteratefn._x))
        for feerate in feerates:
            try:
                utilization = self.txbyteratefn(feerate) / self.capfn(feerate)
            except ZeroDivisionError:
                continue
            if utilization <= utilization_target:
                return feerate

        # This won't normally happen, because we ensure txbyteratefn extends
        # to 0 byterate, and cap is nonzero as feerate -> inf.
        raise ValueError("utilization_target must be > 0.")


# TODO: Deprecate this.

class CapacityRatios(Function):