예제 #1
0
파일: hyperbolic.py 프로젝트: rikardn/sympy
 def _eval_expand_trig(self, **hints):
     arg = self.args[0]
     if arg.is_Add:
         from sympy import symmetric_poly
         n = len(arg.args)
         TX = [
             tanh(x, evaluate=False)._eval_expand_trig() for x in arg.args
         ]
         p = [0, 0]  # [den, num]
         for i in range(n + 1):
             p[i % 2] += symmetric_poly(i, TX)
         return p[1] / p[0]
     elif arg.is_Mul:
         from sympy.functions.combinatorial.numbers import nC
         coeff, terms = arg.as_coeff_Mul()
         if coeff.is_Integer and coeff > 1:
             n = []
             d = []
             T = tanh(terms)
             for k in range(1, coeff + 1, 2):
                 n.append(nC(range(coeff), k) * T**k)
             for k in range(0, coeff + 1, 2):
                 d.append(nC(range(coeff), k) * T**k)
             return Add(*n) / Add(*d)
     return tanh(arg)
예제 #2
0
def _per(M):
    """Returns the permanent of a matrix. Unlike determinant,
    permanent is defined for both square and non-square matrices.

    For an m x n matrix, with m less than or equal to n,
    it is given as the sum over the permutations s of size
    less than or equal to m on [1, 2, . . . n] of the product
    from i = 1 to m of M[i, s[i]]. Taking the transpose will
    not affect the value of the permanent.

    In the case of a square matrix, this is the same as the permutation
    definition of the determinant, but it does not take the sign of the
    permutation into account. Computing the permanent with this definition
    is quite inefficient, so here the Ryser formula is used.

    Examples
    ========

    >>> from sympy import Matrix
    >>> M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    >>> M.per()
    450
    >>> M = Matrix([1, 5, 7])
    >>> M.per()
    13

    References
    ==========

    .. [1] Prof. Frank Ben's notes: https://math.berkeley.edu/~bernd/ban275.pdf
    .. [2] Wikipedia article on Permanent: https://en.wikipedia.org/wiki/Permanent_(mathematics)
    .. [3] https://reference.wolfram.com/language/ref/Permanent.html
    .. [4] Permanent of a rectangular matrix : https://arxiv.org/pdf/0904.3251.pdf
    """
    import itertools

    m, n = M.shape
    if m > n:
        M = M.T
        m, n = n, m
    s = list(range(n))

    subsets = []
    for i in range(1, m + 1):
        subsets += list(map(list, itertools.combinations(s, i)))

    perm = 0
    for subset in subsets:
        prod = 1
        sub_len = len(subset)
        for i in range(m):
            prod *= sum([M[i, j] for j in subset])
        perm += prod * (-1)**sub_len * nC(n - sub_len, m - sub_len)
    perm *= (-1)**m
    perm = sympify(perm)
    return perm.simplify()
def example(seed=None):
    random.seed(seed)
    activity = get_activity()
    number_people = random.randint(0, 1000)
    team_size = random.randint(0, number_people)

    first_sentence = "Out of " + str(
        number_people) + " we want to form a team of " + str(
            team_size) + " to play " + str(activity) + "."
    second_sentence = "How many such distinct teams can be formed?"
    question = seqg(first_sentence, second_sentence)

    ans = nC(number_people, team_size)
    answer = choiceg(ans)
    q, a = make_qa_pair(question, answer, {})
    print('question: %s \nanswer: %s' % (q, a))
예제 #4
0
def probability(n):
    """ Calculates the probability of an n-sided die to have all numbers
        in the list from 1 through n in a trial of n throws
        Input n : n-sided die
        Output prob : probability
    """

    if not isinstance(n, int):
        raise ValueError('Input not of integer instance')

    if n <= 0:
        raise ValueError('Input not a positive integer')

    exclusions_list = [(nC(n, r) * ((n - r)**n)) for r in range(n)]
    nr = reduce(operator.add,
                [x * y for x, y in izip(exclusions_list, cycle([1, -1]))])
    dr = n**n
    prob = mpmath.mpf(nr) / mpmath.mpf(dr)
    return prob
