lca_algebraic.helpers API documentation

# Module `lca_algebraic.helpers`

Expand source code

    import functools
    import re
    import types

    from copy import deepcopy
    from itertools import chain

    import pandas as pd
    from bw2data.backends.peewee.utils import dict_as_exchangedataset
    from bw2data.meta import databases as dbmeta
    from sympy import symbols

    from .base_utils import *
    from .base_utils import _getDb, _actDesc, _getAmountOrFormula, _actName, _isOutputExch
    from .params import *
    from .params import _param_registry, _completeParamValues
    from typing import Tuple, Dict

    BIOSPHERE3_DB_NAME="biosphere3"

    _metaCache = defaultdict(lambda : {})

    def _setMeta(dbname, key, value) :
        """Set meta param on DB"""
        _metaCache[dbname][key] = value

        data = dbmeta[dbname]
        data[key] = value
        dbmeta[dbname] = data
        dbmeta.flush()

    def _getMeta(db_name, key) :

        if key in _metaCache[db_name] :
            return _metaCache[db_name][key]

        val = dbmeta[db_name].get(key)
        _metaCache[db_name][key] = val
        return val

    FOREGROUND_KEY = "fg"

    def _isForeground(db_name) :
        """ Check is db is marked as foreground DB : which means activities may be parametrized / should be developped. """
        return _getMeta(db_name, FOREGROUND_KEY)

    def setForeground(db_name) :
        """ Set a db as being a foreground database, meaning it is parametrized and lca_algebraic should develop its activities"""
        return _setMeta(db_name, FOREGROUND_KEY, True)

    def setBackground(db_name) :
        """ Set a db as being a foreground database, meaning it should be considred as static"""
        return _setMeta(db_name, FOREGROUND_KEY, False)

    def SET_USER_DB(db_name) :
        """Deprecated, use #setForeground() / setBackground() instead"""
        error("Deprecated, use #setForeground() / setBackground() instead")
        setForeground(db_name)

    def _listTechBackgroundDbs() :
        """List all background databases technosphere (non biosphere) batabases"""
        return list(name for name in bw.databases if not _isForeground(name) and not name == BIOSPHERE3_DB_NAME)

    old_amount = symbols("old_amount")  # Can be used in expression of amount for updateExchanges, in order to reference the previous value
    NumOrExpression = Union[float, Basic]

    def list_databases() :
        """List of databases and their status"""
        data = list(
            dict(
                name=name,
                backend=_getMeta(name, "backend"),
                type="foreground" if _isForeground(name) else "background") for name in bw.databases)
        res = pd.DataFrame(data)
        return res.set_index("name")

    def with_db_context(func):
        """ Internal decorator wrapping function into DbContext, using its first parameters (either Activity, Db or Db name)"""
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            act = args[0]
            dbname = act.key[0]
            with DbContext(dbname) :
                return func(*args, **kwargs)
        return wrapper

    class ActivityExtended(Activity):
        """Improved API for activity : adding a few useful methods.
        Those methods are backported to #Activity in order to be directly available on all existing instances
        """

        @with_db_context
        def listExchanges(self) :
            """ Iterates on all exchanges (except "production") and return a list of (exch-name, target-act, amount) """
            res = []
            for exc in self.exchanges():

                # Don't show production
                if _isOutputExch(exc) :
                    continue

                input = bw.get_activity(exc.input.key)
                amount = _getAmountOrFormula(exc)
                res.append((exc["name"], input, amount))
            return res

        @with_db_context
        def getExchange(self, name=None, input=None, single=True):
            """Get exchange by name or input

            Parameters
            ----------
            name : name of the exchange. Name can be suffixed with '#LOCATION' to distinguish several exchanges with same name. \
                It can also be suffised by '*' to match an exchange starting with this name. Location can be a negative match '!'
                Exampple : "Wood*#!RoW" matches any exchange with name  containing Wood, and location not "RoW"

            single :True if a single match is expected. Otherwize, a list of result is returned

            Returns
            -------
                Single exchange or list of exchanges (if _single is False or "name" contains a '*')
                raise Exception if not matching exchange found
            """

            def single_match(name, exch):

                # Name can be "Elecricity#RER"
                if "#" in name:
                    name, loc = name.split("#")
                    negative = False
                    if loc.startswith("!"):
                        negative = True
                        loc = loc[1:]
                    act = getActByCode(*exch['input'])

                    if not 'location' in act or (negative and act['location'] == loc) or (
                            not negative and act['location'] != loc):
                        return False

                if '*' in name:
                    name = name.replace('*', '')
                    return name in exch['name']
                else:
                    return name == exch['name']

            def match(exch):
                if name:
                    if isinstance(name, list):
                        return any(single_match(iname, exch) for iname in name)
                    else:
                        return single_match(name, exch)

                if input:
                    return input == exch['input']

            exchs = list(exch for exch in self.exchangesNp() if match(exch))
            if len(exchs) == 0:
                raise Exception("Found no exchange matching name : %s" % name)

            if single and len(exchs) != 1:
                raise Exception("Expected 1 exchange with name '%s' found %d" % (name, len(exchs)))
            if single:
                return exchs[0]
            else:
                return exchs

        def setOutputAmount(self, amount):
            '''Set the amount for the single output exchange (1 by default)'''
            self.addExchanges({self: amount})

        @with_db_context
        def updateExchanges(self, updates: Dict[str, any] = dict()):

            """Update existing exchanges, by name.

            Parameters
            ----------
            updates : Dict of "<exchange name>" => <new value>

                <exchange name> can be suffixed with '#LOCATION' to distinguish several exchanges with same name. \
                It can also be suffixed by '*' to match an exchange starting with this name. Location can be a negative match '!'
                Exampple : "Wood*#!RoW" matches any exchange with name  containing Wood, and location not "RoW"

                <New Value>  : either single value (float or SympPy expression) for updating only amount, or activity for updating only input,
                or dict of attributes, for updating both at once, or any other attribute.
                The amount can reference the symbol 'old_amount' that will be replaced with the current amount of the exchange.
            """

            parametrized = False

            # Update exchanges
            for name, attrs in updates.items():

                exchs = self.getExchange(name, single=not '*' in name)
                if not isinstance(exchs, list):
                    exchs = [exchs]

                for exch in exchs:

                    if attrs is None:
                        exch.delete()
                        exch.save()
                        continue

                    # Single value ? => amount
                    if not isinstance(attrs, dict):
                        if isinstance(attrs, Activity):
                            attrs = dict(input=attrs)
                        else:
                            attrs = dict(amount=attrs)

                    if 'amount' in attrs:
                        attrs.update(_amountToFormula(attrs['amount'], exch['amount']))

                    exch.update(attrs)
                    exch.save()

                    # We have a formula now ? => register it to parametrized exchange
                    if 'formula' in attrs:
                        parametrized = True

        def deleteExchanges(self, name, single=True):
            ''' Remove matching exchanges '''
            exchs = self.getExchange(name, single=single)
            if not isinstance(exchs, list):
                exchs = [exchs]
            if len(exchs) == 0:
                raise Exception("No exchange found for '%s'" % name)
            for ex in exchs:
                ex.delete()
                ex.save()
            self.save()

        @with_db_context
        def substituteWithDefault(self, exchange_name: str, switch_act: Activity, paramSwitch: EnumParam, amount=None):

            """Substitutes one exchange with a switch on other activities, or fallback to the current one as default (parameter set to None)
            For this purpose, we create a new exchange referencing the activity switch, and we multiply current activity by '<param_name>_default',
            making it null as soon as one enum value is set.

            This is useful for changing electricty mix, leaving the default one if needed

            Parameters
            ----------
            act : Activity to update
            exchange_name : Name of the exchange to update
            switch_act : Activity to substitue as input
            amount : Amount of the input (uses previous amount by default)
            """

            current_exch = self.getExchange(exchange_name)

            prev_amount = amount if amount else _getAmountOrFormula(current_exch)

            self.addExchanges({switch_act: prev_amount})
            self.updateExchanges({exchange_name: paramSwitch.symbol(None) * prev_amount})

        @with_db_context
        def addExchanges(self, exchanges: Dict[Activity, Union[NumOrExpression, dict]] = dict()):
            """Add exchanges to an existing activity, with a compact syntax :

            Parameters
            ----------
            exchanges : Dict of activity => amount or activity => attributes_dict. \
                Amount being either a fixed value or Sympy expression (arithmetic expression of Sympy symbols)
            """

            with DbContext(self.key[0]) :

                for sub_act, attrs in exchanges.items():

                    if isinstance(attrs, dict):
                        amount = attrs.pop('amount')
                    else:
                        amount = attrs
                        attrs = dict()

                    exch = self.new_exchange(
                        input=sub_act.key,
                        name=sub_act['name'],
                        unit=sub_act['unit'] if 'unit' in sub_act else None,
                        type='production' if self == sub_act else 'technosphere' if sub_act.get('type') == 'process' else 'biosphere')

                    exch.update(attrs)
                    exch.update(_amountToFormula(amount))
                    if 'formula' in exch:
                        parametrized = True

                    exch.save()
                self.save()

        @with_db_context
        def getAmount(self, *args, sum=False, **kargs):
            """
            Get the amount of one or several exchanges, selected by name or input. See #getExchange()
            """
            exchs = self.getExchange(*args, single=not sum, **kargs)
            if sum:
                res = 0
                if len(exchs) == 0:
                    raise Exception("No exchange found")
                for exch in exchs:
                    res += _getAmountOrFormula(exch)
                return res
            else:
                return _getAmountOrFormula(exchs)

        def getOutputAmount(self):

            """ Return the amount of the production : 1 if none is found """
            res = 1
            for exch in self.exchanges() :
                if exch['input'] == exch['output']:
                    # Not 1 ?
                    if exch['amount'] != 1:
                        res = exch['amount']
                    break
            return res

        def exchangesNp(self):
            """ List of exchange, except production (output) one."""
            for exch in self.exchanges():
                if exch['input'] != exch['output']:
                    yield exch

    # Backport new methods to vanilla Activity class in order to benefit from it for all existing instances
    for name, item in ActivityExtended.__dict__.items():
        if isinstance(item, types.FunctionType):
            setattr(Activity, name, item)

    def _split_words(name):
        clean = re.sub('[^0-9a-zA-Z]+', ' ', name)
        clean = re.sub(' +', ' ', clean)
        clean = clean.lower()

        return clean.split(' ')

    def _build_index(db):
        res = defaultdict(set)
        for act in db:
            words = _split_words(act['name'])
            for word in words:
                res[word].add(act)
        return res

    # Index of activities per name, for fast search dict[db_name][activity_word] => list of activitites
    db_index = dict()

    def _get_indexed_db(db_name):
        if not db_name in db_index:
            db_index[db_name] = _build_index(_getDb(db_name))
        return db_index[db_name]

    def _find_candidates(db_name, name):
        res = []
        index = _get_indexed_db(db_name)
        words = _split_words(name)
        for word in words:
            candidates = index[word]
            if len(res) == 0 or (0 < len(candidates) < len(res)):
                res = list(candidates)
        return res

    def getActByCode(db_name, code):
        """ Get activity by code """
        return _getDb(db_name).get(code)

    def findActivity(name=None, loc=None, in_name=None, code=None, categories=None, category=None, db_name=None,
                     single=True, unit=None) -> ActivityExtended :
        """
            Find single activity by name & location
            Uses index for fast fetching
        """

        if name and '*' in name:
            in_name = name.replace("*", "")
            name = None

        def act_filter(act):
            if name and not name == act['name']:
                return False
            if in_name and not in_name in act['name']:
                return False
            if loc and not loc == act['location']:
                return False
            if unit and not unit == act['unit']:
                return False
            if category and not category in act['categories']:
                return False
            if categories and not tuple(categories) == act['categories']:
                return False
            return True

        if code:
            acts = [getActByCode(db_name, code)]
        else:
            search = name if name is not None else in_name

            search = search.lower()
            search = search.replace(',', ' ')

            # Find candidates via index
            # candidates = _find_candidates(db_name, name_key)
            candidates = _getDb(db_name).search(search, limit=200)

            if len(candidates) == 0 :
                # Try again removing strange caracters
                search = re.sub(r'\w*[^a-zA-Z ]+\w*', ' ', search)
                candidates = _getDb(db_name).search(search, limit=200)

            # print(search, candidates)

            # Exact match
            acts = list(filter(act_filter, candidates))

        if single and len(acts) == 0:
            raise Exception("No activity found in '%s' with name '%s' and location '%s'" % (db_name, name, loc))
        if single and len(acts) > 1:
            raise Exception("Several activity found in '%s' with name '%s' and location '%s':\n%s" % (
                db_name, name, loc, str(acts)))
        if len(acts) == 1:
            return acts[0]
        else:
            return acts

    def findBioAct(name=None, loc=None, **kwargs):
        """Alias for findActivity(name, ... db_name=BIOSPHERE3_DB_NAME) """
        return findActivity(name=name, loc=loc, db_name=BIOSPHERE3_DB_NAME, **kwargs)

    def findTechAct(name=None, loc=None, single=True, **kwargs):
        """ Search activities in any background db not being biospehere """
        dbs = _listTechBackgroundDbs()
        if len(dbs) > 1 :
            raise Exception("There is more than one technosphere background DB (%s) please use findActivity(..., db_name=YOUR_DB)" % str(dbs))

        return findActivity(name=name, loc=loc, db_name=dbs[0], single=single, **kwargs)

    def _amountToFormula(amount: Union[float, str, Basic], currentAmount=None):
        """Transform amount in exchange to either simple amount or formula"""
        res = dict()
        if isinstance(amount, Basic):

            if currentAmount != None:
                amount = amount.subs(old_amount, currentAmount)

            # Check the expression does not reference undefined params
            all_symbols = list([key for param in _param_registry().values() for key, val in param.expandParams().items()])
            for symbol in amount.free_symbols:
                if not str(symbol) in all_symbols:
                    raise Exception("Symbol '%s' not found in params : %s" % (symbol, all_symbols))

            res['formula'] = str(amount)
            res['amount'] = 0
        elif isinstance(amount, float) or isinstance(amount, int):
            res['amount'] = amount
        else:
            raise Exception(
                "Amount should be either a constant number or a Sympy expression (expression of ParamDef). Was : %s" % type(
                    amount))
        return res

    def _newAct(db_name, code):

        if not _isForeground(db_name) :
            error("WARNING: You are creating activity in background DB. You should only do it in your foreground / user DB : ", db_name)

        db = _getDb(db_name)
        # Already present : delete it ?
        for act in db:
            if act['code'] == code:
                error("Activity '%s' was already in '%s'. Overwriting it" % (code, db_name))
                act.delete()

        return db.new_activity(code)

    def newActivity(db_name, name, unit,
                    exchanges: Dict[Activity, Union[float, str]] = dict(),
                    code=None,
                    **argv):
        """Creates a new activity

        Parameters
        ----------
        name : Name ofthe new activity
        db_name : Destination DB : ACV DB by default
        exchanges : Dict of activity => amount. If amount is a string, is it considered as a formula with parameters
        argv : extra params passed as properties of the new activity
        """
        act = _newAct(db_name, code if code else name)
        act['name'] = name
        act['type'] = 'process'
        act['unit'] = unit
        act.update(argv)

        # Add exchanges
        act.addExchanges(exchanges)

        return act

    def copyActivity(db_name, activity: ActivityExtended, code=None, withExchanges=True, **kwargs) -> ActivityExtended:
        """Copy activity into a new DB"""

        res = _newAct(db_name, code)

        for key, value in activity.items():
            if key not in ['database', 'code']:
                res[key] = value
        for k, v in kwargs.items():
            res._data[k] = v
        res._data[u'code'] = code
        res['name'] = code
        res['type'] = 'process'
        res.save()

        if withExchanges:
            for exc in activity.exchanges():
                data = deepcopy(exc._data)
                data['output'] = res.key
                # Change `input` for production exchanges
                if exc['input'] == exc['output']:
                    data['input'] = res.key
                ExchangeDataset.create(**dict_as_exchangedataset(data))

        return res

    ActivityOrActivityAmount = Union[Activity, Tuple[Activity, float]]
    def newSwitchAct(dbname, name, paramDef: ParamDef, acts_dict: Dict[str, ActivityOrActivityAmount]):
        """Create a new parametrized, virtual activity, made of a map of other activities, controlled by an enum parameter.
        This enables to implement a "Switch" with brightway parameters
        Internally, this will create a linear sum of other activities controlled by <param_name>_<enum_value> : 0 or 1

        By default, all activities have associated amount of 1.
        You can provide other amounts by providing a tuple of (activity, amount).

        Parameters
        ----------
        dbname: name of the target DB
        name: Name of the new activity
        paramDef : parameter definition of type enum
        acts_dict : dict of "enumValue" => activity or "enumValue" => (activity, amount)

        Examples
        --------

        >>> newSwitchAct(MYDB, "switchAct", switchParam, {
        >>>    "val1" : act1 # Amount is 1
        >>>    "val2" : (act2, 0.4) # Different amount
        >>>    "val3" : (act3, b + 6) # Amount with formula
        >>> }
        """

        # Transform map of enum values to corresponding formulas <param_name>_<enum_value>
        exch = defaultdict(lambda : 0)

        # Forward last unit as unit of the switch
        unit= None
        for key, act in acts_dict.items() :
            amount = 1
            if type(act) == list or type(act) == tuple :
                act, amount = act
            exch[act] += amount * paramDef.symbol(key)
            unit = act["unit"]

        res = newActivity(
            dbname,
            name,
            unit=unit,
            exchanges=exch)

        return res

    def printAct(*args, impact=None, **params):
        """
        Print activities and their exchanges.
        If parameter values are provided, formulas will be evaluated accordingly.
        If impact is provided it will be computed.
        """
        tables = []
        names = []

        activities = args

        for act in activities:
            with DbContext(act.key[0]) :
                inputs_by_ex_name = dict()
                df = pd.DataFrame(index=['input', 'amount', 'unit'])
                data = dict()
                for (i, exc) in enumerate(act.exchanges()):

                    # Don't show production
                    if _isOutputExch(exc) :
                        continue

                    input = bw.get_activity(exc.input.key)
                    amount = _getAmountOrFormula(exc)

                    # Params provided ? Evaluate formulas
                    if len(params) > 0 and isinstance(amount, Basic):
                        new_params = [(name, value) for name, value in _completeParamValues(params).items()]
                        amount = amount.subs(new_params)

                    ex_name = exc['name']
                    #if 'location' in input and input['location'] != "GLO":
                    #    name += "#%s" % input['location']
                    #if exc.input.key[0] not in [BIOSPHERE3_DB_NAME, ECOINVENT_DB_NAME()]:
                    #    name += " {user-db}"

                    # Unique name : some exchanges may havve same names
                    _name = ex_name
                    i = 1
                    while ex_name in data:
                        ex_name = "%s#%d" % (_name, i)
                        i += 1

                    inputs_by_ex_name[ex_name] = input

                    input_name = _actName(input)
                    if _isForeground(input.key[0]):
                        input_name += "{FG}"

                    data[ex_name] = [input_name, amount, exc.unit]

                # Provide impact calculation if impact provided

                for key, values in data.items():
                    df[key] = values

                tables.append(df.T)
                names.append(_actDesc(act))

        full = pd.concat(tables, axis=1, keys=names, sort=True)

        # Highlight differences in case two activites are provided
        if len(activities) == 2:
            yellow = "background-color:yellow"
            iamount1 = full.columns.get_loc((names[0], "amount"))
            iamount2 = full.columns.get_loc((names[1], "amount"))
            iact1 = full.columns.get_loc((names[0], "input"))
            iact2 = full.columns.get_loc((names[1], "input"))

            def same_amount(row):
                res = [""] * len(row)

                if row[iamount1] != row[iamount2]:
                    res[iamount1] = yellow
                    res[iamount2] = yellow
                if row[iact1] != row[iact2]:
                    res[iact1] = yellow
                    res[iact2] = yellow
                return res

            full = full.style.apply(same_amount, axis=1)

        display(full)

    def newInterpolatedAct(dbname: str, name: str, act1: ActivityExtended, act2: ActivityExtended, x1, x2, x, alpha1=1,
                           alpha2=1, **kwargs):
        """Creates a new activity made of interpolation of two similar activities.
        For each exchange :
        amount = alpha1 * a1 + (x - X1) * (alpha2 * a2 - alpha1 * a1) / (x2 - x1)

        Parameters
        ----------
        name : Name of new activity
        act1 : Activity 1
        act2 : Activity 2
        x1 : X for act1
        x2 : X for act 2
        x : Should be a parameter symbol
        alpha1 : Ratio for act1 (Default value = 1)
        alpha2 : Ratio for act2 (Default value = 1)
        kwargs : Any other param will be added as attributes of new activity
        """
        res = copyActivity(dbname, act1, name, withExchanges=False, **kwargs)

        exch1_by_input = dict({exch['input']: exch for exch in act1.exchangesNp()})
        exch2_by_input = dict({exch['input']: exch for exch in act2.exchangesNp()})

        inputs = set(chain(exch1_by_input.keys(), exch2_by_input.keys()))

        for input in inputs:

            exch1 = exch1_by_input.get(input)
            exch2 = exch2_by_input.get(input)
            exch = exch1 if exch1 else exch2

            amount1 = exch1['amount'] if exch1 else 0
            amount2 = exch2['amount'] if exch2 else 0

            if exch1 and exch2 and exch1['name'] != exch2['name']:
                raise Exception("Input %s refer two different names : %s, %s" % (input, exch1['name'], exch2['name']))

            amount = interpolate(x, x1, x2, amount1 * alpha1, amount2 * alpha2)
            act = getActByCode(*input)
            res.addExchanges({act: dict(amount=amount, name=exch['name'])})
        return res

    def findMethods(search=None, mainCat=None) :

        """
        Find impact method. Search in all methods against a list of match strings.
        Each parameter can be either an exact match match, or case insenstive search, if suffixed by '*'

        Parameters
        ----------
        search : String to search
        mainCat : if specified, limits the research for method[0] == mainCat.
        """
        res = []
        search = search.lower()
        for method in bw.methods:
            text = str(method).lower()
            match  = search in text
            if mainCat :
                match = match and (mainCat == method[0])
            if match :
                res.append(method)
        return res

