mathmaker
0.6(alpha)
|
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