Example #1
0
def add_type_indication(item: Union[str, MathObject],
                        math_type: MathObject=None) -> Union[str, MathObject]:
    """
    Add type indication for Lean. e.g.
    '0' -> (0:ℝ)
    'x' -> (x:ℝ)
    :param item:        either a string (provided by user in TextDialog) or
    MathObject
    :param math_type:   math_type indication to add. If None, largest number
    set used in current context will be indicated
    :return: either     string or MathObject, with type indication in name
    """
    if math_type:
        number_type = math_type.which_number_set(is_math_type=True)
    if isinstance(item, str):
        number_set = which_number_set(item)
        if number_set and ':' not in item:
            if not math_type:
                MathObject.add_numbers_set(number_set)
                # Add type indication = largest set of numbers among used
                number_type = MathObject.number_sets[-1]
            item = f"({item}:{number_type})"  # e.g. (0:ℝ)
        return item
    else:
        if not math_type:
            number_type = MathObject.number_sets[-1]
        if hasattr(item, 'info'):
            name = item.display_name
            # Do not put 2 type indications!!
            if (':' not in name
                    and hasattr(item, 'info')):
                item.info['name'] = f"({name}:{number_type})"
        return item
Example #2
0
    async def solve_exercise(self):
        """
        Launch exercise window, start lean server, and connect signals.
        """

        log.debug(f"Starting exercise {self.exercise.pretty_name}")

        # Stop Lean server if running
        if self.servint:
            await self.servint.file_invalidated.wait()
            self.servint.stop()
            log.info("Lean server stopped!")
            # ─────────────────── Most Important ─────────────────── #
            #  Re-initialisation of MathObject.Variables
            MathObject.clear()

        # Start Lean server
        self.servint = ServerInterface(self.nursery)
        await self.servint.start()
        log.info("Lean server started")

        # Start exercise window
        self.exercise_window = ExerciseMainWindow(self.exercise, self.servint)

        # Connect signals
        self.exercise_window.window_closed.connect(self.close_exercise_window)
        self.exercise_window.change_exercise.connect(self.choose_exercise)

        # Show window
        self.exercise_window.show()
    def __init__(self, mathobject: MathObject, tag: str = '='):
        """
        Init self with an instance of the class MathObject and a tag.
        See self.__doc__.

        :param mathobject: The MathObject one wants to display.
        :param tag: The tag of mathobject.
        """

        super().__init__()

        self.mathobject = mathobject
        self.tag = tag

        lean_name = mathobject.to_display()
        math_expr = mathobject.math_type.to_display(is_math_type=True)
        caption = f'{lean_name} : {math_expr}'
        self.setText(caption)
        self.setIcon(_TagIcon(tag))
        # set tool_tips (merge several tool_tips if needed)
        tool_tips = explain_how_to_apply(mathobject)
        # log.debug(f"Setting tooltips {tool_tips}")
        if len(tool_tips) == 1:
            tool_tip = _("Double click to") + " " + tool_tips[0]
            self.setToolTip(tool_tip)
        elif len(tool_tips) > 1:
            text = _("Double click to:")
            for tool_tip in tool_tips:
                text += "\n" + "• " + tool_tip
            self.setToolTip(text)
Example #4
0
def action_forall(proof_step,
                  selected_objects: [MathObject],
                  user_input: [str] = [],
                  target_selected: bool = True) -> CodeForLean:
    """
    (1) If no selection and target is of the form ∀ x, P(x):
        introduce x and transform the target into P(x)
    (2) If a single universal property is selected, ask user for an object
        to which the property will be applied
    (3) If 2 or more items are selected, one of which is a universal
        property, try to apply it to the other selected items

    """

    test_selection(selected_objects, target_selected)
    goal = proof_step.goal

    if len(selected_objects) == 0:
        if not goal.target.is_for_all():
            error = _("target is not a universal property '∀x, P(x)'")
            raise WrongUserInput(error)
        else:
            return construct_forall(proof_step)

    elif len(selected_objects) == 1:  # Ask user for item
        if not selected_objects[0].is_for_all():
            error = _("selected property is not a universal property '∀x, "
                      "P(x)'")
            raise WrongUserInput(error)
        elif not user_input:
            raise MissingParametersError(InputType.Text,
                                         title=_("Apply a universal property"),
                                         output=_("Enter element on which you "
                                                  "want to apply:"))
        else:
            item = user_input[0]
            item = add_type_indication(item)  # e.g. (0:ℝ)
            if item[0] != '(':
                item = '(' + item + ')'
            potential_var = MathObject(node="LOCAL_CONSTANT",
                                       info={'name': item},
                                       children=[],
                                       math_type=None)
            selected_objects.insert(0, potential_var)
            # Now len(l) == 2
    # From now on len(l) ≥ 2
    # Search for a universal property among l, beginning with last item
    selected_objects.reverse()
    for item in selected_objects:
        if item.is_for_all():
            # Put item on last position
            selected_objects.remove(item)
            selected_objects.reverse()
            selected_objects.append(item)
            return apply_forall(proof_step, selected_objects)
    raise WrongUserInput(error=_("no universal property among selected"))
Example #5
0
 def visit_expr(self, node, visited_children):
     """
     Create MathObject from children
     (node, children, math_type if any, NO representation)
     """
     node_info = visited_children[0][1]
     children = concatenate(visited_children[1:])[0]
     math_object = MathObject.from_info_and_children(info=node_info,
                                                     children=children)
     return [math_object], {}