## Functions

`def SET_USER_DB(db_name)`

  Deprecated, use #setForeground() / setBackground() instead

  Expand source code

      def SET_USER_DB(db_name) :
          """Deprecated, use #setForeground() / setBackground() instead"""
          error("Deprecated, use #setForeground() / setBackground() instead")
          setForeground(db_name)

`def copyActivity(db_name, activity, code=None, withExchanges=True, **kwargs)`

  Copy activity into a new DB

  Expand source code

      def copyActivity(db_name, activity: ActivityExtended, code=None, withExchanges=True, **kwargs) -> ActivityExtended:
          """Copy activity into a new DB"""

          res = _newAct(db_name, code)

          for key, value in activity.items():
              if key not in ['database', 'code']:
                  res[key] = value
          for k, v in kwargs.items():
              res._data[k] = v
          res._data[u'code'] = code
          res['name'] = code
          res['type'] = 'process'
          res.save()

          if withExchanges:
              for exc in activity.exchanges():
                  data = deepcopy(exc._data)
                  data['output'] = res.key
                  # Change `input` for production exchanges
                  if exc['input'] == exc['output']:
                      data['input'] = res.key
                  ExchangeDataset.create(**dict_as_exchangedataset(data))

          return res

`def findActivity(name=None, loc=None, in_name=None, code=None, categories=None, category=None, db_name=None, single=True, unit=None)`

  Find single activity by name & location Uses index for fast fetching

  Expand source code

      def findActivity(name=None, loc=None, in_name=None, code=None, categories=None, category=None, db_name=None,
                       single=True, unit=None) -> ActivityExtended :
          """
              Find single activity by name & location
              Uses index for fast fetching
          """

          if name and '*' in name:
              in_name = name.replace("*", "")
              name = None

          def act_filter(act):
              if name and not name == act['name']:
                  return False
              if in_name and not in_name in act['name']:
                  return False
              if loc and not loc == act['location']:
                  return False
              if unit and not unit == act['unit']:
                  return False
              if category and not category in act['categories']:
                  return False
              if categories and not tuple(categories) == act['categories']:
                  return False
              return True

          if code:
              acts = [getActByCode(db_name, code)]
          else:
              search = name if name is not None else in_name

              search = search.lower()
              search = search.replace(',', ' ')

              # Find candidates via index
              # candidates = _find_candidates(db_name, name_key)
              candidates = _getDb(db_name).search(search, limit=200)

              if len(candidates) == 0 :
                  # Try again removing strange caracters
                  search = re.sub(r'\w*[^a-zA-Z ]+\w*', ' ', search)
                  candidates = _getDb(db_name).search(search, limit=200)

              # print(search, candidates)

              # Exact match
              acts = list(filter(act_filter, candidates))

          if single and len(acts) == 0:
              raise Exception("No activity found in '%s' with name '%s' and location '%s'" % (db_name, name, loc))
          if single and len(acts) > 1:
              raise Exception("Several activity found in '%s' with name '%s' and location '%s':\n%s" % (
                  db_name, name, loc, str(acts)))
          if len(acts) == 1:
              return acts[0]
          else:
              return acts

