def rw_using_statement(goal: Goal, selected_objects: [MathObject], statement) -> CodeForLean: """ Return codes trying to use statement for rewriting. This should be reserved to iff or equalities. This function is called by action_definition, and by action_theorem in case the theorem is an iff statement. """ codes = CodeForLean.empty_code() defi = statement.lean_name if len(selected_objects) == 0: target_msg = _('definition applied to target') \ if statement.is_definition() else _('theorem applied to target') \ if statement.is_theorem() else _('exercise applied to target') codes = codes.or_else(f'rw {defi}') codes = codes.or_else(f'simp_rw {defi}') codes = codes.or_else(f'rw <- {defi}') codes = codes.or_else(f'simp_rw <- {defi}') codes.add_success_msg(target_msg) else: names = [item.info['name'] for item in selected_objects] arguments = ' '.join(names) context_msg = _('definition applied to') \ if statement.is_definition() else _('theorem applied to') \ if statement.is_theorem() else _('exercise applied to') context_msg += ' ' + arguments codes = codes.or_else(f'rw {defi} at {arguments}') codes = codes.or_else(f'simp_rw {defi} at {arguments}') codes = codes.or_else(f'rw <- {defi} at {arguments}') codes = codes.or_else(f'simp_rw <- {defi} at {arguments}') codes.add_success_msg(context_msg) return codes
def solve_target(prop): """ Try to solve 'prop' as a target, without splitting conjunctions. """ code = CodeForLean.empty_code() # if len(l) == 0: code = code.or_else('assumption') code = code.or_else('contradiction') # (1) Equality, inequality, iff if prop.is_equality() or prop.is_iff(): code = code.or_else(solve_equality(prop)) elif prop.is_inequality(): # try to permute members of the goal equality code = code.or_else('apply ne.symm, assumption') # (2) Try some symmetry rules if prop.is_iff(): code = code.or_else('apply iff.symm, assumption') elif prop.is_and(): # todo: change to "id_and_like" code = code.or_else('apply and.symm, assumption') # The following will be tried only after context splitting: # code = code.or_else('split, assumption, assumption') elif prop.is_or(): code = code.or_else('apply or.symm, assumption') # The following is too much? code = code.or_else('left, assumption') code = code.or_else('right, assumption') 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 split_conjunctions_in_context(proof_step): goal = proof_step.goal code = CodeForLean.empty_code() counter = 0 for prop in goal.context: if prop.is_and(): name = prop.info['name'] h0 = f"H_aux_{counter}" # these hypotheses will disappear h1 = f"H_aux_{counter + 1}" # so names are unimportant code = code.and_then(f"cases {name} with {h0} {h1}") counter += 2 return code
def solve_equality(prop: MathObject) -> CodeForLean: """ Assuming prop is an equality or an iff, return a list of tactics trying to solve prop as a target. """ code = CodeForLean.empty_code() if prop.math_type.children[0] == prop.math_type.children[1]: code = code.or_else('refl') # try to use associativity and commutativity code = code.or_else('ac_reflexivity') # try to permute members of the goal equality code = code.or_else('apply eq.symm, assumption') # congruence closure, solves e.g. (a=b, b=c : f a = f c) code = code.or_else('cc') return code
def __init__(self, nursery): super().__init__() self.log = logging.getLogger("ServerInterface") # Lean environment self.lean_env: LeanEnvironment = LeanEnvironment(inst) # Lean attributes self.lean_file: LeanFile = None self.lean_server: LeanServer = LeanServer(nursery, self.lean_env) self.nursery: trio.Nursery = nursery # Set server callbacks self.lean_server.on_message_callback = self.__on_lean_message self.lean_server.running_monitor.on_state_change_callback = \ self.__on_lean_state_change # Current exercise self.exercise_current = None # Current proof state + Events self.file_invalidated = trio.Event() self.__proof_state_valid = trio.Event() # __proof_receive_done is set when enough information have been # received, i.e. either we have context and target and all effective # codes, OR an error message. self.__proof_receive_done = trio.Event() # self.__proof_receive_error = trio.Event() # Set if request # failed self.__tmp_hypo_analysis = "" self.__tmp_targets_analysis = "" # When some CodeForLean iss sent to the __update method, it will be # duplicated and stored in __tmp_effective_code. This attribute will # be progressively modified into an effective code which is devoid # of or_else combinator, according to the "EFFECTIVE CODE" messages # sent by Lean. self.__tmp_effective_code = CodeForLean.empty_code() self.proof_state = None # Errors memory channels self.error_send, self.error_recv = \ trio.open_memory_channel(max_buffer_size=1024)
def apply_function(proof_step, selected_objects: [MathObject]): """ Apply l[-1], which is assumed to be a function f, to previous elements of l, which can be: - an equality - an object x (then create the new object f(x) ) l should have length at least 2 """ log.debug('Applying function') codes = CodeForLean.empty_code() # let us check the input is indeed a function function = selected_objects[-1] # if function.math_type.node != "FUNCTION": # raise WrongUserInput f = function.info["name"] Y = selected_objects[-1].math_type.children[1] while len(selected_objects) != 1: new_h = get_new_hyp(proof_step) # if function applied to a property, presumed to be an equality if selected_objects[0].math_type.is_prop(): h = selected_objects[0].info["name"] codes = codes.or_else(f'have {new_h} := congr_arg {f} {h}') codes.add_success_msg(_("function {} applied to {}").format(f, h)) # if function applied to element x: # create new element y and new equality y=f(x) else: x = selected_objects[0].info["name"] y = give_global_name(proof_step =proof_step, math_type=Y, hints=[Y.info["name"].lower()]) msg = _("new objet {} added to the context").format(y) codes = codes.or_else(f'set {y} := {f} {x} with {new_h}', success_msg=msg) selected_objects = selected_objects[1:] return codes
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 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 action_apply(proof_step, selected_objects: [MathObject], user_input: [str] = [], target_selected: bool = True): """ Translate into string of lean code corresponding to the action Function explain_how_to_apply should reflect the actions Apply last selected item on the other selected test for last selected item l[-1], and call functions accordingly: - apply_function, if item is a function - apply_susbtitute, if item can_be_used_for_substitution ONLY if the option expanded_apply_button is set: - apply_implicate, if item is an implication or a universal property (or call action_forall if l[-1] is a universal property and none of the other actions apply) - apply_exists, if item is an existential property - apply_and - apply_or ... """ # fixme: rewrite to provide meaningful error msgs if not selected_objects: raise WrongUserInput(error=_("no property selected")) # Now len(l) > 0 prop = selected_objects[-1] # property to be applied # (1) If user wants to apply a function # (note this is exclusive of other types of applications) if prop.is_function(): if len(selected_objects) == 1: # TODO: ask user for element on which to apply the function # (plus new name, cf apply_forall) error = _("select an element or an equality on which to " "apply the function") raise WrongUserInput(error=error) else: return apply_function(proof_step, selected_objects) codes = CodeForLean.empty_code() error = "" # (2) If rewriting is possible test, equality = prop.can_be_used_for_substitution() if test: codes = codes.or_else(apply_substitute(proof_step, selected_objects, user_input, equality)) expanded_apply_button = cvars.get('expanded_apply_button', False) if expanded_apply_button: # What follows applies only if expanded_apply_button # (4) Other easy applications if len(selected_objects) == 1 and user_can_apply(selected_objects[0]): if prop.is_exists(): codes = codes.or_else(apply_exists(proof_step, selected_objects)) if prop.is_and(): codes = codes.or_else(apply_and(proof_step, selected_objects)) if prop.is_or(): codes = codes.or_else(apply_or(proof_step, selected_objects, user_input)) if not codes.is_empty(): return codes else: error = _("I cannot apply this") # fixme: be more precise raise WrongUserInput(error)
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 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)
async def __update(self, lean_code=None): """ Call Lean server to update the proof_state. """ first_line_of_change = self.lean_file.first_line_of_last_change self.log.debug(f"Updating, checking errors from line " f"{first_line_of_change}, and context at " f"line {self.lean_file.last_line_of_inner_content + 1}") if lean_code: self.__tmp_effective_code = deepcopy(lean_code) else: self.__tmp_effective_code = CodeForLean.empty_code() self.lean_file_changed.emit() # will update the lean text editor if hasattr(self.update_started, "emit"): self.update_started.emit() # Invalidate events self.file_invalidated = trio.Event() self.__proof_receive_done = trio.Event() self.__tmp_hypo_analysis = "" self.__tmp_targets_analysis = "" resp = None # Loop in case Lean's answer is None, which happens... while not resp: req = SyncRequest(file_name="deaduction_lean", content=self.lean_file.contents) resp = await self.lean_server.send(req) if resp.message == "file invalidated": self.file_invalidated.set() ######################################### # Waiting for all pieces of information # ######################################### await self.__proof_receive_done.wait() self.log.debug(_("Proof State received")) # Next line removed by FLR # await self.lean_server.running_monitor.wait_ready() self.log.debug(_("After request")) # If data for new proof state have been received if not self.__proof_state_valid.is_set(): # Construct new proof state self.proof_state = ProofState.from_lean_data( self.__tmp_hypo_analysis, self.__tmp_targets_analysis) # Store proof_state for history self.log.debug("storing ProofState") self.lean_file.state_info_attach(ProofState=self.proof_state) self.__proof_state_valid.set() # Emit signal only if from qt context (avoid AttributeError) if hasattr(self.proof_state_change, "emit"): self.proof_state_change.emit(self.proof_state) if hasattr(self.update_ended, "emit"): self.update_ended.emit() # Emit exceptions ? error_list = [] try: while True: error_list.append(self.error_recv.receive_nowait()) except trio.WouldBlock: pass if error_list: raise exceptions.FailedRequestError(error_list, lean_code)