mathmaker
0.4(alpha)
|
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