mathmaker  0.4(alpha)
mathmaker_dev/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 self.get_iteration_list().next()
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) == long                                        \
00514             or type(arg) == Decimal:
00515         #___
00516             self._raw_value = Decimal(str(arg))
00517             if arg >= 0:
00518                 self._sign = '+'
00519             else:
00520                 self._sign = '-'
00521 
00522         elif type(arg) == str:
00523             if is_.a_numerical_string(arg):
00524                 self._raw_value = Decimal(arg)
00525             else:
00526                 self._raw_value = arg
00527 
00528             if len(arg) >= 1 and arg[0] == '-':
00529                 self._sign = '-'
00530 
00531         elif isinstance(arg, Value):
00532             self._raw_value = arg.raw_value
00533             self._has_been_rounded = arg.has_been_rounded
00534             self._unit = arg.unit
00535 
00536         # All other unforeseen cases : an exception is raised.
00537         else:
00538             raise error.UncompatibleType(arg, "Number|String")
00539 
00540 
00541 
00542 
00543 
00544     # --------------------------------------------------------------------------
00545     ##
00546     #   @brief If the object is literal, returns the value
00547     def get_first_letter(self):
00548         if self.is_literal():
00549             return self.raw_value
00550         else:
00551             raise error.UncompatibleType(self, "str, i.e. literal Value")
00552     # --------------------------------------------------------------------------
00553     ##
00554     #   @brief Returns the "has been rounded" state of the Value
00555     def get_has_been_rounded(self):
00556         return self._has_been_rounded
00557     # --------------------------------------------------------------------------
00558     ##
00559     #   @brief Returns the list of elements to iter over
00560     def get_iteration_list(self):
00561         return [self.raw_value]
00562     # --------------------------------------------------------------------------
00563     ##
00564     #   @brief Returns the number of minus signs in the object
00565     def get_minus_signs_nb(self):
00566         raise error.MethodShouldBeRedefined(self, 'get_minus_signs_nb')
00567     # --------------------------------------------------------------------------
00568     ##
00569     #   @brief Returns the raw value contained in the Value
00570     def get_raw_value(self):
00571         return self._raw_value
00572     # --------------------------------------------------------------------------
00573     ##
00574     #   @brief Returns the sign of the Value
00575     def get_sign(self):
00576         return self._sign
00577     # --------------------------------------------------------------------------
00578     ##
00579     #   @brief Returns the unit of the Value
00580     def get_unit(self):
00581         return self._unit
00582 
00583 
00584 
00585 
00586 
00587 
00588 
00589 
00590 
00591 
00592 
00593     raw_value = property(get_raw_value,
00594                          doc = "Raw value of the object")
00595     has_been_rounded = property(get_has_been_rounded,
00596                          doc = "'has been rounded' state of the Value")
00597     sign = property(get_sign,
00598                     doc = "Sign of the Value")
00599 
00600     unit = property(get_unit,
00601                     doc = "Unit of the Value")
00602 
00603 
00604 
00605 
00606 
00607 
00608 
00609 
00610 
00611 
00612     # --------------------------------------------------------------------------
00613     ##
00614     #   @brief Sets the "has been rounded" state of the Value
00615     def set_has_been_rounded(self, arg):
00616         if not arg in [True, False]:
00617             raise error.WrongArgument(str(type(arg)), "True|False")
00618         else:
00619             self._has_been_rounded = arg
00620 
00621 
00622 
00623 
00624 
00625 
00626 
00627 
00628 
00629     # --------------------------------------------------------------------------
00630     ##
00631     #   @brief Set the sign of the object
00632     #   @param  arg String being '+' or '-' or number being +1 or -1
00633     #   @warning Relays an exception if arg is not of the types described
00634     def set_sign(self, arg):
00635         if is_.a_sign(arg):
00636             self._sign = arg
00637         elif arg == 1:
00638             self._sign = '+'
00639         elif arg == -1:
00640             self._sign = '-'
00641         elif isinstance(arg, Calculable):
00642             if arg.is_displ_as_a_single_1():
00643                 self._sign = '+'
00644             elif arg.is_displ_as_a_single_minus_1():
00645                 self._sign = '-'
00646         else:
00647             raise error.UncompatibleType(self, "'+' or '-' or 1 or -1")
00648 
00649 
00650 
00651 
00652 
00653     # --------------------------------------------------------------------------
00654     ##
00655     #   @brief Set the unit of the Value
00656     #   @param  arg String
00657     def set_unit(self, arg):
00658         if not type(arg) == str:
00659             raise error.WrongArgument(str(type(arg)), "a str")
00660         else:
00661             self._unit = arg
00662 
00663 
00664 
00665 
00666     # --------------------------------------------------------------------------
00667     ##
00668     #   @brief Changes the sign of the object
00669     def set_opposite_sign(self):
00670         if self.get_sign() == '-':
00671             self.set_sign('+')
00672         elif self.get_sign() == '+':
00673             self.set_sign('-')
00674         else:
00675             # this case should never happen, just to secure the code
00676             raise error.WrongObject("The sign of the object " \
00677                                     + self.dbg_str() \
00678                                     + " is " \
00679                                     + str(self.sign) \
00680                                     + " instead of '+' or '-'.")
00681 
00682 
00683 
00684 
00685 
00686     # --------------------------------------------------------------------------
00687     ##
00688     #   @brief Creates a string of the given object in the given ML
00689     #   @param options Any options
00690     #   @return The formated string
00691     def into_str(self, **options):
00692 
00693         if 'display_unit' in options and options['display_unit'] in YES \
00694             and self.unit != None and self.unit != '':
00695         #___
00696             unit_str = VALUE_AND_UNIT_SEPARATOR[self.unit] + self.unit
00697 
00698         if self.is_numeric():
00699             if 'display_unit' in options and options['display_unit'] in YES:
00700                 if 'graphic_display' in options\
00701                     and options['graphic_display'] in YES:
00702                 #___
00703                     return locale.str(self.raw_value)\
00704                            + unit_str
00705                 else:
00706                     return locale.str(self.raw_value)\
00707                            + MARKUP['open_text_in_maths']\
00708                            + unit_str \
00709                            + MARKUP['close_text_in_maths']
00710             else:
00711                 return locale.str(self.raw_value)
00712 
00713         else: # self.is_literal()
00714             if len(self.get_first_letter()) >= 2 \
00715                 and not (self.get_first_letter()[0] == "-" \
00716                         or self.get_first_letter()[0] == "+"):
00717             #___
00718                 return MARKUP['open_text_in_maths'] \
00719                        + str(self.raw_value) \
00720                        + MARKUP['close_text_in_maths']
00721             else:
00722                 return str(self.raw_value)
00723 
00724 
00725 
00726 
00727 
00728     # --------------------------------------------------------------------------
00729     ##
00730     #   @brief Returns the value of a numeric Value
00731     #   @warning Raise an exception if not numeric
00732     def evaluate(self):
00733         if not self.is_numeric():
00734             raise error.UncompatibleType(self, "numeric Value")
00735         else:
00736             return self.raw_value
00737 
00738 
00739 
00740 
00741 
00742 
00743     # --------------------------------------------------------------------------
00744     ##
00745     #   @brief Returns None
00746     def calculate_next_step(self, **options):
00747         return None
00748 
00749 
00750 
00751 
00752 
00753     # --------------------------------------------------------------------------
00754     ##
00755     #   @brief Debugging method to print the Value
00756     def dbg_str(self, **options):
00757         return "." + str(self.raw_value) + "."
00758 
00759 
00760 
00761 
00762 
00763     # --------------------------------------------------------------------------
00764     ##
00765     #   @brief Compares two Values
00766     #   @todo check if __cmp__ shouldn't return +1 if value of self > objct
00767     #   @todo comparison directly with numbers... (see alphabetical_order_cmp)
00768     #   @return -1|0 (i.e. they're equal)
00769     def __cmp__(self, other_value):
00770         if not isinstance(other_value, Value):
00771             return -1
00772 
00773         if self.raw_value == other_value.raw_value:
00774             return 0
00775         else:
00776             return -1
00777 
00778 
00779 
00780 
00781 
00782     # --------------------------------------------------------------------------
00783     ##
00784     #   @brief Returns the Value's length
00785     #   @return 1
00786     def __len__(self):
00787         return 1
00788 
00789 
00790 
00791 
00792     # --------------------------------------------------------------------------
00793     ##
00794     #   @brief Executes the multiplication with another object
00795     #   @warning Will raise an error if you try to multiply a literal
00796     #            with a number
00797     def __mul__(self, objct):
00798         if isinstance(objct, Calculable):
00799             return self.raw_value * objct.evaluate()
00800         else:
00801             return self.raw_value * objct
00802 
00803 
00804 
00805 
00806 
00807     # --------------------------------------------------------------------------
00808     ##
00809     #   @brief Executes the addition with another object
00810     #   @warning Will raise an error if you try to add a literal with a number
00811     def __add__(self, objct):
00812         if isinstance(objct, Calculable):
00813             return self.raw_value + objct.evaluate()
00814         else:
00815             return self.raw_value + objct
00816 
00817 
00818 
00819 
00820 
00821     # --------------------------------------------------------------------------
00822     ##
00823     #   @brief Uses the given lexicon to substitute literal Values in self
00824     def substitute(self, subst_dict):
00825         if self.is_literal():
00826             for key in subst_dict:
00827                 if self == key:
00828                     self.__init__(subst_dict[key])
00829                     #done = True
00830 
00831             #if not done:
00832             #    raise error.ImpossibleAction("substitute because the numeric "\
00833             #                + "value matching the literal here is not in the "\
00834             #                + "substitution dictionnary")
00835 
00836         else:
00837             pass
00838 
00839 
00840 
00841 
00842 
00843     # --------------------------------------------------------------------------
00844     ##
00845     #   @brief Returns a Value containing the square root of self
00846     def sqrt(self):
00847         if self.is_numeric():
00848             return Value(self.raw_value.sqrt())
00849         else:
00850             raise error.UncompatibleType(self, "numeric Value")
00851 
00852 
00853 
00854     # --------------------------------------------------------------------------
00855     ##
00856     #   @brief Returns the value once rounded to the given precision
00857     def round(self, precision):
00858         if not self.is_numeric():
00859             raise error.UncompatibleType(self, "numeric Value")
00860         elif not (precision in [UNIT,
00861                                 TENTH,
00862                                 HUNDREDTH,
00863                                 THOUSANDTH,
00864                                 TEN_THOUSANDTH] \
00865              or (type(precision) == int and precision >= 0 and precision <= 4)):
00866         #___
00867             raise error.UncompatibleType(precision, "must be UNIT or" \
00868                                                     + "TENTH, " \
00869                                                     + "HUNDREDTH, " \
00870                                                     + "THOUSANDTH, " \
00871                                                     + "TEN_THOUSANDTH, "\
00872                                                     + "or 0, 1, 2, 3 or 4.")
00873         else:
00874             result_value = None
00875 
00876             if type(precision) == int:
00877                 result_value = Value(round(self.raw_value,
00878                                            Decimal(PRECISION[precision]),
00879                                            rounding=ROUND_HALF_UP
00880                                           )
00881                                     )
00882             else:
00883                 result_value = Value(round(self.raw_value,
00884                                            Decimal(precision),
00885                                            rounding=ROUND_HALF_UP
00886                                           )
00887                                     )
00888 
00889             if self.needs_to_get_rounded(precision):
00890                 result_value.set_has_been_rounded(True)
00891 
00892             return result_value
00893 
00894 
00895 
00896 
00897     # --------------------------------------------------------------------------
00898     ##
00899     #   @brief Returns the number of digits of a numerical value
00900     def digits_number(self):
00901         if not self.is_numeric():
00902             raise error.UncompatibleType(self, "numeric Value")
00903         else:
00904             temp_result = len(str((self.raw_value \
00905                                    - round(self.raw_value,
00906                                            Decimal(UNIT),
00907                                            rounding=ROUND_DOWN
00908                                           )
00909                                   ))
00910                               ) \
00911                            - 2
00912 
00913             if temp_result < 0:
00914                 return 0
00915             else:
00916                 return temp_result
00917 
00918 
00919 
00920 
00921 
00922     # --------------------------------------------------------------------------
00923     ##
00924     #   @brief Returns True/False depending on the need of the value to get
00925     #          rounded (for instance 2.68 doesn't need to get rounded if
00926     #          precision is HUNDREDTH or more, but needs it if it is less)
00927     def needs_to_get_rounded(self, precision):
00928         if not (precision in [UNIT,
00929                               TENTH,
00930                               HUNDREDTH,
00931                               THOUSANDTH,
00932                               TEN_THOUSANDTH] \
00933              or (type(precision) == int and precision >= 0 and precision <= 4)):
00934         #___
00935             raise error.UncompatibleType(precision, "must be UNIT or" \
00936                                                     + "TENTH, " \
00937                                                     + "HUNDREDTH, " \
00938                                                     + "THOUSANDTH, " \
00939                                                     + "TEN_THOUSANDTH, "\
00940                                                     + "or 0, 1, 2, 3 or 4.")
00941 
00942         precision_to_test = 0
00943 
00944         if type(precision) == int:
00945             precision_to_test = precision
00946         else:
00947             precision_to_test = PRECISION_REVERSED[precision]
00948 
00949         return self.digits_number() > precision_to_test
00950 
00951 
00952 
00953 
00954 
00955     # --------------------------------------------------------------------------
00956     ##
00957     #   @brief To check if this contains a rounded number...
00958     #   @return True or False depending on the Value inside
00959     def contains_a_rounded_number(self):
00960         return self.has_been_rounded
00961 
00962 
00963 
00964 
00965 
00966 
00967     # --------------------------------------------------------------------------
00968     ##
00969     #   @brief Always False for a Value
00970     #   @param objct The object to search for
00971     #   @return False
00972     def contains_exactly(self, objct):
00973         return False
00974 
00975 
00976 
00977 
00978 
00979 
00980     # --------------------------------------------------------------------------
00981     ##
00982     #   @brief True if the object contains a perfect square (integer or decimal)
00983     def is_a_perfect_square(self):
00984         if not self.is_numeric():
00985             raise error.UncompatibleType(self, "numeric Value")
00986 
00987         if self.is_an_integer():
00988             return not self.sqrt().needs_to_get_rounded(0)
00989         else:
00990             return len(str(self.raw_value)) > len(str(self.raw_value.sqrt()))
00991 
00992 
00993 
00994 
00995     # --------------------------------------------------------------------------
00996     ##
00997     #   @brief True if the object contains an integer (numeric)
00998     def is_an_integer(self):
00999         if not self.is_numeric():
01000             raise error.UncompatibleType(self, "numeric Value")
01001 
01002         getcontext().clear_flags()
01003 
01004         trash = self.raw_value.to_integral_exact()
01005 
01006         return getcontext().flags[Rounded] == 0
01007 
01008 
01009 
01010 
01011     # --------------------------------------------------------------------------
01012     ##
01013     #   @brief True if the object only contains numeric objects
01014     def is_numeric(self):
01015         if type(self.raw_value) == float                \
01016             or type(self.raw_value) == int              \
01017             or type(self.raw_value) == long             \
01018             or type(self.raw_value) == Decimal:
01019         #___
01020             return True
01021         else:
01022             return False
01023 
01024 
01025 
01026 
01027 
01028     # --------------------------------------------------------------------------
01029     ##
01030     #   @brief True if the object only contains literal objects
01031     def is_literal(self):
01032         if type(self.raw_value) == str:
01033         #___
01034             return True
01035         else:
01036             return False
01037 
01038 
01039 
01040 
01041 
01042     # --------------------------------------------------------------------------
01043     ##
01044     #   @brief True if the evaluated value of an object is null
01045     def is_null(self):
01046         if self.is_numeric() and self.raw_value == 0:
01047             return True
01048         else:
01049             return False
01050 
01051 
01052 
01053 
01054 
01055     # --------------------------------------------------------------------------
01056     ##
01057     #   @brief True if the object can be displayed as a single 1
01058     def is_displ_as_a_single_1(self):
01059         if self.is_numeric() and self.raw_value == 1:
01060             return True
01061         else:
01062             return False
01063 
01064 
01065 
01066 
01067 
01068     # --------------------------------------------------------------------------
01069     ##
01070     #   @brief True if the object can be displayed as a single -1
01071     def is_displ_as_a_single_minus_1(self):
01072         if self.is_numeric() and self.raw_value == -1:
01073             return True
01074         else:
01075             return False
01076 
01077 
01078 
01079 
01080 
01081     # --------------------------------------------------------------------------
01082     ##
01083     #   @brief True if the object can be displayed as a single 0
01084     def is_displ_as_a_single_0(self):
01085         if self.is_numeric() and self.raw_value == 0:
01086             return True
01087         else:
01088             return False
01089 
01090 
01091 
01092 
01093 
01094     # --------------------------------------------------------------------------
01095     ##
01096     #   @brief True if the object is or only contains one numeric Item
01097     def is_displ_as_a_single_numeric_Item(self):
01098         return False
01099 
01100 
01101 
01102 
01103 
01104     # --------------------------------------------------------------------------
01105     ##
01106     #   @brief True if the object can be displayed as a single int
01107     def is_displ_as_a_single_int(self):
01108         return self.is_numeric() and self.is_an_integer()
01109 
01110 
01111 
01112 
01113 # ------------------------------------------------------------------------------
01114 # --------------------------------------------------------------------------
01115 # ------------------------------------------------------------------------------
01116 ##
01117 # @class Exponented
01118 # @brief Exponented objects: CommutativeOperations (Sums&Products), Items, Quotients...
01119 # Any Exponented must have a exponent field and should reimplement the
01120 # methods that are not already defined hereafter
01121 class Exponented(Signed):
01122 
01123 
01124 
01125 
01126 
01127     # --------------------------------------------------------------------------
01128     ##
01129     #   @brief Constructor
01130     #   @return An Exponented, though it can't really be used as is
01131     def __init__(self):
01132         Signed.__init__(self)
01133         self._exponent = Value(1)
01134 
01135 
01136 
01137 
01138 
01139     # --------------------------------------------------------------------------
01140     ##
01141     #   @brief Gets the exponent of the Function
01142     #   @brief this should be already done by Item.get_exponent()...
01143     def get_exponent(self):
01144         return self._exponent
01145     # --------------------------------------------------------------------------
01146     exponent = property(get_exponent, doc = "Exponent of the Function")
01147 
01148 
01149 
01150 
01151 
01152     # --------------------------------------------------------------------------
01153     ##
01154     #   @brief Set the value of the exponent
01155     #   @param  arg Calculable|Number|String
01156     #   @warning Relays an exception if arg is not of the types described
01157     def set_exponent(self, arg):
01158         if isinstance(arg, Calculable):
01159             self._exponent = arg.clone()
01160         else:
01161             self._exponent = Value(arg)
01162 
01163 
01164 
01165 
01166 
01167     # --------------------------------------------------------------------------
01168     ##
01169     #   @brief True if the exponent isn't equivalent to a single 1
01170     #   @return True if the exponent is not equivalent to a single 1
01171     def exponent_must_be_displayed(self):
01172         if not self.exponent.is_displ_as_a_single_1():
01173             return True
01174         else:
01175             return False
01176 
01177 
01178 
01179 
01180 
01181 
01182 
01183 
01184 
01185