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

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

from mathmaker.lib.tools.maths import coprimes_to
from mathmaker.lib import shared
from mathmaker.lib.core.root_calculus import Exponented
from mathmaker.lib.core.base_calculus import (Item, Product, Expandable, Sum,
                                              Monomial, Polynomial)
from mathmaker.lib.core.calculus import Equality, Expression
from .Q_Structure import Q_Structure
from mathmaker.lib.constants import RANDOMLY

AVAILABLE_Q_KIND_VALUES = \
    {'level_01':
     ['default',
      'ax + b',
      'ax² + b',
      'ax² + bx',
      'three_terms',
      'not_factorizable',
      'mixed',
      'mixed_factorizable'],
     # C: common factor
     # F1 & F2: the other factors
     # deg1 and deg2 represent polynoms
     # of 1st and 2d degree
     # F1 and F2 will be randomly exchanged at creation
     # of the expression, so a case like a×deg1 + a×deg2
     # contains also a×deg2 + a×deg1

     # C × F1  ±  C × F2

     'level_02':
     ['default',   # synonym to type_123
      'type_123',  # any of type_1, type_2, type_3 (a polynom as common factor)
      'type_1',    # any of type_1_*
      'type_1_ABC',  # any of type 1 A, B and C cases
      'type_1_DEF',  # any of type 1 D, E and F cases
      'type_1_GHI',  # any of type 1 G, H and I cases
      'type_1_A',  # any of A1 or A0
      'type_1_B',  # any of B1 or B0
      'type_1_C',  # synonym to C0
      'type_1_D',  # any of D1 or D0
      'type_1_E',  # .
      'type_1_F',  # .
      'type_1_G',  # .
      'type_1_H',  # .
      'type_1_I',  # .
      'type_1_0',  # any of type_1_ * 0 cases
      'type_1_1',  # any of type_1_*1 cases
      'type_1_A0',  # a×deg1 + a×deg1' (deg * : at least 2 terms & a!=1)
      'type_1_B0',  # a×deg2 + a×deg2' (deg * : at least 2 terms & a!=1)
      'type_1_C0',  # a×deg1 + a×deg2 (deg * : at least 2 terms & a!=1)
      'type_1_D0',  # ax×deg1 + ax×deg1' (deg * : at least 2 terms)
      'type_1_E0',  # ax×deg2 + ax×deg2' (deg * : at least 2 terms)
      'type_1_F0',  # ax×deg1 + ax×deg2 (deg * : at least 2 terms)
      'type_1_G0',  # ax²×deg1 + ax²×deg1' (deg * : at least 2 terms)
      'type_1_H0',  # ax²×deg2 + ax²×deg2' (deg * : at least 2 terms)
      'type_1_I0',  # ax²×deg1 + ax²×deg2 (deg * : at least 2 terms)
      'type_1_A1',  # a×deg1 + a×1
      'type_1_B1',  # a×deg2 + a×1
      # 'type_1_C1', # C1 has no sense
      'type_1_D1',  # ax×deg1 + ax×1
      'type_1_E1',  # ax×deg2 + ax×1
      # 'type_1_F1', # F1 has no sense
      'type_1_G1',  # ax²×deg1 + ax²×1
      'type_1_H1',  # ax²×deg2 + ax²×1
      # 'type_1_I1', # I1 has no sense

      'type_2',    # any of type_2_*
      'type_2_ABC',  # any of type 2 A, B and C cases
      'type_2_DEF',  # any of type 2 D, E and F cases
      'type_2_A',  # any of A1 or A0
      'type_2_B',  # any of B1 or B0
      'type_2_C',  # synonym to C0
      'type_2_D',  # any of D1 or D0
      'type_2_E',  # .
      'type_2_F',  # .
      'type_2_0',  # any of type_2_ * 0 cases
      'type_2_1',  # any of type_2_*1 cases
      'type_2_A0',  # (ax+b)×deg1 + (ax+b)×deg1'
      'type_2_B0',  # (ax+b)×deg2 + (ax+b)×deg2'
      'type_2_C0',  # (ax+b)×deg1 + (ax+b)×deg2
      'type_2_D0',  # (ax²+b)×deg1 + (ax²+b)×deg1'
      'type_2_E0',  # (ax²+b)×deg2 + (ax²+b)×deg2'
      'type_2_F0',  # (ax²+b)×deg1 + (ax²+b)×deg2
      'type_2_A1',  # (ax+b)×deg1 + (ax+b)×1
      'type_2_B1',  # (ax+b)×deg2 + (ax+b)×1
      # 'type_2_C1', # C1 has no sense
      'type_2_D1',  # (ax²+b)×deg1 + (ax²+b)×1
      'type_2_E1',  # (ax²+b)×deg2 + (ax²+b)×1
      # 'type_2_F1', # F1 has no sense

      'type_3',    # any of type_3_*
      'type_3_ABC',  # any of type 3 A, B and C cases
      'type_3_A',  # any of A1 or A0
      'type_3_B',  # any of B1 or B0
      'type_3_C',  # synonym to C0
      'type_3_0',   # any of type_3_ * 0 cases
      'type_3_1'   # any of type_3_*1 cases
      'type_3_A0',  # (ax²+bx+c)×deg1 + (ax²+bx+c)×deg1'
      'type_3_B0',  # (ax²+bx+c)×deg2 + (ax²+bx+c)×deg2'
      'type_3_C0',  # (ax²+bx+c)×deg1 + (ax²+bx+c)×deg2
      'type_3_A1',  # (ax²+bx+c)×deg1 + (ax²+bx+c)×1
      'type_3_B1',  # (ax²+bx+c)×deg2 + (ax²+bx+c)×1
      # 'type_3_C1'  # C1 has no sense
      'type_4_A0'  # (ax+b)²×deg1 + (ax+b)×deg1'
      ],

     # Here are the binomial identities
     'level_03':
     ['default',   # synonym to 'any'
      'any',
      'any_mixed',  # any but from the mixed types (changed order of the terms)
      'any_straight',           # any but not from a mixed type
      'any_true',               # any that can be factorized
      'any_fake',               # any that cannot be factorized
      'any_true_straight',      # any that can be factorized, not mixed
      'any_fake_straight',      # any that cannot be factorized, not mixed
      'any_true_mixed',         # any that can be factorized, mixed
      'any_fake_mixed',         # any that cannot be factorized, mixed
      'sum_square',               # (ax)² + 2abx + b²
      'sum_square_mixed',         # like above but terms' order is changed
      'difference_square',        # (ax)² - 2abx + b²
      'difference_square_mixed',  # like above but terms' order is changed
      'squares_difference',       # (ax)² - b²
      'squares_difference_mixed',  # like above but terms' order is changed
      'fake_01',       # 2×a×b doesn't match a² and b² (sum)
      'fake_01_mixed',  # 2×a×b doesn't match a² and b² (sum, mixed)
      'fake_02',       # 2×a×b doesn't match a² and b² (difference)
      'fake_02_mixed',  # 2×a×b doesn't match a² and b² (difference, mixed)
      'fake_03',       # (ax)² + b²
      'fake_03_mixed',  # b² + (ax)²
      'fake_04_any',   # any of fake_04 (signs don't match a binomial)
      'fake_04_any_straight',
      'fake_04_any_mixed',
      'fake_04_A',        # (ax)² + 2abx - b²
      'fake_04_A_mixed',
      'fake_04_B',        # -(ax)² + 2abx + b²
      'fake_04_B_mixed',
      'fake_04_C',        # (ax)² - 2abx - b²
      'fake_04_C_mixed',
      'fake_04_D',        # -(ax)² - 2abx + b²
      'fake_04_D_mixed']}