예제 #5
0
def probability(n):
    """ Calculates the probability of an n-sided die to have all numbers
        in the list from 1 through n in a trial of n throws
        Input n : n-sided die
        Output prob : probability
    """

    if not isinstance(n, int):
        raise ValueError('Input not of integer instance')

    if n <= 0:
        raise ValueError('Input not a positive integer')

    exclusions_list = [(nC(n, r) * ((n-r)**n)) for r in range(n)]
    nr = reduce(operator.add,
                [x*y for x, y in izip(exclusions_list, cycle([1, -1]))])
    dr = n**n
    prob = mpmath.mpf(nr)/mpmath.mpf(dr)
    return prob
    def A(
        s, total_num_people, group_size, basketball
    ):  #TODO change the signature of the function according to your answer
        '''
        Small answer description.

        Important Note: first variables are the not consistent variables followed
        by the consistent ones. See sample QA example if you need too.
        '''
        #define some short cuts
        seqg, perg, choiceg = s.seqg, s.perg, s.choiceg
        # TODO
        #ans_sympy
        #ans_numerical
        #ans_vnl_vsympy1
        #ans_vnl_vsympy2
        # choices, try providing a few
        # these can include variations that are hard to encode with permg or variable substitution
        # example, NL variations or equaiton variations
        ans = seqg(nC(total_num_people, group_size))
        ans_vnl = "{} distinct teams can be formed.".format(ans)
        a = choiceg(ans, ans_vnl)
        return a
예제 #7
0
def dice_probability(n):
    """ Calculates the probability of an n-sided die to have all numbers
        in the list from 1 through n in a trial of n throws
        Input n : n-sided dies (Should be more than 1 and up to 120)
        Output probability : dice_probability
    """

    if not isinstance(n, int):
        raise ValueError('Input not of integer instance')
    if n < 1:
        raise ValueError('Input not a positive integer')
    if n == 1:
        raise ValueError('A dice can not have only 1 side. Minimum n =2')
    if n > 120:
        raise ValueError('A dice can not have more than 120 sides')
    if n == '':
        raise ValueError('Input can not be empty')

    exclusions_list = [(nC(n, r) * ((n - r)**n)) for r in range(n)]
    nr = reduce(operator.add,
                [x * y for x, y in zip(exclusions_list, cycle([1, -1]))])
    dr = n**n
    probability = mpmath.mpf(nr) / mpmath.mpf(dr)
    return probability
