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)
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))
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 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
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
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'}))
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'}))
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)
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)
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