# Path: epydemic/gf/gf.py
# class GF:
#     '''Base class for generating functions.
# 
#     A generating function represents a formal power series. In network
#     science they are usually, but not exclusively, used to represent
#     probability distributions, for example of node degrees. Computing
#     with the generating functions then provides a way of working with
#     entire distributions, from which the individual probabilities can
#     later be extracted.
# 
#     Generating functions can be treated in three ways:
# 
#     1. As arrays of coefficients that can be extracted
#     2. As functions that can be called
#     3. As functions to be differentiated one or more times
# 
#     In the first approach, use the array interface, for example:
# 
#     .. code-block:: python
# 
#        p_2 = gf[2]
# 
#     In the second, use the calling interface, for example:
# 
#     .. code-block:: python
# 
#        p_0 = gf(0)
# 
#     In the third, use the :meth:`dx` method, for example:
# 
#     .. code-block:: python
# 
#        gf_prime_prime = gf.dx(2)
# 
#     All three cases are memoised to improve performance, relying on
#     the fact that the generating function class is immutable.
# 
#     '''
# 
#     def __init__(self):
#         pass
# 
#     # ---------- Subclass API ----------
# 
#     def getCoefficient(self, i: int) -> float:
#         '''Return the 'th coefficient, that is the coefficient of the
#         term in :math:`x^i`.
# 
#         This method should be overridden by sub-classes.
# 
#         :param i: the index
#         :returns: the i'th coefficient'''
#         raise NotImplementedError('GF.getCoefficient() must be overridden by sub-classes')
# 
#     def evaluate(self, x: float) -> float:
#         '''Evaluate the generating function at x. This is always a number
#         on the range :math:`[0, 1]`.
# 
#         This method must be overridden by sub-classes.
# 
#         :param x: the argument
#         :returns: the value of the generating function'''
#         raise NotImplementedError('GF.evaluate() must be overridden by sub-classes')
# 
#     def derivative(self, order: int = 1) -> 'GF':
#         '''Return a new generating function representing the derivative
#         (to any order, the first by default).
# 
#         This method should be overridden by sub-classes.
# 
#         :param order: (optional) the order of derivative (defaults to 1)
#         :returns: the dserivative generating function'''
#         raise NotImplementedError('GF.derivative() must be overridden by sub-classes')
# 
# 
#     # ---------- Client API ----------
# 
#     @lru_cache(maxsize=None)
#     def __getitem__(self, i: int) -> float:
#         '''Return the i'th coefficient. This uses :meth:`getCoefficient`
#         but with an array-like interface.
# 
#         :param i: the index
#         :returns: the i'th coefficient'''
#         return self.getCoefficient(i)
# 
#     @lru_cache(maxsize=None)
#     def __call__(self, x: float) -> float:
#         '''Evaluate the generating function. This uses :meth:`evaluate`
#         but with a call interface.
# 
#         :param x: the argument
#         :returns: the value of the generating function'''
#         return self.evaluate(x)
# 
#     @lru_cache(maxsize=None)
#     def dx(self, order: int = 1):
#         '''Return the derivative of the generating function to the desired order.
# 
#         :param order: (optional) the order of derivative (defaults to 1)
#         :returns: the dserivative generating function'''
#         return self.derivative(order)


