Source code for mathmaker.lib.tools.maths

# -*- coding: utf-8 -*-

# Mathmaker creates automatically maths exercises sheets
# with their answers
# Copyright 2006-2017 Nicolas Hainaux <nh.techn@gmail.com>

# This file is part of Mathmaker.

# Mathmaker 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 3 of the License, or
# any later version.

# Mathmaker 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 Mathmaker; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import sys
import math
import random
from decimal import Decimal

from mathmakerlib.calculus import is_integer, is_number


# DIVISORS frequently used by the children
# (the most "exotic" couples (like 4×16) have been withdrawn so that the
# the fractions can be reduced in a way that will look natural to children.
# There should be enough couples left to bring the reduction to fruition, even
# if in several steps. Some "exotic" couples have been kept because they are
# necessary (like 7×13, 5×17...)
# The divisor 1 in the divisors of 0 and 1 has been written twice to avoid
# getting an error on asking the length of an unsized object
DIVISORS = [(1, 1), (1, 1), (2, 1), (3, 1), (4, 2, 1), (5, 1), (6, 3, 2, 1),
            (7, 1), (8, 4, 2, 1), (9, 3, 1), (10, 5, 2, 1), (11, 1),
            (12, 6, 4, 3, 2, 1), (13, 1), (14, 7, 2, 1),
            (15, 5, 3, 1), (16, 8, 4, 2, 1), (17, 1), (18, 9, 6, 3, 2, 1),
            (19, 1), (20, 10, 5, 4, 2, 1), (21, 7, 3, 1), (22, 11, 2, 1),
            (23, 1), (24, 12, 8, 6, 4, 3, 2, 1), (25, 5, 1), (26, 13, 2, 1),
            (27, 9, 3, 1), (28, 7, 4, 2, 1), (29, 1),
            (30, 15, 10, 6, 5, 3, 2, 1), (31, 1), (32, 16, 8, 4, 2, 1),
            (33, 11, 3, 1), (34, 17, 2, 1), (35, 7, 5, 1),
            (36, 18, 12, 9, 6, 4, 3, 2, 1), (37, 1), (38, 19, 2, 1),
            (39, 13, 3, 1), (40, 20, 10, 8, 5, 4, 2, 1), (41, 1),
            (42, 21, 7, 6, 2, 1), (43, 1), (44, 22, 11, 4, 2, 1),
            (45, 9, 5, 1), (46, 23, 2, 1), (47, 1), (48, 24, 8, 6, 2, 1),
            (49, 7, 1), (50, 25, 10, 5, 2, 1), (51, 17, 3, 1),
            (52, 26, 13, 4, 2, 1), (53, 1), (54, 27, 9, 6, 2, 1),
            (55, 11, 5, 1), (56, 28, 8, 7, 2, 1), (57, 19, 3, 1),
            (58, 29, 2, 1), (59, 1),
            (60, 30, 20, 15, 12, 10, 6, 5, 4, 3, 2, 1), (61, 1),
            (62, 31, 2, 1), (63, 21, 9, 7, 3, 1), (64, 32, 8, 2, 1),
            (65, 13, 5, 1), (66, 33, 2, 1), (67, 1), (68, 34, 17, 4, 2, 1),
            (69, 23, 3, 1), (70, 35, 10, 7, 5, 2, 1), (71, 1),
            (72, 12, 9, 8, 6, 2, 1), (73, 1), (74, 37, 2, 1), (75, 15, 5, 1),
            (76, 38, 2, 1), (77, 11, 7, 1), (78, 39, 26, 3, 2, 1), (79, 1),
            (80, 40, 20, 10, 8, 4, 2, 1), (81, 9, 1), (82, 41, 2, 1), (83, 1),
            (84, 42, 2, 1), (85, 17, 5, 1), (86, 43, 2, 1), (87, 29, 3, 1),
            (88, 44, 22, 11, 8, 4, 2, 1), (89, 1),
            (90, 45, 30, 10, 9, 3, 2, 1), (91, 13, 7, 1), (92, 46, 2, 1),
            (93, 31, 3, 1), (94, 47, 2, 1), (95, 19, 5, 1), (96, 48, 2, 1),
            (97, 1), (98, 49, 2, 1), (99, 33, 11, 9, 3, 1),
            (100, 50, 25, 10, 4, 2, 1)]


POLYGONS_NATURES = {3: 'Triangle', 4: 'Quadrilatere', 5: 'Pentagon',
                    6: 'Hexagon', 7: 'Heptagon', 8: 'Octogon',
                    9: 'Enneagon', 10: 'Decagon'}