예제 #8
0
def test_nC_nP_nT():
    from sympy.utilities.iterables import (multiset_permutations,
                                           multiset_combinations,
                                           multiset_partitions, partitions,
                                           subsets, permutations)
    from sympy.functions.combinatorial.numbers import (nP, nC, nT, stirling,
                                                       _multiset_histogram,
                                                       _AOP_product)
    from sympy.combinatorics.permutations import Permutation
    from sympy.core.numbers import oo
    from random import choice

    c = string.ascii_lowercase
    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(8):
                check = nP(s, i)
                tot += check
                assert len(list(multiset_permutations(s, i))) == check
                if u:
                    assert nP(len(s), i) == check
            assert nP(s) == tot
        except AssertionError:
            print(s, i, 'failed perm test')
            raise ValueError()

    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(8):
                check = nC(s, i)
                tot += check
                assert len(list(multiset_combinations(s, i))) == check
                if u:
                    assert nC(len(s), i) == check
            assert nC(s) == tot
            if u:
                assert nC(len(s)) == tot
        except AssertionError:
            print(s, i, 'failed combo test')
            raise ValueError()

    for i in range(1, 10):
        tot = 0
        for j in range(1, i + 2):
            check = nT(i, j)
            tot += check
            assert sum(1 for p in partitions(i, j, size=True)
                       if p[0] == j) == check
        assert nT(i) == tot

    for i in range(1, 10):
        tot = 0
        for j in range(1, i + 2):
            check = nT(range(i), j)
            tot += check
            assert len(list(multiset_partitions(list(range(i)), j))) == check
        assert nT(range(i)) == tot

    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(1, 8):
                check = nT(s, i)
                tot += check
                assert len(list(multiset_partitions(s, i))) == check
                if u:
                    assert nT(range(len(s)), i) == check
            if u:
                assert nT(range(len(s))) == tot
            assert nT(s) == tot
        except AssertionError:
            print(s, i, 'failed partition test')
            raise ValueError()

    # tests for Stirling numbers of the first kind that are not tested in the
    # above
    assert [stirling(9, i, kind=1) for i in range(11)
            ] == [0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1, 0]
    perms = list(permutations(range(4)))
    assert [
        sum(1 for p in perms if Permutation(p).cycles == i) for i in range(5)
    ] == [0, 6, 11, 6, 1] == [stirling(4, i, kind=1) for i in range(5)]
    # http://oeis.org/A008275
    assert [
        stirling(n, k, signed=1) for n in range(10) for k in range(1, n + 1)
    ] == [
        1, -1, 1, 2, -3, 1, -6, 11, -6, 1, 24, -50, 35, -10, 1, -120, 274,
        -225, 85, -15, 1, 720, -1764, 1624, -735, 175, -21, 1, -5040, 13068,
        -13132, 6769, -1960, 322, -28, 1, 40320, -109584, 118124, -67284,
        22449, -4536, 546, -36, 1
    ]
    # https://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind
    assert [stirling(n, k, kind=1) for n in range(10)
            for k in range(n + 1)] == [
                1, 0, 1, 0, 1, 1, 0, 2, 3, 1, 0, 6, 11, 6, 1, 0, 24, 50, 35,
                10, 1, 0, 120, 274, 225, 85, 15, 1, 0, 720, 1764, 1624, 735,
                175, 21, 1, 0, 5040, 13068, 13132, 6769, 1960, 322, 28, 1, 0,
                40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1
            ]
    # https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind
    assert [stirling(n, k, kind=2) for n in range(10)
            for k in range(n + 1)] == [
                1, 0, 1, 0, 1, 1, 0, 1, 3, 1, 0, 1, 7, 6, 1, 0, 1, 15, 25, 10,
                1, 0, 1, 31, 90, 65, 15, 1, 0, 1, 63, 301, 350, 140, 21, 1, 0,
                1, 127, 966, 1701, 1050, 266, 28, 1, 0, 1, 255, 3025, 7770,
                6951, 2646, 462, 36, 1
            ]
    assert stirling(3, 4, kind=1) == stirling(3, 4, kind=1) == 0
    raises(ValueError, lambda: stirling(-2, 2))

    def delta(p):
        if len(p) == 1:
            return oo
        return min(abs(i[0] - i[1]) for i in subsets(p, 2))

    parts = multiset_partitions(range(5), 3)
    d = 2
    assert (sum(1
                for p in parts if all(delta(i) >= d
                                      for i in p)) == stirling(5, 3, d=d) == 7)

    # other coverage tests
    assert nC('abb', 2) == nC('aab', 2) == 2
    assert nP(3, 3, replacement=True) == nP('aabc', 3, replacement=True) == 27
    assert nP(3, 4) == 0
    assert nP('aabc', 5) == 0
    assert nC(4, 2, replacement=True) == nC('abcdd', 2, replacement=True) == \
        len(list(multiset_combinations('aabbccdd', 2))) == 10
    assert nC('abcdd') == sum(nC('abcdd', i) for i in range(6)) == 24
    assert nC(list('abcdd'), 4) == 4
    assert nT('aaaa') == nT(4) == len(list(partitions(4))) == 5
    assert nT('aaab') == len(list(multiset_partitions('aaab'))) == 7
    assert nC('aabb' * 3, 3) == 4  # aaa, bbb, abb, baa
    assert dict(_AOP_product((4, 1, 1, 1))) == {
        0: 1,
        1: 4,
        2: 7,
        3: 8,
        4: 8,
        5: 7,
        6: 4,
        7: 1
    }
    # the following was the first t that showed a problem in a previous form of
    # the function, so it's not as random as it may appear
    t = (3, 9, 4, 6, 6, 5, 5, 2, 10, 4)
    assert sum(_AOP_product(t)[i] for i in range(55)) == 58212000
    raises(ValueError, lambda: _multiset_histogram({1: 'a'}))
예제 #9
0
def test_nC_nP_nT():
    from sympy.utilities.iterables import (
        multiset_permutations, multiset_combinations, multiset_partitions,
        partitions, subsets, permutations)
    from sympy.functions.combinatorial.numbers import (
        nP, nC, nT, stirling, _multiset_histogram, _AOP_product)
    from sympy.combinatorics.permutations import Permutation
    from sympy.core.numbers import oo
    from random import choice

    c = string.ascii_lowercase
    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(8):
                check = nP(s, i)
                tot += check
                assert len(list(multiset_permutations(s, i))) == check
                if u:
                    assert nP(len(s), i) == check
            assert nP(s) == tot
        except AssertionError:
            print(s, i, 'failed perm test')
            raise ValueError()

    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(8):
                check = nC(s, i)
                tot += check
                assert len(list(multiset_combinations(s, i))) == check
                if u:
                    assert nC(len(s), i) == check
            assert nC(s) == tot
            if u:
                assert nC(len(s)) == tot
        except AssertionError:
            print(s, i, 'failed combo test')
            raise ValueError()

    for i in range(1, 10):
        tot = 0
        for j in range(1, i + 2):
            check = nT(i, j)
            tot += check
            assert sum(1 for p in partitions(i, j, size=True) if p[0] == j) == check
        assert nT(i) == tot

    for i in range(1, 10):
        tot = 0
        for j in range(1, i + 2):
            check = nT(range(i), j)
            tot += check
            assert len(list(multiset_partitions(range(i), j))) == check
        assert nT(range(i)) == tot

    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(1, 8):
                check = nT(s, i)
                tot += check
                assert len(list(multiset_partitions(s, i))) == check
                if u:
                    assert nT(range(len(s)), i) == check
            if u:
                assert nT(range(len(s))) == tot
            assert nT(s) == tot
        except AssertionError:
            print(s, i, 'failed partition test')
            raise ValueError()

    # tests for Stirling numbers of the first kind that are not tested in the
    # above
    assert [stirling(9, i, kind=1) for i in range(11)] == [
        0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1, 0]
    perms = list(permutations(range(4)))
    assert [sum(1 for p in perms if Permutation(p).cycles == i)
            for i in range(5)] == [0, 6, 11, 6, 1] == [
            stirling(4, i, kind=1) for i in range(5)]
    # http://oeis.org/A008275
    assert [stirling(n, k, signed=1)
        for n in range(10) for k in range(1, n + 1)] == [
            1, -1,
            1, 2, -3,
            1, -6, 11, -6,
            1, 24, -50, 35, -10,
            1, -120, 274, -225, 85, -15,
            1, 720, -1764, 1624, -735, 175, -21,
            1, -5040, 13068, -13132, 6769, -1960, 322, -28,
            1, 40320, -109584, 118124, -67284, 22449, -4536, 546, -36, 1]
    # http://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind
    assert  [stirling(n, k, kind=1)
        for n in range(10) for k in range(n+1)] == [
            1,
            0, 1,
            0, 1, 1,
            0, 2, 3, 1,
            0, 6, 11, 6, 1,
            0, 24, 50, 35, 10, 1,
            0, 120, 274, 225, 85, 15, 1,
            0, 720, 1764, 1624, 735, 175, 21, 1,
            0, 5040, 13068, 13132, 6769, 1960, 322, 28, 1,
            0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1]
    # http://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind
    assert [stirling(n, k, kind=2)
        for n in range(10) for k in range(n+1)] == [
            1,
            0, 1,
            0, 1, 1,
            0, 1, 3, 1,
            0, 1, 7, 6, 1,
            0, 1, 15, 25, 10, 1,
            0, 1, 31, 90, 65, 15, 1,
            0, 1, 63, 301, 350, 140, 21, 1,
            0, 1, 127, 966, 1701, 1050, 266, 28, 1,
            0, 1, 255, 3025, 7770, 6951, 2646, 462, 36, 1]
    assert stirling(3, 4, kind=1) == stirling(3, 4, kind=1) == 0
    raises(ValueError, lambda: stirling(-2, 2))

    def delta(p):
        if len(p) == 1:
            return oo
        return min(abs(i[0] - i[1]) for i in subsets(p, 2))
    parts = multiset_partitions(range(5), 3)
    d = 2
    assert (sum(1 for p in parts if all(delta(i) >= d for i in p)) ==
            stirling(5, 3, d=d) == 7)

    # other coverage tests
    assert nC('abb', 2) == nC('aab', 2) == 2
    assert nP(3, 3, replacement=True) == nP('aabc', 3, replacement=True) == 27
    assert nP(3, 4) == 0
    assert nP('aabc', 5) == 0
    assert nC(4, 2, replacement=True) == nC('abcdd', 2, replacement=True) == \
        len(list(multiset_combinations('aabbccdd', 2))) == 10
    assert nC('abcdd') == sum(nC('abcdd', i) for i in range(6)) == 24
    assert nC(list('abcdd'), 4) == 4
    assert nT('aaaa') == nT(4) == len(list(partitions(4))) == 5
    assert nT('aaab') == len(list(multiset_partitions('aaab'))) == 7
    assert nC('aabb'*3, 3) == 4  # aaa, bbb, abb, baa
    assert dict(_AOP_product((4,1,1,1))) == {
        0: 1, 1: 4, 2: 7, 3: 8, 4: 8, 5: 7, 6: 4, 7: 1}
    # the following was the first t that showed a problem in a previous form of
    # the function, so it's not as random as it may appear
    t = (3, 9, 4, 6, 6, 5, 5, 2, 10, 4)
    assert sum(_AOP_product(t)[i] for i in range(55)) == 58212000
    raises(ValueError, lambda: _multiset_histogram({1:'a'}))
