示例#1
0
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
示例#2
0
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
示例#3
0
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
示例#4
0
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
示例#5
0
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
示例#6
0
    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)
示例#7
0
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
示例#8
0
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
示例#9
0
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
示例#10
0
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
示例#11
0
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)
示例#12
0
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
示例#13
0
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)
示例#14
0
    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)