from typing import Callable
from mpmath import factorial
from epydemic.gf import GF
import numpy
        a Cauchy contour integral of radius r.

        :param z: point at which to find the derivative
        :param n: order of derivative (defaults to 1)
        :param r: radius of contour (defaults to 1.0)
        :param dx: step size (defaults to 1e-2)'''

        # increase the requested order of derivative by whatever we have intrinsically
        n += self._order

        # simple evaluation if we've got no derivatives to compute
        if n == 0:
            return self._f(z)

        # perform the Cauchy contour integral
        x = r * numpy.exp(2j * numpy.pi * numpy.arange(0, 1, dx))
        return factorial(n) * numpy.mean(self._f(z + x) / x**n)

    def getCoefficient(self, i: int) -> float:
        '''Return the i'th coefficient.

        :param i: the index
        :returns: the coefficient of x^i'''

        # we need to adapt the step distance for the integration
        # so that it is smaller than 1 / i
        step = 1 / (numpy.ceil((i + 1) / 99) * 100)

        return float((self._differentiate(0.0, i, dx=step) / factorial(i)).real)


# Path: epydemic/gf/gf.py
# class GF:
#     '''Base class for generating functions.
# 
#     A generating function represents a formal power series. In network
#     science they are usually, but not exclusively, used to represent
#     probability distributions, for example of node degrees. Computing
#     with the generating functions then provides a way of working with
#     entire distributions, from which the individual probabilities can
#     later be extracted.
# 
#     Generating functions can be treated in three ways:
# 
#     1. As arrays of coefficients that can be extracted
#     2. As functions that can be called
#     3. As functions to be differentiated one or more times
# 
#     In the first approach, use the array interface, for example:
# 
#     .. code-block:: python
# 
#        p_2 = gf[2]
# 
#     In the second, use the calling interface, for example:
# 
#     .. code-block:: python
# 
#        p_0 = gf(0)
# 
#     In the third, use the :meth:`dx` method, for example:
# 
#     .. code-block:: python
# 
#        gf_prime_prime = gf.dx(2)
# 
#     All three cases are memoised to improve performance, relying on
#     the fact that the generating function class is immutable.
# 
#     '''
# 
#     def __init__(self):
#         pass
# 
#     # ---------- Subclass API ----------
# 
#     def getCoefficient(self, i: int) -> float:
#         '''Return the 'th coefficient, that is the coefficient of the
#         term in :math:`x^i`.
# 
#         This method should be overridden by sub-classes.
# 
#         :param i: the index
#         :returns: the i'th coefficient'''
#         raise NotImplementedError('GF.getCoefficient() must be overridden by sub-classes')
# 
#     def evaluate(self, x: float) -> float:
#         '''Evaluate the generating function at x. This is always a number
#         on the range :math:`[0, 1]`.
# 
#         This method must be overridden by sub-classes.
# 
#         :param x: the argument
#         :returns: the value of the generating function'''
#         raise NotImplementedError('GF.evaluate() must be overridden by sub-classes')
# 
#     def derivative(self, order: int = 1) -> 'GF':
#         '''Return a new generating function representing the derivative
#         (to any order, the first by default).
# 
#         This method should be overridden by sub-classes.
# 
#         :param order: (optional) the order of derivative (defaults to 1)
#         :returns: the dserivative generating function'''
#         raise NotImplementedError('GF.derivative() must be overridden by sub-classes')
# 
# 
#     # ---------- Client API ----------
# 
#     @lru_cache(maxsize=None)
#     def __getitem__(self, i: int) -> float:
#         '''Return the i'th coefficient. This uses :meth:`getCoefficient`
#         but with an array-like interface.
# 
#         :param i: the index
#         :returns: the i'th coefficient'''
#         return self.getCoefficient(i)
# 
#     @lru_cache(maxsize=None)
#     def __call__(self, x: float) -> float:
#         '''Evaluate the generating function. This uses :meth:`evaluate`
#         but with a call interface.
# 
#         :param x: the argument
#         :returns: the value of the generating function'''
#         return self.evaluate(x)
# 
#     @lru_cache(maxsize=None)
#     def dx(self, order: int = 1):
#         '''Return the derivative of the generating function to the desired order.
# 
#         :param order: (optional) the order of derivative (defaults to 1)
#         :returns: the dserivative generating function'''
#         return self.derivative(order)


# Path: epydemic/gf/continuous_gf.py
from typing import Callable
from mpmath import factorial
from epydemic.gf import GF
import numpy

        a Cauchy contour integral of radius r.

        :param z: point at which to find the derivative
        :param n: order of derivative (defaults to 1)
        :param r: radius of contour (defaults to 1.0)
        :param dx: step size (defaults to 1e-2)'''

        # increase the requested order of derivative by whatever we have intrinsically
        n += self._order

        # simple evaluation if we've got no derivatives to compute
        if n == 0:
            return self._f(z)

        # perform the Cauchy contour integral
        x = r * numpy.exp(2j * numpy.pi * numpy.arange(0, 1, dx))
        return factorial(n) * numpy.mean(self._f(z + x) / x**n)

    def getCoefficient(self, i: int) -> float:
        '''Return the i'th coefficient.

        :param i: the index
        :returns: the coefficient of x^i'''

        # we need to adapt the step distance for the integration
        # so that it is smaller than 1 / i
        step = 1 / (numpy.ceil((i + 1) / 99) * 100)

        return float((self._differentiate(0.0, i, dx=step) / factorial(i)).real)


    def evaluate(self, x: float) -> float: