Пример #1
0
    def _SearchSupplier(self):
        """
        Find the sector that is a single supplier in a country.
        Throws a LogicError if more than one, or none.

        Need to set SupplyAllocation if you want to do something not
        covered by this default behaviour.

        :return: Sector
        """
        Logger('Market {0} searching Country {1} for a supplier',
               priority=3,
               data_to_format=(self.Code, self.Parent.Code))
        ret_value = None
        for sector in self.Parent.GetSectors():
            if sector.ID == self.ID:
                continue
            if 'SUP_' + self.Code in sector.EquationBlock.Equations:
                if ret_value is None:
                    ret_value = sector
                else:
                    raise LogicError(
                        'More than one supplier, must set SupplyAllocation: ' +
                        self.Code)
        if ret_value is None:
            raise LogicError('No supplier: ' + self.Code)
        self.ResidualSupply = ret_value
        return ret_value
Пример #2
0
 def LookupSector(self, short_code):
     out = None
     for s in self.GetSectors():
         if s.Code == short_code:
             if out is not None:
                 raise LogicError(
                     """Multiple sectors with same short code ({0})
 in CurrencyZone {1}""".format(short_code, self.Code))
             else:
                 out = s
     if out is None:
         raise LogicError(
             'Sector {0} does not exist in CurrencyZone {1}'.format(
                 short_code, self.Code))
     return out
Пример #3
0
    def _GenerateRegisteredCashFlows(self):
        """
        Create cash flows based on those previously registered.

        :return:
        """
        Logger('Model._GenerateRegisteredCashFlows()')
        Logger('Adding {0} cash flows to sectors',
               priority=3,
               data_to_format=(len(self.RegisteredCashFlows), ))
        for source_sector, target_sector, amount_variable, is_income_source, is_income_dest in self.RegisteredCashFlows:
            is_cross_currency = source_sector.CurrencyZone != target_sector.CurrencyZone
            if is_cross_currency:
                if self.ExternalSector is None:
                    msg = """Only can have cross-currency flows if an ExternalSector object is created\nSource={0} Destination={1}""".format(
                        source_sector.FullCode, target_sector.FullCode)
                    raise LogicError(msg)
            full_variable_name = source_sector.GetVariableName(amount_variable)
            source_sector.AddCashFlow('-' + full_variable_name,
                                      eqn=None,
                                      is_income=is_income_source)
            if is_cross_currency:
                fx = self.ExternalSector['FX']
                fx._SendMoney(source_sector, full_variable_name)
                term = fx._ReceiveMoney(target_sector=target_sector,
                                        source_sector=source_sector,
                                        variable_name=full_variable_name)
            else:
                term = '+' + full_variable_name
            target_sector.AddCashFlow(term, eqn=None, is_income=is_income_dest)
Пример #4
0
    def _GenerateMultiSupply(self):
        """
        Generate the supply terms with multiple suppliers.

        :return:
        """
        sup_name = 'SUP_' + self.Code
        dem_name = 'DEM_' + self.Code
        # Set aggregate supply equal to demand
        self.SetEquationRightHandSide(sup_name, rhs=dem_name)
        # Generate individual supply equations
        # These are already supplied for everything other than the residual supply, so
        # we need to build it up.
        # Also, the name of the supply varies, depending on whether we are in te same
        # country/region.
        residual_sector = self.ResidualSupply

        residual_equation = Equation(self.GetSupplierTerm(residual_sector),
                                     'Residual supply', sup_name)
        sector_list = self.OtherSuppliers
        # residual supply = total supply less other supply terms
        for supplier, _ in sector_list:
            term = '-SUP_' + supplier.FullCode
            residual_equation.AddTerm(term)
        # Now that we have an equation for the residual sector, append it to the
        # list of suppliers, so we can process all suppliers in one block of code.
        sector_list.append((residual_sector, residual_equation.RHS()))

        for supplier, eqn in sector_list:
            local_name = 'SUP_' + supplier.FullCode
            self.AddVariable(local_name,
                             'Supply from {0}'.format(supplier.LongName), eqn)
            # Push this local variable into the supplying sector
            # If we are in the same country, use 'SUP_{CODE}'
            # If we are in different countries, use 'SUP_{FULLCODE}'
            supply_name = self.GetSupplierTerm(supplier)
            if supply_name not in supplier.EquationBlock:
                supplier.AddVariable(supply_name,
                                     'Supply to {0}'.format(self.FullCode), '')
            if self.IsSharedCurrencyZone(supplier):
                supplier.AddTermToEquation(supply_name,
                                           self.GetVariableName(local_name))
                supplier.AddCashFlow('+' + supply_name)
            else:
                model = self.GetModel()
                if model.ExternalSector is None:
                    raise LogicError(
                        'Must create ExternalSector if we have cross-currency suppliers'
                    )
                full_local_name = self.GetVariableName(local_name)
                model.ExternalSector._SendMoney(self, full_local_name)
                term = model.ExternalSector._ReceiveMoney(
                    supplier, self, full_local_name)
                supplier.AddTermToEquation(supply_name, term)
                supplier.AddCashFlow(term)
        return