# ------------------------------------------------------------------------------
# --------------------------------------------------------------------------
# ------------------------------------------------------------------------------
##
# @class Q_Factorization
# @brief Question related to the factorization of a literal expression
[docs]class Q_Factorization(Q_Structure): # -------------------------------------------------------------------------- ## # @brief Constructor. # @param **options Any options # @return One instance of question.Q_Factorization 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 q_subkind = self.q_subkind # That's the number of the question, not of the expressions it might # contain ! self.number = "" steps_method = None if q_kind == 'level_01': steps_method = level_01 if q_subkind == 'mixed': q_subkind = random.choice(['default', 'three_terms', 'not_factorizable']) elif q_subkind == 'mixed_factorizable': q_subkind = random.choice(['default', 'three_terms']) elif q_kind == 'level_03': steps_method = level_03 options['markup'] = shared.machine.markup if q_subkind == 'any' or q_subkind == 'default': q_subkind = random.choice(['any_straight', 'any_mixed']) if q_subkind == 'any_straight': q_subkind = random.choice(['any_true_straight', 'any_fake_straight']) if q_subkind == 'any_mixed': q_subkind = random.choice(['any_true_mixed', 'any_fake_mixed']) if q_subkind == 'any_true': q_subkind = random.choice(['any_true_straight', 'any_true_mixed']) if q_subkind == 'any_fake': q_subkind = random.choice(['any_fake_straight', 'any_fake_mixed']) if q_subkind == 'any_true_straight': q_subkind = random.choice(['sum_square', 'difference_square', 'squares_difference']) if q_subkind == 'any_fake_straight': q_subkind = random.choice(['fake_01', 'fake_02', 'fake_03', 'fake_04_any_straight']) if q_subkind == 'any_true_mixed': q_subkind = random.choice(['sum_square_mixed', 'difference_square_mixed', 'squares_difference_mixed']) if q_subkind == 'any_fake_mixed': q_subkind = random.choice(['fake_01_mixed', 'fake_02_mixed', 'fake_03_mixed', 'fake_04_any_mixed']) if q_subkind == 'fake_04_any': q_subkind = random.choice(['fake_04_any_mixed', 'fake_04_any_straight']) if q_subkind == 'fake_04_any_mixed': q_subkind = random.choice(['fake_04_A_mixed', 'fake_04_B_mixed', 'fake_04_C_mixed', 'fake_04_D_mixed']) if q_subkind == 'fake_04_any_straight': q_subkind = random.choice(['fake_04_A', 'fake_04_B', 'fake_04_C', 'fake_04_D']) steps = steps_method(q_subkind, **options) # 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, steps[0]) # Putting the steps and the solution together: self.steps = [] # for i in xrange(len(steps) - 1): # self.steps.append(Expression(number, # steps[i] # ) # ) # # solution = steps[len(steps) - 1] # # if isinstance(solution, Exponented): # self.steps.append(Expression(number, # solution # ) # ) # else: # self.steps.append(solution) for i in range(len(steps)): if isinstance(steps[i], Exponented): self.steps.append(Expression(number, steps[i])) else: self.steps.append(steps[i]) # -------------------------------------------------------------------------- ## # @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 = "" for i in range(len(self.steps)): if type(self.steps[i]) == str: result += M.write(self.steps[i]) else: result += M.write_math_style2(M.type_string(self.steps[i])) result += M.write_new_line() return result
# -------------------------------------------------------------------------- ## # @brief Creates & returns the solution and the answer's steps. # @return steps (list containing the steps)
[docs]def level_01(q_subkind, **options): if q_subkind == 'default' \ or q_subkind == 'three_terms' \ or q_subkind == 'ax + b' \ or q_subkind == 'ax² + b' \ or q_subkind == 'ax² + bx': # __ # the idea is to build the final factorized result first and to # expand it to get the question (and the solution's steps # in the same time) if q_subkind == 'default': common_factor = Monomial((RANDOMLY, 6, 1)) # In order to reduce the number of cases where x² appears, # let the common factor be of degree 0 most of the time. common_factor.set_degree(random.choices([0, 1], weights=[0.85, 0.15])[0]) elif q_subkind in ['three_terms', 'ax + b', 'ax² + b']: common_factor = Monomial((RANDOMLY, 6, 0)) elif q_subkind == 'ax² + bx': common_factor = Monomial((RANDOMLY, 6, 1)) common_factor.set_degree(1) # to avoid having a situation like 1×(2x + 3) which isn't # factorizable: if common_factor.get_degree() == 0: common_factor.set_coeff(random.randint(2, 6)) # signs are randomly chosen ; the only case that is to be avoided # is all signs are negative (then it wouldn't factorize well... # I mean then the '-' should be factorized and not left in the final # result) signs_box = [['+', '+'], ['+', '-']] signs = random.choice(signs_box) # this next test is to avoid -2x + 6 being factorized -2(x - 3) # which is not wrong but not "natural" to pupils # this test should be changed when a third term is being used. if signs == ['+', '-']: common_factor.set_sign('+') coeff_1 = random.randint(2, 10) coeff_2 = random.choice(coprimes_to(coeff_1, [i + 1 for i in range(10)])) coeff_3 = None if q_subkind == 'three_terms': coeff_3 = random.choice(coprimes_to(coeff_1 * coeff_2, [i + 1 for i in range(9)])) third_sign = random.choice(['+', '-']) if third_sign == '-': common_factor.set_sign('+') signs.append(third_sign) lil_box = [] lil_box.append(Monomial(('+', 1, 0))) if q_subkind == 'ax² + b': lil_box.append(Monomial(('+', 1, 2))) else: lil_box.append(Monomial(('+', 1, 1))) if ((common_factor.get_degree() == 0 and random.randint(1, 20) > 17 and q_subkind == 'default') or q_subkind == 'three_terms'): # __ lil_box.append(Monomial(('+', 1, 2))) random.shuffle(lil_box) first_term = lil_box.pop() second_term = lil_box.pop() third_term = None random.shuffle(signs) first_term.set_coeff(coeff_1) first_term.set_sign(signs.pop()) second_term.set_coeff(coeff_2) second_term.set_sign(signs.pop()) if q_subkind == 'three_terms': third_term = lil_box.pop() third_term.set_coeff(coeff_3) third_term.set_sign(signs.pop()) if first_term.is_positive() and second_term.is_positive()\ and third_term.is_positive(): # __ common_factor.set_sign(random.choice(['+', '-'])) if not (q_subkind == 'three_terms'): if common_factor.get_degree() == 0 \ and first_term.get_degree() >= 1 \ and second_term.get_degree() >= 1: # __ if random.choice([True, False]): first_term.set_degree(0) else: second_term.set_degree(0) if q_subkind == 'three_terms': solution = Expandable((common_factor, Sum([first_term, second_term, third_term]))) else: solution = Expandable((common_factor, Sum([first_term, second_term]))) # now create the expanded step and the reduced step (which will # be given as a question) temp_steps = [] current_step = solution.clone() while current_step is not None: temp_steps.append(current_step) current_step = current_step.expand_and_reduce_next_step() # now we put the steps in the right order steps = [] for i in range(len(temp_steps)): steps.append(temp_steps[len(temp_steps) - 1 - i]) return steps elif q_subkind == 'not_factorizable': signs_box = [['+', '+'], ['+', '-']] signs = random.choice(signs_box) coeff_1 = random.randint(2, 10) coeff_2 = random.choice(coprimes_to(coeff_1, [i + 1 for i in range(10)])) lil_box = [] lil_box.append(Monomial(('+', 1, 0))) lil_box.append(Monomial(('+', 1, 1))) lil_box.append(Monomial(('+', 1, 2))) random.shuffle(lil_box) first_term = lil_box.pop() second_term = lil_box.pop() random.shuffle(signs) first_term.set_coeff(coeff_1) first_term.set_sign(signs.pop()) second_term.set_coeff(coeff_2) second_term.set_sign(signs.pop()) if first_term.get_degree() >= 1 \ and second_term.get_degree() >= 1: # __ if random.choice([True, False]): first_term.set_degree(0) else: second_term.set_degree(0) steps = [] solution = _("So far, we don't know if this expression can be " "factorized.") steps.append(Sum([first_term, second_term])) steps.append(solution) return steps
# -------------------------------------------------------------------------- ## # @brief Creates & returns the solution and the answer's steps. # @return steps (list containing the steps)
[docs]def level_03(q_subkind, **options): a = random.randint(1, 10) b = random.randint(1, 10) steps = [] if q_subkind in ['sum_square', 'sum_square_mixed', 'difference_square', 'difference_square_mixed']: # __ first_term = Monomial(('+', Item(('+', a, 2)).evaluate(), 2)) second_term = Monomial(('+', Item(('+', Product([2, a, b]).evaluate(), 1)) .evaluate(), 1)) third_term = Monomial(('+', Item(('+', b, 2)).evaluate(), 0)) if q_subkind in ['difference_square', 'difference_square_mixed']: second_term.set_sign('-') if q_subkind in ['sum_square_mixed', 'difference_square_mixed']: ordered_expression = Polynomial([first_term, second_term, third_term]) terms = [first_term, second_term, third_term] random.shuffle(terms) first_term, second_term, third_term = terms steps.append(Polynomial([first_term, second_term, third_term])) if q_subkind in ['sum_square_mixed', 'difference_square_mixed']: steps.append(ordered_expression) sq_a_monom = Monomial(('+', a, 1)) sq_b_monom = Monomial(('+', b, 0)) let_a_eq = Equality([Item('a'), sq_a_monom]) let_b_eq = Equality([Item('b'), sq_b_monom]) steps.append(_("Let") + " " + let_a_eq.into_str(force_expression_markers=True) + " " + _("and") + " " + let_b_eq.into_str(force_expression_markers=True)) sq_a_monom.set_exponent(2) sq_b_monom.set_exponent(2) a_square_eq = Equality([Item(('+', 'a', 2)), sq_a_monom, sq_a_monom.reduce_()]) b_square_eq = Equality([Item(('+', 'b', 2)), sq_b_monom, sq_b_monom.reduce_()]) steps.append(_("then") + " " + a_square_eq.into_str(force_expression_markers=True)) steps.append(_("and") + " " + b_square_eq.into_str(force_expression_markers=True)) two_times_a_times_b_numeric = Product([Item(2), Monomial(('+', a, 1)), Item(b)]) two_times_a_times_b_reduced = two_times_a_times_b_numeric.reduce_() two_times_a_times_b_eq = Equality([Product([Item(2), Item('a'), Item('b')]), two_times_a_times_b_numeric, two_times_a_times_b_reduced]) steps.append(_("and") + " " + two_times_a_times_b_eq.into_str( force_expression_markers=True)) steps.append(_("So it is possible to factorize:")) if q_subkind in ['difference_square', 'difference_square_mixed']: b = -b factorized_expression = Sum([Monomial(('+', a, 1)), Item(b)]) factorized_expression.set_exponent(2) steps.append(factorized_expression) elif q_subkind in ['squares_difference', 'squares_difference_mixed']: # To have some (ax)² - b² but also sometimes b² - (ax)²: degrees = [2, 0, 1, 0] if random.randint(1, 10) >= 8: degrees = [0, 2, 0, 1] first_term = Monomial(('+', Item(('+', a, 2)).evaluate(), degrees[0])) second_term = Monomial(('-', Item(('+', b, 2)).evaluate(), degrees[1])) sq_first_term = Monomial(('+', Item(('+', a, 1)).evaluate(), degrees[2])) sq_second_term = Monomial(('-', Item(('+', b, 1)).evaluate(), degrees[3])) # The 'mixed' cases are: -b² + (ax)² and -(ax)² + b² if q_subkind == 'squares_difference_mixed': first_term, second_term = second_term, first_term sq_first_term, sq_second_term = sq_second_term, sq_first_term positive_sq_first = sq_first_term.clone() positive_sq_first.set_sign('+') positive_sq_second = sq_second_term.clone() positive_sq_second.set_sign('+') steps.append(Polynomial([first_term, second_term])) first_inter = None second_inter = None if sq_second_term.is_negative(): first_inter = positive_sq_first.clone() first_inter.set_exponent(2) temp_second_inter = positive_sq_second.clone() temp_second_inter.set_exponent(2) second_inter = Product([-1, temp_second_inter]) else: temp_first_inter = positive_sq_first.clone() temp_first_inter.set_exponent(2) first_inter = Product([-1, temp_first_inter]) second_inter = positive_sq_second.clone() second_inter.set_exponent(2) steps.append(Sum([first_inter, second_inter])) if q_subkind == 'squares_difference_mixed': steps.append(Sum([second_inter, first_inter])) steps.append(_("So, this expression can be factorized:")) sum1 = None sum2 = None if sq_second_term.is_negative(): sum1 = Sum([sq_first_term, sq_second_term]) sq_second_term.set_sign('+') sum2 = Sum([sq_first_term, sq_second_term]) else: sum1 = Sum([sq_second_term, sq_first_term]) sq_first_term.set_sign('+') sum2 = Sum([sq_second_term, sq_first_term]) lil_box = [sum1, sum2] random.shuffle(lil_box) steps.append(Product([lil_box.pop(), lil_box.pop()])) elif q_subkind in ['fake_01', 'fake_01_mixed', 'fake_02', 'fake_02_mixed', 'fake_03', 'fake_03_mixed', 'fake_04_A', 'fake_04_A_mixed', 'fake_04_B', 'fake_04_B_mixed', 'fake_04_C', 'fake_04_C_mixed', 'fake_04_D', 'fake_04_D_mixed']: # __ straight_cases = ['fake_01', 'fake_02', 'fake_03', 'fake_04_A', 'fake_04_B', 'fake_04_C', 'fake_04_D'] match_pb_cases = ['fake_01', 'fake_02', 'fake_01_mixed', 'fake_02_mixed'] sign_pb_cases = ['fake_03', 'fake_03_mixed', 'fake_04_A', 'fake_04_B', 'fake_04_C', 'fake_04_D', 'fake_04_A_mixed', 'fake_04_B_mixed', 'fake_04_C_mixed', 'fake_04_D_mixed'] ax = Monomial(('+', a, 1)) b_ = Monomial(('+', b, 0)) ax_2 = ax.clone() ax_2.set_exponent(2) a2x2 = Monomial(('+', a * a, 2)) b_2 = Monomial(('+', b, 0)) b_2.set_exponent(2) b2 = Monomial(('+', b * b, 0)) two_ax_b = Product([Item(2), Monomial(('+', a, 1)), Item(b)]) twoabx = Monomial(('+', 2 * a * b, 1)) fake_twoabx = Monomial(('+', a * b, 1)) if random.randint(1, 10) >= 8: fake_twoabx = Monomial(('+', 2 * a * b + random.choice([-1, 1]) * random.randint(1, 5), 1)) first_term = None second_term = None third_term = None ordered_expression = None mixed_expression = None if q_subkind == 'fake_03' or q_subkind == 'fake_03_mixed': first_term = a2x2.clone() second_term = b2.clone() ordered_expression = Polynomial([first_term, second_term]) mixed_expression = Polynomial([second_term, first_term]) else: first_term = a2x2.clone() third_term = b2.clone() if q_subkind in ['fake_01', 'fake_01_mixed', 'fake_02', 'fake_02_mixed']: # __ second_term = fake_twoabx.clone() else: second_term = twoabx.clone() if q_subkind == 'fake_02' or q_subkind == 'fake_02_mixed': second_term.set_sign('-') elif q_subkind == 'fake_04_A' or q_subkind == 'fake_04_A_mixed': third_term.set_sign('-') elif q_subkind == 'fake_04_B' or q_subkind == 'fake_04_B_mixed': first_term.set_sign('-') elif q_subkind == 'fake_04_C' or q_subkind == 'fake_04_C_mixed': second_term.set_sign('-') third_term.set_sign('-') elif q_subkind == 'fake_04_D' or q_subkind == 'fake_04_D_mixed': first_term.set_sign('-') second_term.set_sign('-') terms = [first_term, second_term, third_term] ordered_expression = Polynomial(terms) random.shuffle(terms) mixed_expression = Polynomial(terms) if q_subkind in straight_cases: steps.append(ordered_expression) elif q_subkind == 'fake_03_mixed': steps.append(mixed_expression) else: steps.append(mixed_expression) steps.append(ordered_expression) if q_subkind in match_pb_cases: let_a_eq = Equality([Item('a'), ax]) let_b_eq = Equality([Item('b'), b_]) steps.append(_("Let") + " " + let_a_eq.into_str(force_expression_markers=True) + " " + _("and") + " " + let_b_eq.into_str(force_expression_markers=True)) a_square_eq = Equality([Item(('+', 'a', 2)), ax_2, a2x2]) b_square_eq = Equality([Item(('+', 'b', 2)), b_2, b2]) steps.append(_("then") + " " + a_square_eq.into_str(force_expression_markers=True)) steps.append(_("and") + " " + b_square_eq.into_str(force_expression_markers=True)) two_times_a_times_b_eq = Equality([Product([Item(2), Item('a'), Item('b')]), two_ax_b, twoabx, fake_twoabx], equal_signs=['=', '=', 'neq']) steps.append(_("but") + " " + two_times_a_times_b_eq.into_str( force_expression_markers=True)) steps.append(_("So it does not match a binomial identity.")) steps.append(_("This expression cannot be factorized.")) elif q_subkind in sign_pb_cases: steps.append(_("Because of the signs,")) steps.append(_("it does not match a binomial identity.")) steps.append(_("This expression cannot be factorized.")) return steps