Source code for py3nt.polynomial.binomial
"""Binomial functions and expansions"""
import math
import numpy as np
[docs]
def homogeneous_binomial(a: int, b: int, n: int) -> int:
r"""
Calculate the homogeneous binomial: :math:`a^{n-1}+a^{n-2}b+\ldots+b^{n-1}`.
We do not use :math:`\frac{a^{n}-b^{n}}{a-b}`.
This can easily run into overflow issue.
If :math:`f(n)=\frac{a^{n}-b^{n}}{a-b}`, then :math:`f(n+1)=(a+b)f(n)-ab \cdot f(n-1)`.
If the companion matrix is
.. math::
C =
\begin{pmatrix}
a+b & -ab\\
1 & 0
\end{pmatrix}
then
.. math::
\begin{pmatrix}
f(n)\\
f(n-1)
\end{pmatrix} = C^{n-2}
\begin{pmatrix}
a+b\\
1
\end{pmatrix}
We use matrix exponentiation to calculate :math:`C^{n-2}` in :math:`O(log{n})`.
Parameters
----------
a : ``int``
An integer.
b : ``int``
An integer different than ``a``.
n : ``int``
A positive integer.
Returns
-------
``int``
Value of :math:`a^{n-1}+a^{n-2}b+\ldots+b^{n-1}`.
Raises
------
ValueError
If :math:`n` is not positive or :math:`a=b`.
"""
if a == b:
raise ValueError(f"a: {a} and b: {b} are same. They must be different.")
if n < 1:
raise ValueError(f"n: {n} must be positive.")
if n == 1:
return n
if n == 2:
return a + b
companion = np.array([[a + b, -a * b], [1, 0]])
f_n = np.linalg.matrix_power(a=companion, n=n - 2)
return f_n[0, 0] * (a + b) + f_n[0, 1]
[docs]
def binomial_coefficient(n: int, k: int) -> int:
r"""Calculate a binomial coefficient directly.
Use :math:`\binom{n}{k}=\dfrac{n(n-1)\cdots (n-k+1)}{1\cdots k}`
Parameters
----------
n : ``int``
A non-negative integer.
k : ``int``
A non-negative integer.
Returns
-------
``int``
Value of :math:`\binom{n}{k}`.
Raises
------
``ValueError``
If :math:`n` or :math:`k` is negative.
"""
if n < 0 or k < 0:
raise ValueError(f"`n`: {n} or `k`: {k} is negative.")
if n < k:
return 0
numerator: int = 1
denominator: int = 1
for i in range(1, k + 1):
numerator *= n - i + 1
denominator *= i
gcd = math.gcd(denominator, numerator)
numerator //= gcd
denominator //= gcd
return numerator
[docs]
def generate_binomial_coefficients(n: int) -> np.ndarray:
r"""Generate binomial coefficients :math:`\binom{i}{j}` for :math:`0\leq j\leq i\leq n`
Use Pascal's rule to generate the coefficients.
Parameters
----------
n : ``int``
A non-negative integer.
Returns
-------
``np.ndarray``
A numpy 2d-array with the coefficients.
"""
nCk = np.zeros(shape=(n + 1, n + 1), dtype=int)
for i in range(n + 1):
nCk[i][0] = 1
for j in range(1, i + 1):
nCk[i][j] = nCk[i - 1][j] + nCk[i - 1][j - 1]
return nCk