Example #1
0
def get_cluster_values(stream_plan):
    param_from_obj = {}
    macro_from_micro = []
    inputs, domain, outputs, certified, functions = [], set(), [], set(), set()
    input_objects, output_objects = [], []
    fluent_facts = []
    for result in stream_plan:
        local_mapping = {}  # global_from_local
        stream = result.instance.external
        add_result_inputs(result, param_from_obj, local_mapping, inputs, input_objects)
        domain.update(set(substitute_expression(stream.domain, local_mapping)) - certified)
        if isinstance(result, PredicateResult):
            # functions.append(Equal(stream.head, result.value))
            # TODO: do I need the new mapping here?
            mapping = {inp: param_from_obj[inp] for inp in result.instance.input_objects}
            functions.update(substitute_expression(result.get_certified(), mapping))
        elif isinstance(result, FunctionResult):
            functions.add(substitute_expression(Minimize(stream.head), local_mapping))
        else:
            fluent_facts.extend(result.instance.fluent_facts)
            add_result_outputs(result, param_from_obj, local_mapping, outputs, output_objects)
            certified.update(substitute_expression(stream.certified, local_mapping))
            macro_from_micro.append(local_mapping)
    #assert not fluent_facts
    return inputs, domain, outputs, certified, functions, \
           macro_from_micro, input_objects, output_objects, fluent_facts
Example #2
0
 def get_constraints(self):
     output_mapping = get_mapping(self.external.outputs, self.external.output_objects)
     output_mapping.update(self.get_mapping())
     #constraints = substitute_expression(self.external.certified, output_mapping)
     constraints = []
     for i, result in enumerate(self.external.stream_plan):
         macro_fact = substitute_expression(result.external.stream_fact, self.external.macro_from_micro[i])
         constraints.append(substitute_expression(macro_fact, output_mapping))
     # TODO: I think I should be able to just disable the fluent fact from being used in that context
     return constraints
Example #3
0
def add_optimizer_axioms(results, instantiated):
    # Ends up being a little slower than version in optimizer.py when not blocking shared
    # TODO: add this to simultaneous
    import pddl
    results_from_instance = defaultdict(list)
    for result in results:
        results_from_instance[result.instance].append(result)
    optimizer_results = list(filter(is_optimizer_result, results))
    optimizers = {result.external.optimizer for result in optimizer_results}
    for optimizer in optimizers:
        optimizer_facts = {
            substitute_expression(result.external.stream_fact,
                                  result.get_mapping())
            for result in optimizer_results
            if result.external.optimizer is optimizer
        }
        facts_from_arg = defaultdict(list)
        for fact in optimizer_facts:
            for arg in get_args(fact):
                facts_from_arg[arg].append(fact)

        for stream in optimizer.streams:
            if not stream.instance.disabled:
                continue
            constraints = stream.instance.get_constraints()
            output_variables = []
            for out in stream.output_objects:
                assert isinstance(out.param, UniqueOptValue)
                output_variables.append([
                    r.output_objects[out.param.output_index]
                    for r in results_from_instance[out.param.instance]
                ])
            for combo in product(*output_variables):
                mapping = get_mapping(stream.output_objects, combo)
                name = '({})'.join(UNSATISFIABLE)
                blocked = set(substitute_expression(constraints, mapping))
                additional = {
                    fact
                    for arg in combo for fact in facts_from_arg[arg]
                } - blocked
                # TODO: like a partial disable, if something has no outputs, then adding things isn't going to help
                if stream.instance.enumerated and not stream.instance.successes:
                    # Assumes the optimizer is submodular
                    condition = list(map(fd_from_fact, blocked))
                else:
                    condition = list(
                        map(fd_from_fact, blocked | set(map(Not, additional))))
                effect = fd_from_fact((UNSATISFIABLE, ))
                instantiated.axioms.append(
                    pddl.PropositionalAxiom(name, condition, effect))
                instantiated.atoms.add(effect)
Example #4
0
def process_conditional_effect(effect, negative_from_predicate):
    import pddl
    new_parts = []
    stream_facts = []
    for disjunctive in get_conjunctive_parts(effect.condition):
        for literal in get_disjunctive_parts(disjunctive):
            # TODO: assert only one disjunctive part
            if isinstance(literal,
                          pddl.Literal) and (literal.predicate
                                             in negative_from_predicate):
                stream = negative_from_predicate[literal.predicate]
                if not isinstance(stream, ConstraintStream):
                    new_parts.append(literal)
                    continue
                certified = find_unique(
                    lambda f: get_prefix(f) == literal.predicate,
                    stream.certified)
                mapping = get_mapping(get_args(certified), literal.args)
                stream_facts.append(
                    fd_from_fact(
                        substitute_expression(stream.stream_fact, mapping)))
                # TODO: add the negated literal as precondition here?
            else:
                new_parts.append(literal)
    return new_parts, stream_facts