Пример #5
0
    def AddCashFlow(self, term, eqn=None, desc=None, is_income=True):
        """
        Add a cash flow to the sector. Will add to the financial asset equation (F), and
        the income equation (INC) if is_income is True.

        Except: There is a list of exclusions to which cash flows are not considered income.
        That setting will override the is_income parameter. This allows us to carve out exceptions
        to the standard behaviour, which generally is to assume that cash flows are associated with
        income.

        :param term: str
        :param eqn: str
        :param desc: str
        :param is_income: bool
        :return: None
        """
        term = term.strip()
        if len(term) == 0:
            return
        term_obj = Term(term)
        if not term_obj.IsSimple:  # pragma: no cover  - Not implemented; cannot hit the line below.
            raise LogicError(
                'Must supply a single variable as the term to AddCashFlow')
        # term = term.replace(' ', '')
        # if not (term[0] in ('+', '-')):
        #     term = '+' + term
        # if len(term) < 2:
        #     raise ValueError('Invalid cash flow term')
        self.EquationBlock['F'].AddTerm(term)
        if is_income:
            # Need to see whether it is excluded
            mod = self.GetModel()
            for obj, excluded in mod.IncomeExclusions:
                if obj.ID == self.ID:
                    if term_obj.Term == excluded:
                        is_income = False
                        break
        if is_income:
            self.EquationBlock['INC'].AddTerm(term)
        if eqn is None:
            return
        # Remove the +/- from the term
        term = term_obj.Term
        if term in self.GetVariables():
            rhs = self.EquationBlock[term].RHS()
            if rhs == '' or rhs == '0.0':
                self.SetEquationRightHandSide(term, eqn)
        else:
            self.AddVariable(term, desc, eqn)
Пример #6
0
    def _AddSector(self, sector):
        """
        Add a sector to this country. This is called by the Sector constructore; users
        should not call directly.

        :param sector: Sector
        :return:
        """
        Logger('Adding Sector {0} To Country {1}',
               priority=1,
               data_to_format=(sector.Code, self.Code))
        if sector.Code in self:
            raise LogicError(
                'Sector with Code {0} already in Country {1}'.format(
                    sector.Code, self.Code))
        self.SectorList.append(sector)
Пример #7
0
    def _GenerateTermsLowLevel(self, prefix, long_desc):
        """
        Generate the terms associated with this market, for supply and demand.

        TODO: This is now only called for the demand function; simplify to just refer
        to demand.

        :param prefix: str
        :param long_desc: str
        :return: None
        """
        Logger('Searching for demand for market {0}',
               priority=3,
               data_to_format=(self.FullCode, ))
        if prefix not in ('SUP', 'DEM'):
            raise LogicError('Input to function must be "SUP" or "DEM"')
        # country = self.Parent
        short_name = prefix + '_' + self.Code
        long_name = prefix + '_' + self.FullCode
        self.AddVariable(short_name, long_desc + ' for Market ' + self.Code,
                         '')
        term_list = []
        for s in self.CurrencyZone.GetSectors():
            if s.ID == self.ID:
                continue
            if self.ShareParent(s):
                var_name = short_name
            else:
                var_name = long_name
            try:
                term = s.GetVariableName(var_name)
            except KeyError:
                Logger('Variable {0} does not exist in {1}',
                       priority=10,
                       data_to_format=(var_name, s.FullCode))
                continue
            term_list.append('+ ' + term)
            if prefix == 'SUP':  # pragma: no cover
                # Since we assume that there is a single supplier, we can set the supply equation to
                # point to the equation in the market.
                s.AddCashFlow(var_name, self.GetVariableName(var_name),
                              long_desc)
            else:
                # Must fill in demand equation in sectors.
                s.AddCashFlow('-' + var_name, '', long_desc)
        eqn = create_equation_from_terms(term_list)
        self.SetEquationRightHandSide(short_name, eqn)