`def findBioAct(name=None, loc=None, **kwargs)`

  Alias for findActivity(name, … db_name=BIOSPHERE3_DB_NAME)

  Expand source code

      def findBioAct(name=None, loc=None, **kwargs):
          """Alias for findActivity(name, ... db_name=BIOSPHERE3_DB_NAME) """
          return findActivity(name=name, loc=loc, db_name=BIOSPHERE3_DB_NAME, **kwargs)

`def findMethods(search=None, mainCat=None)`

  Find impact method. Search in all methods against a list of match strings. Each parameter can be either an exact match match, or case insenstive search, if suffixed by '*'

  ## Parameters

  `search` :`String` `to` `search`

  mainCat : if specified, limits the research for method[0] == mainCat.

  Expand source code

      def findMethods(search=None, mainCat=None) :

          """
          Find impact method. Search in all methods against a list of match strings.
          Each parameter can be either an exact match match, or case insenstive search, if suffixed by '*'

          Parameters
          ----------
          search : String to search
          mainCat : if specified, limits the research for method[0] == mainCat.
          """
          res = []
          search = search.lower()
          for method in bw.methods:
              text = str(method).lower()
              match  = search in text
              if mainCat :
                  match = match and (mainCat == method[0])
              if match :
                  res.append(method)
          return res