Example #5
0
def optimistic_stream_grounding(stream_instance,
                                bindings,
                                evaluations,
                                opt_evaluations,
                                bind=True,
                                immediate=False):
    # TODO: combination for domain predicates
    evaluation_set = set(evaluations)
    opt_instances = []
    if not bind:
        bindings = {}
    input_objects = [
        bindings.get(i, [i]) for i in stream_instance.input_objects
    ]
    for combo in product(*input_objects):
        mapping = dict(zip(stream_instance.input_objects, combo))
        domain = set(
            map(evaluation_from_fact,
                substitute_expression(
                    stream_instance.get_domain(),
                    mapping)))  # TODO: could just instantiate first
        if domain <= opt_evaluations:
            instance = stream_instance.external.get_instance(combo)
            if (instance.opt_index != 0) and (not immediate or
                                              (domain <= evaluation_set)):
                instance.opt_index -= 1
            opt_instances.append(instance)
    return opt_instances
Example #6
0
def create_disable_axiom(external_plan, use_parameters=True):
    # TODO: express constraint mutexes upfront
    # TODO: investigate why use_parameters=True hurts satisfaction
    # TODO: better mix optimization and sampling by determining a splitting point
    # TODO: be careful about the shared objects as parameters
    # TODO: need to block functions & predicates
    stream_plan, _ = partition_external_plan(external_plan)
    assert stream_plan
    #component_plan = stream_plan
    [unsatisfiable] = stream_plan[-1].get_unsatisfiable()
    component_plan = list(flatten(
        r.get_components() for r in stream_plan[:-1])) + list(unsatisfiable)
    increase_free_variables(component_plan)
    #output_objects = get_free_objects(component_plan) if use_parameters else set()
    constraints = [result.stream_fact for result in component_plan]
    optimistic_objects = {
        o
        for f in constraints for o in get_args(f)
        if isinstance(o, OptimisticObject)
    }  # TODO: consider case when variables are free
    #assert optimistic_objects <= output_objects
    #free_objects = list(optimistic_objects & output_objects) # TODO: need to return all variables
    free_objects = optimistic_objects
    parameters = ['?p{}'.format(i) for i in range(len(free_objects))]
    param_from_obj = get_mapping(free_objects, parameters)
    preconditions = substitute_expression(constraints, param_from_obj)
    effect = (UNSATISFIABLE, )
    axiom = make_axiom(parameters, preconditions, effect)
    #axiom.dump()
    return axiom
Example #7
0
def get_stream_actions(results,
                       unique_binding=False,
                       effort_scale=1,
                       **kwargs):
    result_from_name = {}
    stream_actions = []
    for i, result in enumerate(results):
        #if not isinstance(stream_result, StreamResult):
        if type(result) == FunctionResult:
            continue
        effort = compute_result_effort(result, **kwargs)
        if effort == INF:
            continue
        # TODO: state constraints
        # TODO: selectively negate axioms
        result_name = '{}-{}'.format(result.external.name, i)
        #result_name = '{}_{}_{}'.format(result.external.name, # No spaces & parens
        #                        ','.join(map(pddl_from_object, result.instance.input_objects)),
        #                        ','.join(map(pddl_from_object, result.output_objects)))
        assert result_name not in result_from_name
        result_from_name[result_name] = result

        preconditions = list(result.instance.get_domain())
        effects = list(result.get_certified())
        if unique_binding:
            enforce_single_binding(result, preconditions, effects)
        if is_optimizer_result(
                result):  # These effects don't seem to be pruned
            effects.append(
                substitute_expression(result.external.stream_fact,
                                      result.get_mapping()))
        stream_actions.append(
            make_action(result_name, [], preconditions, effects,
                        effort_scale * effort))
    return stream_actions, result_from_name
