mathmaker  0.4(alpha)
mathmaker_dev/sheet/exercise/question/Q_AlgebraExpressionReduction.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 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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(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 xrange(options['plus_brackets_nb']):
00496                     big_box.append(plus_brackets[i])
00497 
00498 
00499             final_terms = []
00500 
00501             for i in xrange(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