예제 #10
0
    def stem(self, a_type=None, max_size=10000, choose_style=None):
        """
        This generates problems asking the students to find the coefficent on the x^n*y^m
        term in the expansion of (ax + by)^p where m + n = p.
        
        Parameters:
        ---------
            a_type       : This determines if the answer is multiple choice (MC) or free response (FR)
            max_size     : Don't accept problems whose answer is bigger in magnitude than this.
            choose_style : (integer index into choose_styles = ['{%s \\choose %s}', 'C(%s,%s)', '{}_%s C_%s']). 
                           If choose_style is < len(choose_styles), then it chooses the associated style, 
                           else it chooses randomly
        """
        
        # Make a copy of current args for later use.
        
        kwargs = {
                    'a_type': a_type,
                    'max_size': max_size,
                    'choose_style': choose_style
                 }
        
        # If an answer is bigger than this generate a new problem
        THREASH = 85000 
        
        x, y = sym.symbols('x, y')

        self.count += 1
        
        if self.count > 200:
            print "too many iterations"
            return None

        if a_type == None:
            a_type = random.choice(["MC", "FR"])
            

        # Choose some coefficients a, b for (ax + by)^p
        coeffs = range(-7, 8)
        coeffs.remove(0)
        a, b = [random.choice(coeffs) for i in range(2)]
        # Choose a power p in [5,8]
        power = random.randint(4, 8)
        x_pow = random.randint(2, power - 2)
        y_pow = power - x_pow
        

        if (a_type, a, b, x_pow, y_pow, power) in self.done:
            return self.stem(**kwargs)
        
        self.done.append((a_type, a, b, x_pow, y_pow))

        
        question_stem = "Find the coefficient of $_x^{%s}y^{%s}$_ in the expansion of \
        $_(%s)^{%s}$_." % (x_pow, y_pow, sym.latex(sym.simplify(a * x + b * y)), power)

        answer = nC(power, x_pow) * a ** x_pow * b ** y_pow

        if abs(answer) > THREASH:
            return self.stem(**kwargs)

        # Chose the "choose" --- Oh have to be a little tricky here since at one place
        # we have choose(n,k) and another we have choose(%s,$s)%(power,x_pow) 
        
        choose_styles = ['{%s \\choose %s}', 'C(%s,%s)', '{}_%s C_%s']
        
        if choose_style not in range(len(choose_styles)):
            choose_ = random.choice(choose_styles)
        else:
            choose_ = choose_styles[choose_style]
            
        def choose(a , b):
            choice = choose_ % (a, b)
            return choice
        
        explanation = "According to the Binomial Theorem," \
                      "$$(a x + b y)^n = \\sum_{k = 0}^{n} %s \\cdot (ax)^k(by)^{n - k}$$" \
                      "In this case, $_n = %d$_, $_a = %d$_, $_b = %d$_, and $_k = %d$_, " \
                      "so the coefficient is" % (choose('n', 'k'), power, a, b, x_pow)
        explanation += tools.align("%s \\cdot (%s)^{%s} \\cdot (%s)^{%s}"
                                   % (choose(power, x_pow), a, x_pow, b, y_pow),
                                   "\\frac{%s!}{%s!\\,%s!} \\cdot (%s)^{%s} \\cdot (%s)^{%s}"
                                   % (power, x_pow, power, a, x_pow, b, y_pow),
                                   sym.latex(answer))

        if a_type == "MC":
            errors = list(set([nC(power + i, x_pow + i // 2) * a ** x_pow * b ** y_pow for i in range(-3, 4) if i != 0 and x_pow + i // 2 > 0]))
            errors += [int(-answer * (1.02)), int(answer * (1.02)), int(-answer * (0.98)), int(answer * (0.98))]
            errors = [e for e in errors if e != answer]
            random.shuffle(errors)
            errors = errors[:4]  # This provides 4 distractions


        if a_type == "FR":
            question_stem += " Give your answer as an integer."
            answer_mathml = tools.fraction_mml(answer)
            return tools.fully_formatted_question(question_stem, explanation, answer_mathml)
        else:
            distractors = [answer] + errors
            distractors = ["$_%s$_" % sym.latex(distractor) for distractor in distractors]
            return tools.fully_formatted_question(question_stem, explanation, answer_choices=distractors)
예제 #11
0
파일: FactCombPerm.py 프로젝트: ketchers/LO
 def stem(self, q_type = None, a_type = None, include_PC = False, 
          seed = datetime.datetime.now().microsecond, perm_style = None, choose_style = None):
     """
     There are three possible types of problems: F = factorials, e.g. 5!, P = 
     permutations, e.g. 5!/3! = (5 - 2)!, and C = combinations, e.g. 5!/(3!2!).
     
     Named Parameters:
     
         q_type     -- "Fact", "Perm", or "Comb" for factorial, permutation, or combination
         a_type     -- "MC" or "FR" for multiplechoice and free response
         include_PC -- This is a boolean. If true include mention of permutations/combinations
                       in both problems and explanations.
         perm_style -- perm_styles = ['P(%s,%s)', '{}_%s P_%s'], if choose_style
                       is < len(choose_styles), then it chooses the associated style, 
                       else it chooses randomly
                       
         choose_style -- choose_styles = ['{%s \\choose %s}', 'C(%s,%s)', '{}_%s C_%s'], if choose_style
                         is < len(choose_styles), then it chooses the associated style, else it 
                         chooses randomly   
         seed         -- A seed for the random ops, to make reproducible output
     """
     
     # Assume if the user sets the perm_style or choose_style, assume include_PC should be True
     if perm_style is not None or choose_style is not None:
         include_PC = True
     
     kwargs = {
                 'q_type': q_type,
                 'a_type': a_type,
                 'include_PC': include_PC,
                 'perm_style': perm_style,
                 'choose_style': choose_style,
              }
     
     
     # These numbers can get big lets set a threashold
     THREASH = 300000
     
     # If we exceed the threashold restart with current settings
     ## I wish I had a better wa to get current values???
     def check():
         if answer > THREASH:
             return self.stem(**kwargs)
         else: 
             pass
     
     q_type_orig = q_type
     a_type_orig = a_type
     
     # About 1/5 simple factorials, 2/5 perms, 3/5 combinations
     if q_type == None:
         q_type = random.choice(["Fact"] + ["Perm"] * 2 + ["Comb"] * 3)
     
     if a_type == None:
         a_type = random.choice(["MC", "FR"])
             
     # Actually set the data for the problem
     if q_type == "Fact":
         N = random.randint(5,7) # Change these to modify range
         R = 0
     else:
         N = random.randint(7,11) # Change these to modify range
         R = random.randint(3, N - 4) # To make the explanations work it is good to have N - R > 3
         
         
     
     # Choose the "choose" --- Oh have to be a little tricky here since at one place
     # we have choose(n,k) and another we have choose(%s,$s)%(power,x_pow) 
     
     choose_styles = ['{%s \\choose %s}', 'C(%s,%s)', '{}_{%s} C_{%s}']
     perm_styles = ['P(%s,%s)', '{}_{%s} P_{%s}']
     
     if choose_style not in range(len(choose_styles)):
         choose_ = random.choice(choose_styles)
     else:
         choose_ = choose_styles[choose_style]
         
         
     # Now choose the "perm"
     
     if perm_style not in range(len(perm_styles)):
         perm_ = random.choice(perm_styles)
     else:
         perm_ = perm_styles[perm_style]
         
     
     def choose(a ,b):
         if q_type == "Comb":
             choice =  choose_ % (a, b)
         else:
             choice =  perm_ % (a, b)
         return choice
     
      
     
     
     q_data = (q_type, N, R) # This determines the  problem
     
     if q_data in self.done:
         return self.stem(q_type_orig, a_type_orig, include_PC) # Try again
     else:
         self.done.append(q_data)  # Mark this data as being used
         
    
     # If include_PC is True, this will be overriden below.
     question_stem_options = ["Evaluate the following expression involving factorials."]
     question_stem = random.choice(question_stem_options)
     
     explanation = "Recall the definition: "
     
     
     if q_type == "Fact":
         
         answer = sym.factorial(N)
         check()
         
         if a_type == "MC":
             errors = [sym.factorial(N+1), sym.factorial(N-1), 
                       sym.factorial(N)/sym.factorial(random.randint(1,N-1))]
         
         question_stem += "$$%s!$$" % (N)
         
         explanation += "$_%s! = %s = %s$_" % (N, FactCombPerm.format_fact(N), answer)
                                      
     elif q_type == "Perm":
         
         if include_PC == True:
             question_stem = "Evaluate the following permutation."
         
         answer = nP(N, N - R)
         check()
         
         
         if a_type == "MC":
             
             errors = list(set([nP(N + i, N - (R + i)) for i in range(-3,4) if i != 0]))
             errors = [e for e in errors if e != answer]
             random.shuffle(errors)
             errors = errors[:4] # This provides 4 distractions
             
             
         
         if include_PC:
             explanation_prefix = "%s = \\frac{%s!}{(%s - %s)!} =" % (choose(N, N - R), N, N, N - R)
         else:
             explanation_prefix = ""
     
         explanation += "$_%s\\frac{%s!}{%s!} = \\frac{%s}{%s} =  %s = %s$_" \
         % (explanation_prefix, N, R, FactCombPerm.format_fact(N), 
            FactCombPerm.format_fact(R), FactCombPerm.format_fact(N, R+1), answer)
       
         if include_PC:
             question_stem += "$$%s$$" %(choose(N, N - R))
         else:
             question_stem += "$$\\frac{%s!}{%s!}$$" % (N, R)
         
     else:
         
         if include_PC == True:
             question_stem = "Evaluate the following combination."
             
         answer = nC(N, R) 
         check()
         
         if a_type == "MC":
             errors = list(set([nC(N + i, R + i // 2) for i in range(-3,4) if i != 0 and R + i // 2 > 0]))
             errors = [e for e in errors if e != answer]
             random.shuffle(errors)
             errors = errors[:4] # This provides 4 distractions
             
             
         if include_PC:
             explanation_prefix = "%s = \\frac{%s!}{%s!\\,(%s - %s)!} = " % (choose(N, R), N, R, N, R)
         else:
             explanation_prefix = ""
     
       
         if R >= N - R:
             explanation += tools.align("%s\\frac{%s!}{%s!\,%s!}" \
                                        % (explanation_prefix, N, R, N-R),
                                        "\\frac{%s}{(%s)(%s)}" \
                                        % (FactCombPerm.format_fact(N), FactCombPerm.format_fact(R), 
                                           FactCombPerm.format_fact(N-R)), 
                                        "\\frac{%s}{%s} = %s" \
                                        % (FactCombPerm.format_fact(N, R + 1), FactCombPerm.format_fact(N - R), answer))
         else:
             explanation += tools.align("%s\\frac{%s!}{%s!\,%s!}" \
                                        % (explanation_prefix, N, R, N-R),
                                        "\\frac{%s}{(%s)(%s)}" \
                                        % (FactCombPerm.format_fact(N), FactCombPerm.format_fact(R),
                                           FactCombPerm.format_fact(N-R)), 
                                        "\\frac{%s}{%s} = %s" \
                                        % (FactCombPerm.format_fact(N, (N - R) + 1), FactCombPerm.format_fact(R), answer))
             
             
         
         if include_PC:
             question_stem += "$$%s$$" % (choose(N, R))
         else:
             question_stem += "$$\\frac{%s!}{%s!\\,%s!}$$" % (N, R, N - R)
     
         explanation += "<br>"                           
 
 
 
     if a_type == "FR":
         question_stem += "Give your answer as an integer."
         answer_mathml = tools.fraction_mml(answer)
         return tools.fully_formatted_question(question_stem, explanation, answer_mathml)
     else:
         distractors = [answer] + errors
         distractors = ["$_%s$_" % sym.latex(distractor) for distractor in distractors]
         return tools.fully_formatted_question(question_stem, explanation, answer_choices=distractors)
예제 #12
0
def compute_cmbns_perfect_cj(nb_i, nb_o):
    '''
    Computes the number of combinations
    for a perfect coinjoin with nb_i inputs and nb_o outputs.

    A perfect coinjoin is defined as a transaction for which:
      - all inputs have the same amount
      - all outputs have the same amount
      - 0 fee are paid (equiv. to same fee paid by each input)
      - nb_i % nb_o == 0, if nb_i >= nb_o
        or
        nb_o % nb_i == 0, if nb_o >= nb_i

    Returns the number of combinations

    Parameters:
        nb_i = number of inputs
        nb_o = number of outputs

    Notes:
    Since all inputs have the same amount
    we can use exponential Bell polynomials to retrieve
    the number and structure of partitions for the set of inputs.

    Since all outputs have the same amount
    we can use a direct computation of combinations of k outputs among n.
    '''
    # Reverses inputs & outputs if nb_i > nb_o
    # (required for the use of EBP)
    if nb_i > nb_o:
        nb_i, nb_o = nb_o, nb_i

    # Checks structure of perfect coinjoin tx
    # (we must have an integer ratio between nb_o and nb_i)
    if nb_o % nb_i != 0:
        return None

    # Checks if we can use precomputed values
    if (nb_i <= 1) or (nb_o <= 1):
        return 1
    elif (nb_i <= 20) and (nb_o <= 60):
        return NB_CMBN_PRFCT_CJ[(nb_i, nb_o)]

    # Initializes the total number of combinations for the tx
    nb_cmbn = 0

    # Computes the ratio between #outputs and #inputs
    ratio_o_i = float(nb_o) / nb_i

    # Iterates over partioning of inputs in k_i parts
    for k_i in range(1, nb_i + 1):
        parts_k_i = bell(nb_i, k_i, symbols('x:%d' % (nb_i + 1))[1:])

        # Splits the Bell polynomial in its basic components
        dict_coeffs = parts_k_i.as_coefficients_dict()

        for monomial, coef_monomial in dict_coeffs.items():
            nb_cmbn_monomial = coef_monomial

            # Splits the monomial in its basic blocks
            # and extracts the exponent for each block
            dict_exp = monomial.as_powers_dict()

            # Initializes number of remaining free outputs
            # for computation of output combinations
            nb_free_o = nb_o

            for block, block_exp in dict_exp.items():
                block_nb_i = int(str(block)[1:])
                block_nb_o = int(ratio_o_i * block_nb_i)
                for _ in range(1, block_exp + 1):
                    nb_cmbn_monomial *= nC(symbols('x:%d' % nb_free_o),
                                           block_nb_o)
                    nb_free_o -= block_nb_o

            nb_cmbn += nb_cmbn_monomial

    return nb_cmbn