Source code for mathmaker.lib.old_style_sheet.exercise.question.Q_AlgebraExpressionReduction

# -*- 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 random

from mathmakerlib.calculus import is_integer, is_natural

from mathmaker.lib import shared
from .Q_Structure import Q_Structure
from mathmaker.lib.core.base_calculus import (Product, Monomial, Item, Sum,
                                              Polynomial)
from mathmaker.lib.core.calculus import Expression
from mathmaker.lib.constants import RANDOMLY, NUMERIC

# Shared constants
AVAILABLE_Q_KIND_VALUES = {'product': ['default'],
                           'sum_of_products': ['default'],
                           'sum': ['default'],
                           'long_sum': ['default'],
                           'long_sum_including_a_coeff_1': ['default'],
                           'sum_not_reducible': ['default'],
                           'sum_with_minus-brackets': ['default']}

MAX_COEFF_TABLE = {'product': 10,
                   'sum_of_products': 10,
                   'sum': 10,
                   'long_sum': 15,
                   'long_sum_including_a_coeff_1': 15,
                   'sum_not_reducible': 20,
                   'sum_with_minus-brackets': 15}

MAX_EXPONENT_TABLE = {'product': 1,
                      'sum_of_products': 1,
                      'sum': 2,
                      'long_sum': 2,
                      'long_sum_including_a_coeff_1': 2,
                      'sum_not_reducible': 2,
                      'sum_with_minus-brackets': 2}

DEFAULT_MINIMUM_LENGTH_TABLE = {'product': 1,
                                'sum_of_products': 2,
                                'sum': 2,
                                'long_sum': 7,
                                'long_sum_including_a_coeff_1': 7,
                                'sum_not_reducible': 2,
                                'sum_with_minus-brackets': 4}

DEFAULT_MAXIMUM_LENGTH_TABLE = {'product': 1,
                                'sum_of_products': 4,
                                'sum': 6,
                                'long_sum': 10,
                                'long_sum_including_a_coeff_1': 10,
                                'sum_not_reducible': 3,
                                'sum_with_minus-brackets': 6}

# Product Reduction constants (PR_*)
PR_MAX_LITERAL_ITEMS_NB = 2
PR_SAME_LETTER_MAX_OCCURENCES_NB = 2
PR_NUMERIC_ITEMS_MAX_NB = 2


