Beispiel #1
0
    def rebase_fragment(self, fragment, flow, direction):
        """
        creates a new fragment by inverting an existing one (via traversal), specifying an alternate flow as the
        reference.  basically wraps the fragment and sets the exchange value, then deletes one child flow.
        doesn't currently work if the fragment contains internal reference flow feedback
        :param fragment:
        :param exchange: should be output from F.get_fragment_inventory
        :return:
        """
        comment = 'rebased %.5s' % fragment.get_uuid()

        exchange = next(ex for ex in self.get_fragment_inventory(fragment)
                        if ex.flow is flow and ex.direction == direction)
        frag = self.new_fragment(exchange.flow, comp_dir(exchange.direction),
                                 Comment=comment, exchange_value=exchange.value)
        frag.terminate(frag)
        self.create_fragment(parent=frag, flow=fragment.flow, direction=comp_dir(exchange.direction),
                             comment=comment, value=fragment.cached_ev)
        n = self.create_fragment(parent=frag, flow=fragment.flow, direction=fragment.direction,
                                 comment=comment, value=fragment.cached_ev)
        n.terminate(fragment)
        ch = self.build_child_flows(n, background_children=False)

        for c in ch:
            if c.flow is exchange.flow:
                c.term.self_terminate()

        return frag
Beispiel #2
0
 def create_fragment_from_process(self, process_ref, ref_flow=None, background=False, background_children=True):
     """
     The major entry into fragment building.  Given only a process ref, construct a fragment from the process,
     using the process's reference exchange as the reference fragment flow.
     :param process_ref:
     :param ref_flow:
     :param background: [False] add as a background fragment and do not traverse children
     :param background_children: [True] automatically terminate child flows with background references (ecoinvent).
     :return:
     """
     process = process_ref.fg()
     if ref_flow is None:
         if len(process.reference_entity) == 0:
             ref = pick_one([x for x in self.db.filter_exch(process_ref, elem=False) if x.direction == 'Output']
                            ).flow
         elif len(process.reference_entity) > 1:
             ref = pick_reference(process)
         else:
             ref = list(process.reference_entity)[0].flow
     else:
         try:
             ref = next(x.flow for x in process.reference_entity if x.flow.match(ref_flow))
         except StopIteration:
             print('Reference flow not found in target process.')
             return None
     ref_exch = next(x for x in process.exchange(ref))
     direction = comp_dir(ref_exch.direction)
     frag = self[0].create_fragment(ref, direction, Name='%s' % process_ref.entity(), background=background)
     frag.terminate(process_ref, flow=ref)
     if not background:
         self.build_child_flows(frag, background_children=background_children)
     return frag
Beispiel #3
0
    def show_balance(self, frag, quantity=None, scenario=None, observed=False):
        def _p_line(f, m, d):
            try:
                # will fail if m is None or non-number
                print(' %+10.4g  %6s  %.5s %s' % (m, d, f.get_uuid(), f['Name']))
            finally:
                pass
        if quantity is None:
            quantity = frag.flow.reference_entity

        print('%s' % quantity)
        mag = frag.flow.cf(quantity)
        if frag.reference_entity is None:
            mag *= frag.exchange_value(scenario, observed=observed)
        if frag.direction == 'Input':
            mag *= -1

        net = mag

        _p_line(frag, mag, comp_dir(frag.direction))

        for c in sorted(self.child_flows(frag), key=lambda x: x.direction):
            mag = c.exchange_value(scenario, observed=observed) * c.flow.cf(quantity)
            if c.direction == 'Output':
                mag *= -1
            if mag is None or mag != 0:
                _p_line(c, mag, c.direction)
            net += mag

        print('----------\n %+10.4g net' % net)
