lca_algebraic API documentation

# Module `lca_algebraic`

Expand source code

    #
    # This file defines several utility functions above brightway2 to be used by notebooks
    #
    import re
    import sys
    from collections import defaultdict
    from typing import Dict, Union, List, Any
    import types
    import brightway2 as bw
    import numpy as np
    import pandas as pd
    from IPython.display import display
    from bw2data.backends.peewee import Activity, ActivityDataset
    from bw2data.parameters import ActivityParameter, ProjectParameter, DatabaseParameter, Group, ExchangeDataset
    from sympy import Symbol, Basic, simplify, symbols
    from sympy.parsing.sympy_parser import parse_expr
    from slugify import slugify
    from sympy.utilities.lambdify import lambdify
    from collections import OrderedDict
    from bw2data.backends.peewee.utils import dict_as_exchangedataset
    from copy import deepcopy
    from itertools import chain
    import builtins
    import matplotlib.pyplot as plt
    from ipywidgets import interact, interactive, fixed, interact_manual
    import ipywidgets as widgets
    import warnings
    from SALib.sample import saltelli
    import math
    from SALib.analyze import sobol
    from scipy.stats import binned_statistic
    import seaborn as sns
    from sys import stderr
    import math
    from enum import Enum
    from scipy.stats import triang
    from scipy.stats import truncnorm
    from math import sqrt

    # -- Constants
    DEBUG=False

    def debug(*args, **kwargs) :
        if DEBUG :
            print(*args, **kwargs)

    def error(*args, **kwargs):
        print(*args, **kwargs, file=stderr)

    # DB names

    ECOINVENT_DB_NAME = 'ecoinvent 3.4 cut off'
    BIOSPHERE3_DB_NAME = 'biosphere3'

    USER_DB_NAME = None

    DEFAULT_PARAM_GROUP = "acv"

    # Global
    def _param_registry() :

        # Prevent reset upon auto reload in jupyter notebook
        if not 'param_registry' in builtins.__dict__:
            builtins.param_registry = dict()

        return builtins.param_registry

    # Sympy symbols
    old_amount = symbols("old_amount")  # Can be used in epxression of amount for updateExchanges, in order to reference the previous value

    NumOrExpression = Union[float, Basic]

    class ParamType:
        '''Type of parameters'''
        ENUM = "enum"
        BOOL = "bool"
        FLOAT = "float"

    class DistributionType :
        '''Type of distribution'''
        LINEAR = "linear"
        NORMAL = "normal"
        TRIANGLE = "triangle"
        FIXED = "fixed"

    class ParamDef(Symbol):
        '''Generic definition of a parameter, with name, bound, type, distribution
        This definition will serve both to generate brightway2 parameters and to evaluate.

        This class inherits sympy Symbol, making it possible to use in standard arithmetic python
        while keeping it as a symbolic expression (delayed evaluation).
        '''

        def __new__(cls, name, *karg, **kargs):
            return Symbol.__new__(cls, name)

        def __init__(self, name, type: str, default, min=None, max=None, unit="", description="", label=None, label_fr=None, group=None, distrib=DistributionType.LINEAR, std=None):
            self.name = name
            self.type = type
            self.default = default
            self.description = description
            self.min = min
            self.max = max
            self.unit = unit
            self.label = label
            self.label_fr = label_fr
            self.group=group
            self.distrib = distrib

            if type == ParamType.FLOAT and self.min is None :
                self.distrib = DistributionType.FIXED

            if distrib == DistributionType.NORMAL and std is None :
                raise Exception("Standard deviation is mandatory for normal distribution")
            self.std = std

        def label(self):
            if self.label is not None :
                return self.label
            else :
                return self.name.replace("_", " ")

        def range(self, n) :
            '''Used for parametric analysis'''
            step = (self.max - self.min) / (n - 1)
            return list(i * step + self.min for i in range(0, n))

        def rand(self, alpha):
            """Transforms a random number between 0 and 1 to valid value according to the distribution of probability of the parameter"""
            if self.distrib == DistributionType.LINEAR :
                return self.min + alpha * (self.max - self.min)

            elif self.distrib == DistributionType.TRIANGLE :
                if not hasattr(self, "_distrib") :
                    scale = self.max - self.min
                    c = (self.default - self.min) / scale
                    self._distrib = triang(c, loc=self.min, scale=scale)

                return self._distrib.ppf(alpha)

            elif self.distrib == DistributionType.NORMAL :
                if not hasattr(self, "_distrib") :
                    self._distrib = truncnorm(
                        (self.min - self.default) / self.std,
                        (self.max - self.min) / self.std,
                        loc=self.default,
                        scale=self.std)

                return self._distrib.ppf(alpha)

            else :
                raise Exception("Unknowk distribution type " + self.distrib)

        # Expand parameter (useful for enum param)
        def expandParams(self, value=None) -> Dict[str, float]:
            if value == None:
                value = self.default
            return {self.name: value}

        # Useful for enum param, having several names
        def names(self) :
            return [self.name]

        def __repr__(self):
            return self.name

    class BooleanDef(ParamDef):
        """Parameter with discrete value 0 or 1"""

        def __init__(self, name, **argv):
            super(BooleanDef, self).__init__(name, ParamType.BOOL, min=0, max=1, **argv)

        def range(self, n):
            return [0, 1]

        def rand(self, alpha):
            return round(alpha)

    class EnumParam(ParamDef):
        """Enum param is a facility representing a choice / switch as many boolean parameters.
        It is not itself a Sympy symbol. use #symbol("value") to access it"""

        def __init__(self, name, values: List[str], **argv):
            super(EnumParam, self).__init__(name, ParamType.ENUM, min=None, max=None, **argv)
            self.values = values

        def expandParams(self, currValue=None):
            values = self.values + [None]
            res = dict()
            for enum_val in values:
                var_name = "%s_%s" % (self.name, enum_val if enum_val is not None else "default")
                res[var_name] = 1.0 if enum_val == currValue else 0.0
            return res

        def symbol(self, enumValue):
            """Access parameter for each enum value : <paramName>_<paramValue>"""
            if enumValue is None:
                return Symbol(self.name + '_default')
            if not enumValue in self.values:
                raise Exception("enumValue should be one of %s. Was %s" % (str(self.values), enumValue))
            return Symbol(self.name + '_' + enumValue)

        def names(self) :
            return ["%s_%s" % (self.name, value) for value in (self.values + ["default"]) ]

        def rand(self, alpha):
            i = math.ceil(alpha * (len(self.values))) -1
            return self.values[int(i)]

        def range(self, n):
            return self.values

    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
        """

        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})

        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.
            """

            # 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:
                        bw.parameters.add_exchanges_to_group(DEFAULT_PARAM_GROUP, self)

        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 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 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)
            """
            parametrized = False
            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 'biosphere' if sub_act['database'] == BIOSPHERE3_DB_NAME else 'technosphere')

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

                exch.save()
            self.save()
            if parametrized:
                bw.parameters.add_exchanges_to_group(DEFAULT_PARAM_GROUP, self)

        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 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 _eprint(*args, **kwargs):
        print(*args, file=sys.stderr, **kwargs)

    def _isnumber(value):
        return isinstance(value, int) or isinstance(value, float)

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

        for act in activities:
            df = pd.DataFrame(index=['input', 'amount', 'unit', 'type'])
            data = dict()
            for (i, exc) in enumerate(act.exchanges()):
                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)

                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}"

                iname = name
                i=1
                while iname in data :
                    iname = "%s#%d" % (name, i)
                    i += 1

                data[iname] = [str(input), amount, exc.unit, exc['type']]

            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)

        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 resetDb(db_name):
        """ Create or cleanup a user DB"""
        if db_name in bw.databases:
            _eprint("Db %s was here. Reseting it" % db_name)
            del bw.databases[db_name]
        db = bw.Database(db_name)
        db.write(dict())

    def importDb(dbname, path):
        '''Import eco invent DB'''

        if dbname in bw.databases:
            _eprint("Database '%s' has already been imported " % dbname)
        else:
            ei34 = bw.SingleOutputEcospold2Importer(path, dbname)
            ei34.apply_strategies()
            ei34.statistics()
            ei34.write_database()

    dbs = dict()

    def _getDb(dbname) -> bw.Database:
        """Pool of Database instances"""
        if not dbname in dbs:
            dbs[dbname] = bw.Database(dbname)
        return dbs[dbname]

    def resetParams(db_name):
        """Reset project and activity parameters"""
        _param_registry().clear()
        ProjectParameter.delete().execute()
        ActivityParameter.delete().execute()
        DatabaseParameter.delete().execute()
        Group.delete().execute()

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

    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

    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):
        """
            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:
            name_key = name if name else in_name

            # Find candidates via index
            candidates = _find_candidates(db_name, name_key)

            # 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, **kwargs):
        """Alias for findActivity(name, ... db_name=ECOINVENT_DB_NAME)
        """
        return findActivity(name=name, loc=loc, db_name=ECOINVENT_DB_NAME, **kwargs)

    def interpolate(x, x1, x2, y1, y2):
        """Build an expression for linear interpolation between two points"""
        return y1 + (y2 - y1) * (x - x1) / (x2 - x1)

    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 newParamDef(name, type, **kwargs):
        """Creates a param and register it into a global registry and as a brightway parameter"""
        if type == ParamType.ENUM:
            param = EnumParam(name, **kwargs)
        elif type == ParamType.BOOL :
            param = BooleanDef(name, **kwargs)
        else:
            param = ParamDef(name, type=type, **kwargs)

        # Put it in local registry (in memory)
        if name in _param_registry():
            _eprint("Param %s was already defined : overriding" % name)
        _param_registry()[name] = param

        # Save in brightway2 project
        bwParams = [dict(name=key, amount=value) for key, value in param.expandParams().items()]
        bw.parameters.new_project_parameters(bwParams)

        return param

    def newFloatParam(name, default, **kwargs):
        return newParamDef(name, ParamType.FLOAT, default=default, **kwargs)

    def newBoolParam(name, default, **kwargs):
        return newParamDef(name, ParamType.BOOL, default=default, **kwargs)

    def newEnumParam(name, default, **kwargs):
        return newParamDef(name, ParamType.ENUM, default=default, **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 _getAmountOrFormula(ex: ExchangeDataset) -> Union[Basic, float]:
        """ Return either a fixed float value or an expression for the amount of this exchange"""
        if 'formula' in ex:
            try:
                return parse_expr(ex['formula'])
            except:
                _eprint("Error while parsing formula '%s' : backing to amount" % ex['formula'])

        return ex['amount']

    def _newAct(db_name, code):

        db = _getDb(db_name)
        # Already present : delete it ?
        for act in db:
            if act['code'] == code:
                _eprint("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.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 newSwitchAct(dbname, name, paramDef: ParamDef, acts_dict: Dict[str, Activity]):
        """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

        Parameters
        ----------
        paramDef : parameter definition of type enum
        acts_dict : dict of <enumValue> => activity
        """

        # Transform map of enum values to correspoding formulas <param_name>_<enum_value>
        exch = {act: paramDef.symbol(key) for key, act in acts_dict.items()}
        res = newActivity(
            dbname,
            name,
            unit=list(acts_dict.values())[0]['unit'],
            exchanges=exch)

        # Unit of switch activity is the one of the children
        for key, act in acts_dict.items():
            if 'unit' in act:
                res['unit'] = act['unit']
                res.save()
        return res

    def _actName(act: Activity):
        """Generate pretty name for activity, appending location if not 'GLO' """
        res = act['name']
        if act['location'] != 'GLO':
            res += "[%s]" % act["location"]
        return res

    def _actDesc(act: Activity):
        """Generate pretty name for activity + basic information """
        name = _actName(act)
        amount = 1
        for ex in act.exchanges() :
            if ex['type'] == 'production' :
                amount = ex['amount']

        return "%s (%f %s)" % (name, amount, act['unit'])

    def _multiLCA(activities, methods):
        """Simple wrapper around brightway API"""
        bw.calculation_setups['process'] = {'inv': activities, 'ia': methods}
        lca = bw.MultiLCA('process')
        cols = [_actName(act) for act_amount in activities for act, amount in act_amount.items()]
        return pd.DataFrame(lca.results.T, index=[method_name(method) for method in methods], columns=cols)

    def _listOfDictToDictOflist(LD):
        return {k: [dic[k] for dic in LD] for k in LD[0]}

    def _completeParamValues(params):
        """Check parameters and expand enum params.

        Returns
        -------
            Dict of param_name => float value
        """

        # undef_params = param_registry.keys() - params.keys()
        # if undef_params :
        #    raise Exception("Some model parameters are not set : %s" % undef_params)

        res = dict()
        for key, val in params.items():
            if key in _param_registry():
                param = _param_registry()[key]
            else:
                raise Exception("Parameter not found : %s. Valid parameters : %s" % (key, list(_param_registry().keys())))

            if isinstance(val, list):
                newvals = [param.expandParams(val) for val in val]
                res.update(_listOfDictToDictOflist(newvals))
            else:
                res.update(param.expandParams(val))
        return res

    def multiLCA(model, methods, **params):
        """Compute LCA for a single activity and a set of methods, after settings the parameters and updating exchange amounts.

        Parameters
        ----------
        model : Single activity (root model) or list of activities
        methods : Impact methods to consider
        params : Other parameters of the model
        """

        # Check and expand params
        params = _completeParamValues(params)

        # Update brightway parameters
        bwParams = [dict(name=key, amount=value) for key, value in params.items()]
        bw.parameters.new_project_parameters(bwParams)

        # ActivityParameter.recalculate_exchanges(DEFAULT_PARAM_GROUP)
        bw.parameters.recalculate()

        if isinstance(model, list):
            activities = [{act: 1} for act in model]
        else:
            activities = [{model: 1}]
        return _multiLCA(activities, methods).transpose()

    def preMultiLCAAlgebric(model:ActivityExtended, methods, param_names=None, amount=1) :
        '''
            This method transforms an activity into a set of functions ready to compute LCA very fast on a set on methods.
            You may use is and pass the result to postMultiLCAAlgebric for fast computation on a model that does not change.

            This method is used by multiLCAAlgebric
        '''

        # print("computing model to expression for %s" % model)
        expr, actBySymbolName = actToExpression(model)

        dbname = model.key[0]

        param_names = _expand_param_names(param_names)

        # Check missing params
        free_names = set([str(symb) for symb in expr.free_symbols])
        act_names = set([str(symb) for symb in actBySymbolName.keys()])
        expected_names  = free_names - act_names
        missing_names = expected_names - set(param_names)

        if len(missing_names) > 0 :
            raise Exception('Missing parameter of the model : %s' % str(missing_names))

        debug(param_names)

        for name in param_names :
            registry_names = _expand_param_names(_param_registry())
            if name not in registry_names :
                raise Exception('Model refers to unknown param "%s"' % name)

        # Create dummy reference to biosphere
        # We cannot run LCA to biosphere activities
        # We create a technosphere activity mapping exactly to 1 biosphere item
        pureTechActBySymbol = OrderedDict()
        for name, act in actBySymbolName.items():
            if act[0] == BIOSPHERE3_DB_NAME:
                act = _getOrCreateDummyBiosphereActCopy(dbname, act[1])
            else:
                act = getActByCode(*act)
            pureTechActBySymbol[name] = act

        # List of activities, ordered
        acts = pureTechActBySymbol.values()

        # Transform to [{act1:1], {act2:1}, etc] for MultiLCA
        actsWithAmount = [{act: 1} for act in acts]

        # Compute LCA for all background activities and methods
        lca = _multiLCA(actsWithAmount, methods)

        # For each method, compute an algebric expression with activities replaced by their values
        lambdas = []
        for imethod, method in enumerate(methods):
            # print("Generating lamba function for %s / %s" % (model, method))

            # Replace activities by their value in expression for this method
            sub = dict({symbol: lca.iloc[imethod, iact] for iact, symbol in enumerate(pureTechActBySymbol.keys())})
            method_expr = expr.xreplace(sub)

            # Tranform Sympy expression to lambda function, based on numpy to fast vectorial evaluation
            lambd = lambdify(param_names, method_expr, 'numpy')
            lambdas.append(lambd)

        return lambdas

    def method_name(method) :
        return method[1] + " - " + method[2]

    def postMultiLCAAlgebric(methods, lambdas, alpha=1, **params):
        '''
            Compute LCA for a given set of parameters and pre-compiled lambda functions.
            This function is used by **multiLCAAlgebric**

            Parameters
            ----------
            methodAndLambdas : Output of preMultiLCAAlgebric
            **params : Parameters of the model
        '''

        # Check and expand params
        params = _completeParamValues(params)

        # Expand parameters as list of parameters
        param_length = 1

        for key, val in params.items():
            if isinstance(val, list):
                if param_length == 1:
                    param_length = len(val)
                elif param_length != len(val):
                    raise Exception("Parameters should be a single value or a list of same number of values")

        # Expand params and transform lists to np.array for vector computation
        for key in params.keys():
            val = params[key]
            if not isinstance(val, list):
                val = list([val] * param_length)
            params[key] = np.array(val)

        res = np.zeros((len(methods), param_length))

        # Compute result on whole vectors of parameter samples at a time : lambdas use numpy for vector computation
        for imethod, lambd in enumerate(lambdas):
            res[imethod, :] = alpha * lambd(**params)

        return pd.DataFrame(res, index=[method_name(method) for method in methods]).transpose()

    def _expand_param_names(param_names) :
        '''Expand parameters names (with enum params) '''
        return [name for key in param_names for name in _param_registry()[key].names()]

    def multiLCAAlgebric(models, methods, **params):
        """Compute LCA by expressing the foreground model as symbolic expression of background activities and parameters.
        Then, compute 'static' inventory of the referenced background activities.
        This enables a very fast recomputation of LCA with different parameters, useful for stochastic evaluation of parametrized model

        Parameters
        ----------
        models : Single model or list of models or dict of model:amount : if list of models, you cannot use param lists
        methods : List of methods / impacts to consider
        params : You should provide named values of all the parameters declared in the model. \
                 Values can be single value or list of samples, all of the same size
        """
        dfs = dict()

        if not isinstance(models, list):
            models = [models]

        for model in models:

            alpha = 1
            if type(model) is tuple :
                model, alpha=model

            lambdas = preMultiLCAAlgebric(model, methods, params.keys())

            df = postMultiLCAAlgebric(methods, lambdas, alpha=alpha, **params)

            model_name = _actName(model)

            # Single params ? => give the single row the name of the model activity
            if df.shape[0] == 1:
                df = df.rename(index={0: model_name})

            dfs[model_name] = df

        if len(dfs) == 1:
            df = list(dfs.values())[0]
            return df
        else:
            # Concat several dataframes for several models
            return pd.concat(list(dfs.values()))

    def _getOrCreateDummyBiosphereActCopy(dbname, code):
        """
            We cannot reference directly biosphere in the model, since LCA can only be applied to products
            We create a dummy activity in our DB, with same code, and single exchange of amount '1'
        """

        code_to_find = code + "#asTech"
        try:
            return _getDb(dbname).get(code_to_find)
        except:
            bioAct = _getDb(BIOSPHERE3_DB_NAME).get(code)
            name = bioAct['name'] + ' # asTech'
            res = newActivity(dbname, name, bioAct['unit'], {bioAct: 1}, code=code_to_find)
            return res

    def actToExpression(act: Activity):
        """Computes a symbolic expression of the model, referencing background activities and model parameters as symbols

        Returns
        -------
            (sympy_expr, dict of symbol => activity)
        """

        act_symbols = dict()  # Dict of  act = > symbol

        def act_to_symbol(db_name, code):

            act = _getDb(db_name).get(code)
            name = act['name']
            base_slug = slugify(name, separator='_')

            slug = base_slug
            i = 1
            while symbols(slug) in act_symbols.values():
                slug = f"{base_slug}{i}"
                i += 1

            return symbols(slug)

        def rec_func(act: Activity):

            res = 0
            outputAmount = 1

            for exch in act.exchanges():

                formula = _getAmountOrFormula(exch)

                if isinstance(formula, types.FunctionType):
                    # Some amounts in EIDB are functions ... we ignore them
                    continue

                input_db, input_code = exch['input']

                #  Different output ?
                if exch['input'] == exch['output']:
                    if exch['amount'] != 1:
                        outputAmount = exch['amount']
                    continue

                # Background DB => reference it as a symbol
                if input_db in [BIOSPHERE3_DB_NAME, ECOINVENT_DB_NAME]:
                    if not (input_db, input_code) in act_symbols:
                        act_symbols[(input_db, input_code)] = act_to_symbol(input_db, input_code)
                    act_expr = act_symbols[(input_db, input_code)]

                # Our model : recursively transform it to a symbolic expression
                else:

                    if input_db == act['database'] and input_code == act['code']:
                        raise Exception("Recursive exchange : %s" % (act.__dict__))

                    sub_act = _getDb(input_db).get(input_code)
                    act_expr = rec_func(sub_act)

                res += formula * act_expr

            return  res / outputAmount

        expr = rec_func(act)

        return (expr, _reverse_dict(act_symbols))

    def _reverse_dict(dic):
        return {v: k for k, v in dic.items()}

    def _heatmap(df, title, vmax, ints=False):
        ''' Produce heatmap of a dataframe'''
        fig, ax = plt.subplots(figsize=(17, 17))
        sns.heatmap(df.transpose(), cmap="gist_heat_r", vmax=vmax, annot=True, fmt='.0f' if ints else 'f', square=True)
        plt.title(title, fontsize=20)
        ax.tick_params(axis="x", labelsize=18)
        ax.tick_params(axis="y", labelsize=18)

    def oat_matrix(model, impacts, n=10) :
        '''Generates a heatmap of the incertitude of the model, varying input parameters one a a time.'''

        # Compile model into lambda functions for fast LCA
        lambdas = preMultiLCAAlgebric(model, impacts, _param_registry().keys())

        change = np.zeros((len(_variable_params()), len(impacts)))

        for iparam, param in enumerate(_variable_params().values()) :
            params = {param.name: param.default for param in _param_registry().values()}

            # Compute range of values for given param
            params[param.name] = param.range(n)

            # Compute LCA
            df = postMultiLCAAlgebric(impacts, lambdas, **params)

            # Compute change
            change[iparam] =  (df.max() - df.min()) / df.median() * 100

        # Build final heatmap
        change = pd.DataFrame(change, index=_variable_params().keys(), columns=[imp[2] for imp in impacts])
        _heatmap(change.transpose(), 'Change of impacts per variability of the input parameters (%)', 100, ints=True)

    def _method_unit(method) :
        return bw.Method(method).metadata['unit']

    def _display_tabs(titlesAndContentF) :
        '''Generate tabs'''
        tabs = []
        titles= []
        for title, content_f in titlesAndContentF :
            titles.append(title)

            tab = widgets.Output()
            with tab :
                content_f()
            tabs.append(tab)

        res = widgets.Tab(children=tabs)
        for i, title in enumerate(titles) :
            res.set_title(i, title)
        display(res)

    def oat_dasboard(modelOrLambdas, impacts, param: ParamDef, n=10) :
        '''
        Analyse the evolution of impacts for a single parameter. The other parameters are set to their default values.

        Parameters
        ----------
        model : activity, or lambdas as precomputed by preMultiLCAAlgebric, for faster computation
        impacts : set of methods
        param: parameter to analyse
        n: number of samples of the parameter
        '''

        params = {param.name : param.default for param in _param_registry().values()}

        # Compute range of values for given param
        params[param.name] = param.range(n)

        #print("Params: ", params)

        if isinstance(modelOrLambdas, Activity) :
            df = multiLCAAlgebric(modelOrLambdas, impacts, **params)
        else :
            df = postMultiLCAAlgebric(impacts, modelOrLambdas, **params)

        # Add X values in the table
        pname = param.name
        if param.unit :
            pname = '%s [%s]' % (pname, param.unit)
        df.insert(0, pname, param.range(n))
        df = df.set_index(pname)

        def table() :
            display(df)

        def graph() :

            with warnings.catch_warnings():

                warnings.simplefilter("ignore")

                nb_rows = len(impacts) // 3 + 1

                fig, axes = plt.subplots(figsize=(15, 15))

                axes = df.plot(
                    ax=axes, sharex=True, subplots=True,
                    layout=(nb_rows, 3),
                    #legend=None,
                    kind = 'line' if param.type == ParamType.FLOAT else 'bar')

                axes = axes.flatten()

                for ax, impact in zip(axes, impacts) :
                    ax.set_ylim(ymin=0)
                    ax.set_ylabel(_method_unit(impact))

                plt.show(fig)

        def change() :

            ch = (df.max() - df.min()) / df.median() * 100
            fig, ax = plt.subplots(figsize=(9, 6))
            plt.title('Relative change for %s' % df.index.name)
            ch.plot(kind='barh', rot=30)
            ax.set_xlabel('Relative change of the median value (%)')
            plt.tight_layout()
            plt.show(fig)

        _display_tabs([
            ("Graphs", graph),
            ("Data", table),
            ("Variation", change)
        ])

    def oat_dashboard_interact(model, methods):
        '''Interative dashboard, with a dropdown for selecting parameter'''

        lambdas = preMultiLCAAlgebric(model, methods, _param_registry().keys())

        def process_func(param) :
            oat_dasboard(lambdas, methods, _param_registry()[param])

        paramlist = list(_variable_params().keys())
        interact(process_func, param=paramlist)

    def _stochastics(modelOrLambdas, methods, n=1000) :

        ''' Compute stochastic impacts for later analysis of incertitude '''

        # Extract variable names
        param_names = list(_variable_params().keys())
        problem = {
            'num_vars': len(param_names),
            'names': param_names,
            'bounds': [[0, 1]] * len(param_names)
        }

        print("Generating samples ...")
        X = saltelli.sample(problem, n, calc_second_order=True)

        # Map normalized 0-1 random values into real values
        print("Transforming samples ...")
        params = dict()
        for i, param_name in enumerate(param_names) :
            param = _param_registry()[param_name]
            vals = list(map(lambda v : param.rand(v), X[:, i]))
            params[param_name] = vals

        # Add static parameters
        for param in _fixed_params().values() :
            params[param.name] = param.default

        print("Processing LCA ...")
        if isinstance(modelOrLambdas, Activity):
            Y = multiLCAAlgebric(modelOrLambdas, methods, **params)
        else:
            Y = postMultiLCAAlgebric(methods, modelOrLambdas, **params)

        return problem, X, Y

    def _variable_params():
        return {key : param for key, param in _param_registry().items() if param.distrib != DistributionType.FIXED}

    def _fixed_params():
        return {key : param for key, param in _param_registry().items() if param.distrib == DistributionType.FIXED}

    def _sobols(methods, problem, Y) :
        ''' Computes sobols indices'''
        s1 = np.zeros((len(problem['names']), len(methods)))
        st = np.zeros((len(problem['names']), len(methods)))

        for i, method in enumerate(methods) :

            try:
                y = Y[Y.columns[i]]
                res = sobol.analyze(problem, y.to_numpy(), calc_second_order=True)
                st[:, i] = res["ST"]
                s1[:, i] = res["S1"]

            except Exception as e:
                error("Sobol failed on %s" % method[2], e)
        return (s1, st)

    def _incer_stochastic_matrix(methods, param_names, Y, st):

        ''' Internal method computing matrix of parameter importance '''
        def draw(mode) :

            if mode == 'sobol' :
                data = st
            else :
                # If percent, express result as percentage of standard deviation / mean
                data = np.zeros((len(param_names), len(methods)))
                for i, method in enumerate(methods):
                    # Total variance
                    var = np.var(Y[Y.columns[i]])
                    mean = np.mean(Y[Y.columns[i]])
                    if mean != 0 :
                        data[:, i] = np.sqrt((st[:, i] * var)) / mean * 100

            df = pd.DataFrame(data, index=param_names, columns=[method_name(method) for method in methods])
            _heatmap(
                df.transpose(),
                title="Relative deviation of impacts (%)" if mode == 'percent' else "Sobol indices (part of variability)",
                vmax=100 if mode == 'percent' else 1,
                ints= mode == 'percent')

        interact(draw, mode=[('Raw sobol indices (ST)', 'sobol'), ('Deviation (ST) / mean', 'percent')])

    def incer_stochastic_matrix(modelOrLambdas, methods, n=1000):
        ''' Method computing matrix of parameter importance '''

        problem, X, Y = _stochastics(modelOrLambdas, methods, n)

        print("Processing Sobol indices ...")
        s1, st = _sobols(methods, problem, Y)

        _incer_stochastic_matrix(methods, problem['names'], Y, st)

    def _incer_stochastic_violin(methods, Y) :
        ''' Internal method for computing violin graph of impacts '''

        nb_rows = math.ceil(len(methods) / 3)
        fig, axes = plt.subplots(nb_rows, 3, figsize=(15, 15), sharex=True)

        for imethod, method, ax in zip(range(len(methods)), methods, axes.flatten()) :
            ax.violinplot(Y[Y.columns[imethod]], showmedians=True)
            ax.title.set_text(method_name(method))
            ax.set_ylim(ymin=0)
            ax.set_ylabel(_method_unit(method))

        plt.tick_params(axis='x', which='both', bottom=False, top=False, labelbottom=False)
        plt.show(fig)

    def incer_stochastic_violin(modelOrLambdas, methods, n=1000):

        ''' Method for computing violin graph of impacts '''

        problem, X, Y = _stochastics(modelOrLambdas, methods, n)

        _incer_stochastic_violin(methods, Y)

    def _incer_stochastic_variations(methods, Y, param_names, sobols1):

        ''' Method for computing violin graph of impacts '''
        method_names=[method_name(method) for method in methods]

        std = np.std(Y)
        mean = np.mean(Y)

        fig = plt.figure(num=None, figsize=(12, 6), dpi=80, facecolor='w', edgecolor='k')
        ax = plt.gca()
        tab20b = plt.get_cmap('tab20b')
        tab20c = plt.get_cmap('tab20c')
        ax.set_prop_cycle('color', [tab20b(k) if k < 1 else tab20c(k-1) for k in np.linspace(0, 2, 40)])

        relative_variance_pct =  std*std / (mean*mean) * 100
        totplt = plt.bar(np.arange(len(method_names)), relative_variance_pct, 0.8)

        sum = np.zeros(len(methods))

        plots = [totplt[0]]

        data = np.zeros((len(param_names) + 2, len(methods)))
        data[0, :] = mean
        data[1, :] = std

        for i_param, param_name in enumerate(param_names) :
            s1 = sobols1[i_param, :]
            data[i_param+2, :] = s1

            curr_bar = s1 * relative_variance_pct
            curr_plt = plt.bar(np.arange(len(method_names)), curr_bar, 0.8, bottom=sum)
            sum += curr_bar
            plots.append(curr_plt[0])

        plt.legend(plots, ['Higher order'] + param_names)
        plt.xticks(np.arange(len(method_names)), method_names, rotation=90)
        plt.title("variance / mean² (%)")
        plt.show(fig)

        # Show raw data
        rows = ["mean", "std"] + ["s1(%s)" % param for param in param_names]
        df = pd.DataFrame(data, index=rows, columns=[method_name(method) for method in methods])
        display(df)

    def incer_stochastic_dasboard(model, methods, n=1000) :
        ''' Generates a dashboard with several statistics : matrix of parameter incertitude, violin diagrams, ...'''

        problem, X, Y = _stochastics(model, methods, n)
        param_names = problem['names']

        print("Processing Sobol indices ...")
        s1, st = _sobols(methods, problem, Y)

        def violin() :
            _incer_stochastic_violin(methods, Y)

        def variation():
            _incer_stochastic_variations(methods, Y, param_names, s1)

        def matrix() :
            _incer_stochastic_matrix(methods, problem['names'], Y, st)

        _display_tabs([
            ("Violin graphs", violin),
            ("Impact variations", variation),
            ("Sobol matrix", matrix)
        ])

## Functions

`def actToExpression(act)`

  Computes a symbolic expression of the model, referencing background activities and model parameters as symbols

  ## Returns

      (sympy_expr, dict of symbol => activity)

  Expand source code

      def actToExpression(act: Activity):
          """Computes a symbolic expression of the model, referencing background activities and model parameters as symbols

          Returns
          -------
              (sympy_expr, dict of symbol => activity)
          """

          act_symbols = dict()  # Dict of  act = > symbol

          def act_to_symbol(db_name, code):

              act = _getDb(db_name).get(code)
              name = act['name']
              base_slug = slugify(name, separator='_')

              slug = base_slug
              i = 1
              while symbols(slug) in act_symbols.values():
                  slug = f"{base_slug}{i}"
                  i += 1

              return symbols(slug)

          def rec_func(act: Activity):

              res = 0
              outputAmount = 1

              for exch in act.exchanges():

                  formula = _getAmountOrFormula(exch)

                  if isinstance(formula, types.FunctionType):
                      # Some amounts in EIDB are functions ... we ignore them
                      continue

                  input_db, input_code = exch['input']

                  #  Different output ?
                  if exch['input'] == exch['output']:
                      if exch['amount'] != 1:
                          outputAmount = exch['amount']
                      continue

                  # Background DB => reference it as a symbol
                  if input_db in [BIOSPHERE3_DB_NAME, ECOINVENT_DB_NAME]:
                      if not (input_db, input_code) in act_symbols:
                          act_symbols[(input_db, input_code)] = act_to_symbol(input_db, input_code)
                      act_expr = act_symbols[(input_db, input_code)]

                  # Our model : recursively transform it to a symbolic expression
                  else:

                      if input_db == act['database'] and input_code == act['code']:
                          raise Exception("Recursive exchange : %s" % (act.__dict__))

                      sub_act = _getDb(input_db).get(input_code)
                      act_expr = rec_func(sub_act)

                  res += formula * act_expr

              return  res / outputAmount

          expr = rec_func(act)

          return (expr, _reverse_dict(act_symbols))

`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.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 debug(*args, **kwargs)`
  Expand source code

      def debug(*args, **kwargs) :
          if DEBUG :
              print(*args, **kwargs)

`def error(*args, **kwargs)`
  Expand source code

      def error(*args, **kwargs):
          print(*args, **kwargs, file=stderr)

`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):
          """
              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:
              name_key = name if name else in_name

              # Find candidates via index
              candidates = _find_candidates(db_name, name_key)

              # 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 findTechAct(name=None, loc=None, **kwargs)`

  Alias for findActivity(name, … db_name=ECOINVENT_DB_NAME)

  Expand source code

      def findTechAct(name=None, loc=None, **kwargs):
          """Alias for findActivity(name, ... db_name=ECOINVENT_DB_NAME)
          """
          return findActivity(name=name, loc=loc, db_name=ECOINVENT_DB_NAME, **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 importDb(dbname, path)`

  Import eco invent DB

  Expand source code

      def importDb(dbname, path):
          '''Import eco invent DB'''

          if dbname in bw.databases:
              _eprint("Database '%s' has already been imported " % dbname)
          else:
              ei34 = bw.SingleOutputEcospold2Importer(path, dbname)
              ei34.apply_strategies()
              ei34.statistics()
              ei34.write_database()

`def incer_stochastic_dasboard(model, methods, n=1000)`

  Generates a dashboard with several statistics : matrix of parameter incertitude, violin diagrams, …

  Expand source code

      def incer_stochastic_dasboard(model, methods, n=1000) :
          ''' Generates a dashboard with several statistics : matrix of parameter incertitude, violin diagrams, ...'''

          problem, X, Y = _stochastics(model, methods, n)
          param_names = problem['names']

          print("Processing Sobol indices ...")
          s1, st = _sobols(methods, problem, Y)

          def violin() :
              _incer_stochastic_violin(methods, Y)

          def variation():
              _incer_stochastic_variations(methods, Y, param_names, s1)

          def matrix() :
              _incer_stochastic_matrix(methods, problem['names'], Y, st)

          _display_tabs([
              ("Violin graphs", violin),
              ("Impact variations", variation),
              ("Sobol matrix", matrix)
          ])

`def incer_stochastic_matrix(modelOrLambdas, methods, n=1000)`

  Method computing matrix of parameter importance

  Expand source code

      def incer_stochastic_matrix(modelOrLambdas, methods, n=1000):
          ''' Method computing matrix of parameter importance '''

          problem, X, Y = _stochastics(modelOrLambdas, methods, n)

          print("Processing Sobol indices ...")
          s1, st = _sobols(methods, problem, Y)

          _incer_stochastic_matrix(methods, problem['names'], Y, st)

`def incer_stochastic_violin(modelOrLambdas, methods, n=1000)`

  Method for computing violin graph of impacts

  Expand source code

      def incer_stochastic_violin(modelOrLambdas, methods, n=1000):

          ''' Method for computing violin graph of impacts '''

          problem, X, Y = _stochastics(modelOrLambdas, methods, n)

          _incer_stochastic_violin(methods, Y)

`def interpolate(x, x1, x2, y1, y2)`

  Build an expression for linear interpolation between two points

  Expand source code

      def interpolate(x, x1, x2, y1, y2):
          """Build an expression for linear interpolation between two points"""
          return y1 + (y2 - y1) * (x - x1) / (x2 - x1)

`def method_name(method)`
  Expand source code

      def method_name(method) :
          return method[1] + " - " + method[2]

`def multiLCA(model, methods, **params)`

  Compute LCA for a single activity and a set of methods, after settings the parameters and updating exchange amounts.

  ## Parameters

  `model` :`Single` `activity` (`root` `model`) or `list` of `activities`
  `methods` :`Impact` `methods` `to` `consider`
  `params` :`Other` `parameters` of `the` `model`
  Expand source code

      def multiLCA(model, methods, **params):
          """Compute LCA for a single activity and a set of methods, after settings the parameters and updating exchange amounts.

          Parameters
          ----------
          model : Single activity (root model) or list of activities
          methods : Impact methods to consider
          params : Other parameters of the model
          """

          # Check and expand params
          params = _completeParamValues(params)

          # Update brightway parameters
          bwParams = [dict(name=key, amount=value) for key, value in params.items()]
          bw.parameters.new_project_parameters(bwParams)

          # ActivityParameter.recalculate_exchanges(DEFAULT_PARAM_GROUP)
          bw.parameters.recalculate()

          if isinstance(model, list):
              activities = [{act: 1} for act in model]
          else:
              activities = [{model: 1}]
          return _multiLCA(activities, methods).transpose()

`def multiLCAAlgebric(models, methods, **params)`

  Compute LCA by expressing the foreground model as symbolic expression of background activities and parameters. Then, compute 'static' inventory of the referenced background activities. This enables a very fast recomputation of LCA with different parameters, useful for stochastic evaluation of parametrized model

  ## Parameters

  `models` :`Single` `model` or `list` of `models` or `dict` of `model`:`amount` : `if` `list` of `models`, `you` `cannot` `use` `param` `lists`
  `methods` :`List` of `methods` / `impacts` `to` `consider`
  `params` :`You` `should` `provide` `named` `values` of `all` `the` `parameters` `declared` `in` `the` `model.` `Values` `can` `be` `single` `value` or `list` of `samples`, `all` of `the` `same` `size`
  Expand source code

      def multiLCAAlgebric(models, methods, **params):
          """Compute LCA by expressing the foreground model as symbolic expression of background activities and parameters.
          Then, compute 'static' inventory of the referenced background activities.
          This enables a very fast recomputation of LCA with different parameters, useful for stochastic evaluation of parametrized model

          Parameters
          ----------
          models : Single model or list of models or dict of model:amount : if list of models, you cannot use param lists
          methods : List of methods / impacts to consider
          params : You should provide named values of all the parameters declared in the model. \
                   Values can be single value or list of samples, all of the same size
          """
          dfs = dict()

          if not isinstance(models, list):
              models = [models]

          for model in models:

              alpha = 1
              if type(model) is tuple :
                  model, alpha=model

              lambdas = preMultiLCAAlgebric(model, methods, params.keys())

              df = postMultiLCAAlgebric(methods, lambdas, alpha=alpha, **params)

              model_name = _actName(model)

              # Single params ? => give the single row the name of the model activity
              if df.shape[0] == 1:
                  df = df.rename(index={0: model_name})

              dfs[model_name] = df

          if len(dfs) == 1:
              df = list(dfs.values())[0]
              return df
          else:
              # Concat several dataframes for several models
              return pd.concat(list(dfs.values()))

`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 newBoolParam(name, default, **kwargs)`
  Expand source code

      def newBoolParam(name, default, **kwargs):
          return newParamDef(name, ParamType.BOOL, default=default, **kwargs)

`def newEnumParam(name, default, **kwargs)`
  Expand source code

      def newEnumParam(name, default, **kwargs):
          return newParamDef(name, ParamType.ENUM, default=default, **kwargs)

`def newFloatParam(name, default, **kwargs)`
  Expand source code

      def newFloatParam(name, default, **kwargs):
          return newParamDef(name, ParamType.FLOAT, default=default, **kwargs)

`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 newParamDef(name, type, **kwargs)`

  Creates a param and register it into a global registry and as a brightway parameter

  Expand source code

      def newParamDef(name, type, **kwargs):
          """Creates a param and register it into a global registry and as a brightway parameter"""
          if type == ParamType.ENUM:
              param = EnumParam(name, **kwargs)
          elif type == ParamType.BOOL :
              param = BooleanDef(name, **kwargs)
          else:
              param = ParamDef(name, type=type, **kwargs)

          # Put it in local registry (in memory)
          if name in _param_registry():
              _eprint("Param %s was already defined : overriding" % name)
          _param_registry()[name] = param

          # Save in brightway2 project
          bwParams = [dict(name=key, amount=value) for key, value in param.expandParams().items()]
          bw.parameters.new_project_parameters(bwParams)

          return param

`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

  ## Parameters

  `paramDef` :`parameter` `definition` of `type` `enum`
  `acts_dict` :`dict` of <`enumValue`> => `activity`
  Expand source code

      def newSwitchAct(dbname, name, paramDef: ParamDef, acts_dict: Dict[str, Activity]):
          """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

          Parameters
          ----------
          paramDef : parameter definition of type enum
          acts_dict : dict of <enumValue> => activity
          """

          # Transform map of enum values to correspoding formulas <param_name>_<enum_value>
          exch = {act: paramDef.symbol(key) for key, act in acts_dict.items()}
          res = newActivity(
              dbname,
              name,
              unit=list(acts_dict.values())[0]['unit'],
              exchanges=exch)

          # Unit of switch activity is the one of the children
          for key, act in acts_dict.items():
              if 'unit' in act:
                  res['unit'] = act['unit']
                  res.save()
          return res

`def oat_dasboard(modelOrLambdas, impacts, param, n=10)`

  Analyse the evolution of impacts for a single parameter. The other parameters are set to their default values.

  ## Parameters

  `model` :`activity`, or `lambdas` `as` `precomputed` `by` `preMultiLCAAlgebric()`, `for` `faster` `computation`
  `impacts` :`set` of `methods`
  `param` :`parameter` `to` `analyse`
  `n` :`number` of `samples` of `the` `parameter`
  Expand source code

      def oat_dasboard(modelOrLambdas, impacts, param: ParamDef, n=10) :
          '''
          Analyse the evolution of impacts for a single parameter. The other parameters are set to their default values.

          Parameters
          ----------
          model : activity, or lambdas as precomputed by preMultiLCAAlgebric, for faster computation
          impacts : set of methods
          param: parameter to analyse
          n: number of samples of the parameter
          '''

          params = {param.name : param.default for param in _param_registry().values()}

          # Compute range of values for given param
          params[param.name] = param.range(n)

          #print("Params: ", params)

          if isinstance(modelOrLambdas, Activity) :
              df = multiLCAAlgebric(modelOrLambdas, impacts, **params)
          else :
              df = postMultiLCAAlgebric(impacts, modelOrLambdas, **params)

          # Add X values in the table
          pname = param.name
          if param.unit :
              pname = '%s [%s]' % (pname, param.unit)
          df.insert(0, pname, param.range(n))
          df = df.set_index(pname)

          def table() :
              display(df)

          def graph() :

              with warnings.catch_warnings():

                  warnings.simplefilter("ignore")

                  nb_rows = len(impacts) // 3 + 1

                  fig, axes = plt.subplots(figsize=(15, 15))

                  axes = df.plot(
                      ax=axes, sharex=True, subplots=True,
                      layout=(nb_rows, 3),
                      #legend=None,
                      kind = 'line' if param.type == ParamType.FLOAT else 'bar')

                  axes = axes.flatten()

                  for ax, impact in zip(axes, impacts) :
                      ax.set_ylim(ymin=0)
                      ax.set_ylabel(_method_unit(impact))

                  plt.show(fig)

          def change() :

              ch = (df.max() - df.min()) / df.median() * 100
              fig, ax = plt.subplots(figsize=(9, 6))
              plt.title('Relative change for %s' % df.index.name)
              ch.plot(kind='barh', rot=30)
              ax.set_xlabel('Relative change of the median value (%)')
              plt.tight_layout()
              plt.show(fig)

          _display_tabs([
              ("Graphs", graph),
              ("Data", table),
              ("Variation", change)
          ])

`def oat_dashboard_interact(model, methods)`

  Interative dashboard, with a dropdown for selecting parameter

  Expand source code

      def oat_dashboard_interact(model, methods):
          '''Interative dashboard, with a dropdown for selecting parameter'''

          lambdas = preMultiLCAAlgebric(model, methods, _param_registry().keys())

          def process_func(param) :
              oat_dasboard(lambdas, methods, _param_registry()[param])

          paramlist = list(_variable_params().keys())
          interact(process_func, param=paramlist)

`def oat_matrix(model, impacts, n=10)`

  Generates a heatmap of the incertitude of the model, varying input parameters one a a time.

  Expand source code

      def oat_matrix(model, impacts, n=10) :
          '''Generates a heatmap of the incertitude of the model, varying input parameters one a a time.'''

          # Compile model into lambda functions for fast LCA
          lambdas = preMultiLCAAlgebric(model, impacts, _param_registry().keys())

          change = np.zeros((len(_variable_params()), len(impacts)))

          for iparam, param in enumerate(_variable_params().values()) :
              params = {param.name: param.default for param in _param_registry().values()}

              # Compute range of values for given param
              params[param.name] = param.range(n)

              # Compute LCA
              df = postMultiLCAAlgebric(impacts, lambdas, **params)

              # Compute change
              change[iparam] =  (df.max() - df.min()) / df.median() * 100

          # Build final heatmap
          change = pd.DataFrame(change, index=_variable_params().keys(), columns=[imp[2] for imp in impacts])
          _heatmap(change.transpose(), 'Change of impacts per variability of the input parameters (%)', 100, ints=True)

`def postMultiLCAAlgebric(methods, lambdas, alpha=1, **params)`

  Compute LCA for a given set of parameters and pre-compiled lambda functions. This function is used by multiLCAAlgebric

  ## Parameters

  `methodAndLambdas` :`Output` of `preMultiLCAAlgebric()`
  `**params` :`Parameters` of `the` `model`
  Expand source code

      def postMultiLCAAlgebric(methods, lambdas, alpha=1, **params):
          '''
              Compute LCA for a given set of parameters and pre-compiled lambda functions.
              This function is used by **multiLCAAlgebric**

              Parameters
              ----------
              methodAndLambdas : Output of preMultiLCAAlgebric
              **params : Parameters of the model
          '''

          # Check and expand params
          params = _completeParamValues(params)

          # Expand parameters as list of parameters
          param_length = 1

          for key, val in params.items():
              if isinstance(val, list):
                  if param_length == 1:
                      param_length = len(val)
                  elif param_length != len(val):
                      raise Exception("Parameters should be a single value or a list of same number of values")

          # Expand params and transform lists to np.array for vector computation
          for key in params.keys():
              val = params[key]
              if not isinstance(val, list):
                  val = list([val] * param_length)
              params[key] = np.array(val)

          res = np.zeros((len(methods), param_length))

          # Compute result on whole vectors of parameter samples at a time : lambdas use numpy for vector computation
          for imethod, lambd in enumerate(lambdas):
              res[imethod, :] = alpha * lambd(**params)

          return pd.DataFrame(res, index=[method_name(method) for method in methods]).transpose()

`def preMultiLCAAlgebric(model, methods, param_names=None, amount=1)`

  This method transforms an activity into a set of functions ready to compute LCA very fast on a set on methods. You may use is and pass the result to postMultiLCAAlgebric for fast computation on a model that does not change.

  This method is used by multiLCAAlgebric

  Expand source code

      def preMultiLCAAlgebric(model:ActivityExtended, methods, param_names=None, amount=1) :
          '''
              This method transforms an activity into a set of functions ready to compute LCA very fast on a set on methods.
              You may use is and pass the result to postMultiLCAAlgebric for fast computation on a model that does not change.

              This method is used by multiLCAAlgebric
          '''

          # print("computing model to expression for %s" % model)
          expr, actBySymbolName = actToExpression(model)

          dbname = model.key[0]

          param_names = _expand_param_names(param_names)

          # Check missing params
          free_names = set([str(symb) for symb in expr.free_symbols])
          act_names = set([str(symb) for symb in actBySymbolName.keys()])
          expected_names  = free_names - act_names
          missing_names = expected_names - set(param_names)

          if len(missing_names) > 0 :
              raise Exception('Missing parameter of the model : %s' % str(missing_names))

          debug(param_names)

          for name in param_names :
              registry_names = _expand_param_names(_param_registry())
              if name not in registry_names :
                  raise Exception('Model refers to unknown param "%s"' % name)

          # Create dummy reference to biosphere
          # We cannot run LCA to biosphere activities
          # We create a technosphere activity mapping exactly to 1 biosphere item
          pureTechActBySymbol = OrderedDict()
          for name, act in actBySymbolName.items():
              if act[0] == BIOSPHERE3_DB_NAME:
                  act = _getOrCreateDummyBiosphereActCopy(dbname, act[1])
              else:
                  act = getActByCode(*act)
              pureTechActBySymbol[name] = act

          # List of activities, ordered
          acts = pureTechActBySymbol.values()

          # Transform to [{act1:1], {act2:1}, etc] for MultiLCA
          actsWithAmount = [{act: 1} for act in acts]

          # Compute LCA for all background activities and methods
          lca = _multiLCA(actsWithAmount, methods)

          # For each method, compute an algebric expression with activities replaced by their values
          lambdas = []
          for imethod, method in enumerate(methods):
              # print("Generating lamba function for %s / %s" % (model, method))

              # Replace activities by their value in expression for this method
              sub = dict({symbol: lca.iloc[imethod, iact] for iact, symbol in enumerate(pureTechActBySymbol.keys())})
              method_expr = expr.xreplace(sub)

              # Tranform Sympy expression to lambda function, based on numpy to fast vectorial evaluation
              lambd = lambdify(param_names, method_expr, 'numpy')
              lambdas.append(lambd)

          return lambdas

`def printAct(*activities, **params)`

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

  Expand source code

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

          for act in activities:
              df = pd.DataFrame(index=['input', 'amount', 'unit', 'type'])
              data = dict()
              for (i, exc) in enumerate(act.exchanges()):
                  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)

                  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}"

                  iname = name
                  i=1
                  while iname in data :
                      iname = "%s#%d" % (name, i)
                      i += 1

                  data[iname] = [str(input), amount, exc.unit, exc['type']]

              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)

          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 resetDb(db_name)`

  Create or cleanup a user DB

  Expand source code

      def resetDb(db_name):
          """ Create or cleanup a user DB"""
          if db_name in bw.databases:
              _eprint("Db %s was here. Reseting it" % db_name)
              del bw.databases[db_name]
          db = bw.Database(db_name)
          db.write(dict())

`def resetParams(db_name)`

  Reset project and activity parameters

  Expand source code

      def resetParams(db_name):
          """Reset project and activity parameters"""
          _param_registry().clear()
          ProjectParameter.delete().execute()
          ActivityParameter.delete().execute()
          DatabaseParameter.delete().execute()
          Group.delete().execute()

## 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
          """

          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})

          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.
              """

              # 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:
                          bw.parameters.add_exchanges_to_group(DEFAULT_PARAM_GROUP, self)

          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 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 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)
              """
              parametrized = False
              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 'biosphere' if sub_act['database'] == BIOSPHERE3_DB_NAME else 'technosphere')

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

                  exch.save()
              self.save()
              if parametrized:
                  bw.parameters.add_exchanges_to_group(DEFAULT_PARAM_GROUP, self)

          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 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

        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)
            """
            parametrized = False
            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 'biosphere' if sub_act['database'] == BIOSPHERE3_DB_NAME else 'technosphere')

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

                exch.save()
            self.save()
            if parametrized:
                bw.parameters.add_exchanges_to_group(DEFAULT_PARAM_GROUP, self)

  `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

        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

        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)

    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

        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

        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.
            """

            # 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:
                        bw.parameters.add_exchanges_to_group(DEFAULT_PARAM_GROUP, self)

`class BooleanDef (name, *karg, **kargs)`

  Parameter with discrete value 0 or 1

  Expand source code

      class BooleanDef(ParamDef):
          """Parameter with discrete value 0 or 1"""

          def __init__(self, name, **argv):
              super(BooleanDef, self).__init__(name, ParamType.BOOL, min=0, max=1, **argv)

          def range(self, n):
              return [0, 1]

          def rand(self, alpha):
              return round(alpha)

  ### Ancestors

  - ParamDef
  - sympy.core.symbol.Symbol
  - sympy.core.expr.AtomicExpr
  - sympy.core.basic.Atom
  - sympy.core.expr.Expr
  - sympy.logic.boolalg.Boolean
  - sympy.core.basic.Basic
  - sympy.core.evalf.EvalfMixin

  ### Inherited members

  - `ParamDef`:
    - `default_assumptions`
    - `rand`
    - `range`
`class DistributionType`

  Type of distribution

  Expand source code

      class DistributionType :
          '''Type of distribution'''
          LINEAR = "linear"
          NORMAL = "normal"
          TRIANGLE = "triangle"
          FIXED = "fixed"

  ### Class variables

  `var FIXED`

    str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

    Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

  `var LINEAR`

    str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

    Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

  `var NORMAL`

    str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

    Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

  `var TRIANGLE`

    str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

    Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

`class EnumParam (name, *karg, **kargs)`

  Enum param is a facility representing a choice / switch as many boolean parameters. It is not itself a Sympy symbol. use #symbol("value") to access it

  Expand source code

      class EnumParam(ParamDef):
          """Enum param is a facility representing a choice / switch as many boolean parameters.
          It is not itself a Sympy symbol. use #symbol("value") to access it"""

          def __init__(self, name, values: List[str], **argv):
              super(EnumParam, self).__init__(name, ParamType.ENUM, min=None, max=None, **argv)
              self.values = values

          def expandParams(self, currValue=None):
              values = self.values + [None]
              res = dict()
              for enum_val in values:
                  var_name = "%s_%s" % (self.name, enum_val if enum_val is not None else "default")
                  res[var_name] = 1.0 if enum_val == currValue else 0.0
              return res

          def symbol(self, enumValue):
              """Access parameter for each enum value : <paramName>_<paramValue>"""
              if enumValue is None:
                  return Symbol(self.name + '_default')
              if not enumValue in self.values:
                  raise Exception("enumValue should be one of %s. Was %s" % (str(self.values), enumValue))
              return Symbol(self.name + '_' + enumValue)

          def names(self) :
              return ["%s_%s" % (self.name, value) for value in (self.values + ["default"]) ]

          def rand(self, alpha):
              i = math.ceil(alpha * (len(self.values))) -1
              return self.values[int(i)]

          def range(self, n):
              return self.values

  ### Ancestors

  - ParamDef
  - sympy.core.symbol.Symbol
  - sympy.core.expr.AtomicExpr
  - sympy.core.basic.Atom
  - sympy.core.expr.Expr
  - sympy.logic.boolalg.Boolean
  - sympy.core.basic.Basic
  - sympy.core.evalf.EvalfMixin

  ### Methods

  `def expandParams(self, currValue=None)`
    Expand source code

        def expandParams(self, currValue=None):
            values = self.values + [None]
            res = dict()
            for enum_val in values:
                var_name = "%s_%s" % (self.name, enum_val if enum_val is not None else "default")
                res[var_name] = 1.0 if enum_val == currValue else 0.0
            return res

  `def names(self)`
    Expand source code

        def names(self) :
            return ["%s_%s" % (self.name, value) for value in (self.values + ["default"]) ]

  `def symbol(self, enumValue)`

    Access parameter for each enum value : _

    Expand source code

        def symbol(self, enumValue):
            """Access parameter for each enum value : <paramName>_<paramValue>"""
            if enumValue is None:
                return Symbol(self.name + '_default')
            if not enumValue in self.values:
                raise Exception("enumValue should be one of %s. Was %s" % (str(self.values), enumValue))
            return Symbol(self.name + '_' + enumValue)

  ### Inherited members

  - `ParamDef`:
    - `default_assumptions`
    - `rand`
    - `range`
