# 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/interface.py
# def gf_from_series(f: Callable[[float], float]) -> GF:
#     '''Create a generating function from the function that defines
#     the complete series.
# 
#     :param f: the series function
#     :returns: the generating function'''
#     return ContinuousGF(f)
# 
# def gf_from_coefficient_function(f: Callable[[int], float]) -> GF:
#     '''Create a generating function from a function that computes
#     the coefficient for the given term.
# 
#     :param f: the coefficients function
#     :returns: a generating function'''
#     return DiscreteGF(f=f)


from cmath import exp
from mpmath import polylog, zeta
from epydemic.gf import GF, gf_from_series, gf_from_coefficient_function

def gf_powerlaw(exponent: float) -> GF:
    '''Return the generating function of the powerlaw
    degree distribution with the given exponent.

    :param exponent: the exponent of the distribution
    :returns: the generating function'''
    return gf_from_series(lambda x: polylog(exponent, x) / zeta(exponent))


def gf_ba(M: int) -> GF:
    '''Return the generating fuynction of the degree distribution of
    Barabasi-Albert network with connectivity of M edges per added
    node, as generated by class :class:`epydemic.BANetwork`. This is a
    powerlaw distribution with exponent 3.

    :param M: the number of edges per node added
    :returns: the generating function'''

    return gf_from_coefficient_function(lambda k: (2 * pow(M, 2)) / pow(k, 3))


def gf_plc(exponent: float, cutoff: float) -> GF:
    '''Return the generating function of the powerlaw-with-cutoff
    degree distribution given its exponent and cutoff, as generated
    by :class:`epydemic.PLCNetwork`.

    :param exponent: the exponent of the distribution
    :param cutoff: the cutoff
    :returns: the generating function'''

# 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/interface.py
# def gf_from_series(f: Callable[[float], float]) -> GF:
#     '''Create a generating function from the function that defines
#     the complete series.
# 
#     :param f: the series function
#     :returns: the generating function'''
#     return ContinuousGF(f)
# 
# def gf_from_coefficient_function(f: Callable[[int], float]) -> GF:
#     '''Create a generating function from a function that computes
#     the coefficient for the given term.
# 
#     :param f: the coefficients function
#     :returns: a generating function'''
#     return DiscreteGF(f=f)


# Path: epydemic/gf/standard_gfs.py
from cmath import exp
from mpmath import polylog, zeta
from epydemic.gf import GF, gf_from_series, gf_from_coefficient_function


def gf_powerlaw(exponent: float) -> GF:
    '''Return the generating function of the powerlaw
    degree distribution with the given exponent.

    :param exponent: the exponent of the distribution
    :returns: the generating function'''
    return gf_from_series(lambda x: polylog(exponent, x) / zeta(exponent))


def gf_ba(M: int) -> GF:
    '''Return the generating fuynction of the degree distribution of
    Barabasi-Albert network with connectivity of M edges per added
    node, as generated by class :class:`epydemic.BANetwork`. This is a
    powerlaw distribution with exponent 3.

    :param M: the number of edges per node added
    :returns: the generating function'''

    return gf_from_coefficient_function(lambda k: (2 * pow(M, 2)) / pow(k, 3))


def gf_plc(exponent: float, cutoff: float) -> GF:
    '''Return the generating function of the powerlaw-with-cutoff
    degree distribution given its exponent and cutoff, as generated
    by :class:`epydemic.PLCNetwork`.

    :param exponent: the exponent of the distribution
    :param cutoff: the cutoff
    :returns: the generating function'''

    return gf_from_series(lambda x: polylog(exponent, x * exp(-1 / cutoff)) / polylog(exponent, exp(-1 / cutoff)))