Beispiel #4
0
    def clone_fragment(self, frag, parent=None, suffix=' (copy)'):
        """
        Creates duplicates of the fragment and its children. returns the reference fragment.
        :param frag:
        :param parent: used internally
        :param suffix: attached to top level fragment
        :return:
        """
        if parent is None:
            parent = frag.reference_entity
        if parent is None:
            direction = comp_dir(frag.direction)  # this gets re-reversed in create_fragment
        else:
            direction = frag.direction
        new = self.create_fragment(parent=parent,
                                   Name=frag['Name'] + suffix, StageName=frag['StageName'],
                                   flow=frag.flow, direction=direction, comment=frag['Comment'],
                                   value=frag.cached_ev, balance=frag._balance_flow)

        self.transfer_evs(frag, new)

        for t_scen in frag.terminations():
            term = frag.termination(t_scen)
            if term.term_node is frag:
                self.terminate_to_foreground(new, scenario=t_scen)
            else:
                new.term_from_term(term, scenario=t_scen)

        for c in self.child_flows(frag):
            self.clone_fragment(c, parent=new, suffix='')
        return new
Beispiel #5
0
    def create_fragment(self, parent=None, flow=None, direction=None, comment=None, value=None, balance=False,
                        **kwargs):
        """

        :param parent:
        :param flow:
        :param direction:
        :param comment:
        :param value:
        :param balance:
        :param kwargs:
        :return:
        """
        if flow is None:
            ch = cyoa('(N)ew flow or (S)earch for flow? ', 'ns')
            if ch == 'n':
                flow = self.new_flow()
            elif ch == 's':
                index = self.current_archive or select_archive(self)
                elem = {'i': False,
                        'e': True,
                        'a': None}[cyoa('(I)ntermediate, (E)lementary, or (A)ll flows? ', 'aei', 'I').lower()]
                flow = self.find_flow(None, index=index, elementary=elem)
                if flow is None:
                    return None
                flow = flow.entity()
            else:
                raise ValueError
        print('Creating fragment with flow %s' % flow)
        direction = direction or {'i': 'Input', 'o': 'Output'}[cyoa('flow is (I)nput or (O)utput?', 'IO').lower()]
        comment = comment or ifinput('Enter FragmentFlow comment: ', '')
        if parent is None:
            # direction reversed for UX! user inputs direction w.r.t. fragment, not w.r.t. parent
            if value is None:
                value = 1.0

            frag = self.new_fragment(flow, comp_dir(direction), Comment=comment, exchange_value=value, **kwargs)
        else:
            if parent.term.is_null:
                self.terminate_to_foreground(parent)
            if value is None:
                val = ifinput('Exchange value (%s per %s): ' % (flow.unit(), parent.unit), '1.0')
                if val == '1.0':
                    value = 1.0
                else:
                    value = parse_math(val)

            frag = self[0].add_child_fragment_flow(parent, flow, direction, Comment=comment, exchange_value=value,
                                                   **kwargs)
            if balance:
                frag.set_balance_flow()
                self.traverse(parent)

        if self.db.is_elementary(frag.flow):
            self.terminate_to_foreground(frag)
        return frag
Beispiel #6
0
    def __init__(self, fragment, process_ref, direction=None, term_flow=None, descend=True, inbound_ev=None):
        self._parent = fragment
        self._process_ref = process_ref  # this is either a catalog_ref (for process) or just a fragment
        self._descend = True
        self.term_flow = None
        self._cached_ev = 1.0
        self._score_cache = LciaResults(fragment)
        if direction is None:
            self.direction = comp_dir(fragment.direction)
        else:
            self.direction = direction

        self.descend = descend
        self.set_term_flow(term_flow)
        self._set_inbound_ev(inbound_ev)
        self.validate_flow_conversion()
Beispiel #7
0
 def serialize(self):
     if self._process_ref is None:
         return {}
     if self.index == 0:
         source = 'foreground'
     else:
         source = self._process_ref.catalog.source_for_index(self._process_ref.index)
     j = {
         'source': source,
         'entityId': self.id
     }
     if self.term_flow != self._parent.flow:
         j['termFlow'] = self.term_flow.get_uuid()
     if self.direction != comp_dir(self._parent.direction):
         j['direction'] = self.direction
     if self._descend is False:
         j['descend'] = False
     # don't serialize score cache- could, of course
     return j
Beispiel #8
0
 def terminates(self, exchange):
     """
     Returns True if the exchange's termination matches the term's term_node, and the flows also match, and the
     directions are complementary.
     If the exchange does not specify a termination, returns True if the flows match and directions are comp.
     :param exchange:
     :return:
     """
     if self.term_flow.match(exchange.flow) and self.direction == comp_dir(exchange.direction):
         if exchange.termination is None:
             return True
         else:
             if self.is_null:
                 return False
             if self.term_node.entity_type != 'process':
                 return False
             if exchange.termination == self._process_ref.id:
                 return True
     return False
