Esempio n. 1
0
def lemma_instantiation_terms(lemma_args, lemma_grammar_terms, annctx):
    example_lemmas = representative_lemmas(lemma_args,
                                           lemma_grammar_terms,
                                           annctx=annctx)
    pfp_formulas = {
        make_pfp_formula(lemma, annctx=annctx)
        for lemma in example_lemmas
    }
    instantiation_terms = get_foreground_terms(pfp_formulas, annctx=annctx)
    # get_foreground_terms already performs subterm closure
    return instantiation_terms
Esempio n. 2
0
def goal_extraction_terms(goal_instantiation_terms, lemma_args,
                          lemma_grammar_terms, annctx):
    example_lemmas = representative_lemmas(lemma_args,
                                           lemma_grammar_terms,
                                           annctx=annctx)
    # Only the variables in lemma_args can be quantified over
    lemma_params = tuple(
        [lemma_arg for lemma_arg in lemma_args if is_var_decl(lemma_arg)])
    bound_example_lemmas = {(lemma_params, example_lemma)
                            for example_lemma in example_lemmas}
    # Instantiate example lemmas with all possible terms from the instantiation the goal and axioms
    example_instantiations = instantiate(bound_example_lemmas,
                                         goal_instantiation_terms)
    example_extraction_terms = get_foreground_terms(example_instantiations,
                                                    annctx=annctx)
    # Model extraction already performs subterm closure so there is no need to do it here.
    return example_extraction_terms
Esempio n. 3
0
    def __init__(self,
                 smtmodel,
                 terms,
                 vocabulary=None,
                 annctx=default_annctx):
        """
        Finite model creation.  
        Foreground universe of extracted model corresponds to terms with subterm-closure. If vocabulary is not 
        specified, the entire vocabulary tracked in annctx is used.  
        :param smtmodel: z3.ModelRef  
        :param terms: set of z3.ExprRef  
        :param vocabulary: set of z3.ExprRef  
        :param annctx: naturalproofs.AnnotatedContext.AnnotatedContext  

        This function also defines the format of finite models.  
        Given a vocabulary of functions f1, f2, ..fm, of arity a1, a2, ...am, the model is as follows:  
        model :: dict {'fk' : dict_fk}  
        where 'fk' is some representation of the function fk, and  
        dict_fk :: dict {(e1, e2,... ek) : fk(e1, e2, ...ek)}  
        where (e1, e2, ...ek) is a tuple such that e1, e2,.. etc are concrete values in python that are 
        dependent on the domain and range sorts of fk.  
        In particular if the arity k is 0, then dict_fk will be of the following form:  
        dict_fk :: dict {() : fk()}  

        These models are meant to be partial models, and in general it will not be possible to evaluate an arbitrary
        formula on such a model, just those that are quantifier-free with foreground terms in the set of terms used 
        to extract the finite model.  
        """
        model = dict()
        # TODO: VERY IMPORTANT: hack to include those terms that are directly given as integers or integer expressions
        elems = {smtmodel.eval(term, model_completion=True) for term in terms}
        # TODO: the assumption is that uninterpreted functions have arguments only from the foreground sort. Must handle
        #  cases where uninterpreted functions have arguments in other domains, primarily integers.
        # Subterm-close the given terms assuming one-way functions
        # get_foreground_terms already performs subterm closure
        subterm_closure = get_foreground_terms(terms, annctx=annctx)
        elems = elems | {
            smtmodel.eval(term, model_completion=True)
            for term in subterm_closure
        }
        if vocabulary is None:
            vocabulary = get_vocabulary(annctx)
        for func in vocabulary:
            arity = func.arity()
            *input_signature, output_sort = get_uct_signature(func, annctx)
            # Only supporting uninterpreted functions with input arguments all from the foreground sort
            if not all(sig == fgsort for sig in input_signature):
                raise ValueError(
                    'Function with input(s) not from the foreground sort. Unsupported.'
                )
            func_key_repr = model_key_repr(func)
            # Distinguish common cases for faster execution
            if arity == 0:
                model[func_key_repr] = {
                    ():
                    _extract_value(
                        smtmodel.eval(func(), model_completion=True),
                        output_sort)
                }
            elif arity == 1:
                model[func_key_repr] = {
                    (_extract_value(elem, fgsort), ): _extract_value(
                        smtmodel.eval(func(elem), model_completion=True),
                        output_sort)
                    for elem in elems
                }
            else:
                func_dict = dict()
                args = itertools.product(elems, repeat=arity)
                for arg in args:
                    arg_value = tuple(
                        _extract_value(component, fgsort) for component in arg)
                    func_dict[arg_value] = _extract_value(
                        smtmodel.eval(func(*arg), model_completion=True),
                        output_sort)
                model[func_key_repr] = func_dict

        # Object attributes
        self.finitemodel = model
        self.smtmodel = smtmodel
        self.vocabulary = vocabulary
        self.annctx = annctx
        self.offset = 0
        self.fg_universe = collect_fg_universe(self.finitemodel, self.annctx)
        # Logging attributes
        self.extraction_terms = subterm_closure
        # Caching attributes
        self.recompute_offset = True