Пример #8
0
    def _AddCountry(self, country):
        """
        Add a country to the list. This is called by the object constructore; users should
        not call this.

        :param country: Country
        :return: None
        """
        Logger('Adding Country: {0} ID={1}',
               data_to_format=(country.Code, country.ID))
        if country.Code in self:
            raise LogicError('Country with Code {0} already in Model'.format(
                country.Code))
        self.CountryList.append(country)
        self.DefaultCurrency = country.Currency
        czone = self._FitIntoCurrencyZone(country)
        country.CurrencyZone = czone
Пример #9
0
 def AddTerm(self, term):
     """
     Add a term to the equation. May be a string or Term object.
     :param term: Term
     :return: None
     """
     term = Term(term)
     if len(self.TermList) > 0:
         if term.IsBlob:
             raise LogicError('Cannot add a blob to non-empty equation')
     for other in self.TermList:
         if term.Term == other.Term:
             # Already exists; just add the constants together.
             other.Constant += term.Constant
             return
     # Otherwise, append
     self.TermList.append(term)
Пример #10
0
 def _GenerateEquations(self):
     CentralBank._GenerateEquations(self)
     ext = self.GetModel().ExternalSector
     if ext is None:
         msg = 'Must Create an ExternalSector in order to use {0}'.format(
             type(self))
         raise LogicError(msg)
     purchases = 'GOLDPURCHASES'
     currency = self.CurrencyZone.Currency
     desc = 'Net Purchases of Gold in TK; Forces EXT_FX_NET_TK to zero'.replace(
         'TK', currency)
     currency_balance = ext['FX'].GetVariableName('NET_' + currency)
     # A somewhat recursive definition; has ugly convergence properties.
     # In fact, will not converge without the step adaptation used.
     # An alternative is to create a special function that is run as a final step
     # before solving.
     # It would get the final net supply terms, remove itself, and use that as the
     # supply variable.
     self.AddVariable(purchases, desc,
                      '{0} - {1}'.format(purchases, currency_balance))
     ext['GOLD'].SetGoldPurchases(self, purchases, self.InitialGoldStock)