Beispiel #9
0
 def revise_exchanges(self, frag, scenario=None):
     """
     interactively update reference exchange values (or values for a particular scenario) for the children of
     a given fragment
     :param frag:
     :param scenario:
     :return:
     """
     print('Reference flow: [%s] %s' % (comp_dir(frag.direction), frag.unit))
     if scenario is None:
         print('Update reference flow')
     else:
         print('Update reference flow for scenario "%s"' % scenario)
     self._update_ev(frag, scenario)
     for c in self.child_flows(frag):
         print('   Child flow: %s ' % c)
         if scenario is None:
             print('Update default value')
         else:
             print('Update value for scenario "%s"' % scenario)
         self._update_ev(c, scenario)
Beispiel #10
0
    def traverse(self, childflows, upstream_nw, scenario,
                 observed=False, frags_seen=None, conserved_qty=None, _balance=None):

        """
        If the node has a non-null termination, use that; follow child flows.

        If the node's termination is null- then look for matching background fragments. If one is found, adopt its
        termination, and return.

        else: assume it is a null foreground node; follow child flows

        :param childflows: this is a lambda that takes current frag and returns a generator of frags listing self
        as parent
        - must be provided by calling environment

        :param upstream_nw: upstream node weight
        :param scenario: string or tuple of strings
        :param observed: whether to use observed or cached evs (overridden by scenario specification)
        :param frags_seen: carried along to catch recursion loops
        :param conserved_qty: in case the parent node is a conservation node
        :param _balance: used when flow magnitude is determined during traversal, i.e. for balance flows and
        children of fragment nodes
        :return: an array of FragmentFlow records reporting the traversal
        """

        def _print(qwer, level=1):
            self._print(qwer, level=level)
        '''
        First handle the traversal entry
        inputs:
         _balance
         conserved_qty
         observed
         scenario
         upstream_nw

        outputs:
         own ff
         conserved_val
        '''
        if _balance is None:
            if self.reference_entity is None:
                # reference fragment exchange values are inbound
                ev = 1.0 / self.exchange_value(scenario, observed=observed)
            else:
                ev = self.exchange_value(scenario, observed=observed)
        else:
            _print('%.3s %g balance' % (self.get_uuid(), _balance), level=2)
            ev = _balance
            self._cache_balance_ev(_balance, scenario)

        magnitude = upstream_nw * ev

        conserved_val = None
        conserved = False
        if conserved_qty is not None:
            if self._balance_flow:
                raise BalanceFlowError  # to be caught
            conserved_val = ev * self.flow.cf(conserved_qty)
            if conserved_val != 0:
                conserved = True
            if self.direction == 'Output':  # convention: inputs to parent are positive
                conserved_val *= -1
            _print('%.3s %g' % (self.get_uuid(), conserved_val), level=2)

        node_weight = self.node_weight(magnitude, scenario)
        term = self.termination(scenario)

        # print('%6f %6f %s' % (magnitude, node_weight, self))
        ff = [FragmentFlow(self, magnitude, node_weight, term, conserved)]

        if term.is_null or self.is_background or magnitude == 0:
            return ff, conserved_val

        '''
        now looking forward: is our child node conserving?
        '''

        if frags_seen is None:
            frags_seen = set()

        if self.reference_entity is None:
            if self.get_uuid() in frags_seen:
                raise InvalidParentChild('Frag %s seeing self\n %s' % (self.get_uuid(), '; '.join(frags_seen)))
            frags_seen.add(self.get_uuid())
        # print('Traversing %s\nfrags seen: %s\n' % (self, '; '.join(frags_seen)))

        if term.is_fg or term.term_node.entity_type == 'process':
            '''
            Handle foreground nodes and processes--> these can be quantity-conserving, but except for
            balancing flows the flow magnitudes are determined at the time of construction
            '''
            stock = None
            bal_f = None
            if self._conserved_quantity is not None:
                stock = self.flow.cf(self._conserved_quantity)
                if self.reference_entity is None:
                    # for reference nodes only, e.v. is inbound exchange-> scale balance back to real units
                    stock *= self.exchange_value(scenario, observed=observed)
                    # use repeat call to avoid double division
                if self.direction == 'Input':  # convention: inputs to self are positive
                    stock *= -1
                _print('%.3s %g inbound-balance' % (self.get_uuid(), stock), level=2)

            for f in childflows(self):
                try:
                    child_ff, cons = f.traverse(childflows, node_weight, scenario, observed=observed,
                                                frags_seen=set(frags_seen), conserved_qty=self._conserved_quantity)
                    if cons is not None:
                        stock += cons
                except BalanceFlowError:
                    bal_f = f
                    child_ff = []

                ff.extend(child_ff)

            if bal_f is not None:
                # balance reports net inflows; positive value is more coming in than out
                # if balance flow is an input, its exchange must be the negative of the balance
                # if it is an output, its exchange must equal the balance
                if bal_f.direction == 'Input':
                    stock *= -1
                bal_ff, cons = bal_f.traverse(childflows, node_weight, scenario, observed=observed,
                                              frags_seen=set(frags_seen), conserved_qty=None, _balance=stock)
                ff.extend(bal_ff)

        else:
            '''
            handle sub-fragments, including background flows--
            for sub-fragments, the flow magnitudes are determined at the time of traversal and must be pushed out to
             child flows
            for background flows, the background ff should replace the current ff, except maintaining self as fragment
            '''

            if term.term_node.is_background:
                bg_ff, cons = term.term_node.traverse(childflows, node_weight, scenario, observed=observed)
                bg_ff[0].fragment = self
                return bg_ff, conserved_val

            """
            if target fragment is not reference flow, fragment needs to be rebased from traversal result.

            to do this:
             * follow reference entities to find the fragment's reference
             * when it comes back:
              - remove current flow from ios
              - create a new ios fragmentflow corresponding to the reference flow reversed
             * if we're aggregating, set this node's node weight to the downstream nw
            """
            if term.term_node.reference_entity is not None:
                _print('inverse traversal---')
                the_ref = _recursive_ref(term.term_node)
                correct_reference = True
                in_ex = the_ref.exchange_value(scenario, observed=observed)
                # If flow directions conflict, subfragment is being run in reverse
                if term.term_node.direction == self.direction:
                    in_ex *= -1
                    _print('%s\nNegating subfragments-- caution ahead!' % self, level=0)
            else:
                the_ref = term.term_node
                correct_reference = False
                in_ex = the_ref.exchange_value(scenario, observed=observed)
                # If flow directions conflict, subfragment is being run in reverse
                if term.term_node.direction != self.direction:
                    in_ex *= -1
                    _print('%s\nNegating subfragments-- caution ahead!' % self, level=0)

            # for proper subfragments, need to determine child flow magnitudes based on traversal record
            subfrag_ffs, cons = the_ref.traverse(childflows, in_ex, scenario,
                                                 observed=observed, frags_seen=set(frags_seen))
            ios = [f for f in subfrag_ffs if f.term.is_null]
            subfrags = [f for f in subfrag_ffs if not f.term.is_null]
            ref_io = None

            if correct_reference:
                for _zz in ios:
                    if _zz.fragment is term.term_node:
                        in_ex = _zz.magnitude
                        _print('%s' % _zz)
                        _print('setting in_ex = %g' % in_ex)
                ios = [_zz for _zz in ios if _zz.fragment is not term.term_node]
                ref_io = GhostFragmentFlow(the_ref.flow, comp_dir(the_ref.direction))
                ios.append(ref_io)

            # first, we determine subfragment activity level by adjusting for any autoconsumption
            matches = [f for f in ios if f.fragment.flow == term.term_flow]
            for m in matches:
                _print('+-%s' % m)
                if m.fragment.direction == term.direction:
                    in_ex -= m.magnitude
                    _print(' -= %g' % m.magnitude)
                else:
                    in_ex += m.magnitude
                    _print(' -= %g' % m.magnitude)
                ios.remove(m)

            downstream_nw = node_weight / abs(in_ex)

            # then we add the results of the subfragment, either in aggregated or disaggregated form
            if term.descend:
                # if appending, we are traversing in situ, so do scale
                _print('descending', level=0)
                for i in subfrags:
                    i.scale(downstream_nw)
                ff.extend(subfrags)
            else:
                # if aggregating, we are only setting unit scores- so don't scale
                _print('aggregating', level=0)
                ff[0].term.aggregate_subfragments(subfrags)
                ff[0].node_weight = downstream_nw

            # next we traverse our own child flows, determining the exchange values from the subfrag traversal
            for f in childflows(self):
                ev = 0.0
                matches = [j for j in ios if j.fragment.flow == f.flow]
                # exchange values are per unit- so don't scale
                for m in matches:
                    if m is ref_io:
                        _print('found it! %s' % m)

                    if m.fragment.direction == f.direction:
                        ev += m.magnitude
                    else:
                        ev -= m.magnitude
                    ios.remove(m)

                child_ff, cons = f.traverse(childflows, downstream_nw, scenario, observed=observed,
                                            frags_seen=frags_seen, _balance=ev)
                ff.extend(child_ff)

            # remaining un-accounted io flows are getting appended, so do scale
            for x in ios:
                x.scale(downstream_nw)
            ff.extend(ios)

        # if descend is true- we give back everything- otherwise we aggregate
        return ff, conserved_val