# ------------------------------------------------------------------------------
# --------------------------------------------------------------------------
# ------------------------------------------------------------------------------
##
# @class Q_AlgebraExpressionReduction
# @brief All algebraic expression reduction questions
[docs]class Q_AlgebraExpressionReduction(Q_Structure): # -------------------------------------------------------------------------- ## # @brief Constructor. # @param q_kind= the kind of question desired # Available values are: 'product' # 'sum' # 'sum_of_products' # @param **options Options detailed below: # - short_test=bool # 'yes' # 'OK' # any other value will be understood as 'no' # - q_subkind=<string> # 'minus_brackets_nb' (values: 1, 2, 3) # 'plus_brackets_nb' (values: 1, 2, 3) # @todo describe the different available options in this comment # @return One instance of question.Q_AlgebraExpressionReduction def __init__(self, q_kind='default_nothing', **options): self.derived = True # The call to the mother class __init__() method will set the # fields matching optional arguments which are so far: # self.q_kind, self.q_subkind # plus self.options (modified) Q_Structure.__init__(self, q_kind, AVAILABLE_Q_KIND_VALUES, **options) # The purpose of this next line is to get the possibly modified # value of **options options = self.options MAX_COEFF = MAX_COEFF_TABLE[q_kind] MAX_EXPONENT = MAX_EXPONENT_TABLE[q_kind] MIN_LENGTH = DEFAULT_MINIMUM_LENGTH_TABLE[q_kind] MAX_LENGTH = DEFAULT_MAXIMUM_LENGTH_TABLE[q_kind] LENGTH_SPAN = MAX_LENGTH - MIN_LENGTH + 1 # This field is to be used in the answer_to_strs() method # to determine a possibly different algorithm for particular cases self.kind_of_answer = "" # Max coefficient & degree values... max_coeff = options.get('max_coeff', MAX_COEFF) max_expon = options.get('max_expon', MAX_EXPONENT) length = options.get('length', random.choice([n + MIN_LENGTH for n in range(LENGTH_SPAN)])) # 1st CASE: # PRODUCT REDUCTION if q_kind == 'product': # First let's determine a pack of letters where to draw # The default one will be [a, b, c, x, y, z] # but the reduced or entire alphabets can be used as well letters_package = ['a', 'b', 'c', 'x', 'y', 'z'] self.kind_of_answer = 'product_detailed' if 'use_reduced_alphabet' in options: letters_package = ['a', 'b', 'c', 'd', 'g', 'h', 'k', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] elif ('use_these_letters' in options and type(options['use_these_letters']) is list and all([type(elt) is str for elt in options['use_these_letters']])): # __ letters_package = options['use_these_letters'] # Maximum Items number. (We make sure at the same time that # we won't # risk to draw a greater number of letters than the available # letters # in letters_package) max_literal_items_nb = min(PR_MAX_LITERAL_ITEMS_NB, len(letters_package)) # Maximum number of occurences of the same letter in # the initial expression same_letter_max_occurences = PR_SAME_LETTER_MAX_OCCURENCES_NB if ('nb_occurences_of_the_same_letter' in options and options['nb_occurences_of_the_same_letter'] >= 1): # __ same_letter_max_occurences = options['nb_occurences_of' '_the_same_letter'] # CREATION OF THE EXPRESSION # We draw randomly the letters that will appear # in the expression current_letters_package = list(letters_package) nb_of_letters_to_draw = random.randint(1, max_literal_items_nb) drawn_letters = list() for j in range(nb_of_letters_to_draw): drawn_letters.append( random.choice(current_letters_package)) # Let's determine how many times will appear each letter # and then create a list containing each of these letters # the number of times they will appear pre_items_list = list() items_list = list() for j in range(len(drawn_letters)): if j == 0: # We make sure that at least one letter occurs twice # so that the exercise remains interesting ! # But the number of cases this letter occurs 3 three # times should be limited to keep sufficient # simple cases for the pupils to begin with. # It is really easy to make it much more complicated # simply giving: # nb_occurences_of_the_same_letter=<enough_high_nb> # as an argument. if random.random() < 0.5: occurences_nb = 2 else: occurences_nb = \ random.randint( min(2, same_letter_max_occurences), same_letter_max_occurences) else: occurences_nb = \ random.randint(1, same_letter_max_occurences) if occurences_nb >= 1: for k in range(occurences_nb): pre_items_list.append(drawn_letters[j]) # draw the number of numeric Items nb_item_num = random.randint(1, PR_NUMERIC_ITEMS_MAX_NB) # put them in the pre items' list for j in range(nb_item_num): pre_items_list.append(NUMERIC) # prepare the items' list that will be given to the Product's # constructor loop_nb = len(pre_items_list) for j in range(loop_nb): next_item_kind = random.choice(pre_items_list) # It's not really useful nor really possible to limit the # number # of occurences of the same letter being drawn twice in # a row because it belongs to the exercise and there # are many cases when # the same letter is in the list in 3 over 4 elements. # if j >= 1 and next_item_kind == items_list[j - 1] # .raw_value: # pre_items_list.append(next_item_kind) # next_item_kind = random.choice(pre_items_list) if next_item_kind == NUMERIC: temp_item = Item((random.choices(['+', '-'], cum_weights=[0.75, 1])[0], random.randint(1, max_coeff), 1)) items_list.append(temp_item) else: item_value = next_item_kind temp_item = Item((random.choices(['+', '-'], cum_weights=[0.9, 1])[0], item_value, random.randint(1, max_expon))) items_list.append(temp_item) # so now that the items_list is complete, # let's build the Product ! self.objct = Product(items_list) self.objct.set_compact_display(False) # Let's take some × symbols off the Product to match a more # usual situation for i in range(len(self.objct) - 1): if ((self.objct.factor[i].is_numeric() and self.objct.factor[i + 1].is_literal()) or (self.objct.factor[i].is_literal() and self.objct.factor[i + 1].is_literal() and self.objct.factor[i].raw_value != self.objct.factor[i + 1].raw_value and random.random() > 0.5)): # __ self.objct.info[i] = False # 2d CASE: # SUM OF PRODUCTS REDUCTION if q_kind == 'sum_of_products': if (not ('length' in options and is_integer(options['length']) and options['length'] >= 2)): # __ length = random.choices( [n + MIN_LENGTH for n in range(LENGTH_SPAN)], weights=[n for n in range(LENGTH_SPAN)])[0] # Creation of the list to give later to the Sum constructor products_list = list() for i in range(length): monomial1 = Monomial((RANDOMLY, max_coeff, max_expon)) monomial2 = Monomial((RANDOMLY, max_coeff, max_expon)) products_list.append(Product([monomial1, monomial2])) # Creation of the Sum self.objct = Sum(products_list) # 3d CASE: # SUM REDUCTION if q_kind == 'sum': self.kind_of_answer = 'sum' length = options.get('length', random.choice([n + MIN_LENGTH for n in range(LENGTH_SPAN)])) self.objct = Polynomial((RANDOMLY, max_coeff, max_expon, length)) # Creation of the expression: number = 0 if ('expression_number' in options and is_natural(options['expression_number'])): # __ number = options['expression_number'] self.expression = Expression(number, self.objct) # -------------------------------------------------------------------------- ## # @brief Returns the text of the question as a str
[docs] def text_to_str(self): M = shared.machine result = M.write_math_style2(M.type_string(self.expression)) result += M.write_new_line() return result
# -------------------------------------------------------------------------- ## # @brief Returns the answer of the question as a str
[docs] def answer_to_str(self): M = shared.machine result = "" if self.kind_of_answer == 'product_detailed': result += M.write_math_style2(M.type_string(self.expression)) result += M.write_new_line() if not all(self.objct.factor[i] .alphabetical_order_cmp(self.objct.factor[i + 1]) > 0 for i in range(len(self.objct.factor) - 1)): ordered_product = self.objct.order() ordered_product.set_compact_display(False) ordered_expression = Expression(self.expression.name, ordered_product) result += M.write_math_style2( M.type_string(ordered_expression)) result += M.write_new_line() final_product = self.objct.reduce_() final_expression = Expression(self.expression.name, final_product) result += M.write_math_style2(M.type_string(final_expression)) result += M.write_new_line() elif ((self.kind_of_answer in ['sum', 'sum_not_reducible']) and self.expression. right_hand_side.expand_and_reduce_next_step() is None): # __ result += M.write_math_style2(M.type_string(self.expression)) result += M.write_new_line() result += M.write(_("This expression is not reducible.")) result += M.write_new_line() else: result += M.write(self.expression.auto_expansion_and_reduction()) return result