Пример #11
0
    def __init__(self, term, is_blob=False):
        """
        Pass in a string (or possibly another term object), and is parsed.

        If is_blob is True, we do not do any parsing (other than squeezing out internal spaces).

        An equation is allowed one "blob" term, which is the first term. It may be followed
        by non-blob terms.

        As parsing improves, terms can be peeled off of the "blob."

        :param term: str
        :param is_blob: False
        """
        if type(term) == Term:
            self.Constant = term.Constant
            self.Term = term.Term
            self.IsSimple = term.IsSimple
            # Ignore the is_blob input
            self.IsBlob = term.IsBlob
            return
        # Force to be a string; remove whitespace
        term_s = str(term).strip()
        # internal spaces do not matter
        term_s = term_s.replace(' ', '')
        if is_blob:
            # If we are a "blob", don't do any parsing.
            self.Constant = 1.0
            self.Term = term_s
            self.IsSimple = True
            self.IsBlob = True
            return
        self.IsBlob = False
        # Rule #1: Eliminate '+' or '-' at front
        self.Constant = 1.0
        if term_s.startswith('+'):
            term_s = term_s[1:]
        elif term_s.startswith('-'):
            self.Constant = -1.0
            term_s = term_s[1:]
        # Rule #2: Allow matched "("
        if term_s.startswith('('):
            if not term_s.endswith(')'):
                raise SyntaxError('Term does not have matching ) - ' +
                                  str(term))
            # Remove brackets
            term_s = term_s[1:-1]
            # If we peeled the brackets, remove '+' or '-' again
            if term_s.startswith('+'):
                term_s = term_s[1:]
            elif term_s.startswith('-'):
                # Flip the sign
                self.Constant *= -1.0
                term_s = term_s[1:]
        # We now cannot have embedded '+' or '-' signs.
        if '+' in term_s:
            raise LogicError('Term cannot contain interior "+" :' + str(term))
        if '-' in term_s:
            raise LogicError('Term cannot contain interior "-" :' + str(term))
        # Do we consist of anything besides a single name token?
        # If so, we are not simple.
        # (Will eventually allow for things like '2*x'.)
        if len(term_s) == 0:
            raise LogicError('Attempting to create an empty term object.')
        if is_python_3:
            g = tokenize.tokenize(BytesIO(
                term_s.encode('utf-8')).readline)  # tokenize the string
        else:  # pragma: no cover   [Do my coverage on Python 3]
            g = tokenize.generate_tokens(
                BytesIO(
                    term_s.encode('utf-8')).readline)  # tokenize the string
        self.IsSimple = True
        g = tuple(g)
        if is_python_3:
            if not g[0][0] == ENCODING:  # pragma: no cover
                raise LogicError('Internal error: tokenize behaviour changed')
            if not g[-1][0] == ENDMARKER:  # pragma: no cover
                raise LogicError('Internal error: tokenize behaviour changed')
            if len(g) > 3:
                if len(g) == 5:
                    # Allow variable*variable as a "simple" Variable.
                    if g[1][0] == NAME and g[3][0] == NAME and g[2][0] == OP:
                        if g[2][1] in ('*', '/'):
                            self.Term = term_s
                            return
                raise NotImplementedError('Non-simple parsing not done')
                # self.IsSimple = False
            else:
                if not g[1][0] == NAME:
                    raise NotImplementedError('Non-simple parsing not done')
                    # self.IsSimple = False
            self.Term = term_s
        else:  # Python 2.7 # pragma: no cover
            # Missing the first term - augh
            if not g[-1][0] == ENDMARKER:  # pragma: no cover
                raise LogicError('Internal error: tokenize behaviour changed')
            if len(g) > 3:
                if len(g) == 4:
                    # Allow variable*variable as a "simple" Variable.
                    if g[0][0] == NAME and g[2][0] == NAME and g[1][0] == OP:
                        if g[1][1] in ('*', '/'):
                            self.Term = term_s
                            return
                raise NotImplementedError('Non-simple parsing not done')
                # self.IsSimple = False
            else:
                if not g[0][0] == NAME:
                    raise NotImplementedError('Non-simple parsing not done')
                    # self.IsSimple = False
            self.Term = term_s