Esempio n. 4
0
def solveProblem(lemma_grammar_args,
                 lemma_grammar_terms,
                 goal,
                 name,
                 grammar_string,
                 config_params=None,
                 annctx=default_annctx):
    # Extract relevant parameters for running the verification-synthesis engine from config_params
    if config_params is None:
        config_params = {}
    lemma_grammar_terms = get_all_subterms(lemma_grammar_terms)
    valid_lemmas = set()
    invalid_lemmas = []
    cex_models = []
    true_cex_models = []

    # Determine proof mode for goal
    goal_instantiation_mode = config_params.get('goal_instantiation_mode',
                                                None)
    supported_goal_instantiation_modes = {
        proveroptions.manual_instantiation,
        proveroptions.depth_one_stratified_instantiation,
        proveroptions.fixed_depth, proveroptions.lean_instantiation,
        proveroptions.lean_instantiation_with_lemmas
    }
    if goal_instantiation_mode is None:
        # stratified instantiation by default
        goal_instantiation_mode = proveroptions.depth_one_stratified_instantiation
    elif goal_instantiation_mode not in supported_goal_instantiation_modes:
        # The set of instantiation terms must stay constant
        # TODO: check for unsoundness of algorithm if instantiation mode has automatic adaptive depth.
        # If the check passes there is no need for this branch of the condition.
        raise ValueError(
            'Unsupported or unknown proof (instantiation) mode for goal.')

    # Create properly configured solver object that checks the goal in the presence of 0 or more lemmas
    # IMPORTANT: this is the solver object that will always be used to attempt proving the goal. Doing this
    # standardises the configuration used to prove the goal and ensures that the changes made to the
    # solver object are reflected everywhere in the pipeline.
    goal_fo_solver = NPSolver()
    goal_fo_solver.options.instantiation_mode = goal_instantiation_mode
    if goal_instantiation_mode == proveroptions.manual_instantiation:
        goal_manual_instantiation_terms = config_params.get(
            'goal_instantiation_terms', None)
        if goal_manual_instantiation_terms is None:
            raise ValueError(
                'Instantiation terms must be specified for goal in manual mode.'
            )
        goal_fo_solver.options.terms_to_instantiate = goal_manual_instantiation_terms
    elif goal_instantiation_mode == proveroptions.fixed_depth:
        # Default depth is 1
        goal_instantiation_depth = config_params.get(
            'goal_instantiation_depth', 1)
        goal_fo_solver.options.depth = goal_instantiation_depth
    config_params['goal_solver'] = goal_fo_solver

    # check if goal is fo provable
    goal_fo_npsolution = goal_fo_solver.solve(goal)
    if goal_fo_npsolution.if_sat:
        print('goal is not first-order provable.')
    else:
        print('goal is first-order provable.')
        exit(0)

    # check if goal is fo provable using its own pfp
    pfp_of_goal = make_pfp_formula(goal)
    goal_pfp_solver = NPSolver()
    goal_pfp_solver.options.instantiation_mode = goal_instantiation_mode
    if goal_instantiation_mode == proveroptions.manual_instantiation:
        warnings.warn(
            'Manual instantiation mode: PFP of goal will be proved using the same terms the goal itself.'
        )
    goal_pfp_npsolution = goal_pfp_solver.solve(pfp_of_goal)
    if goal_pfp_npsolution.if_sat:
        print('goal cannot be proved using induction.')
    else:
        print('goal is provable using induction.')
        exit(0)

    # goal_npsolution_instantiation_terms = goal_fo_npsolution.extraction_terms
    # config_params['goal_npsolution_instantiation_terms'] = goal_npsolution_instantiation_terms
    # Temporarily disabling expanding the set of extraction terms depending on the grammar.
    # goal_extraction_terms = grammar.goal_extraction_terms(goal_npsolution_instantiation_terms,
    #                                                       lemma_grammar_args, lemma_grammar_terms, annctx)
    # config_params['goal_extraction_terms'] = goal_extraction_terms

    # Extract relevant instantiation/extraction terms for lemmas given the grammar
    # This set is constant
    lemma_instantiation_terms = grammar.lemma_instantiation_terms(
        lemma_grammar_args, lemma_grammar_terms, annctx)

    # Initial timeout (in seconds) for streaming
    config_params['streaming_timeout'] = 15 * 60
    if config_params['streaming_timeout'] < 20:
        raise ValueError(
            'Cannot time streams with small timeouts. Use >= 20 seconds.')
    # Dictionary for logging pipeline analytics
    if options.analytics:
        config_params['analytics'] = {'total_lemmas': 0, 'time_charged': 0}

    # Some fixed utility functions for interpreting synthesis outputs
    # Values used to convert CVC4 versions of membership, insertion to z3py versions
    SetIntSort = SetSort(IntSort())
    membership = Function('membership', IntSort(), SetIntSort, BoolSort())
    insertion = Function('insertion', IntSort(), SetIntSort, SetIntSort)
    addl_decls = {'member': membership, 'insert': insertion}
    swap_fcts = {insertion: SetAdd}
    replace_fcts = {membership: IsMember}
    # List of recursive boolean-valued rec defs that can appear on the left hand side of a lemma
    recs = get_boolean_recursive_definitions()

    # continuously get valid lemmas until goal has been proven
    while True:
        sygus_results = getSygusOutput(valid_lemmas, lemma_grammar_args, goal,
                                       name, grammar_string, config_params,
                                       annctx)
        if sygus_results is None or sygus_results == []:
            exit('No lemmas proposed. Instance failed.')
        for rhs_pre, lhs_pre in sygus_results:
            rhs_pre = rhs_pre.strip()
            lhs_pre = lhs_pre.strip()
            if options.analytics:
                config_params['analytics']['total_lemmas'] += 1
            # Casting the lemma into a Z3Py expression
            if options.analytics:
                curr_time = time.time()
                config_params['analytics']['lemma_time'] = int(
                    curr_time -
                    config_params['analytics']['proposal_start_time'])
            pre_validation = time.time()
            rhs_lemma = translateLemma(rhs_pre, lemma_grammar_args, addl_decls,
                                       swap_fcts, replace_fcts, annctx)
            index = int(lhs_pre[:-1].split(' ')[-1])
            lhs_func = recs[index]
            lhs_arity = lhs_func.arity()
            lhs_lemma_args = tuple(lemma_grammar_args[:lhs_arity])
            lhs_lemma = lhs_func(lhs_lemma_args)
            z3py_lemma_body = Implies(lhs_lemma, rhs_lemma)
            z3py_lemma_params = tuple(
                [arg for arg in lemma_grammar_args if is_var_decl(arg)])
            z3py_lemma = (z3py_lemma_params, z3py_lemma_body)

            if options.verbose > 0:
                print('proposed lemma: {}'.format(str(z3py_lemma_body)))
            if options.verbose >= 10 and options.analytics:
                print('total lemmas so far: ' +
                      str(config_params['analytics']['total_lemmas']))

            if z3py_lemma in invalid_lemmas or z3py_lemma in valid_lemmas:
                if options.use_cex_models or options.use_cex_true_models:
                    print('lemma has already been proposed')
                    if z3py_lemma in invalid_lemmas:
                        print(
                            'Something is wrong. Lemma was re-proposed in the presence of countermodels. '
                            'Exiting.')
                    # TODO: remove after replacing this with a check for terms in the grammar
                    if z3py_lemma in valid_lemmas:
                        print(
                            'This is a currently known limitation of the tool. Consider restricting your grammar to '
                            'have terms of lesser height.')
                    exit('Instance failed.')
                else:
                    # No countermodels. Check if streaming mode for synthesis is enabled.
                    if not options.streaming_synthesis_swtich:
                        raise RuntimeError(
                            'Lemmas reproposed with countermodels and streaming disabled. Unsupported.'
                        )
                    if options.verbose >= 7:
                        print(
                            'Countermodels not enabled. Retrying lemma synthesis.'
                        )
                    if options.analytics:
                        post_validation = time.time()
                        validation_time = post_validation - pre_validation
                        config_params['analytics'][
                            'time_charged'] += validation_time
                        if options.verbose >= 10:
                            print('Current lemma handled in: ' +
                                  str(validation_time) + 's')
                            print('Time charged so far: ' + str(
                                config_params['analytics']['time_charged']) +
                                  's')
                    continue
            pfp_lemma = make_pfp_formula(z3py_lemma_body)
            lemmaprover = NPSolver()
            lemmaprover.options.instantiation_mode = proveroptions.manual_instantiation
            lemmaprover.options.terms_to_instantiate = lemma_instantiation_terms
            lemma_npsolution = lemmaprover.solve(pfp_lemma, valid_lemmas)
            if options.analytics and options.streaming_synthesis_swtich:
                post_validation = time.time()
                validation_time = post_validation - pre_validation
                config_params['analytics']['time_charged'] += validation_time
                if options.verbose >= 10:
                    print('Current lemma handled in: ' + str(validation_time) +
                          's')
                    print('Time charged so far: ' +
                          str(config_params['analytics']['time_charged']) +
                          's')
            if lemma_npsolution.if_sat:
                if options.verbose >= 4:
                    print('proposed lemma cannot be proved.')
                if options.debug:
                    # Check that the terms needed from the pfp of the proposed
                    # lemma do not exceed lemma_grammar_terms.
                    # Otherwise finite model extraction will not work.
                    needed_instantiation_terms = get_foreground_terms(
                        pfp_lemma, annctx=annctx)
                    remaining_terms = needed_instantiation_terms - lemma_instantiation_terms
                    if remaining_terms != set():
                        raise ValueError(
                            'lemma_terms is too small.\n'
                            'Lemma: {}\n'
                            'Terms needed after pfp computation: {}'
                            ''.format(str(z3py_lemma_body), remaining_terms))
                invalid_lemmas = invalid_lemmas + [z3py_lemma]

                use_cex_models_fallback = False
                if options.use_cex_true_models:
                    if options.verbose >= 4:
                        print('using true counterexample models')
                    true_model_size = 5
                    true_cex_model, true_model_terms = gen_lfp_model(
                        true_model_size, annctx, invalid_formula=z3py_lemma)
                    if true_cex_model is not None:
                        # Use after gen_lfp_model fixes values on elements not in the lfp (as sort.lattice_bottom)
                        # true_model_terms = {z3.IntVal(elem) for elem in true_cex_model.fg_universe}
                        const = [
                            arg for arg in lemma_grammar_args
                            if not is_var_decl(arg, annctx)
                        ]
                        lemma_arity = len(lemma_grammar_args) - len(const)
                        args = itertools.product(true_model_terms,
                                                 repeat=lemma_arity)
                        instantiations = [
                            arg for arg in args if z3.is_false(
                                true_cex_model.smtmodel.
                                eval(z3.substitute(
                                    z3py_lemma[1],
                                    list(
                                        zip(lemma_grammar_args[:lemma_arity],
                                            arg))),
                                     model_completion=True))
                        ]
                        true_cex_models = true_cex_models + [
                            (true_cex_model, {instantiations[0]})
                        ]
                        config_params['true_cex_models'] = true_cex_models
                    else:
                        # No LFP countermodel found. Supplant with PFP countermodel.
                        use_cex_models_fallback = True
                        if options.verbose >= 4:
                            print(
                                'No true countermodel obtained. Using pfp countermodel instead.'
                            )
                if options.use_cex_models or use_cex_models_fallback:
                    extraction_terms = lemma_npsolution.extraction_terms
                    cex_model = FiniteModel(lemma_npsolution.model,
                                            extraction_terms,
                                            annctx=annctx)
                    cex_models = cex_models + [cex_model]
            else:
                if options.verbose >= 3:
                    print('proposed lemma was proven.')
                valid_lemmas.add(z3py_lemma)
                if options.streaming_synthesis_swtich:
                    # Check if lemma helps prove goal using originally configured goal solver object
                    # TODO: introduce warning or extend streaming algorithm to multiple lemma case
                    goal_npsolution = goal_fo_solver.solve(goal, {z3py_lemma})
                    if not goal_npsolution.if_sat:
                        # Lemma is useful. Wrap up processes and exit.
                        print(
                            'Goal has been proven. Lemmas used to prove goal:')
                        for lem in valid_lemmas:
                            print(lem[1])
                        if options.analytics:
                            # Update analytics entries for the current lemma before exiting.
                            total_time = config_params['analytics'][
                                'time_charged'] + config_params['analytics'][
                                    'lemma_time']
                            if options.verbose >= 0:
                                print('Total lemmas proposed: ' +
                                      str(config_params['analytics']
                                          ['total_lemmas']))
                            if options.verbose >= 0:
                                print('Total time charged: ' +
                                      str(total_time) + 's')
                        # Close the stream
                        try:
                            sygus_results.throw(StopProposal)
                        except StopIteration:
                            pass
                        exit(0)
                    else:
                        # Lemma does not help prove goal. Try the next lemma in the stream.
                        continue
                else:
                    # Not streaming mode: reset countermodels and invalid lemmas to [] and try synthesis again.
                    # We have additional information to retry the proofs.
                    cex_models = []
                    invalid_lemmas = []
                    break

        # Update countermodels before next round of synthesis
        config_params['cex_models'] = cex_models
        # reset everything and increase streaming timeout if streaming mode is on
        if options.streaming_synthesis_swtich:
            # Compute the timeout of the next streaming call
            config_params['streaming_timeout'] *= 2
            # Make each streaming call 15 minutes flat
            if config_params['streaming_timeout'] >= 15 * 60:
                # The following round of streaming will be too expensive. Exit.
                exit('Timeout reached. Exiting')
            if options.analytics:
                config_params['analytics']['time_charged'] = 0
                config_params['analytics']['total_lemmas'] = 0
            valid_lemmas = set()
            invalid_lemmas = []