Beispiel #11
0
    def build_child_flows(self, fragment, scenario=None, background_children=False):
        """
        Given a terminated fragment, construct child flows corresponding to the termination's complementary
        exchanges.

        :param fragment: the parent fragment
        :param scenario:
        :param background_children: if true, automatically terminate child flows to background.
        :return:
        """
        if fragment.is_background:
            return None  # no child flows for background nodes
        term = fragment.termination(scenario=scenario)
        if term.is_null or term.is_fg:
            return None

        if term.term_node.entity_type == 'process':
            if scenario is not None:
                print('Automatic building of child flows is not supported for scenario terminations of process nodes.')
                return []
            if len([c for c in self.child_flows(fragment)]) != 0:
                print('Warning: fragment already has child flows. Continuing will result in (possibly many) ')
                if ifinput('duplicate child flows.  Continue? y/n', 'n') != 'y':
                    return []

            int_exch = self.gen_exchanges(term.term_node, term.term_flow, term.direction)
            # in process case- possible to specify multiple identical children from the same node
            """
             this means that build_child_flows will have undesirable results if called for
             a process- terminated node with similar exchanges for multiple scenarios.
             maybe changing node terminations should only be permitted for subfragments. something to
             think about.
            """

        elif term.term_node.entity_type == 'fragment':

            if term.term_node.reference_entity is not None:
                the_ref = _recursive_ref(term.term_node)
                correct_reference = True
            else:
                the_ref = term.term_node
                correct_reference = False

            int_exch = self.get_fragment_inventory(the_ref, scenario=scenario)

            if correct_reference:
                surrogate_in = next(x for x in int_exch
                                    if x.flow is term.term_flow and x.direction == term.direction)  # or StopIter
                int_exch.remove(surrogate_in)
                int_exch.append(ExchangeValue(the_ref, the_ref.flow, comp_dir(the_ref.direction),
                                              value=the_ref.cached_ev))

                for x in int_exch:
                    x.value *= 1.0 / surrogate_in.value

            # in subfragment case- child flows aggregate so we don't want to create duplicate children
            for x in int_exch:
                match = [c for c in self.child_flows(fragment) if c.flow == x.flow and c.direction == x.direction]
                if len(match) > 1:
                    raise AmbiguousReference('Multiple child flows matching %s' % x)
                elif len(match) == 1:
                    int_exch.remove(x)  # don't make a new child if one is already found

        else:
            raise AmbiguousTermination('Cannot figure out entity type for %s' % term)

        children = []
        for exch in int_exch:
            child = self[0].add_child_ff_from_exchange(fragment, exch)
            if background_children:
                self.terminate_to_background(child)
            children.append(child)
        return children
Beispiel #12
0
 def terminate_fragment(self, index, frag, show=False):
     return self._check_exchanges(index, frag.flow, comp_dir(frag.direction), show=show)
Beispiel #13
0
 def comp_dir(self):
     return comp_dir(self.exchange.direction)