`def findTechAct(name=None, loc=None, single=True, **kwargs)`

  Search activities in any background db not being biospehere

  Expand source code

      def findTechAct(name=None, loc=None, single=True, **kwargs):
          """ Search activities in any background db not being biospehere """
          dbs = _listTechBackgroundDbs()
          if len(dbs) > 1 :
              raise Exception("There is more than one technosphere background DB (%s) please use findActivity(..., db_name=YOUR_DB)" % str(dbs))

          return findActivity(name=name, loc=loc, db_name=dbs[0], single=single, **kwargs)

`def getActByCode(db_name, code)`

  Get activity by code

  Expand source code

      def getActByCode(db_name, code):
          """ Get activity by code """
          return _getDb(db_name).get(code)

`def list_databases()`

  List of databases and their status

  Expand source code

      def list_databases() :
          """List of databases and their status"""
          data = list(
              dict(
                  name=name,
                  backend=_getMeta(name, "backend"),
                  type="foreground" if _isForeground(name) else "background") for name in bw.databases)
          res = pd.DataFrame(data)
          return res.set_index("name")

`def newActivity(db_name, name, unit, exchanges={}, code=None, **argv)`

  Creates a new activity

  ## Parameters

  `name` :`Name` `ofthe` `new` `activity`
  `db_name` :`Destination` `DB` : `ACV` `DB` `by` default
  `exchanges` :`Dict` of `activity` => `amount.` `If` `amount` `is` `a` `string`, `is` `it` `considered` `as` `a` `formula` `with` `parameters`
  `argv` :`extra` `params` `passed` `as` `properties` of `the` `new` `activity`
  Expand source code

      def newActivity(db_name, name, unit,
                      exchanges: Dict[Activity, Union[float, str]] = dict(),
                      code=None,
                      **argv):
          """Creates a new activity

          Parameters
          ----------
          name : Name ofthe new activity
          db_name : Destination DB : ACV DB by default
          exchanges : Dict of activity => amount. If amount is a string, is it considered as a formula with parameters
          argv : extra params passed as properties of the new activity
          """
          act = _newAct(db_name, code if code else name)
          act['name'] = name
          act['type'] = 'process'
          act['unit'] = unit
          act.update(argv)

          # Add exchanges
          act.addExchanges(exchanges)

          return act