Esempio n. 5
0
    def solve(self, goal, lemmas=None):
        """
        Primary function of the NPSolver class. Attempts to prove the goal with respect to given lemmas and the theory
        defined by the AnnotatedContext in self.annctx.  
        :param goal: z3.BoolRef  
        :param lemmas: set of z3.BoolRef  
        :return: NPSolution  
        """
        z3.set_param('smt.random_seed', 0)
        z3.set_param('sat.random_seed', 0)
        # TODO: check that the given lemmas are legitimate bound formula instances with their formal parameters from
        #  the foreground sort.
        options = self.options
        # Make recursive definition unfoldings
        recdefs = get_recursive_definition(None,
                                           alldefs=True,
                                           annctx=self.annctx)
        recdef_unfoldings = make_recdef_unfoldings(recdefs)
        # Sometimes we don't need the unfoldings indexed by recdefs
        untagged_unfoldings = set(recdef_unfoldings.values())
        # Add them to the set of axioms and lemmas to instantiate
        axioms = get_all_axioms(self.annctx)
        if lemmas is None:
            lemmas = set()
        else:
            # Check that each bound parameter in all the lemmas are of the foreground sort
            for lemma in lemmas:
                bound_vars, lemma_body = lemma
                if not all(
                        is_expr_fg_sort(bound_var, annctx=self.annctx)
                        for bound_var in bound_vars):
                    raise TypeError(
                        'Bound variables of lemma: {} must be of the foreground sort'
                        .format(lemma_body))
        recdef_indexed_lemmas = _sort_by_trigger(
            lemmas, list(recdef_unfoldings.keys()))

        if options.instantiation_mode == proveroptions.lean_instantiation_with_lemmas:
            # Recdefs and lemmas need to be treated separately using 'lean' instantiation
            fo_abstractions = axioms
        if options.instantiation_mode == proveroptions.lean_instantiation:
            # Recdefs need to be treated separately using 'lean' instantiation
            fo_abstractions = axioms | lemmas
        else:
            # If the instantiation isn't the 'lean' kind then all defs are going to be instantiated with all terms
            fo_abstractions = axioms | untagged_unfoldings | lemmas

        # All parameters have been set appropriately. Begin constructing instantiations
        # Negate the goal
        neg_goal = z3.Not(goal)
        # Create a solver object and add the goal negation to it
        z3solver = z3.Solver()
        z3solver.add(neg_goal)
        # Keep track of terms in the quantifier-free problem given to the solver
        initial_terms = get_foreground_terms(neg_goal, annctx=self.annctx)
        extraction_terms = initial_terms
        recdef_application_terms = get_recdef_applications(neg_goal,
                                                           annctx=self.annctx)
        instantiation_terms = set()
        # Instantiate and check for provability according to options
        # Handle manual instantiation mode first
        if options.instantiation_mode == proveroptions.manual_instantiation:
            terms_to_instantiate = options.terms_to_instantiate
            instantiations = instantiate(fo_abstractions, terms_to_instantiate)
            if instantiations != set():
                instantiation_terms = terms_to_instantiate
                extraction_terms = extraction_terms.union(
                    get_foreground_terms(instantiations, annctx=self.annctx))
            z3solver.add(instantiations)
            if_sat = _solver_check(z3solver)
            model = z3solver.model() if if_sat else None
            return NPSolution(if_sat=if_sat,
                              model=model,
                              extraction_terms=extraction_terms,
                              instantiation_terms=instantiation_terms,
                              options=options)
        # Automatic instantiation modes
        # stratified instantiation strategy
        if options.instantiation_mode == proveroptions.depth_one_stratified_instantiation:
            conservative_fo_abstractions = axioms | untagged_unfoldings
            tracked_instantiations = instantiate(conservative_fo_abstractions,
                                                 initial_terms)
            if tracked_instantiations != set():
                instantiation_terms = initial_terms
                tracked_terms = get_foreground_terms(tracked_instantiations,
                                                     annctx=self.annctx)
                extraction_terms = extraction_terms.union(tracked_terms)
            z3solver.add(tracked_instantiations)
            untracked_instantiations = instantiate(lemmas, extraction_terms)
            if untracked_instantiations != set():
                instantiation_terms = instantiation_terms.union(
                    extraction_terms)
                untracked_terms = get_foreground_terms(
                    untracked_instantiations, annctx=self.annctx)
                extraction_terms = extraction_terms.union(untracked_terms)
            other_instantiations = instantiate(conservative_fo_abstractions,
                                               extraction_terms)
            z3solver.add(untracked_instantiations)
            z3solver.add(other_instantiations)
            if_sat = _solver_check(z3solver)
            model = z3solver.model() if if_sat else None
            return NPSolution(if_sat=if_sat,
                              model=model,
                              extraction_terms=extraction_terms,
                              instantiation_terms=instantiation_terms,
                              options=options)
        # Set up initial values of variables
        depth_counter = 0
        # Keep track of formulae produced by instantiation
        instantiations = set()
        # When the instantiation mode is infinite we realistically can't exceed 10^3 instantiations anyway
        target_depth = 1000 if options.instantiation_mode == proveroptions.infinite_depth else options.depth
        while depth_counter < target_depth:
            # Try to prove with available instantiations
            z3solver.add(instantiations)
            # If the instantiation mode is fixed depth we can continue instantiating until we get to that depth
            if options.instantiation_mode != proveroptions.fixed_depth:
                # Otherwise check satisfiability with current state of instantiations
                if_sat = _solver_check(z3solver)
                # If unsat, stop and return NPSolution instance
                if not if_sat:
                    return NPSolution(if_sat=if_sat,
                                      model=None,
                                      extraction_terms=extraction_terms,
                                      instantiation_terms=instantiation_terms,
                                      depth=depth_counter,
                                      options=options)
            # target depth not reached or unsat not reached
            # Do another round of instantiations.
            # TODO: optimise instantiations so repeated instantiation is not done. Currently all instantiations
            #  are done in every round. But optimisation is difficult in the presence of multiple arities.
            instantiation_terms = extraction_terms
            # Instantiate all basic abstractions
            instantiations = instantiate(fo_abstractions, instantiation_terms)
            # Instantiate other abstractions depending on instantiation mode. Typically recdefs and lemmas
            if options.instantiation_mode in {
                    proveroptions.lean_instantiation,
                    proveroptions.lean_instantiation_with_lemmas
            }:
                # Add recursive definition instantiations to the set of all instantiations
                for recdef, application_terms in recdef_application_terms.items(
                ):
                    lean_instantiations = instantiate(
                        recdef_unfoldings[recdef], application_terms)
                    instantiations.update(lean_instantiations)
            if options.instantiation_mode == proveroptions.lean_instantiation_with_lemmas:
                # Add lemma instantiations to the set of all instantiations
                for recdef, application_terms in recdef_application_terms.items(
                ):
                    triggered_lemmas = recdef_indexed_lemmas.get(recdef, [])
                    triggered_instantiations = instantiate(
                        triggered_lemmas, application_terms)
                    instantiations.update(triggered_instantiations)
            # If the set of instantiations is empty exit the loop
            if instantiations == set():
                instantiation_terms = set()
                break
            # Update the variables for the next round
            depth_counter = depth_counter + 1
            new_terms = get_foreground_terms(instantiations,
                                             annctx=self.annctx)
            recdef_application_terms = get_recdef_applications(
                instantiations, annctx=self.annctx)
            extraction_terms = extraction_terms.union(new_terms)
        # Reach this case when depth_counter = target depth, either in fixed_depth or bounded_depth mode.
        # Final attempt at proving goal
        z3solver.add(instantiations)
        if_sat = _solver_check(z3solver)
        model = z3solver.model() if if_sat else None
        return NPSolution(if_sat=if_sat,
                          model=model,
                          extraction_terms=extraction_terms,
                          instantiation_terms=instantiation_terms,
                          depth=depth_counter,
                          options=options)