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 from lib import * 00024 from Q_Structure import Q_Structure 00025 from core.base_calculus import * 00026 from core.calculus import * 00027 from lib.common.cst import * 00028 00029 # Shared constants 00030 AVAILABLE_Q_KIND_VALUES = {'product': ['default'], 00031 'sum_of_products': ['default'], 00032 'sum': ['default'], 00033 'long_sum': ['default'], 00034 'long_sum_including_a_coeff_1': ['default'], 00035 'sum_not_reducible': ['default'], 00036 'sum_with_minus-brackets': ['default']} 00037 00038 MAX_COEFF_TABLE = {'product' : 10, 00039 'sum_of_products' : 10, 00040 'sum' : 10, 00041 'long_sum':15, 00042 'long_sum_including_a_coeff_1':15, 00043 'sum_not_reducible':20, 00044 'sum_with_minus-brackets':15} 00045 00046 MAX_EXPONENT_TABLE = {'product' : 1, 00047 'sum_of_products' : 1, 00048 'sum' : 2, 00049 'long_sum':2, 00050 'long_sum_including_a_coeff_1':2, 00051 'sum_not_reducible':2, 00052 'sum_with_minus-brackets':2} 00053 00054 DEFAULT_MINIMUM_LENGTH_TABLE = {'product' : 1, 00055 'sum_of_products' : 2, 00056 'sum' : 2, 00057 'long_sum':7, 00058 'long_sum_including_a_coeff_1':7, 00059 'sum_not_reducible':2, 00060 'sum_with_minus-brackets':4} 00061 00062 DEFAULT_MAXIMUM_LENGTH_TABLE = {'product' : 1, 00063 'sum_of_products' : 4, 00064 'sum' : 6, 00065 'long_sum':10, 00066 'long_sum_including_a_coeff_1':10, 00067 'sum_not_reducible':3, 00068 'sum_with_minus-brackets':6} 00069 00070 # Product Reduction constants (PR_*) 00071 PR_MAX_LITERAL_ITEMS_NB = 2 00072 PR_SAME_LETTER_MAX_OCCURENCES_NB = 2 00073 PR_NUMERIC_ITEMS_MAX_NB = 2 00074 00075 # ------------------------------------------------------------------------------ 00076 # -------------------------------------------------------------------------- 00077 # ------------------------------------------------------------------------------ 00078 ## 00079 # @class Q_AlgebraExpressionReduction 00080 # @brief All algebraic expression reduction questions 00081 class Q_AlgebraExpressionReduction(Q_Structure): 00082 00083 00084 00085 00086 00087 # -------------------------------------------------------------------------- 00088 ## 00089 # @brief Constructor. 00090 # @param embedded_machine The machine to be used 00091 # @param q_kind= the kind of question desired 00092 # Available values are : 'product' 00093 # 'sum' 00094 # 'sum_of_products' 00095 # @param **options Options detailed below : 00096 # - short_test=<string> 00097 # 'yes' 00098 # 'OK' 00099 # any other value will be understood as 'no' 00100 # - q_subkind=<string> 00101 # 'minus_brackets_nb' (values : 1, 2, 3) 00102 # 'plus_brackets_nb' (values : 1, 2, 3) 00103 # @todo describe the different available options in this comment 00104 # @return One instance of question.Q_AlgebraExpressionReduction 00105 def __init__(self, embedded_machine, q_kind='default_nothing', **options): 00106 self.derived = True 00107 00108 # The call to the mother class __init__() method will set the 00109 # fields matching optional arguments which are so far : 00110 # self.q_kind, self.q_subkind 00111 # plus self.machine, self.options (modified) 00112 Q_Structure.__init__(self, embedded_machine, 00113 q_kind, AVAILABLE_Q_KIND_VALUES, 00114 **options) 00115 # The purpose of this next line is to get the possibly modified 00116 # value of **options 00117 options = self.options 00118 00119 MAX_COEFF = MAX_COEFF_TABLE[q_kind] 00120 MAX_EXPONENT = MAX_EXPONENT_TABLE[q_kind] 00121 DEFAULT_MINIMUM_LENGTH = DEFAULT_MINIMUM_LENGTH_TABLE[q_kind] 00122 DEFAULT_MAXIMUM_LENGTH = DEFAULT_MAXIMUM_LENGTH_TABLE[q_kind] 00123 00124 # This field is to be used in the answer_to_strs() method 00125 # to determine a possibly different algorithm for particular cases 00126 self.kind_of_answer = "" 00127 00128 # Max coefficient & degree values... 00129 max_coeff = MAX_COEFF 00130 max_expon = MAX_EXPONENT 00131 00132 if 'max_coeff' in options and options['max_coeff'] >= 1: 00133 max_coeff = options['max_coeff'] 00134 00135 if 'max_expon' in options and options['max_expon'] >= 1: 00136 max_expon = options['max_expon'] 00137 00138 length = randomly.integer(DEFAULT_MINIMUM_LENGTH, 00139 DEFAULT_MAXIMUM_LENGTH, 00140 weighted_table=[0.15, 0.25, 0.6]) 00141 00142 if 'length' in options and is_.an_integer(options['length']) \ 00143 and options['length'] >= 2: 00144 #___ 00145 length = options['length'] 00146 00147 00148 00149 00150 # 1st CASE : 00151 # PRODUCT REDUCTION 00152 if q_kind == 'product': 00153 # First let's determine a pack of letters where to draw 00154 # The default one will be [a, b, c, x, y, z] 00155 # but the reduced or entire alphabets can be used as well 00156 letters_package = alphabet.abc + alphabet.xyz 00157 00158 if 'short_test' in options \ 00159 and (options['short_test'] == 'yes' \ 00160 or options['short_test'] == 'OK'): 00161 #___ 00162 self.objct = Product([Monomial((RANDOMLY, 12, 1)), 00163 Monomial((RANDOMLY, 12, 1)) 00164 ]) 00165 00166 self.objct.factor[0].set_degree(1) 00167 self.objct.factor[1].set_degree(1) 00168 00169 else: 00170 # In the case of an exercise about reducing products 00171 # in a training sheet, the answers will be more detailed 00172 self.kind_of_answer = 'product_detailed' 00173 if 'use_reduced_alphabet' in options: 00174 letters_package = alphabet.reduced 00175 00176 elif 'use_the_entire_alphabet' in options: 00177 letters_package = alphabet.lowercase 00178 00179 elif 'use_these_letters' in options \ 00180 and is_.a_string_list(options['use_these_letters']): 00181 #___ 00182 letters_package = options['use_these_letters'] 00183 00184 00185 # Maximum Items number. (We make sure at the same time that 00186 # we won't 00187 # risk to draw a greater number of letters than the available 00188 # letters 00189 # in letters_package) 00190 max_literal_items_nb = min(PR_MAX_LITERAL_ITEMS_NB, 00191 len(letters_package)) 00192 00193 if 'max_literal_items_nb' in options \ 00194 and options['max_literal_items_nb'] >= 2 \ 00195 and options['max_literal_items_nb'] <= 6 : 00196 #___ 00197 max_literal_items_nb = min(options['max_literal_items_nb'], 00198 len(letters_package)) 00199 00200 # Maximum number of occurences of the same letter in 00201 # the initial expression 00202 same_letter_max_occurences_nb = \ 00203 PR_SAME_LETTER_MAX_OCCURENCES_NB 00204 00205 if 'nb_occurences_of_the_same_letter' in options \ 00206 and options['nb_occurences_of_the_same_letter'] >= 1: 00207 #___ 00208 same_letter_max_occurences_nb = options[ \ 00209 'nb_occurences_of_the_same_letter'] 00210 00211 # CREATION OF THE EXPRESSION 00212 # We draw randomly the letters that will appear 00213 # in the expression 00214 current_letters_package = list(letters_package) 00215 00216 nb_of_letters_to_draw = randomly.integer(1, 00217 max_literal_items_nb) 00218 00219 drawn_letters = list() 00220 00221 for j in range(nb_of_letters_to_draw): 00222 drawn_letters.append(randomly.pop(current_letters_package)) 00223 00224 # Let's determine how many times will appear each letter 00225 # and then create a list containing each of these letters 00226 # the number of times they will appear 00227 pre_items_list = list() 00228 items_list = list() 00229 00230 for j in range(len(drawn_letters)): 00231 if j == 0: 00232 # We make sure that at least one letter occurs twice 00233 # so that the exercise remains interesting ! 00234 # But the number of cases this letter occurs 3 three 00235 # times should be limited to keep sufficient 00236 # simple cases for the pupils to begin with. 00237 # It is really easy to make it much more complicated 00238 # simply giving : 00239 # nb_occurences_of_the_same_letter=<enough_high_nb> 00240 # as an argument. 00241 if randomly.decimal_0_1() < 0.5: 00242 occurences_nb = 2 00243 else: 00244 occurences_nb = randomly.integer(2, 00245 same_letter_max_occurences_nb) 00246 else: 00247 occurences_nb = randomly.integer(1, 00248 same_letter_max_occurences_nb) 00249 00250 if occurences_nb >= 1: 00251 for k in range(occurences_nb): 00252 pre_items_list.append(drawn_letters[j]) 00253 00254 # draw the number of numeric Items 00255 nb_item_num = randomly.integer(1, PR_NUMERIC_ITEMS_MAX_NB) 00256 00257 # put them in the pre items' list 00258 for j in range(nb_item_num): 00259 pre_items_list.append(NUMERIC) 00260 00261 # prepare the items' list that will be given to the Product's 00262 # constructor 00263 loop_nb = len(pre_items_list) 00264 00265 for j in range(loop_nb): 00266 next_item_kind = randomly.pop(pre_items_list) 00267 00268 # It's not really useful nor really possible to limit the 00269 # number 00270 # of occurences of the same letter being drawn twice in 00271 # a row because it belongs to the exercise and there 00272 # are many cases when 00273 # the same letter is in the list in 3 over 4 elements. 00274 #if j >= 1 and next_item_kind == items_list[j - 1].raw_value: 00275 # pre_items_list.append(next_item_kind) 00276 # next_item_kind = randomly.pop(pre_items_list) 00277 00278 if next_item_kind == NUMERIC: 00279 temp_item = Item((randomly.sign(plus_signs_ratio=0.75), 00280 randomly.integer(1, max_coeff), 00281 1 00282 )) 00283 items_list.append(temp_item) 00284 00285 else: 00286 item_value = next_item_kind 00287 temp_item = Item((randomly.sign(plus_signs_ratio=0.9), 00288 item_value, 00289 randomly.integer(1, max_expon) 00290 )) 00291 items_list.append(temp_item) 00292 00293 # so now that the items_list is complete, 00294 # let's build the Product ! 00295 self.objct = Product(items_list) 00296 self.objct.set_compact_display(False) 00297 00298 # Let's take some × symbols off the Product to match a more 00299 # usual situation 00300 for i in range(len(self.objct) - 1): 00301 if (self.objct.factor[i].is_numeric() \ 00302 and self.objct.factor[i+1].is_literal() \ 00303 ) \ 00304 or \ 00305 (self.objct.factor[i].is_literal() \ 00306 and self.objct.factor[i+1].is_literal() \ 00307 and self.objct.factor[i].raw_value \ 00308 != self.objct.factor[i+1].raw_value \ 00309 and randomly.decimal_0_1() > 0.5 \ 00310 ): 00311 #___ 00312 self.objct.info[i] = False 00313 00314 # 2d CASE : 00315 # SUM OF PRODUCTS REDUCTION 00316 if q_kind == 'sum_of_products': 00317 00318 if not ('length' in options and is_.an_integer(options['length']) \ 00319 and options['length'] >= 2): 00320 #___ 00321 length = randomly.integer(DEFAULT_MINIMUM_LENGTH, 00322 DEFAULT_MAXIMUM_LENGTH, 00323 weighted_table=[0.15, 0.25, 0.6]) 00324 00325 # Creation of the list to give later to the Sum constructor 00326 products_list = list() 00327 00328 for i in range(length): 00329 monomial1 = Monomial((RANDOMLY, 00330 max_coeff, 00331 max_expon)) 00332 monomial2 = Monomial((RANDOMLY, 00333 max_coeff, 00334 max_expon)) 00335 products_list.append(Product([monomial1, monomial2])) 00336 00337 # Creation of the Sum 00338 self.objct = Sum(products_list) 00339 00340 00341 # 3d CASE : 00342 # SUM REDUCTION 00343 if q_kind == 'sum': 00344 self.kind_of_answer = 'sum' 00345 # Let's determine the length of the Sum to create 00346 if not ('length' in options and is_.an_integer(options['length']) \ 00347 and options['length'] >= 1): 00348 #___ 00349 length = randomly.integer(DEFAULT_MINIMUM_LENGTH, 00350 DEFAULT_MAXIMUM_LENGTH, 00351 weighted_table=[0.1, 0.25, 0.5, 00352 0.1, 0.05]) 00353 00354 else: 00355 length = options['length'] 00356 00357 # Creation of the Polynomial... 00358 00359 if 'short_test' in options: 00360 self.objct = Polynomial((RANDOMLY, 00361 max_coeff, 00362 2, 00363 length - 1)) 00364 temp_sum = self.objct.term 00365 00366 degree_1_monomial_here = False 00367 for i in range(len(temp_sum)): 00368 if temp_sum[i].degree == 1: 00369 degree_1_monomial_here = True 00370 00371 if degree_1_monomial_here == 1: 00372 temp_sum.append(Monomial((randomly.sign(), 00373 1, 00374 1))) 00375 else: # this should be 2d degree Polynomial without 00376 # any 1st degree term 00377 temp_sum.append(Monomial((randomly.sign(), 00378 1, 00379 2))) 00380 00381 self.objct.reset_element() 00382 00383 for i in range(length): 00384 self.objct.term.append(randomly.pop(temp_sum)) 00385 self.objct.info.append(False) 00386 00387 else: 00388 self.objct = Polynomial((RANDOMLY, 00389 max_coeff, 00390 max_expon, 00391 length)) 00392 00393 if q_kind == 'long_sum': 00394 m = [] 00395 00396 for i in range(length): 00397 m.append(Monomial(RANDOMLY, 00398 max_coeff, 00399 max_expon 00400 ) 00401 ) 00402 00403 self.objct = Polynomial(m) 00404 00405 if q_kind == 'long_sum_including_a_coeff_1': 00406 m = [] 00407 00408 for i in range(length - 1): 00409 m.append(Monomial(RANDOMLY, 00410 max_coeff, 00411 max_expon 00412 ) 00413 ) 00414 00415 m.append(Monomial(RANDOMLY, 00416 1, 00417 max_expon 00418 ) 00419 ) 00420 00421 terms_list = [] 00422 00423 for i in range(len(m)): 00424 terms_list.append(randomly.pop(m)) 00425 00426 self.objct = Polynomial(terms_list) 00427 00428 00429 if q_kind == 'sum_not_reducible': 00430 self.kind_of_answer = 'sum_not_reducible' 00431 00432 m1 = Monomial((RANDOMLY, max_coeff, 0)) 00433 m2 = Monomial((RANDOMLY, max_coeff, 1)) 00434 m3 = Monomial((RANDOMLY, max_coeff, 2)) 00435 00436 lil_box = [m1, m2, m3] 00437 00438 self.objct = Polynomial([randomly.pop(lil_box)]) 00439 00440 for i in range(len(lil_box) - 1): 00441 self.objct.append(randomly.pop(lil_box)) 00442 00443 00444 if q_kind == 'sum_with_minus-brackets': 00445 minus_brackets = [] 00446 00447 for i in range(3): 00448 minus_brackets.append(Expandable((Monomial(('-', 1, 0)), 00449 Polynomial((RANDOMLY, 00450 15, 00451 2, 00452 randomly.integer(2,3) 00453 )) 00454 )) 00455 ) 00456 00457 m1 = Monomial((RANDOMLY, max_coeff, 0)) 00458 m2 = Monomial((RANDOMLY, max_coeff, 1)) 00459 m3 = Monomial((RANDOMLY, max_coeff, 2)) 00460 m4 = Monomial((RANDOMLY, max_coeff, randomly.integer(0, 2))) 00461 00462 lil_box = [m1, m2, m3, m4] 00463 00464 plus_brackets = [] 00465 00466 for i in range(3): 00467 plus_brackets.append(Expandable((Monomial(('+', 1, 0)), 00468 Polynomial((RANDOMLY, 00469 15, 00470 2, 00471 randomly.integer(2,3) 00472 )) 00473 )) 00474 ) 00475 00476 big_box = [] 00477 big_box.append(minus_brackets[0]) 00478 00479 if 'minus_brackets_nb' in options \ 00480 and options['minus_brackets_nb'] >= 2 \ 00481 and options['minus_brackets_nb'] <= 3: 00482 #___ 00483 big_box.append(minus_brackets[1]) 00484 00485 if options['minus_brackets_nb'] == 3: 00486 big_box.append(minus_brackets[2]) 00487 00488 for i in range(randomly.integer(1, 4)): 00489 big_box.append(randomly.pop(lil_box)) 00490 00491 if 'plus_brackets_nb' in options \ 00492 and options['plus_brackets_nb'] >= 1 \ 00493 and options['plus_brackets_nb'] <= 3: 00494 #___ 00495 for i in range(options['plus_brackets_nb']): 00496 big_box.append(plus_brackets[i]) 00497 00498 00499 final_terms = [] 00500 00501 for i in range(len(big_box)): 00502 final_terms.append(randomly.pop(big_box)) 00503 00504 self.objct = Sum(final_terms) 00505 00506 00507 00508 00509 # Creation of the expression : 00510 number = 0 00511 if 'expression_number' in options \ 00512 and is_.a_natural_int(options['expression_number']): 00513 #___ 00514 number = options['expression_number'] 00515 00516 self.expression = Expression(number, self.objct) 00517 00518 00519 00520 00521 00522 # -------------------------------------------------------------------------- 00523 ## 00524 # @brief Returns the text of the question as a str 00525 def text_to_str(self): 00526 M = self.machine 00527 00528 result = M.write_math_style2(M.type_string(self.expression)) 00529 result += M.write_new_line() 00530 00531 return result 00532 00533 00534 00535 00536 00537 # -------------------------------------------------------------------------- 00538 ## 00539 # @brief Returns the answer of the question as a str 00540 def answer_to_str(self): 00541 M = self.machine 00542 00543 result = "" 00544 00545 if self.kind_of_answer == 'product_detailed': 00546 result += M.write_math_style2(M.type_string(self.expression)) 00547 result += M.write_new_line() 00548 00549 if not is_.an_ordered_calculable_objects_list(self.objct.factor): 00550 ordered_product = self.objct.order() 00551 ordered_product.set_compact_display(False) 00552 ordered_expression = Expression(self.expression.name, 00553 ordered_product) 00554 result += M.write_math_style2(M.type_string(ordered_expression)) 00555 result += M.write_new_line() 00556 00557 final_product = self.objct.reduce_() 00558 final_expression = Expression(self.expression.name, 00559 final_product) 00560 00561 result += M.write_math_style2(M.type_string(final_expression)) 00562 result += M.write_new_line() 00563 00564 elif (self.kind_of_answer == 'sum' \ 00565 or self.kind_of_answer == 'sum_not_reducible')\ 00566 and self.expression.\ 00567 right_hand_side.expand_and_reduce_next_step() is None: 00568 #___ 00569 result += M.write_math_style2(M.type_string(self.expression)) 00570 result += M.write_new_line() 00571 result += M.write(_("This expression is not reducible.")) 00572 result += M.write_new_line() 00573 00574 else: 00575 result += M.write(self.expression.auto_expansion_and_reduction()) 00576 00577 return result 00578 00579 00580 00581