mathmaker  0.6(alpha)
mamk_misc/doc/mathmaker4doxygen/core/root_calculus.py
00001 # -*- coding: utf-8 -*-
00002 
00003 # Mathmaker creates automatically maths exercises sheets with their answers
00004 # Copyright 2006-2014 Nicolas Hainaux <nico_h@users.sourceforge.net>
00005 
00006 # This file is part of Mathmaker.
00007 
00008 # Mathmaker is free software; you can redistribute it and/or modify
00009 # it under the terms of the GNU General Public License as published by
00010 # the Free Software Foundation; either version 3 of the License, or
00011 # any later version.
00012 
00013 # Mathmaker is distributed in the hope that it will be useful,
00014 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016 # GNU General Public License for more details.
00017 
00018 # You should have received a copy of the GNU General Public License
00019 # along with Mathmaker; if not, write to the Free Software
00020 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00021 
00022 # ------------------------------------------------------------------------------
00023 # --------------------------------------------------------------------------
00024 # ------------------------------------------------------------------------------
00025 ##
00026 # @package core.root_calculus
00027 # @brief Mostly abstract classes for mathematical calculus objects.
00028 
00029 from base import *
00030 from lib.common import alphabet
00031 from lib import is_
00032 from lib.maths_lib import *
00033 from decimal import *
00034 from lib.common.cst import *
00035 import locale
00036 
00037 markup_choice = cfg.get_value_from_file('MARKUP', 'USE')
00038 
00039 if markup_choice == 'latex':
00040     from lib.common.latex import MARKUP
00041 
00042 try:
00043     locale.setlocale(locale.LC_ALL, LANGUAGE + '.' + ENCODING)
00044 except:
00045     locale.setlocale(locale.LC_ALL, '')
00046 
00047 
00048 
00049 
00050 # ------------------------------------------------------------------------------
00051 # --------------------------------------------------------------------------
00052 # ------------------------------------------------------------------------------
00053 ##
00054 # @class Evaluable
00055 # @brief Abstract mother class of all (evaluable) mathematical objects
00056 # It is not possible to implement any Evaluable object
00057 class Evaluable(Printable):
00058 
00059 
00060 
00061 
00062 
00063     # --------------------------------------------------------------------------
00064     ##
00065     #   @brief If the object is literal, returns the first letter
00066     # The first term of a Sum, the first factor of a Product etc.
00067     def get_first_letter(self):
00068         raise error.MethodShouldBeRedefined(self, 'get_first_letter')
00069 
00070 
00071 
00072 
00073 
00074     # --------------------------------------------------------------------------
00075     ##
00076     #   @brief Returns the numeric value of the object
00077     def evaluate(self):
00078         raise error.MethodShouldBeRedefined(self, 'evaluate')
00079 
00080 
00081 
00082 
00083 
00084     # --------------------------------------------------------------------------
00085     ##
00086     #   @brief To check if this contains a rounded number...
00087     #   @return True or False
00088     def contains_a_rounded_number(self):
00089         raise error.MethodShouldBeRedefined(self, 'contains_a_rounded_number')
00090 
00091 
00092 
00093 
00094 
00095     # --------------------------------------------------------------------------
00096     ##
00097     #   @brief True if the object contains exactly the given objct
00098     #   It can be used to detect objects embedded in a Sum or a Product that
00099     #   contain only one term (or factor)
00100     #   @param objct The object to search for
00101     #   @return True if the object contains exactly the given objct
00102     def contains_exactly(self, objct):
00103         raise error.MethodShouldBeRedefined(self, 'contains_exactly')
00104 
00105 
00106 
00107 
00108 
00109     # --------------------------------------------------------------------------
00110     ##
00111     #   @brief Sort order : numerics < sorted literals
00112     #   @return -1, 0 or +1
00113     def alphabetical_order_cmp(self, other_objct):
00114 
00115         if self.is_numeric() and other_objct.is_numeric():
00116             return 0
00117 
00118         elif self.is_literal() and other_objct.is_numeric():
00119             return 1
00120 
00121         elif self.is_numeric() and other_objct.is_literal():
00122             return -1
00123 
00124         elif self.is_literal() and other_objct.is_literal():
00125             self_value = self.get_first_letter()
00126             other_value = other_objct.get_first_letter()
00127 
00128             # let's compare
00129             if self_value == other_value:
00130                 return 0
00131             elif alphabet.order[self_value] > alphabet.order[other_value]:
00132                 return 1
00133             else:
00134                 return -1
00135 
00136 
00137 
00138 
00139 
00140     # --------------------------------------------------------------------------
00141     ##
00142     #   @brief True if the object only contains numeric objects
00143     def is_numeric(self):
00144         raise error.MethodShouldBeRedefined(self, 'is_numeric')
00145 
00146 
00147 
00148 
00149 
00150     # --------------------------------------------------------------------------
00151     ##
00152     #   @brief True if the object only contains literal objects
00153     def is_literal(self):
00154         raise error.MethodShouldBeRedefined(self, 'is_literal')
00155 
00156 
00157 
00158 
00159 
00160     # --------------------------------------------------------------------------
00161     ##
00162     #   @brief True if the evaluated value of an object is null
00163     def is_null(self):
00164         raise error.MethodShouldBeRedefined(self, 'is_null')
00165 
00166 
00167 
00168 
00169 
00170 # ------------------------------------------------------------------------------
00171 # --------------------------------------------------------------------------
00172 # ------------------------------------------------------------------------------
00173 ##
00174 # @class Calculable
00175 # @brief Abstract mother class of all (calculable) mathematical objects
00176 # It is not possible to implement any Calculable object
00177 class Calculable(Evaluable):
00178 
00179 
00180 
00181 
00182     # --------------------------------------------------------------------------
00183     ##
00184     #   @brief Returns the list of elements to iter over
00185     def get_iteration_list(self):
00186         raise error.MethodShouldBeRedefined(self, 'get_iteration_list')
00187 
00188 
00189 
00190 
00191     # --------------------------------------------------------------------------
00192     ##
00193     #   @brief Returns the next Calculable object during a numeric calculation
00194     def calculate_next_step(self, **options):
00195         raise error.MethodShouldBeRedefined(self, 'calculate_next_step')
00196 
00197 
00198 
00199 
00200 
00201     # --------------------------------------------------------------------------
00202     ##
00203     #   @brief Returns the next step of expansion/reduction of the Sum
00204     #   So, either the Sum of its expanded/reduced terms,
00205     #   or the Sum itself reduced, or None
00206     #   @return Exponented
00207     def expand_and_reduce_next_step(self, **options):
00208         raise error.MethodShouldBeRedefined(self,
00209                                             'expand_and_reduce_next_step')
00210 
00211 
00212 
00213 
00214 
00215     # --------------------------------------------------------------------------
00216     ##
00217     #   @brief Returns the number of elements of the Exponented
00218     def __len__(self):
00219         raise error.MethodShouldBeRedefined(self, "__len__()")
00220 
00221 
00222 
00223 
00224 
00225     # --------------------------------------------------------------------------
00226     ##
00227     #   @brief This will iter over the content of the Calculable
00228     def __iter__(self):
00229         return iter(self.get_iteration_list())
00230 
00231     def __next__(self):
00232         return next(self.get_iteration_list())
00233 
00234 
00235 
00236 
00237 
00238     # --------------------------------------------------------------------------
00239     ##
00240     #   @brief True if the usual writing rules require a × between two factors
00241     #   @param objct The other one
00242     #   @param position The position (integer) of self in the Product
00243     #   @return True if the writing rules require × between self & obj
00244     def multiply_symbol_is_required(self, objct, position):
00245         raise error.MethodShouldBeRedefined(self,
00246                                             'multiply_symbol_is_required')
00247 
00248 
00249 
00250 
00251 
00252     # --------------------------------------------------------------------------
00253     ##
00254     #   @brief True if the argument requires brackets in a product
00255     #   For instance, a Sum with several terms or a negative Item
00256     #   @param position The position of the object in the Product
00257     #   @return True if the object requires brackets in a Product
00258     def requires_brackets(self, position):
00259         raise error.MethodShouldBeRedefined(self, 'requires_brackets')
00260 
00261 
00262 
00263 
00264 
00265     # --------------------------------------------------------------------------
00266     ##
00267     #   @brief True if the argument requires inner brackets
00268     #   The reason for requiring them is having an exponent different
00269     #   from 1 and several terms or factors (in the case of Products & Sums)
00270     #   @return True if the object requires inner brackets
00271     def requires_inner_brackets(self):
00272         raise error.MethodShouldBeRedefined(self,
00273                                             'requires_innner_brackets')
00274 
00275 
00276 
00277 
00278 
00279 
00280     # --------------------------------------------------------------------------
00281     ##
00282     #   @brief Uses the given lexicon to substitute literal Values in self
00283     def substitute(self, subst_dict):
00284         for elt in self:
00285             elt.substitute(subst_dict)
00286 
00287 
00288 
00289 
00290     # --------------------------------------------------------------------------
00291     ##
00292     #   @brief True if the object can be displayed as a single 1
00293     # For instance, the Product 1×1×1×1 or the Sum 0 + 0 + 1 + 0
00294     def is_displ_as_a_single_1(self):
00295         raise error.MethodShouldBeRedefined(self,
00296                                             'is_displ_as_a_single_1')
00297 
00298 
00299 
00300 
00301 
00302     # --------------------------------------------------------------------------
00303     ##
00304     #   @brief True if the object can be displayed as a single int
00305     def is_displ_as_a_single_int(self):
00306         raise error.MethodShouldBeRedefined(self,
00307                                             'is_displ_as_a_single_1')
00308 
00309 
00310 
00311 
00312 
00313     # --------------------------------------------------------------------------
00314     ##
00315     #   @brief True if the object can be displayed as a single -1
00316     # For instance, the Product 1×1×(-1)×1 or the Sum 0 + 0 - 1 + 0
00317     def is_displ_as_a_single_minus_1(self):
00318         raise error.MethodShouldBeRedefined(self,
00319                                            'is_displ_as_a_single_minus_1')
00320 
00321 
00322 
00323 
00324 
00325     # --------------------------------------------------------------------------
00326     ##
00327     #   @brief True if the object can be displayed as a single 0
00328     # For instance, the Product 0×0×0×0 (but not 0×1)
00329     # or the Sum 0 + 0 + 0 (but not 0 + 1 - 1)
00330     def is_displ_as_a_single_0(self):
00331         raise error.MethodShouldBeRedefined(self,
00332                                             'is_displ_as_a_single_0')
00333 
00334 
00335 
00336 
00337 
00338     # --------------------------------------------------------------------------
00339     ##
00340     #   @brief True if the object is or only contains one numeric Item
00341     def is_displ_as_a_single_numeric_Item(self):
00342         raise error.MethodShouldBeRedefined(self,
00343                                       'is_displ_as_a_single_numeric_Item')
00344 
00345 
00346 
00347 
00348 
00349     # --------------------------------------------------------------------------
00350     ##
00351     #   @brief True if the object can be considered as a neutral element
00352     def is_displ_as_a_single_neutral(self, elt):
00353         raise error.MethodShouldBeRedefined(self,
00354                                       'is_displ_as_a_single_neutral')
00355 
00356 
00357 
00358 
00359 
00360 # ------------------------------------------------------------------------------
00361 # --------------------------------------------------------------------------
00362 # ------------------------------------------------------------------------------
00363 ##
00364 # @class Signed
00365 # @brief Signed objects: CommutativeOperations (Sums&Products), Items, Quotients...
00366 # Any Signed must have a sign field
00367 class Signed(Calculable):
00368 
00369 
00370 
00371     # --------------------------------------------------------------------------
00372     ##
00373     #   @brief Constructor
00374     #   @return A Signed, though it can't really be used as is
00375     def __init__(self):
00376         self._sign = '+'
00377 
00378 
00379 
00380 
00381 
00382     # --------------------------------------------------------------------------
00383     ##
00384     #   @brief Returns the number of minus signs in the object
00385     def get_minus_signs_nb(self):
00386         raise error.MethodShouldBeRedefined(self, 'get_minus_signs_nb')
00387 
00388 
00389 
00390 
00391 
00392     # --------------------------------------------------------------------------
00393     ##
00394     #   @brief Returns the sign of the object
00395     def get_sign(self):
00396         return self._sign
00397     # --------------------------------------------------------------------------
00398     sign = property(get_sign,
00399                     doc = "Sign of the object")
00400 
00401 
00402 
00403 
00404 
00405     # --------------------------------------------------------------------------
00406     ##
00407     #   @brief Set the sign of the object
00408     #   @param  arg String being '+' or '-' or number being +1 or -1
00409     #   @warning Relays an exception if arg is not of the types described
00410     def set_sign(self, arg):
00411         if is_.a_sign(arg):
00412             self._sign = arg
00413         elif arg == 1:
00414             self._sign = '+'
00415         elif arg == -1:
00416             self._sign = '-'
00417         elif isinstance(arg, Calculable):
00418             if arg.is_displ_as_a_single_1():
00419                 self._sign = '+'
00420             elif arg.is_displ_as_a_single_minus_1():
00421                 self._sign = '-'
00422         else:
00423             raise error.UncompatibleType(self, "'+' or '-' or 1 or -1")
00424 
00425 
00426 
00427 
00428 
00429     # --------------------------------------------------------------------------
00430     ##
00431     #   @brief Changes the sign of the object
00432     def set_opposite_sign(self):
00433         if self.get_sign() == '-':
00434             self.set_sign('+')
00435         elif self.get_sign() == '+':
00436             self.set_sign('-')
00437         else:
00438             # this case should never happen, just to secure the code
00439             raise error.WrongObject("The sign of the object " \
00440                                     + self.dbg_str() \
00441                                     + " is " \
00442                                     + str(self.sign) \
00443                                     + " instead of '+' or '-'.")
00444 
00445 
00446 
00447 
00448 
00449     # --------------------------------------------------------------------------
00450     ##
00451     #   @brief True if object's *sign* is '-' (ie -(-1) would be "negative")
00452     def is_negative(self):
00453         return self.sign == '-'
00454 
00455 
00456 
00457 
00458 
00459 
00460     # --------------------------------------------------------------------------
00461     ##
00462     #   @brief True if object's *sign* is '+'
00463     def is_positive(self):
00464         return self.sign == '+'
00465 
00466 
00467 
00468 
00469 
00470 
00471 # ------------------------------------------------------------------------------
00472 # --------------------------------------------------------------------------
00473 # ------------------------------------------------------------------------------
00474 ##
00475 # @class Value
00476 # @brief This class embedds Numbers & Strings into a basic object. It doesn't
00477 #        have any exponent field (always set to 1), so does not belong to
00478 #        Exponenteds. This is the only place where numbers are used directly.
00479 #        The Item class for instance, contains Values in its fields, not
00480 #        numbers.
00481 #        This to be sure any content of any field (even if only a simple
00482 #        number is to be saved in the field) can be tested & managed
00483 #        as an object in any other class than Value.
00484 #        Up from 2010/11/19, it is decided that all numeric Values will contain
00485 #        a Decimal.decimal number.
00486 class Value(Signed):
00487 
00488 
00489 
00490 
00491 
00492     # --------------------------------------------------------------------------
00493     ##
00494     #   @brief Constructor
00495     #   @warning Might raise an UncompatibleType exception
00496     #            or InvalidOperation
00497     #   @param arg Number|String
00498     #   If the argument is not of one of these kinds, an exception
00499     #   will be raised.
00500     #   @return One instance of Value
00501     def __init__(self, arg, **options):
00502         Signed.__init__(self)
00503 
00504         self._has_been_rounded = False
00505 
00506         self._unit = ""
00507 
00508         if 'unit' in options and options['unit'] in AVAILABLE_UNITS:
00509             self._unit = options['unit']
00510 
00511         if type(arg) == float                                             \
00512             or type(arg) == int                                          \
00513             or type(arg) == Decimal:
00514         #___
00515             self._raw_value = Decimal(str(arg))
00516             if arg >= 0:
00517                 self._sign = '+'
00518             else:
00519                 self._sign = '-'
00520 
00521         elif type(arg) == str:
00522             if is_.a_numerical_string(arg):
00523                 self._raw_value = Decimal(arg)
00524             else:
00525                 self._raw_value = arg
00526 
00527             if len(arg) >= 1 and arg[0] == '-':
00528                 self._sign = '-'
00529 
00530         elif isinstance(arg, Value):
00531             self._raw_value = arg.raw_value
00532             self._has_been_rounded = arg.has_been_rounded
00533             self._unit = arg.unit
00534 
00535         # All other unforeseen cases : an exception is raised.
00536         else:
00537             raise error.UncompatibleType(arg, "Number|String")
00538 
00539 
00540 
00541 
00542 
00543     # --------------------------------------------------------------------------
00544     ##
00545     #   @brief If the object is literal, returns the value
00546     def get_first_letter(self):
00547         if self.is_literal():
00548             return self.raw_value
00549         else:
00550             raise error.UncompatibleType(self, "str, i.e. literal Value")
00551     # --------------------------------------------------------------------------
00552     ##
00553     #   @brief Returns the "has been rounded" state of the Value
00554     def get_has_been_rounded(self):
00555         return self._has_been_rounded
00556     # --------------------------------------------------------------------------
00557     ##
00558     #   @brief Returns the list of elements to iter over
00559     def get_iteration_list(self):
00560         return [self.raw_value]
00561     # --------------------------------------------------------------------------
00562     ##
00563     #   @brief Returns the number of minus signs in the object
00564     def get_minus_signs_nb(self):
00565         raise error.MethodShouldBeRedefined(self, 'get_minus_signs_nb')
00566     # --------------------------------------------------------------------------
00567     ##
00568     #   @brief Returns the raw value contained in the Value
00569     def get_raw_value(self):
00570         return self._raw_value
00571     # --------------------------------------------------------------------------
00572     ##
00573     #   @brief Returns the sign of the Value
00574     def get_sign(self):
00575         return self._sign
00576     # --------------------------------------------------------------------------
00577     ##
00578     #   @brief Returns the unit of the Value
00579     def get_unit(self):
00580         return self._unit
00581 
00582 
00583 
00584 
00585 
00586 
00587 
00588 
00589 
00590 
00591 
00592     raw_value = property(get_raw_value,
00593                          doc = "Raw value of the object")
00594     has_been_rounded = property(get_has_been_rounded,
00595                          doc = "'has been rounded' state of the Value")
00596     sign = property(get_sign,
00597                     doc = "Sign of the Value")
00598 
00599     unit = property(get_unit,
00600                     doc = "Unit of the Value")
00601 
00602 
00603 
00604 
00605 
00606 
00607 
00608 
00609 
00610 
00611     # --------------------------------------------------------------------------
00612     ##
00613     #   @brief Sets the "has been rounded" state of the Value
00614     def set_has_been_rounded(self, arg):
00615         if not arg in [True, False]:
00616             raise error.WrongArgument(str(type(arg)), "True|False")
00617         else:
00618             self._has_been_rounded = arg
00619 
00620 
00621 
00622 
00623 
00624 
00625 
00626 
00627 
00628     # --------------------------------------------------------------------------
00629     ##
00630     #   @brief Set the sign of the object
00631     #   @param  arg String being '+' or '-' or number being +1 or -1
00632     #   @warning Relays an exception if arg is not of the types described
00633     def set_sign(self, arg):
00634         if is_.a_sign(arg):
00635             self._sign = arg
00636         elif arg == 1:
00637             self._sign = '+'
00638         elif arg == -1:
00639             self._sign = '-'
00640         elif isinstance(arg, Calculable):
00641             if arg.is_displ_as_a_single_1():
00642                 self._sign = '+'
00643             elif arg.is_displ_as_a_single_minus_1():
00644                 self._sign = '-'
00645         else:
00646             raise error.UncompatibleType(self, "'+' or '-' or 1 or -1")
00647 
00648 
00649 
00650 
00651 
00652     # --------------------------------------------------------------------------
00653     ##
00654     #   @brief Set the unit of the Value
00655     #   @param  arg String
00656     def set_unit(self, arg):
00657         if not type(arg) == str:
00658             raise error.WrongArgument(str(type(arg)), "a str")
00659         else:
00660             self._unit = arg
00661 
00662 
00663 
00664 
00665     # --------------------------------------------------------------------------
00666     ##
00667     #   @brief Changes the sign of the object
00668     def set_opposite_sign(self):
00669         if self.get_sign() == '-':
00670             self.set_sign('+')
00671         elif self.get_sign() == '+':
00672             self.set_sign('-')
00673         else:
00674             # this case should never happen, just to secure the code
00675             raise error.WrongObject("The sign of the object " \
00676                                     + self.dbg_str() \
00677                                     + " is " \
00678                                     + str(self.sign) \
00679                                     + " instead of '+' or '-'.")
00680 
00681 
00682 
00683 
00684 
00685     # --------------------------------------------------------------------------
00686     ##
00687     #   @brief Creates a string of the given object in the given ML
00688     #   @param options Any options
00689     #   @return The formated string
00690     def into_str(self, **options):
00691 
00692         if 'display_unit' in options and options['display_unit'] in YES \
00693             and self.unit != None and self.unit != '':
00694         #___
00695             unit_str = VALUE_AND_UNIT_SEPARATOR[self.unit] + self.unit
00696 
00697         if self.is_numeric():
00698             if 'display_unit' in options and options['display_unit'] in YES:
00699                 if 'graphic_display' in options\
00700                     and options['graphic_display'] in YES:
00701                 #___
00702                     return locale.str(self.raw_value)\
00703                            + unit_str
00704                 else:
00705                     return locale.str(self.raw_value)\
00706                            + MARKUP['open_text_in_maths']\
00707                            + unit_str \
00708                            + MARKUP['close_text_in_maths']
00709             else:
00710                 return locale.str(self.raw_value)
00711 
00712         else: # self.is_literal()
00713             if len(self.get_first_letter()) >= 2 \
00714                 and not (self.get_first_letter()[0] == "-" \
00715                         or self.get_first_letter()[0] == "+"):
00716             #___
00717                 return MARKUP['open_text_in_maths'] \
00718                        + str(self.raw_value) \
00719                        + MARKUP['close_text_in_maths']
00720             else:
00721                 return str(self.raw_value)
00722 
00723 
00724 
00725 
00726 
00727     # --------------------------------------------------------------------------
00728     ##
00729     #   @brief Returns the value of a numeric Value
00730     #   @warning Raise an exception if not numeric
00731     def evaluate(self):
00732         if not self.is_numeric():
00733             raise error.UncompatibleType(self, "numeric Value")
00734         else:
00735             return self.raw_value
00736 
00737 
00738 
00739 
00740 
00741 
00742     # --------------------------------------------------------------------------
00743     ##
00744     #   @brief Returns None
00745     def calculate_next_step(self, **options):
00746         return None
00747 
00748 
00749 
00750 
00751 
00752     # --------------------------------------------------------------------------
00753     ##
00754     #   @brief Debugging method to print the Value
00755     def dbg_str(self, **options):
00756         return "." + str(self.raw_value) + "."
00757 
00758 
00759 
00760 
00761 
00762     # --------------------------------------------------------------------------
00763     ##
00764     #   @brief Compares two Values
00765     #   @todo check if __eq__ shouldn't return +1 if value of self > objct
00766     #   @todo comparison directly with numbers... (see alphabetical_order_cmp)
00767     #   @return True if they're equal
00768     def __eq__(self, other_value):
00769         if not isinstance(other_value, Value):
00770             return False
00771 
00772         if self.raw_value == other_value.raw_value:
00773             return True
00774         else:
00775             return False
00776 
00777 
00778 
00779 
00780 
00781     # --------------------------------------------------------------------------
00782     ##
00783     #   @brief Returns the Value's length
00784     #   @return 1
00785     def __len__(self):
00786         return 1
00787 
00788 
00789 
00790 
00791     # --------------------------------------------------------------------------
00792     ##
00793     #   @brief Makes Values hashable (so, usable as dictionnary keys)
00794     def __hash__(self):
00795         return hash(str(self.sign) + str(self.raw_value) \
00796                     + str(self.has_been_rounded) + str(self.unit)
00797                    )
00798 
00799 
00800 
00801 
00802 
00803     # --------------------------------------------------------------------------
00804     ##
00805     #   @brief Executes the multiplication with another object
00806     #   @warning Will raise an error if you try to multiply a literal
00807     #            with a number
00808     def __mul__(self, objct):
00809         if isinstance(objct, Calculable):
00810             return self.raw_value * objct.evaluate()
00811         else:
00812             return self.raw_value * objct
00813 
00814 
00815 
00816 
00817 
00818     # --------------------------------------------------------------------------
00819     ##
00820     #   @brief Executes the addition with another object
00821     #   @warning Will raise an error if you try to add a literal with a number
00822     def __add__(self, objct):
00823         if isinstance(objct, Calculable):
00824             return self.raw_value + objct.evaluate()
00825         else:
00826             return self.raw_value + objct
00827 
00828 
00829 
00830 
00831 
00832     # --------------------------------------------------------------------------
00833     ##
00834     #   @brief Uses the given lexicon to substitute literal Values in self
00835     def substitute(self, subst_dict):
00836         if self.is_literal():
00837             for key in subst_dict:
00838                 if self == key:
00839                     self.__init__(subst_dict[key])
00840                     #done = True
00841 
00842             #if not done:
00843             #    raise error.ImpossibleAction("substitute because the numeric "\
00844             #                + "value matching the literal here is not in the "\
00845             #                + "substitution dictionnary")
00846 
00847         else:
00848             pass
00849 
00850 
00851 
00852 
00853 
00854     # --------------------------------------------------------------------------
00855     ##
00856     #   @brief Returns a Value containing the square root of self
00857     def sqrt(self):
00858         if self.is_numeric():
00859             return Value(self.raw_value.sqrt())
00860         else:
00861             raise error.UncompatibleType(self, "numeric Value")
00862 
00863 
00864 
00865     # --------------------------------------------------------------------------
00866     ##
00867     #   @brief Returns the value once rounded to the given precision
00868     def round(self, precision):
00869         if not self.is_numeric():
00870             raise error.UncompatibleType(self, "numeric Value")
00871         elif not (precision in [UNIT,
00872                                 TENTH,
00873                                 HUNDREDTH,
00874                                 THOUSANDTH,
00875                                 TEN_THOUSANDTH] \
00876              or (type(precision) == int and precision >= 0 and precision <= 4)):
00877         #___
00878             raise error.UncompatibleType(precision, "must be UNIT or" \
00879                                                     + "TENTH, " \
00880                                                     + "HUNDREDTH, " \
00881                                                     + "THOUSANDTH, " \
00882                                                     + "TEN_THOUSANDTH, "\
00883                                                     + "or 0, 1, 2, 3 or 4.")
00884         else:
00885             result_value = None
00886 
00887             if type(precision) == int:
00888                 result_value = Value(round(self.raw_value,
00889                                            Decimal(PRECISION[precision]),
00890                                            rounding=ROUND_HALF_UP
00891                                           )
00892                                     )
00893             else:
00894                 result_value = Value(round(self.raw_value,
00895                                            Decimal(precision),
00896                                            rounding=ROUND_HALF_UP
00897                                           )
00898                                     )
00899 
00900             if self.needs_to_get_rounded(precision):
00901                 result_value.set_has_been_rounded(True)
00902 
00903             return result_value
00904 
00905 
00906 
00907 
00908     # --------------------------------------------------------------------------
00909     ##
00910     #   @brief Returns the number of digits of a numerical value
00911     def digits_number(self):
00912         if not self.is_numeric():
00913             raise error.UncompatibleType(self, "numeric Value")
00914         else:
00915             temp_result = len(str((self.raw_value \
00916                                    - round(self.raw_value,
00917                                            Decimal(UNIT),
00918                                            rounding=ROUND_DOWN
00919                                           )
00920                                   ))
00921                               ) \
00922                            - 2
00923 
00924             if temp_result < 0:
00925                 return 0
00926             else:
00927                 return temp_result
00928 
00929 
00930 
00931 
00932 
00933     # --------------------------------------------------------------------------
00934     ##
00935     #   @brief Returns True/False depending on the need of the value to get
00936     #          rounded (for instance 2.68 doesn't need to get rounded if
00937     #          precision is HUNDREDTH or more, but needs it if it is less)
00938     def needs_to_get_rounded(self, precision):
00939         if not (precision in [UNIT,
00940                               TENTH,
00941                               HUNDREDTH,
00942                               THOUSANDTH,
00943                               TEN_THOUSANDTH] \
00944              or (type(precision) == int and precision >= 0 and precision <= 4)):
00945         #___
00946             raise error.UncompatibleType(precision, "must be UNIT or" \
00947                                                     + "TENTH, " \
00948                                                     + "HUNDREDTH, " \
00949                                                     + "THOUSANDTH, " \
00950                                                     + "TEN_THOUSANDTH, "\
00951                                                     + "or 0, 1, 2, 3 or 4.")
00952 
00953         precision_to_test = 0
00954 
00955         if type(precision) == int:
00956             precision_to_test = precision
00957         else:
00958             precision_to_test = PRECISION_REVERSED[precision]
00959 
00960         return self.digits_number() > precision_to_test
00961 
00962 
00963 
00964 
00965 
00966     # --------------------------------------------------------------------------
00967     ##
00968     #   @brief To check if this contains a rounded number...
00969     #   @return True or False depending on the Value inside
00970     def contains_a_rounded_number(self):
00971         return self.has_been_rounded
00972 
00973 
00974 
00975 
00976 
00977 
00978     # --------------------------------------------------------------------------
00979     ##
00980     #   @brief Always False for a Value
00981     #   @param objct The object to search for
00982     #   @return False
00983     def contains_exactly(self, objct):
00984         return False
00985 
00986 
00987 
00988 
00989 
00990 
00991     # --------------------------------------------------------------------------
00992     ##
00993     #   @brief True if the object contains a perfect square (integer or decimal)
00994     def is_a_perfect_square(self):
00995         if not self.is_numeric():
00996             raise error.UncompatibleType(self, "numeric Value")
00997 
00998         if self.is_an_integer():
00999             return not self.sqrt().needs_to_get_rounded(0)
01000         else:
01001             return len(str(self.raw_value)) > len(str(self.raw_value.sqrt()))
01002 
01003 
01004 
01005 
01006     # --------------------------------------------------------------------------
01007     ##
01008     #   @brief True if the object contains an integer (numeric)
01009     def is_an_integer(self):
01010         if not self.is_numeric():
01011             raise error.UncompatibleType(self, "numeric Value")
01012 
01013         getcontext().clear_flags()
01014 
01015         trash = self.raw_value.to_integral_exact()
01016 
01017         return getcontext().flags[Rounded] == 0
01018 
01019 
01020 
01021 
01022     # --------------------------------------------------------------------------
01023     ##
01024     #   @brief True if the object only contains numeric objects
01025     def is_numeric(self):
01026         if type(self.raw_value) == float                \
01027             or type(self.raw_value) == int              \
01028             or type(self.raw_value) == Decimal:
01029         #___
01030             return True
01031         else:
01032             return False
01033 
01034 
01035 
01036 
01037 
01038     # --------------------------------------------------------------------------
01039     ##
01040     #   @brief True if the object only contains literal objects
01041     def is_literal(self):
01042         if type(self.raw_value) == str:
01043         #___
01044             return True
01045         else:
01046             return False
01047 
01048 
01049 
01050 
01051 
01052     # --------------------------------------------------------------------------
01053     ##
01054     #   @brief True if the evaluated value of an object is null
01055     def is_null(self):
01056         if self.is_numeric() and self.raw_value == 0:
01057             return True
01058         else:
01059             return False
01060 
01061 
01062 
01063 
01064 
01065     # --------------------------------------------------------------------------
01066     ##
01067     #   @brief True if the object can be displayed as a single 1
01068     def is_displ_as_a_single_1(self):
01069         if self.is_numeric() and self.raw_value == 1:
01070             return True
01071         else:
01072             return False
01073 
01074 
01075 
01076 
01077 
01078     # --------------------------------------------------------------------------
01079     ##
01080     #   @brief True if the object can be displayed as a single -1
01081     def is_displ_as_a_single_minus_1(self):
01082         if self.is_numeric() and self.raw_value == -1:
01083             return True
01084         else:
01085             return False
01086 
01087 
01088 
01089 
01090 
01091     # --------------------------------------------------------------------------
01092     ##
01093     #   @brief True if the object can be displayed as a single 0
01094     def is_displ_as_a_single_0(self):
01095         if self.is_numeric() and self.raw_value == 0:
01096             return True
01097         else:
01098             return False
01099 
01100 
01101 
01102 
01103 
01104     # --------------------------------------------------------------------------
01105     ##
01106     #   @brief True if the object is or only contains one numeric Item
01107     def is_displ_as_a_single_numeric_Item(self):
01108         return False
01109 
01110 
01111 
01112 
01113 
01114     # --------------------------------------------------------------------------
01115     ##
01116     #   @brief True if the object can be displayed as a single int
01117     def is_displ_as_a_single_int(self):
01118         return self.is_numeric() and self.is_an_integer()
01119 
01120 
01121 
01122 
01123 # ------------------------------------------------------------------------------
01124 # --------------------------------------------------------------------------
01125 # ------------------------------------------------------------------------------
01126 ##
01127 # @class Exponented
01128 # @brief Exponented objects: CommutativeOperations (Sums&Products), Items, Quotients...
01129 # Any Exponented must have a exponent field and should reimplement the
01130 # methods that are not already defined hereafter
01131 class Exponented(Signed):
01132 
01133 
01134 
01135 
01136 
01137     # --------------------------------------------------------------------------
01138     ##
01139     #   @brief Constructor
01140     #   @return An Exponented, though it can't really be used as is
01141     def __init__(self):
01142         Signed.__init__(self)
01143         self._exponent = Value(1)
01144 
01145 
01146 
01147 
01148 
01149     # --------------------------------------------------------------------------
01150     ##
01151     #   @brief Gets the exponent of the Function
01152     #   @brief this should be already done by Item.get_exponent()...
01153     def get_exponent(self):
01154         return self._exponent
01155     # --------------------------------------------------------------------------
01156     exponent = property(get_exponent, doc = "Exponent of the Function")
01157 
01158 
01159 
01160 
01161 
01162     # --------------------------------------------------------------------------
01163     ##
01164     #   @brief Set the value of the exponent
01165     #   @param  arg Calculable|Number|String
01166     #   @warning Relays an exception if arg is not of the types described
01167     def set_exponent(self, arg):
01168         if isinstance(arg, Calculable):
01169             self._exponent = arg.clone()
01170         else:
01171             self._exponent = Value(arg)
01172 
01173 
01174 
01175 
01176 
01177     # --------------------------------------------------------------------------
01178     ##
01179     #   @brief True if the exponent isn't equivalent to a single 1
01180     #   @return True if the exponent is not equivalent to a single 1
01181     def exponent_must_be_displayed(self):
01182         if not self.exponent.is_displ_as_a_single_1():
01183             return True
01184         else:
01185             return False
01186 
01187 
01188 
01189 
01190 
01191 
01192 
01193 
01194 
01195