Example #6
0
def inequality_from_pattern_matching(math_object: MathObject,
                                     variable: MathObject) -> MathObject:
    """
    Check if math_object.math_type has the form
    ∀ x:X, (x R ... ==> ...)
    where R is some inequality relation, and if this statement may be
    applied to variable. If so, return inequality with x replaced by variable
    """
    inequality = None
    if math_object.is_for_all():
        math_type, var, body = math_object.math_type.children
        # NB: following line does not work because of coercions
        # if var.math_type == variable.math_type:
        if body.is_implication(is_math_type=True):
            premise = body.children[0]  # children (2,0)
            if (premise.is_inequality(is_math_type=True)
                    and var == premise.children[0]):
                children = [variable, premise.children[1]]
                inequality = MathObject(node=premise.node,
                                        info={},
                                        children=children,
                                        math_type=premise.math_type)
    return inequality
Example #7
0
 def visit_ENTRY(self, node, visited_children):
     """
     Create a global_var corresponding to the entry
     According to the rules, Entry has three children
     (head, sep_equal, tail)
     :return:
     """
     # Get info from HEAD and a math_object from TAIL
     (_, head), _, ([tail], _) = visited_children
     info = head
     info['math_type'] = tail
     math_object = MathObject.from_info_and_children(info=info, children=[])
     # log.debug(f"Creating global var MathObject {math_object}")
     return math_object
Example #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
    def fireworks(self):
        """
        As of now,
        - display a dialog when the target is successfully solved,
        - replace the target by a message "No more goal"
        Note that the dialog is displayed only the first time the signal is
        triggered, thanks to the flag self.cqdf.
        """
        # TODO: make it a separate class

        # Display msg_box unless redoing or test mode
        # (Previously was: Display unless exercise already solved)
        # if not self.exercise_solved:
        if not self.proof_step.is_redo() and not self.test_mode:
            title = _('Target solved')
            text = _('The proof is complete!')
            msg_box = QMessageBox(parent=self)
            msg_box.setText(text)
            msg_box.setWindowTitle(title)
            button_ok = msg_box.addButton(_('Back to exercise'),
                                          QMessageBox.YesRole)
            button_change = msg_box.addButton(_('Change exercise'),
                                              QMessageBox.YesRole)
            button_change.clicked.connect(self.change_exercise)
            msg_box.exec()

        self.proof_step.no_more_goal = True
        self.proof_step.new_goals = []
        # Artificially create a final proof_state by replacing target by a msg
        # (We do not get the final proof_state from Lean).
        proof_state = deepcopy(self.proof_step.proof_state)
        target = proof_state.goals[0].target
        target.math_type = MathObject(
            node="NO_MORE_GOAL",
            info={},
            children=[],
        )
        # Update proof_step and UI
        self.update_proof_state(proof_state)

        if not self.exercise_solved:
            self.exercise_solved = True
            if not self.test_mode:  # Save exercise for auto-test
                self.lean_file.save_exercise_for_autotest(self)
Example #10
0
def explain_how_to_apply(math_object: MathObject, dynamic=False, long=False) \
        -> str:
    """
    Provide explanations of how the math_object may be applied
    (--> to serve as tooltip or help)
    :param math_object:
    :param dynamic: if False, caption depends only on main node
    :param long: boolean
    TODO: implement dynamic and long tooltips
    """
    captions = []  # default value

    if math_object.is_function():
        captions.append(tooltips.get('tooltip_apply_function'))
    elif math_object.can_be_used_for_substitution()[0]:
        captions.append(tooltips.get('tooltip_apply_substitute'))

    return captions

    #TODO: include this when extended apply button
    # the following 4 cases are mutually exclusive
    if math_object.is_function():
        captions.append(tooltips.get('tooltip_apply_function'))

    elif math_object.is_for_all():
        captions.append(tooltips.get('tooltip_apply_for_all'))

    elif math_object.is_exists():
        captions.append(tooltips.get('tooltip_apply_exists'))

    elif math_object.is_and():
        captions.append(tooltips.get('tooltip_apply_and'))

    # Additional line
    if math_object.can_be_used_for_implication():
        captions.append(tooltips.get('tooltip_apply_implication'))

    elif math_object.can_be_used_for_substitution()[0]:
        captions.append(tooltips.get('tooltip_apply_substitute'))

    return captions
Example #11
0
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)
Example #12
0
def give_local_name(math_type: MathObject,
                    body: MathObject,
                    hints: [str] = [],
                    forbidden_vars: [MathObject] = []) -> str:
    """
    Attribute a name to a local variable. See give_name below.
    Mainly computes all pertinent forbidden variables, by adding vars from
    body to the forbidden_vars list.

    :param math_type:       MathObject type of new variable
    :param body:            a MathObject inside which the new name will serve
                            as a bound variable
    :param hints:           a list of hints (str) for the future name
    :param forbidden_vars:  list of vars (MathObject) whose names are forbidden
    :return:                str, a name for the new variable
    """

    additional_forbidden_vars = body.extract_local_vars()
    names = [var.info['name'] for var in forbidden_vars]
    # log.debug(f'Giving name to bound var, a priori forbidden names ={names}')
    more_names = [var.info['name'] for var in additional_forbidden_vars]
    # log.debug(f'Additional forbidden names ={more_names}')
    forbidden_vars.extend(additional_forbidden_vars)
    return give_name(math_type, forbidden_vars, hints)