`class ParamDef (name, *karg, **kargs)`

  Generic definition of a parameter, with name, bound, type, distribution This definition will serve both to generate brightway2 parameters and to evaluate.

  This class inherits sympy Symbol, making it possible to use in standard arithmetic python while keeping it as a symbolic expression (delayed evaluation).

  Expand source code

      class ParamDef(Symbol):
          '''Generic definition of a parameter, with name, bound, type, distribution
          This definition will serve both to generate brightway2 parameters and to evaluate.

          This class inherits sympy Symbol, making it possible to use in standard arithmetic python
          while keeping it as a symbolic expression (delayed evaluation).
          '''

          def __new__(cls, name, *karg, **kargs):
              return Symbol.__new__(cls, name)

          def __init__(self, name, type: str, default, min=None, max=None, unit="", description="", label=None, label_fr=None, group=None, distrib=DistributionType.LINEAR, std=None):
              self.name = name
              self.type = type
              self.default = default
              self.description = description
              self.min = min
              self.max = max
              self.unit = unit
              self.label = label
              self.label_fr = label_fr
              self.group=group
              self.distrib = distrib

              if type == ParamType.FLOAT and self.min is None :
                  self.distrib = DistributionType.FIXED

              if distrib == DistributionType.NORMAL and std is None :
                  raise Exception("Standard deviation is mandatory for normal distribution")
              self.std = std

          def label(self):
              if self.label is not None :
                  return self.label
              else :
                  return self.name.replace("_", " ")

          def range(self, n) :
              '''Used for parametric analysis'''
              step = (self.max - self.min) / (n - 1)
              return list(i * step + self.min for i in range(0, n))

          def rand(self, alpha):
              """Transforms a random number between 0 and 1 to valid value according to the distribution of probability of the parameter"""
              if self.distrib == DistributionType.LINEAR :
                  return self.min + alpha * (self.max - self.min)

              elif self.distrib == DistributionType.TRIANGLE :
                  if not hasattr(self, "_distrib") :
                      scale = self.max - self.min
                      c = (self.default - self.min) / scale
                      self._distrib = triang(c, loc=self.min, scale=scale)

                  return self._distrib.ppf(alpha)

              elif self.distrib == DistributionType.NORMAL :
                  if not hasattr(self, "_distrib") :
                      self._distrib = truncnorm(
                          (self.min - self.default) / self.std,
                          (self.max - self.min) / self.std,
                          loc=self.default,
                          scale=self.std)

                  return self._distrib.ppf(alpha)

              else :
                  raise Exception("Unknowk distribution type " + self.distrib)

          # Expand parameter (useful for enum param)
          def expandParams(self, value=None) -> Dict[str, float]:
              if value == None:
                  value = self.default
              return {self.name: value}

          # Useful for enum param, having several names
          def names(self) :
              return [self.name]

          def __repr__(self):
              return self.name

  ### Ancestors

  - sympy.core.symbol.Symbol
  - sympy.core.expr.AtomicExpr
  - sympy.core.basic.Atom
  - sympy.core.expr.Expr
  - sympy.logic.boolalg.Boolean
  - sympy.core.basic.Basic
  - sympy.core.evalf.EvalfMixin

  ### Subclasses

  - BooleanDef
  - EnumParam

  ### Class variables

  `var default_assumptions`

    A FactKB specialised for the built-in rules

    This is the only kind of FactKB that Basic objects should use.

  ### Methods

  `def expandParams(self, value=None)`
    Expand source code

        def expandParams(self, value=None) -> Dict[str, float]:
            if value == None:
                value = self.default
            return {self.name: value}

  `def label(self)`
    Expand source code

        def label(self):
            if self.label is not None :
                return self.label
            else :
                return self.name.replace("_", " ")

  `def names(self)`
    Expand source code

        def names(self) :
            return [self.name]

  `def rand(self, alpha)`

    Transforms a random number between 0 and 1 to valid value according to the distribution of probability of the parameter

    Expand source code

        def rand(self, alpha):
            """Transforms a random number between 0 and 1 to valid value according to the distribution of probability of the parameter"""
            if self.distrib == DistributionType.LINEAR :
                return self.min + alpha * (self.max - self.min)

            elif self.distrib == DistributionType.TRIANGLE :
                if not hasattr(self, "_distrib") :
                    scale = self.max - self.min
                    c = (self.default - self.min) / scale
                    self._distrib = triang(c, loc=self.min, scale=scale)

                return self._distrib.ppf(alpha)

            elif self.distrib == DistributionType.NORMAL :
                if not hasattr(self, "_distrib") :
                    self._distrib = truncnorm(
                        (self.min - self.default) / self.std,
                        (self.max - self.min) / self.std,
                        loc=self.default,
                        scale=self.std)

                return self._distrib.ppf(alpha)

            else :
                raise Exception("Unknowk distribution type " + self.distrib)

  `def range(self, n)`

    Used for parametric analysis

    Expand source code

        def range(self, n) :
            '''Used for parametric analysis'''
            step = (self.max - self.min) / (n - 1)
            return list(i * step + self.min for i in range(0, n))

`class ParamType`

  Type of parameters

  Expand source code

      class ParamType:
          '''Type of parameters'''
          ENUM = "enum"
          BOOL = "bool"
          FLOAT = "float"

  ### Class variables

  `var BOOL`

    str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

    Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

  `var ENUM`

    str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

    Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

  `var FLOAT`

    str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

    Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.