`def newInterpolatedAct(dbname, name, act1, act2, x1, x2, x, alpha1=1, alpha2=1, **kwargs)`

  Creates a new activity made of interpolation of two similar activities. For each exchange : amount = alpha1 * a1 + (x - X1) * (alpha2 * a2 - alpha1 * a1) / (x2 - x1)

  ## Parameters

  `name` :`Name` of `new` `activity`
  `act1` :`Activity` `1`
  `act2` :`Activity` `2`
  `x1` :`X` `for` `act1`
  `x2` :`X` `for` `act` `2`
  `x` :`Should` `be` `a` `parameter` `symbol`
  `alpha1` :`Ratio` `for` `act1` (`Default` `value` = `1`)
  `alpha2` :`Ratio` `for` `act2` (`Default` `value` = `1`)
  `kwargs` :`Any` `other` `param` `will` `be` `added` `as` `attributes` of `new` `activity`
  Expand source code

      def newInterpolatedAct(dbname: str, name: str, act1: ActivityExtended, act2: ActivityExtended, x1, x2, x, alpha1=1,
                             alpha2=1, **kwargs):
          """Creates a new activity made of interpolation of two similar activities.
          For each exchange :
          amount = alpha1 * a1 + (x - X1) * (alpha2 * a2 - alpha1 * a1) / (x2 - x1)

          Parameters
          ----------
          name : Name of new activity
          act1 : Activity 1
          act2 : Activity 2
          x1 : X for act1
          x2 : X for act 2
          x : Should be a parameter symbol
          alpha1 : Ratio for act1 (Default value = 1)
          alpha2 : Ratio for act2 (Default value = 1)
          kwargs : Any other param will be added as attributes of new activity
          """
          res = copyActivity(dbname, act1, name, withExchanges=False, **kwargs)

          exch1_by_input = dict({exch['input']: exch for exch in act1.exchangesNp()})
          exch2_by_input = dict({exch['input']: exch for exch in act2.exchangesNp()})

          inputs = set(chain(exch1_by_input.keys(), exch2_by_input.keys()))

          for input in inputs:

              exch1 = exch1_by_input.get(input)
              exch2 = exch2_by_input.get(input)
              exch = exch1 if exch1 else exch2

              amount1 = exch1['amount'] if exch1 else 0
              amount2 = exch2['amount'] if exch2 else 0

              if exch1 and exch2 and exch1['name'] != exch2['name']:
                  raise Exception("Input %s refer two different names : %s, %s" % (input, exch1['name'], exch2['name']))

              amount = interpolate(x, x1, x2, amount1 * alpha1, amount2 * alpha2)
              act = getActByCode(*input)
              res.addExchanges({act: dict(amount=amount, name=exch['name'])})
          return res

