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