Source code for bw2data.proxies

from collections.abc import MutableMapping
from numbers import Number

from stats_arrays import uncertainty_choices

from bw2data import databases
from bw2data.configuration import labels
from bw2data.errors import InvalidExchange
from bw2data.utils import get_activity


[docs] class ProxyBase(MutableMapping): def __init__(self, data, *args, **kwargs):
[docs] self._data = data
[docs] def as_dict(self): return self._data
def __str__(self) -> str: return "Instance of base proxy class" __repr__ = lambda x: str(x) def __contains__(self, key): return key in self._data def __iter__(self): return iter(self._data) def __len__(self): return len(self._data) def __getitem__(self, key): return self._data[key] def __setitem__(self, key, value): self._data[key] = value def __delitem__(self, key): del self._data[key] def __eq__(self, other): return self._dict == other def __hash__(self): return hash(self._dict)
[docs] class ActivityProxyBase(ProxyBase): def __str__(self): if self.valid(): return "'{}' ({}, {}, {})".format( self.get("name"), self.get("unit"), self.get("location"), self.get("categories"), ) else: return "Activity with missing fields (call ``valid(why=True)`` to see more)" @property
[docs] def key(self): return (self.get("database"), self.get("code"))
def __eq__(self, other): return self.key == other def __lt__(self, other): if not isinstance(other, ActivityProxyBase): raise TypeError else: return self.key < other.key def __hash__(self): return hash(self.key) def __getitem__(self, key): # Basically a hack to let this act like a tuple with two # elements, database and code. Useful for using as functional unit in LCA. if key == 0: return self["database"] elif key == 1: return self["code"] return self._data[key] def __delitem__(self, key): del self._data[key]
[docs] def valid(self, why=False): errors = [] if not self.get("database"): errors.append("Missing field ``database``") elif self.get("database") not in databases: errors.append("``database`` refers to unknown database") if not self.get("code"): errors.append("Missing field ``code``") if not self.get("name"): errors.append("Missing field ``name``") if errors: if why: return (False, errors) else: return False else: return True
[docs] def lca(self, method=None, amount=1.0): """Shortcut to construct an LCA object for this activity.""" from bw2calc import LCA lca = LCA({self: amount}, method=method) lca.lci() if method is not None: lca.lcia() return lca
[docs] class ExchangeProxyBase(ProxyBase): def __str__(self) -> str: if not self.valid(): return "Exchange with missing fields (call ``valid(why=True)`` to see more)" elif ( self.input.get("type") in labels.product_node_types and self.output.get("type") in labels.process_node_types and self.get("type") in labels.technosphere_positive_edge_types ): return "Exchange: {} {} {} from {}".format( self.amount, self.unit, self.input, self.output ) else: return "Exchange: {} {} {} to {}".format( self.amount, self.unit, self.input, self.output ) def __lt__(self, other): if not isinstance(other, ExchangeProxyBase): raise TypeError else: return (self.input.key, self.output.key) < ( other.input.key, other.output.key, ) def __eq__(self, other): return self._data == other def __hash__(self): return hash(self._data.__str__())
[docs] def _get_input(self): """Get or set the exchange input. When getting, returns an `Activity` - this will raise an error if the linked activity doesn't yet exist. When setting, either an `Activity` or a tuple can be given. The linked activity does not have to exist yet. """ if not self.get("input"): raise InvalidExchange("Missing valid data for `input` field") elif not hasattr(self, "_input"): self._input = get_activity(self["input"]) return self._input
[docs] def _set_input(self, value): if isinstance(value, ActivityProxyBase): self._input = value self._data["input"] = value.key elif isinstance(value, (tuple, list)): self._data["input"] = value else: raise ValueError("Provided input data is invalid")
[docs] def _get_output(self): """Get or set the exchange output. When getting, returns an `Activity` - this will raise an error if the linked activity doesn't yet exist. When setting, either an `Activity` or a tuple can be given. The linked activity does not have to exist yet. """ if not self.get("output"): raise InvalidExchange("Missing valid data for `output` field") elif not hasattr(self, "_output"): self._output = get_activity(self["output"]) return self._output
[docs] def _set_output(self, value): if isinstance(value, ActivityProxyBase): self._output = value self._data["output"] = value.key elif isinstance(value, (tuple, list)): self._data["output"] = value else: raise ValueError("Provided input data is invalid")
[docs] input = property(_get_input, _set_input)
[docs] output = property(_get_output, _set_output)
def __setitem__(self, key, value): if key == "input": self.input = value elif key == "output": self.output = value else: self._data[key] = value
[docs] def valid(self, why=False): errors = [] if not self.get("input"): errors.append("Missing field ``input``") elif not isinstance(self["input"], tuple): errors.append("Field ``input`` must be a tuple") elif self["input"][0] not in databases: errors.append("Input database ``{}`` doesn't exist".format(self["input"][0])) if not self.get("output"): errors.append("Missing field ``output``") elif not isinstance(self["output"], tuple): errors.append("Field ``output`` must be a tuple") elif self["output"][0] not in databases: errors.append("Output database ``{}`` doesn't exist".format(self["output"][0])) if not isinstance(self.get("amount", None), Number): errors.append("Invalid or missing field ``amount``") if not self.get("type"): errors.append("Missing field ``type``") if errors: if why: return (False, errors) else: return False else: return True
@property
[docs] def unit(self): """Get exchange unit. Separate property because the unit is a property of the input, not the exchange itself. """ return self.input.get("unit")
@property
[docs] def amount(self): return self.get("amount")
@property
[docs] def uncertainty(self): """Get uncertainty dictionary that can be used in uncertainty analysis.""" KEYS = { "uncertainty type", "loc", "scale", "shape", "minimum", "maximum", "negative", } return {k: v for k, v in self.items() if k in KEYS}
@property
[docs] def uncertainty_type(self): """Get uncertainty type as a ``stats_arrays`` class.""" return uncertainty_choices[self.get("uncertainty type", 0)]
[docs] def random_sample(self, n=100): """Draw a random sample from this exchange.""" ut = self.uncertainty_type array = ut.from_dicts(self.uncertainty) return ut.bounded_random_variables(array, n).ravel()
[docs] def lca(self, method=None, amount=None): """Shortcut to construct an LCA object for this exchange **input**. Uses the exchange amount if no other amount is provided.""" from bw2calc import LCA if amount is None: amount = self["amount"] lca = LCA({self.input: amount}, method=method) lca.lci() if method is not None: lca.lcia() return lca