
/**************************************************************************
 *                                                                        *
 *  Regina - A Normal Surface Theory Calculator                           *
 *  Computational Engine                                                  *
 *                                                                        *
 *  Copyright (c) 1999-2025, Ben Burton                                   *
 *  For further details contact Ben Burton (bab@debian.org).              *
 *                                                                        *
 *  This program is free software; you can redistribute it and/or         *
 *  modify it under the terms of the GNU General Public License as        *
 *  published by the Free Software Foundation; either version 2 of the    *
 *  License, or (at your option) any later version.                       *
 *                                                                        *
 *  As an exception, when this program is distributed through (i) the     *
 *  App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or     *
 *  (iii) Google Play by Google Inc., then that store may impose any      *
 *  digital rights management, device limits and/or redistribution        *
 *  restrictions that are required by its terms of service.               *
 *                                                                        *
 *  This program is distributed in the hope that it will be useful, but   *
 *  WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *  General Public License for more details.                              *
 *                                                                        *
 *  You should have received a copy of the GNU General Public License     *
 *  along with this program. If not, see <https://www.gnu.org/licenses/>. *
 *                                                                        *
 **************************************************************************/

#ifndef __REGINA_BINOM_H
#ifndef __DOXYGEN
#define __REGINA_BINOM_H
#endif

/*! \file maths/binom.h
 *  \brief Provides small binomial coefficients.
 */

#include <cstdint>
#include "utilities/exception.h"

namespace regina {

/**
 * Returns the binomial coefficient \a n choose \a k in constant time
 * for small arguments (\a n ≤ 16).
 *
 * This routine is very fast, since it uses a constant-time lookup.
 * The trade-off is that it can only be used for \a n ≤ 16.
 *
 * \note The constraint \a n ≤ 16 is large enough for working with
 * triangulations in Regina, since Regina restricts its triangulations to
 * dimension ≤ 15 (where each simplex has 16 vertices).
 *
 * \nopython As of Regina 7.4, this function is \c consteval which means it
 * cannot be used at runtime, and therefore cannot be used in Python at all.
 * Python users should call binomMedium() instead, which will be just as fast
 * for arguments n ≤ 16.
 *
 * \param n the parameter \a n in (\a n choose \a k); this must be
 * between 0 and 16 inclusive.
 * \param k the parameter \a k in (\a n choose \a k); this must be
 * between 0 and \a n inclusive.
 * \return the binomial coefficient \a n choose \a k.
 *
 * \ingroup maths
 */
consteval int binomSmall(int n, int k);

/**
 * Returns the binomial coefficient \a n choose \a k in linear time
 * for medium-sized arguments (\a n ≤ 61).
 *
 * This routine computes the binomial coefficient using the standard
 * formula.  It works entirely with native integers of a large enough size;
 * the constraint \a n ≤ 61 is designed to avoid overflow (since all
 * intermediate results are guaranteed to stay below 2^63).
 *
 * If \a n ≤ 16 then this routine will use the same constant-time
 * lookup as binomSmall() (i.e., there is no penalty for calling this
 * routine with very small arguments).
 *
 * If \a k is outside the usual range (i.e., \a k is negative or greater
 * than \a n), then this routine will return 0.
 *
 * \exception InvalidArgument The argument \a n is negative or greater than 61.
 *
 * \param n the parameter \a n in (\a n choose \a k); this must be
 * between 0 and 61 inclusive.
 * \param k the parameter \a k in (\a n choose \a k).
 * \return the binomial coefficient \a n choose \a k.
 *
 * \ingroup maths
 */
constexpr int_fast64_t binomMedium(int n, int k);

namespace detail {

/**
 * A lookup table that stores (\a n choose \a k) for all \a n ≤ \a 16.
 *
 * For all values 0 ≤ \a k ≤ \a n ≤ 16, the value
 * \a binomSmall_[\a n][\a k] is the binomial coefficient (\a n choose \a k).
 *
 * This array is used in the implementation of the function binomSmall().
 * End users should call binomSmall() instead of referring to this array
 * directly.
 *
 * \ingroup maths
 */
inline constexpr int binomSmall_[17][17] = {
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
    {1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
    {1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
    {1,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0},
    {1,4,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0},
    {1,5,10,10,5,1,0,0,0,0,0,0,0,0,0,0,0},
    {1,6,15,20,15,6,1,0,0,0,0,0,0,0,0,0,0},
    {1,7,21,35,35,21,7,1,0,0,0,0,0,0,0,0,0},
    {1,8,28,56,70,56,28,8,1,0,0,0,0,0,0,0,0},
    {1,9,36,84,126,126,84,36,9,1,0,0,0,0,0,0,0},
    {1,10,45,120,210,252,210,120,45,10,1,0,0,0,0,0,0},
    {1,11,55,165,330,462,462,330,165,55,11,1,0,0,0,0,0},
    {1,12,66,220,495,792,924,792,495,220,66,12,1,0,0,0,0},
    {1,13,78,286,715,1287,1716,1716,1287,715,286,78,13,1,0,0,0},
    {1,14,91,364,1001,2002,3003,3432,3003,2002,1001,364,91,14,1,0,0},
    {1,15,105,455,1365,3003,5005,6435,6435,5005,3003,1365,455,105,15,1,0},
    {1,16,120,560,1820,4368,8008,11440,12870,11440,8008,4368,1820,560,120,16,1}
};

} // namespace detail

// Inline functions

inline consteval int binomSmall(int n, int k) {
    return detail::binomSmall_[n][k];
}

inline constexpr int_fast64_t binomMedium(int n, int k) {
    if (n < 0 || n > 61)
        throw InvalidArgument("binomMedium() requires 0 ≤ n ≤ 61");
    if (k < 0 || k > n)
        return 0;

    if (n <= 16)
        return detail::binomSmall_[n][k];

    if (k + k > n)
        k = n - k;

    int_fast64_t ans = 1;
    for (int i = 1; i <= k; ++i) {
        ans *= (n + 1 - i);
        ans /= i;
    }
    return ans;
}

} // namespace regina

#endif

