mathmaker  0.6(alpha)
mamk_misc/doc/mathmaker4doxygen/lib/maths_lib.py
00001 # -*- coding: utf-8 -*-
00002 
00003 # Mathmaker creates automatically maths exercises sheets
00004 # with their answers
00005 # Copyright 2006-2014 Nicolas Hainaux <nico_h@users.sourceforge.net>
00006 
00007 # This file is part of Mathmaker.
00008 
00009 # Mathmaker is free software; you can redistribute it and/or modify
00010 # it under the terms of the GNU General Public License as published by
00011 # the Free Software Foundation; either version 3 of the License, or
00012 # any later version.
00013 
00014 # Mathmaker is distributed in the hope that it will be useful,
00015 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017 # GNU General Public License for more details.
00018 
00019 # You should have received a copy of the GNU General Public License
00020 # along with Mathmaker; if not, write to the Free Software
00021 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00022 
00023 import sys
00024 import math
00025 from decimal import *
00026 import core
00027 import is_
00028 import randomly
00029 import utils
00030 
00031 
00032 # DIVISORS frequently used by the children
00033 # (the most "exotic" couples (like 4×16) have been withdrawn so that the
00034 # the fractions can be reduced in a way that will look natural to children.
00035 # There should be enough couples left to bring the reduction to fruition, even
00036 # if in several steps. Some "exotic" couples have been kept because they are
00037 # necessary (like 7×13, 5×17...)
00038 # The divisor 1 in the divisors of 0 and 1 has been written twice to avoid
00039 # getting an error on asking the length of an unsized object
00040 DIVISORS = [(1,1), (1,1), (2,1), (3,1), (4,2,1), (5,1), (6,3,2,1), (7,1),
00041             (8,4,2,1),
00042             (9,3,1), (10,5,2,1), (11,1), (12,6,4,3,2,1), (13,1), (14,7,2,1),
00043             (15,5,3,1), (16,8,4,2,1), (17,1), (18,9,6,3,2,1), (19,1),
00044             (20,10,5,4,2,1), (21,7,3,1), (22,11,2,1), (23,1),
00045             (24,12,8,6,4,3,2,1), (25,5,1), (26,13,2,1), (27,9,3,1),
00046             (28,7,4,2,1), (29,1), (30,15,10,6,5,3,2,1), (31,1),
00047             (32,16,8,4,2,1), (33,11,3,1), (34,17,2,1), (35,7,5,1),
00048             (36,18,12,9,6,4,3,2,1), (37,1), (38,19,2,1), (39,13,3,1),
00049             (40,20,10,8,5,4,2,1), (41,1), (42,21,7,6,2,1), (43,1),
00050             (44,22,11,4,2,1), (45,9,5,1), (46,23,2,1), (47,1),
00051             (48,24,8,6,2,1), (49,7,1), (50,25,10,5,2,1), (51,17,3,1),
00052             (52,26,13,4,2,1), (53,1), (54,27,9,6,2,1), (55,11,5,1),
00053             (56,28,8,7,2,1), (57,19,3,1), (58,29,2,1), (59,1),
00054             (60,30,20,15,12,10,6,5,4,3,2,1), (61,1), (62,31,2,1),
00055             (63,21,9,7,3,1), (64,32,8,2,1), (65,13,5,1), (66,33,2,1), (67,1),
00056             (68,34,17,4,2,1), (69,23,3,1), (70,35,10,7,5,2,1), (71,1),
00057             (72,12,9,8,6,2,1), (73,1), (74,37,2,1), (75,15,5,1), (76,38,2,1),
00058             (77,11,7,1), (78,39,26,3,2,1), (79,1), (80,40,20,10,8,4,2,1),
00059             (81,9,1), (82,41,2,1), (83,1), (84,42,2,1), (85,17,5,1),
00060             (86,43,2,1), (87,29,3,1), (88,44,22,11,8,4,2,1), (89,1),
00061             (90,45,30,10,9,3,2,1), (91,13,7,1), (92,46,2,1), (93,31,3,1),
00062             (94,47,2,1), (95,19,5,1), (96,48,2,1), (97,1), (98,49,2,1),
00063             (99,33,11,9,3,1), (100,50,25,10,4,2,1)]
00064 
00065 
00066 
00067 # CONSTANTS CONCERNING MATH OBJECTS
00068 ZERO_POLYNOMIAL_DEGREE = -sys.maxsize
00069 
00070 
00071 def abs(nb):
00072     if nb >= 0:
00073         return nb
00074     else:
00075         return -nb
00076 
00077 
00078 
00079 
00080 # --------------------------------------------------------------------------
00081 ##
00082 #   @brief Returns the sign of the product of relatives numbers
00083 #   @param signed_objctlist A list of any objects having a sign
00084 #   @return A sign ('+' ou '-')
00085 def sign_of_product(signed_objctlist):
00086 
00087     if not(type(signed_objctlist) == list) or not(len(signed_objctlist) >= 1):
00088         raise error.UncompatibleType(signed_objctlist, "non empty list")
00089 
00090     minus_signs_nb = 0
00091 
00092     for i in range(len(signed_objctlist)):
00093 
00094         if not (is_.a_sign(signed_objctlist[i])                               \
00095            or is_.a_number(signed_objctlist[i])                               \
00096            or isinstance(signed_objctlist[i], core.base_calculus.Exponented)):
00097         #___
00098             raise error.UncompatibleType(signed_objctlist[i],                 \
00099                                          "'+' or '-'|number|Exponented")
00100 
00101         elif signed_objctlist[i] == '-':
00102             minus_signs_nb += 1
00103 
00104         elif is_.a_number(signed_objctlist[i]) and signed_objctlist[i] < 0:
00105             minus_signs_nb += 1
00106 
00107         elif isinstance(signed_objctlist[i], core.base_calculus.Exponented):
00108             minus_signs_nb += signed_objctlist[i].get_minus_signs_nb()
00109 
00110     if is_even(minus_signs_nb):
00111         return '+'
00112     else:
00113         return '-'
00114 
00115 
00116 
00117 
00118 
00119 # --------------------------------------------------------------------------
00120 ##
00121 #   @brief Returns the GCD of two integers
00122 def gcd(a, b):
00123     if not is_.an_integer(a):
00124         raise error.UncompatibleType(a, "Integer")
00125 
00126     if not is_.an_integer(b):
00127         raise error.UncompatibleType(b, "Integer")
00128 
00129     if b == 0:
00130         raise error.OutOfRangeArgument(b, "Integer but not zero !")
00131 
00132     if a % b == 0:
00133         return int(math.fabs(b))
00134 
00135     return gcd(b, a % b)
00136 
00137 
00138 
00139 
00140 
00141 # --------------------------------------------------------------------------
00142 ##
00143 #   @brief Returns the GCD of a list of integers
00144 def gcd_of_the_list(l):
00145     if len(l) == 2:
00146         return gcd(l[0], l[1])
00147     else:
00148         return gcd(l.pop(), gcd_of_the_list(l))
00149 
00150 
00151 
00152 
00153 
00154 # --------------------------------------------------------------------------
00155 ##
00156 #   @brief Returns the GCD that a pupil would think of
00157 #   If the numbers are too high, the real gcd will be returned to avoid having
00158 #   reducible fractions found irreducible.
00159 def pupil_gcd(a, b):
00160     if not is_.an_integer(a):
00161         raise error.UncompatibleType(a, "Integer")
00162 
00163     if not is_.an_integer(b):
00164         raise error.UncompatibleType(b, "Integer")
00165 
00166     if b == 0:
00167         raise error.OutOfRangeArgument(b, "Integer but not zero !")
00168 
00169     if a == b:
00170         return a
00171 
00172     if a >= len(DIVISORS) or b >= len(DIVISORS):
00173         if a % 10 == 0 and b % 10 == 0:
00174             return 10 * ten_power_gcd(a / 10, b / 10)
00175 
00176         elif a % 5 == 0 and b % 5 == 0:
00177             return 5
00178 
00179         elif a % 2 == 0 and b % 2 == 0:
00180             return 2
00181 
00182         elif a % 3 == 0 and b % 3 == 0:
00183             return 3
00184 
00185         else:
00186             return gcd(a,b)
00187 
00188     result = 1
00189 
00190     for i in range(len(DIVISORS[int(a)])):
00191         if (DIVISORS[int(a)][i] in DIVISORS[int(b)]) \
00192             and DIVISORS[int(a)][i] > result:
00193         #___
00194             result = DIVISORS[int(a)][i]
00195 
00196     # to finally get the fraction reduced even if the gcd isn't in the
00197     # pupil's divisors table :
00198     if gcd(a,b) != 1 and result == 1:
00199         result = gcd(a,b)
00200 
00201     return result
00202 
00203 
00204 
00205 
00206 
00207 # --------------------------------------------------------------------------
00208 ##
00209 #   @brief Returns the GCD among powers of 10
00210 #   For instance, ten_power_gcd(20, 300) returns 10,
00211 #   ten_power_gcd(3000, 6000) returns 1000.
00212 def ten_power_gcd(a, b):
00213     if not is_.an_integer(a):
00214         raise error.UncompatibleType(a, "Integer")
00215 
00216     if not is_.an_integer(b):
00217         raise error.UncompatibleType(b, "Integer")
00218 
00219     if b == 0:
00220         raise error.OutOfRangeArgument(b, "Integer but not zero !")
00221 
00222     if a % 10 == 0 and b % 10 == 0:
00223         return 10 * ten_power_gcd(a // 10, b // 10)
00224     else:
00225         return 1
00226 
00227 
00228 
00229 
00230 # --------------------------------------------------------------------------
00231 ##
00232 #   @brief Returns the lcm of two integers
00233 def lcm(a, b):
00234     return int(math.fabs(a*b/gcd(a,b)))
00235 
00236 
00237 
00238 
00239 
00240 # --------------------------------------------------------------------------
00241 ##
00242 #   @brief Returns the LCM of a list of integers
00243 def lcm_of_the_list(l):
00244     if len(l) == 2:
00245         return lcm(l[0], l[1])
00246     else:
00247         return lcm(l.pop(), lcm_of_the_list(l))
00248 
00249 
00250 
00251 
00252 
00253 # --------------------------------------------------------------------------
00254 ##
00255 #   @brief True if objct is an even number|numeric Item. Otherwise, False
00256 #   @param objct The object to test
00257 #   @return True if objct is an even number|numeric Item. Otherwise, False
00258 def is_even(objct):
00259     if is_.an_integer(objct) or isinstance(objct, Decimal):
00260         if objct % 2 == 0:
00261             return True
00262         else:
00263             return False
00264 
00265     elif isinstance(objct, core.base_calculus.Item) and objct.is_numeric():
00266         return is_even(objct.raw_value)
00267 
00268     elif isinstance(objct, core.base_calculus.Value) and objct.is_numeric():
00269         return is_even(objct.raw_value)
00270 
00271     else:
00272         return False
00273 
00274 
00275 
00276 
00277 
00278 # --------------------------------------------------------------------------
00279 ##
00280 #   @brief True if objct is an uneven number|numeric Item. Otherwise, False
00281 #   @param objct The object to test
00282 #   @return True if objct is an uneven number|numeric Item. Otherwise, False
00283 def is_uneven(objct):
00284     if is_.an_integer(objct):
00285         if objct % 2 == 0:
00286             return False
00287         else:
00288             return True
00289 
00290     elif isinstance(objct, core.base_calculus.Item) and objct.is_numeric():
00291         return is_uneven(objct.raw_value)
00292 
00293     elif isinstance(objct, core.base_calculus.Value) and objct.is_numeric():
00294         return is_uneven(objct.raw_value)
00295 
00296     else:
00297         return False
00298 
00299 
00300 
00301 
00302 
00303 # --------------------------------------------------------------------------
00304 ##
00305 #   @brief Conversions between degrees and radians
00306 def deg_to_rad(arg):
00307     if not is_.a_number(arg):
00308         raise error.WrongArgument(' a number ', str(type(arg)))
00309 
00310     return arg*math.pi/180
00311 
00312 
00313 
00314 
00315 
00316 # --------------------------------------------------------------------------
00317 ##
00318 #   @brief Conversions between degrees and radians
00319 def rad_to_deg(arg):
00320     if not is_.a_number(arg):
00321         raise error.WrongArgument(' a number ', str(type(arg)))
00322 
00323     return arg*180/math.pi
00324 
00325 
00326 
00327 
00328 
00329 # --------------------------------------------------------------------------
00330 ##
00331 #   @brief Mean of a list of numbers
00332 def mean(numberList):
00333     if not type(numberList) == list:
00334         raise error.WrongArgument(' a list ', str(type(arg)))
00335 
00336     if len(numberList) == 0:
00337         raise error.WrongArgument(' a list of length > 0 ', ' an empty list ')
00338 
00339     for i in range(len(numberList)):
00340         if not is_.a_number(numberList[i]):
00341             raise error.WrongArgument(' a number ', str(type(numberList[i])))
00342 
00343     decimalNums = [Decimal(str(x)) for x in numberList]
00344 
00345     return Decimal(str(sum(decimalNums) / len(numberList)))
00346 
00347 
00348 
00349 
00350 
00351 # --------------------------------------------------------------------------
00352 ##
00353 #   @brief Barycenter of a list of Points
00354 def barycenter(points_list, barycenter_name):
00355     if not type(points_list) == list:
00356         raise error.WrongArgument(' a list ', str(type(points_list)))
00357 
00358     if len(points_list) == 0:
00359         raise error.WrongArgument(' a list of length > 0 ', ' an empty list ')
00360 
00361     for i in range(len(points_list)):
00362         if not isinstance(points_list[i], core.base_geometry.Point):
00363             raise error.WrongArgument(' a Point ', str(type(points_list[i])))
00364 
00365     if not type(barycenter_name) == str:
00366         raise error.WrongArgument(' a str ', str(type(barycenter_name)))
00367 
00368     abscissas_list = [P.x_exact for P in points_list]
00369     ordinates_list = [P.y_exact for P in points_list]
00370 
00371     return core.base_geometry.Point([barycenter_name, (mean(abscissas_list),
00372                                     mean(ordinates_list)
00373                                    )
00374                  ])
00375 
00376 
00377 
00378 
00379 
00380 # --------------------------------------------------------------------------
00381 ##
00382 #   @brief Rounds correctly a Decimal
00383 #   @options They are the same as the decimal's module quantize() method
00384 def round(d, precision, **options):
00385     if not isinstance(d, Decimal):
00386         raise error.WrongArgument(str(type(d)), "a Decimal")
00387 
00388     return utils.correct_normalize_results(d.quantize(precision, **options))
00389 
00390 
00391 
00392 
00393