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