Пример #12
0
    def __init__(self, term, is_blob=False):
        """
        Pass in a string (or possibly another term object), and is parsed.

        If is_blob is True, we do not do any parsing (other than squeezing out internal spaces).

        An equation is allowed one "blob" term, which is the first term. It may be followed
        by non-blob terms.

        As parsing improves, terms can be peeled off of the "blob."

        :param term: str
        :param is_blob: False
        """
        if type(term) == Term:
            self.Constant = term.Constant
            self.Term = term.Term
            self.IsSimple = term.IsSimple
            # Ignore the is_blob input
            self.IsBlob = term.IsBlob
            return
        # Force to be a string; remove whitespace
        term_s = str(term).strip()
        # internal spaces do not matter
        term_s = term_s.replace(' ', '')
        if is_blob:
            # If we are a "blob", don't do any parsing.
            self.Constant = 1.0
            self.Term = term_s
            self.IsSimple = True
            self.IsBlob = True
            return
        self.IsBlob = False
        # Rule #1: Eliminate '+' or '-' at front
        self.Constant = 1.0
        if term_s.startswith('+'):
            term_s = term_s[1:]
        elif term_s.startswith('-'):
            self.Constant = -1.0
            term_s = term_s[1:]
        # Rule #2: Allow matched "("
        if term_s.startswith('('):
            if not term_s.endswith(')'):
                raise SyntaxError('Term does not have matching ) - ' +
                                  str(term))
            # Remove brackets
            term_s = term_s[1:-1]
            # If we peeled the brackets, remove '+' or '-' again
            if term_s.startswith('+'):
                term_s = term_s[1:]
            elif term_s.startswith('-'):
                # Flip the sign
                self.Constant *= -1.0
                term_s = term_s[1:]
        # We now cannot have embedded '+' or '-' signs.
        if '+' in term_s:
            raise LogicError('Term cannot contain interior "+" :' + str(term))
        if '-' in term_s:
            raise LogicError('Term cannot contain interior "-" :' + str(term))
        # Do we consist of anything besides a single name token?
        # If so, we are not simple.
        # (Will eventually allow for things like '2*x'.)
        if len(term_s) == 0:
            raise LogicError('Attempting to create an empty term object.')
        if is_python_3:
            g = tokenize.tokenize(BytesIO(
                term_s.encode('utf-8')).readline)  # tokenize the string
        else:  # pragma: no cover   [Do my coverage on Python 3]
            g = tokenize.generate_tokens(
                BytesIO(
                    term_s.encode('utf-8')).readline)  # tokenize the string
        self.IsSimple = True
        g = tuple(g)
        if is_python_3:
            # Behaviour changed on me, so needed to clean up logic.
            # Remove "white space" tokens
            # Note: The change in behaviour happened between version 3.5 and 3.7. This new code
            # worked for me on version 3.5 as well as 3.7.
            cleaned = [
                x for x in g if x[0] not in (ENCODING, NEWLINE, ENDMARKER)
            ]
            if len(cleaned) == 1:
                if cleaned[0][0] == NAME or cleaned[0][0] == NUMBER:
                    self.Term = term_s
                    return
            elif len(cleaned) == 3:
                # Allow
                if ((cleaned[0][0] in (NAME, NUMBER)) and (cleaned[1][0] == OP)
                        and (cleaned[2][0] in (NAME, NUMBER))
                        and (cleaned[1][1] in ('*', '/'))):
                    self.Term = term_s
                    return
            # Puke otherwise
            raise NotImplementedError(
                'Non-simple parsing not supported ' +
                '(Note: May fail for Python versions before 3.7 (?)): ' +
                term_s)
            #  Old code. Will delete once I know new code works...
            # if not g[0][0] == ENCODING:  # pragma: no cover
            #     raise LogicError('Internal error: tokenize behaviour changed')
            # if not g[-1][0] == ENDMARKER:  # pragma: no cover
            #     raise LogicError('Internal error: tokenize behaviour changed')
            #
            # if len(g) > 3:
            #     if len(g) == 4:
            #         # Behaviour changed in Version 3.7?
            #         if (not g[2][0] == NEWLINE) or (not g[1][0] == NAME):
            #             raise NotImplementedError('Non-simple parsing not done')
            #         self.Term = term_s
            #         return
            #     elif len(g) in (5, 6):
            #         # For some reason, getting a newline?
            #         # Allow variable*variable as a "simple" Variable.
            #         if g[1][0] == NAME and g[3][0] == NAME and g[2][0] == OP:
            #             if g[2][1] in ('*', '/'):
            #                 self.Term = term_s
            #                 return
            #     raise NotImplementedError('Non-simple parsing not done')
            #     # self.IsSimple = False
            # else:
            #     if not g[1][0] == NAME:
            #         raise NotImplementedError('Non-simple parsing not done')
            #         # self.IsSimple = False
            # self.Term = term_s
        else:  # Python 2.7 # pragma: no cover
            # Missing the first term - augh
            if not g[-1][0] == ENDMARKER:  # pragma: no cover
                raise LogicError('Internal error: tokenize behaviour changed')
            if len(g) > 3:
                if len(g) == 4:
                    # Allow variable*variable as a "simple" Variable.
                    if g[0][0] == NAME and g[2][0] == NAME and g[1][0] == OP:
                        if g[1][1] in ('*', '/'):
                            self.Term = term_s
                            return
                raise NotImplementedError('Non-simple parsing not done')
                # self.IsSimple = False
            else:
                if not g[0][0] == NAME:
                    raise NotImplementedError('Non-simple parsing not done')
                    # self.IsSimple = False
            self.Term = term_s