# Path: epydemic/gf/function_gf.py
# class FunctionGF(GF):
#     '''A generating function whose coefficients are given by a function.
#     The function must be total on the range :math:`[0, n]`.
# 
#     :param generator: a function that generates coefficients
#     :param n: (optional) the largest term in the generating function (defaults to 300)
#     '''
# 
#     def __init__(self, generator: Callable[[int], float], n: int = None):
#         super().__init__()
#         self._coefficients: Callable[[int], float] = generator
#         self._maxTerm = 300 if n is None else 300
# 
#     def getCoefficient(self, i: int) -> float:
#         '''Return the i'th coefficient.
# 
#         :param i: the index
#         :returns: the coefficient of x^i'''
#         return self._coefficients(i)
# 
#     def evaluate(self, x: float) -> float:
#         '''Evaluate the generating function.
# 
#         :param x: the argument
#         :returns: the value of the generating function'''
#         v = 0
#         for i in range(self._maxTerm + 1):
#             v += self[i] * x**i
#         return v
# 
#     def _differentiate(self, f: Callable[[int], float], order: int) -> Callable[[int], float]:
#         '''Return a coefficient function representing the n'th
#         order derivative of the function given.
# 
#         :param f: the coefficient function
#         :param order: the order or derivative
#         :returns: the derivative coefficient function'''
# 
#         def df(i: int) -> float:
#             # low-order terms fall away
#             if i < order:
#                 return 0
# 
#             # high-order terms are transformed
#             m = f(i + order)
#             for j in range(1, order + 1):
#                 m *= (i + j)
#             return m
# 
#         return df
# 
#     def derivative(self, order: int = 1) -> GF:
#         '''Return the requested derivative. This creates a new
#         generating function over the same underlying coefficients,
#         shifted and scaled.
# 
#         :param order: (optional) order of derivative (defaults to 1)
#         :returns: a generating function'''
#         df = self._differentiate(self._coefficients, order)
#         return FunctionGF(df, self._maxTerm)


from collections import Counter
from typing import List, Callable
from networkx import Graph
from epydemic.gf import FunctionGF
            raise TypeError('At most one of a network, list, or coefficient function permitted')

        # switch on the method
        ncoeff = None
        if g is not None:
            # network
            coefficients = self._coefficientsFromNetwork(g)
            generator = self._coefficientsWrapper(coefficients)
            ncoeff = len(coefficients)
        elif coefficients is not None:
            # list of coefficients
            generator = self._coefficientsWrapper(coefficients)
            ncoeff = len(coefficients)
        else:
            # function returning coefficients
            generator = f
            ncoeff = None

        super().__init__(generator, ncoeff)

    def _coefficientsFromNetwork(self, g: Graph) -> List[float]:
        '''Compute the degree histogram.

        :param g: the network
        :returns: the fractional occurrence of each degree'''
        N = g.order()
        seq = sorted([d for (_, d) in g.degree()])
        hist = Counter(seq)
        maxk = max(seq)
        cs = [hist[i] / N for i in range(maxk + 1)]

# Path: epydemic/gf/function_gf.py
# class FunctionGF(GF):
#     '''A generating function whose coefficients are given by a function.
#     The function must be total on the range :math:`[0, n]`.
# 
#     :param generator: a function that generates coefficients
#     :param n: (optional) the largest term in the generating function (defaults to 300)
#     '''
# 
#     def __init__(self, generator: Callable[[int], float], n: int = None):
#         super().__init__()
#         self._coefficients: Callable[[int], float] = generator
#         self._maxTerm = 300 if n is None else 300
# 
#     def getCoefficient(self, i: int) -> float:
#         '''Return the i'th coefficient.
# 
#         :param i: the index
#         :returns: the coefficient of x^i'''
#         return self._coefficients(i)
# 
#     def evaluate(self, x: float) -> float:
#         '''Evaluate the generating function.
# 
#         :param x: the argument
#         :returns: the value of the generating function'''
#         v = 0
#         for i in range(self._maxTerm + 1):
#             v += self[i] * x**i
#         return v
# 
#     def _differentiate(self, f: Callable[[int], float], order: int) -> Callable[[int], float]:
#         '''Return a coefficient function representing the n'th
#         order derivative of the function given.
# 
#         :param f: the coefficient function
#         :param order: the order or derivative
#         :returns: the derivative coefficient function'''
# 
#         def df(i: int) -> float:
#             # low-order terms fall away
#             if i < order:
#                 return 0
# 
#             # high-order terms are transformed
#             m = f(i + order)
#             for j in range(1, order + 1):
#                 m *= (i + j)
#             return m
# 
#         return df
# 
#     def derivative(self, order: int = 1) -> GF:
#         '''Return the requested derivative. This creates a new
#         generating function over the same underlying coefficients,
#         shifted and scaled.
# 
#         :param order: (optional) order of derivative (defaults to 1)
#         :returns: a generating function'''
#         df = self._differentiate(self._coefficients, order)
#         return FunctionGF(df, self._maxTerm)


# Path: epydemic/gf/discrete_gf.py
from collections import Counter
from typing import List, Callable
from networkx import Graph
from epydemic.gf import FunctionGF

            raise TypeError('At most one of a network, list, or coefficient function permitted')

        # switch on the method
        ncoeff = None
        if g is not None:
            # network
            coefficients = self._coefficientsFromNetwork(g)
            generator = self._coefficientsWrapper(coefficients)
            ncoeff = len(coefficients)
        elif coefficients is not None:
            # list of coefficients
            generator = self._coefficientsWrapper(coefficients)
            ncoeff = len(coefficients)
        else:
            # function returning coefficients
            generator = f
            ncoeff = None

        super().__init__(generator, ncoeff)

    def _coefficientsFromNetwork(self, g: Graph) -> List[float]:
        '''Compute the degree histogram.

        :param g: the network
        :returns: the fractional occurrence of each degree'''
        N = g.order()
        seq = sorted([d for (_, d) in g.degree()])
        hist = Counter(seq)
        maxk = max(seq)
        cs = [hist[i] / N for i in range(maxk + 1)]

        return cs