Example #8
0
def add_stream_costs(node_from_atom, instantiated, unit_efforts, effort_weight):
    # TODO: instantiate axioms with negative on effects for blocking
    # TODO: fluent streams using conditional effects. Special fluent predicate for inputs to constraint
    # This strategy will only work for relaxed to ensure that the current state is applied
    for instance in instantiated.actions:
        # TODO: prune stream actions here?
        # Ignores conditional effect costs
        facts = []
        for precondition in get_literals(instance.action.precondition):
            if precondition.negated:
                continue
            args = [instance.var_mapping.get(arg, arg) for arg in precondition.args]
            literal = precondition.__class__(precondition.predicate, args)
            fact = fact_from_fd(literal)
            if fact in node_from_atom:
                facts.append(fact)
        #effort = COMBINE_OP([0] + [node_from_atom[fact].effort for fact in facts])
        stream_plan = []
        extract_stream_plan(node_from_atom, facts, stream_plan)
        if unit_efforts:
            effort = len(stream_plan)
        else:
            effort = scale_cost(sum([0] + [r.instance.get_effort() for r in stream_plan]))
        if effort_weight is not None:
            instance.cost += effort_weight*effort
        # TODO: bug! The FD instantiator prunes the result.external.stream_fact
        for result in stream_plan:
            # TODO: need to make multiple versions if several ways of achieving the action
            if is_optimizer_result(result):
                fact = substitute_expression(result.external.stream_fact, result.get_mapping())
                atom = fd_from_fact(fact)
                instantiated.atoms.add(atom)
                effect = (tuple(), atom)
                instance.add_effects.append(effect)
Example #9
0
 def __init__(self, instance, output_objects, opt_index=None):
     super(StreamResult, self).__init__(instance, opt_index)
     self.output_objects = tuple(output_objects)
     self.mapping = dict(
         zip(self.instance.external.outputs, self.output_objects))
     self.mapping.update(instance.mapping)
     self.certified = substitute_expression(
         self.instance.external.certified, self.get_mapping())
Example #10
0
 def __init__(self, external, input_objects):
     self.external = external
     self.input_objects = tuple(input_objects)
     self.enumerated = False
     self.disabled = False
     self.opt_index = 0
     self.results_history = []
     self.mapping = dict(zip(self.external.inputs, self.input_objects))
     for constant in self.external.constants:
         self.mapping[constant] = Object.from_name(constant)
     self.domain = substitute_expression(self.external.domain,
                                         self.get_mapping())
Example #11
0
 def get_unsatisfiable(self):
     constraints = substitute_expression(self.external.certified, self.external.mapping)
     index_from_constraint = {c: i for i, c in enumerate(constraints)}
     # TODO: compute connected components
     result_from_index = defaultdict(set)
     for result in self.external.stream_plan:
         for fact in result.get_certified():
             if fact in index_from_constraint:
                 result_from_index[index_from_constraint[fact]].add(result)
     # TODO: add implied results
     #orders = get_partial_orders(self.external.stream_plan)
     return [{result for index in cluster for result in result_from_index[index]}
             for cluster in prune_dominated(self.infeasible)]
Example #12
0
def optimizer_conditional_effects(domain, externals):
    import pddl
    #from pddlstream.algorithms.scheduling.negative import get_negative_predicates
    # TODO: extend this to predicates
    if UNIVERSAL_TO_CONDITIONAL:
        negative_streams = list(filter(lambda e: e.is_negated(), externals))
    else:
        negative_streams = list(
            filter(
                lambda e: isinstance(e, ConstraintStream) and e.is_negated(),
                externals))
    negative_from_predicate = get_predicate_map(negative_streams)
    if not negative_from_predicate:
        return
    for action in domain.actions:
        universal_to_conditional(action)
        new_effects = []
        for effect in action.effects:
            if effect.literal.predicate != UNSATISFIABLE:
                new_effects.append(effect)
                continue
            new_parts = []
            stream_facts = []
            for disjunctive in get_conjunctive_parts(effect.condition):
                for literal in get_disjunctive_parts(disjunctive):
                    # TODO: assert only one disjunctive part
                    if isinstance(literal, pddl.Literal) and (
                            literal.predicate in negative_from_predicate):
                        stream = negative_from_predicate[literal.predicate]
                        if not isinstance(stream, ConstraintStream):
                            new_parts.append(literal)
                            continue
                        certified = find_unique(
                            lambda f: get_prefix(f) == literal.predicate,
                            stream.certified)
                        mapping = get_mapping(get_args(certified),
                                              literal.args)
                        stream_facts.append(
                            fd_from_fact(
                                substitute_expression(stream.stream_fact,
                                                      mapping)))
                        # TODO: add the negated literal as precondition here?
                    else:
                        new_parts.append(literal)
            if not stream_facts:
                new_effects.append(effect)
            for stream_fact in stream_facts:
                new_effects.append(
                    pddl.Effect(effect.parameters, pddl.Conjunction(new_parts),
                                stream_fact))
        action.effects = new_effects