`def newSwitchAct(dbname, name, paramDef, acts_dict)`

  Create a new parametrized, virtual activity, made of a map of other activities, controlled by an enum parameter. This enables to implement a "Switch" with brightway parameters Internally, this will create a linear sum of other activities controlled by _ : 0 or 1

  By default, all activities have associated amount of 1. You can provide other amounts by providing a tuple of (activity, amount).

  ## Parameters

  `dbname` :`name` of `the` `target` `DB`
  `name` :`Name` of `the` `new` `activity`
  `paramDef` :`parameter` `definition` of `type` `enum`
  `acts_dict` :`dict` of `"enumValue"` => `activity` or `"enumValue"` => (`activity`, `amount`)

  ## Examples

      >>> newSwitchAct(MYDB, "switchAct", switchParam, {
      >>>    "val1" : act1 # Amount is 1
      >>>    "val2" : (act2, 0.4) # Different amount
      >>>    "val3" : (act3, b + 6) # Amount with formula
      >>> }

  Expand source code

      def newSwitchAct(dbname, name, paramDef: ParamDef, acts_dict: Dict[str, ActivityOrActivityAmount]):
          """Create a new parametrized, virtual activity, made of a map of other activities, controlled by an enum parameter.
          This enables to implement a "Switch" with brightway parameters
          Internally, this will create a linear sum of other activities controlled by <param_name>_<enum_value> : 0 or 1

          By default, all activities have associated amount of 1.
          You can provide other amounts by providing a tuple of (activity, amount).

          Parameters
          ----------
          dbname: name of the target DB
          name: Name of the new activity
          paramDef : parameter definition of type enum
          acts_dict : dict of "enumValue" => activity or "enumValue" => (activity, amount)

          Examples
          --------

          >>> newSwitchAct(MYDB, "switchAct", switchParam, {
          >>>    "val1" : act1 # Amount is 1
          >>>    "val2" : (act2, 0.4) # Different amount
          >>>    "val3" : (act3, b + 6) # Amount with formula
          >>> }
          """

          # Transform map of enum values to corresponding formulas <param_name>_<enum_value>
          exch = defaultdict(lambda : 0)

          # Forward last unit as unit of the switch
          unit= None
          for key, act in acts_dict.items() :
              amount = 1
              if type(act) == list or type(act) == tuple :
                  act, amount = act
              exch[act] += amount * paramDef.symbol(key)
              unit = act["unit"]

          res = newActivity(
              dbname,
              name,
              unit=unit,
              exchanges=exch)

          return res

`def printAct(*args, impact=None, **params)`

  Print activities and their exchanges. If parameter values are provided, formulas will be evaluated accordingly. If impact is provided it will be computed.

  Expand source code

      def printAct(*args, impact=None, **params):
          """
          Print activities and their exchanges.
          If parameter values are provided, formulas will be evaluated accordingly.
          If impact is provided it will be computed.
          """
          tables = []
          names = []

          activities = args

          for act in activities:
              with DbContext(act.key[0]) :
                  inputs_by_ex_name = dict()
                  df = pd.DataFrame(index=['input', 'amount', 'unit'])
                  data = dict()
                  for (i, exc) in enumerate(act.exchanges()):

                      # Don't show production
                      if _isOutputExch(exc) :
                          continue

                      input = bw.get_activity(exc.input.key)
                      amount = _getAmountOrFormula(exc)

                      # Params provided ? Evaluate formulas
                      if len(params) > 0 and isinstance(amount, Basic):
                          new_params = [(name, value) for name, value in _completeParamValues(params).items()]
                          amount = amount.subs(new_params)

                      ex_name = exc['name']
                      #if 'location' in input and input['location'] != "GLO":
                      #    name += "#%s" % input['location']
                      #if exc.input.key[0] not in [BIOSPHERE3_DB_NAME, ECOINVENT_DB_NAME()]:
                      #    name += " {user-db}"

                      # Unique name : some exchanges may havve same names
                      _name = ex_name
                      i = 1
                      while ex_name in data:
                          ex_name = "%s#%d" % (_name, i)
                          i += 1

                      inputs_by_ex_name[ex_name] = input

                      input_name = _actName(input)
                      if _isForeground(input.key[0]):
                          input_name += "{FG}"

                      data[ex_name] = [input_name, amount, exc.unit]

                  # Provide impact calculation if impact provided

                  for key, values in data.items():
                      df[key] = values

                  tables.append(df.T)
                  names.append(_actDesc(act))

          full = pd.concat(tables, axis=1, keys=names, sort=True)

          # Highlight differences in case two activites are provided
          if len(activities) == 2:
              yellow = "background-color:yellow"
              iamount1 = full.columns.get_loc((names[0], "amount"))
              iamount2 = full.columns.get_loc((names[1], "amount"))
              iact1 = full.columns.get_loc((names[0], "input"))
              iact2 = full.columns.get_loc((names[1], "input"))

              def same_amount(row):
                  res = [""] * len(row)

                  if row[iamount1] != row[iamount2]:
                      res[iamount1] = yellow
                      res[iamount2] = yellow
                  if row[iact1] != row[iact2]:
                      res[iact1] = yellow
                      res[iact2] = yellow
                  return res

              full = full.style.apply(same_amount, axis=1)

          display(full)

`def setBackground(db_name)`

  Set a db as being a foreground database, meaning it should be considred as static

  Expand source code

      def setBackground(db_name) :
          """ Set a db as being a foreground database, meaning it should be considred as static"""
          return _setMeta(db_name, FOREGROUND_KEY, False)

`def setForeground(db_name)`

  Set a db as being a foreground database, meaning it is parametrized and lca_algebraic should develop its activities

  Expand source code

      def setForeground(db_name) :
          """ Set a db as being a foreground database, meaning it is parametrized and lca_algebraic should develop its activities"""
          return _setMeta(db_name, FOREGROUND_KEY, True)

`def with_db_context(func)`

  Internal decorator wrapping function into DbContext, using its first parameters (either Activity, Db or Db name)

  Expand source code

      def with_db_context(func):
          """ Internal decorator wrapping function into DbContext, using its first parameters (either Activity, Db or Db name)"""
          @functools.wraps(func)
          def wrapper(*args, **kwargs):
              act = args[0]
              dbname = act.key[0]
              with DbContext(dbname) :
                  return func(*args, **kwargs)
          return wrapper

## Classes

`class ActivityExtended (document=None, **kwargs)`

  Improved API for activity : adding a few useful methods. Those methods are backported to #Activity in order to be directly available on all existing instances

  Create an `Activity` proxy object.

  If this is a new activity, can pass `kwargs`.

  If the activity exists in the database, `document` should be an `ActivityDataset`.

  Expand source code

      class ActivityExtended(Activity):
          """Improved API for activity : adding a few useful methods.
          Those methods are backported to #Activity in order to be directly available on all existing instances
          """

          @with_db_context
          def listExchanges(self) :
              """ Iterates on all exchanges (except "production") and return a list of (exch-name, target-act, amount) """
              res = []
              for exc in self.exchanges():

                  # Don't show production
                  if _isOutputExch(exc) :
                      continue

                  input = bw.get_activity(exc.input.key)
                  amount = _getAmountOrFormula(exc)
                  res.append((exc["name"], input, amount))
              return res

          @with_db_context
          def getExchange(self, name=None, input=None, single=True):
              """Get exchange by name or input

              Parameters
              ----------
              name : name of the exchange. Name can be suffixed with '#LOCATION' to distinguish several exchanges with same name. \
                  It can also be suffised by '*' to match an exchange starting with this name. Location can be a negative match '!'
                  Exampple : "Wood*#!RoW" matches any exchange with name  containing Wood, and location not "RoW"

              single :True if a single match is expected. Otherwize, a list of result is returned

              Returns
              -------
                  Single exchange or list of exchanges (if _single is False or "name" contains a '*')
                  raise Exception if not matching exchange found
              """

              def single_match(name, exch):

                  # Name can be "Elecricity#RER"
                  if "#" in name:
                      name, loc = name.split("#")
                      negative = False
                      if loc.startswith("!"):
                          negative = True
                          loc = loc[1:]
                      act = getActByCode(*exch['input'])

                      if not 'location' in act or (negative and act['location'] == loc) or (
                              not negative and act['location'] != loc):
                          return False

                  if '*' in name:
                      name = name.replace('*', '')
                      return name in exch['name']
                  else:
                      return name == exch['name']

              def match(exch):
                  if name:
                      if isinstance(name, list):
                          return any(single_match(iname, exch) for iname in name)
                      else:
                          return single_match(name, exch)

                  if input:
                      return input == exch['input']

              exchs = list(exch for exch in self.exchangesNp() if match(exch))
              if len(exchs) == 0:
                  raise Exception("Found no exchange matching name : %s" % name)

              if single and len(exchs) != 1:
                  raise Exception("Expected 1 exchange with name '%s' found %d" % (name, len(exchs)))
              if single:
                  return exchs[0]
              else:
                  return exchs

          def setOutputAmount(self, amount):
              '''Set the amount for the single output exchange (1 by default)'''
              self.addExchanges({self: amount})

          @with_db_context
          def updateExchanges(self, updates: Dict[str, any] = dict()):

              """Update existing exchanges, by name.

              Parameters
              ----------
              updates : Dict of "<exchange name>" => <new value>

                  <exchange name> can be suffixed with '#LOCATION' to distinguish several exchanges with same name. \
                  It can also be suffixed by '*' to match an exchange starting with this name. Location can be a negative match '!'
                  Exampple : "Wood*#!RoW" matches any exchange with name  containing Wood, and location not "RoW"

                  <New Value>  : either single value (float or SympPy expression) for updating only amount, or activity for updating only input,
                  or dict of attributes, for updating both at once, or any other attribute.
                  The amount can reference the symbol 'old_amount' that will be replaced with the current amount of the exchange.
              """

              parametrized = False

              # Update exchanges
              for name, attrs in updates.items():

                  exchs = self.getExchange(name, single=not '*' in name)
                  if not isinstance(exchs, list):
                      exchs = [exchs]

                  for exch in exchs:

                      if attrs is None:
                          exch.delete()
                          exch.save()
                          continue

                      # Single value ? => amount
                      if not isinstance(attrs, dict):
                          if isinstance(attrs, Activity):
                              attrs = dict(input=attrs)
                          else:
                              attrs = dict(amount=attrs)

                      if 'amount' in attrs:
                          attrs.update(_amountToFormula(attrs['amount'], exch['amount']))

                      exch.update(attrs)
                      exch.save()

                      # We have a formula now ? => register it to parametrized exchange
                      if 'formula' in attrs:
                          parametrized = True

          def deleteExchanges(self, name, single=True):
              ''' Remove matching exchanges '''
              exchs = self.getExchange(name, single=single)
              if not isinstance(exchs, list):
                  exchs = [exchs]
              if len(exchs) == 0:
                  raise Exception("No exchange found for '%s'" % name)
              for ex in exchs:
                  ex.delete()
                  ex.save()
              self.save()

          @with_db_context
          def substituteWithDefault(self, exchange_name: str, switch_act: Activity, paramSwitch: EnumParam, amount=None):

              """Substitutes one exchange with a switch on other activities, or fallback to the current one as default (parameter set to None)
              For this purpose, we create a new exchange referencing the activity switch, and we multiply current activity by '<param_name>_default',
              making it null as soon as one enum value is set.

              This is useful for changing electricty mix, leaving the default one if needed

              Parameters
              ----------
              act : Activity to update
              exchange_name : Name of the exchange to update
              switch_act : Activity to substitue as input
              amount : Amount of the input (uses previous amount by default)
              """

              current_exch = self.getExchange(exchange_name)

              prev_amount = amount if amount else _getAmountOrFormula(current_exch)

              self.addExchanges({switch_act: prev_amount})
              self.updateExchanges({exchange_name: paramSwitch.symbol(None) * prev_amount})

          @with_db_context
          def addExchanges(self, exchanges: Dict[Activity, Union[NumOrExpression, dict]] = dict()):
              """Add exchanges to an existing activity, with a compact syntax :

              Parameters
              ----------
              exchanges : Dict of activity => amount or activity => attributes_dict. \
                  Amount being either a fixed value or Sympy expression (arithmetic expression of Sympy symbols)
              """

              with DbContext(self.key[0]) :

                  for sub_act, attrs in exchanges.items():

                      if isinstance(attrs, dict):
                          amount = attrs.pop('amount')
                      else:
                          amount = attrs
                          attrs = dict()

                      exch = self.new_exchange(
                          input=sub_act.key,
                          name=sub_act['name'],
                          unit=sub_act['unit'] if 'unit' in sub_act else None,
                          type='production' if self == sub_act else 'technosphere' if sub_act.get('type') == 'process' else 'biosphere')

                      exch.update(attrs)
                      exch.update(_amountToFormula(amount))
                      if 'formula' in exch:
                          parametrized = True

                      exch.save()
                  self.save()

          @with_db_context
          def getAmount(self, *args, sum=False, **kargs):
              """
              Get the amount of one or several exchanges, selected by name or input. See #getExchange()
              """
              exchs = self.getExchange(*args, single=not sum, **kargs)
              if sum:
                  res = 0
                  if len(exchs) == 0:
                      raise Exception("No exchange found")
                  for exch in exchs:
                      res += _getAmountOrFormula(exch)
                  return res
              else:
                  return _getAmountOrFormula(exchs)

          def getOutputAmount(self):

              """ Return the amount of the production : 1 if none is found """
              res = 1
              for exch in self.exchanges() :
                  if exch['input'] == exch['output']:
                      # Not 1 ?
                      if exch['amount'] != 1:
                          res = exch['amount']
                      break
              return res

          def exchangesNp(self):
              """ List of exchange, except production (output) one."""
              for exch in self.exchanges():
                  if exch['input'] != exch['output']:
                      yield exch

  ### Ancestors

  - bw2data.backends.peewee.proxies.Activity
  - bw2data.proxies.ActivityProxyBase
  - bw2data.proxies.ProxyBase
  - collections.abc.MutableMapping
  - collections.abc.Mapping
  - collections.abc.Collection
  - collections.abc.Sized
  - collections.abc.Iterable
  - collections.abc.Container

  ### Methods

  `def addExchanges(self, exchanges={})`

    Add exchanges to an existing activity, with a compact syntax :

    ## Parameters

    `exchanges` :`Dict` of `activity` => `amount` or `activity` => `attributes_dict.` `Amount` `being` `either` `a` `fixed` `value` or `Sympy` `expression` (`arithmetic` `expression` of `Sympy` `symbols`)
    Expand source code

        @with_db_context
        def addExchanges(self, exchanges: Dict[Activity, Union[NumOrExpression, dict]] = dict()):
            """Add exchanges to an existing activity, with a compact syntax :

            Parameters
            ----------
            exchanges : Dict of activity => amount or activity => attributes_dict. \
                Amount being either a fixed value or Sympy expression (arithmetic expression of Sympy symbols)
            """

            with DbContext(self.key[0]) :

                for sub_act, attrs in exchanges.items():

                    if isinstance(attrs, dict):
                        amount = attrs.pop('amount')
                    else:
                        amount = attrs
                        attrs = dict()

                    exch = self.new_exchange(
                        input=sub_act.key,
                        name=sub_act['name'],
                        unit=sub_act['unit'] if 'unit' in sub_act else None,
                        type='production' if self == sub_act else 'technosphere' if sub_act.get('type') == 'process' else 'biosphere')

                    exch.update(attrs)
                    exch.update(_amountToFormula(amount))
                    if 'formula' in exch:
                        parametrized = True

                    exch.save()
                self.save()

  `def deleteExchanges(self, name, single=True)`

    Remove matching exchanges

    Expand source code

        def deleteExchanges(self, name, single=True):
            ''' Remove matching exchanges '''
            exchs = self.getExchange(name, single=single)
            if not isinstance(exchs, list):
                exchs = [exchs]
            if len(exchs) == 0:
                raise Exception("No exchange found for '%s'" % name)
            for ex in exchs:
                ex.delete()
                ex.save()
            self.save()

  `def exchangesNp(self)`

    List of exchange, except production (output) one.

    Expand source code

        def exchangesNp(self):
            """ List of exchange, except production (output) one."""
            for exch in self.exchanges():
                if exch['input'] != exch['output']:
                    yield exch

  `def getAmount(self, *args, sum=False, **kargs)`

    Get the amount of one or several exchanges, selected by name or input. See #getExchange()

    Expand source code

        @with_db_context
        def getAmount(self, *args, sum=False, **kargs):
            """
            Get the amount of one or several exchanges, selected by name or input. See #getExchange()
            """
            exchs = self.getExchange(*args, single=not sum, **kargs)
            if sum:
                res = 0
                if len(exchs) == 0:
                    raise Exception("No exchange found")
                for exch in exchs:
                    res += _getAmountOrFormula(exch)
                return res
            else:
                return _getAmountOrFormula(exchs)

  `def getExchange(self, name=None, input=None, single=True)`

    Get exchange by name or input

    ## Parameters

    `name` :`name` of `the` `exchange.` `Name` `can` `be` `suffixed` `with` `'`#`LOCATION'` `to` `distinguish` `several` `exchanges` `with` `same` `name.` `It` `can` `also` `be` `suffised` `by` `'`*`'` `to` `match` `an` `exchange` `starting` `with` `this` `name.` `Location` `can` `be` `a` `negative` `match` `'`!`'`
      Exampple : "Wood*#!RoW" matches any exchange with name containing Wood, and location not "RoW"

    single :True if a single match is expected. Otherwize, a list of result is returned

    ## Returns

        Single exchange or list of exchanges (if _single is False or "name" contains a '*')
        raise Exception if not matching exchange found

    Expand source code

        @with_db_context
        def getExchange(self, name=None, input=None, single=True):
            """Get exchange by name or input

            Parameters
            ----------
            name : name of the exchange. Name can be suffixed with '#LOCATION' to distinguish several exchanges with same name. \
                It can also be suffised by '*' to match an exchange starting with this name. Location can be a negative match '!'
                Exampple : "Wood*#!RoW" matches any exchange with name  containing Wood, and location not "RoW"

            single :True if a single match is expected. Otherwize, a list of result is returned

            Returns
            -------
                Single exchange or list of exchanges (if _single is False or "name" contains a '*')
                raise Exception if not matching exchange found
            """

            def single_match(name, exch):

                # Name can be "Elecricity#RER"
                if "#" in name:
                    name, loc = name.split("#")
                    negative = False
                    if loc.startswith("!"):
                        negative = True
                        loc = loc[1:]
                    act = getActByCode(*exch['input'])

                    if not 'location' in act or (negative and act['location'] == loc) or (
                            not negative and act['location'] != loc):
                        return False

                if '*' in name:
                    name = name.replace('*', '')
                    return name in exch['name']
                else:
                    return name == exch['name']

            def match(exch):
                if name:
                    if isinstance(name, list):
                        return any(single_match(iname, exch) for iname in name)
                    else:
                        return single_match(name, exch)

                if input:
                    return input == exch['input']

            exchs = list(exch for exch in self.exchangesNp() if match(exch))
            if len(exchs) == 0:
                raise Exception("Found no exchange matching name : %s" % name)

            if single and len(exchs) != 1:
                raise Exception("Expected 1 exchange with name '%s' found %d" % (name, len(exchs)))
            if single:
                return exchs[0]
            else:
                return exchs

  `def getOutputAmount(self)`

    Return the amount of the production : 1 if none is found

    Expand source code

        def getOutputAmount(self):

            """ Return the amount of the production : 1 if none is found """
            res = 1
            for exch in self.exchanges() :
                if exch['input'] == exch['output']:
                    # Not 1 ?
                    if exch['amount'] != 1:
                        res = exch['amount']
                    break
            return res

  `def listExchanges(self)`

    Iterates on all exchanges (except "production") and return a list of (exch-name, target-act, amount)

    Expand source code

        @with_db_context
        def listExchanges(self) :
            """ Iterates on all exchanges (except "production") and return a list of (exch-name, target-act, amount) """
            res = []
            for exc in self.exchanges():

                # Don't show production
                if _isOutputExch(exc) :
                    continue

                input = bw.get_activity(exc.input.key)
                amount = _getAmountOrFormula(exc)
                res.append((exc["name"], input, amount))
            return res

  `def setOutputAmount(self, amount)`

    Set the amount for the single output exchange (1 by default)

    Expand source code

        def setOutputAmount(self, amount):
            '''Set the amount for the single output exchange (1 by default)'''
            self.addExchanges({self: amount})

  `def substituteWithDefault(self, exchange_name, switch_act, paramSwitch, amount=None)`

    Substitutes one exchange with a switch on other activities, or fallback to the current one as default (parameter set to None) For this purpose, we create a new exchange referencing the activity switch, and we multiply current activity by '_default', making it null as soon as one enum value is set.

    This is useful for changing electricty mix, leaving the default one if needed

    ## Parameters

    `act` :`Activity` `to` `update`
    `exchange_name` :`Name` of `the` `exchange` `to` `update`
    `switch_act` :`Activity` `to` `substitue` `as` `input`
    `amount` :`Amount` of `the` `input` (`uses` `previous` `amount` `by` default)
    Expand source code

        @with_db_context
        def substituteWithDefault(self, exchange_name: str, switch_act: Activity, paramSwitch: EnumParam, amount=None):

            """Substitutes one exchange with a switch on other activities, or fallback to the current one as default (parameter set to None)
            For this purpose, we create a new exchange referencing the activity switch, and we multiply current activity by '<param_name>_default',
            making it null as soon as one enum value is set.

            This is useful for changing electricty mix, leaving the default one if needed

            Parameters
            ----------
            act : Activity to update
            exchange_name : Name of the exchange to update
            switch_act : Activity to substitue as input
            amount : Amount of the input (uses previous amount by default)
            """

            current_exch = self.getExchange(exchange_name)

            prev_amount = amount if amount else _getAmountOrFormula(current_exch)

            self.addExchanges({switch_act: prev_amount})
            self.updateExchanges({exchange_name: paramSwitch.symbol(None) * prev_amount})

  `def updateExchanges(self, updates={})`

    Update existing exchanges, by name.

    ## Parameters

    `updates` :`Dict` of `"`<`exchange` `name`>`"` => <`new` `value`>

      can be suffixed with '#LOCATION' to distinguish several exchanges with same name. It can also be suffixed by '' to match an exchange starting with this name. Location can be a negative match '!' Exampple : "Wood#!RoW" matches any exchange with name containing Wood, and location not "RoW"

      : either single value (float or SympPy expression) for updating only amount, or activity for updating only input, or dict of attributes, for updating both at once, or any other attribute. The amount can reference the symbol 'old_amount' that will be replaced with the current amount of the exchange.

    Expand source code

        @with_db_context
        def updateExchanges(self, updates: Dict[str, any] = dict()):

            """Update existing exchanges, by name.

            Parameters
            ----------
            updates : Dict of "<exchange name>" => <new value>

                <exchange name> can be suffixed with '#LOCATION' to distinguish several exchanges with same name. \
                It can also be suffixed by '*' to match an exchange starting with this name. Location can be a negative match '!'
                Exampple : "Wood*#!RoW" matches any exchange with name  containing Wood, and location not "RoW"

                <New Value>  : either single value (float or SympPy expression) for updating only amount, or activity for updating only input,
                or dict of attributes, for updating both at once, or any other attribute.
                The amount can reference the symbol 'old_amount' that will be replaced with the current amount of the exchange.
            """

            parametrized = False

            # Update exchanges
            for name, attrs in updates.items():

                exchs = self.getExchange(name, single=not '*' in name)
                if not isinstance(exchs, list):
                    exchs = [exchs]

                for exch in exchs:

                    if attrs is None:
                        exch.delete()
                        exch.save()
                        continue

                    # Single value ? => amount
                    if not isinstance(attrs, dict):
                        if isinstance(attrs, Activity):
                            attrs = dict(input=attrs)
                        else:
                            attrs = dict(amount=attrs)

                    if 'amount' in attrs:
                        attrs.update(_amountToFormula(attrs['amount'], exch['amount']))

                    exch.update(attrs)
                    exch.save()

                    # We have a formula now ? => register it to parametrized exchange
                    if 'formula' in attrs:
                        parametrized = True