mathmaker  0.6(alpha)
mamk_misc/doc/mathmaker4doxygen/core/base_calculus.py
00001 # -*- coding: utf-8 -*-
00002 
00003 # Mathmaker creates automatically maths exercises sheets with their answers
00004 # Copyright 2006-2014 Nicolas Hainaux <nico_h@users.sourceforge.net>
00005 
00006 # This file is part of Mathmaker.
00007 
00008 # Mathmaker is free software; you can redistribute it and/or modify
00009 # it under the terms of the GNU General Public License as published by
00010 # the Free Software Foundation; either version 3 of the License, or
00011 # any later version.
00012 
00013 # Mathmaker is distributed in the hope that it will be useful,
00014 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016 # GNU General Public License for more details.
00017 
00018 # You should have received a copy of the GNU General Public License
00019 # along with Mathmaker; if not, write to the Free Software
00020 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00021 
00022 # ------------------------------------------------------------------------------
00023 # ------------------------------------------------------------------------------
00024 # ------------------------------------------------------------------------------
00025 ##
00026 # @package core.base_calculus
00027 # @brief Mathematical elementary arithmetic and algebraic objects.
00028 import locale
00029 import math
00030 from decimal import *
00031 
00032 import core
00033 from base import *
00034 from root_calculus import *
00035 
00036 from lib import randomly
00037 #from lib import translator
00038 from lib.maths_lib import *
00039 from lib.common.cst import *
00040 from lib.common.default import *
00041 from lib.utils import *
00042 from maintenance import debug
00043 from lib.common import cfg
00044 
00045 markup_choice = cfg.get_value_from_file('MARKUP', 'USE')
00046 
00047 if markup_choice == 'latex':
00048     from lib.common.latex import MARKUP
00049 
00050 
00051 if debug.ENABLED:
00052     from lib.common import latex
00053     import machine
00054 
00055 try:
00056     locale.setlocale(locale.LC_ALL, LANGUAGE + '.' + ENCODING)
00057 except:
00058     locale.setlocale(locale.LC_ALL, '')
00059 
00060 # Maximum ratio of constant terms accepted during the creation of random
00061 # polynomials
00062 CONSTANT_TERMS_MAXIMUM_RATIO = 0.4
00063 # Minimum always authorized number of constant terms during the creation of
00064 # random polynomials. This minimum is necessary to still have constant terms
00065 # in short polynomials, like the ones having only 1 ou 2 terms
00066 CONSTANT_TERMS_MINIMUM_NUMBER = 1
00067 
00068 # GLOBAL
00069 expression_begins = True
00070 
00071 
00072 
00073 
00074 # ------------------------------------------------------------------------------
00075 # ------------------------------------------------------------------------------
00076 # ------------------------------------------------------------------------------
00077 ##
00078 #   @class Item
00079 #   @brief It's the smallest displayable element (sign, value, exponent)
00080 #   The value can be either numeric or literal
00081 class Item(Exponented):
00082 
00083 
00084 
00085 
00086 
00087     # --------------------------------------------------------------------------
00088     ##
00089     #   @brief Constructor
00090     #   @warning Might raise an UncompatibleType exception.
00091     #   @param arg None|Number|String|Item|(sign,value,exponent)|
00092     #              (sign,number|letter|Value)|0-degree Monomial|Value
00093     #   Possible arguments can be :
00094     #   - a number which will will be the same as (sign_number, number, 1)
00095     #   - a letter : for example, passing 'a' is equivalent to ('+', 'a', 1)
00096     #                but passing '-x' is equivalent to ('-', 'x', 1)
00097     #                further characters are ignored ('ax' is equivalent to 'a')
00098     #   - another Item which will be copied
00099     #   - a tuple ('+'|'-', number|string)
00100     #   - a tuple ('+'|'-', number|string, <exponent as number|Exponented>)
00101     #   - None which will be the same as giving 1
00102     #   - a Monomial of degree zero and coefficient is an Item
00103     #   The is_out_striked field will always be initialized at False but will
00104     #   be copied in the case of an Item given as argument.
00105     #   If the argument is not of one of these kinds, an exception
00106     #   will be raised.
00107     #   @return One instance of Item
00108     def __init__(self, arg):
00109         Exponented.__init__(self)
00110         self._is_out_striked = False
00111         self._force_display_sign_once = False
00112         self._unit = None
00113 
00114         # 1st CASE : number
00115         # Item's sign will be number's sign
00116         # Item's value will be abs(number)
00117         # Item's exponent will be 1
00118         if is_.a_number(arg):
00119             if arg > 0:
00120                 self._value_inside = Value(arg)
00121             else:
00122                 self._sign = '-'
00123                 self._value_inside = Value(-arg)
00124 
00125         # 2d CASE : string
00126         # Item's sign will be either '-' if given, or '+'
00127         # Item's value will be the next letters
00128         # Item's exponent will be 1
00129         elif is_.a_string(arg) and len(arg) >= 1:
00130             if is_.a_sign(arg[0]) and len(arg) >= 2:
00131                 self._sign = arg[0]
00132                 self._value_inside = Value(arg[1:len(arg)])
00133             else:
00134                 self._sign = '+'
00135                 self._value_inside = Value(arg)
00136 
00137         # 3d CASE : Item
00138         elif type(arg) == Item:
00139             self._sign = arg.sign
00140             self._value_inside = arg.value_inside.clone()
00141             self._exponent = arg.exponent.clone()
00142             self._is_out_striked = arg.is_out_striked
00143             self._force_display_sign_once = arg.force_display_sign_once
00144 
00145         # 4th CASE : (sign, number|letter, <exponent as number|Exponented>)
00146         elif type(arg) == tuple and len(arg) == 3 and is_.a_sign(arg[0])      \
00147              and (is_.a_number(arg[1]) or is_.a_string(arg[1]))               \
00148              and (is_.a_number(arg[2]) or isinstance(arg[2], Exponented) \
00149                   or isinstance(arg[2], Value)):
00150         #___
00151             self._sign = arg[0]
00152             self._value_inside = Value(arg[1])
00153             if isinstance(arg[2], Exponented):
00154                 self._exponent = arg[2].clone()
00155             else:
00156                 self._exponent = Value(arg[2])
00157 
00158         # 5th CASE : (sign, number|letter)
00159         elif type(arg) == tuple and len(arg) == 2 and is_.a_sign(arg[0])      \
00160              and (is_.a_number(arg[1]) or is_.a_string(arg[1])):
00161         #___
00162             self._sign = arg[0]
00163             self._value_inside = Value(arg[1])
00164 
00165         # 6th CASE : None
00166         elif arg is None:
00167             self._value_inside = Value(1)
00168 
00169         # 7th CASE : A zero-degree Monomial having an Item as coefficient
00170         elif isinstance(arg, Monomial) and arg.is_numeric()\
00171              and isinstance(arg.factor[0], Item):
00172         #___
00173             self._sign = arg.get_sign()
00174             self._value_inside = Value(arg.factor[0].raw_value)
00175 
00176         # 8th CASE : A Value (the exponent will be one)
00177         elif isinstance(arg, Value):
00178             if arg.is_numeric():
00179                 if arg.raw_value < 0:
00180                     self._sign = '-'
00181                     self._value_inside = Value(-arg.raw_value)
00182                 else:
00183                     self._sign = '+'
00184                     self._value_inside = Value(arg.raw_value)
00185             else:
00186                 self._sign = '+'
00187                 self._value_inside = Value(arg.raw_value)
00188 
00189             self._value_inside.set_has_been_rounded(arg.has_been_rounded)
00190 
00191 
00192 
00193         # All other unforeseen cases : an exception is raised.
00194         else:
00195             raise error.UncompatibleType(arg,
00196                                          "Number|String|Item|" \
00197                                          + "(sign, Number|String, exponent)|" \
00198                                          + "(sign, Number|String)")
00199 
00200 
00201     # --------------------------------------------------------------------------
00202     ##
00203     #   @brief Gets the raw value of the Item
00204     #   @return value_inside.raw_value
00205     def get_is_out_striked(self):
00206         return self._is_out_striked
00207 
00208 
00209 
00210 
00211 
00212     # --------------------------------------------------------------------------
00213     ##
00214     #   @brief Gets force_display_sign_once field
00215     #   @return Item's force_display_sign_once field
00216     def get_force_display_sign_once(self):
00217         return self._force_display_sign_once
00218 
00219 
00220 
00221 
00222 
00223     # --------------------------------------------------------------------------
00224     ##
00225     #   @brief Gets the raw value of the Item
00226     #   @return value_inside.raw_value
00227     def get_raw_value(self):
00228         return self.value_inside.raw_value
00229 
00230 
00231 
00232 
00233 
00234     # --------------------------------------------------------------------------
00235     ##
00236     #   @brief Gets the Value of the Item
00237     #   @return value_inside
00238     def get_value_inside(self):
00239         return self._value_inside
00240 
00241 
00242 
00243 
00244 
00245     # --------------------------------------------------------------------------
00246     ##
00247     #   @brief Returns the unit of the Item
00248     def get_unit(self):
00249         return self._unit
00250 
00251 
00252 
00253 
00254 
00255     # --------------------------------------------------------------------------
00256     ##
00257     #   @brief Gets the number of '-' signs of the Item
00258     #   @return The number of '-' signs of the Item (either 0, 1 or 2)
00259     def get_minus_signs_nb(self):
00260         nb = 0
00261 
00262         if self.is_negative() and not self.is_null():
00263             nb += 1
00264 
00265         if self.is_numeric():
00266             if Decimal(str(self.raw_value)) < Decimal("0") \
00267                 and is_uneven(self.exponent):
00268             #___
00269                 nb += 1
00270 
00271         return nb
00272 
00273 
00274 
00275 
00276 
00277     # --------------------------------------------------------------------------
00278     ##
00279     #   @brief Returns the list of elements to iter over
00280     def get_iteration_list(self):
00281         return [self.value_inside, self.exponent]
00282 
00283 
00284 
00285 
00286 
00287 
00288     # --------------------------------------------------------------------------
00289     ##
00290     #   @brief Returns the letter of the Item, in case it's a literal
00291     def get_first_letter(self):
00292         if self.is_literal():
00293             return self.raw_value
00294 
00295         else:
00296             raise error.UncompatibleType(self, "Litteral Item")
00297 
00298 
00299 
00300 
00301 
00302 
00303 
00304 
00305 
00306 
00307 
00308 
00309     is_out_striked = property(get_is_out_striked,
00310                               doc = "Item's is_out_striked field")
00311 
00312     force_display_sign_once = property(get_force_display_sign_once,
00313                               doc = "Item's force_display_sign_once field")
00314 
00315     raw_value = property(get_raw_value, doc = "Item's raw value")
00316 
00317     value_inside = property(get_value_inside, doc = "Item's Value")
00318 
00319     unit = property(get_unit, doc = "Unit of the Item")
00320 
00321 
00322 
00323 
00324     # --------------------------------------------------------------------------
00325     ##
00326     #   @brief Set the unit of the Item
00327     #   @param  arg String
00328     def set_unit(self, arg):
00329         if not type(arg) == str:
00330             raise error.WrongArgument(str(type(arg)), "a str")
00331         else:
00332             self._unit = arg
00333 
00334 
00335 
00336 
00337     # --------------------------------------------------------------------------
00338     ##
00339     #   @brief Sets a value to the "is_out_striked" field
00340     # If is_out_striked is set to True, the Item will be displayed out striked
00341     def set_is_out_striked(self, arg):
00342         if arg in [True, False]:
00343             self._is_out_striked = arg
00344         else:
00345             raise error.WrongArgument(str(arg), "True|False")
00346 
00347 
00348 
00349 
00350 
00351     # --------------------------------------------------------------------------
00352     ##
00353     #   @brief Sets a True|False value to the "force_display_sign_once" field
00354     def set_force_display_sign_once(self, arg):
00355         if arg in [True, False]:
00356             self._force_display_sign_once = arg
00357         else:
00358             raise error.WrongArgument(str(arg), "True|False")
00359 
00360 
00361 
00362 
00363 
00364     # --------------------------------------------------------------------------
00365     ##
00366     #   @brief Sets the Value inside
00367     def set_value_inside(self, arg):
00368         if isinstance(arg, Value):
00369             self._value_inside = arg.clone()
00370         else:
00371             raise error.WrongArgument(str(type(arg)), "a Value")
00372 
00373 
00374 
00375 
00376 
00377 
00378 
00379     # --------------------------------------------------------------------------
00380     ##
00381     #   @brief Creates a string of the given object in the given ML
00382     #   @param options Any options
00383     #   @return The formated string
00384     def into_str(self, **options):
00385         global expression_begins
00386         # Displaying the + sign depends on the expression_begins flag of the
00387         # machine :
00388         #  - either it's True : + won't be displayed
00389         #  - or it's False : + will be displayed
00390         # "Normal" state of this flag is False.
00391         # It is set to True outside of this section of into_str, every time
00392         # it's necessary (for example, as soon as a bracket has been
00393         # displayed the next object should be displayed like at the beginning
00394         # of an expression).
00395         # Everytime an Item is written, expression_begins is set to False again
00396 
00397         if 'force_expression_begins' in options \
00398            and options['force_expression_begins'] == True:
00399         #___
00400             expression_begins = options['force_expression_begins']
00401             options['force_expression_begins'] = False
00402 
00403         #DEBUG
00404         debug.write("\n" + "[Item] Entering into_str " \
00405                          + "with force_display_sign_once == " \
00406                          + str(self.force_display_sign_once),
00407                          case=debug.into_str_in_item)
00408 
00409         resulting_string = ""
00410 
00411         sign = ''
00412 
00413         inner_bracket_1 = ''
00414         inner_bracket_2 = ''
00415 
00416         if self.requires_inner_brackets():
00417             inner_bracket_1 = MARKUP['opening_bracket']
00418             inner_bracket_2 = MARKUP['closing_bracket']
00419 
00420         if not expression_begins                                              \
00421            or ('force_display_sign' in options and self.is_numeric()) \
00422            or self.force_display_sign_once:
00423         #___
00424             if self.sign == '+' or self.is_null():
00425                 sign = MARKUP['plus']
00426             else:
00427                 sign = MARKUP['minus']
00428             if self.force_display_sign_once:
00429                 self.set_force_display_sign_once(False)
00430         else:
00431             if self.sign == '-' and not self.is_null():
00432                 sign = MARKUP['minus']
00433 
00434         if self.exponent.is_displ_as_a_single_0():
00435         #___
00436             if 'force_display_exponent_0' in options                          \
00437                or 'force_display_exponents' in options:
00438             #___
00439                 resulting_string += inner_bracket_1                           \
00440                                  + self.value_inside.into_str()      \
00441                                  + inner_bracket_2                            \
00442                                  + MARKUP['opening_exponent']                 \
00443                                  + MARKUP['zero']                             \
00444                                  + MARKUP['closing_exponent']
00445             else:
00446                 resulting_string += MARKUP['one']
00447 
00448         elif self.exponent_must_be_displayed():
00449             if isinstance(self.exponent, Exponented):
00450                 expression_begins = True
00451 
00452             resulting_string += inner_bracket_1                               \
00453                              + self.value_inside.into_str()          \
00454                              + inner_bracket_2                                \
00455                              + MARKUP['opening_exponent']                     \
00456                              + self.exponent.into_str(**options)           \
00457                              + MARKUP['closing_exponent']
00458 
00459         else: # that should only concern cases where the exponent
00460               # is equivalent to 1
00461             if 'force_display_exponent_1' in options                          \
00462                or 'force_display_exponents' in options:
00463             #___
00464                 resulting_string += inner_bracket_1                           \
00465                                  + self.value_inside.into_str()      \
00466                                  + inner_bracket_2                            \
00467                                  + MARKUP['opening_exponent']                 \
00468                                  + MARKUP['one']                              \
00469                                  + MARKUP['closing_exponent']
00470             else:
00471                 resulting_string += inner_bracket_1                           \
00472                                  + self.value_inside.into_str()      \
00473                                  + inner_bracket_2
00474                                  #+ MARKUP['space']
00475 
00476         if self.is_out_striked:
00477             resulting_string = MARKUP['opening_out_striked']                  \
00478                                + resulting_string                             \
00479                                + MARKUP['closing_out_striked']
00480 
00481         if self.unit != None and 'display_unit' in options \
00482             and (options['display_unit'] == True \
00483                  or options['display_unit'] == 'yes'):
00484         #___
00485             resulting_string += " " + str(self.unit)
00486 
00487         expression_begins = False
00488 
00489 
00490         return sign + resulting_string
00491 
00492 
00493 
00494 
00495 
00496     # --------------------------------------------------------------------------
00497     ##
00498     #   @brief Returns the value of a numerically evaluable Item
00499     #   @warning Relays an exception if the exponent is not Exponented|Value
00500     def evaluate(self):
00501         expon = self.exponent.evaluate()
00502 
00503         if self.is_numeric():
00504             if self.is_positive():
00505                 return (self.raw_value)**expon
00506             else:
00507                 return -(self.raw_value)**expon
00508 
00509         else:
00510             raise error.IncompatibleType(self, "Number|numeric Exponented")
00511 
00512 
00513 
00514 
00515 
00516     # --------------------------------------------------------------------------
00517     ##
00518     #   @brief Returns None|an Item
00519     #   @todo Manage the case when the exponent is a calculable that should
00520     #   be calculated itself.
00521     #   @warning Relays an exception if the exponent is not Exponented|Value
00522     # If the Item has an exponent equivalent to a single 1, then nothing
00523     # can be calculated, so this method returns None
00524     # In another case, it returns the evaluated Item
00525     def calculate_next_step(self, **options):
00526         if not self.is_numeric():
00527             raise error.UncompatibleType(self, "Number|numeric Exponented")
00528 
00529         #DEBUG
00530         debug.write("\n[calculate_next_step_item] Entered\n"\
00531                   + "[calculate_next_step_item] current Item is : " \
00532                     + self.dbg_str() + "\n",
00533                     case=debug.calculate_next_step_item)
00534 
00535         # First, either get the exponent as a Number or calculate it further
00536         expon_test = self.exponent.calculate_next_step(**options)
00537 
00538         #DEBUG
00539         debug.write("[calculate_next_step_item] expon_test = "\
00540                     + str(expon_test) + "\n",
00541                     case=debug.calculate_next_step_item)
00542 
00543         if expon_test != None:
00544             return Item((self.sign,
00545                              self.raw_value,
00546                              expon_test
00547                             ))
00548 
00549         expon = self.exponent.evaluate()
00550 
00551         #DEBUG
00552         debug.write("[calculate_next_step_item] expon = "\
00553                     + str(expon) + "\n",
00554                     case=debug.calculate_next_step_item)
00555 
00556         # Now the exponent is a number (saved in "expon")
00557 
00558         # If it is different from 1, then the next step is to calculate
00559         # a new Item using this exponent (for example,
00560         # the Item 4² would return the Item 16)
00561         if expon != 1:
00562             #DEBUG
00563             debug.write("[calculate_next_step_item] expon is != 1\n",
00564                         case=debug.calculate_next_step_item)
00565             # Intricated case where the inner sign is negative
00566             # (like in ±(-5)³)
00567             if self.raw_value < 0:
00568                 #DEBUG
00569                 debug.write("[calculate_next_step_item] self.raw_value < 0\n",
00570                             case=debug.calculate_next_step_item)
00571 
00572                 aux_inner_sign = Item(('+', -1, expon))
00573                 return Item((sign_of_product([self.sign,
00574                                                   aux_inner_sign]),
00575                              (- self.raw_value)**expon,
00576                              1))
00577             # Simple case like -3² or 5³
00578             else:
00579                 #DEBUG
00580                 debug.write("[calculate_next_step_item] self.raw_value >= 0\n",
00581                             case=debug.calculate_next_step_item)
00582                 return Item((self.sign,
00583                              self.raw_value ** expon,
00584                              1))
00585 
00586         # Now the exponent is 1
00587         else:
00588             #DEBUG
00589             debug.write("[calculate_next_step_item] expon is == 1\n",
00590                         case=debug.calculate_next_step_item)
00591             # Case of -(-something)
00592             if self.raw_value < 0 and self.sign == '-':
00593                 return Item(('+',
00594                              - self.raw_value,
00595                              expon))
00596 
00597             # Other cases like ±number where ± is either external or
00598             # inner sign
00599             else:
00600                 return None
00601 
00602 
00603 
00604 
00605 
00606     # --------------------------------------------------------------------------
00607     ##
00608     #   @brief Returns None (an Item can't get expanded nor reduced !)
00609     #   @return Exponented
00610     def expand_and_reduce_next_step(self, **options):
00611         if self.is_numeric():
00612             return self.calculate_next_step(**options)
00613         else:
00614             return None
00615 
00616 
00617 
00618 
00619 
00620     # --------------------------------------------------------------------------
00621     ##
00622     #   @brief Raw display of the Item (debugging method)
00623     #   @param options No option available so far
00624     #   @return A string containing "{sign value ^ exponent}"
00625     def dbg_str(self, **options):
00626         if self.is_out_striked:
00627             begining = " s{"
00628         else:
00629             begining = " {"
00630 
00631 
00632         return begining                                     \
00633                + self.sign                                   \
00634                + str(self.raw_value)                              \
00635                + "^"                                           \
00636                + self.exponent.dbg_str() +"} "
00637 
00638 
00639 
00640 
00641 
00642     # --------------------------------------------------------------------------
00643     ##
00644     #   @brief Compares two Items
00645     #   @return True if they're equal
00646     def __eq__(self, other_item):
00647         if not isinstance(other_item, Item):
00648             #DEBUG
00649             debug.write("\n[Item.__eq__()] : the other object is not an Item,"\
00650                         + " returning False",
00651                 case=debug.eq_in_item)
00652 
00653             return False
00654 
00655         if self.sign == other_item.sign                         \
00656                 and self.raw_value == other_item.raw_value      \
00657                 and self.exponent == other_item.exponent:
00658         #___
00659             #DEBUG
00660             debug.write("\n[Item.__eq__()] : everything found equal",
00661                         case=debug.eq_in_item)
00662             return True
00663         else:
00664             #DEBUG
00665             debug.write("\n[Item.__eq__()] : "\
00666                         + "self.sign is " + str(self.sign) + " "\
00667                         + "other_item.sign is " + str(other_item.sign)\
00668                         + " and they're equal? " \
00669                         + str(self.sign == other_item.sign) \
00670                         + "\n[Item.__eq__()] : "\
00671                         + "self.raw_value is " + str(self.raw_value) + " "\
00672                         + "other_item.raw_value is " + str(other_item.raw_value)\
00673                         + " and they're equal? " \
00674                         + str(self.raw_value == other_item.raw_value) \
00675                         + "\n[Item.__eq__()] : "\
00676                         + "self.exponent is " + str(self.exponent) + " "\
00677                         + "other_item.exponent is " + str(other_item.exponent)\
00678                         + " and they're equal? " \
00679                         + str(self.exponent == other_item.exponent),
00680                         case=debug.eq_in_item)
00681             return False
00682 
00683 
00684 
00685 
00686 
00687     # --------------------------------------------------------------------------
00688     ##
00689     #   @brief Compares an Item to something else ; it's a reimplementing of
00690     #          alphabetical_order_cmp(), since we need this comparison
00691     #   @return True if self is lower than the other_item
00692     def __lt__(self, other_objct):
00693         if self.is_numeric() and other_objct.is_numeric():
00694             return False
00695 
00696         elif self.is_literal() and other_objct.is_numeric():
00697             return False
00698 
00699         elif self.is_numeric() and other_objct.is_literal():
00700             return True
00701 
00702         elif self.is_literal() and other_objct.is_literal():
00703             self_value = self.get_first_letter()
00704             other_value = other_objct.get_first_letter()
00705 
00706             # let's compare
00707             if self_value == other_value:
00708                 return False
00709             elif alphabet.order[self_value] > alphabet.order[other_value]:
00710                 return False
00711             else:
00712                 return True
00713 
00714 
00715 
00716 
00717     # --------------------------------------------------------------------------
00718     ##
00719     #   @brief Makes Items hashable (so, usable as dictionnary keys)
00720     def __hash__(self):
00721         return hash(str(self.raw_value) + str(self.sign) \
00722                                         + self.exponent.dbg_str())
00723 
00724 
00725 
00726 
00727 
00728     # --------------------------------------------------------------------------
00729     ##
00730     #   @brief Returns the Item's length
00731     #   @return 1
00732     def __len__(self):
00733         return 1
00734 
00735 
00736 
00737 
00738     # --------------------------------------------------------------------------
00739     ##
00740     #   @brief True if the usual writing rules require a × between two factors
00741     #   @param objct The other one
00742     #   @param position The position (integer) of self in the Product
00743     #   @return True if the writing rules require × between self & obj
00744     def multiply_symbol_is_required(self, objct, position):
00745         # 1st CASE : Item × Item
00746         # and other cases inside : numeric × numeric or numeric × literal etc.
00747         # The code could be shortened but will not, for better comprehension.
00748         if isinstance(objct, Item):
00749             # ex: 2 × 4
00750             if ((self.is_numeric() or self.is_displ_as_a_single_1()) \
00751                and (objct.is_numeric() \
00752                     or objct.is_displ_as_a_single_1())):
00753             #___
00754                 return True
00755 
00756             # ex: a × 3 (writing a3 isn't OK)
00757             elif self.is_literal() \
00758                  and (objct.is_numeric() \
00759                       or objct.is_displ_as_a_single_1()):
00760             #___
00761                 return True
00762 
00763             elif self.is_numeric() and objct.is_literal():
00764                 if self.raw_value == 1                                      \
00765                    or self.requires_brackets(position)                        \
00766                    or objct.requires_brackets(position +1):
00767                 #___
00768                     return True
00769                 else:
00770                     return False
00771 
00772             elif self.is_literal() and objct.is_literal():
00773                 if self.requires_brackets(position)                           \
00774                    or objct.requires_brackets(position +1):
00775                 #___
00776                     return True
00777                 else:
00778                     if not self.exponent_must_be_displayed()                  \
00779                        and self.raw_value == objct.raw_value:
00780                     #___
00781                         return True
00782                     else:
00783                         return False
00784 
00785         # 2d CASE : Item × Product
00786         if isinstance(objct, Product):
00787             return self.multiply_symbol_is_required(objct.factor[0],
00788                                                     position)
00789 
00790         # 3d CASE : Item × Sum
00791         if isinstance(objct, Sum):
00792             if len(objct.throw_away_the_neutrals()) >= 2:
00793                 if self.is_numeric() and self.raw_value == 1:
00794                     return True
00795                 else:
00796                     return False
00797             else:
00798                 return self.multiply_symbol_is_required(objct.\
00799                                              throw_away_the_neutrals().term[0],
00800                                                         position)
00801 
00802         # 4th CASE : Item × Quotient
00803         if isinstance(objct, Quotient):
00804             return True
00805 
00806 
00807 
00808 
00809 
00810     # --------------------------------------------------------------------------
00811     ##
00812     #   @brief True if the argument requires brackets in a product
00813     #   For instance, a Sum with several terms or a negative Item
00814     #   @param position The position of the object in the Product
00815     #   @return True if the object requires brackets in a Product
00816     def requires_brackets(self, position):
00817         # an Item at first position doesn't need brackets
00818         if position == 0:
00819             return False
00820         # if the Item isn't at first position, then it depends on its sign
00821         elif self.sign == '+':
00822             # The case of literals which don't need inner brackets but
00823             # do have a minus sign "inside" is quite tricky. Maybe should
00824             # be managed better than that ; check the requires_inner_bracket()
00825             # and take care that it already calls requires_brackets() !
00826             if self.is_literal() and self.raw_value[0] == '-':
00827                 return True
00828             else:
00829                 return False
00830         elif self.sign == '-':
00831             return True
00832 
00833 
00834 
00835 
00836 
00837     # --------------------------------------------------------------------------
00838     ##
00839     #   @brief True if the object requires inner brackets
00840     #   The reason for requiring them is having a negative *value* and
00841     #   if the exponent is either :
00842     #   - (numeric Item | number) and even
00843     #   - (numeric Item | number) equivalent to 1 the object has a '-' *sign*
00844     #   - litteral Item
00845     #   - any Exponented apart from Items.
00846     #   @todo Case of non-Item-Exponented exponents probably is to be improved
00847     #   @todo Case of numerator-only equivalent Quotients not made so far
00848     #   @return True if the object requires inner brackets
00849     def requires_inner_brackets(self):
00850         # CHECK if the *value* is negative (not the sign !) :
00851         if (self.is_numeric() and self.raw_value < 0)                  \
00852            or (self.is_literal() and self.raw_value[0] == '-'):
00853         #___
00854             # To avoid two - signs in a row, inner brackets must be displayed
00855             if self.is_negative():
00856                 return True
00857 
00858             # First, check the most common cases of Item/number/string
00859             # exponents...
00860 
00861             # NUMERIC exponents (with a positive *sign*, the negative ones
00862             #                    having already be managed just above)
00863             if (isinstance(self.exponent, Value) \
00864                 or isinstance(self.exponent, Item)) \
00865                and self.exponent.is_numeric():
00866             #___
00867                 if is_even(self.exponent):
00868                     return True
00869 
00870             # LITERAL exponents
00871             elif self.exponent.is_literal():
00872                 return True
00873 
00874             # Now the cases of non-Item-but-though-(numeric) Exponented
00875             # exponents :
00876             # General rule is the brackets will be required as long as the
00877             # displayed value is not simple. In something like (-4)^{2 - 1},
00878             # the inner brackets must be displayed although they are not
00879             # necessary
00880             elif isinstance(self.exponent, Exponented)                              \
00881                  and not isinstance(self.exponent, Item):
00882             #___
00883                 if self.exponent.is_displ_as_a_single_1():
00884                     return False
00885                 elif isinstance(self.exponent, CommutativeOperation)                     \
00886                      and len(self.exponent) == 1                              \
00887                      and not self.exponent.exponent_must_be_displayed():
00888                 #___
00889                     aux_item = Item((self.sign,
00890                                      self.raw_value,
00891                                      self.exponent.element[0]))
00892                     return aux_item.requires_inner_brackets()
00893 
00894                 else:
00895                     return True
00896 
00897         return False
00898 
00899 
00900 
00901 
00902 
00903     # --------------------------------------------------------------------------
00904     ##
00905     #   @brief Always False for an Item
00906     #   @param objct The object to search for
00907     #   @return False
00908     def contains_exactly(self, objct):
00909         return False
00910 
00911 
00912 
00913 
00914 
00915 
00916     # --------------------------------------------------------------------------
00917     ##
00918     #   @brief To check if this contains a rounded number...
00919     #   @return True or False depending on the Value inside
00920     def contains_a_rounded_number(self):
00921         return self.value_inside.has_been_rounded
00922 
00923 
00924 
00925 
00926 
00927 
00928     # --------------------------------------------------------------------------
00929     ##
00930     #   @brief Returns the (numeric) Item once rounded to the given precision
00931     def round(self, precision):
00932         if not self.exponent.is_displ_as_a_single_1():
00933             raise error.UncompatibleType(self, "the exponent should be" \
00934                                                + " equivalent to a single 1")
00935         else:
00936             return Item(self.value_inside.round(precision))
00937 
00938 
00939 
00940 
00941 
00942     # --------------------------------------------------------------------------
00943     ##
00944     #   @brief Returns the number of digits of a numerical Item
00945     def digits_number(self):
00946         if not self.exponent.is_displ_as_a_single_1():
00947             raise error.UncompatibleType(self, "the exponent should be" \
00948                                                + " equivalent to a single 1")
00949         else:
00950             return self.value_inside.digits_number()
00951 
00952 
00953 
00954 
00955 
00956     # --------------------------------------------------------------------------
00957     ##
00958     #   @brief Returns True/False depending on the need of the value to get
00959     #          rounded (for instance 2.68 doesn't need to get rounded if
00960     #          precision is HUNDREDTH or more, but needs it if it is less)
00961     #          If the Item is not numeric, or if the given precision is
00962     #          incorrect, the matching call to the Value
00963     #          will raise an exception.
00964     def needs_to_get_rounded(self, precision):
00965         if not self.exponent.is_displ_as_a_single_1():
00966             raise error.UncompatibleType(self, "the exponent should be" \
00967                                                + " equivalent to a single 1")
00968 
00969         return self.value_inside.needs_to_get_rounded(precision)
00970 
00971 
00972 
00973 
00974 
00975     # --------------------------------------------------------------------------
00976     ##
00977     #   @brief Turns the Item into the fraction item itself over item 1
00978     def turn_into_fraction(self):
00979         return Fraction(('+', self, Item(1)))
00980 
00981 
00982 
00983 
00984 
00985 
00986     # --------------------------------------------------------------------------
00987     ##
00988     #   @brief True if it's a numeric Item
00989     def is_numeric(self):
00990         if self.value_inside.is_numeric() and self.exponent.is_numeric():
00991             return True
00992         else:
00993             return False
00994 
00995 
00996 
00997 
00998 
00999     # --------------------------------------------------------------------------
01000     ##
01001     #   @brief True if it's a literal Item
01002     def is_literal(self):
01003         if self.value_inside.is_literal() or self.exponent.is_literal(): \
01004             return True
01005         else:
01006             return False
01007 
01008 
01009 
01010 
01011 
01012     # --------------------------------------------------------------------------
01013     ##
01014     #   @brief True if it's the null Item
01015     def is_null(self):
01016         if self.exponent.evaluate() == ZERO_POLYNOMIAL_DEGREE \
01017            or (self.value_inside.is_null()):
01018         #___
01019             return True
01020         else:
01021             return False
01022 
01023 
01024 
01025 
01026 
01027 
01028     # --------------------------------------------------------------------------
01029     ##
01030     #   @brief True if it's positive w/ (exponent 0 or numeric w/ value 1)
01031     def is_displ_as_a_single_1(self):
01032         if self.sign == '+':
01033             if (self.exponent.is_null()) \
01034                or (self.value_inside.is_displ_as_a_single_1()):
01035             #___
01036                 return True
01037 
01038         return False
01039 
01040 
01041 
01042 
01043 
01044     # --------------------------------------------------------------------------
01045     ##
01046     #   @brief True if it's negative w/ (exponent 0 or numeric w/ value 1)
01047     def is_displ_as_a_single_minus_1(self):
01048         if self.sign == '-':
01049             if self.exponent.is_null() \
01050                or (self.value_inside.is_displ_as_a_single_1()):
01051             #___
01052                 return True
01053 
01054         if self.sign == '+':
01055             if self.value_inside.is_displ_as_a_single_minus_1()   \
01056                and is_uneven(self.exponent):
01057             #___
01058                 return True
01059 
01060         return False
01061 
01062 
01063 
01064 
01065 
01066     # --------------------------------------------------------------------------
01067     ##
01068     #   @brief True if self.is_null()
01069     def is_displ_as_a_single_0(self):
01070         return self.is_null()
01071 
01072 
01073 
01074 
01075 
01076 
01077     # --------------------------------------------------------------------------
01078     ##
01079     #   @brief True if the Item is numeric
01080     def is_displ_as_a_single_numeric_Item(self):
01081         return self.is_numeric()
01082 
01083 
01084 
01085 
01086 
01087     # --------------------------------------------------------------------------
01088     ##
01089     #   @brief True if the object can be displayed as a single int
01090     def is_displ_as_a_single_int(self):
01091         return self.value_inside.is_displ_as_a_single_int() and \
01092                 self.exponent.is_displ_as_a_single_1()
01093 
01094 
01095 
01096 
01097     # --------------------------------------------------------------------------
01098     ##
01099     #   @brief True if the object can be considered as a neutral element
01100     def is_displ_as_a_single_neutral(self, elt):
01101         if elt == Item(0):
01102             return self.is_displ_as_a_single_0()
01103         elif elt == Item(1):
01104             return self.is_displ_as_a_single_1()
01105         else:
01106             print(elt.dbg_str())
01107             print(Item(0).dbg_str())
01108             raise error.UncompatibleType(elt, "neutral element for addition" \
01109                                               + " or multiplication, e.g." \
01110                                               + " Item(1) | Item(0).")
01111 
01112 
01113 
01114 
01115 
01116     # --------------------------------------------------------------------------
01117     ##
01118     #   @brief False
01119     #   @return False
01120     def is_expandable(self):
01121         return False
01122 
01123 
01124 
01125 
01126 
01127 # ------------------------------------------------------------------------------
01128 # --------------------------------------------------------------------------
01129 # ------------------------------------------------------------------------------
01130 ##
01131 #   @class Function
01132 #   @brief It's all the f(x), cos(x) etc. with only one variable at the moment
01133 class Function(Item):
01134 
01135 
01136 
01137 
01138 
01139     # --------------------------------------------------------------------------
01140     ##
01141     #   @brief Constructor
01142     #   @warning Might raise an UncompatibleType exception.
01143     #   @param arg (String,
01144     #               literalCalculable|Angle,
01145     #               None|math.function()|Calculable,
01146     #               None|math.function()|Calculable)
01147     #   The first String will be the name of the function, the second arg
01148     #   will give the variable String
01149     #   The third argument can be either None (if None is needed), a function
01150     #   of the math module, or a Calculable
01151     #   The fourth one can be of the same type as the third one ; this is
01152     #   meant to be the inverse function of the third one
01153     #   @return One instance of Function
01154     def __init__(self, arg):
01155         if not type(arg) == tuple:
01156             raise error.WrongArgument(str(type(arg)), "a tuple")
01157 
01158         if not len(arg) == 4:
01159             raise error.WrongArgument("a tuple of length " + str(len(arg)),
01160                                       "a tuple of length 4")
01161 
01162         if not type(arg[0]) == str:
01163             raise error.WrongArgument(str(type(arg[0])), "a str")
01164 
01165         if not isinstance(arg[1], core.base_geometry.Angle) \
01166             and not (isinstance(arg[1], Calculable) and arg[1].is_literal()):
01167         #___
01168             raise error.WrongArgument(str(type(arg[1])),
01169                                       "literalCalculable|Angle")
01170 
01171         # the hasattr conditions test if the elt is a function from math module
01172         for elt in [arg[2], arg[3]]:
01173             if not (elt is None \
01174                     or isinstance(elt, Calculable) \
01175                     or (hasattr(elt, "__name__") and hasattr(math, elt.__name__))
01176                    ):
01177             #___
01178                 raise error.WrongArgument(str(type(elt)),
01179                                           "None|math.function()|Calculable")
01180 
01181         self._name = arg[0]
01182 
01183         self._variable = arg[1]
01184 
01185         self._internal_expression = arg[2]
01186         self._reverse_expression= arg[3]
01187 
01188         self._sign = "+"
01189 
01190         self._numeric_value = None
01191 
01192         self._displayed_value = self._variable
01193 
01194         self._exponent = Value(1)
01195 
01196         self._value_inside = Value('x')
01197 
01198         self._is_out_striked = False
01199         self._force_display_sign_once = False
01200         self._unit = ""
01201 
01202 
01203 
01204 
01205 
01206 
01207 
01208     # --------------------------------------------------------------------------
01209     ##
01210     #   @brief Returns the variable as a String
01211     def get_variable(self):
01212         return self._variable
01213 
01214 
01215 
01216 
01217 
01218     # --------------------------------------------------------------------------
01219     ##
01220     #   @brief Returns the numeric Value to replace the variable with
01221     def get_numeric_value(self):
01222         return self._numeric_value
01223 
01224 
01225 
01226 
01227     # --------------------------------------------------------------------------
01228     ##
01229     #   @brief Returns either the variable or the numeric value to display
01230     def get_displayed_value(self):
01231         return self._displayed_value
01232 
01233 
01234 
01235 
01236 
01237     # --------------------------------------------------------------------------
01238     ##
01239     #   @brief Returns the expression used to evaluate the Function
01240     def get_internal_expression(self):
01241         return self._internal_expression
01242 
01243 
01244 
01245 
01246 
01247     numeric_value = property(get_numeric_value,
01248                              doc = "Value to use to replace the variable"\
01249                                    " (e.g. '9' or '60\textdegree'...)")
01250 
01251     variable = property(get_variable,
01252                         doc = "Variable of the Function"\
01253                               " (e.g. 'x' or '\widehat{ABC}'...)")
01254 
01255     displayed_value = property(get_displayed_value,
01256                                doc = "Value to display (variable or numeric)")
01257 
01258     internal_expression = property(get_internal_expression,
01259                                    doc = "Used to evaluate the Function")
01260 
01261 
01262 
01263 
01264 
01265     # --------------------------------------------------------------------------
01266     ##
01267     #   @brief Sets the numeric Value to replace the variable with
01268     def set_numeric_value(self, arg):
01269         if not (isinstance(arg, Item) or isinstance(arg, Value)):
01270             raise error.WrongArgument(arg, "an Item|Value")
01271 
01272         if not arg.is_numeric():
01273             raise error.WrongArgument(arg, "a numeric Item|Value")
01274 
01275         self._numeric_value = arg.clone()
01276 
01277 
01278 
01279 
01280 
01281     # --------------------------------------------------------------------------
01282     ##
01283     #   @brief Sets the displayed value to the literal one
01284     #   @return Nothing
01285     def swap_to_literal(self):
01286         self._displayed_value = self._variable
01287 
01288 
01289 
01290 
01291 
01292     # --------------------------------------------------------------------------
01293     ##
01294     #   @brief Sets the displayed value to the numeric one
01295     #   @return Nothing
01296     def swap_to_numeric(self):
01297         self._displayed_value = self._numeric_value
01298 
01299 
01300 
01301 
01302 
01303     # --------------------------------------------------------------------------
01304     ##
01305     #   @brief Always False
01306     def is_displ_as_a_single_1(self):
01307         return False
01308 
01309 
01310 
01311 
01312 
01313     # --------------------------------------------------------------------------
01314     ##
01315     #   @brief True if the object can be displayed as a single int
01316     def is_displ_as_a_single_int(self):
01317         return False
01318 
01319 
01320 
01321 
01322     # --------------------------------------------------------------------------
01323     ##
01324     #   @brief Always False
01325     def is_displ_as_a_single_minus_1(self):
01326         return False
01327 
01328 
01329 
01330 
01331 
01332     # --------------------------------------------------------------------------
01333     ##
01334     #   @brief Always False
01335     def is_displ_as_a_single_0(self):
01336         return False
01337 
01338 
01339 
01340 
01341 
01342 
01343     # --------------------------------------------------------------------------
01344     ##
01345     #   @brief Always False
01346     def is_displ_as_a_single_numeric_Item(self):
01347         return False
01348 
01349 
01350 
01351 
01352 
01353     # --------------------------------------------------------------------------
01354     ##
01355     #   @brief False
01356     #   @return False
01357     def is_expandable(self):
01358         return False
01359 
01360 
01361 
01362 
01363 
01364 
01365 # ------------------------------------------------------------------------------
01366 # --------------------------------------------------------------------------
01367 # ------------------------------------------------------------------------------
01368 ##
01369 #   @class SquareRoot
01370 #   @brief It's a Exponented under a square root
01371 #   The Exponented can be either numeric or literal
01372 class SquareRoot(Function):
01373 
01374 
01375 
01376 
01377 
01378     # --------------------------------------------------------------------------
01379     ##
01380     #   @brief Constructor
01381     #   @warning Might raise an UncompatibleType exception.
01382     #   @param arg Exponented|(sign, Exponented)
01383     #           The given Exponented will be "embedded" in the SquareRoot
01384     #   @param options : copy='yes' can be used to produce a copy of
01385     #                    another SquareRoot. If not used, the other SquareRoot
01386     #                    will get embedded in a new SquareRoot.
01387     #   @return One instance of SquareRoot
01388     def __init__(self, arg, **options):
01389         Exponented.__init__(self)
01390         self._force_display_sign_once = False
01391         self.radicand = Item(1)
01392 
01393         # 1st CASE : a SquareRoot
01394         if isinstance(arg, SquareRoot):
01395             if 'embbed' in options \
01396                 and options['embbed'] == 'yes':
01397             #___
01398                 self.radicand = arg.clone()
01399             else:
01400                 self._force_display_sign_once = arg.force_display_sign_once
01401                 self._sign = arg.sign
01402                 self.radicand = arg.radicand.clone()
01403 
01404         # 2d CASE : any other Exponented
01405         elif isinstance(arg, Exponented):
01406             self.radicand = arg.clone()
01407 
01408         # 3d CASE : a tuple (sign, Exponented)
01409         elif isinstance(arg, tuple) \
01410             and len(arg) == 2 \
01411             and is_.a_sign(arg[0]) \
01412             and isinstance(arg[1], Exponented):
01413         #___
01414             self._sign = arg[0]
01415             self.radicand = arg[1].clone()
01416 
01417         # All other unforeseen cases : an exception is raised.
01418         else:
01419             raise error.UncompatibleType(arg,
01420                                          "Exponented")
01421 
01422 
01423 
01424 
01425 
01426     # --------------------------------------------------------------------------
01427     ##
01428     #   @brief Returns the list of elements to iter over
01429     def get_iteration_list(self):
01430         return [self.radicand, self.exponent]
01431 
01432 
01433 
01434 
01435 
01436 
01437     # --------------------------------------------------------------------------
01438     ##
01439     #   @brief Gets the number of '-' signs of the SquareRoot
01440     #   @return The number of '-' signs of the SquareRoot (either 0 or 1)
01441     def get_minus_signs_nb(self):
01442         if self.is_negative() and not self.is_null():
01443             return 1
01444         else:
01445             return 0
01446 
01447 
01448 
01449 
01450 
01451     # --------------------------------------------------------------------------
01452     ##
01453     #   @brief Gets force_display_sign_once field
01454     #   @return Item's force_display_sign_once field
01455     def get_force_display_sign_once(self):
01456         return self._force_display_sign_once
01457 
01458 
01459 
01460 
01461 
01462     force_display_sign_once = property(get_force_display_sign_once,
01463                               doc = "Item's force_display_sign_once field")
01464 
01465 
01466 
01467 
01468 
01469     # --------------------------------------------------------------------------
01470     ##
01471     #   @brief Sets a True|False value to the "force_display_sign_once" field
01472     def set_force_display_sign_once(self, arg):
01473         if arg in [True, False]:
01474             self._force_display_sign_once = arg
01475         else:
01476             raise error.WrongArgument(str(arg), "True|False")
01477 
01478 
01479 
01480 
01481 
01482     # --------------------------------------------------------------------------
01483     ##
01484     #   @brief Creates a string of the given object in the given ML
01485     #   @param options Any options
01486     #   @return The formated string
01487     def into_str(self, **options):
01488         global expression_begins
01489         # Displaying the + sign depends on the expression_begins flag of the
01490         # machine :
01491         #  - either it's True : + won't be displayed
01492         #  - or it's False : + will be displayed
01493         # "Normal" state of this flag is False.
01494         # It is set to True outside of this section of into_str, every time
01495         # it's necessary (for example, as soon as a bracket has been
01496         # displayed the next object should be displayed like at the beginning
01497         # of an expression).
01498         # Everytime an SquareRoot is written,
01499         # expression_begins is set to False again
01500 
01501         if 'force_expression_begins' in options \
01502            and options['force_expression_begins'] == True:
01503         #___
01504             expression_begins = options['force_expression_begins']
01505             options['force_expression_begins'] = False
01506 
01507         resulting_string = ""
01508 
01509         sign = ''
01510 
01511         if not expression_begins                                              \
01512            or ('force_display_sign' in options and self.is_numeric()) \
01513            or self.force_display_sign_once:
01514         #___
01515             if self.sign == '+' or self.is_null():
01516                 sign = MARKUP['plus']
01517             else:
01518                 sign = MARKUP['minus']
01519             if self.force_display_sign_once:
01520                 self.set_force_display_sign_once(False)
01521         else:
01522             if self.sign == '-' and not self.is_null():
01523                 sign = MARKUP['minus']
01524 
01525 
01526         resulting_string = sign + MARKUP['opening_sqrt']               \
01527                                 + self.radicand.into_str()           \
01528                                 + MARKUP['closing_sqrt']
01529 
01530         expression_begins = False
01531 
01532         return resulting_string
01533 
01534 
01535 
01536 
01537 
01538     # --------------------------------------------------------------------------
01539     ##
01540     #   @brief Returns None|an SquareRoot
01541     #   @todo Manage the case when the exponent is a calculable that should
01542     #   be calculated itself.
01543     #   @warning Relays an exception if the content is negative
01544     def calculate_next_step(self, **options):
01545         if not self.is_numeric():
01546             return SquareRoot((self.sign,
01547                                self.radicand.expand_and_reduce_next_step()
01548                              ))
01549 
01550         # First, let's handle the case when a Decimal Result is awaited
01551         # rather than a SquareRoot's simplification
01552         if 'decimal_result' in options \
01553            and self.radicand.calculate_next_step() is None:
01554         #___
01555             result = Item(Value(self.radicand.evaluate()).sqrt()\
01556                             .round(options['decimal_result'])
01557                             )
01558 
01559             result.set_sign(self.sign)
01560 
01561             return result
01562 
01563         # Now, decimal_resultat isn't awaited but the SquareRoot is one
01564         # of an integer perfect square
01565         # Case of "perfect decimal squares" (like 0.49) will be treated later.
01566         # For instance, 49×10^{-14} can be squarerooted...
01567         # the significant digits should be computed and checked if the
01568         # square root of them is right or must be rounded. Then check if the
01569         # matching exponent is even.
01570         # (this could be an improvement of the method is_a_perfect_square())
01571         elif self.radicand.calculate_next_step() is None \
01572             and Value(self.radicand.evaluate()).is_a_perfect_square():
01573         #___
01574             result = Item(Value(self.radicand.evaluate()).sqrt()
01575                          )
01576 
01577             result.set_sign(self.sign)
01578 
01579             return result
01580 
01581 
01582         # There should be the code to handle the steps of SquareRoots'
01583         # simplification, in an analog way as by Fractions.
01584         else:
01585             #print "here + " + self.dbg_str() + "\n"
01586             return None
01587 
01588 
01589 
01590 
01591 
01592     # --------------------------------------------------------------------------
01593     ##
01594     #   @brief Returns SquareRoot(self.object.expand_and_reduce_next_step())
01595     def expand_and_reduce_next_step(self, **options):
01596         if self.is_numeric():
01597             return self.calculate_next_step(**options)
01598         else:
01599             return SquareRoot((self.sign,
01600                                self.radicand.\
01601                                         expand_and_reduce_next_step(**options)
01602                               ))
01603 
01604 
01605 
01606 
01607 
01608     # --------------------------------------------------------------------------
01609     ##
01610     #   @brief Raw display of the SquareRoot (debugging method)
01611     #   @param options No option available so far
01612     #   @return A string containing "signSQR{{str(object)}}"
01613     def dbg_str(self, **options):
01614         return " " + self.sign \
01615                + "SQR{{ "   \
01616                + self.radicand.dbg_str()  \
01617                + " }} "
01618 
01619 
01620 
01621 
01622 
01623     # --------------------------------------------------------------------------
01624     ##
01625     #   @brief Compares two SquareRoots
01626     #   @return 0 (i.e. they're equal) if sign, value & exponent are equal
01627     #   @obsolete ?
01628     def __eq__(self, other_item):
01629         raise error.MethodShouldBeRedefined(self,
01630                                             '__eq__ in SquareRoot')
01631 
01632 
01633 
01634 
01635 
01636     # --------------------------------------------------------------------------
01637     ##
01638     #   @brief Returns the SquareRoot's length
01639     #   @return 1
01640     def __len__(self):
01641         return 1
01642 
01643 
01644 
01645 
01646     # --------------------------------------------------------------------------
01647     ##
01648     #   @brief Turns the SquareRoot into the fraction item itself over item 1
01649     def turn_into_fraction(self):
01650         return Fraction(('+', self, Item(1)))
01651 
01652 
01653 
01654 
01655 
01656 
01657     # --------------------------------------------------------------------------
01658     ##
01659     #   @brief True if the usual writing rules require a × between two factors
01660     #   @param objct The other one
01661     #   @param position The position (integer) of self in the Product
01662     #   @return True if the writing rules require × between self & obj
01663     def multiply_symbol_is_required(self, objct, position):
01664         raise error.MethodShouldBeRedefined(self,
01665                                             'multiply_symbol_is_required')
01666 
01667         # 1st CASE : Item × Item
01668 
01669         # 2d CASE : Item × Product
01670 
01671         # 3d CASE : Item × Sum
01672 
01673         # 4th CASE : Item × Quotient
01674 
01675 
01676 
01677 
01678 
01679     # --------------------------------------------------------------------------
01680     ##
01681     #   @brief True if the argument requires brackets in a product
01682     #   For instance, a Sum with several terms or a negative Item
01683     #   @param position The position of the object in the Product
01684     #   @return True if the object requires brackets in a Product
01685     def requires_brackets(self, position):
01686         # a SquareRoot at first position doesn't need brackets
01687         if position == 0:
01688             return False
01689         # if the SquareRoot isn't at first position,
01690         # then it depends on its sign
01691         elif self.sign == '+':
01692             if self.force_display_sign_once:
01693                 return True
01694             else:
01695                 return False
01696         elif self.sign == '-':
01697             return True
01698 
01699 
01700 
01701 
01702 
01703     # --------------------------------------------------------------------------
01704     ##
01705     #   @brief Always false for SquareRoots !
01706     def requires_inner_brackets(self):
01707        return False
01708 
01709 
01710 
01711 
01712 
01713     # --------------------------------------------------------------------------
01714     ##
01715     #   @brief Always False for a SquareRoot ?
01716     #   @param objct The object to search for
01717     #   @return False
01718     def contains_exactly(self, objct):
01719         return False
01720 
01721 
01722 
01723 
01724 
01725 
01726     # --------------------------------------------------------------------------
01727     ##
01728     #   @brief To check if this contains a rounded number...
01729     #   @return True or False depending on the Value inside
01730     def contains_a_rounded_number(self):
01731         return self.radicand.contains_a_rounded_number()
01732 
01733 
01734 
01735 
01736 
01737 
01738     # --------------------------------------------------------------------------
01739     ##
01740     #   @brief True if it's a numeric SquareRoot
01741     def is_numeric(self):
01742         return self.radicand.is_numeric()
01743 
01744 
01745 
01746 
01747 
01748     # --------------------------------------------------------------------------
01749     ##
01750     #   @brief True if it's a literal SquareRoot
01751     def is_literal(self):
01752         return self.radicand.is_literal()
01753 
01754 
01755 
01756 
01757 
01758     # --------------------------------------------------------------------------
01759     ##
01760     #   @brief True if it's the null SquareRoot
01761     def is_null(self):
01762         return self.radicand.is_null()
01763 
01764 
01765 
01766 
01767 
01768 
01769     # --------------------------------------------------------------------------
01770     ##
01771     #   @brief True if it's positive w/ radicand itself eq. to a single 1
01772     def is_displ_as_a_single_1(self):
01773         if self.sign == '+':
01774             return self.radicand.is_displ_as_a_single_1()
01775 
01776         return False
01777 
01778 
01779 
01780 
01781 
01782     # --------------------------------------------------------------------------
01783     ##
01784     #   @brief True if it's negative w/ radicand itself eq. to a single 1
01785     def is_displ_as_a_single_minus_1(self):
01786         if self.sign == '-':
01787             return self.radicand.is_displ_as_a_single_1()
01788 
01789         return False
01790 
01791 
01792 
01793 
01794 
01795     # --------------------------------------------------------------------------
01796     ##
01797     #   @brief True if self.is_null()
01798     def is_displ_as_a_single_0(self):
01799         return self.radicand.is_null()
01800 
01801 
01802 
01803 
01804 
01805 
01806     # --------------------------------------------------------------------------
01807     ##
01808     #   @brief Should never be True (if it is, then self is not a SquareRoot...)
01809     def is_displ_as_a_single_numeric_Item(self):
01810         return False
01811 
01812 
01813 
01814 
01815 
01816     # --------------------------------------------------------------------------
01817     ##
01818     #   @brief True if the object can be displayed as a single int
01819     def is_displ_as_a_single_int(self):
01820         return False
01821 
01822 
01823 
01824 
01825     # --------------------------------------------------------------------------
01826     ##
01827     #   @brief True if the object can be considered as a neutral element
01828     def is_displ_as_a_single_neutral(self, elt):
01829         if elt == Item(0):
01830             return self.is_displ_as_a_single_0()
01831         elif elt == Item(1):
01832             return self.is_displ_as_a_single_1()
01833         else:
01834             print(elt.dbg_str())
01835             print(Item(0).dbg_str())
01836             raise error.UncompatibleType(elt, "neutral element for addition" \
01837                                               + " or multiplication, e.g." \
01838                                               + " Item(1) | Item(0).")
01839 
01840 
01841 
01842 
01843 
01844     # --------------------------------------------------------------------------
01845     ##
01846     #   @brief Depends on the radicand
01847     #   @return True/False
01848     def is_expandable(self):
01849         return self.radicand.is_expandable()
01850 
01851 
01852 
01853 
01854 
01855 # ------------------------------------------------------------------------------
01856 # --------------------------------------------------------------------------
01857 # ------------------------------------------------------------------------------
01858 ##
01859 # @class Operation
01860 # @brief Abstract mother class of Quotient and of CommutativeOperation
01861 class Operation(Exponented):
01862 
01863 
01864 
01865 
01866 
01867     # --------------------------------------------------------------------------
01868     ##
01869     #   @brief Constructor
01870     #   @warning Operation objects are not really usable
01871     #   @return An "instance" of Operation
01872     def __init__(self):
01873         # This is an "external" exponent (like ³ in (x + 5)³ or (3x²)³)
01874         Exponented.__init__(self)
01875 
01876         # The elements are terms for the Sum and factors for the Product,
01877         # the numerator and denominator for the Quotient
01878         self._element = list()
01879 
01880         # The neutral element for the Sum == Item(0)
01881         # The neutral element for the Product|Quotient == Item(1)
01882         self._neutral = None
01883 
01884         # The symbol for the Sum is '+' (should be taken from a markup list)
01885         # The symbol for the Product is '×' (idem)
01886         # The symbol for the Quotient is '÷' (idem) or a Fraction's bar
01887         self._symbol = None
01888 
01889 
01890 
01891 
01892 
01893 
01894     # --------------------------------------------------------------------------
01895     ##
01896     #   @brief Returns the list of elements
01897     def get_element(self):
01898         return self._element
01899 
01900 
01901 
01902 
01903 
01904 
01905     # --------------------------------------------------------------------------
01906     ##
01907     #   @brief Returns the list of elements to iter over
01908     def get_neutral(self):
01909         return self._neutral
01910 
01911 
01912 
01913 
01914 
01915 
01916     # --------------------------------------------------------------------------
01917     ##
01918     #   @brief Returns the symbol field of the Operation
01919     def get_symbol(self):
01920         return self._symbol
01921 
01922 
01923 
01924 
01925 
01926 
01927     # --------------------------------------------------------------------------
01928     ##
01929     #   @brief Returns the list of elements to iter over
01930     def get_iteration_list(self):
01931         return self.element  + [self.exponent]
01932 
01933 
01934 
01935 
01936 
01937     element = property(get_element,
01938                        doc = "element field of Operation")
01939 
01940     neutral = property(get_neutral,
01941                        doc = "neutral field of Operation")
01942 
01943     symbol = property(get_symbol,
01944                       doc = "symbol field of Operation")
01945 
01946 
01947 
01948 
01949 
01950     # --------------------------------------------------------------------------
01951     ##
01952     #   @brief
01953     #   @param n : number of the element to set
01954     #   @param arg : the object to put as n-th element
01955     def set_element(self, n, arg):
01956         if not isinstance(arg, Calculable):
01957             raise error.WrongArgument(str(type(arg)), "a Calculable")
01958 
01959         self._element[n] = arg.clone()
01960 
01961 
01962 
01963 
01964     # --------------------------------------------------------------------------
01965     ##
01966     #   @brief Resets the element field
01967     def set_symbol(self, arg):
01968         if not type(arg) == str:
01969             raise error.WrongArgument(str(type(arg)), 'a str')
01970 
01971         else:
01972             self._symbol = arg
01973 
01974 
01975 
01976 
01977 
01978     # --------------------------------------------------------------------------
01979     ##
01980     #   @brief Resets the element field
01981     def reset_element(self):
01982         self._element = []
01983 
01984 
01985 
01986 
01987 
01988 
01989     # --------------------------------------------------------------------------
01990     ##
01991     #   @brief Makes any Operation hashable (so, usable as dictionnary keys)
01992     #   @warning    Seems that subclasses that redefine __eq__() are forgetting
01993     #               __hash__()
01994     def __hash__(self):
01995         return hash(self.symbol + str(self.sign) \
01996                     + [elt.dbg_str() for elt in self.element].join()\
01997                     + self.exponent.dbg_str()
01998                    )
01999 
02000 
02001 
02002 
02003 
02004     # --------------------------------------------------------------------------
02005     ##
02006     #   @brief It is possible to index an CommutativeOperation
02007     def __getitem__(self, i):
02008         return self._element[i]
02009 
02010 
02011     def __setitem__(self, i, data):
02012         self._element[i] = data
02013 
02014 
02015 
02016 
02017 
02018     # --------------------------------------------------------------------------
02019     ##
02020     #   @brief Defines the performed CommutativeOperation
02021     def operator(self, arg1, arg2):
02022         raise error.MethodShouldBeRedefined(self, 'operator')
02023 
02024 
02025 
02026 
02027 
02028     # --------------------------------------------------------------------------
02029     ##
02030     #   @brief True if the Operation contains any Expandable
02031     #   @return True|False
02032     def is_expandable(self):
02033         for elt in self:
02034             if elt.is_expandable():
02035                 return True
02036 
02037         return False
02038 
02039 
02040 
02041 
02042 
02043     # --------------------------------------------------------------------------
02044     ##
02045     #   @brief True if the Operation contains only numeric elements
02046     def is_numeric(self):
02047         for elt in self:
02048             if not elt.is_numeric():
02049                 return False
02050 
02051         return True
02052 
02053 
02054 
02055 
02056 
02057     # --------------------------------------------------------------------------
02058     ##
02059     #   @brief True if the Operation contains only literal terms
02060     def is_literal(self):
02061         for elt in self:
02062             if not elt.is_literal():
02063                 return False
02064 
02065         return True
02066 
02067 
02068 
02069 
02070 # ------------------------------------------------------------------------------
02071 # --------------------------------------------------------------------------
02072 # ------------------------------------------------------------------------------
02073 ##
02074 # @class Quotient
02075 # @brief Sign, Exponented numerator, Exponented denominator, exponent
02076 class Quotient (Operation):
02077 
02078 
02079 
02080 
02081 
02082 
02083     # --------------------------------------------------------------------------
02084     ##
02085     #   @brief Constructor
02086     #   @warning Might raise an UncompatibleType exception.
02087     #   @param arg Quotient|(sign, num, deno [, exponent [, symbol]])
02088     #   If the argument isn't of the kinds listed above, an exception will be
02089     #   raised. num and deno are expected to be Exponented ; nevertheless if
02090     #   they are only Values they get turned into Items.
02091     #   @param options Can be use_divide_symbol
02092     #   @todo Maybe : (RANDOMLY, num_max, deno_max) as possible argument ?
02093     #   @return One instance of Quotient
02094     def __init__(self, arg, **options):
02095         Operation.__init__(self)
02096 
02097         # default initialization of other fields
02098         self._numerator = Value(1)
02099         self._denominator = Value(1)
02100         self._symbol = 'like_a_fraction'
02101 
02102         if 'use_divide_symbol' in options:
02103             self._symbol = 'use_divide_symbol'
02104 
02105         # 1st CASE : (sign, Exponented num, Exponented deno)
02106         if type(arg) == tuple and len(arg) >= 3 and  is_.a_sign(arg[0])       \
02107            and                                                                \
02108                (isinstance(arg[1], Calculable) or is_.a_number(arg[1]) or           \
02109                 is_.a_string(arg[1]))                                         \
02110            and                                                                \
02111                (isinstance(arg[2], Calculable) or is_.a_number(arg[2]) or           \
02112                 is_.a_string(arg[2])):
02113         #___
02114             self._sign = arg[0]
02115             if is_.a_number(arg[1]) or is_.a_string(arg[1]) \
02116                 or isinstance(arg[1], Value):
02117             #___
02118                 self._numerator = Item(arg[1])
02119             else:
02120                 self._numerator = arg[1].clone()
02121 
02122             if is_.a_number(arg[2]) or is_.a_string(arg[2]) \
02123                 or isinstance(arg[2], Value):
02124             #___
02125                 self._denominator = Item(arg[2])
02126             else:
02127                 self._denominator = arg[2].clone()
02128 
02129         # 2d CASE : imbricated in the 1st
02130             if len(arg) >= 4:
02131                 if is_.a_number(arg[3]):
02132                     self._exponent = Value(arg[3])
02133                 else:
02134                     self._exponent = arg[3].clone()
02135 
02136             if len(arg) >= 5:
02137                 self._symbol = arg[4]
02138 
02139         # 3d CASE : another Quotient to copy
02140         elif isinstance(arg, Quotient):
02141             self._exponent = arg.exponent.clone()
02142             self._numerator = arg.numerator.clone()
02143             self._denominator = arg.denominator.clone()
02144             self._sign = arg.sign
02145             self._symbol = arg.symbol
02146 
02147         # All other unforeseen cases : an exception is raised.
02148         else:
02149             raise error.UncompatibleType(arg,
02150                                      "(sign, numerator, denominator)|\
02151                                      (sign, numerator, denominator, exponent)")
02152 
02153 
02154 
02155 
02156 
02157     # --------------------------------------------------------------------------
02158     ##
02159     #   @brief Returns the sign of the object
02160     def get_sign(self):
02161         return self._sign
02162 
02163 
02164 
02165 
02166 
02167     # --------------------------------------------------------------------------
02168     ##
02169     #   @brief Returns the numerator of the object
02170     def get_numerator(self):
02171         return self._numerator
02172 
02173 
02174 
02175 
02176 
02177     # --------------------------------------------------------------------------
02178     ##
02179     #   @brief Returns the denominator of the object
02180     def get_denominator(self):
02181         return self._denominator
02182 
02183 
02184 
02185 
02186 
02187     # --------------------------------------------------------------------------
02188     ##
02189     #   @brief Returns the list of elements to iter over
02190     def get_iteration_list(self):
02191         return [self.numerator, self.denominator, self.exponent]
02192 
02193 
02194 
02195 
02196 
02197 
02198     # --------------------------------------------------------------------------
02199     ##
02200     #   @brief Gets the number of '-' signs of the Quotient
02201     #   @return The number of '-' signs of the Quotient
02202     def get_minus_signs_nb(self):
02203         answer = 0
02204         if self.sign == '-':
02205             answer += 1
02206 
02207         return answer + self.numerator.get_minus_signs_nb()                   \
02208                       + self.denominator.get_minus_signs_nb()
02209 
02210 
02211 
02212 
02213     numerator = property(get_numerator,
02214                          doc = "numerator field of Quotient")
02215 
02216     denominator = property(get_denominator,
02217                            doc = "denominator field of Quotient")
02218 
02219 
02220 
02221     # --------------------------------------------------------------------------
02222     ##
02223     #   @brief Sets the numerator of the object
02224     def set_numerator(self, arg):
02225         if not isinstance(arg, Exponented):
02226             raise error.WrongArgument(str(type(arg)), "an Exponented")
02227 
02228         else:
02229             self._numerator = arg.clone()
02230 
02231 
02232 
02233 
02234 
02235     # --------------------------------------------------------------------------
02236     ##
02237     #   @brief Sets the denominator of the object
02238     def set_denominator(self, arg):
02239         if not isinstance(arg, Exponented):
02240             raise error.WrongArgument(str(type(arg)), "an Exponented")
02241 
02242         else:
02243             self._denominator = arg.clone()
02244 
02245 
02246 
02247 
02248 
02249     # --------------------------------------------------------------------------
02250     ##
02251     #   @brief Creates a string of the given object in the given ML
02252     #   @param options Any options
02253     #   @return The formated string
02254     def into_str(self, **options):
02255         global expression_begins
02256 
02257         # DEBUG
02258         debug.write("In into_str of Quotient\nDetails :\n" \
02259                                + self.dbg_str() \
02260                                + "\n",
02261                                case=debug.into_str_in_quotient)
02262         if 'force_expression_begins' in options \
02263            and options['force_expression_begins'] == True:
02264         #___
02265             expression_begins = True
02266             options['force_expression_begins'] = False
02267 
02268         if 'force_position' in options \
02269            and is_.an_integer(options['force_position']):
02270         #___
02271             temp_options = dict()
02272             for key in options:
02273                 if key != 'force_position':
02274                     temp_options[key] = options[key]
02275             options = temp_options
02276 
02277         # Quotient objects displaying
02278         sign = ''
02279         nume = ''
02280         deno = ''
02281 
02282         if self.sign == '+' and not expression_begins:
02283             sign = MARKUP['plus']
02284         elif self.sign == '-':
02285             sign = MARKUP['minus']
02286 
02287         expression_begins = True
02288         nume = self.numerator.into_str(force_position=0,
02289                                        **options)
02290         expression_begins = True
02291         deno = self.denominator.into_str(force_position=0,
02292                                          **options)
02293 
02294         if self.symbol == 'use_divide_symbol':
02295             if isinstance(self.numerator, Sum) \
02296                and len(self.numerator.throw_away_the_neutrals()) >= 2 \
02297                and not self.numerator.requires_inner_brackets():
02298             #___
02299                 nume = MARKUP['opening_bracket'] \
02300                        + nume \
02301                        + MARKUP['closing_bracket']
02302 
02303             if isinstance(self.denominator, Sum) \
02304                and len(self.denominator.throw_away_the_neutrals()) >= 2 \
02305                and not self.denominator.requires_inner_brackets():
02306             #___
02307                 deno = MARKUP['opening_bracket'] \
02308                        + deno \
02309                        + MARKUP['closing_bracket']
02310 
02311             # This is made to avoid having 4 ÷ -5
02312             # but to get 4 ÷ (-5) instead
02313             elif self.denominator.get_sign() == '-':
02314             #___
02315                 deno = MARKUP['opening_bracket'] \
02316                        + deno \
02317                        + MARKUP['closing_bracket']
02318 
02319 
02320 
02321         if self.exponent_must_be_displayed():
02322             expression_begins = True
02323             exponent_string = self.exponent.into_str(**options)
02324 
02325             if self.symbol == 'like_a_fraction':
02326                 return sign + MARKUP['opening_bracket']                       \
02327                             + MARKUP['opening_fraction']                      \
02328                             + nume                                            \
02329                             + MARKUP['fraction_vinculum']                     \
02330                             + deno                                            \
02331                             + MARKUP['closing_fraction']                      \
02332                             + MARKUP['closing_bracket']                       \
02333                             + MARKUP['opening_exponent']                      \
02334                             + exponent_string                                 \
02335                             + MARKUP['closing_exponent']
02336 
02337             else:
02338                 return sign + MARKUP['opening_bracket']                       \
02339                             + nume                                            \
02340                             + MARKUP['divide']                                \
02341                             + deno                                            \
02342                             + MARKUP['closing_bracket']                       \
02343                             + MARKUP['opening_exponent']                      \
02344                             + exponent_string                                 \
02345                             + MARKUP['closing_exponent']
02346 
02347         else:
02348             if self.symbol == 'like_a_fraction':
02349                 return sign + MARKUP['opening_fraction']                      \
02350                             + nume                                            \
02351                             + MARKUP['fraction_vinculum']                     \
02352                             + deno                                            \
02353                             + MARKUP['closing_fraction']
02354 
02355             else:
02356                 return sign + nume                                            \
02357                             + MARKUP['divide']                                \
02358                             + deno
02359 
02360 
02361 
02362 
02363 
02364     # --------------------------------------------------------------------------
02365     ##
02366     #   @brief Returns the value of a numerically evaluable object
02367     def evaluate(self, **options):
02368         if not('stop_recursion' in options and options['stop_recursion'] in YES):
02369             next_step = self.calculate_next_step()
02370 
02371             if next_step != None:
02372                 return next_step.evaluate()
02373 
02374 
02375         if self.sign == '+':
02376             sign = 1
02377         else:
02378             sign = -1
02379 
02380         num = self.numerator.evaluate()
02381         deno = self.denominator.evaluate()
02382 
02383         return sign * (num / deno) ** self.exponent.evaluate()
02384 
02385 
02386 
02387 
02388 
02389     # --------------------------------------------------------------------------
02390     ##
02391     #   @brief Returns the Quotient in the next step of simplification
02392     #   @todo The case where exponent has to be calculated as well.
02393     def calculate_next_step(self, **options):
02394         # First, check if any of numerator | denominator needs to be
02395         # calculated. If so, return the Quotient one step further.
02396         if isinstance(self.denominator, Fraction):
02397             if self.sign == '+':
02398                 sign_item = Item(1)
02399             else:
02400                 sign_item = Item(-1)
02401 
02402             next_step = Product([sign_item,
02403                                  self.numerator,
02404                                  self.denominator.invert()])
02405             next_step.set_exponent(self.exponent)
02406             return next_step.throw_away_the_neutrals()
02407 
02408         if 'decimal_result' in options:
02409             return Item(self.evaluate(**options))
02410 
02411         if self.numerator.calculate_next_step(**options) != None:
02412             if self.denominator.calculate_next_step(**options) != None:
02413                 return Quotient((self.sign,
02414                              self.numerator.calculate_next_step(**options),
02415                              self.denominator.calculate_next_step(**options),
02416                              self.exponent,
02417                              self.symbol))
02418             else:
02419                 return Quotient((self.sign,
02420                                  self.numerator.calculate_next_step(**options),
02421                                  self.denominator,
02422                                  self.exponent,
02423                                  self.symbol))
02424 
02425         elif self.denominator.calculate_next_step(**options) != None:
02426             return Quotient((self.sign,
02427                              self.numerator,
02428                              self.denominator.calculate_next_step(**options),
02429                              self.exponent,
02430                              self.symbol))
02431 
02432         # Now, neither the numerator nor the denominator can be calculated
02433         else:
02434             if isinstance(self.denominator, Quotient):
02435                 if self.sign == '+':
02436                     sign_item = Item(1)
02437                 else:
02438                     sign_item = Item(-1)
02439 
02440                 next_step = Product([sign_item,
02441                                      self.numerator,
02442                                      self.denominator.invert()])
02443                 next_step.set_exponent(self.exponent)
02444                 return next_step
02445 
02446             else:
02447                 # We could evaluate it ? It isn't useful so far, but
02448                 # maybe it could become useful later... ?
02449                 return None
02450 
02451 
02452 
02453 
02454 
02455 
02456     # --------------------------------------------------------------------------
02457     ##
02458     #   @brief Raw display of the Quotient (debugging method)
02459     #   @param options No option available so far
02460     #   @return A string : "Q# sign ( numerator / denominator )^{ exponent }#Q"
02461     def dbg_str(self, **options):
02462         return "Q# " +                                       \
02463                str(self.sign) +                                               \
02464                " ( " +                                                        \
02465                self.numerator.dbg_str() +                                     \
02466                " / " +                                                        \
02467                self.denominator.dbg_str() +                                   \
02468                " ) ^ { " +                                                    \
02469                self.exponent.dbg_str() +                                      \
02470                " } #Q"
02471 
02472 
02473 
02474 
02475 
02476     # --------------------------------------------------------------------------
02477     ##
02478     #   @brief Returns the Quotient's length
02479     #   It is used in Product.into_str(), changing it will have consequences
02480     #   on sheets like Fractions Products & Quotients...
02481     #   @return 1
02482     def __len__(self):
02483         return 1
02484 
02485 
02486 
02487 
02488     # --------------------------------------------------------------------------
02489     ##
02490     #   @brief Defines the performed Operation as a Quotient
02491     def operator(self, arg1, arg2):
02492         return arg1 / arg2
02493 
02494 
02495 
02496 
02497 
02498     # --------------------------------------------------------------------------
02499     ##
02500     #   @brief True if the usual writing rules require a × between two factors
02501     #   @param objct The other one
02502     #   @param position The position (integer) of self in the Product
02503     #   @return True if the writing rules require × between self & obj
02504     def multiply_symbol_is_required(self, objct, position):
02505         # 1st CASE : Quotient × Quotient
02506         if isinstance(objct, Quotient):
02507             return True
02508 
02509         # 2d CASE : Quotient × <anything but a Quotient>
02510         if objct.is_literal():
02511             return False
02512         else:
02513             return True
02514 
02515 
02516 
02517 
02518 
02519     # --------------------------------------------------------------------------
02520     ##
02521     #   @brief True if the argument requires brackets in a product
02522     #   For instance, a Sum with several terms or a negative Item
02523     #   @param position The position of the object in the Product
02524     #   @return True if the object requires brackets in a Product
02525     def requires_brackets(self, position):
02526         if self.sign == '-' and position >= 1:
02527             return True
02528         else:
02529             return False
02530 
02531 
02532 
02533 
02534 
02535     # --------------------------------------------------------------------------
02536     ##
02537     #   @brief True if the argument requires inner brackets
02538     #   The reason for requiring them is having an exponent different from 1
02539     #   @return True if the object requires inner brackets
02540     def requires_inner_brackets(self):
02541         return self.exponent_must_be_displayed()
02542 
02543 
02544 
02545 
02546 
02547     # --------------------------------------------------------------------------
02548     ##
02549     #   @brief True if the Quotient contains exactly the given objct
02550     #   It can be used to detect objects embedded in this Quotient (with
02551     #   a denominator equal to 1)
02552     #   @param objct The object to search for
02553     #   @return True if the Quotient contains exactly the given objct
02554     def contains_exactly(self, objct):
02555         if self.denominator.is_displ_as_a_single_1() \
02556            and self.sign == '+':
02557         #___
02558             if self.numerator == objct:
02559                 return True
02560             else:
02561                 return self.numerator.contains_exactly(objct)
02562 
02563         else:
02564             return False
02565 
02566 
02567 
02568 
02569 
02570 
02571 
02572     # --------------------------------------------------------------------------
02573     ##
02574     #   @brief To check if this contains a rounded number...
02575     #   @return True or False
02576     def contains_a_rounded_number(self):
02577         if self.numerator.contains_a_rounded_number() \
02578            or self.denominator.contains_a_rounded_number():
02579         #___
02580             return True
02581 
02582         return False
02583 
02584 
02585 
02586 
02587 
02588     # --------------------------------------------------------------------------
02589     ##
02590     #   @brief Returns the inverted Quotient
02591     def invert(self):
02592         new_quotient = Quotient(self)
02593         if isinstance(self, Fraction):
02594             new_quotient = Fraction(self)
02595         new_quotient.set_numerator(self.denominator)
02596         new_quotient.set_denominator(self.numerator)
02597 
02598         return new_quotient
02599 
02600 
02601 
02602 
02603 
02604     # --------------------------------------------------------------------------
02605     ##
02606     #   @brief True if the numerator is null
02607     def is_null(self):
02608         return self.numerator.is_null()
02609 
02610 
02611 
02612 
02613 
02614     # --------------------------------------------------------------------------
02615     ##
02616     #   @brief True if the Quotient contains only single 1-equivalent Calcs.
02617     # So, if the Quotient has a positive sign and if its numerator and
02618     # both are equivalent to single 1.
02619     def is_displ_as_a_single_1(self):
02620         if self.sign == '+' and                                               \
02621            self.numerator.is_displ_as_a_single_1() and                   \
02622            self.denominator.is_displ_as_a_single_1():
02623             return True
02624 
02625         else:
02626             return False
02627 
02628 
02629 
02630 
02631 
02632     # --------------------------------------------------------------------------
02633     ##
02634     #   @brief True if the Quotient can be displayed as a single -1
02635     # If the Quotient is negative and its numerator and
02636     # both are equivalent to single 1.
02637     #   @todo Other cases should return True as well (use the sign_of_product())
02638     def is_displ_as_a_single_minus_1(self):
02639         if self.sign == '-' and                                               \
02640            self.numerator.is_displ_as_a_single_1() and                   \
02641            self.denominator.is_displ_as_a_single_1():
02642         #___
02643             return True
02644 
02645         else:
02646             return False
02647 
02648 
02649 
02650 
02651 
02652     # --------------------------------------------------------------------------
02653     ##
02654     #   @brief True if the Quotient can be displayed as a single 0
02655     # If the numerator is equivalent to a single 0
02656     def is_displ_as_a_single_0(self):
02657         return self.numerator.is_displ_as_a_single_0()
02658 
02659 
02660 
02661 
02662 
02663     # --------------------------------------------------------------------------
02664     ##
02665     #   @brief True if the object is or only contains one numeric Item
02666     def is_displ_as_a_single_numeric_Item(self):
02667         if not self.is_numeric():
02668             return False
02669         else:
02670             return self.denominator.is_displ_as_a_single_1() \
02671                    and self.numerator.is_displ_as_a_single_numeric_Item()
02672 
02673 
02674 
02675 
02676 
02677     # --------------------------------------------------------------------------
02678     ##
02679     #   @brief True if the object can be displayed as a single int
02680     def is_displ_as_a_single_int(self):
02681         return False
02682 
02683 
02684 
02685 
02686 
02687     # --------------------------------------------------------------------------
02688     ##
02689     #   @brief True if the object can be considered as a neutral element
02690     def is_displ_as_a_single_neutral(self, elt):
02691         if elt == Item(0):
02692             return self.is_displ_as_a_single_0()
02693         elif elt == Item(1):
02694             return self.is_displ_as_a_single_1()
02695         else:
02696             print(elt.dbg_str())
02697             print(Item(0).dbg_str())
02698             raise error.UncompatibleType(elt, "neutral element for addition" \
02699                                               + " or multiplication, e.g." \
02700                                               + " Item(1) | Item(0).")
02701 
02702 
02703 
02704 
02705 
02706 # ------------------------------------------------------------------------------
02707 # --------------------------------------------------------------------------
02708 # ------------------------------------------------------------------------------
02709 ##
02710 # @class Fraction
02711 # @brief Quotient of two numeric Sums and/or Products
02712 class Fraction(Quotient):
02713 
02714 
02715 
02716 
02717 
02718 
02719     # --------------------------------------------------------------------------
02720     ##
02721     #   @brief Constructor
02722     #   @warning Can raise UncompatibleType.
02723     #   @param arg Fraction|(num,den)|(sign,num,den)|(sign,num,den,exponent)|
02724     #              (RANDOMLY, sign, num_sign, num_max, deno_sign, deno_max)|
02725     #              zero-degree-Monomial having a Fraction as coefficient
02726     #   @param **options copy_other_fields_from=<Fraction>
02727     #                    -> can be used with (num, den) to get all the
02728     #                       other fields from the given Fraction (including
02729     #                       sign)
02730     #   @todo ? raise a division by zero error !
02731     #   @return One instance of Fraction
02732     def __init__(self, arg, **options):
02733         Exponented.__init__(self)
02734 
02735         # default initialization of the other fields
02736         self._numerator = Product([Item(1)])
02737         self._denominator = Product([Item(2)])
02738         self._status = "nothing"
02739         self._symbol = 'like_a_fraction'
02740         self._same_deno_reduction_in_progress = False
02741 
02742         arg_sign = 'default'
02743         arg_nume = 'default'
02744         arg_deno = 'default'
02745 
02746         if type(arg) == tuple:
02747             if len(arg) >= 3 and arg[0] != RANDOMLY:
02748                 arg_sign = arg[0]
02749                 arg_nume = arg[1]
02750                 arg_deno = arg[2]
02751             elif len(arg) == 6 and arg[0] == RANDOMLY:
02752                 arg_sign = arg[1]
02753                 arg_nume = arg[3]
02754                 arg_deno = arg[5]
02755             elif len(arg) == 2:
02756                 arg_nume = arg[0]
02757                 arg_deno = arg[1]
02758 
02759         # 1st CASE :
02760         # The argument's a tuple containing exactly one sign and 2 Exponenteds
02761         # OR (num,den)[, copy_other_fields_from=<Fraction>]
02762         if type(arg) == tuple                                              \
02763            and len(arg) >= 2                                               \
02764            and arg[0] != RANDOMLY                                       \
02765            and                                                             \
02766            ((isinstance(arg_nume, Exponented) and arg_nume.is_numeric())   \
02767                                       or                                   \
02768                           is_.a_number(arg_nume))                          \
02769            and                                                             \
02770            ((isinstance(arg_deno, Exponented) and arg_deno.is_numeric())   \
02771                                       or                                   \
02772                           is_.a_number(arg_deno)):
02773         #___
02774 
02775             if is_.a_number(arg_nume):
02776                 self._numerator = Product([Item(arg_nume)])
02777             elif not isinstance(arg_nume, Product):
02778                 self._numerator = Product(arg_nume.clone())
02779             else:
02780                 self._numerator = arg_nume.clone()
02781 
02782             if is_.a_number(arg_deno):
02783                 self._denominator = Product([Item(arg_deno)])
02784             elif not isinstance(arg_deno, Product):
02785                 self._denominator = Product(arg_deno.clone())
02786             else:
02787                 self._denominator = arg_deno.clone()
02788 
02789             if len(arg) == 2 \
02790                and 'copy_other_fields_from' in options \
02791                and isinstance(options['copy_other_fields_from'], Fraction):
02792             #___
02793                 self._exponent = options['copy_other_fields_from'].exponent.\
02794                                                                     clone()
02795                 self._sign = options['copy_other_fields_from'].sign
02796                 self._status = options['copy_other_fields_from'].status
02797                 self._symbol = options['copy_other_fields_from'].symbol
02798                 self._same_deno_reduction_in_progress = \
02799               options['copy_other_fields_from'].same_deno_reduction_in_progress
02800 
02801             if len(arg) >= 3 and is_.a_sign(arg_sign):
02802                 self._sign = arg[0]
02803 
02804             if len(arg) >= 4:
02805                 self._exponent = arg[3].clone()
02806 
02807         # 2d CASE :
02808         # (RANDOMLY, sign, num_sign, num_max, deno_sign, deno_max)
02809         elif type(arg) == tuple                                              \
02810              and len(arg) == 6 \
02811              and arg[0] == RANDOMLY \
02812              and (is_.a_sign(arg_sign) or arg_sign == RANDOMLY) \
02813              and is_.an_integer(arg_deno) and arg_deno >= 2 \
02814              and is_.an_integer(arg_nume) and arg_nume >= 1 \
02815              and (is_.a_sign(arg[2]) or arg[2] == RANDOMLY) \
02816              and (is_.a_sign(arg[4]) or arg[4] == RANDOMLY):
02817         #___
02818             numbers_box_nume = [j+1 for j in range(arg_nume)]
02819             numbers_box_deno = [j+1 for j in range(arg_deno)]
02820 
02821             nume = randomly.pop(numbers_box_nume)
02822             nume_sign = '+'
02823             if arg[2] == RANDOMLY:
02824                 nume_sign = randomly.sign(plus_signs_ratio=0.75)
02825                 if nume_sign == '-':
02826                     nume_sign = -1
02827                 else:
02828                     nume_sign = 1
02829             else:
02830                 nume_sign = arg[2]
02831 
02832             if numbers_box_deno[0] == 1:
02833                 numbers_box_deno.pop(0)
02834             deno = randomly.pop(numbers_box_deno)
02835             deno_sign = '+'
02836             if arg[4] == RANDOMLY:
02837                 deno_sign = randomly.sign(plus_signs_ratio=0.75)
02838                 if deno_sign == '-':
02839                     deno_sign = -1
02840                 else:
02841                     deno_sign = 1
02842             else:
02843                 deno_sign = arg[4]
02844 
02845             self._numerator = Product([nume_sign, nume]).reduce_()
02846             self._denominator = Product([deno_sign, deno]).reduce_()
02847 
02848             if arg_sign == RANDOMLY:
02849                 self._sign = randomly.sign(plus_signs_ratio=0.75)
02850             else:
02851                 self._sign = arg_sign
02852 
02853 
02854 
02855         # 3d CASE :
02856         # The argument's a Fraction to copy
02857         elif isinstance(arg, Fraction):
02858             self._exponent = arg.exponent.clone()
02859             self._numerator = arg.numerator.clone()
02860             self._denominator = arg.denominator.clone()
02861             self._sign = arg.sign
02862             self._status = arg.status
02863             self._same_deno_reduction_in_progress = \
02864                                             arg.same_deno_reduction_in_progress
02865 
02866         # 4th CASE :
02867         elif arg == "default":
02868             # Just keep the default values (see begining of this method)
02869             pass
02870 
02871         # 5th CASE : A zero-degree Monomial having a Fraction as coefficient
02872         elif isinstance(arg, Monomial) and arg.is_numeric()\
02873              and isinstance(arg.factor[0], Fraction):
02874         #___
02875             self._exponent = Value(1)
02876             self._numerator = arg.factor[0].numerator.clone()
02877             self._denominator = arg.factor[0].denominator.clone()
02878             self._sign = arg.factor[0].sign
02879             self._status = arg.factor[0].status
02880             self._same_deno_reduction_in_progress = \
02881                                 arg.factor[0].same_deno_reduction_in_progress
02882 
02883 
02884         # All unforeseen cases : an exception is raised
02885         else:
02886             raise error.UncompatibleType(arg, "(sign, numerator, denominator)")
02887 
02888         # Now it may be useful to de-embbed some Products or Sums...
02889         temp_objects = [self.numerator, self.denominator]
02890 
02891         for i in range(len(temp_objects)):
02892             if len(temp_objects[i]) == 1:
02893                 if isinstance(temp_objects[i].factor[0], Sum) \
02894                    and len(temp_objects[i].factor[0]) == 1:
02895                 #___
02896                     temp_objects[i] = temp_objects[i].factor[0].term[0]
02897 
02898                 elif isinstance(temp_objects[i].factor[0], Product) \
02899                      and len(temp_objects[i].factor[0]) == 1:
02900                 #___
02901                     temp_objects[i] = temp_objects[i].factor[0].factor[0]
02902 
02903                 if not isinstance(temp_objects[i], Product):
02904                     temp_objects[i] = Product([temp_objects[i]])
02905 
02906         self._numerator = temp_objects[0]
02907         self._denominator = temp_objects[1]
02908 
02909 
02910 
02911 
02912     # --------------------------------------------------------------------------
02913     ##
02914     #   @brief Returns the Fraction's status
02915     #   @return the Fraction's status
02916     def get_status(self):
02917         return self._status
02918 
02919 
02920 
02921 
02922 
02923     # --------------------------------------------------------------------------
02924     ##
02925     #   @brief Returns the Fraction's status
02926     #   @return the Fraction's status
02927     def get_same_deno_reduction_in_progress(self):
02928         return self._same_deno_reduction_in_progress
02929 
02930 
02931 
02932 
02933 
02934     # --------------------------------------------------------------------------
02935     ##
02936     #   @brief Returns True if Fraction's status is simplification_in_progress
02937     #   @return True if Fraction's status is simplification_in_progress
02938     def get_simplification_in_progress(self):
02939         for i in range(len(self.numerator)):
02940             if isinstance(self.numerator.factor[i], Item):
02941                 if self.numerator.factor[i].is_out_striked:
02942                     return True
02943 
02944         for i in range(len(self.denominator)):
02945             if isinstance(self.denominator.factor[i], Item):
02946                 if self.denominator.factor[i].is_out_striked:
02947                     return True
02948 
02949         return False
02950 
02951 
02952 
02953 
02954 
02955     status = property(get_status,
02956                       doc = "Fraction's status")
02957 
02958     simplification_in_progress = property(get_simplification_in_progress,
02959                                           doc = "Fraction's simplification_" \
02960                                           + "in_progress status")
02961 
02962     same_deno_reduction_in_progress = property(
02963                       get_same_deno_reduction_in_progress,
02964                       doc = "Fraction's same_deno_reduction_in_progress field")
02965 
02966 
02967 
02968 
02969 
02970     # --------------------------------------------------------------------------
02971     ##
02972     #   @brief Sets the numerator of the object
02973 #    def set_numerator(self, arg):
02974 #        if not isinstance(arg, Exponented):
02975 #            raise error.WrongArgument(str(type(arg)), "an Exponented")
02976 #
02977 #        if isinstance(arg, Product):
02978 #            self._numerator = arg.clone()
02979 
02980 #        else:
02981 #            self._numerator = Product(arg)
02982 
02983 
02984 
02985 
02986 
02987     # --------------------------------------------------------------------------
02988     ##
02989     #   @brief Sets the denominator of the object
02990 #    def set_denominator(self, arg):
02991 #        if not isinstance(arg, Exponented):
02992 #            raise error.WrongArgument(str(type(arg)), "an Exponented")
02993 
02994  #       if isinstance(arg, Product):
02995  #           self._denominator = arg.clone()
02996 
02997  #       else:
02998   #          self._denominator = Product(arg)
02999 
03000 
03001 
03002 
03003 
03004     # --------------------------------------------------------------------------
03005     ##
03006     #   @brief Sets the Fraction's status
03007     def set_status(self, arg):
03008         if not type(arg) == str:
03009             raise error.WrongArgument(str(type(arg)), "a str")
03010 
03011         else:
03012             self._status = arg
03013 
03014 
03015 
03016 
03017 
03018     # --------------------------------------------------------------------------
03019     ##
03020     #   @brief Sets the Fraction's status
03021     def set_same_deno_reduction_in_progress(self, arg):
03022         if not arg in [True, False]:
03023             raise error.WrongArgument(str(type(arg)), "True|False")
03024 
03025         else:
03026             self._same_deno_reduction_in_progress = arg
03027 
03028 
03029 
03030 
03031 
03032     # --------------------------------------------------------------------------
03033     ##
03034     #   @brief Sets the sign of the fraction and of numerator in the case
03035     #   @brief of this example : +{-2}/{5} (nothing to compute just the minus
03036     #   @brief sign to put "down"
03037     #   @return True if Fraction's status is simplification_in_progress
03038     def set_down_numerator_s_minus_sign(self):
03039         if len(self.numerator) == 1 \
03040            and self.numerator.calculate_next_step() is None \
03041            and len(self.denominator) == 1 \
03042            and self.denominator.calculate_next_step() is None \
03043            and self.denominator.get_sign() == '+' \
03044            and self.numerator.get_sign() == '-' \
03045            and self.get_sign() == '+':
03046         #___
03047             self.set_sign(sign_of_product([\
03048                                     self.get_sign(),
03049                                     self.numerator.get_sign()
03050                                     ]))
03051             self.numerator.set_sign('+')
03052 
03053 
03054 
03055 
03056     # --------------------------------------------------------------------------
03057     ##
03058     #   @brief Returns the value of a numerically evaluable object
03059     def evaluate(self, **options):
03060         if 'keep_not_decimal_nb_as_fractions' in options \
03061             and options['keep_not_decimal_nb_as_fractions'] in YES \
03062             and not self.is_a_decimal_number():
03063         #___
03064             return self.completely_reduced()
03065         else:
03066             return Quotient.evaluate(self, **options)
03067 
03068 
03069 
03070     # --------------------------------------------------------------------------
03071     ##
03072     #   @brief Returns None|The Fraction in the next step of simplification
03073     #   @todo Fix the 4th case. Should be less cases... check source
03074     #   @todo Fix the /!\ or check if the 3d CASE is not obsolete (duplicated
03075     #   in the simplified method)
03076     def calculate_next_step(self, **options):
03077         #DEBUG
03078         debug.write("\nEntering calculate_next_step_fraction\n"\
03079                        + "with Fraction :\n" \
03080                        + self.dbg_str(),
03081                        case=debug.calculate_next_step_fraction)
03082 
03083         # First, let's handle the case when a Decimal Result is awaited
03084         # rather than a Fraction's simplification
03085         if 'decimal_result' in options \
03086            and self.numerator.calculate_next_step() is None \
03087            and self.denominator.calculate_next_step() is None:
03088         #___
03089             if self.sign == '+':
03090                 result_sign = 1
03091             else:
03092                 result_sign = -1
03093             result = Item(Value(result_sign \
03094                               * self.numerator.evaluate() \
03095                               / self.denominator.evaluate())\
03096                         .round(options['decimal_result'])
03097                             )
03098             #DEBUG
03099             debug.write("\n[calculate_next_step_fraction] " \
03100                            + "Decimal calculation has been done. Result : \n"\
03101                            + result.dbg_str() + " which _has_been_rounded : " \
03102                            + str(result.contains_a_rounded_number()) \
03103                            + "\n",
03104                            case=debug.calculate_next_step_fraction)
03105             return result
03106 
03107         temp_next_nume = self.numerator.calculate_next_step(**options)
03108         temp_next_deno = self.denominator.calculate_next_step(**options)
03109 
03110 
03111         # 1st CASE
03112         if self.simplification_in_progress:
03113             self_simplified = Fraction(self).simplified()
03114             if isinstance(self_simplified, Item) \
03115                or (isinstance(self_simplified, Fraction) \
03116                    and not self_simplified.is_reducible()):
03117             #___
03118                 #DEBUG
03119                 debug.write("\n[calculate_next_step_fraction] "\
03120                                        + "1st CASE-a\n",
03121                                      case=debug.calculate_next_step_fraction)
03122                 return self_simplified
03123             else:
03124                 #DEBUG
03125                 debug.write("\n[calculate_next_step_fraction] "\
03126                                        + "1st CASE-b\n",
03127                                      case=debug.calculate_next_step_fraction)
03128                 aux_fraction = Fraction(self)
03129                 aux_fraction = aux_fraction.replace_striked_out()
03130                 aux_fraction.set_numerator(
03131                                aux_fraction.numerator.throw_away_the_neutrals())
03132                 aux_fraction.set_denominator(
03133                              aux_fraction.denominator.throw_away_the_neutrals())
03134                 return aux_fraction.simplification_line()
03135 
03136         # 2d CASE
03137         elif self.same_deno_reduction_in_progress:
03138             #DEBUG
03139             debug.write("\n[calculate_next_step_fraction] "\
03140                                    + "2d CASE\n",
03141                                  case=debug.calculate_next_step_fraction)
03142             new_nume = self.numerator
03143             new_deno = self.denominator
03144 
03145             if temp_next_nume != None:
03146                 new_nume = temp_next_nume
03147             if temp_next_deno != None:
03148                 new_deno = temp_next_deno
03149 
03150             resulting_fraction = Fraction((new_nume, new_deno),
03151                                           copy_other_fields_from=self)
03152 
03153             if temp_next_deno != None or temp_next_nume != None:
03154                 return resulting_fraction
03155             else:
03156                 return None
03157 
03158         # 3d CASE
03159         elif self.is_reducible():
03160             #DEBUG
03161             debug.write("\n[calculate_next_step_fraction] "\
03162                                    + "3d CASE\n",
03163                                  case=debug.calculate_next_step_fraction)
03164             return self.simplification_line()
03165 
03166         # 4th CASE
03167         # don't forget to put the exponent of the fraction on the numerator !!
03168         # obsolete ?
03169         elif self.denominator.is_displ_as_a_single_1():
03170             #DEBUG
03171             debug.write("\n[calculate_next_step_fraction] "\
03172                                    + "4th CASE\n",
03173                                     case=debug.calculate_next_step_fraction)
03174             if self.sign == '-':
03175                 # /!\ this might lead to results like -(-3) instead of 3
03176                 return Item((Product([Item(-1), self.numerator])).evaluate())
03177             else:
03178                 return self.numerator
03179 
03180         # 5th CASE
03181         elif temp_next_nume != None or temp_next_deno != None:
03182             #DEBUG
03183             debug.write("\n[calculate_next_step_fraction] "\
03184                                    + "6th CASE\n",
03185                                  case=debug.calculate_next_step_fraction)
03186             new_nume = self.numerator
03187             new_deno = self.denominator
03188 
03189             if temp_next_nume != None:
03190                 new_nume = temp_next_nume
03191             if temp_next_deno != None:
03192                 new_deno = temp_next_deno
03193 
03194             resulting_fraction = Fraction((new_nume, new_deno),
03195                                           copy_other_fields_from=self)
03196 
03197             resulting_fraction.set_down_numerator_s_minus_sign()
03198 
03199             return resulting_fraction
03200 
03201         # 6th CASE
03202         elif (len(self.numerator) == 1 \
03203               and self.numerator.factor[0].get_sign() == '-') \
03204              or (len(self.denominator) == 1 \
03205               and self.denominator.factor[0].get_sign() == '-'):
03206         #___
03207             self.set_sign(sign_of_product([self.get_sign(),
03208                                                self.numerator.get_sign(),
03209                                                self.denominator.get_sign()]))
03210 
03211             self.numerator.factor[0].set_sign('+')
03212             self.denominator.factor[0].set_sign('+')
03213 
03214             return self
03215 
03216 
03217         # 7th CASE
03218         # don't forget to check the exponent of the fraction
03219         # before returning None... if it's not equivalent to a single 1, it
03220         # should be put on both numerator & denominator
03221         else:
03222             #DEBUG
03223             debug.write("\n[calculate_next_step_fraction] "\
03224                                    + "7th CASE\n",
03225                                  case=debug.calculate_next_step_fraction)
03226             return None
03227 
03228 
03229 
03230 
03231 
03232     # --------------------------------------------------------------------------
03233     ##
03234     #   @brief Same as calculate_next_step in the case of Fractions
03235     def expand_and_reduce_next_step(self, **options):
03236         return self.calculate_next_step(**options)
03237 
03238 
03239 
03240 
03241 
03242     # --------------------------------------------------------------------------
03243     ##
03244     #   @brief Raw display of the Fraction (debugging method)
03245     #   @param options No option available so far
03246     #   @return A string : "F# sign ( numerator / denominator )^{ exponent }#F"
03247     def dbg_str(self, **options):
03248         return "F# " +                                       \
03249                str(self.sign) +                                               \
03250                " ( " +                                                        \
03251                self.numerator.dbg_str() +                                     \
03252                " / " +                                                        \
03253                self.denominator.dbg_str() +                                   \
03254                " ) ^ { " +                                                    \
03255                self.exponent.dbg_str() +                                      \
03256                " } #F"
03257 
03258 
03259 
03260 
03261 
03262     # --------------------------------------------------------------------------
03263     ##
03264     #   @brief Compares two Fractions
03265     #   @return True if they're equal
03266     def __eq__(self, obj):
03267         if not isinstance(obj, Fraction):
03268             return False
03269 
03270         if self.sign == obj.sign                       \
03271            and self.numerator == obj.numerator            \
03272            and self.denominator == obj.denominator       \
03273            and self.exponent == obj.exponent:
03274         #___
03275             return True
03276         else:
03277             # it is difficult to tell whether a Fraction is greater or
03278             # lower than another... needs same denominator reduction etc.
03279             return False
03280 
03281 
03282 
03283 
03284 
03285 
03286     # --------------------------------------------------------------------------
03287     ##
03288     #   @brief Simplification line of a fraction
03289     # i.e. Factorization of numerator and denominator in the right smaller
03290     # numbers Products.
03291     #   @todo Add an option to __init__ to allow "inserting Products": see code
03292     #   @todo maybe the case of Item having a negative *value* has not been
03293     # managed. I mean, Items like ±(-2)
03294     #   @return One Fraction
03295     def simplification_line(self):
03296         if not self.is_reducible():
03297             return self
03298 
03299         elif self.numerator.evaluate() == 0:
03300             return Item(0)
03301 
03302         else:
03303             # the new numerator will be constructed this way :
03304             # first create a Product containing as many factors as the
03305             # original one but each factor replaced by a one ; same action
03306             # for numerator and denominator
03307             # these ones will be later replaced by the processed values (like
03308             # striked Items) or at the end by the original values for the
03309             # factors that haven't changed.
03310             new_numerator = Product([Item(1)                                  \
03311                                          for i in range(len(self.numerator))])
03312             new_denominator = Product([Item(1)                                \
03313                                        for i in range(len(self.denominator))])
03314 
03315             this_numerators_factor_has_been_processed = [False                \
03316                                           for i in range(len(self.numerator))]
03317             this_denominators_factor_has_been_processed = [False              \
03318                                         for i in range(len(self.denominator))]
03319 
03320             # If this method is called on a fraction which already contains
03321             # striked out Items, then they shouldn't be taken in account, so
03322             # let's consider them as already processed
03323             #for i in xrange(len(self.numerator)):
03324             #    if isinstance(self.numerator.factor[i], Item) and   \
03325             #       self.numerator.factor[i].is_out_striked:
03326             #    #___
03327             #        this_numerators_factor_has_been_processed[i] = True
03328 
03329             #for j in xrange(len(self.denominator)):
03330             #    if isinstance(self.denominator.factor[j], Item) and   \
03331             #       self.denominator.factor[j].is_out_striked:
03332             #    #___
03333             #        this_denominators_factor_has_been_processed[j] = True
03334 
03335 
03336             # Let's first check if there are any factors already "strikable"
03337             # like in <10×5>/<5×9>
03338             # If we don't do that first, the first 10 will turn into <5×2> and
03339             # its 5 will be simplified with the one of the denominator ;
03340             # which is not false but it would be more natural to simplify the
03341             # two 5 before decomposing any factor
03342             for i in range(len(self.numerator)):
03343                 for j in range(len(self.denominator)):
03344                     if self.numerator.factor[i].raw_value \
03345                        == self.denominator.factor[j].raw_value \
03346                        and not (this_numerators_factor_has_been_processed[i] \
03347                        or this_denominators_factor_has_been_processed[j]):
03348                     #___
03349                         new_numerator.set_element(i, self.numerator.factor[i].\
03350                                                                     clone())
03351                         new_numerator.element[i].set_is_out_striked(True)
03352                         debug.write("\n[0]Striked out : " \
03353                                + new_numerator.factor[i].dbg_str(),
03354                             case=debug.striking_out_in_simplification_line)
03355 
03356                         new_denominator.set_element(j,
03357                                                     self.denominator.factor[j]\
03358                                                                     .clone())
03359                         new_denominator.factor[j].set_is_out_striked(True)
03360                         debug.write("\n[1]Striked out : " \
03361                                + new_denominator.factor[j].dbg_str(),
03362                             case=debug.striking_out_in_simplification_line)
03363 
03364                         this_numerators_factor_has_been_processed[i] = True
03365                         this_denominators_factor_has_been_processed[j] = True
03366 
03367             # Now let's check if decomposing some factors could help simplify
03368             # the fraction
03369             for i in range(len(self.numerator)):
03370                 for j in range(len(self.denominator)):
03371                     if not this_denominators_factor_has_been_processed[j] \
03372                        and not this_numerators_factor_has_been_processed[i]:
03373                     #___
03374                         gcd = pupil_gcd(self.numerator.factor[i].raw_value,   \
03375                                             self.denominator.factor[j].raw_value)
03376                         if gcd != 1:
03377                             if gcd == self.numerator.factor[i].raw_value:
03378                                 new_numerator.set_element(i, \
03379                                                 self.numerator.factor[i].clone())
03380 
03381                                 new_numerator.factor[i].set_is_out_striked(   \
03382                                                                           True)
03383 
03384                                 debug.write("\n[2A]Striked out : " \
03385                                            + new_numerator.factor[i].dbg_str(),
03386                               case=debug.striking_out_in_simplification_line)
03387 
03388                                 this_numerators_factor_has_been_processed[i] =\
03389                                                                            True
03390 
03391                                 factor1 = gcd
03392                                 factor2 = self.denominator.factor[j].raw_value/gcd
03393 
03394                                 if self.denominator.factor[j].sign == '-':
03395                                     factor1 *= -1
03396 
03397                                 item1 = Item(factor1)
03398                                 item1.set_is_out_striked(True)
03399                                 debug.write("\n[2B]Striked out : " \
03400                                                         + item1.dbg_str(),
03401                               case=debug.striking_out_in_simplification_line)
03402 
03403 
03404                                 item2 = Item(factor2)
03405 
03406                                 new_denominator.factor[j] =                   \
03407                                                         Product([item1, item2])
03408 
03409                                 this_denominators_factor_has_been_processed[j]\
03410                                                                          = True
03411 
03412                             elif gcd == self.denominator.factor[j].raw_value:
03413                                 new_denominator.factor[j] =             \
03414                                                     self.denominator.factor[j]\
03415                                                                    .clone()
03416 
03417                                 new_denominator.factor[j].set_is_out_striked( \
03418                                                                           True)
03419 
03420                                 this_denominators_factor_has_been_processed[j]\
03421                                                                          = True
03422 
03423                                 factor1 = gcd
03424                                 factor2 = self.numerator.factor[i].raw_value / gcd
03425 
03426                                 if self.numerator.factor[i].sign == '-':
03427                                     factor1 *= -1
03428 
03429                                 item1 = Item(factor1)
03430                                 item1.set_is_out_striked(True)
03431                                 debug.write("\n[3]Striked out : " \
03432                                                         + item1.dbg_str(),
03433                               case=debug.striking_out_in_simplification_line)
03434 
03435                                 item2 = Item(factor2)
03436 
03437                                 new_numerator.factor[i] = Product([item1,
03438                                                                    item2])
03439 
03440                                 this_numerators_factor_has_been_processed[i] =\
03441                                                                            True
03442 
03443                             else:
03444                                 factor1 = gcd
03445                                 factor2 = self.numerator.factor[i].raw_value / gcd
03446 
03447                                 if self.numerator.factor[i].sign == '-':
03448                                     factor1 *= -1
03449 
03450                                 item1 = Item(factor1)
03451                                 item1.set_is_out_striked(True)
03452                                 debug.write("\n[4]Striked out : " \
03453                                                         + item1.dbg_str(),
03454                               case=debug.striking_out_in_simplification_line)
03455 
03456                                 item2 = Item(factor2)
03457 
03458                                 new_numerator.factor[i] = Product([item1,
03459                                                                   item2])
03460 
03461                                 this_numerators_factor_has_been_processed[i] =\
03462                                                                            True
03463 
03464                                 factor1 = gcd
03465                                 factor2 = self.denominator.factor[j].raw_value/gcd
03466                                 if self.denominator.factor[j].sign == '-':
03467                                     factor1 *= -1
03468 
03469                                 item1 = Item(factor1)
03470                                 item1.set_is_out_striked(True)
03471                                 debug.write("\n[5]Striked out : " \
03472                                                         + item1.dbg_str(),
03473                               case=debug.striking_out_in_simplification_line)
03474                                 item2 = Item(factor2)
03475 
03476                                 new_denominator.factor[j] = Product([item1,
03477                                                                      item2])
03478 
03479                                 this_denominators_factor_has_been_processed[j]\
03480                                                                          = True
03481 
03482             for i in range(len(new_numerator)):
03483                 if not this_numerators_factor_has_been_processed[i]:
03484                     new_numerator.factor[i] = self.numerator.factor[i]
03485 
03486             for j in range(len(new_denominator)):
03487                 if not this_denominators_factor_has_been_processed[j]:
03488                     new_denominator.factor[j] = self.denominator.factor[j]
03489 
03490             # Check if there are some unstriked factors left that could've
03491             # been striked : (it is the case when simplifying fractions like
03492             # <8×3>/<5×6> : at this point, the fraction is
03493             # <<2×4>×3>/<5×<2×3>>) with striked 2s but not striked 3s !
03494             # So, first, let's "dissolve" the inserted Products
03495             # (e.g. if numerator "<3×8>" has been transformed into "<3×<2×4>>"
03496             # let's rewrite it "<3×2×4>")
03497             # Products imbricated in Products imbricated in Products... are not
03498             # managed recursively by this function. If that should be useful,
03499             # an auxiliary function doing that could be implemented. (or
03500             # maybe an option in __init__ (which is recursive), which would
03501             # be much better)
03502             final_numerator = []
03503             final_denominator = []
03504             for i in range(len(new_numerator)):
03505                 if isinstance(new_numerator.factor[i], Item):
03506                     final_numerator.append(new_numerator.factor[i].clone())
03507                 elif isinstance(new_numerator.factor[i], Product):
03508                     for j in range(len(new_numerator.factor[i])):
03509                         final_numerator.append(new_numerator.factor[i]\
03510                                                             .factor[j]\
03511                                                             .clone())
03512 
03513             for i in range(len(new_denominator)):
03514                 if isinstance(new_denominator.factor[i], Item):
03515                     final_denominator.append(new_denominator.factor[i]\
03516                                                             .clone())
03517                 elif isinstance(new_denominator.factor[i], Product):
03518                     for j in range(len(new_denominator.factor[i])):
03519                         final_denominator.append( \
03520                                            new_denominator.factor[i]\
03521                                                           .factor[j]\
03522                                                           .clone())
03523 
03524             # Now let's check if some unstriked Items could be striked
03525             for i in range(len(final_numerator)):
03526                 if not final_numerator[i].is_out_striked:
03527                     for j in range(len(final_denominator)):
03528                         if not final_denominator[j].is_out_striked:
03529                             if final_numerator[i].raw_value ==                    \
03530                                final_denominator[j].raw_value:
03531                             #___
03532                                 final_numerator[i].set_is_out_striked(True)
03533                                 debug.write("\n[6]Striked out : " \
03534                                                 + final_numerator[i].dbg_str(),
03535                               case=debug.striking_out_in_simplification_line)
03536                                 final_denominator[j].set_is_out_striked(True)
03537                                 debug.write("\n[7]Striked out : " \
03538                                                 + final_numerator[i].dbg_str(),
03539                               case=debug.striking_out_in_simplification_line)
03540 
03541             # Now let's simplify eventually minus signs and display them
03542             # as forced "+". We'll do that only if the numerator and/or the
03543             # denominator have at least 2 elements. (More simple cases are
03544             # handled somewhere else, in a different way : we want the -2/-5
03545             # fraction first be visible and then the - signs just "disappear").
03546             # We'll convert them into plusses just two by two.
03547             position_of_the_last_minus_sign = None
03548 
03549             if self.sign == '-':
03550                     position_of_the_last_minus_sign = ('f', 0)
03551 
03552             if len(final_numerator) >= 2 or len(final_denominator) >= 2:
03553                 debug.write("\n[Simplification line entering minus " \
03554                             + "signs simplification]",
03555                               case=debug.simplification_line_minus_signs)
03556                 for I in [final_numerator, final_denominator]:
03557                     for i in range(len(I)):
03558                         if I[i].get_sign() == '-':
03559                             if position_of_the_last_minus_sign is None:
03560                                 if I == final_numerator:
03561                                     position_of_the_last_minus_sign = ('n', i)
03562                                 else:
03563                                     position_of_the_last_minus_sign = ('d', i)
03564                             elif position_of_the_last_minus_sign[0] == 'f':
03565                                 self.set_sign('+')
03566                                 I[i].set_sign('+')
03567                                 I[i].set_force_display_sign_once(True)
03568                                 position_of_the_last_minus_sign = None
03569                                 debug.write("\n[Simplification line : minus " \
03570                                             + "signs simplification]" \
03571                                             + " [A] 2 signs set to '+' ",
03572                                 case=debug.simplification_line_minus_signs)
03573                             elif position_of_the_last_minus_sign[0] == 'n':
03574                                 final_numerator[ \
03575                                     position_of_the_last_minus_sign[1]].\
03576                                     set_sign('+')
03577                                 final_numerator[ \
03578                                     position_of_the_last_minus_sign[1]].\
03579                                     set_force_display_sign_once(True)
03580                                 I[i].set_sign('+')
03581                                 I[i].set_force_display_sign_once(True)
03582                                 position_of_the_last_minus_sign = None
03583                                 debug.write("\n[Simplification line : minus " \
03584                                             + "signs simplification]" \
03585                                             + " [B] 2 signs set to '+' ",
03586                                 case=debug.simplification_line_minus_signs)
03587                             elif position_of_the_last_minus_sign[0] == 'd':
03588                                 final_denominator[ \
03589                                     position_of_the_last_minus_sign[1]].\
03590                                     set_sign('+')
03591                                 final_denominator[ \
03592                                     position_of_the_last_minus_sign[1]].\
03593                                     set_force_display_sign_once(True)
03594                                 I[i].set_sign('+')
03595                                 I[i].set_force_display_sign_once(True)
03596                                 position_of_the_last_minus_sign = None
03597                                 debug.write("\n[Simplification line : minus " \
03598                                             + "signs simplification]" \
03599                                             + " [C] 2 signs set to '+' ",
03600                                 case=debug.simplification_line_minus_signs)
03601 
03602             answer = Fraction((self.sign,
03603                                Product(final_numerator),
03604                                Product(final_denominator)
03605                               ))
03606 
03607             if debug.ENABLED and debug.simplification_line_minus_signs:
03608 
03609                 for i in range(len(answer.numerator)):
03610                     if answer.numerator[i].force_display_sign_once:
03611                         debug.write("\n[Simplification line : found a plus " \
03612                                             + "sign forced to display in nume]",
03613                                     case=debug.simplification_line_minus_signs)
03614 
03615                 for i in range(len(answer.denominator)):
03616                     if answer.denominator[i].force_display_sign_once:
03617                         debug.write("\n[Simplification line : found a plus " \
03618                                             + "sign forced to display in deno]",
03619                                     case=debug.simplification_line_minus_signs)
03620 
03621 
03622             answer.set_status("simplification_in_progress")
03623 
03624             return answer
03625 
03626 
03627 
03628 
03629 
03630     # --------------------------------------------------------------------------
03631     ##
03632     #   @brief Replace the striked out Items by Item(1)
03633     def replace_striked_out(self):
03634 
03635         debug.write("\nEntering replace_striked_out\n"\
03636                                + "with the Fraction :\n" \
03637                                + self.dbg_str(),
03638                                case=debug.replace_striked_out)
03639 
03640         result = Fraction(self)
03641 
03642         # The values of all striked out factors get replaced by "1"
03643         # ... numerator :
03644         for i in range(len(result.numerator)):
03645             if result.numerator.factor[i].is_out_striked:
03646                 result.numerator.factor[i].set_value_inside(Value(1))
03647                 result.numerator.factor[i].set_is_out_striked(False)
03648 
03649         # ... denominator
03650         for j in range(len(result.denominator)):
03651             if result.denominator.factor[j].is_out_striked:
03652                 result.denominator.factor[j].set_value_inside(Value(1))
03653                 result.denominator.factor[j].set_is_out_striked(False)
03654 
03655         return result
03656 
03657 
03658 
03659 
03660 
03661 
03662     # --------------------------------------------------------------------------
03663     ##
03664     #   @brief Returns the fraction after a simplification step
03665     def simplified(self):
03666         debug.write("\nEntering simplified\n"\
03667                                + "with Fraction :\n" \
03668                                + self.dbg_str(),
03669                                case=debug.simplified)
03670 
03671         aux_fraction = None
03672 
03673         if self.simplification_in_progress:
03674             aux_fraction = Fraction(self)
03675         else:
03676             aux_fraction = Fraction(self.simplification_line())
03677 
03678 
03679         # determination of the sign :
03680         final_sign = sign_of_product([Item((aux_fraction.sign, 1, 1)),
03681                                           aux_fraction.numerator,
03682                                           aux_fraction.denominator])
03683 
03684         aux_fraction = aux_fraction.replace_striked_out()
03685 
03686         final_numerator = Product([Item(int(                                  \
03687                                math.fabs(aux_fraction.numerator.evaluate())))])
03688 
03689         final_denominator = Product([Item(int(                                \
03690                              math.fabs(aux_fraction.denominator.evaluate())))])
03691 
03692         # Note that this final Fraction has a
03693         # status = "nothing" (default)
03694 
03695         if final_denominator.is_displ_as_a_single_1():
03696             if final_sign == '-':
03697                 return Item((Product([Item(-1), final_numerator])).evaluate())
03698             else:
03699                 if len(final_numerator) == 1:
03700                     return final_numerator[0]
03701                 else:
03702                     return final_numerator
03703         else:
03704             return Fraction((final_sign, final_numerator, final_denominator))
03705 
03706 
03707 
03708 
03709 
03710     # --------------------------------------------------------------------------
03711     ##
03712     #   @brief Returns the fraction after all simplification steps
03713     def completely_reduced(self):
03714         temp = gcd(self.numerator.evaluate(), self.denominator.evaluate())
03715 
03716         if temp == 1:
03717             return self
03718 
03719         else:
03720             return Fraction((self.sign,
03721                              Item(self.numerator.evaluate() / temp),
03722                              Item(self.denominator.evaluate() / temp)
03723                             ))
03724 
03725 
03726 
03727 
03728     # --------------------------------------------------------------------------
03729     ##
03730     #   @brief True if the fraction is reducible
03731     # So if numerator and denominator *are numeric Products* with both
03732     # exponent 1 both and their GCD is strictly greater than 1.
03733     # If any of denominator or numerator is an Item, then it is embedded by
03734     # this method in a Product to allow the further simplification of the
03735     # Fraction
03736     def is_reducible(self):
03737         if not self.is_numeric():
03738             return False
03739 
03740         if self.numerator.evaluate() == 0:
03741             return True
03742 
03743         if not isinstance(self.numerator, Product):
03744             if isinstance(self.numerator, Item):
03745                 self.set_numerator(Product(self.numerator))
03746             else:
03747                 return False
03748 
03749         if not isinstance(self.denominator, Product):
03750             if isinstance(self.denominator, Item):
03751                 self.set_denominator(Product(self.denominator))
03752             else:
03753                 return False
03754 
03755 
03756         for i in range(len(self.numerator)):
03757             if not (isinstance(self.numerator.factor[i], Item)                \
03758                     and is_.an_integer(self.numerator.factor[i].raw_value)        \
03759                     and self.numerator.factor[i].exponent == Value(1)         \
03760                     ):
03761                 return False
03762 
03763         for i in range(len(self.denominator)):
03764             if not (isinstance(self.denominator.factor[i], Item)              \
03765                     and is_.an_integer(self.denominator.factor[i].raw_value)      \
03766                     and self.denominator.factor[i].exponent == Value(1)       \
03767                     ):
03768                 return False
03769 
03770         if gcd(self.numerator.evaluate(), self.denominator.evaluate()) > 1:
03771             return True
03772         else:
03773             return False
03774 
03775 
03776 
03777 
03778 
03779     # --------------------------------------------------------------------------
03780     ##
03781     #   @brief True if the Fraction is a decimal number
03782     def is_a_decimal_number(self):
03783         deno = self.completely_reduced().denominator.evaluate()
03784 
03785         while not (deno % 2):
03786             deno /= 2
03787 
03788         while not (deno % 5):
03789             deno /= 5
03790 
03791         if deno == 1:
03792             return True
03793 
03794         else:
03795             return False
03796 
03797 
03798 
03799 
03800 
03801 
03802 
03803 # ------------------------------------------------------------------------------
03804 # --------------------------------------------------------------------------
03805 # ------------------------------------------------------------------------------
03806 ##
03807 # @class CommutativeOperation
03808 # @brief Abstract mother class of Product and Sum. Gathers common methods.
03809 class CommutativeOperation(Operation):
03810 
03811 
03812 
03813 
03814 
03815     # --------------------------------------------------------------------------
03816     ##
03817     #   @brief Constructor
03818     #   @warning CommutativeOperation objects are not really usable
03819     #   @return An "instance" of CommutativeOperation
03820     def __init__(self):
03821         Operation.__init__(self)
03822 
03823         # These strings are used in the (debugging) dbg_str method.
03824         # The __init__ of Sum and Product will initialize them at
03825         # desired values (so far, [] for a Sum and <> for a Product)
03826         # They are not treated as the other fields because they're only
03827         # useful for debugging
03828         self.str_openmark = ""
03829         self.str_closemark = ""
03830 
03831         # Two "displaying mode" fields
03832         self._compact_display = True
03833         self._info = []
03834 
03835 
03836 
03837 
03838 
03839     # --------------------------------------------------------------------------
03840     ##
03841     #   @brief Returns the compact_display field of a CommutativeOperation
03842     def get_compact_display(self):
03843         return self._compact_display
03844 
03845 
03846 
03847 
03848 
03849     # --------------------------------------------------------------------------
03850     ##
03851     #   @brief Allow the subclasses to access this field
03852     def get_info(self):
03853         return self._info
03854 
03855 
03856 
03857 
03858 
03859     # --------------------------------------------------------------------------
03860     ##
03861     #   @brief If the Product is literal, returns the first factor's letter
03862     def get_first_letter(self):
03863         if self.is_literal():
03864             return self.element[0].get_first_letter()
03865 
03866         else:
03867             raise error.UncompatibleType(self, "LitteralCommutativeOperation")
03868 
03869 
03870 
03871 
03872 
03873     # --------------------------------------------------------------------------
03874     ##
03875     #   @brief Returns the sign of the first element of the CommutativeOperation
03876     def get_sign(self):
03877         return self.element[0].sign
03878 
03879 
03880 
03881 
03882     info = property(get_info, doc="info field of a CommutativeOperation")
03883 
03884     compact_display = property(get_compact_display,
03885                         doc="compact_display field of a CommutativeOperation")
03886 
03887 
03888 
03889     # --------------------------------------------------------------------------
03890     ##
03891     #   @brief Sets the info field of the CommutativeOperation
03892     def set_info(self, arg):
03893         if not type(arg) == list:
03894             raise error.WrongArgument(str(type(arg)), "a list")
03895 
03896         if len(arg) == 0:
03897             self._info = arg
03898 
03899         else:
03900             for elt in arg:
03901                 if not elt in [True, False]:
03902                     raise error.WrongArgument(str(type(elt)), "True|False")
03903 
03904             self._info = arg
03905 
03906 
03907 
03908 
03909 
03910     # --------------------------------------------------------------------------
03911     ##
03912     #   @brief Sets the sign of the first element of the CommutativeOperation
03913     def set_sign(self, arg):
03914         self._element[0].set_sign(arg)
03915 
03916 
03917 
03918 
03919 
03920     # --------------------------------------------------------------------------
03921     ##
03922     #   @brief Sets a value to the compact_display field
03923     #   @param arg Must be True or False
03924     def set_compact_display(self, arg):
03925         if not (arg == True or arg == False):
03926             raise error.UncompatibleType(self, "Boolean")
03927 
03928         self._compact_display = arg
03929 
03930 
03931 
03932 
03933 
03934 
03935     # --------------------------------------------------------------------------
03936     ##
03937     #   @brief Returns the value (number) of an numerically evaluable CommutativeOperation
03938     #   @return The result as a number
03939     def evaluate(self, **options):
03940         #DEBUG
03941         debug.write("\n[evaluate in CommutativeOperation] Entered\n" \
03942                     + "[evaluate in CommutativeOperation] current CommutativeOperation is : " \
03943                     + self.dbg_str() + "\n",
03944                     case=debug.evaluate_in_operation)
03945 
03946         if not('stop_recursion' in options and options['stop_recursion'] in YES):
03947             next_step = self.calculate_next_step()
03948 
03949             if next_step != None:
03950                 #DEBUG
03951                 debug.write("\n[evaluate in CommutativeOperation] exiting," \
03952                             + " returning evaluate() called on : "\
03953                             + next_step.dbg_str()\
03954                             + "\n",
03955                             case=debug.evaluate_in_operation)
03956                 return next_step.evaluate()
03957 
03958         answer = self.neutral.raw_value
03959 
03960         for elt in self.element:
03961             #DEBUG
03962             debug.write("[evaluate in CommutativeOperation] current elt is : " \
03963                         + elt.dbg_str() + "\n",
03964                         case=debug.evaluate_in_operation)
03965             if isinstance(elt, Item) or isinstance(elt, Value):
03966                 # we don't check if the possibly Item is numeric.
03967                 # if it's not, an error will be raised
03968                 val = elt.raw_value
03969                 expo = 1
03970                 sign_val = 1
03971 
03972                 if isinstance(elt, Item):
03973                     expo = elt.exponent.evaluate()
03974                     if elt.is_negative():
03975                         sign_val = -1
03976 
03977                 if expo == 0:
03978                     val = 1
03979 
03980                 answer = self.operator(answer, sign_val * (val ** expo))
03981                 #DEBUG
03982                 debug.write("[evaluate in CommutativeOperation] a- current answer is : " \
03983                             + str(answer) + "\n",
03984                             case=debug.evaluate_in_operation)
03985 
03986             elif isinstance(elt, CommutativeOperation):
03987                 answer =  self.operator(answer, elt.evaluate())
03988                 #DEBUG
03989                 debug.write("[evaluate in CommutativeOperation] b- current answer is : " \
03990                             + str(answer) + "\n",
03991                             case=debug.evaluate_in_operation)
03992 
03993         external_expon = self.exponent.evaluate()
03994 
03995         #DEBUG
03996         debug.write("[evaluate in CommutativeOperation] external_expon is : " \
03997                     + str(external_expon) + "\n",
03998                     case=debug.evaluate_in_operation)
03999 
04000         return (answer ** external_expon)
04001 
04002 
04003 
04004 
04005 
04006     # --------------------------------------------------------------------------
04007     ##
04008     #   @brief Raw display of the CommutativeOperation (debugging method)
04009     #   @param options : info='OK' let dbg_str display more info
04010     #   @return A string : "<info1|info2||factor0, ..., factorn>^{exponent}"
04011     #                 or : "[info1|info2||term0, ..., termn]^{exponent}"
04012     def dbg_str(self, **options):
04013         elements_list_string = ""
04014         for i in range(len(self)):
04015             elements_list_string += self.element[i].dbg_str()
04016             if i < len(self) - 1:
04017                 elements_list_string += ' ' + self.symbol + ' '
04018 
04019         info = ""
04020         if 'info' in options:
04021             info = str(self.compact_display) \
04022                  + "|"                         \
04023                  + "|"  \
04024                  + str(self.info) \
04025                  + "||"
04026 
04027         expo = ""
04028         if not self.exponent.is_displ_as_a_single_1():
04029             expo = "^{" + self.exponent.dbg_str() + "}"
04030 
04031         return self.str_openmark  \
04032                + info \
04033                + elements_list_string                          \
04034                + self.str_closemark \
04035                + expo \
04036                + " "
04037 
04038 
04039 
04040 
04041 
04042     # --------------------------------------------------------------------------
04043     ##
04044     #   @brief Returns the number of elements of the CommutativeOperation
04045     def __len__(self):
04046         return len(self.element)
04047 
04048 
04049 
04050 
04051 
04052     # --------------------------------------------------------------------------
04053     ##
04054     #   @brief True if the CommutativeOperation contains exactly the given objct
04055     #   It can be used to detect objects embedded in this CommutativeOperation
04056     #   @param objct The object to search for
04057     #   @return True if the CommutativeOperation contains exactly the given objct
04058     def contains_exactly(self, objct):
04059         if len(self) != 1:
04060             return False
04061         elif self.element[0] == objct:
04062             return True
04063         else:
04064             return self.element[0].contains_exactly(objct)
04065 
04066 
04067 
04068 
04069     # --------------------------------------------------------------------------
04070     ##
04071     #   @brief To check if this contains a rounded number...
04072     #   @return True or False
04073     def contains_a_rounded_number(self):
04074         for elt in self:
04075             if elt.contains_a_rounded_number():
04076                 return True
04077 
04078         return False
04079 
04080 
04081 
04082 
04083 
04084     # --------------------------------------------------------------------------
04085     ##
04086     #   @brief Appends a given element to the current CommutativeOperation
04087     #   @param elt  The element to append (assumed to be a Exponented)
04088     def append(self, elt):
04089         self._element.append(elt)
04090         self._info.append(False)
04091 
04092 
04093 
04094 
04095 
04096     # --------------------------------------------------------------------------
04097     ##
04098     #   @brief Removes a given element from the current CommutativeOperation
04099     #   @param elt  The element to remove (assumed to be a Exponented)
04100     def remove(self, elt):
04101         i = 0
04102 
04103         # Let's find the position of the term
04104         while i < len(self) \
04105               and self.element[i] != elt \
04106               and not self.element[i].contains_exactly(elt):
04107         #___
04108             i += 1
04109 
04110         # Check if we really find the researched term
04111         if i == len(self):
04112             raise error.UnreachableData(str(elt) \
04113                                         + " in " \
04114                                         + self.dbg_str())
04115 
04116         # Then pop the right one
04117         self._element.pop(i)
04118         self._info.pop(i)
04119 
04120 
04121 
04122 
04123 
04124     # --------------------------------------------------------------------------
04125     ##
04126     #   @brief Returns self without the equivalent-to-a-single-neutral elements
04127     def throw_away_the_neutrals(self):
04128         collected_positions = list()
04129 
04130         for i in range(len(self)):
04131             if self.element[i].is_displ_as_a_single_neutral(self.neutral):
04132                 #DEBUG
04133                 debug.write("\n" + self.element[i].dbg_str() \
04134                                        + "has been detected as " \
04135                                        + "'single neutral'" \
04136                                        + " the ref. neutral being " \
04137                                        + self.neutral.dbg_str(),
04138                                        case=debug.throw_away_the_neutrals)
04139                 collected_positions.append(i)
04140 
04141         result = None
04142 
04143         if isinstance(self, Product):
04144             result = Product(self)
04145         elif isinstance(self, Sum):
04146             result = Sum(self)
04147 
04148         if len(collected_positions) == len(self):
04149             result.reset_element()
04150             result.element.append(Item(self.neutral))
04151             result.set_info([False])
04152 
04153         else:
04154             for i in range(len(collected_positions)):
04155                 # this - i is necessary in the case of several items
04156                 # because the real length of the element list diminishes
04157                 # each time an item is poped.
04158                 result._element.pop(collected_positions[i] - i)
04159                 result._info.pop(collected_positions[i] - i)
04160 
04161         return result
04162 
04163 
04164 
04165 
04166 
04167 
04168 
04169     # --------------------------------------------------------------------------
04170     ##
04171     #   @brief True if the object can be displayed as a single neutral element
04172     def is_displ_as_a_single_neutral(self, neutral_elt):
04173         for elt in self.element:
04174             if not elt.is_displ_as_a_single_neutral(neutral_elt):
04175                 return False
04176 
04177         return True
04178 
04179 
04180 
04181 
04182 
04183     # --------------------------------------------------------------------------
04184     ##
04185     #   @brief True if the CommutativeOperation contains only one numeric Item
04186     def is_displ_as_a_single_numeric_Item(self):
04187         if not self.is_numeric() or not len(self) == 1:
04188             return False
04189         else:
04190             return self.element[0].is_displ_as_a_single_numeric_Item()
04191 
04192 
04193 
04194 
04195 
04196     # --------------------------------------------------------------------------
04197     ##
04198     #   @brief True if the object can be displayed as a single int
04199     def is_displ_as_a_single_int(self):
04200         if len(self) == 1:
04201             return self[0].is_displ_as_a_single_int()
04202         else:
04203             return False
04204 
04205 
04206 
04207 
04208 # ------------------------------------------------------------------------------
04209 # --------------------------------------------------------------------------
04210 # ------------------------------------------------------------------------------
04211 ##
04212 # @class Product
04213 # @brief Has Exponented factors & an exponent. Iterable. Two display modes.
04214 class Product (CommutativeOperation):
04215 
04216 
04217 
04218 
04219 
04220     # --------------------------------------------------------------------------
04221     ##
04222     #   @brief Constructor
04223     #   @warning Might raise an UncompatibleType exception.
04224     #   @param arg None|Product|Number|Exponented|[Numbers|Exponenteds]
04225     #   In the case of the list, the Products having an exponant equal to 1
04226     #   won't be treated so that their factors are inserted in the current
04227     #   Product instead of inserting a factor as a Product.
04228     #   If it would, then the compact and non compact display properties
04229     #   might be lost. (For instance, multiplying two Monomials and setting
04230     #   the compact display field of the resulting Product to False would
04231     #   result in displaying all the Monomials Items which isn't always wished)
04232     #   If the argument isn't of the kinds listed above, an exception will be
04233     #   raised.
04234     #   Giving None or an empty list is equivalent to giving 1.
04235     #   The exponent of a Product is 'outside' (like 3 in (4×5x)³)
04236     #   @return An instance of Product
04237     def __init__(self, arg):
04238         CommutativeOperation.__init__(self)
04239         # The self.compact_display flag is let set to True
04240         # If it is set to False, the display_multiply_symbol (aka info)
04241         # will be used to know whether the × symbol is to be displayed
04242         # between two factors.
04243         # If it is set to True, the × symbol will never be displayed unless
04244         # the general writing rules of mathematical expressions force the ×
04245         # to be displayed (for instance between two numbers)
04246 
04247         # The self._info is a list whose first element doesn't match to anything,
04248         # it will always be False, whatever. The 2d element means the
04249         # × symbol between 1st and 2d factor etc.
04250         self._symbol = '×'
04251 
04252         self._neutral = Item(1)
04253 
04254         self.str_openmark = "<"
04255         self.str_closemark = ">"
04256 
04257         # 1st CASE : None or void list []
04258         if arg is None or (type(arg) == list and len(arg) == 0):
04259             self._element.append(Item(1))
04260 
04261         # 2d CASE : Product
04262         elif isinstance(arg, Product):
04263             self._compact_display = arg.compact_display
04264             self._exponent = arg.exponent.clone()
04265             for i in range(len(arg.element)):
04266                 self._element.append(arg.element[i].clone())
04267             for i in range(len(arg.info)):
04268                 self._info.append(arg.info[i])
04269 
04270         # 3d CASE : Number
04271         elif is_.a_number(arg):
04272             self._element.append(Item(arg))
04273 
04274         # 4th CASE : Exponented
04275         elif isinstance(arg, Exponented):
04276             self._element.append(arg.clone())
04277 
04278         # 5th CASE : [Numbers|Exponenteds]
04279         elif (type(arg) == list) and len(arg) >= 1:
04280             for i in range(len(arg)):
04281 
04282                 if i == 0:
04283                     self._info.append(False)
04284                 else:
04285                     self._info.append(True)
04286 
04287                 # If 1-exponent Products are being treated as the 1-exponent
04288                 # Sums it leads to bugs : the Monomials would "dissolve" into
04289                 # other Products, indeed ! And more generally, what about
04290                 # "adding" a compact Product to a non-compact one... ?
04291                 # So, this is definitely not to do.
04292                 if isinstance(arg[i], Exponented):
04293                     self._element.append(arg[i].clone())
04294 
04295                 elif is_.a_number(arg[i]):
04296                     self._element.append(Item(arg[i]))
04297 
04298                 elif arg[i] is None:
04299                     self._element.append(Item(1))
04300 
04301                 else:
04302                     raise error.UncompatibleType(arg[i],
04303                                                  "This element of the\
04304                                                  provided list\
04305                                                  should have been either a\
04306                                                  a Exponented or a Number")
04307 
04308         # All other unforeseen cases : an exception is raised.
04309         else:
04310             raise error.UncompatibleType(arg,
04311                                          "Product|Exponented|Number|\
04312                                          [Exponenteds|Numbers]")
04313 
04314 
04315 
04316 
04317 
04318     # --------------------------------------------------------------------------
04319     ##
04320     #   @brief Returns the factors' list of the Product except the given one
04321     def get_factors_list_except(self, objct):
04322         if len(self) == 1 and self.factor[0] == objct:
04323             return None
04324         else:
04325             if objct in self.factor:
04326                 aux_list = list()
04327                 objct_was_found = False
04328                 for i in range(len(self)):
04329                     if self.factor[i] != objct:
04330                         aux_list.append(self.factor[i])
04331                     else:
04332                         if objct_was_found:
04333                             aux_list.append(self.factor[i])
04334                         else:
04335                             objct_was_found = True
04336                 return aux_list
04337             else:
04338                 raise error.UnreachableData("the object : " + objct.dbg_str() \
04339                                             + " in this Product : "           \
04340                                             + self.dbg_str())
04341 
04342 
04343 
04344 
04345 
04346     # --------------------------------------------------------------------------
04347     ##
04348     #   @brief Returns the first Sum/Item factor of the Product
04349     #   @warning Maybe not functionnal because of the 1's...
04350     #   For instance, if <factor1,factor2> is a Product & [term1,term2] a Sum:
04351     #   <<2,3>,x> would return 2
04352     #   <[2,3],x>² would return (2+3)²
04353     #   <<2x>²,4> would return 2²
04354     #   @todo check the intricate case of <<[<0, 1>]>, 4>
04355     def get_first_factor(self):
04356         if len(self) == 0:
04357             return None
04358         else:
04359             # Either it's a one-term Sum, it has to be managed recursively
04360             # as if this term was embedded in a Product instead of a Sum
04361             if isinstance(self.factor[0], Sum) \
04362                 and len(self.factor[0]) == 1:
04363             #___
04364                 answer = Product(self.factor[0].term[0]).get_first_factor()
04365 
04366             # Or it's a Product, we get its first factor then recursively
04367             elif isinstance(self.factor[0], Product):
04368                 answer = self.factor[0].get_first_factor()
04369 
04370             # Or anything else, we just get the thing
04371             # (Anything else being a )
04372             else:
04373                 answer = self.factor[0]
04374 
04375             answer.set_exponent(answer.exponent * self.exponent)
04376             return answer
04377 
04378 
04379 
04380 
04381 
04382     # --------------------------------------------------------------------------
04383     ##
04384     #   @brief Returns the number of - signs (negative factors) in the Product
04385     def get_minus_signs_nb(self):
04386         answer = 0
04387         for factor in self.element:
04388             answer += factor.get_minus_signs_nb()
04389 
04390         return answer
04391 
04392 
04393 
04394 
04395 
04396     # --------------------------------------------------------------------------
04397     ##
04398     #   @brief Returns the factors' list of a given kind (numeric, literal...)
04399     #   For instance, for the product :
04400     #   2x × (-4x²) × (x + 3)³ × 5 × (x²)³ × (-1)² × (2×3)²,
04401     #   this method would return :
04402     #   - in the case of simple numeric items : [2, -4, 5, (-1)², 2², 3²]
04403     #   - in the case of simple literal items : [x, x², x**6]
04404     #   - in the "others" list : [(x+3)³]
04405     #   This method helps to reduce a Product.
04406     #   It doesn't calculate anything and doesn't manage exotic cases
04407     #   (imbricated Products...) which are too complex to foresee. As far as
04408     #   I could... (later note : but I'm not sure it doesn't manage them now,
04409     #   I might have done that later than this comment).
04410     #   It will still be convenient to reorder the factors of a Monomials
04411     #   Product.
04412     #   @param given_kind : NUMERIC | LITERALS | OTHERS
04413     #   @return a list containing the factors
04414     #   @todo The - signs of the literals should be treated as -1 in the
04415     #   numeric list (and shouldn't remain in the literals' list)
04416     def get_factors_list(self, given_kind):
04417         resulting_list = list()
04418         a_factor_not_equivalent_to_1_has_been_found = False
04419         #DEBUG
04420         debug.write("\n[get_factors_list] Entered, looking for " \
04421                     + given_kind + "\n" \
04422                     + "[get_factors_list] current Product is : " \
04423                     + self.dbg_str() + "\n",
04424                     case=debug.get_factors_list_product)
04425 
04426         for factor in self.element:
04427             #DEBUG
04428             debug.write("[get_factors_list] " \
04429                         + "current factor is : " \
04430                         + factor.dbg_str() + "\n",
04431                         case=debug.get_factors_list_product)
04432 
04433             if isinstance(factor, Item)                              \
04434                and (                                                          \
04435                     (factor.is_numeric() and given_kind == NUMERIC)        \
04436                     or                                                        \
04437                     (factor.is_literal() and given_kind == LITERALS)       \
04438                    ):
04439             #___
04440                 #DEBUG
04441                 debug.write("[get_factors_list] " \
04442                         + "current factor is an Item\n" \
04443                         + "[get_factors_list] it is positive : "\
04444                         + str(factor.is_positive()) + "\n"\
04445                         + "[get_factors_list] it is negative : "\
04446                         + str(factor.is_negative()) + "\n"\
04447                         + "[get_factors_list] self.compact_display is " \
04448                         + str(self.compact_display) + "\n",
04449                         case=debug.get_factors_list_product)
04450                 # Here the possible external exponent has to get down
04451                 # and a possible (-1)² product has to be created here
04452                 if factor.is_positive()                                       \
04453                    and not (self.compact_display
04454                             and factor.is_displ_as_a_single_1()):
04455                 #___
04456                     # If the Item has got a '+' sign, no worry, it can get
04457                     # added to the list without forgetting to put the external
04458                     # exponent on it.
04459                     # The "1" value Items are not managed here in the case of
04460                     # compact displayed Products
04461                     # (that's useless, moreover, it would make reappear all
04462                     # the "1" of terms such like x, x², x³ etc.)
04463                     # But one "1" has to be added in the case when the given
04464                     # product contains only "1"s (otherwise we would return
04465                     # an empty list)
04466                     #DEBUG
04467                     debug.write("[get_factors_list] " \
04468                             + "current factor is positive\n",
04469                             case=debug.get_factors_list_product)
04470                     item_to_be_added = Item((factor.sign,                     \
04471                                              factor.raw_value,                    \
04472                                              factor.exponent * self.exponent  \
04473                                             ))
04474                     item_to_be_added.set_is_out_striked(factor.is_out_striked)
04475                     resulting_list.append(item_to_be_added)
04476                     #DEBUG
04477                     debug.write("[get_factors_list] " \
04478                             + "adding : " + item_to_be_added.dbg_str() + "\n",
04479                             case=debug.get_factors_list_product)
04480                     a_factor_not_equivalent_to_1_has_been_found = True
04481 
04482                 elif factor.is_negative():
04483                     #DEBUG
04484                     debug.write("[get_factors_list] " \
04485                             + "current factor is negative\n",
04486                             case=debug.get_factors_list_product)
04487                     # If the Item has got a '-' sign, it has to be embedded
04488                     # in a Product (of only one factor) on which the external
04489                     # has to be put down. For instance, (a × (-1) × b)² should
04490                     # return a², b² (managed in the Items section) AND also
04491                     # (-1)²
04492                     # It has to be done only if the exponent is even.
04493                     item_to_be_added = factor
04494                     if is_even(self.exponent):
04495                         item_to_be_added = Product([factor])
04496                     item_to_be_added.set_exponent(self.exponent)
04497                     #DEBUG
04498                     debug.write("[get_factors_list] " \
04499                             + "adding : " + item_to_be_added.dbg_str() + "\n",
04500                             case=debug.get_factors_list_product)
04501                     resulting_list.append(item_to_be_added)
04502                     a_factor_not_equivalent_to_1_has_been_found = True
04503 
04504             elif isinstance(factor, Product):
04505                 #DEBUG
04506                 debug.write("[get_factors_list] " \
04507                         + "current factor is a Product\n",
04508                         case=debug.get_factors_list_product)
04509                 # If it's a Product, the external exponent must get down on
04510                 # it and the function is recursively called. This includes
04511                 # managing the factor (-1)³ in this example : (a * (-1)³ * b)²
04512                 # It will be then managed like the Product (-1)⁶ and managed
04513                 # in the negative Items section somewhat above.
04514                 aux_product = Product(factor)
04515                 aux_product.set_exponent(factor.exponent * self.exponent)
04516                 temp_list = aux_product.get_factors_list(given_kind)
04517 
04518                 if len(temp_list) != 0:
04519                     resulting_list += temp_list
04520                     a_factor_not_equivalent_to_1_has_been_found = True
04521 
04522             elif isinstance(factor, Sum):
04523                 if len(factor) == 1:
04524                     # Only-one-term Sum : it is copied into an only-one-factor
04525                     # Product (including the exponent) and the method is
04526                     # called recursively on it.
04527                     aux_list = list()
04528                     aux_list.append(factor.term[0])
04529                     aux_product = Product(aux_list)
04530                     aux_product.set_exponent(factor.exponent)
04531                     temp_list = aux_product.get_factors_list(given_kind)
04532                     if len(temp_list) != 0:
04533                         resulting_list += temp_list
04534                         a_factor_not_equivalent_to_1_has_been_found = True
04535 
04536                 elif given_kind == OTHERS:
04537                     # Several-terms Sums get managed only if OTHERS kind of
04538                     # factors are wanted
04539                     aux_sum = Sum(factor)
04540                     aux_sum.set_exponent(factor.exponent * self.exponent)
04541                     resulting_list.append(aux_sum)
04542                     a_factor_not_equivalent_to_1_has_been_found = True
04543 
04544             elif isinstance(factor, Quotient):
04545                 if (factor.is_numeric() and given_kind == NUMERIC)         \
04546                    or                                                         \
04547                    (factor.is_literal() and given_kind == LITERALS):
04548                 #___
04549                     resulting_list.append(factor)
04550                     a_factor_not_equivalent_to_1_has_been_found = True
04551                 else:
04552                     if given_kind == OTHERS:
04553                         resulting_list.append(factor)
04554                         a_factor_not_equivalent_to_1_has_been_found = True
04555 
04556 
04557         if given_kind == NUMERIC                                           \
04558            and not a_factor_not_equivalent_to_1_has_been_found                \
04559            and len(resulting_list) == 0:
04560         #___
04561             resulting_list.append(Item(1))
04562 
04563         #DEBUG
04564         debug.write("[get_factors_list] exiting ; len(resulting_list) = "\
04565                     + str(len(resulting_list)) + "\n",
04566                     case=debug.get_factors_list_product)
04567 
04568         return resulting_list
04569 
04570 
04571 
04572 
04573 
04574 
04575     factor = property(CommutativeOperation.get_element,
04576                       doc = "To access the factors of the Product.")
04577 
04578 
04579 
04580 
04581 
04582     # --------------------------------------------------------------------------
04583     ##
04584     #   @brief
04585     #   @param n : number of the factor to set
04586     #   @param arg : the object to put as n-th factor
04587     def set_factor(self, n, arg):
04588         self.set_element(n, arg)
04589 
04590 
04591 
04592 
04593     # --------------------------------------------------------------------------
04594     ##
04595     #   @brief Creates a string of the given object in the given ML
04596     #   @param options Any options
04597     #   @return The formated string
04598     def into_str(self, **options):
04599         global expression_begins
04600 
04601         if 'force_expression_begins' in options \
04602            and options['force_expression_begins'] == True:
04603         #___
04604             expression_begins = True
04605             options['force_expression_begins'] = False
04606 
04607         # This reflects the position to indicate to the function
04608         # requires_brackets()
04609         # It will be incremented everytime something is displayed.
04610         position = 0
04611         if 'force_position' in options \
04612            and is_.an_integer(options['force_position']):
04613         #___
04614             position = options['force_position']
04615             # let's remove this option from the options
04616             # since it might be re-used recursively
04617             temp_options = dict()
04618             for key in options:
04619                 if key != 'force_position':
04620                     temp_options[key] = options[key]
04621             options = temp_options
04622 
04623         #DEBUG
04624         debug.write( \
04625             "\n\nEntered into_str in Product : expression_begins = " \
04626             + str(expression_begins) + " | position set to " + str(position)\
04627             + "\nCurrent Product :\n" + self.dbg_str(),
04628             case=debug.into_str_in_product)
04629 
04630 
04631         # All Product objects are treated here
04632         # (including Monomials & Developables)
04633         # Displaying the + sign depends on the expression_begins machine's flag
04634         # In a product, this flag has to be reset to True for each new factor
04635         # after the first one.
04636         if self.compact_display:
04637             #debug
04638             debug.write("\n[Product.into_str():self.compact_display is True]" \
04639                                   + " is_equiv_single_neutral : " \
04640                                    + str(self.element[0].\
04641                                    is_displ_as_a_single_neutral(\
04642                                    self.neutral)),
04643                                    case=debug.into_str_in_product)
04644 
04645 
04646             #copy = self.clone().throw_away_the_neutrals()
04647             #DEBUG
04648             #debug.write( \
04649             #    "\nCurrent Copy without 'ones' :\n" + str(copy),
04650             #    case=debug.into_str_in_product)
04651 
04652 
04653 
04654             # Compact display section :
04655             # - All unnecessary and unrequired × signs won't be displayed,
04656             # - All factors equal to 1 won't be displayed (which means
04657             #   positive items having a 0 exponent and/or whose value is 1
04658             #   in the case of numeric items).
04659             # - If the first factor equals -1, only the - sign is displayed
04660             # - If the product only contains ones (1), 1 is displayed at
04661             #   the end
04662             # - And -1 is displayed if the product contains only one -1 and
04663             #   items equal to 1
04664             resulting_string = ""
04665 
04666             # This couple is a couple of objects to display.
04667             # It is needed to determine wether a - sign is necessary or not
04668             # before dislaying a new factor (the second one in the couple)
04669             # If the couple contains :
04670             # - (None, None), then nothing has been displayed yet
04671             #   In this case, if any new factor "factor1" gets displayed,
04672             #   the couple becomes (factor1, None)
04673             # - (factor1, None), only factor1 has been displayed yet.
04674             #   In this case, if any new factor "factor2" gets displayed,
04675             #   the couple becomes (factor1, factor2)
04676             # - (factor1, factor2), then these two factors have already
04677             #   been displayed.
04678             #   In this case, if any new factor "factor3" gets displayed,
04679             #   the couple becomes (factor2, factor3)
04680             couple = (None, None)
04681 
04682             # This flag checks if one factor at least has already been
04683             # displayed. If not, then a "1" will be displayed at the end.
04684             flag = False
04685 
04686             # Here was the previous localization of the initialization of the
04687             # local variable position...
04688 
04689             #debug.write("\nposition : " + str(position) + "\n")
04690 
04691             # This checks if an "orphan" - sign has been displayed
04692             orphan_minus_sign = False
04693 
04694             # This flag is used to add brackets inside of "compact" Products
04695             # like in 9×(-2a)×4b where (-2a)×4b is a "compact" Product itself
04696             unclosed_bracket = 0
04697 
04698             # Any product must contain one factor at least.
04699             # It is processed here.
04700             # Its processing is made apart from the others because it
04701             # requires a special processing if it is a -1
04702             if self.factor[0].is_displ_as_a_single_1():
04703                 # Nothing has to be done. If the product only contains
04704                 # ones, a "1" will be displayed at the end (because flag
04705                 # will then remain to False)
04706                 # expression_begins = False : that can't be made here like
04707                 # it is in the case of -1 (the case of -1 has the help of
04708                 # the flag orphan_minus_sign). It is made later, when the
04709                 # finally lonely 1 is displayed. If it is not a lonely 1
04710                 # then other factors will set expression_begins to False
04711                 pass
04712             elif self.factor[0].is_displ_as_a_single_minus_1():
04713                 #DEBUG
04714                 debug.write("\n[Product.into_str():n°0]" \
04715                                        + " processing a '-1' 1st " \
04716                                        + "factor : " \
04717                                        + self.factor[0].dbg_str() \
04718                                        + "\nwith position forced to " \
04719                                        + str(position),
04720                                        case=debug.into_str_in_product)
04721                 if position >= 1:
04722                     debug.write(" and a bracket is */opened/*\n",
04723                                        case=debug.into_str_in_product)
04724                     resulting_string += MARKUP['opening_bracket']
04725                     unclosed_bracket += 1
04726 
04727                 # Then the - sign has to be displayed
04728                 # and position has to be incremented (because it
04729                 # influences the next factor displaying)
04730                 resulting_string += MARKUP['minus']
04731                 position += 1
04732                 orphan_minus_sign = True
04733                 # This expression_begins set to False is required to
04734                 # avoid the problem Sum(Product(Item(-1)), Item(x))
04735                 # being displayed -1x instead of -1+x
04736                 # The next factor needs to have it set True to be displayed
04737                 # correctly... but it doesn't matter thanks to the flag
04738                 # orphan_minus_sign which is used later to reset expressions_
04739                 # begins to True just in time :o)
04740                 expression_begins = False
04741             else:
04742                 # In this case, the first factor is different
04743                 # from 1 and from -1 : it will be displayed normally.
04744                 # To avoid putting brackets around a Sum that would be
04745                 # alone in the Product or with other factors which all
04746                 # are equivalent to 1, we check if it's not the case
04747                 # Note that the test position == 0 is necessary since
04748                 # the current Product might not be the first to be displayed :
04749                 # the current "first" factor is maybe not the first factor
04750                 # to be displayed
04751                 if (Product(self.get_factors_list_except(self.factor[0]))
04752                      .is_displ_as_a_single_1()                     \
04753                     and position == 0 )                                 \
04754                    or not (self.factor[0].requires_brackets(position)    \
04755                            and len(self) >= 2)                          \
04756                    or self.requires_inner_brackets():
04757                 #___
04758                     #DEBUG
04759                     debug.write("\n[Product.into_str():n°1A]" \
04760                                            + " processing 1st " \
04761                                            + "factor : " \
04762                                            + self.factor[0].dbg_str() \
04763                                            + "\nwith position forced to " \
04764                                            + str(position) \
04765                                            + " ; NO brackets",
04766                                            case=debug.into_str_in_product)
04767 
04768                     resulting_string += self.factor[0].into_str(
04769                                                      force_position=position,
04770                                                      **options)
04771 
04772                 # This case has been added to get rid of the "Monomial's
04773                 # patch" in Product.requires_brackets()
04774                 #elif
04775 
04776                 else:
04777                     expression_begins = True
04778                     #DEBUG
04779                     debug.write("\n[Product.into_str():n°1B] processing " \
04780                                            + "1st factor :" \
04781                                            + " " + self.factor[0].dbg_str() \
04782                                            + "\nwith position NOT forced to "\
04783                                            + str(position) \
04784                                            + ", needs brackets ? " \
04785                                            + str(self.factor[0].\
04786                                                requires_brackets(position)) \
04787                                            + " ; a bracket is */opened/*\n",
04788                                            case=debug.into_str_in_product)
04789 
04790                     resulting_string += MARKUP['opening_bracket']         \
04791                                      + self.factor[0].into_str(**options) #\
04792                                     # + MARKUP['closing_bracket']
04793 
04794                     unclosed_bracket += 1
04795 
04796                     if len(self.factor[0]) >= 2:
04797                         debug.write("and " \
04798                                                 + "*/closed/*\n",
04799                                case=debug.into_str_in_product)
04800                         resulting_string += \
04801                                           MARKUP['closing_bracket']
04802                         unclosed_bracket -= 1
04803 
04804                 # Flag is set to True because something has been
04805                 # displayed
04806                 # & this factor gets into the "couple" as first member
04807                 flag = True
04808                 position += 1
04809                 couple = (self.factor[0], None)
04810 
04811                 #DEBUG
04812                 debug.write("\n[Product.into_str()] " \
04813                             + "Finished processing the first factor" \
04814                             + " ; couple is (" +  self.factor[0].dbg_str() \
04815                             + ", None) ; flag is " + str(flag) + " position is "\
04816                             + str(position),
04817                             case=debug.into_str_in_product)
04818 
04819             # If there are other factors :
04820             if len(self) >= 2:
04821                 for i in range(len(self) - 1):
04822                     if self.factor[i + 1].is_displ_as_a_single_1():
04823                         # inside the product, the 1 factors just don't
04824                         # matter
04825                         pass
04826                     else:
04827                         if couple == (None, None):
04828                             # That means it's the first factor that will be
04829                             # displayed (apart from a possibly only - sign)
04830                             #DEBUG
04831                             debug.write("\n[Product.into_str()] " \
04832                                         + "more than one factor to process ; "\
04833                                         + "current couple is ("\
04834                                         + str(couple[0]) + ", "\
04835                                         + str(couple[1]) + ") ; "\
04836                                         + "flag is " + str(flag)\
04837                                         + " position is "\
04838                                         + str(position),
04839                                         case=debug.into_str_in_product)
04840                             couple = (self.factor[i + 1], None)
04841                             if self.factor[i + 1].requires_brackets(position)\
04842                                and not Product(self.get_factors_list_except(
04843                                                self.factor[i+1])
04844                                                ).is_displ_as_a_single_1():
04845                             #___
04846                                 expression_begins = True
04847                                 #DEBUG
04848                                 debug.write("\n[Product.into_str():n°2A] " \
04849                                                        + "processing " \
04850                                                        + "factor : "\
04851                                                 + self.factor[i+1].dbg_str()\
04852                                                        + "\nwith position " \
04853                                                        + "NOT forced to " \
04854                                                        + str(position) \
04855                                                        + " ; " \
04856                                                        + "a bracket is " \
04857                                                        + "*/opened/*\n",
04858                                            case=debug.into_str_in_product)
04859 
04860                                 resulting_string += MARKUP['opening_bracket'] \
04861                                                + self.factor[i+1].into_str(\
04862                                                                   **options) #\
04863                                                  #+ MARKUP['closing_bracket']
04864 
04865                                 unclosed_bracket += 1
04866 
04867                                 if len(self.factor[i+1]) >= 2:
04868                                     debug.write("[Product.into_str():]" \
04869                                                 + "and */closed/*\n",
04870                                            case=debug.into_str_in_product)
04871                                     resulting_string += \
04872                                                       MARKUP['closing_bracket']
04873                                     unclosed_bracket -= 1
04874 
04875 
04876                             else:
04877                                 # If an orphan - has been displayed, the
04878                                 # next item shouldn't display its
04879                                 # + sign if it is positive.
04880                                 if orphan_minus_sign:
04881                                     expression_begins = True
04882                                     #DEBUG
04883                                     debug.write("\n[Product.into_str():n°2B]" \
04884                                                        + " (orphan - sign) " \
04885                                                        + "processing " \
04886                                                        + "factor : "\
04887                                                 + self.factor[i+1].dbg_str()\
04888                                                        + "\nwith position " \
04889                                                        + "forced to " \
04890                                                        + str(position) \
04891                                                        + " ; NO brackets",
04892                                            case=debug.into_str_in_product)
04893 
04894                                     resulting_string +=                       \
04895                                                   self.factor[i+1].into_str(
04896                                                        force_position=position,
04897                                                        **options)
04898                                 else:
04899                                     #DEBUG
04900                                     debug.write("\n[Product.into_str():n°2C] " \
04901                                                        + "(NO orphan - sign) "\
04902                                                        + "processing " \
04903                                                        + "factor : "\
04904                                                   + self.factor[i+1].dbg_str()\
04905                                                        + "\nwith position " \
04906                                                        + "forced to " \
04907                                                        + str(position) \
04908                                                        + " ; NO brackets",
04909                                            case=debug.into_str_in_product)
04910 
04911                                     resulting_string +=                       \
04912                                                   self.factor[i+1].into_str(
04913                                                        force_position=position,
04914                                                        **options)
04915 
04916                             # Something has been displayed, so...
04917                             flag = True
04918                             position += 1
04919 
04920                         else:
04921                             if couple[1] is None:
04922                                 # It's the second factor to be displayed.
04923                                 # Let's update the current couple :
04924                                 current_factor_1 = couple[0]
04925                                 current_factor_2 = self.factor[i + 1]
04926                             else:
04927                                 # At least two factors have been displayed
04928                                 # Let's update the current couple :
04929                                 current_factor_1 = couple[1]
04930                                 current_factor_2 = self.factor[i + 1]
04931 
04932                             couple = (current_factor_1, current_factor_2)
04933 
04934                             # If necessary, the × sign will be displayed.
04935                             # The value of position - 1 will be used
04936                             # because couple[0]'s position matters
04937                             # and position matches couple[1]'s position
04938                             #DEBUG
04939                             debug.write( \
04940                                "\n[Product.into_str()] "\
04941                                + "Checking if a × should be required \n" \
04942                                + "between " + couple[0].dbg_str() \
04943                                + "    and    " \
04944                                + couple[1].dbg_str(),
04945                                case=debug.into_str_in_product)
04946                             if couple[0].multiply_symbol_is_required(couple[1],
04947                                                                  position - 1):
04948                             #___
04949                                 #DEBUG
04950                                 debug.write( \
04951                                    "\n... yes\n",
04952                                    case=debug.into_str_in_product)
04953                                 if unclosed_bracket >= 1:
04954                                     if couple[0].multiply_symbol_is_required(\
04955                                                                  couple[1],
04956                                                                  0):
04957                                     #___
04958                                         #DEBUG
04959                                         debug.write( "\n[Product.into_str()]" \
04960                                            + "the bracket is */closed/*\n",
04961                                            case=debug.into_str_in_product)
04962                                         resulting_string += \
04963                                                       MARKUP['closing_bracket']
04964                                         unclosed_bracket -= 1
04965 
04966                                         resulting_string += MARKUP['times']
04967                                     else:
04968                                         pass
04969                                 else:
04970                                     resulting_string += MARKUP['times']
04971 
04972                             else:
04973                                 #DEBUG
04974                                 debug.write( \
04975                                        "\n... no\n",
04976                                        case=debug.into_str_in_product)
04977 
04978 
04979 
04980                             # Because at least one factor has already been
04981                             # displayed, the expression_begins flag has to
04982                             # be reset (wether the next factor requires
04983                             # brackets or not).
04984                             expression_begins = True
04985 
04986                             if couple[1].requires_brackets(position):
04987                                 # It is useless to test if the other factors
04988                                 # are all equivalent to 1 because since we're
04989                                 # here, there must have been one factor that'd
04990                                 # not equivalent to 1
04991                                 #and not Product(self.get_factors_list_except(
04992                                           #     couple[1])
04993                                            #   ).is_displ_as_a_single_1():
04994                                 #DEBUG
04995                                 debug.write("\n[Product.into_str():n°3A] " \
04996                                                    + "processing " \
04997                                                    + "factor : "\
04998                                                    + couple[1].dbg_str()\
04999                                                    + "\nwith position " \
05000                                                    + "NOT forced to " \
05001                                                    + str(position) \
05002                                                    + " ; a bracket is " \
05003                                                    + "*/opened/*\n",
05004                                        case=debug.into_str_in_product)
05005 
05006                                 resulting_string += MARKUP['opening_bracket'] \
05007                                                  + couple[1].into_str(**options)
05008                                                  #+ MARKUP['closing_bracket']
05009 
05010                                 unclosed_bracket += 1
05011 
05012                                 if len(self.factor[i+1]) >= 2:
05013                                     debug.write("and " \
05014                                                             + "*/closed/*\n",
05015                                            case=debug.into_str_in_product)
05016                                     resulting_string += \
05017                                                       MARKUP['closing_bracket']
05018                                     unclosed_bracket -= 1
05019 
05020                             else:
05021                                 #DEBUG
05022                                 debug.write("\n[Product.into_str():n°3B] " \
05023                                                    + "processing " \
05024                                                    + "factor : "\
05025                                                    + couple[1].dbg_str()\
05026                                                    + "\nwith position " \
05027                                                    + "forced to " \
05028                                                    + str(position) \
05029                                                    + " ; NO brackets",
05030                                        case=debug.into_str_in_product)
05031 
05032                                 resulting_string +=                           \
05033                                                   couple[1].into_str( \
05034                                                        force_position=position,
05035                                                        **options)
05036 
05037                             position += 1
05038                             flag = True
05039 
05040             # All factors have been processed so far.
05041             # If flag is still False, nothing has been displayed (excepted
05042             # maybe an orphan -) and in this case, a "1" has to be
05043             # displayed.
05044             if not flag:
05045                 # If the product only contained plus ones, no sign has been
05046                 # displayed yet
05047                 if not expression_begins                                      \
05048                    and self.factor[0].is_displ_as_a_single_1():
05049                 #___
05050                     resulting_string += MARKUP['plus'] + MARKUP['one']
05051                     #expression_begins = False (useless !)
05052                 else:
05053                     resulting_string += MARKUP['one']
05054                     # If expression begins is not reset to False here,
05055                     # it is then possible to display something and still
05056                     # have it True !
05057                     expression_begins = False
05058 
05059             # Before leaving, maybe close a possibly left unclosed bracket
05060             if unclosed_bracket >= 1:
05061                 for i in range(unclosed_bracket):
05062                     #DEBUG
05063                     debug.write( "[Product.into_str()] "\
05064                               "\n[end of product]the bracket is */closed/*\n",
05065                            case=debug.into_str_in_product)
05066                     resulting_string += MARKUP['closing_bracket']
05067 
05068 
05069             # Displaying the product's exponent does not depends on the
05070             # compact or not compact displaying so this portion of code has
05071             # been written once for the two cases, somewhat farther, just
05072             # before the final return resulting_string.
05073 
05074 
05075             # end of the compact displaying section.
05076 
05077         # begining of the non compact displaying section
05078         else:
05079             # Non compact displaying :
05080             # All factors will be displayed,
05081             # All × signs will be displayed except the ones that are
05082             # especially specified not to be displayed in the
05083             # display_multiply_symbol list of the product
05084             # (unless they're required by conventional rules, like between
05085             # two numbers, for example)
05086             resulting_string = ""
05087 
05088             # First factor is displayed :
05089             if self.factor[0].requires_brackets(position)                            \
05090                and not len(self) == 1: # to avoid displaying a single Sum
05091                                        # with brackets around it
05092             #___
05093                 expression_begins = True
05094                 #DEBUG
05095                 debug.write("\n[Product.into_str():n°4A]× : " \
05096                                    + "processing " \
05097                                    + "1st factor : "\
05098                                    + self.factor[0].dbg_str()\
05099                                    + "\nwith position " \
05100                                    + "NOT forced to " \
05101                                    + str(position) \
05102                                    + " ; WITH brackets",
05103                        case=debug.into_str_in_product)
05104 
05105                 resulting_string += MARKUP['opening_bracket']    \
05106                                  + self.factor[0].into_str(**options) \
05107                                  + MARKUP['closing_bracket']
05108             else:
05109                 #DEBUG
05110                 debug.write("\n[Product.into_str():n°4B]× : " \
05111                                    + "processing " \
05112                                    + "1st factor : "\
05113                                    + self.factor[0].dbg_str()\
05114                                    + "\nwith position " \
05115                                    + "forced to " \
05116                                    + str(position) \
05117                                    + " ; NO brackets",
05118                        case=debug.into_str_in_product)
05119                 resulting_string += self.factor[0].into_str( \
05120                                                        force_position=position,
05121                                                              **options)
05122 
05123             #
05124             position += 1
05125 
05126             # If there are other factors, the × symbols are displayed
05127             # as well as the next factors
05128             if len(self) >= 2:
05129                 for i in range(len(self) - 1):
05130                     # /!\ The i-th is the last displayed, the (i+1)th is
05131                     # the current one
05132                     if self.factor[i].multiply_symbol_is_required(
05133                                                               self.factor[i+1],
05134                                                               i)              \
05135                        or self.info[i+1]:
05136                     #___
05137                         resulting_string += MARKUP['times']
05138                     else:
05139                         pass
05140                         #resulting_string += MARKUP['space']
05141 
05142                     expression_begins = True
05143 
05144                     if self.factor[i+1].requires_brackets(i+1):
05145                         # here it is not necessary to check if the brackets
05146                         # might be useless because in non compact display,
05147                         # if there are several factors they all will be
05148                         # displayed
05149                         #DEBUG
05150                         debug.write("\n[Product.into_str():n°5A]× : " \
05151                                            + "processing " \
05152                                            + "factor : "\
05153                                            + self.factor[i+1].dbg_str()\
05154                                            + "\nwith position " \
05155                                            + "NOT forced to " \
05156                                            + str(position) \
05157                                            + " ; WITH brackets",
05158                                            case=debug.into_str_in_product)
05159 
05160                         resulting_string += MARKUP['opening_bracket']         \
05161                                          + self.factor[i+1].into_str(**options)\
05162                                          + MARKUP['closing_bracket']
05163                     else:
05164                         #DEBUG
05165                         debug.write("\n[Product.into_str():n°5B]× : " \
05166                                            + "processing " \
05167                                            + "factor : "\
05168                                            + self.factor[i+1].dbg_str()\
05169                                            + "\nwith position " \
05170                                            + "forced to " \
05171                                            + str(position) \
05172                                            + " ; NO brackets",
05173                                            case=debug.into_str_in_product)
05174                         resulting_string += self.factor[i+1].into_str(
05175                                                        force_position=position,
05176                                                                      **options)
05177 
05178                     position += 1
05179 
05180         # Display of the possible product's exponent
05181         # and management of inner brackets (could be necessary because
05182         # of the exponent)
05183         if self.requires_inner_brackets():
05184             #DEBUG
05185             debug.write("\n[Product.into_str():n°6] " \
05186                             + "- wrapped in (inner) brackets\n",
05187                                    case=debug.into_str_in_product)
05188 
05189             resulting_string = MARKUP['opening_bracket']                      \
05190                                + resulting_string                             \
05191                                + MARKUP['closing_bracket']
05192 
05193         if self.exponent_must_be_displayed():
05194             expression_begins = True
05195             exponent_string = self.exponent.into_str(**options)
05196             #DEBUG
05197             debug.write("\n[Product.into_str():n°7] " \
05198                         + "- processing the exponent\n",
05199                                    case=debug.into_str_in_product)
05200 
05201             resulting_string += MARKUP['opening_exponent']                    \
05202                              + exponent_string                                \
05203                              + MARKUP['closing_exponent']
05204             expression_begins = False
05205 
05206         return resulting_string
05207 
05208 
05209 
05210 
05211 
05212     # --------------------------------------------------------------------------
05213     ##
05214     #   @brief Returns the next calculated step of a numeric Product
05215     #   @todo This method is only very partially implemented (see source code)
05216     #   @todo The way the exponents are handled is still to be decided
05217     #   @todo the inner '-' signs (±(-2)) are not handled by this method so far
05218     def calculate_next_step(self, **options):
05219         if not self.is_numeric() or isinstance(self, Monomial):
05220             return self.expand_and_reduce_next_step(**options)
05221 
05222         # general idea : check if the exponent is to calculate_next_step itself
05223         # if yes, replace it by self.exponent.calculate_next_step() in the
05224         # newly built object
05225         # then check if any of the factors is to be calculated_next_step itself
05226         # as well. if yes, rebuild the Product replacing any of these factors
05227         # by factor[i].calculate_next_step()  (maybe except Fractions which
05228         # can be simplified at a later step, which would be shorter and
05229         # more efficient...) BUT don't forget to put the exponent on the
05230         # numes & denos to make the next steps easier (for instance, at this
05231         # step, (4×(3/4)²)³ should become (4×{9/16})³ (or maybe distribute
05232         # the Product's exponent on the factors ??)
05233         # if any of the preceding calculations has been done, then return a
05234         # newly rebuilt Product
05235 
05236         # case of Products having several factors but None of them neither
05237         # its exponent is to be calculated
05238         # that can be : 2×3 | (2×3)² | 2×{3/4} | {5/2}×{4/15} etc.
05239         # but shouldn't be 7³×5 because 7³ would have already been replaced
05240         # by its value (343)
05241         # what has to be done is effectively calculate the Product
05242         # of its factors so that there's only one remaining :
05243 
05244         # CASE
05245         # Several factors (not to be calculated anymore)
05246         if len(self) >= 2:
05247             # Possibly cases : only Items | Items & Fractions | only Fractions
05248             # Plus, the 0-degree-Monomials are converted into Items
05249             nb_items = 0
05250             nb_minus_1 = 0
05251             nb_fractions = 0
05252 
05253             # Let's count how many items, fractions etc. there are here
05254             for i in range(len(self)):
05255                 # Is this content factorizable ?
05256                 if isinstance(self.factor[i], Item):
05257                     if not (self.factor[i].is_displ_as_a_single_1()      \
05258                             and self.compact_display
05259                            or
05260                            self.factor[i].is_displ_as_a_single_minus_1()):
05261                     #___
05262                         nb_items += 1
05263                     else:
05264                         if self.factor[i].is_displ_as_a_single_minus_1():
05265                             nb_minus_1 += 1
05266 
05267                 if isinstance(self.factor[i], Monomial) \
05268                    and self.factor[i].is_numeric() \
05269                    and isinstance(self.factor[i][0], Item):
05270                 #___
05271                     if not (self.factor[i].is_displ_as_a_single_1()      \
05272                             and self.compact_display
05273                            or
05274                            self.factor[i].is_displ_as_a_single_minus_1()):
05275                     #___
05276                         nb_items += 1
05277                         self.factor[i] = Item(self.factor[i][0])
05278                     else:
05279                         if self.factor[i].is_displ_as_a_single_minus_1():
05280                             self.factor[i] = Item(-1)
05281                             nb_minus_1 += 1
05282 
05283 
05284                 if isinstance(self.factor[i], Fraction):
05285                     if not (self.factor[i].is_displ_as_a_single_1()      \
05286                            or
05287                            self.factor[i].is_displ_as_a_single_minus_1()):
05288                     #___
05289                         nb_fractions += 1
05290                     else:
05291                         if self.factor[i].is_displ_as_a_single_minus_1():
05292                             nb_minus_1 += 1
05293 
05294             # Now let's check if...
05295 
05296             # 1st
05297             # There are only Items :
05298             if nb_fractions == 0 and nb_items >= 1:
05299                 return Product([Item(self.evaluate(stop_recursion=True))])
05300 
05301             # 2d
05302             # There is at least one Fraction & one Item (not equivalent to a
05303             # single ±1)
05304             # THIS IS PARTIALLY IMPLEMENTED : negative Fractions and
05305             # exponented Fractions are not being handled at all ; the case of
05306             # equivalent-to-±1 Items is not being handled neither
05307             elif nb_fractions >= 1 and nb_items >= 1:
05308                 nume_list = []
05309                 deno_list = []
05310 
05311                 for i in range(len(self)):
05312                     if isinstance(self[i], Item):
05313                         nume_list += [self[i]]
05314                     elif isinstance(self[i], Fraction):
05315                         nume_list += [self[i].numerator]
05316                         deno_list += [self[i].denominator]
05317 
05318                 return Fraction((Product(nume_list), Product(deno_list)))
05319 
05320 
05321             # 3d
05322             # There are at least two Fractions (& maybe Items that are
05323             # equivalent to a single ±1)
05324             elif nb_fractions >= 2 and nb_items == 0:
05325                 resulting_sign_list = [] # useless initializations
05326                 resulting_nume_list = []
05327                 resulting_deno_list = []
05328                 possibly_items_list = []
05329 
05330                 for i in range(len(self)):
05331                     if isinstance(self.factor[i], Fraction)                   \
05332                        and not (self.factor[i].is_displ_as_a_single_1()  \
05333                                or
05334                            self.factor[i].is_displ_as_a_single_minus_1()):
05335                     #___
05336                         # don't forget possibly exponents here !
05337                         # (not implemented yet...)
05338                         # THEY SHOULD BE TREATED EITHER EARLIER (1st CASE)
05339                         # OR LATER...
05340                         resulting_sign_list.append(self.factor[i])
05341                         for j in range(len(self.factor[i].numerator.factor)):
05342                             item_to_add = Item( \
05343                                             self.factor[i].numerator.factor[j])
05344                             item_to_add.set_sign('+')
05345                             resulting_nume_list.append(item_to_add)
05346 
05347                         #resulting_nume_list += self.factor[i].numerator.factor
05348                         for j in range(len(self.factor[ \
05349                                                        i].denominator.factor)):
05350                         #___
05351                             item_to_add = Item( \
05352                                           self.factor[i].denominator.factor[j])
05353                             item_to_add.set_sign('+')
05354                             resulting_deno_list.append(item_to_add)
05355                         #resulting_deno_list += \
05356                         #                     self.factor[i].denominator.factor
05357 
05358                     else: # there won't be checked if there are Items here !
05359                         possibly_items_list.append(self.factor[i])
05360 
05361                 resulting_sign = sign_of_product(resulting_sign_list)
05362                 resulting_nume = Product(resulting_nume_list)
05363                 resulting_nume.set_compact_display(False)
05364                 resulting_deno = Product(resulting_deno_list)
05365                 resulting_deno.set_compact_display(False)
05366 
05367                 resulting_fraction = Fraction((resulting_sign,
05368                                                resulting_nume,
05369                                                resulting_deno))
05370 
05371                 if len(possibly_items_list) == 0:
05372                     return resulting_fraction
05373                 else:
05374                     return Product(possibly_items_list + [resulting_fraction])
05375 
05376 
05377             # 4th
05378             # There is one Fraction (and two subcases : with an Item equivalent
05379             # to a single -1 OR without such an Item)
05380             # Note that the cases "with Item(s) equivalent to a single 1" and
05381             # "with several Items equivalent to a single -1" should have been
05382             # handled before (2d case of this list)
05383             elif nb_fractions == 1 and nb_minus_1 == 1:
05384                 # Let's get this fraction...
05385                 the_fraction = None
05386                 for i in range(len(self)):
05387                     if isinstance(self.factor[i], Fraction):
05388                         the_fraction = Fraction(self.factor[i])
05389 
05390                 if the_fraction.calculate_next_step(**options) is None:
05391                     return None
05392 
05393                 else:
05394                      return Product([Item(-1),
05395                                    the_fraction.calculate_next_step(**options)])
05396 
05397 
05398             # 5th
05399             # There is one Fraction (and remaining ones...)
05400             # (should never happen)
05401             elif nb_fractions == 1 and nb_minus_1 == 0 and nb_items == 0:
05402                 # Let's get this fraction...
05403                 the_fraction = None
05404                 for i in range(len(self)):
05405                     if isinstance(self.factor[i], Fraction):
05406                         the_fraction = Fraction(self.factor[i])
05407 
05408                 return the_fraction.calculate_next_step(**options)
05409 
05410             # 6th
05411             # There is nothing ?? (could that happen ?)
05412             # (or if there's only equivalent to ±1 objects... ?)
05413 
05414 
05415         # in the case of Products having only one factor (that does not have
05416         # to be calculated : which implies the factor's exponent is 1)
05417         # put the product's exponent on the factor[0]'s exponent and
05418         # return factor[0].calculate_next_step()
05419         # so that in the case of a Product where factor[0] = Item(3)
05420         # and exponent = 4, it will return Item(81) ;
05421         # and in the case of a Product where factor[0] = Item(5)
05422         # and exponent = 1, it will return None
05423         # (in this case the result is simply recursively delegated to Item
05424         # or Fraction etc.)
05425 
05426         # CASE
05427         # Only one remaining factor
05428         elif len(self) == 1:
05429             if isinstance(self.factor[0], Item):
05430                 new_item = Item(self.factor[0])
05431                 new_item.set_exponent(self.exponent * new_item.exponent)
05432                 if is_even(new_item.exponent):
05433                     new_item.set_sign('+')
05434                 return new_item.calculate_next_step(**options)
05435 
05436             elif isinstance(self.factor[0], Fraction):
05437                 new_fraction = Fraction(self.factor[0])
05438                 new_fraction.set_exponent(self.exponent \
05439                                           * new_fraction.exponent)
05440                 if is_even(new_fraction.exponent):
05441                     new_fraction.set_sign('+')
05442                 return new_fraction.calculate_next_step(**options)
05443 
05444             elif isinstance(self.factor[0], Sum):
05445                 new_sum = self.factor[0].calculate_next_step(**options)
05446                 if new_sum != None:
05447                     return Product([new_sum])
05448                 else:
05449                     return None
05450 
05451 
05452             # add here the cases of a Sum, of a Product (do that recursively
05453             # although... well there shouldn't be a Product still there nor
05454             # a Sum) + cases of Quotient|Fraction
05455 
05456 
05457 
05458 
05459 
05460 
05461 
05462 
05463     # --------------------------------------------------------------------------
05464     ##
05465     #   @brief Returns the next step of reduction of the Product
05466     #   It won't check if it is expandable. Either it IS and the object is not
05467     #   just a Product but an Expandable or it isn't.
05468     #   @return Exponented
05469     def expand_and_reduce_next_step(self, **options):
05470         #DEBUG
05471         debug.write("\n[expand_and_reduce_next_step_product] : entered\n" \
05472                     + "[expand_and_reduce_next_step_product] : Current Product is : " \
05473                     + self.dbg_str(),
05474                     case=debug.expand_and_reduce_next_step_product)
05475 
05476 
05477         if type(self) == BinomialIdentity:
05478             return self.expand()
05479 
05480         if self.is_numeric() and not isinstance(self, Monomial):
05481             #DEBUG
05482             debug.write("\n[expand_and_reduce_next_step_product] " \
05483                         + "Exiting and calling calculate_next_step on self\n",
05484                        case=debug.expand_and_reduce_next_step_product)
05485             return self.calculate_next_step(**options)
05486 
05487         if isinstance(self, Monomial):
05488             #DEBUG
05489             debug.write("\n[expand_and_reduce_next_step_product] " \
05490                        + "Exiting and returning None\n",
05491                        case=debug.expand_and_reduce_next_step_product)
05492             return None
05493 
05494         copy = Product(self)
05495 
05496         a_factor_at_least_has_been_modified = False
05497 
05498         # check if any of the factors needs to be reduced
05499         for i in range(len(copy)):
05500             test = copy.factor[i].expand_and_reduce_next_step(**options)
05501             if test != None:
05502                 if isinstance(test, CommutativeOperation) and len(test) == 1:
05503                     # in order to depack useless 1-element CommutativeOperations...
05504                     copy.set_element(i, test[0])
05505                 else:
05506                     copy.set_element(i, test)
05507                 a_factor_at_least_has_been_modified = True
05508 
05509         if a_factor_at_least_has_been_modified:
05510             # we should now return the copy ; but to avoid special
05511             # problematic cases, let's check and possibly modify something
05512             # first (the case of copy being like <{-1}, <{9}, {x^2}>> would
05513             # produce a new step, later, transformed in <{-9}, {x^2}> what
05514             # will be displayed though the user won't see any difference in
05515             # compact display mode)
05516             if len(copy) >= 2 \
05517                and copy.factor[0].is_displ_as_a_single_minus_1():
05518             #___
05519                 if isinstance(copy.factor[1], Product) \
05520                    and copy.factor[1].exponent.is_displ_as_a_single_1() \
05521                    and copy.factor[1].get_first_factor().is_positive():
05522                 #___
05523                     new_copy = Product(copy)
05524                     new_copy.reset_element()
05525                     new_first_factor = Product([Item(-1), copy.factor[1].
05526                                                       get_first_factor()])
05527                     new_first_factor = new_first_factor.reduce_()
05528                     new_copy.element.append(new_first_factor)
05529                     for i in range(len(copy.factor[1]) - 1):
05530                         new_copy.element.append(copy.factor[1].factor[i + 1])
05531                     for i in range(len(copy.factor) - 2):
05532                         new_copy.element.append(copy.factor[i + 2])
05533                     copy = new_copy
05534             return copy
05535 
05536         # no factor of the Product needs to be reduced
05537         else:
05538             #DEBUG
05539             debug.write("\n[expand_and_reduce_next_step_product] " \
05540                        + "No factor has been modified\n",
05541                        case=debug.expand_and_reduce_next_step_product)
05542             if self.is_reducible():
05543                 #self.set_compact_display(True)
05544                 debug.write("\n[expand_and_reduce_next_step_product] " \
05545                            + "self is reducible, returning self.reduce_\n",
05546                            case=debug.expand_and_reduce_next_step_product)
05547                 return self.reduce_()
05548 
05549             # this next test let Products like 2×1 (which are not considered
05550             # as reducible) be reduced at the end of the calculation
05551             #elif self.is_numeric() and len(self) >= 2 \
05552             #     and not self.compact_display:
05553             #___
05554              #   return self.throw_away_the_neutrals()
05555 
05556             else:
05557                 #self.set_compact_display(True)
05558                 debug.write("\n[expand_and_reduce_next_step_product] " \
05559                            + "self is not reducible, returning None\n",
05560                            case=debug.expand_and_reduce_next_step_product)
05561                 return None
05562 
05563 
05564 
05565 
05566 
05567 
05568     # --------------------------------------------------------------------------
05569     ##
05570     #   @brief Compares two Products
05571     #   Returns 0 if all factors are the same in the same order and if the
05572     #   exponents are also the same
05573     #   /!\ a × b will be different from b × a
05574     #   It's not a mathematical comparison, but a "displayable"'s one.
05575     #   @return True if all factors are the same in the same order & the exponent
05576     def __eq__(self, objct):
05577         if not isinstance(objct, Product):
05578             return False
05579 
05580         if len(self) != len(objct):
05581             return False
05582 
05583         for i in range(len(self)):
05584             if self.factor[i] != objct.factor[i]:
05585                 return False
05586 
05587         if self.exponent != objct.exponent:
05588             return False
05589 
05590         return True
05591 
05592 
05593 
05594 
05595 
05596     # --------------------------------------------------------------------------
05597     ##
05598     #   @brief Makes Products hashable (so, usable as dictionnary keys)
05599     def __hash__(self):
05600         return hash(self.symbol + str(self.sign) \
05601                     + ''.join([elt.dbg_str() for elt in self.element])\
05602                     + self.exponent.dbg_str()
05603                    )
05604 
05605 
05606 
05607 
05608 
05609     # --------------------------------------------------------------------------
05610     ##
05611     #   @brief Defines the performed CommutativeOperation as a Product
05612     def operator(self, arg1, arg2):
05613         return arg1 * arg2
05614 
05615 
05616 
05617 
05618 
05619     # --------------------------------------------------------------------------
05620     ##
05621     #   @brief True if the usual writing rules require a × between two factors
05622     #   @param objct The other one
05623     #   @param position The position (integer) of self in the Product
05624     #   @todo   check Why in source code
05625     #   @return True if the writing rules require × between self & obj
05626     def multiply_symbol_is_required(self, objct, position):
05627         next_to_last = len(self) - 1
05628         # 1st CASE : Product × Item
05629         if isinstance(objct, Item):
05630             return self.factor[next_to_last].multiply_symbol_is_required(objct,
05631                                                                       position)
05632 
05633         # 2d CASE : Product × Product
05634         if isinstance(objct, Product):
05635             return self.factor[next_to_last].multiply_symbol_is_required(     \
05636                                                                objct.factor[0],
05637                                                                position)
05638 
05639         # 3d CASE : Product × Sum
05640         if isinstance(objct, Sum):
05641             if len(objct) == 1:
05642                 return self.multiply_symbol_is_required(objct.term[0],
05643                                                         position)
05644             else:
05645                 # Why factor[0] and not factor[next_to_last] ?
05646                 return self.factor[0].multiply_symbol_is_required(objct,
05647                                                                   position)
05648 
05649         # 4th CASE : Product × Quotient
05650         if isinstance(objct, Quotient):
05651             return True
05652 
05653 
05654 
05655 
05656 
05657     # --------------------------------------------------------------------------
05658     ##
05659     #   @brief True if (one)self requires brackets inside of a Product.
05660     #   For instance, a Sum with several terms or a negative Item would.
05661     #   @param position The position of the object in the Product
05662     #   @return True if the object requires brackets in a Product
05663     def requires_brackets(self, position):
05664         # If the exponent is equal or equivalent to 1
05665         if self.exponent.is_displ_as_a_single_1():
05666             # Either there's only one displayable factor and then
05667             # putting it in brackets depends on what it is...
05668             self_without_ones = self.throw_away_the_neutrals()
05669 
05670             if len(self_without_ones) == 1:
05671                 return self_without_ones.factor[0].requires_brackets(position)
05672             # Or there are several factors and then it doesn't require
05673             # brackets : the exact positions of any brackets inside of a
05674             # Product are determined in into_str() ; for instance,
05675             # if you want to display 9×(-2x)×4x, where (-2x)×4x is a compact
05676             # Product, you need to put brackets INSIDE it (only wrapping to
05677             # factors, not the whole of it). No, not outside.
05678             else:
05679                 return False
05680 
05681         # If the exponent is different from one, then the brackets are
05682         # always useless. Take care that here we manage the "external"
05683         # brackets, not the inner ones. Here is told that (ab)² doesn't
05684         # require brackets i.e. shouldn't be displayed like that :
05685         # ((ab)²).
05686         # The inner brackets (the one around ab and meaning the squared
05687         # influences the entire ab product) are managed in
05688         # requires_inner_brackets()
05689         else:
05690             return False
05691 
05692 
05693 
05694 
05695 
05696     # --------------------------------------------------------------------------
05697     ##
05698     #   @brief True if the argument requires inner brackets
05699     #   The reason for requiring them is having an exponent different
05700     #   from 1 and several terms or factors (in the case of Products & Sums)
05701     #   @return True if the object requires inner brackets
05702     def requires_inner_brackets(self):
05703         #DEBUG
05704         debug.write("\nEntering Product.requires_inner_brackets()\n",
05705                                case=debug.requires_inner_brackets_in_product)
05706 
05707         if self.exponent_must_be_displayed():
05708 
05709             #DEBUG
05710             debug.write("\nProduct.requires_inner_brackets() : the exponent" \
05711                         " should be displayed\n",
05712                                case=debug.requires_inner_brackets_in_product)
05713 
05714             compacted_self = Product(self)
05715             compacted_self = compacted_self.throw_away_the_neutrals()
05716 
05717             if len(compacted_self) == 1:
05718 
05719                 #DEBUG
05720                 debug.write("\nProduct.requires_inner_brackets() : len(comp" \
05721                             "acted_self is 1\n",
05722                                case=debug.requires_inner_brackets_in_product)
05723 
05724                 if compacted_self.get_sign() == '+' \
05725                    and \
05726                    not compacted_self.factor[0].exponent_must_be_displayed() \
05727                    and \
05728                    not (compacted_self.exponent_must_be_displayed() \
05729                         and len(compacted_self.factor[0]) >= 2):
05730                 #___
05731                     return False
05732                 else:
05733                     return True
05734 
05735             # this case is when there are several factors (at least two
05736             # factors not equivalent to a single 1)
05737             else:
05738                 return True
05739 
05740         else:
05741             return False
05742 
05743 
05744 
05745 
05746     # --------------------------------------------------------------------------
05747     ##
05748     #   @brief Returns the Product once put in order
05749     def order(self):
05750         num_factors = self.get_factors_list(NUMERIC)
05751 
05752         literal_factors = self.get_factors_list(LITERALS)
05753         #python2.7 code : literal_factors.sort(Exponented.alphabetical_order_cmp)
05754         sorted(literal_factors)
05755 
05756         other_factors = self.get_factors_list(OTHERS)
05757 
05758         return Product(num_factors + literal_factors + other_factors)
05759 
05760 
05761 
05762 
05763 
05764     # --------------------------------------------------------------------------
05765     ##
05766     #   @brief Return a reduced Product (if possible)
05767     #   For instance, giving this Product :
05768     #   2x × (-4x²) × (x + 3)³ × 5 × (x²)³ × (-1)² × (2×3)²,
05769     #   reduce_() would return :
05770     #   -1440 * x⁹ * (x + 3)³
05771     def reduce_(self):
05772         # Get each kind of factors possible (numeric, literals, others like
05773         # Sums of more than one term)
05774 
05775         #DEBUG
05776         debug.write("\n[reduce_ in Product] Entered\n" \
05777                     + "[reduce_ in Product] Current Product is : " \
05778                     + self.dbg_str() + "\n",
05779                     case=debug.reduce__product)
05780 
05781         # So, numeric factors :
05782         numeric_part = Product(self.get_factors_list(NUMERIC)).evaluate()
05783 
05784         #DEBUG
05785         debug.write("[reduce_ in Product] numeric part found is : " \
05786                     + str(numeric_part) + "\n",
05787                     case=debug.reduce__product)
05788 
05789         # Literal factors :
05790         raw_literals_list = self.get_factors_list(LITERALS)
05791         literals_list = reduce_literal_items_product(raw_literals_list)
05792         #python2.7 code: literals_list.sort(Exponented.alphabetical_order_cmp)
05793         sorted(literals_list)
05794 
05795 
05796         # Determine the sign
05797         final_sign = sign_of_product([numeric_part] + raw_literals_list)
05798 
05799         if numeric_part >= 0:
05800            numeric_item = Item((final_sign, numeric_part, 1))
05801         else:
05802            numeric_item = Item((final_sign, -1*numeric_part, 1))
05803 
05804         # Other factors
05805         others_list = self.get_factors_list(OTHERS)
05806 
05807         # Reassemble the different parts and return it
05808         if numeric_item.is_displ_as_a_single_0():
05809             return Product([Item(0)])
05810         else:
05811             return Product([numeric_item] + literals_list + others_list)
05812 
05813 
05814 
05815 
05816 
05817     # --------------------------------------------------------------------------
05818     ##
05819     #   @brief True if any of the factors is null
05820     def is_null(self):
05821         for elt in self.element:
05822             if elt.is_null():
05823                 return True
05824 
05825         return False
05826 
05827 
05828 
05829 
05830 
05831     # --------------------------------------------------------------------------
05832     ##
05833     #   @brief True if the Product contains only single 1s. For instance, 1×1×1
05834     def is_displ_as_a_single_1(self):
05835 
05836         # Why is there this difference between Sums & Products ??
05837         if not self.exponent.is_displ_as_a_single_1():
05838             return False
05839 
05840         if len(self) == 1:
05841             return self.factor[0].is_displ_as_a_single_1()
05842 
05843         return self.is_displ_as_a_single_neutral(Item(1))
05844 
05845 
05846 
05847 
05848 
05849     # --------------------------------------------------------------------------
05850     ##
05851     #   @brief True if the Product can be displayed as a single -1
05852     # For instance, the Product 1×1×(-1)×1
05853     def is_displ_as_a_single_minus_1(self):
05854         if not self.exponent.is_displ_as_a_single_1():
05855             return False
05856 
05857         a_factor_different_from_1_and_minus1_has_been_found = False
05858         equivalent_to_minus1_nb_factors = 0
05859 
05860         for factor in self.element:
05861             if factor.is_displ_as_a_single_minus_1():
05862                 equivalent_to_minus1_nb_factors += 1
05863             elif not factor.is_displ_as_a_single_1():
05864                 a_factor_different_from_1_and_minus1_has_been_found = True
05865 
05866         if a_factor_different_from_1_and_minus1_has_been_found:
05867             return False
05868         elif equivalent_to_minus1_nb_factors == 1:
05869             return True
05870         else:
05871             return False
05872 
05873 
05874 
05875 
05876 
05877     # --------------------------------------------------------------------------
05878     ##
05879     #   @brief True if the object can be DISPLAYED as a single 0
05880     # For instance, the Product 0×0×0×0 (but NOT 0×1)
05881     def is_displ_as_a_single_0(self):
05882         answer = True
05883 
05884         for factor in self.element:
05885             answer = answer and factor.is_displ_as_a_single_0()
05886 
05887         return answer
05888 
05889 
05890 
05891 
05892 
05893 
05894     # --------------------------------------------------------------------------
05895     ##
05896     #   @brief True if the Product is reducible
05897     #   This is based on the result of the get_factors_list method
05898     #   @return True|False
05899     #   @todo check the comment in the code
05900     #   Fix the problem bound to the get_factors list not giving - signs
05901     #   of literals as -1 in the list of numerics.
05902     def is_reducible(self):
05903         if self.is_displ_as_a_single_0() \
05904            or self.is_displ_as_a_single_1() \
05905            or self.is_displ_as_a_single_minus_1():
05906         #___
05907             # DEBUG
05908             debug.write("[Product.is_reducible() returning False] - A\n",
05909                         case=debug.product_is_reducible)
05910 
05911             return False
05912 
05913         # That's a fix about the exponent of the Product itself which won't
05914         # get counted when creating the numerics list below... only the
05915         # exponents of the factors themselves will be reported in this list.
05916         # Note : don't change the get_factors_list() before thinking about
05917         # why it doesn't return the exponent of the Product × the one of
05918         # each factor : there may be a good reason for that.
05919         if not self.exponent.is_displ_as_a_single_1():
05920             # DEBUG
05921             debug.write("[Product.is_reducible() returning True] - B\n",
05922                         case=debug.product_is_reducible)
05923 
05924             return True
05925 
05926         # Check the numeric factors : if there are several, then, the Product
05927         # is reducible.
05928         # First of all, throwing away the ones will make the
05929         # job easier in the case of compact displayed Product
05930         if self.compact_display:
05931             # DEBUG
05932             debug.write("[Product.is_reducible()] Throw away the neutrals\n",
05933                         case=debug.product_is_reducible)
05934             test_product = self.throw_away_the_neutrals()
05935         else:
05936             # DEBUG
05937             debug.write("[Product.is_reducible()] Work on a copy of self\n",
05938                         case=debug.product_is_reducible)
05939             test_product = Product(self)
05940 
05941         # Note : maybe manage the non-compact-display Products another way ?
05942         # Does it make sense at all... ?
05943         # YES IT DOES !!! (otherwise, the 2×1 Product and 1×7x Product are
05944         # found as not reducible and are displayed so even after "reduction"
05945 
05946         numerics = test_product.get_factors_list(NUMERIC)
05947 
05948         # DEBUG
05949         debug.write("[Product.is_reducible()] len(numerics) == " \
05950                     + str(len(numerics)) + "\n",
05951                         case=debug.product_is_reducible)
05952 
05953         # If there are two numeric factors left, then the Product is reducible
05954         # (they are both different from one if Product is compact_displayable):
05955         if len(numerics) >= 2:
05956             # DEBUG
05957             debug.write("[Product.is_reducible() returning True] - C\n",
05958                         case=debug.product_is_reducible)
05959 
05960             return True
05961 
05962         # If there is only one numeric factor left, it can be either
05963         # - a one, which means either it's the only factor of the Product
05964         #   (and that the Product is therefore not reducible)
05965         #   or that there was no other numeric factor & that it has been
05966         #   added to the list by get_factors_list() so we can't say
05967         #   anything in this case so far, so just don't !
05968         #if len(numerics) == 1 and is_.equivalent_to_a_single_1(numerics[0]):
05969           #  return False
05970 
05971         # - or another number. If its exponent is different from 1, then
05972         #   it can be reduced :
05973         if len(numerics) == 1 \
05974            and (not numerics[0].exponent.is_displ_as_a_single_1() \
05975                 or numerics[0].is_displ_as_a_single_0()):
05976         #___
05977             # DEBUG
05978             debug.write("[Product.is_reducible() returning True] - D\n",
05979                         case=debug.product_is_reducible)
05980 
05981             return True
05982 
05983         # finally if this factor is the only one of the Product, then
05984         # it is not reducible
05985         elif len(numerics) == 1 and len(test_product) == 1:
05986             # DEBUG
05987             debug.write("[Product.is_reducible() returning False] - E\n",
05988                         case=debug.product_is_reducible)
05989 
05990             return False
05991 
05992         # If the remaining number is not a one and has an exponent equivalent
05993         # to one, and is not the only factor of the Product, then the result
05994         # depends on the literals. If there's no remaining number, then it
05995         # depends on the literals as well.
05996 
05997         # Let's check the literals...
05998         literals = self.get_factors_list(LITERALS)
05999         aux_lexicon = dict()
06000 
06001         for element in literals:
06002             put_term_in_lexicon(element, Item(1), aux_lexicon)
06003 
06004         # If the same literal has been found several times, then the Product
06005         # is reducible (Caution, x and x² are of course not the same literals)
06006         for key in aux_lexicon:
06007             if len(aux_lexicon[key]) >= 2:
06008                 # DEBUG
06009                 debug.write("[Product.is_reducible() returning True] - F\n",
06010                             case=debug.product_is_reducible)
06011 
06012                 return True
06013 
06014         # Now we almost know that the literals are reduced. In fact, the
06015         # get_factors_list() method doesn't return the minus 1 if they're
06016         # "stuck" in the sign of a literal. For instance, the factors' list of
06017         # a×(-b) would be [Item(1)] only. The - sign before the b will be
06018         # managed in the literal factors' list. So. Let's check if any - sign
06019         # remains there before asserting the literals are reduced.
06020         for i in range(len(literals)):
06021             # this next test is to avoid the cases of a literal in first
06022             # position having a '-' sign (doesn't need to be reduced then)
06023             # e.g. -ab ; but if the first factor is numeric then we know that
06024             # the first literal doesn't come first (so if it has a '-' sign,
06025             # then the Product has to be reduced)
06026             if test_product.factor[0].is_numeric() \
06027                or (test_product.factor[0].is_literal() and i != 0):
06028             #___
06029                 if literals[i].sign == '-':
06030                 #___
06031                     # DEBUG
06032                     debug.write(\
06033                                "[Product.is_reducible() returning True] - G\n",
06034                                 case=debug.product_is_reducible)
06035 
06036                     return True
06037 
06038         # Now we know that the literals are reduced.
06039 
06040         # Let's finally check if the order is right, i.e. that the possibly
06041         # numeric comes first
06042         # It means that neither ab×3 nor a×3b are accepted as reduced !
06043 
06044         # First, if there were no numeric factors (and the literals reduced),
06045         # as we don't have a rule to reduce the OTHERS kinds of factors,
06046         # we can consider that the Product can't be reduced.
06047         if len(numerics) == 0:
06048             # DEBUG
06049             debug.write("[Product.is_reducible() returning False] - H\n",
06050                         case=debug.product_is_reducible)
06051 
06052             return False
06053         # Second, if there is one number left, let's check if it appears
06054         # first in the Product
06055         elif len(numerics) == 1:
06056             if test_product.get_first_factor().is_numeric():
06057                 # DEBUG
06058                 debug.write("[Product.is_reducible() returning False] - I\n",
06059                             case=debug.product_is_reducible)
06060 
06061                 return False
06062 
06063             elif numerics[0].is_displ_as_a_single_1() \
06064                  and self.compact_display:
06065             #___
06066                 debug.write("[Product.is_reducible() returning False] - J\n",
06067                                 case=debug.product_is_reducible)
06068                 return False
06069 
06070             else:
06071                 # DEBUG
06072                 debug.write("[Product.is_reducible() returning True] - K\n",
06073                             case=debug.product_is_reducible)
06074                 debug.write("numerics[0] = " + numerics[0].dbg_str(),
06075                             case=debug.product_is_reducible)
06076 
06077                 return True
06078 
06079         # This last return should be useless
06080         # DEBUG
06081         debug.write("[Product.is_reducible() returning False] - L\n",
06082                     case=debug.product_is_reducible)
06083 
06084         return False
06085 
06086 
06087 
06088 
06089 # ------------------------------------------------------------------------------
06090 # --------------------------------------------------------------------------
06091 # ------------------------------------------------------------------------------
06092 ##
06093 # @class Sum
06094 # @brief Has Exponented terms & an exponent. Iterable. Two display modes.
06095 class Sum (CommutativeOperation):
06096 
06097 
06098 
06099 
06100 
06101     # --------------------------------------------------------------------------
06102     ##
06103     #   @brief Constructor
06104     #   @warning Might raise an UncompatibleType exception.
06105     #   @param arg None|Sum|Number|String|Exponented|[Number|String|Exponented]
06106     #   In the case of the list, the Sums having an exponent equal to 1
06107     #   will be treated before the Exponenteds so that their terms are
06108     #   inserted in the current Sum instead of inserting a term as a
06109     #   Sum. If the exponent is greater than 1, then it will be the case.
06110     #   If the argument isn't of the kinds listed above, an exception will be
06111     #   raised.
06112     #   Giving None or an empty list is equivalent to giving 0
06113     #   @return One instance of Sum
06114     def __init__(self, arg):
06115         CommutativeOperation.__init__(self)
06116         # self._info is set to an empty list.
06117         # The flag 'compact_display' is let to True.
06118         # If this flag is set to True, no addition sign will be displayed.
06119         # If it is set to False, they all will be displayed, except the ones
06120         # which are mentioned not to be in the info list, aka info.
06121         # Example with compact_display=True : 2 - 3 + 5
06122         # Same with compact_display=False : (+2) + (-3) + (+5)
06123 
06124         self._symbol = '+'
06125 
06126         self._neutral = Item(0)
06127 
06128 
06129         # should this next flag be copied when creating a copy of a Sum ?
06130         self._force_inner_brackets_display = False
06131 
06132         self.str_openmark = "["
06133         self.str_closemark = "]"
06134 
06135         # 1st CASE : Sum
06136         if isinstance(arg, Sum):
06137             self._compact_display = arg.compact_display
06138             self._force_inner_brackets_display = \
06139                                                arg.force_inner_brackets_display
06140             self._exponent = arg.exponent.clone()
06141             for i in range(len(arg)):
06142                 self._element.append(arg.term[i].clone())
06143                 self._info.append(arg.info[i])
06144 
06145         # 2d CASE : Number
06146         elif is_.a_number(arg) or is_.a_string(arg):
06147             self._element.append(Item(arg))
06148             self._info.append(False)
06149 
06150         # 3d CASE : Exponented
06151         elif isinstance(arg, Exponented):
06152             self._element.append(arg.clone())
06153             self._info.append(False)
06154 
06155         # 4th CASE : [Numbers|Exponenteds]
06156         elif (type(arg) == list) and len(arg) >= 1:
06157             for i in range(len(arg)):
06158                 # The 1-exponent Sum are processed apart from all other
06159                 # Exponenteds
06160                 if isinstance(arg[i], Sum)                                    \
06161                    and arg[i].exponent.is_displ_as_a_single_1():
06162                 #___
06163                     for j in range(len(arg[i])):
06164                         self._element.append(arg[i].term[j].clone())
06165                         self._info.append(arg[i].info[j])
06166 
06167                 elif isinstance(arg[i], Exponented):
06168                     self._element.append(arg[i].clone())
06169                     self._info.append(False)
06170 
06171                 elif is_.a_number(arg[i]) or is_.a_string(arg[i]):
06172                     self._element.append(Item(arg[i]))
06173                     self._info.append(False)
06174 
06175                 else:
06176                     raise error.UncompatibleType(arg[i],
06177                                                  "This element from the "     \
06178                                                  + "provided list should have"\
06179                                                  + "been : Number|String|"    \
06180                                                  + "Exponented")
06181 
06182         # 5th CASE : None|[]
06183         elif arg is None or (type(arg) == list and len(arg) == 0):
06184             self._element.append(Item(0))
06185             self._info.append(False)
06186 
06187         # All other unforeseen cases : an exception is raised.
06188         else:
06189             raise error.UncompatibleType(arg,
06190                                          "Sum|Number|String|Exponented|"      \
06191                                          + "[Numbers|Strings|Exponenteds]")
06192 
06193 
06194     # --------------------------------------------------------------------------
06195     ##
06196     #   @brief Returns the number of negative factors of the Sum (i.e. 0)
06197     def get_minus_signs_nb(self):
06198         return 0
06199 
06200 
06201 
06202 
06203 
06204     # --------------------------------------------------------------------------
06205     ##
06206     #   @brief Returns a raw list of the numeric terms of the Sum
06207     def get_numeric_terms(self):
06208         numeric_terms_list = []
06209 
06210         for term in self.element:
06211             if isinstance(term, Sum):
06212                 numeric_terms_list = numeric_terms_list \
06213                                      + term.get_numeric_terms()
06214 
06215             elif term.is_numeric():
06216                 numeric_terms_list.append(term)
06217 
06218         return numeric_terms_list
06219 
06220 
06221 
06222 
06223 
06224     # --------------------------------------------------------------------------
06225     ##
06226     #   @brief Returns a raw list of the literal terms of the Sum
06227     def get_literal_terms(self):
06228         literal_terms_list = []
06229 
06230         for term in self.element:
06231             if isinstance(term, Sum):
06232                 literal_terms_list = literal_terms_list \
06233                                      + term.get_literal_terms()
06234 
06235             elif not term.is_numeric():
06236                 literal_terms_list.append(term)
06237 
06238         return literal_terms_list
06239 
06240 
06241 
06242 
06243 
06244     # --------------------------------------------------------------------------
06245     ##
06246     #   @brief Creates a dict. of couples (literal object):(numeric coeffs sum)
06247     #   Two objects are in fact created : a dictionary + an index which is a
06248     #   list containing the objects in the order they appear. The dictionary
06249     #   loses this order in general and the Sums created after that would never
06250     #   be in the same order without this index.
06251     #   Numeric Items are labeled NUMERIC in the index.
06252     #   For ex. giving the expression 6 + 2x² + 3x - 9 + x³ - 5x as an argument
06253     #   should return ({NUMERIC:(6-9), x²:2, x:(3-5), x³:1},
06254     #                  [NUMERIC, x², x, x³]).
06255     #   This method is fundamental to reduce Sums correctly, it is most
06256     #   important that it returns an object of the kind mentionned here above.
06257     #   @param provided_sum The Sum to examine...
06258     #   @return A tuple (dic, index).
06259     def get_terms_lexicon(self):
06260         # Here's the dictionary ("lexicon") to build and return
06261         lexi = {}
06262         # Here's the index
06263         index = []
06264 
06265         # GLANCE AT THE TERMS ONE AFTER THE OTHER
06266         for term in self.element:
06267             # IF THE i-TH TERM IS AN ITEM WHICH IS...
06268             # ... NUMERIC :
06269             if isinstance(term, Item) and term.is_numeric():
06270                 put_term_in_lexicon(NUMERIC, term, lexi)
06271                 if not NUMERIC in index:
06272                     index.append(NUMERIC)
06273 
06274             # ... LITERAL :
06275             elif isinstance(term, Item) and term.is_literal():
06276                 # First create the coefficient that will be put into the
06277                 # coeffs Sum i.e. either +1 or -1 depending on the sign
06278                 # of the Item
06279                 if term.is_positive():
06280                     associated_coeff = Item(1)
06281                 else:
06282                     associated_coeff = Item(-1)
06283 
06284                 # Create the Item "without sign" (this version is used
06285                 # as a key in the lexicon
06286                 positive_associated_item = Item(('+',
06287                                                  term.raw_value,
06288                                                  term.exponent))
06289 
06290                 # and put it in the lexicon :
06291                 put_term_in_lexicon(positive_associated_item,
06292                                     associated_coeff,
06293                                     lexi)
06294                 if not positive_associated_item in index:
06295                     index.append(positive_associated_item)
06296 
06297             # IF THE i-TH TERM IS A MONOMIAL :
06298             elif isinstance(term, Monomial):
06299                 if term.get_degree() == 0 or term.is_null():
06300                     put_term_in_lexicon(NUMERIC, term[0], lexi)
06301                     if not NUMERIC in index:
06302                         index.append(NUMERIC)
06303                 else:
06304                     put_term_in_lexicon(term[1], term[0], lexi)
06305                     if not term[1] in index:
06306                         index.append(term[1])
06307 
06308             # IF THE i-TH TERM IS A PRODUCT :
06309             elif isinstance(term, Product):
06310                 # first reduce it to make things clearer !
06311                 aux_product = term.reduce_()
06312                 # get the first Item of this reduced Product, it is sure that
06313                 # it is numeric because a Product reduction necessary creates
06314                 # a Product (numeric Item) × (possibly something else)
06315                 associated_coeff = aux_product.factor.pop(0)
06316 
06317                 # either there's nothing left in the remaining factors list
06318                 # which means the product only contained one numeric Item
06319                 # which has to be put in the right place of the lexicon
06320                 if len(aux_product.factor) == 0:
06321                     put_term_in_lexicon(NUMERIC, associated_coeff, lexi)
06322                     if not NUMERIC in index:
06323                         index.append(NUMERIC)
06324 
06325                 # or there's another factor left in the remaining list
06326                 # if it's literal, then it means that the Product was
06327                 # something like -5x, and so the Item -5 has to be added
06328                 # to the "x" key
06329                 # (the one which has been "poped" just somewhat above)
06330                 elif len(aux_product.factor) == 1                             \
06331                      and isinstance(aux_product.factor[0], Item)              \
06332                      and aux_product.factor[0].is_literal():
06333                 #___
06334                     put_term_in_lexicon(aux_product.factor[0],                \
06335                                         associated_coeff,                     \
06336                                         lexi)
06337                     if not aux_product.factor[0] in index:
06338                         index.append(aux_product.factor[0])
06339 
06340                 # in all other cases (several factors Product, or the 2d
06341                 # factor is a Sum etc.) the term is put at its place in
06342                 # the lexicon
06343                 else:
06344                     put_term_in_lexicon(aux_product, associated_coeff, lexi)
06345                     if not aux_product in index:
06346                         index.append(aux_product)
06347 
06348 
06349             # IF THE i-TH TERM IS A SUM :
06350             elif isinstance(term, Sum):
06351                 # If the exponent is different from 1, it is managed as a
06352                 # standard term (just check if a key already matches it
06353                 # and add +/- 1 as associated coeff)
06354                 # ex: the 2d term in : 2x + (x + 3)² + 5
06355                 # notice that the case of 2x + 7(x + 3)² + 5 would be managed
06356                 # in the "PRODUCT" section of this method
06357                 if term.exponent != Value(1):
06358                     associated_coeff = Item(1)
06359                     put_term_in_lexicon(term, associated_coeff, lexi)
06360 
06361                     if not term in index:
06362                         index.append(term)
06363 
06364                 else: # Case of a Sum having a exponent equal to 1
06365                       # and imbricated in the initial Sum : we get its
06366                       # lexicon&index recursively
06367                     lexi_index_tuple_to_add = term.get_terms_lexicon()
06368 
06369                     lexi_to_add = lexi_index_tuple_to_add[0]
06370                     index_to_add = lexi_index_tuple_to_add[1]
06371 
06372                     #___e content of this lexicon is added to the one
06373                     # we're building now
06374                     additional_keys = list()
06375                                      # this list let us collect the keys
06376                                      # that don't exist yet in the lexicon.
06377                                      # as the loop is on this lexicon, it is
06378                                      # not possible to add new keys while
06379                                      # we're reading it (otherwise we'll get a
06380                                      # run time error)
06381                     for suppl_key in index_to_add:
06382 
06383                         # what follows here looks like the put_term_in_lexicon
06384                         # method but difference is that there are maybe several
06385                         # coefficients to add for each key
06386                         added_key = False
06387 
06388                         for key in lexi:
06389                             if key == suppl_key:
06390                                 for objct in lexi_to_add[suppl_key].term:
06391                                     put_term_in_lexicon(key, objct, lexi)
06392                                     # nothing has to be added to the index
06393                                     # because all of these terms are already
06394                                     # in the lexicon
06395 
06396                                 added_key = True
06397 
06398                         if not added_key:
06399                             additional_keys.append(suppl_key)
06400 
06401                     # the keys that weren't in the lexicon yet have been saved
06402                     # in the list additional_keys, they are added now to the
06403                     # lexicon
06404                     # /!\ POSSIBLE BUG HERE !!!!! if any of these keys is many
06405                     # times there ? check if using put_term_in_lexicon
06406                     # wouldn't be better
06407                     # (--> or is this situation impossible ?)
06408                     for suppl_key in additional_keys:
06409                         lexi[suppl_key] = lexi_to_add[suppl_key]
06410                         index.append(suppl_key)
06411 
06412         return (lexi, index)
06413 
06414 
06415 
06416 
06417     # --------------------------------------------------------------------------
06418     ##
06419     #   @brief Gets the value of the force_inner_brackets_display field
06420     def get_force_inner_brackets_display(self):
06421         return self._force_inner_brackets_display
06422 
06423 
06424 
06425 
06426 
06427     term = property(CommutativeOperation.get_element,
06428                     doc = "To access the terms of the Sum.")
06429 
06430     force_inner_brackets_display = property(get_force_inner_brackets_display,
06431                     doc = "force_inner_brackets_display field of a Sum")
06432 
06433 
06434 
06435 
06436 
06437     # --------------------------------------------------------------------------
06438     ##
06439     #   @brief Sets the n-th term to the given arg
06440     def set_term(self, n, arg):
06441         self.set_element(n, arg)
06442 
06443 
06444 
06445 
06446     # --------------------------------------------------------------------------
06447     ##
06448     #   @brief Sets a value to the force_inner_brackets_display field
06449     #   @param arg Assumed to be True or False (not tested)
06450     def set_force_inner_brackets_display(self, arg):
06451         self._force_inner_brackets_display = arg
06452 
06453 
06454 
06455 
06456 
06457     # --------------------------------------------------------------------------
06458     ##
06459     #   @brief Creates a string of the given object in the given ML
06460     #   @param options Any options
06461     #   @return The formated string
06462     def into_str(self, **options):
06463         global expression_begins
06464 
06465         if 'force_expression_begins' in options \
06466            and options['force_expression_begins'] == True:
06467         #___
06468             expression_begins = options['force_expression_begins']
06469             options['force_expression_begins'] = False
06470 
06471         # Here are processed all Sum objects (including Polynomials etc.)
06472         # Displaying the + sign at the begining of an expression still depends
06473         # on the machine's expression_begins flag.
06474         # In a Sum, expression_begins can be reset to True for one good reason
06475         # at least : if inner brackets are required because of an exponent
06476         # different from 1
06477         resulting_string = ""
06478 
06479         # This flag checks if one term at least has been displayed
06480         # If it is still False at the end of this processing, a 0 will
06481         # be displayed.
06482         flag = False
06483 
06484         # If
06485         # - the exponent differs from 1 and if (the sum contains several
06486         #   terms or only one which is negative)
06487         # OR
06488         # - displaying inner brackets is being forced (needed in exercises
06489         #   like reducing 3x + 4 - (2x + 7) + (x + 1))
06490         # then
06491         # the Sum has to be displayed between inner brackets
06492         #DEBUG
06493         debug.write( \
06494             "\nEntering into_str in Sum : expression_begins = " \
06495             + str(expression_begins) \
06496             + "\nforce_inner_brackets_display = " \
06497             + str(self.force_inner_brackets_display),
06498             case=debug.into_str_in_sum)
06499 
06500         if ((not (self.exponent.is_displ_as_a_single_1()))               \
06501             and (len(self) >= 2                                           \
06502                  or                                                           \
06503                 (len(self) == 1 and self.term[0].get_sign() == '-')))     \
06504            or self.force_inner_brackets_display:
06505         #___
06506             if not expression_begins:
06507                 resulting_string += MARKUP['plus'] + MARKUP['opening_bracket']
06508             else:
06509                 resulting_string += MARKUP['opening_bracket']
06510 
06511             expression_begins = True
06512 
06513         # Compact_display's main loop
06514         # which will display the terms one after the other
06515         if self.compact_display:
06516             #DEBUG
06517             debug.write( \
06518                 "\nIn into_str in Sum [compact_display] : expression_begins = " \
06519                 + str(expression_begins) \
06520                 + "\nforce_inner_brackets_display = " \
06521                 + str(self.force_inner_brackets_display),
06522                 case=debug.into_str_in_sum)
06523             for i in range(len(self)):
06524                 # compact_display : zeros won't be displayed
06525                 if not self.term[i].is_displ_as_a_single_0():
06526                     resulting_string += self.term[i].into_str(**options)
06527                     flag = True
06528                     next_term_nb = self.next_displayable_term_nb(i)
06529                     expression_begins = False
06530 
06531                     #DEBUG
06532                     if debug.ENABLED and debug.into_str_in_sum:
06533                         if next_term_nb is None:
06534                             debug.write( \
06535                                 "\nIn into_str in Sum : " \
06536                                 + "no next term to display.",
06537                             case=debug.into_str_in_sum)
06538                         else:
06539                              debug.write( \
06540                                 "\nIn into_str in Sum : " \
06541                                 + "the next term to display is " \
06542                                 + self.term[next_term_nb].dbg_str(),
06543                             case=debug.into_str_in_sum)
06544 
06545                     if next_term_nb != None                                   \
06546                        and (                                                  \
06547                        self.term[next_term_nb].requires_inner_brackets()      \
06548                             or (isinstance(self.term[next_term_nb], Product)  \
06549                                 and                                           \
06550           (self.term[next_term_nb].factor[0].requires_brackets(0)\
06551            or self.term[next_term_nb].factor[0].is_displ_as_a_single_0()\
06552            )                    and not \
06553           (len(self.term[next_term_nb].throw_away_the_neutrals()) == 1 \
06554            and self.term[next_term_nb].throw_away_the_neutrals() \
06555                                       .factor[0].requires_brackets(0)
06556            )
06557                                 )
06558                             or (isinstance(self.term[next_term_nb], Sum)       \
06559                                 and \
06560           (self.term[next_term_nb].term[0].requires_inner_brackets()) \
06561                                )
06562                             ):
06563                     #___
06564                         #DEBUG
06565                         debug.write( \
06566                             "\nIn into_str in Sum : " \
06567                             + "adding a + for the next term in case it can't"\
06568                             + " do that itself.\n",
06569                             case=debug.into_str_in_sum)
06570 
06571                          #\
06572                          #   + "Tests results : \n" \
06573                          #   + "self.term[next_term_nb]." \
06574                          #   + "requires_inner_brackets()" + " returned " \
06575                          #   + str(self.term[next_term_nb].\
06576                          #         requires_inner_brackets()) \
06577                          #   + "\nself.term[next_term_nb].factor[0]" \
06578                          #   + ".requires_brackets(0) returned " \
06579                          #   + str(self.term[next_term_nb].\
06580                          #         factor[0].requires_brackets(0)) \
06581                          #   + "\nself.term[next_term_nb].factor[0]." \
06582                          #   + "is_displ_as_a_single_0() returned " \
06583                          #   + str(self.term[next_term_nb].\
06584                          #         factor[0].is_displ_as_a_single_0())
06585 
06586                         resulting_string += MARKUP['plus']
06587                         expression_begins = True
06588 
06589         # Not compact_display's main loop
06590         else:
06591             #DEBUG
06592             debug.write( \
06593                 "\nIn into_str in Sum [not compact_display] :" \
06594                 + " expression_begins = " \
06595                 + str(expression_begins) \
06596                 + "\nforce_inner_brackets_display = " \
06597                 + str(self.force_inner_brackets_display),
06598                 case=debug.into_str_in_sum)
06599             for i in range(len(self)):
06600                 if self.info[i]                           \
06601                    and not self.term[i].requires_inner_brackets()             \
06602                    and not self.term[i].is_displ_as_a_single_0():
06603                 #___
06604                     resulting_string += MARKUP['opening_bracket']             \
06605                                      + self.term[i].into_str(
06606                                                       force_display_sign='ok',\
06607                                                       **options)              \
06608                                      + MARKUP['closing_bracket']
06609                     flag = True
06610 
06611                 elif not self.term[i].is_displ_as_a_single_0():
06612                     resulting_string += self.term[i].into_str(**options)
06613                     flag = True
06614 
06615                 # A "+" is finally added in the case of another term left
06616                 # to be complete-writing displayed
06617                 next_term_nb = self.next_displayable_term_nb(i)
06618 
06619                 if next_term_nb != None                                   \
06620                    and self.info[next_term_nb]:
06621                 #___
06622                     resulting_string += MARKUP['plus']
06623                     expression_begins = True
06624 
06625         # if nothing has been displayed, a default 0 is displayed :
06626         if flag == False:
06627             resulting_string += MARKUP['zero']
06628 
06629         # if the sum's exponent differs from 1 and (the sum contains
06630         # several terms or only one negative term),
06631         # then the bracket earlier opened has to be shut
06632         if ((not (self.exponent.is_displ_as_a_single_1()))               \
06633             and (len(self) >= 2                                           \
06634                  or                                                           \
06635                 (len(self) == 1 and self.term[0].get_sign() == '-')))     \
06636            or self.force_inner_brackets_display:
06637         #___
06638             resulting_string += MARKUP['closing_bracket']
06639 
06640         if self.exponent_must_be_displayed():
06641             expression_begins = True
06642             exponent_string = self.exponent.into_str(**options)
06643             resulting_string += MARKUP['opening_exponent']                    \
06644                              + exponent_string                                \
06645                              + MARKUP['closing_exponent']
06646 
06647 
06648         #DEBUG
06649         debug.write( \
06650             "\nLeaving into_str in Sum : resulting_string = " \
06651             + resulting_string + '\n',
06652             case=debug.into_str_in_sum)
06653 
06654         return resulting_string
06655 
06656 
06657 
06658 
06659     # --------------------------------------------------------------------------
06660     ##
06661     #   @brief Returns the next calculated step of a *numeric* Sum
06662     #   @todo This method may be only partially implemented (see source)
06663     #   @todo the inner '-' signs (±(-2)) are not handled by this method so far
06664     def calculate_next_step(self, **options):
06665         if not self.is_numeric():
06666             return self.expand_and_reduce_next_step(**options)
06667 
06668         copy = self.clone()
06669         #DEBUG
06670         debug.write("\n[SUM] Entering calculate_next_step\n"\
06671                            + "with copied Sum :\n" \
06672                            + copy.dbg_str(),
06673                            case=debug.calculate_next_step_sum)
06674         # First recursively dive into embedded sums &| products &| fractions:
06675         if len(copy) == 1 and (isinstance(copy.term[0], CommutativeOperation) \
06676                                or isinstance(copy.term[0], Fraction)):
06677         #___
06678             #DEBUG
06679             debug.write("\n[SUM]Exiting calculate_next_step" \
06680                                    + "diving recursively in element[0]",
06681                                    case=debug.calculate_next_step_sum)
06682             return copy.term[0].calculate_next_step(**options)
06683 
06684         # Also de-embed recursively all terms that are CommutativeOperations containing
06685         # only one element AND replace these f*** 0-degree-Monomials by
06686         # equivalent Item/Fraction.
06687         a_term_has_been_depacked = False
06688         for i in range(len(copy)):
06689             if (isinstance(copy.term[i], CommutativeOperation) \
06690                 and len(copy.term[i]) == 1) \
06691                or isinstance(copy.term[i], Monomial):
06692             #___
06693                 copy.set_element(i, copy.term[i].element[0])
06694                 a_term_has_been_depacked = True
06695 
06696         if a_term_has_been_depacked:
06697             #DEBUG
06698             debug.write("\n[SUM]Exiting calculate_next_step" \
06699                                    + "having depacked one element at least" \
06700                                    + "\ncalculate_next_step is called on :\n"\
06701                                    + copy.dbg_str(),
06702                                    case=debug.calculate_next_step_sum)
06703             return copy.calculate_next_step(**options)
06704 
06705 
06706         # Second point :
06707         # if any sign of numerator or denominator is negative, compute the
06708         # sign of the fraction in order to get a "clean" Sum and by the way,
06709         # all denominators will be positive
06710         a_minus_sign_in_a_fraction_was_found = False
06711         for i in range(len(copy)):
06712             if isinstance(copy.term[i], Fraction) \
06713                and (copy.term[i].numerator.get_sign() == '-'\
06714                     or copy.term[i].denominator.get_sign() == '-'):
06715             #___
06716                 # case of a denominator like -(-3) is probably not well handled
06717                 copy.element[i].set_sign( \
06718                             sign_of_product([ \
06719                                 copy.element[i].get_sign(),
06720                                 copy.element[i].numerator.get_sign(),
06721                                 copy.element[i].denominator.get_sign()])
06722                                         )
06723                 copy.element[i].numerator.set_sign('+')
06724                 copy.element[i].denominator.set_sign('+')
06725                 a_minus_sign_in_a_fraction_was_found = True
06726 
06727         if a_minus_sign_in_a_fraction_was_found:
06728             #DEBUG
06729             debug.write("\n[SUM]Exiting calculate_next_step" \
06730                                    + "having changed negative denominators" \
06731                                    + "\nthe returned object is :\n"\
06732                                    + copy.dbg_str(),
06733                                    case=debug.calculate_next_step_sum)
06734             return copy
06735 
06736         # Then, check if the case is not this special one :
06737         # at least two fractions having the same denominator
06738         # If yes, then there's something special to do (put these fractions
06739         # together)
06740         # If not, just go on...
06741 
06742         # First let's count how many numbers & fractions there are
06743         numeric_items_nb = 0
06744         fractions_nb = 0
06745 
06746         for i in range(len(copy)):
06747             if isinstance(copy.term[i], Fraction):
06748                 fractions_nb += 1
06749             elif isinstance(copy.term[i], Item) \
06750                  and copy.term[i].is_numeric():
06751             #___
06752                 numeric_items_nb += 1
06753             elif isinstance(copy.term[i], Product) \
06754                  and len(copy.term[i]) == 1 \
06755                  and isinstance(copy.term[i].factor[0], Item) \
06756                  and copy.term[i].factor[0].is_numeric():
06757             #___
06758                 copy.element[i] = copy.term[i].factor[0]
06759                 numeric_items_nb += 1
06760 
06761         #DEBUG
06762         debug.write("\n[SUM]calculate_next_step\n" \
06763                                + "We found : " \
06764                                + str(fractions_nb) + " fractions, and " \
06765                                + str(numeric_items_nb) \
06766                                + " numeric items.\n",
06767                                case=debug.calculate_next_step_sum)
06768 
06769         # Now check if there are at least two fractions having the
06770         # same denominator
06771         fractions_have_been_added = False
06772         dont_touch_these = []
06773 
06774         if fractions_nb >= 1:
06775             # we will build a dictionnary containing :
06776             # {denominator1:[fraction1 + fraction3], denominator2:[...], ...}
06777             lexi = {}
06778             for objct in copy:
06779                 if isinstance(objct, Fraction):
06780                     put_term_in_lexicon(objct.denominator, objct, lexi)
06781 
06782             #DEBUG
06783             if debug.calculate_next_step_sum and debug.ENABLED:
06784                 built_lexi = ""
06785                 for key in lexi:
06786                     built_lexi += "Key : " + key.dbg_str() + \
06787                                   "\nValue : " + lexi[key].dbg_str() + "\n"
06788                 debug.write("\n[SUM]calculate_next_step\n" \
06789                                        + "Looking for fractions having the "\
06790                                        + "same denominator ; " \
06791                                        + "built the lexicon : \n" \
06792                                        + str(built_lexi),
06793                                        case=debug.calculate_next_step_sum)
06794 
06795             # now we check if any of these denominators is there several times
06796             # and if yes, we change the copy
06797             for denominator_key in lexi:
06798                 if len(lexi[denominator_key]) >= 2:
06799                     fractions_have_been_added = True
06800                     common_denominator = denominator_key
06801                     numes_list = []
06802                     for fraction in lexi[denominator_key].element:
06803                         if fraction.sign == '+':
06804                             numes_list.append(fraction.numerator)
06805                         else:
06806                             new_term = Product([Item(-1)] \
06807                                                + fraction.numerator.factor)
06808                             if fraction.numerator.get_sign() == '+':
06809                                 new_term = new_term.reduce_()
06810                             new_term.set_compact_display(True)
06811                             numes_list.append(new_term)
06812 
06813                     new_fraction = Fraction((Sum(numes_list),
06814                                             common_denominator))
06815 
06816                     #DEBUG
06817                     debug.write("\n[SUM]calculate_next_step\n" \
06818                                        + "Found " \
06819                                        + str(len(lexi[denominator_key])) \
06820                                        + " fractions having this denominator "\
06821                                        + "\n" + denominator_key.dbg_str()\
06822                                        + "\n new_fraction looks like :\n" \
06823                                        + new_fraction.dbg_str(),
06824                                        case=debug.calculate_next_step_sum)
06825 
06826                     first_fraction_met = True
06827                     for i in range(len(copy)):
06828                         if not i >= len(copy):
06829                             #DEBUG
06830                             debug.write(\
06831                                    "\n[SUM]calculate_next_step\n" \
06832                                    + "copy.term[" + str(i) + "] = " \
06833                                    + copy.term[i].dbg_str() \
06834                                    + "\nlooked for here :\n" \
06835                                    + lexi[denominator_key].dbg_str(),
06836                                    case=debug.calculate_next_step_sum)
06837                             if isinstance(copy.term[i], Fraction) \
06838                                and copy.term[i] in lexi[denominator_key].term:
06839                             #___
06840                                 #DEBUG
06841                                 debug.write(\
06842                                        "\n[SUM]calculate_next_step\n" \
06843                                        + "copy.term[" + str(i) + "] is in"\
06844                                        + " this lexicon :\n" \
06845                                        + lexi[denominator_key].dbg_str(),
06846                                        case=debug.calculate_next_step_sum)
06847                                 if first_fraction_met:
06848                                     copy.set_element(i, new_fraction)
06849                                     first_fraction_met = False
06850                                     dont_touch_these.append(new_fraction)
06851                                 else:
06852                                     copy.remove(copy.term[i])
06853 
06854         #if fractions_have_been_added:
06855          #   return copy
06856 
06857         # now gather numeric items in a sum.
06858         # if they were scattered, the sum doesn't have to be calculated
06859         # hereafter but if they were not scattered, then the sum may be
06860         # calculated (so we'll put the newly created sum in dont_touch_these
06861         # or not, depending on the case)
06862         some_terms_have_been_moved = False
06863         max_found_gap = 1
06864         last_numeric_item_position = 0
06865 
06866         if numeric_items_nb >= 2:
06867             numeric_terms_collection = []
06868             for i in range(len(copy)):
06869                 if isinstance(copy.term[i], Item) \
06870                    and copy.term[i].is_numeric():
06871                 #___
06872                     if i - last_numeric_item_position > max_found_gap:
06873                         max_found_gap = i - last_numeric_item_position
06874                     last_numeric_item_position = i
06875                     numeric_terms_collection.append(copy.term[i])
06876 
06877             # if there are only numeric items, then they don't have to be
06878             # embedded in a Sum (it would result in infinitely embedding
06879             # sums)
06880             if fractions_nb >= 1:
06881                 first_numeric_item_met = True
06882                 for i in range(len(copy)):
06883                     if not i >= len(copy) \
06884                        and isinstance(copy.term[i], Item) \
06885                        and copy.term[i].is_numeric():
06886                     #___
06887                         if first_numeric_item_met:
06888                             copy.term[i] = Sum(numeric_terms_collection)
06889                             first_numeric_item_met = False
06890                             if max_found_gap >= 2:
06891                                 some_terms_have_been_moved = True
06892                                 dont_touch_these.append(copy.term[i])
06893                         else:
06894                             copy.remove(copy.term[i])
06895 
06896         # Now check if any of the terms has to be
06897         # "calculated_next_step" itself.
06898         # If yes, just replace these terms by the calculated ones.
06899 
06900         a_term_has_been_modified = False
06901 
06902         for i in range(len(copy)):
06903             if not copy.term[i] in dont_touch_these:
06904                 temp = copy.term[i].calculate_next_step(**options)
06905                 if temp != None:
06906                     copy.term[i] = temp
06907                     a_term_has_been_modified = True
06908 
06909         #if a_term_has_been_modified:
06910          #   return copy
06911 
06912         if fractions_have_been_added \
06913            or a_term_has_been_modified \
06914            or some_terms_have_been_moved:
06915         #___
06916             return copy
06917 
06918         # no term has been modified, no term has been moved,
06919         # no fractions have already been added,
06920         # so let's check in which case we are :
06921         # 1. There are only numbers (e.g. numeric Items)
06922         # 2. There are only fractions
06923         # 3. There are fractions mixed with numbers.
06924         # first if nothing has to be computed, just return None
06925         if len(copy) == 1:
06926             # if this unique term has to be calculated, then it has been
06927             # done above
06928             return None
06929 
06930         # 1. There are only numbers (e.g. numeric Items)
06931         if numeric_items_nb >= 1 and fractions_nb == 0:
06932             return Sum([Item(copy.evaluate(stop_recursion=True))])
06933 
06934         # 2. There are only fractions
06935         if numeric_items_nb == 0 and fractions_nb >= 1:
06936             denos_list = []
06937             for i in range(len(copy)):
06938                 denos_list.append(copy.term[i].denominator.factor[0].raw_value)
06939 
06940             lcm = lcm_of_the_list(denos_list)
06941             lcm_item = Item(lcm)
06942 
06943             # now check if at least two denominators are different
06944             # if yes, then the fractions have to be reduced to the same
06945             # denominator
06946             # if no, then the numerators can be added and the denominator
06947             # will be kept
06948             same_deno_reduction_required = False
06949 
06950             for i in range(len(copy)):
06951                 if lcm_item != copy.term[i].denominator.factor[0]:
06952                     same_deno_reduction_required = True
06953 
06954             if same_deno_reduction_required:
06955                 for i in range(len(copy)):
06956                     aux_item = Item(int(lcm/copy.term[i].\
06957                                                 denominator.factor[0].raw_value))
06958                     if aux_item.raw_value != 1:
06959                         copy.term[i].set_numerator(
06960                           Product([copy.term[i].numerator.factor[0],
06961                                    aux_item]))
06962                         copy.term[i].numerator.set_compact_display(False)
06963                         copy.term[i].set_denominator(
06964                           Product([copy.term[i].denominator.factor[0],
06965                                    aux_item]))
06966                         copy.term[i].denominator.set_compact_display(False)
06967                         copy.term[i].set_same_deno_reduction_in_progress(True)
06968 
06969             else:
06970                 numes_list = []
06971                 for i in range(len(copy)):
06972                     numes_list.append(Product([copy.term[i].numerator.factor[0],
06973                                                Item((copy.term[i].sign, 1))
06974                                               ]).reduce_().factor[0]
06975                                       )
06976 
06977                 copy = Sum([Fraction(('+',
06978                                       Sum(numes_list),
06979                                       lcm_item))])
06980 
06981             return copy
06982 
06983         # 3. There are fractions mixed with numbers.
06984         if numeric_items_nb >= 1 and fractions_nb >= 1:
06985             #copy = Sum(self)
06986             for i in range(len(copy)):
06987                 if isinstance(copy.term[i], Item) \
06988                    and copy.term[i].is_numeric():
06989                 #___
06990                     copy.term[i] = copy.term[i].turn_into_fraction()
06991                     copy.term[i].set_same_deno_reduction_in_progress(True)
06992 
06993             return copy.calculate_next_step(**options)
06994 
06995 
06996 
06997 
06998 
06999 
07000 
07001 
07002 
07003     # --------------------------------------------------------------------------
07004     ##
07005     #   @brief Returns the next step of expansion/reduction of the Sum
07006     #   So, either the Sum of its expanded/reduced terms,
07007     #   or the Sum itself reduced, or None
07008     #   @return Exponented
07009     def expand_and_reduce_next_step(self, **options):
07010         if self.is_numeric():
07011             return self.calculate_next_step(**options)
07012 
07013         #DEBUG
07014         debug.write("\nEntered :\n" \
07015                                + "[expand_and_reduce_next_step_sum]\n" \
07016                                + "Current Sum is : \n" \
07017                                + self.dbg_str() + "\n" \
07018                                + str(self.info)\
07019                                + "\n",
07020                                case=debug.expand_and_reduce_next_step_sum)
07021 
07022         copy = Sum(self).throw_away_the_neutrals()
07023 
07024         # to reduce the number of required steps in the case of
07025         # imbricated Sums like : [x, [x, x]] what should produce [(1+1+1)x]
07026         # in the next step and not [x, [(1+1)x]] and then [x, [2x]] and
07027         # then [(1+2)x] etc. which is far too long !
07028         # The Monomials with degree 0 also cause problem (because they
07029         # require a reduction line to become the equivalent Item and again,
07030         # the user won't see any difference...)
07031         new_copy = Sum(copy)
07032         new_copy.reset_element()
07033         new_copy.set_info(list())
07034         an_imbricated_sum_has_been_found = False
07035         a_degree_0_monomial_has_been_found = False
07036 
07037         for i in range(len(copy)):
07038             if (not isinstance(copy.term[i], Sum) \
07039                or (isinstance(copy.term[i], Sum) \
07040                    and not copy.term[i].exponent.is_displ_as_a_single_1( \
07041                                                                            )))\
07042                and not (isinstance(copy.term[i], Monomial) \
07043                         and copy.term[i].degree == 0):
07044             #___
07045                 new_copy.element.append(copy.term[i])
07046                 new_copy.info.append( \
07047                                               copy.info[i])
07048 
07049             elif isinstance(copy.term[i], Monomial) \
07050                  and copy.term[i].degree == 0:
07051             #___
07052                 new_copy.element.append(copy.term[i].factor[0])
07053                 new_copy.info.append(copy.info[i])
07054             else:
07055                 an_imbricated_sum_has_been_found = True
07056                 for j in range(len(copy.term[i])):
07057                     new_copy.element.append(copy.term[i].term[j])
07058                     new_copy.info.append(copy.term[i].info[j])
07059 
07060         if an_imbricated_sum_has_been_found \
07061            or a_degree_0_monomial_has_been_found:
07062         #___
07063             #DEBUG
07064             debug.write("\nExiting recursively :\n" \
07065                                    + "[expand_and_reduce_next_step_sum]\n" \
07066                                    + "a term has been modified, " \
07067                                    + "recursive call on :\n"\
07068                                    + new_copy.dbg_str() \
07069                                    + "\n",
07070                                   case=debug.expand_and_reduce_next_step_sum)
07071 
07072             return new_copy.expand_and_reduce_next_step(**options)
07073 
07074 
07075         # Now let's begin without any imbricated Sum nor 0-degree Monomials !
07076         # That's clean :o)
07077         a_term_at_least_has_been_modified = False
07078 
07079         # A similar protection as in intermediate_reduction_line has to
07080         # be made for other cases
07081         numeric_terms_must_be_reduced = False
07082         the_first_numeric_term_has_been_found = False
07083 
07084         if copy.numeric_terms_require_to_be_reduced():
07085         #___
07086             numeric_terms_must_be_reduced = True
07087             (lexi, index) = copy.get_terms_lexicon()
07088             numeric_value = Item(lexi[NUMERIC].evaluate())
07089 
07090         # this list will contain the numeric terms that should be removed
07091         # from the Sum after the reduction of numeric terms (e.g.
07092         # if 12 - 1 + 4x has to be reduced, will become 11 + 4x this
07093         # way : replacing 12 by 11 and removing -1)
07094         terms_to_remove = list()
07095         # the terms that have been modified in the "first round" don't have
07096         # to be modified anew in the "second round".
07097         modified_term = list()
07098 
07099         # first round :
07100         # check if any of the terms needs to be reduced
07101         # (excepted numeric terms)
07102         for i in range(len(copy)):
07103             test = copy.term[i].expand_and_reduce_next_step(**options)
07104             if test != None:
07105                 copy.term[i] = test
07106                 a_term_at_least_has_been_modified = True
07107                 modified_term.append(True)
07108             else:
07109                 modified_term.append(False)
07110 
07111         # second round :
07112         # if necessary, let's reduce the numeric terms
07113         if numeric_terms_must_be_reduced \
07114            and a_term_at_least_has_been_modified:
07115         #___
07116             for i in range(len(copy)):
07117                 if copy.term[i].is_numeric() \
07118                    and not modified_term[i]:
07119                 #___
07120                     if the_first_numeric_term_has_been_found:
07121                         terms_to_remove.append(copy.term[i])
07122                     else:
07123                         copy.term[i] = numeric_value
07124                         the_first_numeric_term_has_been_found = True
07125 
07126             for j in range(len(terms_to_remove)):
07127                 copy.remove(terms_to_remove[j])
07128 
07129 
07130             #elif numeric_terms_must_be_reduced \
07131             #   and copy.term[i].is_numeric():
07132             #___
07133             #    if the_first_numeric_term_has_been_found:
07134             #        terms_to_remove.append(copy.term[i])
07135 
07136             #    else:
07137             #        copy.term[i] = numeric_value
07138             #        the_first_numeric_term_has_been_found = True
07139             #        a_term_at_least_has_been_modified = True
07140 
07141         #for j in xrange(len(terms_to_remove)):
07142         #    copy.remove(terms_to_remove[j])
07143 
07144         if a_term_at_least_has_been_modified:
07145             #DEBUG
07146             debug.write("\nExiting :\n" \
07147                                    + "[expand_and_reduce_next_step_sum]\n" \
07148                                    + "a term has been modified, returning :\n"\
07149                                    + copy.dbg_str() \
07150                                    + "\n",
07151                                   case=debug.expand_and_reduce_next_step_sum)
07152             return copy
07153 
07154         # no term of the Sum needs to be reduced
07155         else:
07156             if copy.is_numeric() and copy.is_reducible():
07157                 #DEBUG
07158                 debug.write("\nExiting :\n" \
07159                                   + "[expand_and_reduce_next_step_sum]\n"\
07160                                   + "no term has been modified, but : "\
07161                                   + copy.dbg_str() \
07162                                   + "\nis numeric and reducible\n" \
07163                                   + "returning a reduced copy of it",
07164                                   case=debug.expand_and_reduce_next_step_sum)
07165 
07166                 return copy.reduce_()
07167 
07168             elif copy.is_reducible():
07169                 debug.write("\nExiting :\n" \
07170                                   + "[expand_and_reduce_next_step_sum]\n"\
07171                                   + "no term has been modified, and : "\
07172                                   + copy.dbg_str() \
07173                                   + "\nisn't numeric but is reducible\n" \
07174                                   + "returning the intermediate line " \
07175                                   + "generated from it",
07176                                   case=debug.expand_and_reduce_next_step_sum)
07177 
07178                 return copy.intermediate_reduction_line()
07179 
07180             else:
07181                 debug.write("\nExiting :\n" \
07182                                   + "[expand_and_reduce_next_step_sum]\n"\
07183                                   + "no term has been modified, and : "\
07184                                   + copy.dbg_str() \
07185                                   + "\nisn't numeric nor reducible, " \
07186                                   + "returning None\n",
07187                                   case=debug.expand_and_reduce_next_step_sum)
07188 
07189                 return None
07190 
07191 
07192 
07193 
07194     # --------------------------------------------------------------------------
07195     ##
07196     #   @brief Compares two Sums
07197     #   Returns 0 if all terms are the same in the same order and if the
07198     #   exponents are also the same
07199     #   /!\ a + b will be different from de b + a
07200     #   It's not a mathematical comparison, but a "displayable"'s one.
07201     #   @return True if all terms are the same in the same order & the exponent
07202     def __eq__(self, objct):
07203         if not isinstance(objct, Sum):
07204             return False
07205 
07206         if len(self) != len(objct):
07207             return False
07208 
07209         for i in range(len(self)):
07210             if self.term[i] != objct.term[i]:
07211                 return False
07212 
07213         if self.exponent != objct.exponent:
07214             return False
07215 
07216         return True
07217 
07218 
07219 
07220 
07221 
07222     # --------------------------------------------------------------------------
07223     ##
07224     #   @brief Defines the performed CommutativeOperation as a Sum
07225     def operator(self, arg1, arg2):
07226         return arg1 + arg2
07227 
07228 
07229 
07230 
07231 
07232     # --------------------------------------------------------------------------
07233     ##
07234     #   @brief Returns the rank of next non-equivalent to 0 term...
07235     #   @param position The point where to start from to search
07236     #   @return The rank of next non-equivalent to 0 term (or None)
07237     def next_displayable_term_nb(self, position):
07238         for i in range(len(self) - 1 - position):
07239             if not self.term[i + 1 + position].is_displ_as_a_single_0():
07240                 return i + 1 + position
07241         return None
07242 
07243 
07244 
07245 
07246 
07247     # --------------------------------------------------------------------------
07248     ##
07249     #   @brief True if the usual writing rules require a × between two factors
07250     #   @param objct The other one
07251     #   @param position The position (integer) of self in the Product
07252     #   @return True if the writing rules require × between self & obj
07253     def multiply_symbol_is_required(self, objct, position):
07254         # 1st CASE : Sum × Item
07255         if isinstance(objct, Item):
07256             if len(self) >= 2:
07257                 if objct.is_numeric() or (objct.is_literal() \
07258                                           and objct.sign == '-'):
07259                     return True
07260                 else:
07261                     return False
07262             else:
07263                 return self.term[0].multiply_symbol_is_required(objct,
07264                                                                 position)
07265 
07266         # 2d CASE : Sum × Sum
07267         if isinstance(objct, Sum):
07268             if len(self) >= 2 and len(objct) >= 2:
07269                 return False
07270             if len(self) == 1:
07271                 return self.term[0].multiply_symbol_is_required(objct,
07272                                                                 position)
07273             if len(objct) == 1:
07274                 return self.multiply_symbol_is_required(objct.term[0],
07275                                                         position)
07276 
07277         # 3d CASE : Sum × Product
07278         if isinstance(objct, Product):
07279             if len(self) == 1:
07280                 return self.term[0].multiply_symbol_is_required(objct,
07281                                                                 position)
07282             else:
07283                 return self.multiply_symbol_is_required(objct.factor[0],
07284                                                         position)
07285 
07286         # 4th CASE : Sum × Quotient
07287         if isinstance(objct, Quotient):
07288             return True
07289 
07290 
07291 
07292 
07293 
07294     # --------------------------------------------------------------------------
07295     ##
07296     #   @brief True if the argument requires brackets in a product
07297     #   For instance, a Sum with several terms or a negative Item
07298     #   @param position The position of the object in the Product
07299     #   @return True if the object requires brackets in a Product
07300     def requires_brackets(self, position):
07301         # A Sum having more than one term requires brackets
07302         tested_sum = None
07303         if self.compact_display:
07304             tested_sum = self.throw_away_the_neutrals()
07305 
07306         else:
07307             tested_sum = self.clone()
07308 
07309         if len(tested_sum) >= 2:
07310             #return True
07311             #debug
07312             debug.write( \
07313             "\n[SUM] requires_brackets : self.requires_inner_brackets = " \
07314             + str(tested_sum.requires_inner_brackets()) \
07315             + "\n",
07316             case=debug.requires_brackets_in_sum)
07317             return not tested_sum.requires_inner_brackets()
07318         # If the Sum has only one term, then it will depend on its
07319         # exponent and on its content
07320         else:
07321             # If the exponent is different from 1 (or is not equivalent
07322             # to 1) then the answer (True/False) will be the same than
07323             # the one for self.term[0] but not at the first position.
07324             # In other words, if the first term begins with a -, it
07325             # requires brackets.
07326             if not tested_sum.exponent.is_displ_as_a_single_1():
07327                 return tested_sum.term[0].requires_brackets(position + 1)
07328             else:
07329                 return tested_sum.term[0].requires_brackets(position)
07330 
07331 
07332 
07333 
07334 
07335     # --------------------------------------------------------------------------
07336     ##
07337     #   @brief True if the argument requires inner brackets
07338     #   The reason for requiring them is having an exponent different
07339     #   from 1 and several terms or factors (in the case of Products & Sums)
07340     #   @return True if the object requires inner brackets
07341     def requires_inner_brackets(self):
07342         if self.exponent_must_be_displayed():
07343             if len(self) == 1 or self.is_displ_as_a_single_1():
07344                 if not (self.get_sign() == '+' and self.term[0].is_numeric()):
07345                     return True
07346                 else:
07347                     return False
07348 
07349             # this case is when there are several terms
07350             else:
07351                 return True
07352 
07353         return False
07354 
07355 
07356 
07357 
07358 
07359     # --------------------------------------------------------------------------
07360     ##
07361     #   @brief True if a gathering of numeric terms must be reduced
07362     #   It is True if at least two consecutive numeric Items are in the list
07363     #   and no other numeric term elsewhere.
07364     #   Examples where it's True :
07365     #   3x - 5 + 2 + 5x
07366     #   -2 + 4 + 9 - 2x + 3x²
07367     #   When it's False :
07368     #   3 - 5 + 2x + 6
07369     #   4 + x - 2
07370     #   -7 + 2×5 + 4 + 2x
07371     #   @return True if a gathering of numeric terms must be reduced
07372     def numeric_terms_require_to_be_reduced(self):
07373         at_least_one_numeric_term_has_been_found = False
07374         at_least_two_numeric_terms_have_been_found = False
07375         two_scattered_terms_have_been_found = False
07376         last_numeric_term_position = -1
07377         for i in range(len(self)):
07378             if self.term[i].is_displ_as_a_single_numeric_Item():
07379                 if not at_least_one_numeric_term_has_been_found:
07380                     at_least_one_numeric_term_has_been_found = True
07381                     last_numeric_term_position = i
07382                 else:
07383                     at_least_two_numeric_terms_have_been_found = True
07384                     if last_numeric_term_position == i - 1:
07385                         last_numeric_term_position = i
07386                     else:
07387                         two_scattered_terms_have_been_found = True
07388 
07389         if at_least_two_numeric_terms_have_been_found \
07390            and not two_scattered_terms_have_been_found:
07391         #___
07392             return True
07393 
07394 
07395 
07396     # --------------------------------------------------------------------------
07397     ##
07398     #   @brief Creates the intermediate reduction line.
07399     #   For instance, giving the Sum 5x + 7x³ - 3x + 1,
07400     #   this method would return (5 - 3)x + 7x³ + 1.
07401     #   No intermediate expansion will be done, though.
07402     #   For instance, the expression a + b + (a + b)
07403     #   will be reduced in 2a + 2b (provided the (a+b) is given as a Sum and
07404     #   not, for instance, as this Product : 1×(a + b) ;
07405     #   but the expression a + b + 5(a + b) won't be reduced in 6a + 6b. It has
07406     #   to be expanded first (there's a Expandable term there !).
07407     #   This method is the base to process the reduction of a Sum.
07408     #   It is therefore important that it returns the described kind of result.
07409     #   @return A Products' Sum : (coefficients Sum)×(literal factor)
07410     def intermediate_reduction_line(self):
07411         # In the case of a given Sum like 5x + 4 - 7 + 3x²
07412         # the intermediate_reduction_line should return the reduced Sum
07413         # instead of... the same line.
07414         # So if the numeric terms are in a row (and no other numeric term
07415         # is anywhere else, otherwise it would be useless), we reduce them
07416         # This is redundant (?) with the expand_and_reduce_next_step() but is
07417         # necessary to prevent cases when this method is called without
07418         # protection
07419         numeric_terms_must_be_reduced = False
07420 
07421         if self.numeric_terms_require_to_be_reduced():
07422         #___
07423             numeric_terms_must_be_reduced = True
07424 
07425         # Let's begin to build the intermediate line
07426         (lexi, index) = self.get_terms_lexicon()
07427 
07428         final_sum_terms_list = list()
07429 
07430         for key in index:
07431             if key == NUMERIC:
07432                 temp_object = lexi[key]
07433                 if numeric_terms_must_be_reduced:
07434                     temp_object = Item(temp_object.evaluate())
07435                 elif len(temp_object) == 1:
07436                     temp_object = temp_object.term[0]
07437 
07438             elif lexi[key].is_displ_as_a_single_1():
07439                 temp_object = key
07440 
07441             else:
07442                 if len(lexi[key]) == 1:
07443                     temp_object = Product([lexi[key].term[0], key])
07444                 else:
07445                     temp_object = Product([lexi[key], key])
07446 
07447             final_sum_terms_list.append(temp_object)
07448 
07449         final_sum = Sum(final_sum_terms_list)
07450 
07451         if self.force_inner_brackets_display:
07452             final_sum.set_force_inner_brackets_display(True)
07453 
07454         return final_sum
07455 
07456 
07457 
07458 
07459 
07460     # --------------------------------------------------------------------------
07461     ##
07462     #   @brief Returns the reduced Sum
07463     #   For instance, giving the Sum 5x + 7x³ - 3x + 1,
07464     #   this method would return -2x + 7x³ + 1.
07465     #   No intermediate expandment will be done, though.
07466     #   For instance, the expression a + b + (a + b)
07467     #   will be reduced in 2a + 2b (provided the (a+b) is given as a Sum and
07468     #   not, for instance, as this Product : 1×(a + b) ;
07469     #   but the expression a + b + 5(a + b) won't be reduced in 6a + 6b. It has
07470     #   to be expanded first (there's a Expandable term there !).
07471     #   @todo support for Fractions (evaluation...)
07472     #   @return One instance of Sum (reduced) [?] or of the only remaining term
07473     def reduce_(self):
07474         # The main difference with the intermediate_reduction_line method
07475         # is that the numeric part will be evaluated
07476         (lexi, index) = self.get_terms_lexicon()
07477 
07478         #DEBUG
07479         debug.write("\n[SUM] Entered reduce_ with the Sum :\n"\
07480                     + self.dbg_str(),
07481                     case=debug.reduce_in_sum)
07482 
07483         if debug.ENABLED and debug.reduce_in_sum:
07484             lexi_content = ""
07485             for elt in lexi:
07486                 lexi_content += el.dbg_str() + " : " + lexi[elt].dbg_str() \
07487                                 + "   ;   "
07488 
07489             debug.write("\nget_terms_lexicon returned this : "\
07490                         + lexi_content,
07491                         case=debug.reduce_in_sum)
07492 
07493 
07494         final_sum_terms_list = list()
07495 
07496         for key in index:
07497 
07498             if key == NUMERIC:
07499                 temp_object = lexi[key].evaluate()
07500 
07501             elif lexi[key].is_displ_as_a_single_1():
07502                 temp_object = key
07503 
07504             else:
07505                 computed_coeff = Item(lexi[key].evaluate())
07506                 if computed_coeff.is_displ_as_a_single_0():
07507                     temp_object = computed_coeff
07508                 else:
07509                     temp_object = Product([computed_coeff, key])
07510 
07511             final_sum_terms_list.append(temp_object)
07512 
07513         final_sum = (Sum(final_sum_terms_list)).throw_away_the_neutrals()
07514 
07515         if self.force_inner_brackets_display:
07516             final_sum.set_force_inner_brackets_display(True)
07517 
07518         # Here follows a patch to avoid returning a Sum containing only one
07519         # term.
07520         if len(final_sum) == 1 \
07521            and final_sum.term[0].exponent.is_displ_as_a_single_1():
07522         #___
07523             final_sum = final_sum.term[0]
07524 
07525         #DEBUG
07526         debug.write("\n[SUM] Leaving reduce_, returning :\n"\
07527                        + final_sum.dbg_str(),
07528                        case=debug.reduce_in_sum)
07529 
07530         return final_sum
07531 
07532 
07533 
07534 
07535 
07536     # --------------------------------------------------------------------------
07537     ##
07538     #   @brief True if the result of the Sum is null
07539     def is_null(self):
07540         if self.evaluate() == 0:
07541             return True
07542         else:
07543             return False
07544 
07545 
07546 
07547 
07548 
07549     # --------------------------------------------------------------------------
07550     ##
07551     #   @brief True if the Sum contains only 0s and one 1. For instance, 1+0+0
07552     def is_displ_as_a_single_1(self):
07553         # dive recursively into embedded objects
07554         if len(self) == 1:
07555             return self.term[0].is_displ_as_a_single_1()
07556 
07557         a_term_different_from_0_and_1_has_been_found = False
07558         equivalent_to_1_terms_nb = 0
07559 
07560         for term in self.element:
07561             if term.is_displ_as_a_single_1():
07562                 equivalent_to_1_terms_nb += 1
07563             elif not term.is_displ_as_a_single_0():
07564                 a_term_different_from_0_and_1_has_been_found = True
07565 
07566         if a_term_different_from_0_and_1_has_been_found:
07567             return False
07568         elif equivalent_to_1_terms_nb == 1:
07569             return True
07570         else:
07571             return False
07572 
07573 
07574 
07575 
07576 
07577     # --------------------------------------------------------------------------
07578     ##
07579     #   @brief True if the Sum can be displayed as a single -1
07580     # So, if it only contains 0s and a single -1. For instance, 0+0+(-1)+0
07581     def is_displ_as_a_single_minus_1(self):
07582         a_term_different_from_0_and_minus1_has_been_found = False
07583         equivalent_to_minus1_terms_nb = 0
07584 
07585         for term in self.element:
07586             if term.is_displ_as_a_single_minus_1():
07587                 equivalent_to_minus1_terms_nb += 1
07588             elif not term.is_displ_as_a_single_0():
07589                 a_term_different_from_0_and_minus1_has_been_found = True
07590 
07591         if a_term_different_from_0_and_minus1_has_been_found:
07592             return False
07593         elif equivalent_to_minus1_terms_nb == 1:
07594             return True
07595         else:
07596             return False
07597 
07598 
07599 
07600 
07601 
07602     # --------------------------------------------------------------------------
07603     ##
07604     #   @brief True if the object can be displayed as a single 0
07605     #   For instance, 0 + 0 + 0 but NOT - 1 + 0 + 1 (it's a matter of display)
07606     def is_displ_as_a_single_0(self):
07607         return self.is_displ_as_a_single_neutral(Item(0))
07608 
07609 
07610 
07611 
07612 
07613     # --------------------------------------------------------------------------
07614     ##
07615     #   @brief True if the Sum is reducible
07616     #   This is based on the result of the get_term_lexicon method
07617     #   @return True|False
07618     def is_reducible(self):
07619         if self.is_displ_as_a_single_0() \
07620            or self.is_displ_as_a_single_1() \
07621            or self.is_displ_as_a_single_minus_1():
07622         #___
07623             return False
07624 
07625         lexi = self.get_terms_lexicon()[0]
07626 
07627         # If one of the coefficient Sums contains at least 2 terms, then
07628         # the Sum is reducible
07629         for key in lexi:
07630             if len(lexi[key].term) >= 2:
07631                 return True
07632 
07633         return False
07634 
07635 
07636 
07637 
07638 
07639 # ------------------------------------------------------------------------------
07640 # --------------------------------------------------------------------------
07641 # ------------------------------------------------------------------------------
07642 ##
07643 # @class Monomial
07644 # @brief A Monomial is a Product of a numeric Exponented and a literal Item
07645 class Monomial(Product):
07646 
07647 
07648 
07649 
07650 
07651     # --------------------------------------------------------------------------
07652     ##
07653     #   @brief Constructor
07654     #   @warning Might raise an UncompatibleType exception.
07655     #   @param arg DEFAULT|Monomial|(sign, coeff, degree)|.......
07656     #   Possible arguments are :
07657     #   - DEFAULT, which is equivalent to ('+', 1, 0)
07658     #   - another Monomial which will be copied
07659     #   - (sign, coeff, degree) where coeff is a number and degree an integer
07660     #   - (coeff, degree) where coeff's numeric Exponented & degree an integer
07661     #   - (RANDOMLY, max_coeff, max_degree) where max_* are integers
07662     #   A Monomial will always be by default compact displayed (i.e. 2x and not
07663     #   2×x).
07664     #   If the argument isn't of the kinds listed above, an exception will be
07665     #   raised.
07666     #   @param options any option
07667     #   Options can be :
07668     #   - randomly_plus_signs_ratio : will be effective only in the case
07669     #     of (RANDOMLY, max_coeff, max_degree) arg. In this case, the
07670     #     random choice of the sign of the Monomial will respect the given
07671     #     ratio
07672     #   @return A instance of Monomial
07673     def __init__(self, arg, **options):
07674         CommutativeOperation.__init__(self)
07675 
07676         self._info = [False, False]
07677 
07678         self._neutral = Item(1)
07679 
07680         # 1st CASE : DEFAULT
07681         if arg == DEFAULT:
07682             factor1 = Item(1)
07683             factor2 = Item(('+', MONOMIAL_LETTER, 0))
07684             self._element.append(factor1)
07685             self._element.append(factor2)
07686 
07687         # 2d CASE : another Monomial
07688         elif type(arg) == Monomial :
07689             self._compact_display = arg.compact_display
07690             self._info = [arg.info[0], arg.info[1]]
07691             factor1 = arg.factor[0].clone()
07692             factor2 = Item(arg.factor[1])
07693             self._element.append(factor1)
07694             self._element.append(factor2)
07695             self._exponent = arg.exponent.clone()
07696 
07697         # 3d CASE : tuple (sign, number, integer)
07698         elif type(arg) == tuple and len(arg) == 3 and is_.a_sign(arg[0])      \
07699              and (is_.a_number(arg[1]) and is_.an_integer(arg[2])):
07700         #___
07701             factor1 = Item((arg[0], arg[1]))
07702             factor2 = Item(('+', MONOMIAL_LETTER, arg[2]))
07703             self._element.append(factor1)
07704             self._element.append(factor2)
07705 
07706         # 4th CASE : tuple (number|numeric Exponented, integer)
07707         elif type(arg) == tuple and len(arg) == 2                             \
07708              and (is_.a_number(arg[0]) or (isinstance(arg[0], Exponented)     \
07709                                              and arg[0].is_numeric())         \
07710              and is_.an_integer(arg[1])):
07711         #___
07712             if is_.a_number(arg[0]):
07713                 if arg[0] >= 0:
07714                     factor1 = Item(('+', arg[0]))
07715                 elif arg[0] < 0:
07716                     factor1 = Item(('-', -arg[0]))
07717             else:
07718                 factor1 = arg[0].clone()
07719 
07720             factor2 = Item(('+', MONOMIAL_LETTER, arg[1]))
07721             self._element.append(factor1)
07722             self._element.append(factor2)
07723 
07724         # 5th CASE : tuple (RANDOMLY, max_coeff, max_degree)
07725         elif type(arg) == tuple and len(arg) == 3 and arg[0] == RANDOMLY   \
07726              and is_.a_number(arg[1]) and is_.an_integer(arg[2]):
07727         #___
07728             aux_ratio = 0.5
07729             if 'randomly_plus_signs_ratio' in options \
07730                and is_.a_number(options['randomly_plus_signs_ratio']):
07731             #___
07732                 aux_ratio = options['randomly_plus_signs_ratio']
07733             factor1 = Item((randomly.sign(plus_signs_ratio=aux_ratio),
07734                             randomly.integer(1, arg[1])))
07735             factor2 = Item(('+',
07736                             MONOMIAL_LETTER,
07737                             randomly.integer(0, arg[2])))
07738             self._element.append(factor1)
07739             self._element.append(factor2)
07740 
07741         # All other unforeseen cases : an exception is raised.
07742         else:
07743             raise error.UncompatibleType(arg,                                 \
07744                                          "DEFAULT|Monomial|" \
07745                                          + "(sign, coeff, degree)|" \
07746                                          + "(number|numeric Exponented, " \
07747                                          + "integer)|" \
07748                                          + "(RANDOMLY, max_coeff, " \
07749                                          + "max_degree)")
07750 
07751         # We take care to set the exponent to ZERO_POLYNOMIAL_DEGREE
07752         # in the case the coefficient is null :
07753         if self.factor[0].is_null():
07754             self._element[1].set_exponent(Value(ZERO_POLYNOMIAL_DEGREE))
07755 
07756         if self.element[1].exponent == Value(0) \
07757             and isinstance(self.element[0], Item):
07758         #___
07759             # This is just to mimic the Item it could be, when the exponent
07760             # of x is zero :
07761             # Monomial 3×x^0 is like Item 3.
07762             self._value_inside = Value(self.factor[0].raw_value)
07763 
07764 
07765 
07766 
07767 
07768     # --------------------------------------------------------------------------
07769     ##
07770     #   @brief Gets the sign of the Monomial
07771     #   @return The sign of the Monomial
07772     #   This can't be done by CommutativeOperation.get_sign() apparently. Maybe check
07773     #   exactly why, some day
07774     def get_sign(self):
07775         if self.is_null():
07776             return '+'
07777         else:
07778             return self.factor[0].get_sign()
07779 
07780 
07781 
07782 
07783 
07784     # --------------------------------------------------------------------------
07785     ##
07786     #   @brief Returns the numeric coefficient of the Monomial
07787     #   @return The numeric coefficient of the Monomial
07788     def get_coeff(self):
07789         return self.factor[0]
07790 
07791 
07792 
07793 
07794 
07795     # --------------------------------------------------------------------------
07796     ##
07797     #   @brief Gets the value of a Monomial of degree 0
07798     #   @warning Raises an error if asked on non-degree-0 Monomial
07799     #   @return value_inside.raw_value
07800     def get_raw_value(self):
07801         return self.value_inside.raw_value
07802 
07803 
07804 
07805 
07806 
07807     # --------------------------------------------------------------------------
07808     ##
07809     #   @brief Returns the letter of the Monomial
07810     #   @return The letter of the Monomial
07811     def get_first_letter(self):
07812         return self.factor[1].raw_value
07813 
07814 
07815 
07816 
07817 
07818     # --------------------------------------------------------------------------
07819     ##
07820     #   @brief Returns the degree of the Monomial (i.e. exponent of factor[1])
07821     #   @return The degree of the Monomial
07822     def get_degree(self):
07823         return self.factor[1].exponent.evaluate()
07824 
07825 
07826 
07827 
07828 
07829     # --------------------------------------------------------------------------
07830     ##
07831     #   @brief Gets the Value of the Monomial, just to mimic the case when
07832     #          it is of degree 0
07833     #   @return value_inside
07834     def get_value_inside(self):
07835         return self._value_inside
07836 
07837 
07838 
07839 
07840 
07841     sign = property(get_sign, doc = "Monomial's sign")
07842 
07843     coeff = property(get_coeff, doc = "Monomial's coefficient")
07844 
07845     raw_value = property(get_raw_value, doc = "0-degree-Monomial's value")
07846 
07847     degree = property(get_degree, doc = "Monomial's degree")
07848 
07849     letter = property(get_first_letter, doc = "Monomial's letter")
07850 
07851     value_inside = property(get_value_inside,
07852                             doc = "0-degree Monomial's Value inside")
07853 
07854 
07855 
07856 
07857 
07858     # --------------------------------------------------------------------------
07859     ##
07860     #   @brief Sets the letter of the Monomial
07861     def set_letter(self, letter):
07862         self.element[1].set_value_inside(Value(letter))
07863 
07864 
07865 
07866 
07867 
07868 
07869     # --------------------------------------------------------------------------
07870     ##
07871     #   @brief Set the degree of the Monomial
07872     def set_degree(self, arg):
07873         if is_.a_natural_int(arg):
07874             self.factor[1].set_exponent(arg)
07875         else:
07876             raise error.UncompatibleType(arg, "natural integer")
07877 
07878 
07879 
07880 
07881     # --------------------------------------------------------------------------
07882     ##
07883     #   @brief Set the degree of the Monomial
07884     def set_coeff(self, arg):
07885         self._element[0] = Item(arg)
07886 
07887 
07888 
07889 
07890     # --------------------------------------------------------------------------
07891     ##
07892     #   @brief Raw display of the Monomial (debugging method)
07893     #   @param options No option available so far
07894     #   @return A string containing " << coeff × X ^ degree>> "
07895     def dbg_str(self, **options):
07896 
07897         expo = ""
07898 
07899         if self.exponent != Value(1):
07900             expo = "^{" + self.exponent.dbg_str() + "} "
07901 
07902         return " <<" + self.coeff.dbg_str()          \
07903                + "× X ^" + str(self.degree) + ">> " + expo
07904 
07905 
07906 
07907 
07908 
07909     # --------------------------------------------------------------------------
07910     ##
07911     #   @brief True if it's the null Monomial
07912     def is_null(self):
07913         if self.degree == ZERO_POLYNOMIAL_DEGREE                          \
07914            or self.coeff.is_null():
07915             return True
07916         else:
07917             return False
07918 
07919 
07920 
07921 
07922 
07923     # --------------------------------------------------------------------------
07924     ##
07925     #   @brief True if Monomial's degree is 0 or ZERO_POLYNOMIAL_DEGREE
07926     def is_numeric(self):
07927         if self.is_null():
07928             return True
07929 
07930         if self.degree == 0:
07931             return True
07932 
07933         return False
07934 
07935 
07936 
07937 
07938 
07939     # --------------------------------------------------------------------------
07940     ##
07941     #   @brief True if Monomial's coefficient's *sign* is '+'
07942     #   @todo How to answer to the question if this Monomial is null ?
07943     def is_positive(self):
07944         return self.element[0].is_positive()
07945 
07946 
07947 
07948 
07949 
07950 
07951     # --------------------------------------------------------------------------
07952     ##
07953     #   @brief True if Monomial's coefficient's *sign* is '-'
07954     #   @todo How to answer to the question if this Monomial is null ?
07955     def is_negative(self):
07956         return self.element[0].is_negative()
07957 
07958 
07959 
07960 
07961 
07962 # ------------------------------------------------------------------------------
07963 # --------------------------------------------------------------------------
07964 # ------------------------------------------------------------------------------
07965 ##
07966 # @class Polynomial
07967 # @brief A Polynomial is a Sum of Monomials, not necessarily reduced or ordered
07968 class Polynomial(Sum):
07969 
07970 
07971 
07972 
07973 
07974     # --------------------------------------------------------------------------
07975     ##
07976     #   @brief Constructor
07977     #   @warning Might raise an UncompatibleType exception.
07978     #   @param arg DEFAULT|[Monomial|Polynomial]|Sum(...)|(RANDOMLY, ...)
07979     #   Possible arguments are :
07980     #   - DEFAULT :
07981     #     Will create a default Monomial embedded in a Polynomial
07982     #   - [Monomial|Polynomial] or Sum(Monomial|Polynomial) :
07983     #     They'll get turned into one Polynomial
07984     #   - (RANDOMLY,
07985     #      max_coeff,
07986     #      max_degree,
07987     #      [length|tuple(RANDOMLY, max_length)]) :
07988     #     The coefficients and degrees of the Polynomial will be created
07989     #     randomly. Limits are given by : CONSTANT_TERMS_MAXIMUM_RATIO
07990     #     and CONSTANT_TERMS_MINIMUM_NUMBER. The length can either be given
07991     #     or be let generated randomly.
07992     #   If the argument isn't of the kinds listed above, an exception will be
07993     #   raised.
07994     #   @return One instance of Polynomial
07995     def __init__(self, arg):
07996         # The exponent of a Polynomial should always be 1.
07997         # Maybe redefine the set_exponent function (inherited from Sum)
07998         # so that it raises an exception in the case of Polynomials.
07999         # To get something like (2x+3)³, it is possible to put the 2x+3
08000         # into a Product whose exponent would be 3.
08001         # (Or let it still so ? to avoid recursivity problems ?)
08002         # Anyway it wouldn't be good to set the exponent of a Polynomial to
08003         # something else than 1, it would cause problems because it is
08004         # everywhere assumed that the exponent is 1. That makes for example the
08005         # Sum of two Polynomials just a simple other Polynomial etc.
08006         CommutativeOperation.__init__(self)
08007 
08008         self._neutral = Item(0)
08009 
08010         self._force_inner_brackets_display = False
08011 
08012         if isinstance(arg, Sum):
08013             self._force_inner_brackets_display = \
08014                                               arg.force_inner_brackets_display
08015 
08016         # 1st CASE : DEFAULT
08017         if arg == DEFAULT:
08018             self._element.append(Monomial(DEFAULT))
08019             self._info.append(False)
08020 
08021         # 2d CASE : [Monomial|Polynomial] or Sum(Monomial|Polynomial)
08022         elif ((type(arg) == list) and len(arg) >= 1) or isinstance(arg, Sum):
08023             for i in range(len(arg)):
08024                 #DEBUG
08025                 debug.write("\nCopying : " + arg[i].dbg_str(),
08026                                        case=debug.init_in_polynomial)
08027                 if isinstance(arg[i], Monomial):
08028                     self._element.append(arg[i].clone())
08029                     self._info.append(False)
08030                 elif isinstance(arg[i], Polynomial):
08031                     for j in range(len(arg[i])):
08032                         self._element.append(arg[i].term[j].clone())
08033                         self._info.append(arg[i].info[j])
08034                 else:
08035                     raise error.UncompatibleType(arg[i],
08036                                                  " but in this list or Sum are" \
08037                                                  + " only Monomials & " \
08038                                                  + "Polynomials welcome. " \
08039                                                  "Given object : " \
08040                                                  + arg[i].dbg_str())
08041 
08042         # 3d CASE : (RANDOMLY, max_coeff, max_degree, length)
08043         elif type(arg) == tuple and len(arg) == 4 and arg[0] == RANDOMLY:
08044             # Let's determine first the desired length
08045             length = 0
08046 
08047             # ...either randomly
08048             if type(arg[3]) == tuple and len(arg[3]) == 2                     \
08049                and is_.an_integer(arg[3][1]) and arg[3][0] == RANDOMLY:
08050             #___
08051                 if arg[3][1] < 1:
08052                     raise error.OutOfRangeArgument(arg[3][1],
08053                                                    "This integer should be\
08054                                                    greater or equal to 1.")
08055                 else:
08056                     length = randomly.integer(1, arg[3][1])
08057 
08058             # ...or simply using the provided number
08059             elif is_.an_integer(arg[3]) and arg[3] >= 1:
08060                 length = arg[3]
08061             else:
08062                 raise error.UncompatibleType(arg,
08063                                             "(RANDOMLY,\
08064                                             max_coeff,\
08065                                             max_degree,\
08066                                             length|(RANDOMLY, max_length))")
08067 
08068             # Then create the Monomials randomly
08069             # Note that no null Monomial will be created (not interesting)
08070             # To avoid having twice the same degree, we will put the last
08071             # drawing off the list for one turn and put it in again for the
08072             # (over) next turn. To avoid having to many constant terms, we
08073             # determine their max number, and once there are enough of them,
08074             # the 0 degree (= constant terms) won't be put again in the drawing
08075             # list
08076             max_nb_constant_terms = max([
08077                                          int(CONSTANT_TERMS_MAXIMUM_RATIO
08078                                              * length),
08079                                          CONSTANT_TERMS_MINIMUM_NUMBER
08080                                          ])
08081 
08082             current_nb_constant_terms = 0
08083             deg_to_put_in_again = None
08084             the_last_drawing_has_to_be_put_in_again = False
08085             degrees_list = [i for i in range(arg[2] + 1)]
08086 
08087             for i in range(length):
08088                 # Let's determine the coefficient...
08089                 coeff = randomly.integer(1, arg[1])
08090 
08091                 # ...then the degree...
08092                 deg = randomly.pop(degrees_list)
08093 
08094                 if the_last_drawing_has_to_be_put_in_again:
08095                     degrees_list.append(deg_to_put_in_again)
08096 
08097                 the_last_drawing_has_to_be_put_in_again = True
08098 
08099                 deg_to_put_in_again = deg
08100 
08101                 if deg == 0:
08102                     current_nb_constant_terms += 1
08103                     if current_nb_constant_terms == max_nb_constant_terms:
08104                         degrees_list = [i + 1 for i in range(arg[2])]
08105                         the_last_drawing_has_to_be_put_in_again = False
08106 
08107                 # ...and finally append the new Monomial !
08108                 self.append(Monomial((randomly.sign(), coeff, deg)))
08109 
08110             #DEBUG
08111             debug.write("\n[init_in_polynomial]\n" \
08112                                    + "Randomly Created Polynomial is : \n" \
08113                                    + self.dbg_str() + "\n" \
08114                                    + str(self.info)\
08115                                    + "\n",
08116                                    case=debug.init_in_polynomial)
08117 
08118 
08119 
08120         else:
08121             raise error.UncompatibleType(arg,
08122                                          "DEFAULT |\
08123                                          [Monomial|Polynomial] |\
08124                                          Sum(Monomial|Polynomial) |\
08125                                          (RANDOMLY,\
08126                                           max_coeff,\
08127                                           max_degree,\
08128                                           [length|(RANDOMLY, max_length)])"
08129                                          )
08130 
08131 
08132 
08133 
08134 
08135     # --------------------------------------------------------------------------
08136     ##
08137     #   @brief Gets the maximal degree value that can be found in thePolynomial
08138     #   @return The maximal degree value that can be found in the Polynomial
08139     def get_max_degree(self):
08140         d = ZERO_POLYNOMIAL_DEGREE
08141 
08142         for i in range(len(self)):
08143             if self.term[i].degree > d:
08144                 d = self.term[i].degree
08145 
08146         return d
08147 
08148 
08149 
08150 
08151 
08152     # --------------------------------------------------------------------------
08153     ##
08154     #   @brief Gets the real Polynomial's degree
08155     #   @return The real Polynomial's degree
08156     def get_degree(self):
08157         # Let's glance at the degrees in reverse order
08158         for i in range(self.get_max_degree(), -1, -1):
08159             coefficients_sum = 0
08160             # Let's calculate the sum of coefficients for a given degree
08161             for j in range(len(self)):
08162                 # We check each Monomial of i-th degree
08163                 if self.term[j].degree == i:
08164                     if self.term[j].sign == '+':
08165                         coefficients_sum += self.term[j].coeff
08166                     else:
08167                         coefficients_sum -= self.term[j].coeff
08168 
08169             # If this sum isn't null, it means we found the term of highest
08170             # possible degree that's not null. Its degree is therefore the one
08171             # of the Polynomial
08172             if coefficients_sum != 0:
08173                 # As the Polynomial's exponent is assumed to be 1, we don't
08174                 # return i×exponent but just i.
08175                 return i
08176 
08177         return ZERO_POLYNOMIAL_DEGREE
08178 
08179 
08180 
08181 
08182 
08183     degree = property(get_degree, doc = 'Real degree of the Polynomial')
08184 
08185 
08186 
08187 
08188 
08189     # --------------------------------------------------------------------------
08190     ##
08191     #   @brief Raw display of the Polynomial (debugging method)
08192     #   @param options No option available so far
08193     #   @return " [[ term0, ..., termn ]]"
08194     def dbg_str(self, **options):
08195         resulting_string = " [["
08196         for i in range(len(self)):
08197             resulting_string += self.term[i].dbg_str()
08198             if i < len(self) - 1:
08199                 resulting_string += ", "
08200 
08201 
08202         resulting_string += "]] "
08203         return resulting_string
08204 
08205 
08206 
08207 
08208 
08209 # ------------------------------------------------------------------------------
08210 # --------------------------------------------------------------------------
08211 # ------------------------------------------------------------------------------
08212 ##
08213 # @class Expandable
08214 # @brief Mother class of all expandable objects
08215 class Expandable(Product):
08216 
08217 
08218 
08219 
08220 
08221     # --------------------------------------------------------------------------
08222     ##
08223     #   @brief Constructor.
08224     #   @param arg (Exponented, Exponented)|(RANDOMLY, <type>)
08225     #   (randomly) types details :
08226     #   - monom0_polyn1 will create this kind of objects : 5(3x-2)
08227     #   - monom1_polyn1 will create this kind of objects : -5x(2-3x)
08228     #   - polyn1_polyn1 will create this kind of objects : (5x+1)(3x-2)
08229     #   - minus_polyn1_polyn1 will create -<polyn1_polyn1>
08230     #   - sign_exp will create ±(±ax²±bx±c) | ±(±bx±c) | ±(±ax²±c) | ±(±ax²±bx)
08231     #   @param options reversed|randomly_reversed=<nb>
08232     #   Options details :
08233     #   - reversed will change the sums' order. This is useless if the sums
08234     #     are the same kind of objects (like (2x+3)(3x-7))
08235     #   - randomly_reversed=0.3 will change the sums' order in a ratio of 0.3
08236     #   @warning Might raise an UncompatibleType exception.
08237     def __init__(self, arg, **options):
08238         if not ((isinstance(arg, tuple) and len(arg) == 2) \
08239                 or isinstance(arg, Expandable)):
08240         #___
08241             raise error.UncompatibleType(arg,
08242                                          "That should be a tuple of two" \
08243                                          + " elements")
08244 
08245         CommutativeOperation.__init__(self)
08246 
08247         max_coeff = 12
08248 
08249         if 'max_coeff' in options and type(options['max_coeff']) == int:
08250             max_coeff = options['max_coeff']
08251 
08252         self._info = [False, False]
08253 
08254         self._neutral = Item(1)
08255 
08256         self._symbol = '×'
08257         self.str_openmark = "<XPD:"
08258         self.str_closemark = ":XPD>"
08259 
08260         sum1 = None
08261         sum2 = None
08262 
08263         # 1st CASE
08264         # another Expandable to copy
08265         if isinstance(arg, Expandable):
08266             self._compact_display = arg.compact_display
08267             self.reset_element()
08268             for i in range(len(arg.element)):
08269                 self._element.append(arg.element[i].clone())
08270             for i in range(len(arg.info)):
08271                 self._info.append(arg.info[i])
08272 
08273 
08274         # 2d CASE
08275         # given Exponenteds
08276         else:
08277             if isinstance(arg[0], Exponented) \
08278                and isinstance(arg[1], Exponented):
08279             #___
08280                 if not isinstance(arg[0], Sum):
08281                     sum1 = Sum([arg[0].clone()])
08282                 else:
08283                     sum1 = arg[0].clone()
08284                 if not isinstance(arg[1], Sum):
08285                     sum2 = Sum([arg[1].clone()])
08286                 else:
08287                     sum2 = arg[1].clone()
08288 
08289         # 3d CASE
08290         # RANDOMLY
08291             elif arg[0] == RANDOMLY:
08292                 if arg[1] == 'monom0_polyn1':
08293                     sum1 = Sum(Monomial((RANDOMLY, max_coeff, 0)))
08294 
08295                     if sum1.element[0].factor[0].raw_value == 1:
08296                         sum1.element[0].factor[0].set_value_inside(Value(\
08297                                                    randomly.integer(2,
08298                                                                     max_coeff)))
08299 
08300                     sum2 = Polynomial((RANDOMLY, max_coeff, 1, 2))
08301 
08302                 elif arg[1] == 'monom1_polyn1':
08303                     sum1 = Sum(Monomial((RANDOMLY, max_coeff, 1)))
08304                     sum1.element[0].set_degree(1)
08305                     sum2 = Polynomial((RANDOMLY, max_coeff, 1, 2))
08306 
08307                 elif arg[1] == 'polyn1_polyn1':
08308                     sum1 = Polynomial((RANDOMLY, max_coeff, 1, 2))
08309                     sum2 = Polynomial((RANDOMLY, max_coeff, 1, 2))
08310 
08311                 elif arg[1] == 'minus_polyn1_polyn1':
08312                     sum1 = Sum(Monomial(('-', 1, 0)))
08313                     sum2 = Sum(Expandable((RANDOMLY, 'polyn1_polyn1')))
08314 
08315                 elif arg[1] == 'sign_exp':
08316                     sum1 = Sum(Monomial((randomly.sign(plus_signs_ratio=0.25),
08317                                          1, 0
08318                                        ))
08319                               )
08320 
08321                     if randomly.heads_or_tails():
08322                         sum2 = Sum(Polynomial((RANDOMLY, 15, 2, 3)))
08323                     else:
08324                         sum2 = Sum(Polynomial((RANDOMLY, 15, 2, 2)))
08325 
08326 
08327                 else:
08328                     raise error.UncompatibleType(arg[1],
08329                                                  'monom0_polyn1|monom1_polyn1'\
08330                                                  + '|polyn1_polyn1' \
08331                                                  + 'minus_polyn1_polyn1' \
08332                                                  + 'sign_exp')
08333 
08334             else:
08335                 raise error.UncompatibleType(arg,
08336                                              "(Exponented, Exponented)|" \
08337                                              + "(RANDOMLY, <type>)")
08338 
08339             if 'reversed' in options \
08340                or ('randomly_reversed' in options \
08341                    and is_.a_number(options['randomly_reversed']) \
08342                 and randomly.decimal_0_1() <= options['randomly_reversed']):
08343             #___
08344                 self._element.append(sum2)
08345                 self._element.append(sum1)
08346             else:
08347                 self._element.append(sum1)
08348                 self._element.append(sum2)
08349 
08350             # In the case of the Expandable +(...), force the displaying
08351             # of the brackets
08352             if len(self.factor[0]) == 1 \
08353                and self.factor[0].element[0].is_displ_as_a_single_1():
08354             #___
08355                 self.factor[1].set_force_inner_brackets_display(True)
08356 
08357 
08358 
08359 
08360     # --------------------------------------------------------------------------
08361     ##
08362     #   @brief The expanded object, like 2×(x+3) would return 2×x + 2×3
08363     def expand(self):
08364         # First : imbricated Sums are managed recursively
08365         if len(self.factor[0]) == 1 \
08366            and isinstance(self.factor[0].term[0], Sum):
08367         #___
08368             copy = Expandable(self)
08369             copy.set_element(0, copy.factor[0].term[0])
08370             return copy.expand()
08371 
08372         if len(self.factor[1]) == 1 \
08373            and isinstance(self.factor[1].term[0], Sum):
08374         #___
08375             copy = Expandable(self)
08376             copy.set_element(1, copy.factor[1].term[0])
08377             return copy.expand()
08378 
08379         # And here we go :
08380         terms_list = list()
08381 
08382         for i in range(len(self.factor[0])):
08383             for j in range(len(self.factor[1])):
08384                 temp = Product([self.factor[0].term[i],
08385                                 self.factor[1].term[j]
08386                                ])
08387                 temp.set_compact_display(False)
08388                 terms_list.append(temp)
08389 
08390         if len(self.factor[0]) == 1 \
08391            and (self.factor[0].term[0].is_displ_as_a_single_minus_1() \
08392                 or self.factor[0].term[0].is_displ_as_a_single_1()):
08393         #___
08394             for i in range(len(terms_list)):
08395                 terms_list[i] = terms_list[i].reduce_()
08396 
08397         return Sum(terms_list)
08398 
08399 
08400 
08401 
08402 
08403     # --------------------------------------------------------------------------
08404     ##
08405     #   @brief The expanded & reduced object, like 2×(x+3) would return 2x + 6
08406     #   Take care that the resulting Sum might not be reduced itself.
08407     #   For instance, (3 + x)(2x - 5) would return 6x - 15 + 2x² - 5x
08408     #   The rest of the calculation has to be done with the Sum's reduction
08409     #   method
08410     #   @obsolete ?
08411     def expand_and_reduce_(self):
08412         expanded_objct = self.expand()
08413 
08414         terms_list = list()
08415 
08416         for term in expanded_objct:
08417             terms_list.append(term.reduce_())
08418 
08419         return Sum(terms_list)
08420 
08421 
08422 
08423 
08424 
08425     # --------------------------------------------------------------------------
08426     ##
08427     #   @brief Returns the expanded object as a Sum
08428     def expand_and_reduce_next_step(self, **options):
08429         copy = Expandable(self)
08430 
08431         a_factor_at_least_has_been_modified = False
08432 
08433         # to expand the ± signs first in the case of a Sum as second factor
08434         if len(self.factor[0]) == 1 \
08435                and (self.factor[0].term[0].is_displ_as_a_single_1() \
08436                  or self.factor[0].term[0].is_displ_as_a_single_minus_1()\
08437                     ):
08438             #___
08439                 if isinstance(self.factor[1], Sum) \
08440                    and len(self.factor[1]) > 1 \
08441                    and self.factor[1].exponent.is_displ_as_a_single_1():
08442                 #___
08443                     return self.expand()
08444 
08445         # check if any of the factors needs to be reduced
08446         for i in range(len(copy)):
08447             test = copy.factor[i].expand_and_reduce_next_step(**options)
08448             if test != None:
08449                 copy.set_element(i, test)
08450                 a_factor_at_least_has_been_modified = True
08451 
08452         if a_factor_at_least_has_been_modified:
08453             return copy
08454 
08455         # no factor of the Expandable needs to be reduced
08456         else:
08457             return self.expand()
08458 
08459 
08460 
08461 
08462 
08463     # --------------------------------------------------------------------------
08464     ##
08465     #   @brief True
08466     #   @return True
08467     def is_expandable(self):
08468         return True
08469 
08470 
08471 
08472 
08473 
08474 # ------------------------------------------------------------------------------
08475 # --------------------------------------------------------------------------
08476 # ------------------------------------------------------------------------------
08477 ##
08478 # @class BinomialIdentity
08479 # @brief These objects are expanded using : (a+b)² = a² + 2ab + b², (a-b)² =
08480 # a² -2ab + b² and (a+b)(a-b) = a² - b²
08481 # This object is a Product of two Sums but won't be displayed as is in the
08482 # case of (a+b)² and (a-b)².
08483 # For instance, (3x-2)(3x-2) will be displayed (3x-2)². It would be
08484 # complicated to derive BinomialIdentity from a Sum since a Sum isn't
08485 # expandable.
08486 # Let it derive simultaneously from Sum and Expandable could create problems
08487 # when calling the into_str function (which of the Sum's or the Product's
08488 # would be called ?).
08489 class BinomialIdentity(Expandable):
08490 
08491 
08492 
08493 
08494 
08495     # --------------------------------------------------------------------------
08496     ##
08497     #   @brief Constructor.
08498     #   @param arg (Exponented, Exponented)|(RANDOMLY, <type>)
08499     #   Types details :
08500     #   - sum_square : matches (a+b)²
08501     #   - difference_square : matches (a-b)²
08502     #   - squares_difference : matches (a+b)(a-b) (name comes from a²-b²)
08503     #   - any : matches any of (a+b)², (a-b)², (-a+b)², (-a-b)², (a+b)(a-b)...
08504     #   - numeric_* : matches a numeric one...
08505     #   @param options squares_difference
08506     #   Options details :
08507     #   - in the case of arg being (Exponented, Exponented),
08508     #     squares_difference let produce a (a+b)(a-b) from the given
08509     #     Exponenteds instead of a default (a+b)²
08510     #   @warning Might raise an UncompatibleType exception.
08511     #   @todo fix the square_difference option (see source code)
08512     def __init__(self, arg, **options):
08513         if not (type(arg) == tuple and len(arg) == 2) \
08514            and not isinstance(arg, BinomialIdentity):
08515         #___
08516             raise error.UncompatibleType(arg,
08517                                          "That should be a tuple of two" \
08518                                          + "elements or a BinomialIdentity")
08519 
08520         # The exponent (like 3 in ((3-x)(4+5x))³)
08521         CommutativeOperation.__init__(self)
08522 
08523         self._info = [False, False]
08524 
08525         self._neutral = Item(1)
08526 
08527         self._symbol = '×'
08528         self.str_openmark = "BI:: "
08529         self.str_closemark = " ::BI"
08530 
08531         # This property is to set to help the into_str and expand functions
08532         # It has to be either 'positive' which refers to (a+b)² objects,
08533         # or 'negative', which refers to (a-b)² objects,
08534         # or 'squares_difference' which refers to (a+b)(a-b) objects
08535         self._kind = ""
08536 
08537         # 1st CASE
08538         # Another BinomialIdentity to copy
08539         if isinstance(arg, BinomialIdentity):
08540             for i in range(len(arg.element)):
08541                 self._element.append(arg.element[i].clone())
08542             self._compact_display = arg.compact_display
08543             for i in range(len(arg.info)):
08544                 self._info.append(arg.info[i])
08545             self._exponent = arg.exponent.clone()
08546             self._kind = arg.kind
08547             self._a = arg.a.clone()
08548             self._b = arg.b.clone()
08549 
08550         # 2d CASE
08551         # given Exponented
08552         elif isinstance(arg[0], Exponented) and isinstance(arg[1], Exponented):
08553             a = arg[0].clone()
08554             b = arg[1].clone()
08555 
08556             self._a = a
08557             self._b = b
08558 
08559             self._element.append(Sum([a, b]))
08560 
08561             if b.get_sign() == '+':
08562                 self._kind = 'sum_square'
08563             else:
08564                 self._kind = 'difference_square'
08565 
08566             if 'squares_difference' in options:
08567                 # fix it : b will be reduced every time ! it is not
08568                 # the desired effect !!
08569                 b = Product([Item(-1), b]).reduce_()
08570                 self._kind = 'squares_difference'
08571 
08572             self._element.append(Sum([a, b]))
08573 
08574         # 3d CASE
08575         # RANDOMLY
08576         elif arg[0] == RANDOMLY:
08577 
08578             if arg[1] == 'numeric_sum_square'\
08579                 or arg[1] == 'numeric_difference_square' \
08580                 or arg[1] == 'numeric_squares_difference':
08581             #___
08582                 a_list = [20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400,
08583                           500, 600, 700, 800, 1000]
08584 
08585                 b_list = [1, 2, 3]
08586 
08587                 a_choice = randomly.pop(a_list)
08588 
08589                 if a_choice >= 200:
08590                     b_list = [1, 2]
08591 
08592                 a = Monomial(('+',
08593                               a_choice,
08594                               0))
08595 
08596                 b_choice = randomly.pop(b_list)
08597 
08598                 b = Monomial(('+',
08599                               b_choice,
08600                               0))
08601 
08602             else :
08603                 degrees_list = [0, 1]
08604                 coeff_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
08605 
08606                 a = Monomial(('+',
08607                               randomly.pop(coeff_list),
08608                               randomly.pop(degrees_list)))
08609 
08610                 b = Monomial(('+',
08611                               randomly.pop(coeff_list),
08612                               randomly.pop(degrees_list)))
08613 
08614             self._a = a
08615             self._b = b
08616 
08617 
08618             if arg[1] == 'sum_square' or arg[1] == 'numeric_sum_square':
08619                 self._element.append(Sum([a, b]))
08620                 self._element.append(Sum([a, b]))
08621                 self._kind = 'sum_square'
08622 
08623             elif arg[1] == 'difference_square' \
08624                 or arg[1] == 'numeric_difference_square':
08625             #___
08626                 b.set_sign('-')
08627                 self._element.append(Sum([a, b]))
08628                 self._element.append(Sum([a, b]))
08629                 self._kind = 'difference_square'
08630 
08631             elif arg[1] == 'squares_difference' \
08632                 or arg[1] == 'numeric_squares_difference':
08633             #___
08634                 sums_list = list()
08635                 sums_list.append(Sum([a, b]))
08636                 minus_b = Monomial(b)
08637                 minus_b.set_sign('-')
08638                 sums_list.append(Sum([a, minus_b]))
08639                 self._element.append(randomly.pop(sums_list))
08640                 self._element.append(randomly.pop(sums_list))
08641                 self._kind = 'squares_difference'
08642 
08643 
08644             elif arg[1] == 'any':
08645                 if randomly.heads_or_tails():
08646                     # doesn't make sense to have (-3 - 4x)² as a BI,
08647                     # it's uselessly complicated for the pupils
08648                     a.set_sign('+')
08649                     b.set_sign(randomly.sign())
08650                     self._a = a
08651                     self._b = b
08652                     self._element.append(Sum([a, b]))
08653                     self._element.append(Sum([a, b]))
08654                     if b.get_sign() == '+':
08655                         self._kind = 'sum_square'
08656                     else:
08657                         self._kind = 'difference_square'
08658 
08659                 else:
08660                     # doesn't make sense to have (-3 - 4x)² as a BI,
08661                     # it's uselessly complicated for the pupils
08662                     a.set_sign('+')
08663                     b.set_sign(randomly.sign())
08664                     self._a = a
08665                     self._b = b
08666                     self._element.append(Sum([a, b]))
08667                     minus_b = Monomial(b)
08668                     minus_b.set_sign(sign_of_product(['-', b.sign]))
08669                     self._element.append(Sum([a, minus_b]))
08670                     self._kind = 'squares_difference'
08671 
08672             else:
08673                 raise error.UncompatibleType(arg[1],
08674                                              'sum_square|difference_square|' \
08675                                              + 'squares_difference|' \
08676                                              + 'numeric_{sum_square|differen' \
08677                                              + 'ce_square|' \
08678                                              + 'squares_difference}|any')
08679 
08680         else:
08681             raise error.UncompatibleType(arg,
08682                                          "(Exponented, Exponented)|" \
08683                                          + "(RANDOMLY, <type>)")
08684 
08685 
08686 
08687 
08688 
08689 
08690     # --------------------------------------------------------------------------
08691     ##
08692     #   @brief Gets the 'a' term of the BinomialIdentity
08693     #   @return the content of the 'a' term
08694     def get_a(self):
08695         return self._a
08696 
08697 
08698 
08699 
08700 
08701     # --------------------------------------------------------------------------
08702     ##
08703     #   @brief Gets the 'b' term of the BinomialIdentity
08704     #   @return the content of the 'b' term
08705     def get_b(self):
08706         return self._b
08707 
08708 
08709 
08710 
08711 
08712     # --------------------------------------------------------------------------
08713     ##
08714     #   @brief Gets the kind of BinomialIdentity it is
08715     #   @return 'sum_square'|'difference_square'|'squares_difference'
08716     def get_kind(self):
08717         return self._kind
08718 
08719 
08720 
08721 
08722     a = property(get_a,
08723                  doc="Gets the 'a' term of the BinomialIdentity")
08724 
08725     b = property(get_b,
08726                  doc="Gets the 'b' term of the BinomialIdentity")
08727 
08728     kind = property(get_kind,
08729                     doc="kind of BinomialIdentity it, " \
08730                 + "e.g. 'sum_square'|'difference_square'|'squares_difference'"
08731                    )
08732 
08733 
08734 
08735 
08736 
08737     # --------------------------------------------------------------------------
08738     ##
08739     #   @brief Creates a string of the given object in the given ML
08740     #   @param options Any options
08741     #   @return The formated string
08742     def into_str(self, **options):
08743         global expression_begins
08744 
08745         #if 'force_expression_begins' in options \
08746         #   and options['force_expression_begins'] == True:
08747         #___
08748         #    expression_begins = options['force_expression_begins']
08749         #    options['force_expression_begins'] = False
08750 
08751         if self.kind == 'squares_difference':
08752             return Product(self.factor).into_str(**options)
08753 
08754         else:
08755             squared_sum = Sum([self.a, self.b])
08756             squared_sum.set_exponent(2)
08757             return squared_sum.into_str(**options)
08758 
08759 
08760 
08761 
08762 
08763     # --------------------------------------------------------------------------
08764     ##
08765     #   @brief The expanded object, like (2x+3)² would return (2x)²+2×2x×3+3²
08766     def expand(self):
08767 
08768         #DEBUG
08769         debug.write( \
08770             "\nEntering :\n[expand][BinomialIdentity]\n" \
08771             + "self.kind = " + str(self.kind) \
08772             + "\n",
08773             case=debug.expand_in_special_identity)
08774 
08775         if self.kind == 'sum_square':
08776             square_a = Product(self.a)
08777             square_a.set_exponent(2)
08778             double_product = Product([2, self.a, self.b])
08779             square_b = Product(self.b)
08780             square_b.set_exponent(2)
08781             return Sum([square_a,
08782                         double_product,
08783                         square_b])
08784 
08785         elif self.kind == 'difference_square':
08786             square_a = Product(self.a)
08787             square_a.set_exponent(2)
08788             b = self.b
08789             b.set_sign('+')
08790             double_product = Product([-2, self.a, b])
08791             square_b = Product(b)
08792             square_b.set_exponent(2)
08793             return Sum([square_a,
08794                         double_product,
08795                         square_b])
08796 
08797         elif self.kind == 'squares_difference':
08798             square_a = Product(self.a)
08799             square_a.set_exponent(2)
08800             b = self.b
08801             b.set_sign('+')
08802             square_b = Product(b)
08803             square_b.set_exponent(2)
08804             square_b = Product([Item(-1), square_b])
08805             return Sum([square_a,
08806                         square_b])
08807 
08808 
08809 
08810 
08811     # --------------------------------------------------------------------------
08812     ##
08813     #   @brief Returns the next step of reduction of the BinomialIdentity
08814     #   @return Exponented
08815     def expand_and_reduce_next_step(self, **options):
08816         return self.expand()
08817 
08818 
08819 
08820 
08821