Example #13
0
def optimistic_stream_instantiation(instance, bindings, evaluations, opt_evaluations,
                                    only_immediate=False):
    # TODO: combination for domain predicates
    new_instances = []
    for input_combo in product(*[bindings.get(i, [i]) for i in instance.input_objects]):
        mapping = get_mapping(instance.input_objects, input_combo)
        domain_evaluations = set(map(evaluation_from_fact, substitute_expression(
            instance.get_domain(), mapping))) # TODO: could just instantiate first
        if domain_evaluations <= opt_evaluations:
            new_instance = instance.external.get_instance(input_combo)
            if (new_instance.opt_index != 0) and (not only_immediate or (domain_evaluations <= evaluations)):
                new_instance.opt_index -= 1
            new_instances.append(new_instance)
    return new_instances
Example #14
0
def add_optimizer_effects(instantiated, instance, stream_plan):
    # TODO: instantiate axioms with negative on effects for blocking
    # TODO: fluent streams using conditional effects. Special fluent predicate for inputs to constraint
    # This strategy will only work for relaxed to ensure that the current state is applied
    # TODO: bug! The FD instantiator prunes the result.external.stream_fact
    for result in stream_plan:
        if not is_optimizer_result(result):
            continue
        # TODO: need to make multiple versions if several ways of achieving the action
        atom = fd_from_fact(
            substitute_expression(result.external.stream_fact,
                                  result.get_mapping()))
        instantiated.atoms.add(atom)
        effect = (tuple(), atom)
        instance.add_effects.append(effect)
Example #15
0
def optimistic_stream_instantiation(instance, bindings, opt_evaluations, only_immediate=False):
    # TODO: combination for domain predicates
    new_instances = []
    input_candidates = [bindings.get(i, [i]) for i in instance.input_objects]
    if only_immediate and not all(len(candidates) == 1 for candidates in input_candidates):
        return new_instances
    for input_combo in product(*input_candidates):
        mapping = get_mapping(instance.input_objects, input_combo)
        domain_evaluations = set(map(evaluation_from_fact, substitute_expression(
            instance.get_domain(), mapping))) # TODO: could just instantiate first
        if domain_evaluations <= opt_evaluations:
            new_instance = instance.external.get_instance(input_combo)
            # TODO: method for eagerly evaluating some of these?
            if not new_instance.is_refined():
                new_instance.refine()
            new_instances.append(new_instance)
    return new_instances
Example #16
0
def get_stream_actions(results,
                       unique_binding=False,
                       unit_efforts=True,
                       effort_scale=1):
    #from pddl_parser.parsing_functions import parse_action
    import pddl
    stream_result_from_name = {}
    stream_actions = []
    for i, result in enumerate(results):
        #if not isinstance(stream_result, StreamResult):
        if type(result) == FunctionResult:
            continue
        effort = get_instance_effort(result.instance, unit_efforts)
        if effort == INF:
            continue
        # TODO: state constraints
        # TODO: selectively negate axioms
        result_name = '{}-{}'.format(result.external.name, i)
        #result_name = '{}_{}_{}'.format(result.external.name, # No spaces & parens
        #                        ','.join(map(pddl_from_object, result.instance.input_objects)),
        #                        ','.join(map(pddl_from_object, result.output_objects)))
        assert result_name not in stream_result_from_name
        stream_result_from_name[result_name] = result

        preconditions = list(result.instance.get_domain())
        effects = list(result.get_certified())
        #if ORDER_OUTPUT:
        #    enforce_output_order(result, preconditions, effects)
        if unique_binding:
            enforce_single_binding(result, preconditions, effects)
        if is_optimizer_result(
                result):  # These effects don't seem to be pruned
            effects.append(
                substitute_expression(result.external.stream_fact,
                                      result.get_mapping()))
        parameters = []  # Usually all parameters are external
        stream_actions.append(
            pddl.Action(name=result_name,
                        parameters=parameters,
                        num_external_parameters=len(parameters),
                        precondition=make_preconditions(preconditions),
                        effects=make_effects(effects),
                        cost=make_cost(effort_scale *
                                       effort)))  # Can also be None
    return stream_actions, stream_result_from_name