# CONSTANTS CONCERNING MATH OBJECTS
ZERO_POLYNOMIAL_DEGREE = -sys.maxsize


# --------------------------------------------------------------------------
##
#   @brief Returns the sign of the product of relatives numbers
#   @param signed_objctlist A list of any objects having a sign
#   @return A sign ('+' ou '-')
[docs]def sign_of_product(signed_objctlist): from mathmaker.lib.core.base_calculus import Exponented if not(type(signed_objctlist) == list): raise TypeError('Expected a list, got a ' + str(type(signed_objctlist)) + 'instead') if not(len(signed_objctlist) >= 1): raise ValueError('This list shouldn\'t be empty') minus_signs_nb = 0 for i in range(len(signed_objctlist)): if not (signed_objctlist[i] in ['+', '-'] or is_number(signed_objctlist[i]) or isinstance(signed_objctlist[i], Exponented)): # __ raise TypeError('Expected a sign + or -, or a number, or an ' 'Exponented. Got a ' + str(type(signed_objctlist[i])) + ' instead') elif signed_objctlist[i] == '-': minus_signs_nb += 1 elif is_number(signed_objctlist[i]) and signed_objctlist[i] < 0: minus_signs_nb += 1 elif isinstance(signed_objctlist[i], Exponented): minus_signs_nb += signed_objctlist[i].get_minus_signs_nb() if is_even(minus_signs_nb): return '+' else: return '-'
# -------------------------------------------------------------------------- ## # @brief Returns the GCD of two integers
[docs]def gcd(a, b): if not is_integer(a): raise TypeError('Expected an Integer, got a ' + str(type(a)) + ' instead') if not is_integer(b): raise TypeError('Expected an Integer, got a ' + str(type(b)) + ' instead') if b == 0: raise ValueError('Expected a value different from 0.') if a % b == 0: return int(math.fabs(b)) return gcd(b, a % b)
# -------------------------------------------------------------------------- ## # @brief Returns the GCD that a pupil would think of # If the numbers are too high, the real gcd will be returned to avoid having # reducible fractions found irreducible.
[docs]def pupil_gcd(a, b): if not is_integer(a): raise TypeError('Expected an Integer, got a ' + str(type(a)) + ' instead') if not is_integer(b): raise TypeError('Expected an Integer, got a ' + str(type(b)) + ' instead') if b == 0: raise ValueError('Expected a value different from 0.') if a == b: return a if a >= len(DIVISORS) or b >= len(DIVISORS): if a % 10 == 0 and b % 10 == 0: return 10 * ten_power_gcd(a / 10, b / 10) elif a % 5 == 0 and b % 5 == 0: return 5 elif a % 2 == 0 and b % 2 == 0: return 2 elif a % 3 == 0 and b % 3 == 0: return 3 else: return gcd(a, b) result = 1 for i in range(len(DIVISORS[int(a)])): if ((DIVISORS[int(a)][i] in DIVISORS[int(b)]) and DIVISORS[int(a)][i] > result): # __ result = DIVISORS[int(a)][i] # to finally get the fraction reduced even if the gcd isn't in the # pupil's divisors table: if gcd(a, b) != 1 and result == 1: result = gcd(a, b) return result
# -------------------------------------------------------------------------- ## # @brief Returns the GCD among powers of 10 # For instance, ten_power_gcd(20, 300) returns 10, # ten_power_gcd(3000, 6000) returns 1000.
[docs]def ten_power_gcd(a, b): if not is_integer(a): raise TypeError('Expected an Integer, got a ' + str(type(a)) + ' instead') if not is_integer(b): raise TypeError('Expected an Integer, got a ' + str(type(b)) + ' instead') if b == 0: raise ValueError('Expected a value different from 0.') if a % 10 == 0 and b % 10 == 0: return 10 * ten_power_gcd(a // 10, b // 10) else: return 1
# -------------------------------------------------------------------------- ## # @brief Returns the lcm of two integers
[docs]def lcm(a, b): return int(math.fabs(a * b / gcd(a, b)))
# -------------------------------------------------------------------------- ## # @brief Returns the LCM of a list of integers
[docs]def lcm_of_the_list(l): if len(l) == 2: return lcm(l[0], l[1]) else: return lcm(l.pop(), lcm_of_the_list(l))
# -------------------------------------------------------------------------- ## # @brief True if objct is an even number|numeric Item. Otherwise, False # @param objct The object to test # @return True if objct is an even number|numeric Item. Otherwise, False
[docs]def is_even(objct): from mathmaker.lib.core.base_calculus import Item, Function from mathmaker.lib.core.root_calculus import Value if is_number(objct) and is_integer(objct): if objct % 2 == 0: return True else: return False elif (isinstance(objct, Item) and objct.is_numeric() and not isinstance(objct, Function)): return is_even(objct.raw_value) elif isinstance(objct, Value) and objct.is_numeric(): return is_even(objct.raw_value) else: return False
# -------------------------------------------------------------------------- ## # @brief True if objct is an uneven number|numeric Item. Otherwise, False # @param objct The object to test # @return True if objct is an uneven number|numeric Item. Otherwise, False
[docs]def is_uneven(objct): return not is_even(objct)
# -------------------------------------------------------------------------- ## # @brief Mean of a list of numbers
[docs]def mean(numberList, weights=None): if not type(numberList) == list: raise TypeError('Expected a list, got a ' + str(type(numberList)) + 'instead') if len(numberList) == 0: raise ValueError('This list shouldn\'t be empty') for i in range(len(numberList)): if not is_number(numberList[i]): raise TypeError('Expected a number, got a ' + str(type(numberList[i])) + ' instead') decimalNums = [Decimal(str(x)) for x in numberList] if weights is None: weights = [1 for n in decimalNums] if len(weights) != len(decimalNums): raise ValueError('There should be as many weights as numbers, but ' 'there are {w} weights and {n} numbers.' .format(w=len(weights), n=len(decimalNums))) total_weight = Decimal(str(sum(weights))) terms = [Decimal(str(w)) * d for w, d in zip(weights, decimalNums)] return Decimal(str(sum(terms) / total_weight))
# -------------------------------------------------------------------------- ## # @brief Barycenter of a list of Points
[docs]def barycenter(points_list, barycenter_name, weights=None): from mathmaker.lib.core.base_geometry import Point if not type(points_list) == list: raise TypeError('Expected a list, got a ' + str(type(points_list)) + 'instead') if len(points_list) == 0: raise ValueError('This list shouldn\'t be empty') for i in range(len(points_list)): if not isinstance(points_list[i], Point): raise TypeError('Expected a Point, got a ' + str(type(points_list[i])) + ' instead') if not type(barycenter_name) == str: raise TypeError('Expected a str, got a ' + str(type(barycenter_name)) + 'instead') if weights is None: weights = [1 for p in points_list] abscissas_list = [P.x_exact for P in points_list] ordinates_list = [P.y_exact for P in points_list] return Point(barycenter_name, mean(abscissas_list, weights=weights), mean(ordinates_list, weights=weights))
# -------------------------------------------------------------------------- ## # @brief Generator of coprime numbers
[docs]def coprime_generator(n): for i in range(20): if gcd(i, n) == 1: yield i
# -------------------------------------------------------------------------- ## # @brief Returns a list of numbers of the given kind
[docs]def generate_decimal(width, places_scale, start_place): # Probability to fill a higher place rather than a lower one php = 0.5 hp = lr = start_place places = [start_place] for i in range(width - 1): if lr == 0: php = 1 elif hp == len(places_scale) - 1: php = 0 if random.random() < php: hp += 1 places += [hp] php *= 0.4 else: lr -= 1 places += [lr] php *= 2.5 figures = [str(i + 1) for i in range(9)] random.shuffle(figures) deci = Decimal('0') for p in places: figure = figures.pop() deci += Decimal(figure) * places_scale[p] return deci
[docs]def prime_factors(n): """ Return all the prime factors of a positive integer Taken from https://stackoverflow.com/a/412942/3926735. """ try: n = int(n) except ValueError: raise TypeError('n must be an int') factors = [] d = 2 while n > 1: while n % d == 0: factors.append(d) n //= d d = d + 1 if d * d > n: if n > 1: factors.append(n) break return factors
[docs]def coprimes_to(n, span): """ List numbers coprime to n inside provided span. :param n: integer number :type n: int :param span: a list of integer numbers :type span: list :rtype: list """ return [x for x in span if gcd(n, x) == 1]
[docs]def not_coprimes_to(n, span, exclude=None): """ List numbers NOT coprime to n inside provided span. :param n: integer number :type n: int :param span: a list of integer numbers :type span: list :param exclude: a list of number to always exclude from the results :type exclude: list :rtype: list """ if exclude is None: exclude = [] return [x for x in span if (x not in exclude and gcd(n, x) != 1)]