def action_negate(proof_step, selected_objects: [MathObject], target_selected: bool = True) -> CodeForLean: """ Translate into string of lean code corresponding to the action If no hypothesis has been previously selected: transform the target in an equivalent one with its negations 'pushed'. If a hypothesis has been previously selected: do the same to the hypothesis. """ test_selection(selected_objects, target_selected) goal = proof_step.goal if len(selected_objects) == 0: if not goal.target.is_not(): raise WrongUserInput(error=_("Target is not a negation 'NOT P'")) code = CodeForLean.from_string('push_neg') code.add_success_msg(_("Negation pushed on target")) elif len(selected_objects) == 1: if not selected_objects[0].is_not(): error = _("Selected property is not a negation 'NOT P'") raise WrongUserInput(error) selected_hypo = selected_objects[0].info["name"] code = CodeForLean.from_string(f'push_neg at {selected_hypo}') code.add_success_msg( _(f"Negation pushed on property " f"{selected_hypo}")) else: raise WrongUserInput(error=_('Only one property at a time')) return code
def construct_or(proof_step, user_input: [str]) -> CodeForLean: """ Assuming target is a disjunction 'P OR Q', choose to prove either P or Q. """ target = proof_step.goal.target left = target.math_type.children[0].to_display() right = target.math_type.children[1].to_display() choices = [(_("Left"), left), (_("Right"), right)] if not user_input: raise MissingParametersError(InputType.Choice, choices, title=_("Choose new goal"), output=_("Which property will you " "prove?")) code = None if len(user_input) == 1: i = user_input[0] if i == 0: code = CodeForLean.from_string("left") code.add_success_msg(_("Target replaced by the left alternative")) else: code = CodeForLean.from_string("right") code.add_success_msg(_("Target replaced by the right alternative")) return code
def action_compute(proof_step, selected_objects, target_selected: bool = True): """ If the target is an equality, an inequality (or 'False'), then send tactics trying to prove the goal by (mainly linearly) computing. """ goal = proof_step.goal target = goal.target if not (target.is_equality() or target.is_inequality() or target.is_false()): error = _("target is not an equality, an inequality, " "nor a contradiction") raise WrongUserInput(error) # try_before = "try {apply div_pos}, " \ # + "try { all_goals {norm_num at *}}" \ # + ", " # simplify_1 = "simp only " \ # + "[*, ne.symm, ne.def, not_false_iff, lt_of_le_of_ne]" # possible_code = [try_before + "linarith", # try_before + simplify_1] # "finish" "norm_num *" # if user_config.getboolean('use_library_search_for_computations'): # possible_code.append('library_search') code1 = CodeForLean.from_string("norm_num at *").solve1() code2 = CodeForLean.from_string("compute_n 10") code3 = CodeForLean.from_string("norm_num at *").try_().and_then(code2) possible_code = code1.or_else(code3) #possible_code = [solve1_wrap("norm_num at *"), # solve1_wrap("try {norm_num at *}, compute_n 10")] return possible_code
def action_assumption(proof_step, selected_objects: [MathObject], target_selected: bool = True) -> CodeForLean: """ Translate into string of lean code corresponding to the action. """ goal = proof_step.goal # (1) First trials target = goal.target improved_assumption = solve_target(target) codes = [improved_assumption] # (2) Use user selection if len(selected_objects) == 1: apply_user = CodeForLean.from_string( f'apply {selected_objects[0].info["name"]}') codes.append(apply_user.solve1()) # (3) Conjunctions: try to split hypotheses once # TODO: recursive splitting in hypo # And then retry many things (in improved_assumption_2). split_conj = split_conjunctions_in_context(proof_step) improved_assumption_2 = solve_target(target) # Split target if target.is_and(): # TODO: recursive splitting in target, and_then for each subgoal # apply improved_assumption split_code = CodeForLean.from_string('split, assumption, assumption') improved_assumption_2 = improved_assumption_2.or_else(split_code) # Try norm_num if (goal.target.is_equality() or goal.target.is_inequality() or goal.target.is_false()): # Do not remove above test, since norm_num may solve some # not-so-trivial goal, e.g. implications norm_num_code = CodeForLean.from_string('norm_num at *').solve1() improved_assumption_2 = improved_assumption_2.or_else(norm_num_code) # Try specific properties more_assumptions = search_specific_prop(proof_step) if not more_assumptions.is_empty(): improved_assumption_2 = improved_assumption_2.or_else(more_assumptions) more_code = split_conj.and_then(improved_assumption_2) codes.append(more_code) code = CodeForLean.or_else_from_list(codes) code = code.solve1() code.add_error_msg(_("I don't know how to conclude")) return code
def apply_implicate(proof_step, selected_object: [MathObject]) -> CodeForLean: """ Here selected_object contains a single property which is an implication P ⇒ Q; if the target is Q then it will be replaced by P. """ selected_hypo = selected_object[0].info["name"] code = CodeForLean.from_string(f'apply {selected_hypo}') code.add_success_msg( _("Target modified using implication {}").format(selected_hypo)) return code
def method_absurdum(proof_step, selected_objects: [MathObject]) -> CodeForLean: """ If no selection, engage in a proof by contradiction. """ if len(selected_objects) == 0: new_hypo = get_new_hyp(proof_step) code = CodeForLean.from_string(f'by_contradiction {new_hypo}') code.add_success_msg(_("Negation of target added to the context")) return code else: error = _('Proof by contradiction only applies to target') raise WrongUserInput(error)
def construct_implicate(proof_step) -> CodeForLean: """ Here the target is assumed to be an implication P ⇒ Q, P is added to the context, and the target becomes Q. """ if not proof_step.goal.target.is_implication(): raise WrongUserInput(error=_("Target is not an implication 'P ⇒ Q'")) else: new_hypo_name = get_new_hyp(proof_step) code = CodeForLean.from_string(f'intro {new_hypo_name}') code.add_success_msg( _("Property {} added to the context").format(new_hypo_name)) return code
def construct_forall(proof_step) -> CodeForLean: """ Here goal.target is assumed to be a universal property "∀ x:X, ...", and variable x is introduced. """ goal = proof_step.goal math_object = goal.target.math_type math_type = math_object.children[0] variable = math_object.children[1] body = math_object.children[2] hint = variable.display_name # not optimal if math_type.node == "PRODUCT": [math_type_1, math_type_2] = math_type.children x = give_global_name(proof_step=proof_step, math_type=math_type_1) y = give_global_name(proof_step=proof_step, math_type=math_type_2) possible_codes = CodeForLean.from_string(f'rintro ⟨ {x}, {y} ⟩') name = f"({x},{y})" else: x = give_global_name(proof_step=proof_step, math_type=math_type, hints=[hint]) possible_codes = CodeForLean.from_string(f'intro {x}') name = f"{x}" possible_codes.add_success_msg( _("Object {} added to the context").format(name)) if body.is_implication(is_math_type=True): # If math_object has the form # ∀ x:X, (x R ... ==> ...) # where R is some inequality relation # then introduce the inequality on top of x premise = body.children[0] # children (2,0) if premise.is_inequality(is_math_type=True): h = get_new_hyp(proof_step) # Add and_then intro h possible_codes = possible_codes.and_then(f'intro {h}') return possible_codes
def apply_and(proof_step, selected_objects) -> CodeForLean: """ Destruct a property 'P and Q'. Here selected_objects is assumed to contain exactly one conjunction property. """ selected_hypo = selected_objects[0].info["name"] h1 = get_new_hyp(proof_step) h2 = get_new_hyp(proof_step) code = CodeForLean.from_string(f'cases {selected_hypo} with {h1} {h2}') code.add_success_msg( _("Split property {} into {} and {}").format(selected_hypo, h1, h2)) return code
def construct_and_hyp(proof_step, selected_objects: [MathObject]) \ -> CodeForLean: """ Construct 'P AND Q' from properties P and Q. Here selected_objects is assumed to contain exactly two properties. """ h1 = selected_objects[0].info["name"] h2 = selected_objects[1].info["name"] new_hypo_name = get_new_hyp(proof_step) code = f'have {new_hypo_name} := and.intro {h1} {h2}' code = CodeForLean.from_string(code) code.add_success_msg( _("Conjunction {} added to the context").format(new_hypo_name)) return code
def construct_iff_on_hyp(proof_step, selected_objects: [MathObject]) -> CodeForLean: """ Construct property 'P iff Q' from both implications. len(selected_objects) should be 2. """ new_hypo_name = get_new_hyp(proof_step) h1 = selected_objects[0].info["name"] h2 = selected_objects[1].info["name"] code_string = f'have {new_hypo_name} := iff.intro {h1} {h2}' code = CodeForLean.from_string(code_string) code.add_success_msg( _("Logical equivalence {} added to the context").format(new_hypo_name)) return code
def construct_exists(proof_step, user_input: [str]) -> CodeForLean: """ Assuming the target is an existential property '∃ x, P(x)', prove it by providing a witness x and proving P(x). """ if not user_input: raise MissingParametersError( InputType.Text, title=_("Exist"), output=_("Enter element you want to use:")) x = user_input[0] code = CodeForLean.from_string(f'use {x}, dsimp') code = code.or_else(f'use {x}') code.add_success_msg(_("now prove {} suits our needs").format(x)) return code
def destruct_iff(proof_step) -> CodeForLean: """ Check if target is a conjunction of two implications (but not if these implications are logical inverses). If so, return code that builds the equivalent iff statement. If not, return None. """ goal = proof_step.goal code = None target = goal.target if target.is_and(): left = target.math_type.children[0] right = target.math_type.children[1] if left.is_implication(is_math_type=True) \ and right.is_implication(is_math_type=True): code = CodeForLean.from_string("apply iff_def.mp") code.add_success_msg(_("target replaced by iff property")) return code
def search_specific_prop(proof_step): """Search context for some specific properties to conclude the proof.""" goal = proof_step.goal more_code = CodeForLean.empty_code() for prop in goal.context: math_type = prop.math_type # Conclude from "x in empty set" if (math_type.node == "PROP_BELONGS" and math_type.children[1].node == "SET_EMPTY"): hypo = prop.info['name'] if goal.target.node != "PROP_FALSE": more_code = CodeForLean.from_string("exfalso") else: more_code = CodeForLean.empty_code() more_code = more_code.and_then(f"exact set.not_mem_empty _ {hypo}") return more_code
def method_contrapose(proof_step, selected_objects: [MathObject]) -> CodeForLean: """ If target is an implication, turn it to its contrapose. """ goal = proof_step.goal if len(selected_objects) == 0: if goal.target.math_type.node == "PROP_IMPLIES": code = CodeForLean.from_string("contrapose") code.add_success_msg(_("Target replaced by contrapositive")) return code else: error = _('Proof by contraposition only applies when target is ' 'an implication') else: error = _('Proof by contraposition only applies to target') raise WrongUserInput(error)
def introduce_fun(proof_step, selected_objects: [MathObject]) -> CodeForLean: """ If a hypothesis of form ∀ a ∈ A, ∃ b ∈ B, P(a,b) has been previously selected: use the axiom of choice to introduce a new function f : A → B and add ∀ a ∈ A, P(a, f(a)) to the properties. """ goal = proof_step.goal error = _('select a property "∀ x, ∃ y, P(x,y)" to get a function') success = _("function {} and property {} added to the context") if len(selected_objects) == 1: h = selected_objects[0].info["name"] # Finding expected math_type for the new function universal_property = selected_objects[0] if universal_property.is_for_all(): source_type = universal_property.math_type.children[0] exist_property = universal_property.math_type.children[2] if exist_property.is_exists(is_math_type=True): target_type = exist_property.children[0] math_type = MathObject(node="FUNCTION", info={}, children=[source_type, target_type], math_type=NO_MATH_TYPE) hf = get_new_hyp(proof_step) f = give_global_name(math_type=math_type, proof_step=proof_step) code = CodeForLean.from_string(f'cases ' f'classical.axiom_of_choice ' f'{h} with {f} {hf}, ' f'dsimp at {hf}, ' f'dsimp at {f}') code.add_error_msg(error) success = success.format(f, hf) code.add_success_msg(success) return code raise WrongUserInput(error)
def construct_iff(proof_step, user_input: [str]) -> CodeForLean: """ Assuming target is an iff, split into two implications. """ target = proof_step.goal.target.math_type code = CodeForLean.empty_code() left = target.children[0] right = target.children[1] if not user_input: choices = [("⇒", f'({left.to_display()}) ⇒ ({right.to_display()})'), ("⇐", f'({right.to_display()}) ⇒ ({left.to_display()})')] raise MissingParametersError( InputType.Choice, choices, title=_("Choose sub-goal"), output=_("Which implication to prove first?")) elif len(user_input) == 1: if user_input[0] == 1: code = CodeForLean.from_string("rw iff.comm") left, right = right, left code = code.and_then("split") else: raise WrongUserInput(error=_("Undocumented error")) code.add_success_msg(_("Iff split in two implications")) impl1 = MathObject(info={}, node="PROP_IMPLIES", children=[left, right], math_type="PROP") impl2 = MathObject(info={}, node="PROP_IMPLIES", children=[right, left], math_type="PROP") code.add_conjunction(target, impl1, impl2) return code
def apply_or(proof_step, selected_objects: [MathObject], user_input: [str]) -> CodeForLean: """ Assuming selected_objects is one disjunction 'P OR Q', engage in a proof by cases. """ # if not selected_objects[0].is_or(): # raise WrongUserInput(error=_("Selected property is not " # "a disjunction 'P OR Q'")) selected_hypo = selected_objects[0] code = CodeForLean.empty_code() left = selected_hypo.math_type.children[0] right = selected_hypo.math_type.children[1] if not user_input: choices = [(_("Left"), left.to_display()), (_("Right"), right.to_display())] raise MissingParametersError(InputType.Choice, choices=choices, title=_("Choose case"), output=_("Which case to assume first?")) else: # len(user_input) == 1 if user_input[0] == 1: # If user wants the second property first, then first permute code = f'rw or.comm at {selected_hypo.info["name"]}' code = CodeForLean.from_string(code) left, right = right, left h1 = get_new_hyp(proof_step) h2 = get_new_hyp(proof_step) # Destruct the disjunction code = code.and_then(f'cases {selected_hypo.info["name"]} with {h1} {h2}') code.add_success_msg(_("Proof by cases")) code.add_disjunction(selected_hypo, left, right) return code
def method_cbr(proof_step, selected_objects: [MathObject], user_input: [str] = []) -> CodeForLean: """ Engage in a proof by cases. - If selection is empty, then ask the user to choose a property - If a disjunction is selected, use this disjunction for the proof by cases _ If anything else is selected try to discuss on this property (useful only in propositional calculus?) In any case, ask the user which case she wants to prove first. """ if len(selected_objects) == 0: # NB: user_input[1] contains the needed property if len(user_input) == 1: raise MissingParametersError( InputType.Text, title=_("cases"), output=_("Enter the property you want to discriminate on:") ) else: h0 = user_input[1] h1 = get_new_hyp(proof_step) h2 = get_new_hyp(proof_step) code = CodeForLean.from_string(f"cases (classical.em ({h0})) " f"with {h1} {h2}") code.add_success_msg(_("Proof by cases")) code.add_disjunction(h0, h1, h2) # Strings, not MathObject else: prop = selected_objects[0] if not prop.is_or(): error = _("Selected property is not a disjunction") raise WrongUserInput(error) else: code = apply_or(proof_step, selected_objects, user_input) return code
def construct_exists_on_hyp(proof_step, selected_objects: [MathObject]) -> CodeForLean: """ Try to construct an existence property from some object and some property Here len(l) = 2 """ x = selected_objects[0].info["name"] hx = selected_objects[1].info["name"] if (not selected_objects[0].math_type.is_prop()) \ and selected_objects[1].math_type.is_prop(): new_hypo = get_new_hyp(proof_step) code_string = f'have {new_hypo} := exists.intro {x} {hx}' elif (not selected_objects[1].math_type.is_prop()) \ and selected_objects[0].math_type.is_prop(): x, hx = hx, x new_hypo = get_new_hyp(proof_step) code_string = f'have {new_hypo} := exists.intro {x} {hx}' else: error = _("I cannot build an existential property with this") raise WrongUserInput(error) code = CodeForLean.from_string(code_string) code.add_success_msg(_("get new existential property {}").format(new_hypo)) return code
def apply_exists(proof_step, selected_object: [MathObject]) -> CodeForLean: """ Apply a property '∃ x, P(x)' to get an x with property P(x). """ selected_hypo = selected_object[0].math_type hypo_name = selected_object[0].info["name"] x = give_global_name(proof_step=proof_step, math_type=selected_hypo.children[0], hints=[selected_hypo.children[1].to_display()]) new_hypo_name = get_new_hyp(proof_step) if selected_hypo.children[2].node == "PROP_∃": code = f'rcases {hypo_name} with ' \ f'⟨ {x}, ⟨ {new_hypo_name}, {hypo_name} ⟩ ⟩' else: code = f'cases {hypo_name} with {x} {new_hypo_name}' code = CodeForLean.from_string(code) if selected_hypo.node == 'QUANT_∃!': # We have to add the "simp" tactic to avoid appearance of lambda expr code = code.and_then(f'simp at {new_hypo_name}') code.add_success_msg( _("new object {} with property {}").format(x, new_hypo_name)) return code
def construct_and(proof_step, user_input: [str]) -> CodeForLean: """ Split the target 'P AND Q' into two sub-goals. """ target = proof_step.goal.target.math_type if not target.is_and(is_math_type=True): raise WrongUserInput(error=_("Target is not a conjunction 'P AND Q'")) left = target.children[0] right = target.children[1] if not user_input: # User choice choices = [(_("Left"), left.to_display()), (_("Right"), right.to_display())] raise MissingParametersError( InputType.Choice, choices, title=_("Choose sub-goal"), output=_("Which property to prove first?")) else: if user_input[0] == 1: # Prove second property first if target.node == "PROP_∃": # In this case, first rw target as a conjunction code = CodeForLean.and_then_from_list( ["rw exists_prop", "rw and.comm"]) else: code = CodeForLean.from_string("rw and.comm") left, right = right, left else: code = CodeForLean.empty_code() code = code.and_then("split") code.add_success_msg(_('Target split')) code.add_conjunction(target, left, right) return code
def apply_substitute(proof_step, l: [MathObject], user_input: [int], equality): """ Try to rewrite the goal or the first selected property using the last selected property. """ goal = proof_step.goal codes = CodeForLean.empty_code() heq = l[-1] left_term = equality.children[0] right_term = equality.children[1] success1 = ' ' + _("{} replaced by {}").format(left_term.to_display(), right_term.to_display()) + ' ' success2 = ' ' + _("{} replaced by {}").format(right_term.to_display(), left_term.to_display()) + ' ' choices = [(left_term.to_display(), f'Replace by {right_term.to_display()}'), (right_term.to_display(), f'Replace by {left_term.to_display()}')] if len(l) == 1: # If the user has chosen a direction, apply substitution # else if both directions make sense, ask the user for a choice # else try direct way or else reverse way. h = l[0].info["name"] if len(user_input) > 0: if user_input[0] == 1: success_msg = success2 + _("in target") more_code = CodeForLean.from_string(f'rw <- {h}', success_msg=success_msg) elif user_input[0] == 0: success_msg = success1 + _("in target") more_code = CodeForLean.from_string(f'rw {h}', success_msg=success_msg) codes = codes.or_else(more_code) else: if goal.target.math_type.contains(left_term) and \ goal.target.math_type.contains(right_term): raise MissingParametersError( InputType.Choice, choices, title=_("Precision of substitution"), output=_("Choose which expression you want to replace")) else: msg2 = success2 + _("in target") more_code2 = CodeForLean.from_string(f'rw <- {h}', success_msg=msg2) codes = codes.or_else(more_code2) msg1 = success1 + _("in target") more_code1 = CodeForLean.from_string(f'rw {h}', success_msg=msg1) codes = codes.or_else(more_code1) if len(l) == 2: h = l[0].info["name"] heq_name = l[-1].info["name"] if len(user_input) > 0: if user_input[0] == 1: success_msg = success2 + _("in {}").format(h) more_code = CodeForLean.from_string(f'rw <- {heq_name} at {h}', success_msg=success_msg) codes = codes.or_else(more_code) elif user_input[0] == 0: success_msg = success1 + _("in {}").format(h) more_code = CodeForLean.from_string(f'rw {heq_name} at {h}', success_msg=success_msg) codes = codes.or_else(more_code) else: if l[0].math_type.contains(left_term) and \ l[0].math_type.contains(right_term): raise MissingParametersError( InputType.Choice, choices, title=_("Precision of substitution"), output=_("Choose which expression you want to replace")) # h, heq_name = heq_name, h # codes = codes.or_else(f'rw <- {heq_name} at {h}') # codes = codes.or_else(f'rw {heq_name} at {h}') msg2 = success2 + _("in {}").format(h) more_code2 = CodeForLean.from_string(f'rw <- {heq_name} at {h}', success_msg=msg2) codes = codes.or_else(more_code2) msg1 = success1 + _("in {}").format(h) more_code1 = CodeForLean.from_string(f'rw {heq_name} at {h}', success_msg=msg1) codes = codes.or_else(more_code1) if heq.is_for_all(): # if property is, e.g. "∀n, u n = c" # there is a risk of meta vars if Lean does not know to which n # applying the equality codes = codes.and_then('no_meta_vars') return codes
def action_new_object(proof_step, selected_objects: [MathObject], user_input: [str] = None, target_selected: bool = True) -> CodeForLean: """ Introduce new object / sub-goal / function """ goal = proof_step.goal codes = [] # Choose between object/sub-goal/function if not user_input: raise MissingParametersError(InputType.Choice, [(_("Object"), _("Introduce a new object")), (_("Goal"), _("Introduce a new " "intermediate sub-goal")), (_("Function"), _("Introduce a new " "function"))], title=_("New object"), output=_("Choose what to introduce:")) # Choice = new object if user_input[0] == 0: if len(user_input) == 1: # Ask for name raise MissingParametersError(InputType.Text, title="+", output=_("Name your object:")) if len(user_input) == 2: # Ask for new object raise MissingParametersError(InputType.Text, title="+", output=_("Introduce a new object") + "(" + _("e.g.") + "0, {1}, f(2), ...)") else: # Send code name = user_input[1] new_hypo_name = get_new_hyp(proof_step) new_object = user_input[2] codes = CodeForLean.from_string(f"let {name} := {new_object}") codes = codes.and_then(f"have {new_hypo_name} : {name} = " f"{new_object}") codes = codes.and_then("refl") codes.add_success_msg(_("New object {} added to the context"). format(name)) if goal.target.is_for_all(): # User might want to prove a universal property "∀x..." # and mistake "new object" for introduction of the relevant x. codes.add_error_msg(_("You might try the ∀ button...")) # Choice = new sub-goal elif user_input[0] == 1: if len(user_input) == 1: raise MissingParametersError(InputType.Text, title="+", output=_("Introduce new subgoal:")) else: new_hypo_name = get_new_hyp(proof_step) codes = CodeForLean.from_string(f"have {new_hypo_name}:" f" ({user_input[1]})") codes.add_success_msg(_("New target will be added to the context " "after being proved")) codes.add_subgoal(user_input[1]) # Choice = new function elif user_input[0] == 2: return introduce_fun(proof_step, selected_objects) return codes
def method_sorry(proof_step, selected_objects: [MathObject]) -> CodeForLean: """ Close the current sub-goal by sending the 'sorry' code. """ return CodeForLean.from_string('sorry')
def apply_forall(proof_step, l: [MathObject]) -> CodeForLean: """ Try to apply last selected property on the other ones. The last property should be a universal property (or equivalent to such after unfolding definitions) :param l: list of MathObjects of length ≥ 2 :return: """ # FIXME: return error msg if user try to apply "forall x:X, P(x)" # to some object of wrong type (e.g. implication) # For the moment "forall x, P->Q" works with "P->Q" and button forall goal = proof_step.goal universal_property = l[-1] # The property to be applied unsolved_inequality_counter = 0 # Variable_names will contain the list of variables and proofs of # inequalities that will be passed to universal_property variable_names = [] code = CodeForLean.empty_code() for potential_var in l[:-1]: # TODO: replace by pattern matching # Check for "∀x>0" (and variations) inequality = inequality_from_pattern_matching(universal_property, potential_var) variable_names.append(potential_var.info['name']) if inequality: math_types = [p.math_type for p in goal.context] if inequality in math_types: index = math_types.index(inequality) inequality_name = goal.context[index].display_name variable_names.append(inequality_name) else: inequality_name = get_new_hyp(proof_step) variable_names.append(inequality_name) unsolved_inequality_counter += 1 # Add type indication to the variable in inequality math_type = inequality.children[1].math_type # Variable is not used explicitly, but this affects inequality: variable = inequality.children[0] variable = add_type_indication(variable, math_type) display_inequality = inequality.to_display(is_math_type=False, format_='lean') # Code I: state corresponding inequality # code = code.and_then(f"have {inequality_name}: " f"{display_inequality}") code = code.and_then("rotate") # Code II: Apply universal_property # new_hypo_name = get_new_hyp(proof_step) code = code.and_then( have_new_property(universal_property, variable_names, new_hypo_name)) # Code III: try to solve inequalities # e.g.: # iterate 2 { solve1 {try {norm_num at *}, try {compute_n 10}} <|> # rotate}, rotate, more_code = CodeForLean.empty_code() if unsolved_inequality_counter: code = code.and_then("rotate") # back to first inequality more_code1 = CodeForLean.from_string("norm_num at *") more_code1 = more_code1.try_() more_code2 = CodeForLean.from_string("compute_n 1") more_code2 = more_code2.try_() # Try to solve1 inequality by norm_num, maybe followed by compute: more_code = more_code1.and_then(more_code2) more_code = more_code.single_combinator("solve1") # If it fails, rotate to next inequality more_code = more_code.or_else("rotate") # Do this for all inequalities # more_code = more_code.single_combinator(f"iterate # {unsolved_inequality_counter}") --> replaced by explicit iteration code_list = [more_code] * unsolved_inequality_counter more_code = CodeForLean.and_then_from_list(code_list) # Finally come back to first inequality more_code = more_code.and_then("rotate") code.add_success_msg( _("Property {} added to the context").format(new_hypo_name)) return code.and_then(more_code)