Example #17
0
 def _add_disabled_axiom(self, domain):
     # TODO: be careful about the shared objects as parameters
     for results in self._get_unsatisfiable():
         constraints = {r.stream_fact
                        for r in results
                        }  # r.get_domain(), r.get_certified()
         # TODO: what if free parameter is a placeholder value
         #free_objects = list({o for f in constraints for o in get_args(f) if isinstance(o, OptimisticObject)})
         free_objects = list({
             o
             for f in constraints for o in get_args(f)
             if o in self.external.output_objects
         })
         parameters = ['?p{}'.format(i) for i in range(len(free_objects))]
         preconditions = substitute_expression(
             constraints, get_mapping(free_objects, parameters))
         disabled_axiom = make_axiom(parameters, preconditions,
                                     (UNSATISFIABLE, ))
         #self._disabled_axioms.append(disabled_axiom)
         domain.axioms.append(disabled_axiom)
Example #18
0
def apply_rules_to_streams(rules, streams):
    # TODO: can actually this with multiple condition if stream certified contains all
    # TODO: do also when no domain conditions
    processed_rules = deque(rules)
    while processed_rules:
        rule = processed_rules.popleft()
        if len(rule.domain) != 1:
            continue
        [rule_fact] = rule.domain
        rule.info.p_success = 0 # Need not be applied
        for stream in streams:
            if not isinstance(stream, Stream):
                continue
            for stream_fact in stream.certified:
                if get_prefix(rule_fact) == get_prefix(stream_fact):
                    mapping = get_mapping(get_args(rule_fact), get_args(stream_fact))
                    new_facts = set(substitute_expression(rule.certified, mapping)) - set(stream.certified)
                    stream.certified = stream.certified + tuple(new_facts)
                    if new_facts and (stream in rules):
                            processed_rules.append(stream)
Example #19
0
 def _get_unsatisfiable(self):
     if not self.infeasible:
         yield set(self.external.stream_plan)
     constraints = substitute_expression(self.external.certified,
                                         self.external.mapping)
     index_from_constraint = {c: i for i, c in enumerate(constraints)}
     # TODO: just prune any subsets
     #sets = []
     #for s1 in self.infeasible:
     #    for s2 in self.infeasible:
     #    else:
     #        sets.append(s1)
     # TODO: compute connected components
     result_from_index = defaultdict(set)
     for result in self.external.stream_plan:
         for fact in result.get_certified():
             if fact in index_from_constraint:
                 result_from_index[index_from_constraint[fact]].add(result)
     for cluster in self.infeasible:
         yield {
             result
             for index in cluster for result in result_from_index[index]
         }
Example #20
0
def ground_stream_instances(stream_instance, bindings, evaluations,
                            opt_evaluations, plan_index):
    # TODO: combination for domain predicates
    evaluation_set = set(evaluations)
    combined_evaluations = evaluation_set | opt_evaluations
    real_instances = []
    opt_instances = []
    input_objects = [
        bindings.get(i, [i]) for i in stream_instance.input_objects
    ]
    for combo in product(*input_objects):
        mapping = dict(zip(stream_instance.input_objects, combo))
        domain = set(
            map(evaluation_from_fact,
                substitute_expression(stream_instance.get_domain(), mapping)))
        if domain <= combined_evaluations:
            instance = stream_instance.external.get_instance(combo)
            immediate = False
            if immediate:
                if domain <= evaluation_set:
                    if instance.opt_index == 0:
                        real_instances.append(instance)
                    else:
                        instance.opt_index -= 1
                        opt_instances.append(instance)
                else:
                    opt_instances.append(instance)
            else:
                #if (instance.opt_index == 0) and (domain <= evaluation_set):
                if (plan_index == 0) and (domain <= evaluation_set):
                    real_instances.append(instance)
                else:
                    if instance.opt_index != 0:
                        instance.opt_index -= 1
                    opt_instances.append(instance)
    return real_instances, opt_instances
Example #21
0
def optimizer_conditional_effects(domain, externals):
    import pddl
    #from pddlstream.algorithms.scheduling.negative import get_negative_predicates
    # TODO: extend this to predicates
    negative_streams = list(
        filter(lambda e: isinstance(e, ConstraintStream) and e.is_negated(),
               externals))
    negative_from_predicate = get_predicate_map(negative_streams)
    if not negative_from_predicate:
        return
    for action in domain.actions:
        universal_to_conditional(action)
        for effect in action.effects:
            if isinstance(effect, pddl.Effect) and (effect.literal.predicate
                                                    == UNSATISFIABLE):
                condition = effect.condition
                new_parts = []
                stream_fact = None
                for literal in get_conjuctive_parts(condition):
                    if isinstance(literal, pddl.Literal) and (
                            literal.predicate in negative_from_predicate):
                        if stream_fact is not None:
                            raise NotImplementedError()
                        stream = negative_from_predicate[literal.predicate]
                        certified = find_unique(
                            lambda f: get_prefix(f) == literal.predicate,
                            stream.certified)
                        mapping = get_mapping(get_args(certified),
                                              literal.args)
                        stream_fact = substitute_expression(
                            stream.stream_fact, mapping)
                    else:
                        new_parts.append(literal)
                if stream_fact is not None:
                    effect.condition = pddl.Conjunction(new_parts)
                    effect.literal = fd_from_fact(stream_fact)
Example #22
0
 def certified(self):
     if self._certified is None:
         self._certified = substitute_expression(self.external.certified,
                                                 self.get_mapping())
     return self._certified
Example #23
0
 def stream_fact(self):
     if self._stream_fact is None:
         self._stream_fact = substitute_expression(
             self.external.stream_fact, self.mapping)
     return self._stream_fact
Example #24
0
 def get_head(self):
     return substitute_expression(self.external.head, self.get_mapping())
Example #25
0
 def get_objectives(self):
     return substitute_expression(self.external.objectives, self.get_mapping())
Example #26
0
 def gen_fn(*input_values):
     mapping = get_mapping(inputs, input_values)
     targets = substitute_expression(certified, mapping)
     return procedure(outputs, targets)
Example #27
0
 def gen_fn(*input_values): # TODO: take in guess values for inputs?
     assert (len(inputs) == len(input_values))
     mapping = dict(zip(inputs, input_values))
     targets = substitute_expression(certified | functions, mapping)
     return synthesizer.gen_fn(outputs, targets) # TODO: could also return a map
Example #28
0
 def get_functions(self):
     return substitute_expression(self.instance.external.functions, self.get_mapping())
Example #29
0
 def head(self):
     if self._head is None:
         self._head = substitute_expression(self.external.head,
                                            self.get_mapping())
     return self._head
Example #30
0
    def get_synth_stream(self, stream_plan):
        key = frozenset(stream_plan)
        if key in self.macro_results:
            return self.macro_results[key]
        param_from_obj = {}
        macro_from_micro = []
        inputs, domain, outputs, certified = [], set(), [], set()
        input_objects, output_objects = [], []
        functions = set()
        streams = []
        for result in stream_plan:
            local_mapping = {}
            stream = result.instance.external
            for inp, input_object in zip(stream.inputs,
                                         result.instance.input_objects):
                # TODO: only do optimistic parameters?
                # if isinstance()
                if input_object not in param_from_obj:
                    param_from_obj[input_object] = '?i{}'.format(len(inputs))
                    inputs.append(param_from_obj[input_object])
                    input_objects.append(input_object)
                local_mapping[inp] = param_from_obj[input_object]
            domain.update(
                set(substitute_expression(stream.domain, local_mapping)) -
                certified)

            if isinstance(result, PredicateResult):
                # functions.append(Equal(stream.head, result.value))
                mapping = {
                    inp: param_from_obj[inp]
                    for inp in result.instance.input_objects
                }
                functions.update(
                    substitute_expression(result.get_certified(), mapping))
            elif isinstance(result, FunctionResult):
                functions.add(
                    substitute_expression(Minimize(stream.head),
                                          local_mapping))
            else:
                for out, output_object in zip(stream.outputs,
                                              result.output_objects):
                    if output_object not in param_from_obj:
                        param_from_obj[output_object] = '?o{}'.format(
                            len(outputs))
                        outputs.append(param_from_obj[output_object])
                        output_objects.append(output_object)
                    local_mapping[out] = param_from_obj[output_object]
                certified.update(
                    substitute_expression(stream.certified, local_mapping))
                streams.append(stream)
                macro_from_micro.append(local_mapping)

        gen_fn = self.get_gen_fn(inputs, outputs, certified | functions)
        mega_stream = SynthStream(self,
                                  gen_fn,
                                  inputs=tuple(inputs),
                                  domain=domain,
                                  outputs=tuple(outputs),
                                  certified=certified,
                                  streams=streams,
                                  macro_from_micro=macro_from_micro)
        mega_instance = mega_stream.get_instance(input_objects)
        self.macro_results[key] = SynthStreamResult(mega_instance,
                                                    output_objects)
        return self.macro_results[key]