예제 #1
0
 def extractMyInitArgValue(self, argName):
     '''
     Return the most proper initialization value for the
     initialization argument of the given name in order to
     reconstruct this Expression in its current style.
     '''
     init_argname_mapping = self.__class__._init_argname_mapping_
     argName = init_argname_mapping.get(argName, argName)
     if argName == 'operator':
         return self.operator  # simply the operator
     elif argName == 'instanceVarOrVars':
         # return the joined instance variables according to style.
         return singleOrCompositeExpression(
             OperationOverInstances.explicitInstanceVars(self))
     elif argName == 'instanceExpr':
         # return the inner instance expression after joining the
         # instance variables according to the style
         return OperationOverInstances.explicitInstanceExpr(self)
     elif argName == 'domain' or argName == 'domains':
         # return the proper single domain or list of domains
         if self.domain is None: return None
         domains = OperationOverInstances.explicitDomains(self)
         if domains == [self.domain] * len(domains):
             return self.domain if argName == 'domain' else None
         elif not None in domains:
             return ExprTuple(*domains) if argName == 'domains' else None
         return None
     elif argName == 'conditions':
         # return the joined conditions excluding domain conditions
         conditions = compositeExpression(
             OperationOverInstances.explicitConditions(self))
         if len(conditions) == 0:
             conditions = tuple()  # set to match the "default"
         return conditions
예제 #2
0
    def __init__(self, operator, operand_or_operands, styles=None):
        '''
        Create an operation with the given operator and operands.
        The operator must be a Label (a Variable or a Literal).
        If a composite expression is provided as the 
        'operand_or_operands' it is taken to be the 'operands' of the
        Operation; otherwise the 'operands' will be the the provided
        Expression wrapped as a single entry in an ExprTuple.
        When there is a single operand that is not an ExprRange, there
        will be an 'operand' attribute as well as 'operands' as an 
        ExprTuple containing the one operand.
        '''
        from proveit._core_.expression.composite import (
            single_or_composite_expression, Composite, ExprTuple)
        from proveit._core_.expression.label.label import Label
        from .indexed_var import IndexedVar
        self.operator = operator
        operand_or_operands = single_or_composite_expression(
            operand_or_operands, do_singular_reduction=True)
        if isinstance(operand_or_operands, Composite):
            # a composite of multiple operands
            self.operands = operand_or_operands
        else:
            self.operands = ExprTuple(operand_or_operands)

        def raise_bad_operator_type(operator):
            raise TypeError('operator must be a Label or an indexed variable '
                            '(IndexedVar). %s is none of those.' %
                            str(operator))

        if (not isinstance(self.operator, Label)
                and not isinstance(self.operator, IndexedVar)):
            raise_bad_operator_type(self.operator)
        if (isinstance(self.operands, ExprTuple)
                and self.operands.is_single()):
            # This is a single operand.
            self.operand = self.operands[0]
        sub_exprs = (self.operator, self.operands)
        if isinstance(self, IndexedVar):
            core_type = 'IndexedVar'
        else:
            core_type = 'Operation'
        Expression.__init__(self, [core_type], sub_exprs, styles=styles)
예제 #3
0
    def decorated_relation_prover(*args, **kwargs):
        from proveit._core_.expression.expr import Expression
        from proveit._core_.expression.composite import ExprRange, ExprTuple
        from proveit.relation import Relation

        # 'preserve' the 'self' or 'self.expr' expression so it will
        # be on the left side without simplification.
        _self = args[0]
        if isinstance(_self, Expression):
            expr = _self
        elif hasattr(_self, 'expr'):
            expr = _self.expr
        else:
            raise TypeError("@relation_prover, %s, expected to be a "
                            "method for an Expression type or it must "
                            "have an 'expr' attribute." % func)
        if 'preserve_expr' in kwargs:
            if 'preserved_exprs' in kwargs:
                kwargs['preserved_exprs'] = (kwargs['preserved_exprs'].union(
                    [expr]))
            else:
                kwargs['preserved_exprs'] = (defaults.preserved_exprs.union(
                    [expr]))
        else:
            kwargs['preserve_expr'] = expr

        # Use the regular @prover wrapper.
        proven_truth = decorated_prover(*args, **kwargs)

        # Check that the result is of the expected form.
        proven_expr = proven_truth.expr
        if not isinstance(proven_expr, Relation):
            raise TypeError("@relation_prover, %s, expected to prove a "
                            "Relation expression, not %s of type %s." %
                            (func, proven_expr, proven_expr.__class__))
        expected_lhs = expr
        if isinstance(expr, ExprRange):
            expected_lhs = ExprTuple(expr)
        if proven_expr.lhs != expected_lhs:
            raise TypeError("@relation_prover, %s, expected to prove a "
                            "relation with %s on its left side "
                            "('lhs').  %s does not satisfy this "
                            "requirement." % (func, expected_lhs, proven_expr))

        # Make the style consistent with the original expression.
        if not proven_expr.lhs.has_same_style(expected_lhs):
            # Make the left side of the proven truth have a style
            # that matches the original expression.
            inner_lhs = proven_truth.inner_expr().lhs
            proven_truth = inner_lhs.with_matching_style(expected_lhs)
        return proven_truth
예제 #4
0
    def _formatted(self, formatType, fence=False):
        '''
        Format the OperationOverInstances according to the style
        which may join nested operations of the same type.
        '''
        # override this default as desired
        explicitIvars = list(self.explicitInstanceVars()
                             )  # the (joined) instance vars to show explicitly
        explicitConditions = ExprTuple(*self.explicitConditions(
        ))  # the (joined) conditions to show explicitly after '|'
        explicitDomains = ExprTuple(
            *self.explicitDomains())  # the (joined) domains
        explicitInstanceExpr = self.explicitInstanceExpr(
        )  # left over after joining instnace vars according to the style
        hasExplicitIvars = (len(explicitIvars) > 0)
        hasExplicitConditions = (len(explicitConditions) > 0)
        hasMultiDomain = (len(explicitDomains) > 1 and explicitDomains !=
                          ExprTuple(*[self.domain] * len(explicitDomains)))
        outStr = ''
        formattedVars = ', '.join(
            [var.formatted(formatType, abbrev=True) for var in explicitIvars])
        if formatType == 'string':
            if fence: outStr += '['
            outStr += self.operator.formatted(formatType) + '_{'
            if hasExplicitIvars:
                if hasMultiDomain: outStr += '(' + formattedVars + ')'
                else: outStr += formattedVars
            if hasMultiDomain or self.domain is not None:
                outStr += ' in '
                if hasMultiDomain:
                    outStr += explicitDomains.formatted(
                        formatType, operatorOrOperators='*', fence=False)
                else:
                    outStr += self.domain.formatted(formatType, fence=False)
            if hasExplicitConditions:
                if hasExplicitIvars: outStr += " | "
                outStr += explicitConditions.formatted(formatType, fence=False)
                #outStr += ', '.join(condition.formatted(formatType) for condition in self.conditions if condition not in implicitConditions)
            outStr += '} ' + explicitInstanceExpr.formatted(formatType,
                                                            fence=True)
            if fence: outStr += ']'
        if formatType == 'latex':
            if fence: outStr += r'\left['
            outStr += self.operator.formatted(formatType) + '_{'
            if hasExplicitIvars:
                if hasMultiDomain: outStr += '(' + formattedVars + ')'
                else: outStr += formattedVars
            if hasMultiDomain or self.domain is not None:
                outStr += r' \in '
                if hasMultiDomain:
                    outStr += explicitDomains.formatted(
                        formatType, operatorOrOperators=r'\times', fence=False)
                else:
                    outStr += self.domain.formatted(formatType, fence=False)
            if hasExplicitConditions:
                if hasExplicitIvars: outStr += "~|~"
                outStr += explicitConditions.formatted(formatType, fence=False)
                #outStr += ', '.join(condition.formatted(formatType) for condition in self.conditions if condition not in implicitConditions)
            outStr += '}~' + explicitInstanceExpr.formatted(formatType,
                                                            fence=True)
            if fence: outStr += r'\right]'

        return outStr
예제 #5
0
class Operation(Expression):
    # Map _operator_ Literals to corresponding Operation classes.
    # This is populated automatically when the _operator_ attribute
    # is accessed (see ExprType in proveit._core_.expression.expr).
    operation_class_of_operator = dict()

    @staticmethod
    def _clear_():
        '''
        Clear all references to Prove-It information under
        the Expression jurisdiction.  All Expression classes that store
        Prove-It state information must implement _clear_ to clear that
        information.
        '''
        Operation.operation_class_of_operator.clear()

    def __init__(self, operator, operand_or_operands, styles=None):
        '''
        Create an operation with the given operator and operands.
        The operator must be a Label (a Variable or a Literal).
        If a composite expression is provided as the 
        'operand_or_operands' it is taken to be the 'operands' of the
        Operation; otherwise the 'operands' will be the the provided
        Expression wrapped as a single entry in an ExprTuple.
        When there is a single operand that is not an ExprRange, there
        will be an 'operand' attribute as well as 'operands' as an 
        ExprTuple containing the one operand.
        '''
        from proveit._core_.expression.composite import (
            single_or_composite_expression, Composite, ExprTuple)
        from proveit._core_.expression.label.label import Label
        from .indexed_var import IndexedVar
        self.operator = operator
        operand_or_operands = single_or_composite_expression(
            operand_or_operands, do_singular_reduction=True)
        if isinstance(operand_or_operands, Composite):
            # a composite of multiple operands
            self.operands = operand_or_operands
        else:
            self.operands = ExprTuple(operand_or_operands)

        def raise_bad_operator_type(operator):
            raise TypeError('operator must be a Label or an indexed variable '
                            '(IndexedVar). %s is none of those.' %
                            str(operator))

        if (not isinstance(self.operator, Label)
                and not isinstance(self.operator, IndexedVar)):
            raise_bad_operator_type(self.operator)
        if (isinstance(self.operands, ExprTuple)
                and self.operands.is_single()):
            # This is a single operand.
            self.operand = self.operands[0]
        sub_exprs = (self.operator, self.operands)
        if isinstance(self, IndexedVar):
            core_type = 'IndexedVar'
        else:
            core_type = 'Operation'
        Expression.__init__(self, [core_type], sub_exprs, styles=styles)

    def style_options(self):
        '''
        Return the StyleOptions object for the Operation.
        '''
        from proveit._core_.expression.composite.expr_tuple import ExprTuple
        trivial_op = False
        if (isinstance(self.operands, ExprTuple)
                and len(self.operands.entries) > 0
                and not self.operands.is_single()):
            # 'infix' is only a sensible option when there are
            # multiple operands as an ExprTuple.
            default_op_style = 'infix'
        else:
            # With no operands or 1 operand, infix is not an option.
            trivial_op = True
            default_op_style = 'function'
        options = StyleOptions(self)
        options.add_option(
            name='operation',
            description="'infix' or 'function' style formatting",
            default=default_op_style,
            related_methods=())
        if not trivial_op:
            # Wrapping is only relevant if there is more than one
            # operand.
            options.add_option(
                name='wrap_positions',
                description=("position(s) at which wrapping is to occur; "
                             "'2 n - 1' is after the nth operand, '2 n' is "
                             "after the nth operation."),
                default='()',
                related_methods=('with_wrapping_at',
                                 'with_wrap_before_operator',
                                 'with_wrap_after_operator', 'wrap_positions'))
            options.add_option(
                name='justification',
                description=(
                    "if any wrap positions are set, justify to the 'left', "
                    "'center', or 'right'"),
                default='center',
                related_methods=('with_justification', ))
        return options

    def with_wrapping_at(self, *wrap_positions):
        return self.with_styles(wrap_positions='(' +
                                ' '.join(str(pos)
                                         for pos in wrap_positions) + ')')

    def with_wrap_before_operator(self):
        if self.operands.num_entries() != 2:
            raise NotImplementedError(
                "'with_wrap_before_operator' only valid when there are 2 operands"
            )
        return self.with_wrapping_at(1)

    def with_wrap_after_operator(self):
        if self.operands.num_entries() != 2:
            raise NotImplementedError(
                "'with_wrap_after_operator' only valid when there are 2 operands"
            )
        return self.with_wrapping_at(2)

    def with_justification(self, justification):
        return self.with_styles(justification=justification)

    @classmethod
    def _implicitOperator(operation_class):
        if hasattr(operation_class, '_operator_'):
            return operation_class._operator_
        return None

    @classmethod
    def extract_init_arg_value(cls, arg_name, operator, operands):
        '''
        Given a name of one of the arguments of the __init__ method,
        return the corresponding value contained in the 'operands'
        composite expression (i.e., the operands of a constructed operation).

        Except when this is an OperationOverInstances, this method should
        be overridden if you cannot simply pass the operands directly
        into the __init__ method.
        '''
        raise NotImplementedError(
            "'%s.extract_init_arg_value' must be appropriately implemented; "
            "__init__ arguments do not fall into a simple 'default' "
            "scenarios." % str(cls))

    def extract_my_init_arg_value(self, arg_name):
        '''
        Given a name of one of the arguments of the __init__ method,
        return the corresponding value appropriate for reconstructing self.
        The default calls the extract_init_arg_value static method.  Overload
        this when the most proper way to construct the expression is style
        dependent.  Then "remake_arguments" will use this overloaded method.
        "_make" will do it via the static method but will fix the styles
        afterwards.
        '''
        return self.__class__.extract_init_arg_value(arg_name, self.operator,
                                                     self.operands)

    def _extractMyInitArgs(self):
        '''
        Call the _extract_init_args class method but will set "obj" to "self".
        This will cause extract_my_init_arg_value to be called instead of
        the extract_init_arg_value static method.
        '''
        return self.__class__._extract_init_args(self.operator,
                                                 self.operands,
                                                 obj=self)

    @classmethod
    def _extract_init_args(cls, operator, operands, obj=None):
        '''
        For a particular Operation class and given operator and 
        operands, yield (name, value) pairs to pass into the 
        initialization method for creating the operation consistent 
        with the given operator and operands.

        First attempt to call 'extract_init_arg_value' (or 
        'extract_my_init_arg_value' if 'obj' is provided) for each of 
        the __init__ arguments to determine how to generate the 
        appropriate __init__ parameters from the given operators
        and operands.  If that is not implemented, fall back to one of 
        the following default scenarios.

        If the particular Operation class has an 'implicit operator' 
        defined via an _operator_ attribute and the number of __init__
        arguments matches the number of operands or it takes only a
        single *args variable-length argument list: construct the 
        Operation by passing each operand individually.

        Otherwise, if there is no 'implicit operator' and __init__ 
        accepts only two arguments (no variable-length or keyward
        arguments): construct the Operation by passeng the operation(s) 
        as the first argument and operand(s) as the second argument.  
        If the length of either of these is 1, then the single 
        expression is passed rather than a composite.

        Otherwise, if there is no 'implicit operator' and __init__ 
        accepts one formal  argument and a variable-length argument and 
        no keyword arguments, or a number of formal arguments equal to
        the number of operands plus 1 and no variable-length argument 
        and no keyword arguments: construct the Operation by passing 
        the operator(s) and each operand individually.
        '''
        implicit_operator = cls._implicitOperator()
        matches_implicit_operator = (operator == implicit_operator)
        if implicit_operator is not None and not matches_implicit_operator:
            raise OperationError("An implicit operator may not be changed")
        args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, _ = \
            inspect.getfullargspec(cls.__init__)
        args = args[1:]  # skip over the 'self' arg
        if obj is None:

            def extract_init_arg_value_fn(arg):
                return cls.extract_init_arg_value(arg, operator, operands)
        else:
            extract_init_arg_value_fn = \
                lambda arg: obj.extract_my_init_arg_value(arg)
        try:
            arg_vals = [extract_init_arg_value_fn(arg) for arg in args]
            if varargs is not None:
                arg_vals += extract_init_arg_value_fn(varargs)
            if defaults is None:
                defaults = []
            for k, (arg, val) in enumerate(zip(args, arg_vals)):
                if len(defaults) - len(args) + k < 0:
                    if not isinstance(val, Expression):
                        raise TypeError(
                            "extract_init_arg_val for %s should return an Expression but is returning a %s"
                            % (arg, type(val)))
                    yield val  # no default specified; just supply the value, not the argument name
                else:
                    if val == defaults[len(defaults) - len(args) + k]:
                        continue  # using the default value
                    else:
                        yield (arg, val)  # override the default
            if varkw is not None:
                kw_arg_vals = extract_init_arg_value_fn(varkw)
                for arg, val in kw_arg_vals.items():
                    yield (arg, val)
            if kwonlyargs is not None:
                for kwonlyarg in kwonlyargs:
                    val = extract_init_arg_value_fn(kwonlyarg)
                    if val != kwonlydefaults[kwonlyarg]:
                        yield (kwonlyarg, val)
        except NotImplementedError:
            # and (operation_class.extract_init_arg_value ==
            # Operation.extract_init_arg_value):
            if (varkw is None):
                # try some default scenarios (that do not involve
                # keyword arguments) handle default implicit operator
                # case
                if implicit_operator and (
                    (len(args) == 0 and varargs is not None) or
                    (len(args) == operands.num_entries() and varargs is None)):
                    # yield each operand separately
                    for operand in operands:
                        yield operand
                    return

                # handle default explicit operator case
                if (not implicit_operator) and (varkw is None):
                    if varargs is None and len(args) == 2:
                        # assume one argument for the operator and one
                        # argument for the operands
                        yield operator
                        yield operands
                        return
                    elif ((varargs is not None and len(args) == 1)
                          or (len(args) == operands.num_entries() + 1
                              and varargs is None)):
                        # yield the operator and each operand separately
                        yield operator
                        for operand in operands:
                            yield operand
                        return
                raise NotImplementedError(
                    "Must implement 'extract_init_arg_value' for the "
                    "Operation of type %s if it does not fall into "
                    "one of the default cases for 'extract_init_args'" %
                    str(cls))

    @classmethod
    def _make(operation_class, core_info, sub_expressions):
        '''
        Make the appropriate Operation.  core_info should equal 
        ('Operation',).  The first of the sub_expressions should be 
        the operator and the second should be the operands.  This 
        implementation expects the Operation sub-class to have a
        class variable named '_operator_' that defines the Literal 
        operator of the class.  It will instantiate the Operation 
        sub-class with just *operands and check that the operator is 
        consistent.  Override this method if a different behavior is 
        desired.
        '''
        if len(core_info) != 1 or core_info[0] != 'Operation':
            raise ValueError(
                "Expecting Operation core_info to contain exactly one item: 'Operation'"
            )
        if len(sub_expressions) == 0:
            raise ValueError(
                'Expecting at least one sub_expression for an Operation, for the operator'
            )
        operator, operands = sub_expressions[0], sub_expressions[1]
        args = []
        kw_args = dict()
        for arg in operation_class._extract_init_args(operator, operands):
            if isinstance(arg, Expression):
                args.append(arg)
            else:
                kw, val = arg
                kw_args[kw] = val
        return operation_class(*args, **kw_args)

    def remake_arguments(self):
        '''
        Yield the argument values or (name, value) pairs
        that could be used to recreate the Operation.
        '''
        for arg in self._extractMyInitArgs():
            yield arg

    def remake_with_style_calls(self):
        '''
        In order to reconstruct this Expression to have the same styles,
        what "with..." method calls are most appropriate?  Return a
        tuple of strings with the calls to make.  The default for the
        Operation class is to include appropriate 'with_wrapping_at'
        and 'with_justification' calls.
        '''
        wrap_positions = self.wrap_positions()
        call_strs = []
        if len(wrap_positions) > 0:
            call_strs.append('with_wrapping_at(' +
                             ','.join(str(pos)
                                      for pos in wrap_positions) + ')')
        justification = self.get_style('justification', 'center')
        if justification != 'center':
            call_strs.append('with_justification("' + justification + '")')
        return call_strs

    def string(self, **kwargs):
        # When there is a single operand, we must use the "function"-style
        # formatting.
        if self.get_style('operation', 'function') == 'function':
            return self._function_formatted('string', **kwargs)
        return self._formatted('string', **kwargs)

    def latex(self, **kwargs):
        # When there is a single operand, we must use the "function"-style
        # formatting.
        if self.get_style('operation', 'function') == 'function':
            return self._function_formatted('latex', **kwargs)
        return self._formatted('latex', **kwargs)

    def wrap_positions(self):
        '''
        Return a list of wrap positions according to the current style setting.
        '''
        return [
            int(pos_str) for pos_str in self.get_style(
                'wrap_positions', '').strip('()').split(' ') if pos_str != ''
        ]

    def _function_formatted(self, format_type, **kwargs):
        from proveit._core_.expression.composite.expr_tuple import ExprTuple
        formatted_operator = self.operator.formatted(format_type, fence=True)
        if (hasattr(self, 'operand')
                and not isinstance(self.operand, ExprTuple)):
            return '%s(%s)' % (formatted_operator,
                               self.operand.formatted(format_type,
                                                      fence=False))
        return '%s(%s)' % (formatted_operator,
                           self.operands.formatted(
                               format_type, fence=False, sub_fence=False))

    def _formatted(self, format_type, **kwargs):
        '''
        Format the operation in the form "A * B * C" where '*' is a stand-in for
        the operator that is obtained from self.operator.formatted(format_type).

        '''
        if hasattr(self, 'operator'):
            return Operation._formattedOperation(
                format_type,
                self.operator,
                self.operands,
                wrap_positions=self.wrap_positions(),
                justification=self.get_style('justification', 'center'),
                **kwargs)
        else:
            return Operation._formattedOperation(
                format_type,
                self.operators,
                self.operands,
                wrap_positions=self.wrap_positions(),
                justification=self.get_style('justification', 'center'),
                **kwargs)

    @staticmethod
    def _formattedOperation(format_type,
                            operator_or_operators,
                            operands,
                            wrap_positions,
                            justification,
                            implicit_first_operator=False,
                            **kwargs):
        from proveit import ExprRange, ExprTuple, composite_expression
        if isinstance(operator_or_operators, Expression) and not isinstance(
                operator_or_operators, ExprTuple):
            operator = operator_or_operators
            # Single operator case.
            # Different formatting when there is 0 or 1 element, unless
            # it is an ExprRange.
            if operands.num_entries() < 2:
                if operands.num_entries() == 0 or not isinstance(
                        operands[0], ExprRange):
                    if format_type == 'string':
                        return '[' + operator.string(
                            fence=True) + '](' + operands.string(
                                fence=False, sub_fence=False) + ')'
                    else:
                        return r'\left[' + operator.latex(
                            fence=True) + r'\right]\left(' + operands.latex(
                                fence=False, sub_fence=False) + r'\right)'
                    raise ValueError("Unexpected format_type: " +
                                     str(format_type))
            fence = kwargs.get('fence', False)
            sub_fence = kwargs.get('sub_fence', True)
            do_wrapping = len(wrap_positions) > 0
            formatted_str = ''
            formatted_str += operands.formatted(format_type,
                                                fence=fence,
                                                sub_fence=sub_fence,
                                                operator_or_operators=operator,
                                                wrap_positions=wrap_positions,
                                                justification=justification)
            return formatted_str
        else:
            operators = operator_or_operators
            operands = composite_expression(operands)
            # Multiple operator case.
            # Different formatting when there is 0 or 1 element, unless it is
            # an ExprRange
            if operands.num_entries() < 2:
                if operands.num_entries() == 0 or not isinstance(
                        operands[0], ExprRange):
                    raise OperationError(
                        "No defaut formatting with multiple operators and zero operands"
                    )
            fence = kwargs['fence'] if 'fence' in kwargs else False
            sub_fence = kwargs['sub_fence'] if 'sub_fence' in kwargs else True
            do_wrapping = len(wrap_positions) > 0
            formatted_str = ''
            if fence:
                formatted_str = '(' if format_type == 'string' else r'\left('
            if do_wrapping and format_type == 'latex':
                formatted_str += r'\begin{array}{%s} ' % justification[0]
            formatted_str += operands.formatted(
                format_type,
                fence=False,
                sub_fence=sub_fence,
                operator_or_operators=operators,
                implicit_first_operator=implicit_first_operator,
                wrap_positions=wrap_positions)
            if do_wrapping and format_type == 'latex':
                formatted_str += r' \end{array}'
            if fence:
                formatted_str += ')' if format_type == 'string' else r'\right)'
            return formatted_str

    def _replaced(self, repl_map, allow_relabeling, assumptions, requirements,
                  equality_repl_requirements):
        '''
        Returns this expression with sub-expressions substituted
        according to the replacement map (repl_map) dictionary.

        When an operater of an Operation is substituted by a Lambda map,
        the operation itself will be substituted with the Lambda map
        applied to the operands.  For example, substituting
        f : (x,y) -> x+y
        on f(a, b) will result in a+b.  When performing operation
        substitution with a range of parameters, the Lambda map
        application will require the number of these parameters
        to equal with the number of corresponding operand elements.
        For example,
        f : (a, b_1, ..., b_n) -> a*b_1 + ... + a*b_n
        n : 3
        applied to f(w, x, y, z) will result in w*x + w*y + w*z provided
        that |(b_1, ..., b_3)| = |(x, y, z)| is proven.
        Assumptions may be needed to prove such requirements.
        Requirements will be appended to the 'requirements' list if one
        is provided.

        There are limitations with respect the Lambda map application involving
        iterated parameters when perfoming operation substitution in order to
        keep derivation rules (i.e., instantiation) simple.  For details,
        see the ExprRange.substituted documentation.
        '''
        from proveit import Lambda, ExprRange

        if len(repl_map) > 0 and (self in repl_map):
            # The full expression is to be substituted.
            return repl_map[self]

        # Perform substitutions for the operator(s) and operand(s).
        subbed_operator = \
            self.operator.replaced(repl_map, allow_relabeling,
                                   assumptions, requirements,
                                   equality_repl_requirements)
        subbed_operands = \
            self.operands.replaced(repl_map, allow_relabeling,
                                   assumptions, requirements,
                                   equality_repl_requirements)

        # Check if the operator is being substituted by a Lambda map in
        # which case we should perform full operation substitution.
        if isinstance(subbed_operator, Lambda):
            # Substitute the entire operation via a Lambda map
            # application.  For example, f(x, y) -> x + y,
            # or g(a, b_1, ..., b_n) -> a * b_1 + ... + a * b_n.

            if isinstance(subbed_operator.body, ExprRange):
                raise ImproperReplacement(
                    self, repl_map,
                    "The function %s cannot be defined using this "
                    "lambda, %s, that has an ExprRange for its body; "
                    "that could lead to tuple length contradictions." %
                    (self.operator, subbed_operator))
            return Lambda._apply(
                subbed_operator.parameters,
                subbed_operator.body,
                *subbed_operands.entries,
                assumptions=assumptions,
                requirements=requirements,
                equality_repl_requirements=equality_repl_requirements)

        # If the operator is a literal operator of
        # an Operation class defined via an "_operator_" class
        # attribute, then create the Operation of that class.
        if subbed_operator in Operation.operation_class_of_operator:
            op_class = Operation.operation_class_of_operator[subbed_operator]
            if op_class != self.__class__:
                # Don't transfer the styles; they may not apply in
                # the same manner in the setting of the new
                # operation.
                subbed_sub_exprs = (subbed_operator, subbed_operands)
                substituted = op_class._checked_make(
                    ['Operation'], sub_expressions=subbed_sub_exprs)
                return substituted._auto_reduced(assumptions, requirements,
                                                 equality_repl_requirements)

        subbed_sub_exprs = (subbed_operator, subbed_operands)
        substituted = self.__class__._checked_make(self._core_info,
                                                   subbed_sub_exprs)
        return substituted._auto_reduced(assumptions, requirements,
                                         equality_repl_requirements)
예제 #6
0
    def __init__(self,
                 operator,
                 operand_or_operands=None,
                 *,
                 operands=None,
                 styles):
        '''
        Create an operation with the given operator and operands.
        The operator must be a Label (a Variable or a Literal).
        If a composite expression is provided as the 
        'operand_or_operands' it is taken to be the 'operands' of the
        Operation; otherwise the 'operands' will be the the provided
        Expression wrapped as a single entry in an ExprTuple.
        When there is a single operand that is not an ExprRange, there
        will be an 'operand' attribute as well as 'operands' as an 
        ExprTuple containing the one operand.
        '''
        from proveit._core_.expression.composite import (
            composite_expression, single_or_composite_expression, Composite,
            ExprTuple, NamedExprs)
        from proveit._core_.expression.lambda_expr import Lambda
        from .indexed_var import IndexedVar
        if self.__class__ == Operation:
            raise TypeError("Do not create an object of type Operation; "
                            "use a derived class (e.g., Function) instead.")
        self.operator = operator
        if (operand_or_operands == None) == (operands == None):
            if operands == None:
                raise ValueError(
                    "Must supply either 'operand_or_operands' or 'operands' "
                    "when constructing an Operation")
            else:
                raise ValueError(
                    "Must supply 'operand_or_operands' or 'operands' but not "
                    "both when constructing an Operation")
        if operands is not None:
            if not isinstance(operands, Expression):
                operands = composite_expression(operands)
            self.operands = operands
        else:
            orig_operand_or_operands = operand_or_operands
            operand_or_operands = single_or_composite_expression(
                operand_or_operands, do_singular_reduction=True)
            if isinstance(operand_or_operands, Composite):
                # a composite of multiple operands
                self.operands = operand_or_operands
            else:
                if isinstance(orig_operand_or_operands, Composite):
                    self.operands = orig_operand_or_operands
                else:
                    self.operands = ExprTuple(operand_or_operands)

        def raise_bad_operator_type(operator):
            raise TypeError("An operator may not be an explicit Lambda map "
                            "like %s; this is necessary to avoid a Curry's "
                            "paradox." % str(operator))

        if isinstance(self.operator, Lambda):
            raise_bad_operator_type(self.operator)
        if (isinstance(self.operands, ExprTuple)
                and self.operands.is_single()):
            # This is a single operand.
            self.operand = self.operands[0]
        sub_exprs = (self.operator, self.operands)
        if isinstance(self, IndexedVar):
            core_type = 'IndexedVar'
        else:
            core_type = 'Operation'
        if isinstance(self.operands, NamedExprs):
            # Make attributes to make the keys of the NamedExprs
            # operands.
            for key in self.operands.keys():
                setattr(self, key, self.operands[key])
        Expression.__init__(self, [core_type], sub_exprs, styles=styles)
예제 #7
0
class Operation(Expression):
    # Map _operator_ Literals to corresponding Operation classes.
    # This is populated automatically when the _operator_ attribute
    # is accessed (see ExprType in proveit._core_.expression.expr).
    operation_class_of_operator = dict()

    @staticmethod
    def _clear_():
        '''
        Clear all references to Prove-It information under
        the Expression jurisdiction.  All Expression classes that store
        Prove-It state information must implement _clear_ to clear that
        information.
        '''
        Operation.operation_class_of_operator.clear()

    def __init__(self,
                 operator,
                 operand_or_operands=None,
                 *,
                 operands=None,
                 styles):
        '''
        Create an operation with the given operator and operands.
        The operator must be a Label (a Variable or a Literal).
        If a composite expression is provided as the 
        'operand_or_operands' it is taken to be the 'operands' of the
        Operation; otherwise the 'operands' will be the the provided
        Expression wrapped as a single entry in an ExprTuple.
        When there is a single operand that is not an ExprRange, there
        will be an 'operand' attribute as well as 'operands' as an 
        ExprTuple containing the one operand.
        '''
        from proveit._core_.expression.composite import (
            composite_expression, single_or_composite_expression, Composite,
            ExprTuple, NamedExprs)
        from proveit._core_.expression.lambda_expr import Lambda
        from .indexed_var import IndexedVar
        if self.__class__ == Operation:
            raise TypeError("Do not create an object of type Operation; "
                            "use a derived class (e.g., Function) instead.")
        self.operator = operator
        if (operand_or_operands == None) == (operands == None):
            if operands == None:
                raise ValueError(
                    "Must supply either 'operand_or_operands' or 'operands' "
                    "when constructing an Operation")
            else:
                raise ValueError(
                    "Must supply 'operand_or_operands' or 'operands' but not "
                    "both when constructing an Operation")
        if operands is not None:
            if not isinstance(operands, Expression):
                operands = composite_expression(operands)
            self.operands = operands
        else:
            orig_operand_or_operands = operand_or_operands
            operand_or_operands = single_or_composite_expression(
                operand_or_operands, do_singular_reduction=True)
            if isinstance(operand_or_operands, Composite):
                # a composite of multiple operands
                self.operands = operand_or_operands
            else:
                if isinstance(orig_operand_or_operands, Composite):
                    self.operands = orig_operand_or_operands
                else:
                    self.operands = ExprTuple(operand_or_operands)

        def raise_bad_operator_type(operator):
            raise TypeError("An operator may not be an explicit Lambda map "
                            "like %s; this is necessary to avoid a Curry's "
                            "paradox." % str(operator))

        if isinstance(self.operator, Lambda):
            raise_bad_operator_type(self.operator)
        if (isinstance(self.operands, ExprTuple)
                and self.operands.is_single()):
            # This is a single operand.
            self.operand = self.operands[0]
        sub_exprs = (self.operator, self.operands)
        if isinstance(self, IndexedVar):
            core_type = 'IndexedVar'
        else:
            core_type = 'Operation'
        if isinstance(self.operands, NamedExprs):
            # Make attributes to make the keys of the NamedExprs
            # operands.
            for key in self.operands.keys():
                setattr(self, key, self.operands[key])
        Expression.__init__(self, [core_type], sub_exprs, styles=styles)

    def style_options(self):
        '''
        Return the StyleOptions object for the Operation.
        '''
        from proveit._core_.expression.composite.expr_tuple import ExprTuple
        trivial_op = not (isinstance(self.operands, ExprTuple)
                          and len(self.operands.entries) > 0
                          and not self.operands.is_single())
        options = StyleOptions(self)
        # Note: when there are no operands or 1 operand, 'infix'
        # is like the 'function' style except the operator is
        # wrapped in square braces.
        options.add_option(
            name='operation',
            description="'infix' or 'function' style formatting",
            default='infix',
            related_methods=())
        if not trivial_op:
            # Wrapping is only relevant if there is more than one
            # operand.
            options.add_option(
                name='wrap_positions',
                description=("position(s) at which wrapping is to occur; "
                             "'2 n - 1' is after the nth operand, '2 n' is "
                             "after the nth operation."),
                default='()',
                related_methods=('with_wrapping_at',
                                 'with_wrap_before_operator',
                                 'with_wrap_after_operator',
                                 'without_wrapping', 'wrap_positions'))
            options.add_option(
                name='justification',
                description=(
                    "if any wrap positions are set, justify to the 'left', "
                    "'center', or 'right'"),
                default='center',
                related_methods=('with_justification', ))
        return options

    def with_wrapping_at(self, *wrap_positions):
        return self.with_styles(wrap_positions='(' +
                                ' '.join(str(pos)
                                         for pos in wrap_positions) + ')')

    def without_wrapping(self, *wrap_positions):
        return self.with_wrapping_at()

    def with_wrap_before_operator(self):
        if self.operands.num_entries() != 2:
            raise NotImplementedError(
                "'with_wrap_before_operator' only valid when there are 2 operands"
            )
        return self.with_wrapping_at(1)

    def with_wrap_after_operator(self):
        if self.operands.num_entries() != 2:
            raise NotImplementedError(
                "'with_wrap_after_operator' only valid when there are 2 operands"
            )
        return self.with_wrapping_at(2)

    def with_justification(self, justification):
        return self.with_styles(justification=justification)

    @classmethod
    def _implicit_operator(operation_class):
        if hasattr(operation_class, '_operator_'):
            return operation_class._operator_
        return None

    @classmethod
    def extract_init_arg_value(cls, arg_name, operator, operands):
        '''
        Given a name of one of the arguments of the __init__ method,
        return the corresponding value contained in the 'operands'
        composite expression (i.e., the operands of a constructed operation).

        Except when this is an OperationOverInstances, this method should
        be overridden if you cannot simply pass the operands directly
        into the __init__ method.
        '''
        from proveit import NamedExprs
        if isinstance(operands, NamedExprs):
            # If the operands are NamedExprs, presume the arguments
            # correspond to the names of the sub-expressions.
            if arg_name in operands:
                return operands[arg_name]
            else:
                return None
        raise NotImplementedError(
            "'%s.extract_init_arg_value' must be appropriately implemented; "
            "__init__ arguments do not fall into a simple 'default' "
            "scenarios." % str(cls))

    def extract_my_init_arg_value(self, arg_name):
        '''
        Given a name of one of the arguments of the __init__ method,
        return the corresponding value appropriate for reconstructing self.
        The default calls the extract_init_arg_value static method.  Overload
        this when the most proper way to construct the expression is style
        dependent.  Then "remake_arguments" will use this overloaded method.
        "_make" will do it via the static method but will fix the styles
        afterwards.
        '''
        return self.__class__.extract_init_arg_value(arg_name, self.operator,
                                                     self.operands)

    def _extract_my_init_args(self):
        '''
        Call the _extract_init_args class method but will set "obj" to "self".
        This will cause extract_my_init_arg_value to be called instead of
        the extract_init_arg_value static method.
        '''
        return self.__class__._extract_init_args(self.operator,
                                                 self.operands,
                                                 obj=self)

    @classmethod
    def _extract_init_args(cls, operator, operands, obj=None):
        '''
        For a particular Operation class and given operator and 
        operands, yield (name, value) pairs to pass into the 
        initialization method for creating the operation consistent 
        with the given operator and operands.

        First attempt to call 'extract_init_arg_value' (or 
        'extract_my_init_arg_value' if 'obj' is provided) for each of 
        the __init__ arguments to determine how to generate the 
        appropriate __init__ parameters from the given operators
        and operands.  If that is not implemented, fall back to one of 
        the following default scenarios.

        If the particular Operation class has an 'implicit operator' 
        defined via an _operator_ attribute and the number of __init__
        arguments matches the number of operands or it takes only a
        single *args variable-length argument list: construct the 
        Operation by passing each operand individually.

        Otherwise, if there is no 'implicit operator' and __init__ 
        accepts only two arguments (no variable-length or keyward
        arguments): construct the Operation by passeng the operation(s) 
        as the first argument and operand(s) as the second argument.  
        If the length of either of these is 1, then the single 
        expression is passed rather than a composite.

        Otherwise, if there is no 'implicit operator' and __init__ 
        accepts one formal  argument and a variable-length argument and 
        no keyword arguments, or a number of formal arguments equal to
        the number of operands plus 1 and no variable-length argument 
        and no keyword arguments: construct the Operation by passing 
        the operator(s) and each operand individually.
        '''
        from .function import Function
        from proveit._core_.expression.composite import (ExprTuple, Composite)
        implicit_operator = cls._implicit_operator()
        matches_implicit_operator = (operator == implicit_operator)
        if implicit_operator is not None and not matches_implicit_operator:
            raise OperationError("An implicit operator may not be changed "
                                 "(%s vs %s)" % (operator, implicit_operator))
        sig = inspect.signature(cls.__init__)
        Parameter = inspect.Parameter
        init_params = sig.parameters
        if obj is None:

            def extract_init_arg_value_fn(arg):
                return cls.extract_init_arg_value(arg, operator, operands)
        else:
            extract_init_arg_value_fn = \
                lambda arg: obj.extract_my_init_arg_value(arg)
        try:
            first_param = True
            for param_name, param in init_params.items():
                if first_param:
                    # Skip the 'self' parameter.
                    first_param = False
                    continue
                if param_name == 'styles':
                    continue  # skip the styles parameter
                param = init_params[param_name]
                val = extract_init_arg_value_fn(param_name)
                default = param.default
                if default is param.empty or val != default:
                    # Override the default if there is one.
                    if not isinstance(val, Expression):
                        raise TypeError(
                            "extract_init_arg_val for %s should return "
                            "an Expression but is returning a %s" %
                            (param_name, type(val)))
                    if param.kind == Parameter.POSITIONAL_ONLY:
                        yield val
                    else:
                        yield (param_name, val)
        except NotImplementedError:
            # Try some default scenarios that don't require
            # extract_init_arg_value_fn to be implemented.
            pos_params = []
            var_params = None
            kwonly_params = []
            varkw = None
            for name, param in init_params.items():
                if param.kind in (Parameter.POSITIONAL_ONLY,
                                  Parameter.POSITIONAL_OR_KEYWORD):
                    pos_params.append(name)
                elif param.kind == Parameter.VAR_POSITIONAL:
                    var_params = name
                elif param.kind == Parameter.KEYWORD_ONLY:
                    kwonly_params.append(name)
                elif param.kind == Parameter.VAR_KEYWORD:
                    varkw = name
            pos_params = pos_params[1:]  # skip 'self'

            # Note: None keyword indicates that we should create a
            # generic Function for these all-in-one operands
            # (e.g. a variable representing an ExprTuple).
            if cls == Function:
                operands_kw = 'operands'
            else:
                operands_kw = None

            if (varkw is None):
                # handle default implicit operator case
                if implicit_operator and (
                    (len(pos_params) == 0 and var_params is not None) or
                    (len(pos_params) == operands.num_entries()
                     and var_params is None)):
                    if isinstance(operands, ExprTuple):
                        # yield each operand separately
                        for operand in operands:
                            yield operand
                        return
                    elif not isinstance(operands, Composite):
                        # Create a generic Operation
                        yield operator
                        yield (operands_kw, operands)
                        return
                # handle default explicit operator case
                elif (implicit_operator is None) and (varkw is None):
                    if var_params is None and len(pos_params) == 2:
                        # assume one argument for the operator and one
                        # argument for the operands
                        yield operator
                        if isinstance(operands, Composite):
                            yield operands
                        else:
                            yield (operands_kw, operands)
                        return
                    elif ((var_params is not None and len(pos_params) == 1)
                          or (len(pos_params) == operands.num_entries() + 1
                              and var_params is None)):
                        # yield the operator
                        yield operator
                        if isinstance(operands, ExprTuple):
                            # yield each operand separately
                            for operand in operands:
                                yield operand
                            return
                        elif not isinstance(operands, Composite):
                            yield (operands_kw, operands)
                            return
                raise NotImplementedError(
                    "Must implement 'extract_init_arg_value' for the "
                    "Operation of type %s if it does not fall into "
                    "one of the default cases for 'extract_init_args'" %
                    str(cls))

    @classmethod
    def _make(operation_class, core_info, sub_expressions, *, styles):
        '''
        Make the appropriate Operation.  core_info should equal 
        ('Operation',).  The first of the sub_expressions should be 
        the operator and the second should be the operands.  This 
        implementation expects the Operation sub-class to have a
        class variable named '_operator_' that defines the Literal 
        operator of the class.  It will instantiate the Operation 
        sub-class with just *operands and check that the operator is 
        consistent.  Override this method if a different behavior is 
        desired.
        '''
        from proveit._core_.expression.label.var import Variable
        from .function import Function
        if len(core_info) != 1 or core_info[0] != 'Operation':
            raise ValueError(
                "Expecting Operation core_info to contain exactly one item: 'Operation'"
            )
        if len(sub_expressions) == 0:
            raise ValueError(
                'Expecting at least one sub_expression for an Operation, for the operator'
            )
        operator, operands = sub_expressions[0], sub_expressions[1]
        implicit_operator = operation_class._implicit_operator()
        if implicit_operator is not None and isinstance(operator, Variable):
            # Convert an implicit operator to a variable.
            return Function(operator, operands, styles=styles)
        if operation_class == Operation:
            return Function(operator, operands, styles=styles)
        args = []
        kw_args = dict()
        for arg in operation_class._extract_init_args(operator, operands):
            if isinstance(arg, Expression):
                args.append(arg)
            else:
                kw, val = arg
                if kw == None:
                    # kw=None used to indicate that
                    # we should create a generic Function
                    # and use the 'operands' keyword
                    # (e.g. to represent an ExprTuple of
                    # operands with a variable).
                    operation_class = Function
                    kw = 'operands'
                kw_args[kw] = val
        return operation_class(*args, **kw_args, styles=styles)

    def remake_arguments(self):
        '''
        Yield the argument values or (name, value) pairs
        that could be used to recreate the Operation.
        '''
        for arg in self._extract_my_init_args():
            yield arg

    def remake_with_style_calls(self):
        '''
        In order to reconstruct this Expression to have the same styles,
        what "with..." method calls are most appropriate?  Return a
        tuple of strings with the calls to make.  The default for the
        Operation class is to include appropriate 'with_wrapping_at'
        and 'with_justification' calls.
        '''
        wrap_positions = self.wrap_positions()
        call_strs = []
        if len(wrap_positions) > 0:
            call_strs.append('with_wrapping_at(' +
                             ','.join(str(pos)
                                      for pos in wrap_positions) + ')')
        justification = self.get_style('justification', 'center')
        if justification != 'center':
            call_strs.append('with_justification("' + justification + '")')
        return call_strs

    def string(self, **kwargs):
        # When there is a single operand, we must use the "function"-style
        # formatting.
        from proveit._core_.expression.composite.expr_tuple import ExprTuple
        if (isinstance(self.operands, ExprTuple)
                and self.get_style('operation', 'function') == 'function'):
            return self._function_formatted('string', **kwargs)
        return self._formatted('string', **kwargs)

    def latex(self, **kwargs):
        # When there is a single operand, we must use the "function"-style
        # formatting.
        from proveit._core_.expression.composite.expr_tuple import ExprTuple
        if (isinstance(self.operands, ExprTuple)
                and self.get_style('operation', 'function') == 'function'):
            return self._function_formatted('latex', **kwargs)
        return self._formatted('latex', **kwargs)

    def wrap_positions(self):
        '''
        Return a list of wrap positions according to the current style setting.
        '''
        return [
            int(pos_str) for pos_str in self.get_style(
                'wrap_positions', '').strip('()').split(' ') if pos_str != ''
        ]

    def _function_formatted(self, format_type, **kwargs):
        from proveit._core_.expression.composite.expr_tuple import ExprTuple
        formatted_operator = self.operator.formatted(format_type, fence=True)
        lparen = r'\left(' if format_type == 'latex' else '('
        rparen = r'\right)' if format_type == 'latex' else ')'
        if (hasattr(self, 'operand')
                and not isinstance(self.operand, ExprTuple)):
            formatted_operand = self.operand.formatted(format_type,
                                                       fence=False)
        else:
            formatted_operand = self.operands.formatted(format_type,
                                                        fence=False,
                                                        sub_fence=False)

        return (formatted_operator + lparen + formatted_operand + rparen)

    def _formatted(self, format_type, **kwargs):
        '''
        Format the operation in the form "A * B * C" where '*' is a stand-in for
        the operator that is obtained from self.operator.formatted(format_type).

        '''
        return Operation._formatted_operation(
            format_type,
            self.operator,
            self.operands,
            implicit_first_operator=True,
            wrap_positions=self.wrap_positions(),
            justification=self.get_style('justification', 'center'),
            **kwargs)

    @staticmethod
    def _formatted_operation(format_type,
                             operator_or_operators,
                             operands,
                             *,
                             wrap_positions,
                             justification,
                             implicit_first_operator=True,
                             **kwargs):
        from proveit import ExprRange, ExprTuple, composite_expression
        if (isinstance(operator_or_operators, Expression)
                and not isinstance(operator_or_operators, ExprTuple)):
            # Single operator case.
            operator = operator_or_operators
            if isinstance(operands, ExprTuple):
                # An ExprTuple of operands.
                # Different formatting when there is 0 or 1 element, unless
                # it is an ExprRange.
                if operands.num_entries() < 2:
                    if operands.num_entries() == 0 or not isinstance(
                            operands[0], ExprRange):
                        if format_type == 'string':
                            return (
                                '[' + operator.string(fence=True) + '](' +
                                operands.string(fence=False, sub_fence=False) +
                                ')')
                        else:
                            return (
                                r'\left[' + operator.latex(fence=True) +
                                r'\right]\left(' +
                                operands.latex(fence=False, sub_fence=False) +
                                r'\right)')
                        raise ValueError("Unexpected format_type: " +
                                         str(format_type))
                fence = kwargs.get('fence', False)
                sub_fence = kwargs.get('sub_fence', True)
                do_wrapping = len(wrap_positions) > 0
                formatted_str = ''
                formatted_str += operands.formatted(
                    format_type,
                    fence=fence,
                    sub_fence=sub_fence,
                    operator_or_operators=operator,
                    implicit_first_operator=implicit_first_operator,
                    wrap_positions=wrap_positions,
                    justification=justification)
                return formatted_str
            else:
                # The operands ExprTuple are being represented by a Variable
                # (or Operation) equating to an ExprTuple.  We will format this
                # similarly as when we have 1 element except we drop the
                # round parantheses.  For example, [+]a, where a represents
                # the ExprTuple of operands for the '+' operation.
                if format_type == 'string':
                    return '[' + operator.string(
                        fence=True) + ']' + operands.string(fence=False,
                                                            sub_fence=False)
                else:
                    return (r'\left[' + operator.latex(fence=True) +
                            r'\right]' +
                            operands.latex(fence=False, sub_fence=False))
        else:
            operators = operator_or_operators
            operands = composite_expression(operands)
            # Multiple operator case.
            # Different formatting when there is 0 or 1 element, unless it is
            # an ExprRange
            if operands.num_entries() < 2:
                if operands.num_entries() == 0 or not isinstance(
                        operands[0], ExprRange):
                    raise OperationError(
                        "No defaut formatting with multiple operators and zero operands"
                    )
            fence = kwargs['fence'] if 'fence' in kwargs else False
            sub_fence = kwargs['sub_fence'] if 'sub_fence' in kwargs else True
            do_wrapping = len(wrap_positions) > 0
            formatted_str = ''
            if fence:
                formatted_str = '(' if format_type == 'string' else r'\left('
            if do_wrapping and format_type == 'latex':
                formatted_str += r'\begin{array}{%s} ' % justification[0]
            formatted_str += operands.formatted(
                format_type,
                fence=False,
                sub_fence=sub_fence,
                operator_or_operators=operators,
                implicit_first_operator=implicit_first_operator,
                wrap_positions=wrap_positions)
            if do_wrapping and format_type == 'latex':
                formatted_str += r' \end{array}'
            if fence:
                formatted_str += ')' if format_type == 'string' else r'\right)'
            return formatted_str

    def basic_replaced(self,
                       repl_map,
                       *,
                       allow_relabeling=False,
                       requirements=None):
        '''
        Returns this expression with sub-expressions substituted
        according to the replacement map (repl_map) dictionary.

        When an operater of an Operation is substituted by a Lambda map,
        the operation itself will be substituted with the Lambda map
        applied to the operands.  For example, substituting
        f : (x,y) -> x+y
        on f(a, b) will result in a+b.  When performing operation
        substitution with a range of parameters, the Lambda map
        application will require the number of these parameters
        to equal with the number of corresponding operand elements.
        For example,
        f : (a, b_1, ..., b_n) -> a*b_1 + ... + a*b_n
        n : 3
        applied to f(w, x, y, z) will result in w*x + w*y + w*z provided
        that |(b_1, ..., b_3)| = |(x, y, z)| is proven.
        Assumptions may be needed to prove such requirements.
        Requirements will be appended to the 'requirements' list if one
        is provided.

        There are limitations with respect the Lambda map application involving
        iterated parameters when perfoming operation substitution in order to
        keep derivation rules (i.e., instantiation) simple.  For details,
        see the ExprRange.substituted documentation.
        '''
        from proveit import Lambda, ExprRange

        if len(repl_map) > 0 and (self in repl_map):
            # The full expression is to be substituted.
            return repl_map[self]

        # Perform substitutions for the operator(s) and operand(s).
        subbed_operator = \
            self.operator.basic_replaced(
                    repl_map, allow_relabeling=allow_relabeling,
                    requirements=requirements)
        subbed_operands = \
            self.operands.basic_replaced(
                    repl_map, allow_relabeling=allow_relabeling,
                    requirements=requirements)

        # Check if the operator is being substituted by a Lambda map in
        # which case we should perform full operation substitution.
        if isinstance(subbed_operator, Lambda):
            # Substitute the entire operation via a Lambda map
            # application.  For example, f(x, y) -> x + y,
            # or g(a, b_1, ..., b_n) -> a * b_1 + ... + a * b_n.

            if isinstance(subbed_operator.body, ExprRange):
                raise ImproperReplacement(
                    self, repl_map,
                    "The function %s cannot be defined using this "
                    "lambda, %s, that has an ExprRange for its body; "
                    "that could lead to tuple length contradictions." %
                    (self.operator, subbed_operator))
            return Lambda._apply(subbed_operator.parameters,
                                 subbed_operator.body,
                                 *subbed_operands.entries,
                                 requirements=requirements)

        # If the operator is a literal operator of
        # an Operation class defined via an "_operator_" class
        # attribute, then create the Operation of that class.
        if subbed_operator in Operation.operation_class_of_operator:
            op_class = Operation.operation_class_of_operator[subbed_operator]
            if op_class != self.__class__:
                # Don't transfer the styles; they may not apply in
                # the same manner in the setting of the new
                # operation.
                subbed_sub_exprs = (subbed_operator, subbed_operands)
                return op_class._checked_make(
                    ['Operation'],
                    sub_expressions=subbed_sub_exprs,
                    style_preferences=self._style_data.styles)

        subbed_sub_exprs = (subbed_operator, subbed_operands)
        return self.__class__._checked_make(
            self._core_info,
            subbed_sub_exprs,
            style_preferences=self._style_data.styles)

    @equality_prover('evaluated', 'evaluate')
    def evaluation(self, **defaults_config):
        '''
        If possible, return a Judgment of this expression equal to an
        irreducible value.  This Operation.evaluation version
        simplifies the operands and then calls shallow_simplification
        with must_evaluat=True.
        '''
        from proveit import UnsatisfiedPrerequisites, ProofFailure
        from proveit.logic import EvaluationError, SimplificationError

        # Try to simplify the operands first.
        reduction = self.simplification_of_operands(
            simplify_with_known_evaluations=True)

        # After making sure the operands have been simplified,
        # try 'shallow_simplification' with must_evaluate=True.
        try:
            if reduction.lhs == reduction.rhs:
                # _no_eval_check is a directive to the @equality_prover wrapper
                # to tell it not to check for an existing evaluation if we have
                # already checked.
                return self.shallow_simplification(must_evaluate=True,
                                                   _no_eval_check=True)
            evaluation = reduction.rhs.shallow_simplification(
                must_evaluate=True)
        except (SimplificationError, UnsatisfiedPrerequisites,
                NotImplementedError, ProofFailure):
            raise EvaluationError(self)
        return reduction.apply_transitivity(evaluation)

    @equality_prover('simplified', 'simplify')
    def simplification(self, **defaults_config):
        '''
        If possible, return a Judgment of this expression equal to a
        simplified form (according to strategies specified in 
        proveit.defaults). 
        
        This Operation.simplification version tries calling
        simplifies the operands and then calls 'shallow_simplification'.
        '''
        # Try to simplify the operands first.
        reduction = self.simplification_of_operands()

        # After making sure the operands have been simplified,
        # try 'shallow_simplification'.
        # Use the 'reduction' as a replacement in case it is needed.
        # For example, consider
        #       1*b + 3*b
        #   It's reduction is
        #       1*b + 3*b = b + 3*b
        #   But in the shallow simplification, we'll do a factorization
        #   that will exploit the "reduction" fact which wouldn't
        #   otherwise be used because (1*b + 3*b) is a preserved
        #   expression since simplification is an @equality_prover.

        if reduction.lhs == reduction.rhs:
            # _no_eval_check is a directive to the @equality_prover wrapper
            # to tell it not to check for an existing evaluation if we have
            # already checked.
            return self.shallow_simplification(_no_eval_check=True)
        else:
            simplification = reduction.rhs.shallow_simplification(
                replacements=[reduction])
            return reduction.apply_transitivity(simplification)

    @equality_prover('simplified_operands', 'operands_simplify')
    def simplification_of_operands(self, **defaults_config):
        '''
        Prove this Operation equal to a form in which its operands
        have been simplified.
        '''
        from proveit.relation import TransRelUpdater
        from proveit import ExprRange, NamedExprs
        from proveit.logic import is_irreducible_value
        if any(isinstance(operand, ExprRange) for operand in self.operands):
            # If there is any ExprRange in the operands, simplify the
            # operands together as an ExprTuple.
            return self.inner_expr().operands[:].simplification()
        else:
            expr = self
            eq = TransRelUpdater(expr)
            with defaults.temporary() as temp_defaults:
                # No auto-simplification or replacements here;
                # just simplify operands one at a time.
                temp_defaults.preserve_all = True
                operands = self.operands
                if isinstance(operands, NamedExprs):
                    # operands as NamedExprs
                    for key in operands.keys():
                        operand = operands[key]
                        if not is_irreducible_value(operand):
                            inner_operand = getattr(expr.inner_expr(), key)
                            expr = eq.update(inner_operand.simplification())
                else:
                    # operands as ExprTuple
                    for k, operand in enumerate(operands):
                        if not is_irreducible_value(operand):
                            inner_operand = expr.inner_expr().operands[k]
                            expr = eq.update(inner_operand.simplification())
        return eq.relation

    @equality_prover('operator_substituted', 'operator_substitute')
    def operator_substitution(self, equality, **defaults_config):
        from proveit import f, g, n, x
        from proveit.core_expr_types.operations import (operator_substitution)
        _n = self.operands.num_elements()
        if equality.lhs == self.operator:
            return operator_substitution.instantiate({
                n: _n,
                x: self.operands,
                f: equality.lhs,
                g: equality.rhs
            })
        elif equality.rhs == self.operator:
            return operator_substitution.instantiate({
                n: _n,
                x: self.operands,
                f: equality.rhs,
                g: equality.lhs
            })
        else:
            raise ValueError("%s is not an appropriate 'equality' for "
                             "operator_substitution on %s (the 'operator' "
                             "is not matched on either side)" %
                             (equality, self))

    @equality_prover('operands_substituted', 'operands_substitute')
    def operands_substitution(self, equality, **defaults_config):
        from proveit import f, n, x, y
        from proveit.core_expr_types.operations import (operands_substitution)
        from proveit.logic.equality import substitution
        if equality.lhs == self.operands:
            _x, _y = equality.lhs, equality.rhs
        elif equality.rhs == self.operands:
            _x, _y = equality.rhs, equality.lhs
        else:
            raise ValueError("%s is not an appropriate 'equality' for "
                             "operator_substitution on %s (the 'operator' "
                             "is not matched on either side)" %
                             (equality, self))

        if self.operands.is_single():
            # This is a simple single-operand substitution.
            return substitution.instantiate({
                f: self.operator,
                x: _x[0],
                y: _y[0]
            })

        # More general mult-operand substitution:
        _n = self.operands.num_elements()
        if equality.lhs == self.operands:
            return operands_substitution.instantiate({
                n: _n,
                f: self.operator,
                x: equality.lhs,
                y: equality.rhs
            })
        elif equality.rhs == self.operands:
            return operands_substitution.instantiate({
                n: _n,
                f: self.operator,
                x: equality.rhs,
                y: equality.lhs
            })
        else:
            raise ValueError("%s is not an appropriate 'equality' for "
                             "operator_substitution on %s (the 'operator' "
                             "is not matched on either side)" %
                             (equality, self))

    @equality_prover('sub_expr_substituted', 'sub_expr_substitute')
    def sub_expr_substitution(self, new_sub_exprs, **defaults_config):
        '''
        Given new sub-expressions to replace existing sub-expressions,
        return the equality between this Expression and the new
        one with the new sub-expressions.
        '''
        from proveit.logic import Equals
        from proveit.relation import TransRelUpdater
        assert len(new_sub_exprs) == 2, (
            "Expecting 2 sub-expressions: operator and operands")
        eq = TransRelUpdater(self)
        expr = self
        if new_sub_exprs[0] != self.sub_expr(0):
            expr = eq.update(
                expr.operator_substitution(
                    Equals(self.sub_expr(0), new_sub_exprs[0])))
        if new_sub_exprs[1] != self.sub_expr(1):
            expr = eq.update(
                expr.operands_substitution(
                    Equals(self.sub_expr(1), new_sub_exprs[1])))
        return eq.relation

    def operands_are_irreducible(self):
        '''
        Return True iff all of the operands of this Operation are
        irreducible.
        '''
        from proveit.logic import is_irreducible_value
        return all(
            is_irreducible_value(operand) for operand in self.operands.entries)
예제 #8
0
def bundle(expr, bundle_thm, num_levels=2, *, assumptions=USE_DEFAULTS):
    '''
    Given a nested OperationOverInstances, derive or equate an
    equivalent form in which a given number of nested levels is
    bundled together.  Use the given theorem specific to the particular
    OperationOverInstances.

    For example,
        \forall_{x, y | Q(x, y)} \forall_{z | R(z)} P(x, y, z)
    can become
        \forall_{x, y, z | Q(x, y), R(z)} P(x, y, z)
    via bundle with num_levels=2.

    For example of the form of the theorem required, see
    proveit.logic.boolean.quantification.bundling or
    proveit.logic.boolean.quantification.bundling_equality.
    '''
    from proveit.relation import TransRelUpdater
    from proveit.logic import Implies, Equals
    # Make a TransRelUpdater only if the bundle_thm yield an
    # equation, in which case we'll want the result to be an equation.
    eq = None
    bundled = expr
    while num_levels >= 2:
        if (not isinstance(bundled, OperationOverInstances) or
                not isinstance(bundled.instanceExpr, OperationOverInstances)):
            raise ValueError(
                "May only 'bundle' nested OperationOverInstances, "
                "not %s" % bundled)
        _m = bundled.instanceParams.length()
        _n = bundled.instanceExpr.instanceParams.length()
        _P = bundled.instanceExpr.instanceExpr
        _Q = bundled.effectiveCondition()
        _R = bundled.instanceExpr.effectiveCondition()
        m, n = bundle_thm.instanceVars
        P, Q, R = bundle_thm.instanceExpr.instanceVars
        correspondence = bundle_thm.instanceExpr.instanceExpr
        if isinstance(correspondence, Implies):
            if (not isinstance(correspondence.antecedent,
                               OperationOverInstances)
                    or not len(correspondence.consequent.instanceParams) == 2):
                raise ValueError("'bundle_thm', %s, does not have the "
                                 "expected form with the bundled form as "
                                 "the consequent of the implication, %s" %
                                 (bundle_thm, correspondence))
            x_1_to_m, y_1_to_n = correspondence.consequent.instanceParams
        elif isinstance(correspondence, Equals):
            if not isinstance(
                    correspondence.rhs, OperationOverInstances
                    or not len(correspondence.antecedent.instanceParams) == 2):
                raise ValueError("'bundle_thm', %s, does not have the "
                                 "expected form with the bundled form on "
                                 "right of the an equality, %s" %
                                 (bundle_thm, correspondence))
            x_1_to_m, y_1_to_n = correspondence.rhs.instanceParams

        all_params = bundled.instanceParams + bundled.instanceExpr.instanceParams
        Pxy = Function(P, all_params)
        Qx = Function(Q, bundled.instanceParams)
        Rxy = Function(R, all_params)
        x_1_to_m = x_1_to_m.replaced({m: _m})
        y_1_to_n = y_1_to_n.replaced({n: _n})
        instantiation = bundle_thm.instantiate(
            {
                m: _m,
                n: _n,
                ExprTuple(x_1_to_m): bundled.instanceParams,
                ExprTuple(y_1_to_n): bundled.instanceExpr.instanceParams,
                Pxy: _P,
                Qx: _Q,
                Rxy: _R
            },
            assumptions=assumptions)
        if isinstance(instantiation.expr, Implies):
            bundled = instantiation.deriveConsequent()
        elif isinstance(instantiation.expr, Equals):
            if eq is None:
                eq = TransRelUpdater(bundled)
            try:
                bundled = eq.update(instantiation)
            except ValueError:
                raise ValueError(
                    "Instantiation of bundle_thm %s is %s but "
                    "should match %s on one side of the equation." %
                    (bundle_thm, instantiation, bundled))
        else:
            raise ValueError("Instantiation of bundle_thm %s is %s but "
                             "should be an Implies or Equals expression." %
                             (bundle_thm, instantiation))
        num_levels -= 1
    if eq is None:
        # Return the bundled result.
        return bundled
    else:
        # Return the equality between the original expression and
        # the bundled result.
        return eq.relation
예제 #9
0
def unbundle(expr,
             unbundle_thm,
             num_param_entries=(1, ),
             *,
             assumptions=USE_DEFAULTS):
    '''
    Given a nested OperationOverInstances, derive or equate an
    equivalent form in which the parameter entries are split in
    number according to 'num_param_entries'.  Use the given theorem
    specific to the particular OperationOverInstances.

    For example,
        \forall_{x, y, z | Q(x, y), R(z)} P(x, y, z)
    can become
        \forall_{x, y | Q(x, y)} \forall_{z | R(z)} P(x, y, z)
    via bundle with num_param_entries=(2, 1) or
    num_param_entries=(2,) -- the last number can be implied
    by the remaining number of parameters.

    For example of the form of the theorem required, see
    proveit.logic.boolean.quantification.unbundling or
    proveit.logic.boolean.quantification.bundling_equality.
    '''
    from proveit.relation import TransRelUpdater
    from proveit.logic import Implies, Equals, And
    # Make a TransRelUpdater only if the bundle_thm yield an
    # equation, in which case we'll want the result to be an equation.
    eq = None
    unbundled = expr
    net_indicated_param_entries = sum(num_param_entries)
    num_actual_param_entries = len(expr.instanceParams)
    for n in num_param_entries:
        if not isinstance(n, int) or n <= 0:
            raise ValueError(
                "Each of 'num_param_entries', must be an "
                "integer greater than 0.  %s fails this requirement." %
                (num_param_entries))
    if net_indicated_param_entries > num_actual_param_entries:
        raise ValueError(
            "Sum of 'num_param_entries', %s=%d should not "
            "be greater than the number of parameter entries "
            "of %s for unbundling." %
            (num_param_entries, net_indicated_param_entries, expr))
    if net_indicated_param_entries < num_actual_param_entries:
        diff = num_actual_param_entries - net_indicated_param_entries
        num_param_entries = list(num_param_entries) + [diff]
    else:
        num_param_entries = list(num_param_entries)
    while len(num_param_entries) > 1:
        n_last_entries = num_param_entries.pop(-1)
        first_params = ExprTuple(*unbundled.instanceParams[:-n_last_entries])
        first_param_vars = {getParamVar(param) for param in first_params}
        remaining_params = \
            ExprTuple(*unbundled.instanceParams[-n_last_entries:])
        _m = first_params.length()
        _n = remaining_params.length()
        _P = unbundled.instanceExpr
        # Split up the conditions between the outer
        # OperationOverInstances and inner OperationOverInstances
        condition = unbundled.effectiveCondition()
        if isinstance(condition, And):
            _nQ = 0
            for cond in condition.operands:
                cond_vars = free_vars(cond, err_inclusively=True)
                if first_param_vars.isdisjoint(cond_vars): break
                _nQ += 1
            if _nQ == 0:
                _Q = And()
            elif _nQ == 1:
                _Q = condition.operands[0]
            else:
                _Q = And(*condition.operands[:_nQ])
            _nR = len(condition.operands) - _nQ
            if _nR == 0:
                _R = And()
            elif _nR == 1:
                _R = condition.operands[-1]
            else:
                _R = And(*condition.operands[_nQ:])
        elif first_param_vars.isdisjoint(
                free_vars(condition, err_inclusively=True)):
            _Q = condition
            _R = And()
        else:
            _Q = And()
            _R = condition
        m, n = unbundle_thm.instanceVars
        P, Q, R = unbundle_thm.instanceExpr.instanceVars
        correspondence = unbundle_thm.instanceExpr.instanceExpr
        if isinstance(correspondence, Implies):
            if (not isinstance(correspondence.antecedent,
                               OperationOverInstances)
                    or not len(correspondence.antecedent.instanceParams) == 2):
                raise ValueError("'unbundle_thm', %s, does not have the "
                                 "expected form with the bundled form as "
                                 "the antecedent of the implication, %s" %
                                 (unbundle_thm, correspondence))
            x_1_to_m, y_1_to_n = correspondence.antecedent.instanceParams
        elif isinstance(correspondence, Equals):
            if not isinstance(
                    correspondence.rhs, OperationOverInstances
                    or not len(correspondence.antecedent.instanceParams) == 2):
                raise ValueError("'unbundle_thm', %s, does not have the "
                                 "expected form with the bundled form on "
                                 "right of the an equality, %s" %
                                 (unbundle_thm, correspondence))
            x_1_to_m, y_1_to_n = correspondence.rhs.instanceParams
        else:
            raise ValueError("'unbundle_thm', %s, does not have the expected "
                             "form with an equality or implication  "
                             "correspondence, %s" %
                             (unbundle_thm, correspondence))

        Qx = Function(Q, first_params)
        Rxy = Function(R, unbundled.instanceParams)
        Pxy = Function(P, unbundled.instanceParams)
        x_1_to_m = x_1_to_m.replaced({m: _m})
        y_1_to_n = y_1_to_n.replaced({n: _n})
        instantiation = unbundle_thm.instantiate(
            {
                m: _m,
                n: _n,
                ExprTuple(x_1_to_m): first_params,
                ExprTuple(y_1_to_n): remaining_params,
                Pxy: _P,
                Qx: _Q,
                Rxy: _R
            },
            assumptions=assumptions)
        if isinstance(instantiation.expr, Implies):
            unbundled = instantiation.deriveConsequent()
        elif isinstance(instantiation.expr, Equals):
            if eq is None:
                eq = TransRelUpdater(unbundled)
            try:
                unbundled = eq.update(instantiation)
            except ValueError:
                raise ValueError(
                    "Instantiation of bundle_thm %s is %s but "
                    "should match %s on one side of the equation." %
                    (unbundle_thm, instantiation, unbundled))
        else:
            raise ValueError("Instantiation of bundle_thm %s is %s but "
                             "should be an Implies or Equals expression." %
                             (unbundle_thm, instantiation))
    if eq is None:
        # Return the unbundled result.
        return unbundled
    else:
        # Return the equality between the original expression and
        # the unbundled result.
        return eq.relation
예제 #10
0
    def _formatted(self, formatType, fence=False):
        '''
        Format the OperationOverInstances according to the style
        which may join nested operations of the same type.
        '''
        # override this default as desired
        explicitIparams = list(self.explicitInstanceParams())
        explicitConditions = ExprTuple(*self.explicitConditions())
        explicitDomains = ExprTuple(*self.explicitDomains())
        instanceExpr = self.instanceExpr
        hasExplicitIparams = (len(explicitIparams) > 0)
        hasExplicitConditions = (len(explicitConditions) > 0)
        hasMultiDomain = (len(explicitDomains) > 1 and
                          (not hasattr(self, 'domain') or explicitDomains !=
                           ExprTuple(*[self.domain] * len(explicitDomains))))
        domain_conditions = ExprTuple(*self.domainConditions())
        outStr = ''
        formattedParams = ', '.join([
            param.formatted(formatType, abbrev=True)
            for param in explicitIparams
        ])
        if formatType == 'string':
            if fence: outStr += '['
            outStr += self.operator.formatted(formatType) + '_{'
            if hasExplicitIparams:
                if hasMultiDomain:
                    outStr += domain_conditions.formatted(
                        formatType, operatorOrOperators=',', fence=False)
                else:
                    outStr += formattedParams
            if not hasMultiDomain and self.domain is not None:
                outStr += ' in '
                if hasMultiDomain:
                    outStr += explicitDomains.formatted(
                        formatType, operatorOrOperators='*', fence=False)
                else:
                    outStr += self.domain.formatted(formatType, fence=False)
            if hasExplicitConditions:
                if hasExplicitIparams: outStr += " | "
                outStr += explicitConditions.formatted(formatType, fence=False)
                #outStr += ', '.join(condition.formatted(formatType) for condition in self.conditions if condition not in implicitConditions)
            outStr += '} ' + instanceExpr.formatted(formatType, fence=True)
            if fence: outStr += ']'
        if formatType == 'latex':
            if fence: outStr += r'\left['
            outStr += self.operator.formatted(formatType) + '_{'
            if hasExplicitIparams:
                if hasMultiDomain:
                    outStr += domain_conditions.formatted(
                        formatType, operatorOrOperators=',', fence=False)
                else:
                    outStr += formattedParams
            if not hasMultiDomain and self.domain is not None:
                outStr += r' \in '
                outStr += self.domain.formatted(formatType, fence=False)
            if hasExplicitConditions:
                if hasExplicitIparams: outStr += "~|~"
                outStr += explicitConditions.formatted(formatType, fence=False)
                #outStr += ', '.join(condition.formatted(formatType) for condition in self.conditions if condition not in implicitConditions)
            outStr += '}~' + instanceExpr.formatted(formatType, fence=True)
            if fence: outStr += r'\right]'

        return outStr
예제 #11
0
    def __init__(self,
                 operator,
                 instanceParamOrParams,
                 instanceExpr,
                 *,
                 domain=None,
                 domains=None,
                 condition=None,
                 conditions=None,
                 styles=None,
                 _lambda_map=None):
        '''
        Create an Operation for the given operator that is applied over
        instances of the given instance parameter(s), instanceParamOrParams,
        for the given instance Expression,  instanceExpr under the given
        conditions.  That is, the operation operates over all possibilities of
        given Variable(s) wherever the condition(s) is/are satisfied.  Examples
        include forall, exists, summation, etc. instanceParamOrParams may be
        singular or plural (iterable).  Each parameter may be a Variable or
        Iter over IndexedVars (just as a Lambda parameter).  An
        OperationOverInstances is effected as an Operation over a Lambda map
        with a conditional body.

        If a 'domain' is supplied, additional conditions are generated that
        each instance parameter is in the domain "set": InSet(x_i, domain),
        where x_i is for each instance parameter.  If, instead, 'domains' are
        supplied, then each instance parameter is supplied with its own domain
        (one for each instance parameter).  Whether the OperationOverInstances
        is constructed with domain/domains explicitly, or they are provided as
        conditions in the proper order does not matter.  Essentially, the
        'domain' concept is simply a convenience for conditions of this form
        and may be formatted using a shorthand notation.
        For example, "forall_{x in S | Q(x)} P(x)" is a shorthand notation for
        "forall_{x | x in S, Q(x)} P(x)".

        _lambda_map is used internally for efficiently rebuilding an
        OperationOverInstances expression.
        '''
        from proveit.logic import InSet
        from proveit._core_.expression.lambda_expr.lambda_expr import getParamVar

        if styles is None: styles = dict()
        if condition is not None:
            if conditions is not None:
                raise ValueError("Cannot specify both 'conditions' and "
                                 "'condition'")
            conditions = (condition, )
        elif conditions is None:
            conditions = tuple()

        if _lambda_map is not None:
            # Use the provided 'lambda_map' instead of creating one.
            from proveit.logic import And
            lambda_map = _lambda_map
            instance_params = lambda_map.parameters
            if isinstance(lambda_map.body, Conditional):
                # Has conditions.
                instanceExpr = lambda_map.body.value
                if (isinstance(lambda_map.body.condition, And)
                        and not lambda_map.body.condition.operands.singular()):
                    conditions = compositeExpression(
                        lambda_map.body.condition.operands)
                else:
                    conditions = compositeExpression(lambda_map.body.condition)
            else:
                # No conditions.
                instanceExpr = lambda_map.body
                conditions = ExprTuple()
        else:
            # We will need to generate the Lambda sub-expression.
            # Do some initial preparations w.r.t. instanceParams, domain(s), and
            # conditions.
            instance_params = compositeExpression(instanceParamOrParams)
            if len(instance_params) == 0:
                raise ValueError(
                    "Expecting at least one instance parameter when "
                    "constructing an OperationOverInstances")

            # Add appropriate conditions for the domains:
            if domain is not None:
                # prepend domain conditions
                if domains is not None:
                    raise ValueError(
                        "Provide a single domain or multiple domains, "
                        "not both")
                if not isinstance(domain, Expression):
                    raise TypeError(
                        "The domain should be an 'Expression' type")
                domains = [domain] * len(instance_params)

            if domains is not None:
                # Prepend domain conditions.  Note that although we start with
                # all domain conditions at the beginning,
                # some may later get pushed back as "inner conditions"
                # (see below),
                if len(domains) != len(instance_params):
                    raise ValueError(
                        "When specifying multiple domains, the number "
                        "should be the same as the number of instance "
                        "variables.")
                for domain in domains:
                    if domain is None:
                        raise ValueError(
                            "When specifying multiple domains, none "
                            "of them can be the None value")
                domain_conditions = []
                for iparam, domain in zip(instance_params, domains):
                    if isinstance(iparam, ExprRange):
                        condition = ExprRange(iparam.parameter,
                                              InSet(iparam.body, domain),
                                              iparam.start_index,
                                              iparam.end_index)
                    else:
                        condition = InSet(iparam, domain)
                    domain_conditions.append(condition)
                conditions = domain_conditions + list(conditions)
                domain = domains[
                    0]  # domain of the outermost instance variable
            conditions = compositeExpression(conditions)

        # domain(s) may be implied via the conditions.  If domain(s) were
        # supplied, this should simply reproduce them from the conditions that
        # were prepended.
        domain = domains = None  # These may be reset below if there are ...
        if (len(conditions) >= len(instance_params)):
            domains = [
                _extract_domain_from_condition(ivar, cond)
                for ivar, cond in zip(instance_params, conditions)
            ]
            if all(domain is not None for domain in domains):
                # Used if we have a single instance variable
                domain = domains[0]
            else:
                domains = None

        if _lambda_map is None:
            # Now do the actual lambda_map creation
            if len(instance_params) == 1:
                instanceParamOrParams = instance_params[0]
            # Generate the Lambda sub-expression.
            lambda_map = OperationOverInstances._createOperand(
                instanceParamOrParams, instanceExpr, conditions)

        self.instanceExpr = instanceExpr
        '''Expression corresponding to each 'instance' in the OperationOverInstances'''

        self.instanceParams = instance_params
        if len(instance_params) > 1:
            '''Instance parameters of the OperationOverInstance.'''
            self.instanceVars = [
                getParamVar(parameter) for parameter in instance_params
            ]
            self.instanceParamOrParams = self.instanceParams
            self.instanceVarOrVars = self.instanceVars
            '''Instance parameter variables of the OperationOverInstance.'''
            if domains is not None:
                self.domains = domains  # Domain for each instance variable
                '''Domains of the instance parameters (may be None)'''
            else:
                self.domain = None
        else:
            self.instanceParam = instance_params[0]
            '''Outermost instance parameter of the OperationOverInstance.'''
            self.instanceVar = getParamVar(self.instanceParam)
            self.instanceParamOrParams = self.instanceParam
            self.instanceVarOrVars = self.instanceVar
            '''Outermost instance parameter variable of the OperationOverInstance.'''
            self.domain = domain
            '''Domain of the outermost instance parameter (may be None)'''

        self.conditions = conditions
        '''Conditions applicable to the outermost instance variable (or iteration of indexed variables) of the OperationOverInstance.  May include an implicit 'domain' condition.'''

        if isinstance(lambda_map.body, Conditional):
            self.condition = lambda_map.body.condition

        Operation.__init__(self, operator, lambda_map, styles=styles)
예제 #12
0
    def _replaced(self, repl_map, allow_relabeling, assumptions, requirements,
                  equality_repl_requirements):
        '''
        Returns this expression with sub-expressions substituted 
        according to the replacement map (repl_map) dictionary.
        
        When an operater of an Operation is substituted by a Lambda map, 
        the operation itself will be substituted with the Lambda map 
        applied to the operands.  For example, substituting 
        f : (x,y) -> x+y
        on f(a, b) will result in a+b.  When performing operation
        substitution with a range of parameters, the Lambda map 
        application will require the number of these parameters 
        to equal with the number of corresponding operand elements.  
        For example,
        f : (a, b_1, ..., b_n) -> a*b_1 + ... + a*b_n
        n : 3
        applied to f(w, x, y, z) will result in w*x + w*y + w*z provided
        that |(b_1, ..., b_3)| = |(x, y, z)| is proven.
        Assumptions may be needed to prove such requirements.  
        Requirements will be appended to the 'requirements' list if one 
        is provided.
        
        There are limitations with respect the Lambda map application involving
        iterated parameters when perfoming operation substitution in order to
        keep derivation rules (i.e., instantiation) simple.  For details,
        see the ExprRange.substituted documentation.
        '''
        from proveit import (Lambda, singleOrCompositeExpression,
                             compositeExpression, ExprTuple, ExprRange)

        if len(repl_map) > 0 and (self in repl_map):
            # The full expression is to be substituted.
            return repl_map[self]

        # Perform substitutions for the operator(s) and operand(s).
        subbed_operator_or_operators = \
            self.operator_or_operators.replaced(repl_map, allow_relabeling,
                                                assumptions, requirements,
                                                equality_repl_requirements)
        subbed_operand_or_operands = \
            self.operand_or_operands.replaced(repl_map, allow_relabeling,
                                              assumptions, requirements,
                                              equality_repl_requirements)
        subbed_operators = compositeExpression(subbed_operator_or_operators)

        # Check if the operator is being substituted by a Lambda map in
        # which case we should perform full operation substitution.
        if len(subbed_operators) == 1:
            subbed_operator = subbed_operators[0]
            if isinstance(subbed_operator, Lambda):
                # Substitute the entire operation via a Lambda map
                # application.  For example, f(x, y) -> x + y,
                # or g(a, b_1, ..., b_n) -> a * b_1 + ... + a * b_n.

                if isinstance(subbed_operator.body, ExprRange):
                    raise ImproperReplacement(
                        self, repl_map,
                        "The function %s cannot be defined using this "
                        "lambda, %s, that has an ExprRange for its body; "
                        "that could lead to tuple length contradictions." %
                        (self.operator, subbed_operator))
                if len(self.operands)==1 and \
                        not isinstance(self.operands[0], ExprRange):
                    # A single operand case (even if that operand
                    # happens to be a tuple).
                    subbed_operands = [subbed_operand_or_operands]
                else:
                    subbed_operands = subbed_operand_or_operands
                return Lambda._apply(
                    subbed_operator.parameters,
                    subbed_operator.body,
                    *subbed_operands,
                    assumptions=assumptions,
                    requirements=requirements,
                    equality_repl_requirements=equality_repl_requirements)

        had_singular_operand = hasattr(self, 'operand')
        if (had_singular_operand
                and isinstance(subbed_operand_or_operands, ExprTuple)
                and not isinstance(self.operand_or_operands, ExprTuple)):
            # If a singular operand is replaced with an ExprTuple,
            # we must wrap an extra ExprTuple around it to indicate
            # that it is still a singular operand with the operand
            # as the ExprTuple (rather than expanding to multiple
            # operands).
            subbed_operand_or_operands = ExprTuple(subbed_operand_or_operands)
        else:
            # Possibly collapse multiple operands to a single operand
            # via "do_singular_reduction=True".
            subbed_operand_or_operands = singleOrCompositeExpression(
                subbed_operand_or_operands, do_singular_reduction=True)

        # Remake the Expression with substituted operator and/or
        # operands
        if len(subbed_operators) == 1:
            # If it is a single operator that is a literal operator of
            # an Operation class defined via an "_operator_" class
            # attribute, then create the Operation of that class.
            operator = subbed_operators[0]
            if operator in Operation.operationClassOfOperator:
                op_class = Operation.operationClassOfOperator[operator]
                if op_class != self.__class__:
                    # Don't transfer the styles; they may not apply in
                    # the same manner in the setting of the new
                    # operation.
                    subbed_sub_exprs = (operator, subbed_operand_or_operands)
                    substituted = op_class._checked_make(
                        ['Operation'],
                        styles=None,
                        subExpressions=subbed_sub_exprs)
                    return substituted._auto_reduced(
                        assumptions, requirements, equality_repl_requirements)

        subbed_sub_exprs = (subbed_operator_or_operators,
                            subbed_operand_or_operands)
        substituted = self.__class__._checked_make(self._coreInfo,
                                                   self.getStyles(),
                                                   subbed_sub_exprs)
        return substituted._auto_reduced(assumptions, requirements,
                                         equality_repl_requirements)
예제 #13
0
class Iter(Expression):
    '''
    An Iter Expression represents an iteration of  Expressions to be 
    inserted into a containing composite Expression.
    
    Upon substitution, it automatically expands to if it contains an 
    Indexed expression whose variable is substituted with a tuple or 
    array.  To ensure such an expansion is unambiguous and sensibly 
    defined, Iter expressions must only contain Indexed variables with
    indices that are 'simple' functions of an iteration (the parameter 
    itself or a sum of terms with one term as the parameter).
    For example
    a_1*b_1 + ... + a_n*b_n
    when substituted with 
    a:(x_1, ..., x_j, y_1, ..., y_k)
    b:(z_1, ..., z_k, x_1, ..., x_j)
    under the assumption that j<k will be transformed into
    x_1*z_1 + ... + x_j*z_j +
    y_1*z_{j+1} + ... + y_{k-j}*z_{j+(k-j)} +
    y_{k-j+1}*x_1 + ... + y_{k-j+j}*x_j
    '''
    def __init__(self,
                 parameter_or_parameters,
                 body,
                 start_index_or_indices,
                 end_index_or_indices,
                 styles=None,
                 requirements=tuple(),
                 _lambda_map=None):
        '''
        Create an Iter that represents an iteration of the body for the
        parameter(s) ranging from the start index/indices to the end 
        index/indices.  A Lambda expression will be created as its 
        sub-expression that maps the parameter(s) to the body with
        conditions that restrict the parameter(s) to the appropriate interval.
        
        _lambda_map is used internally for efficiently rebuilding an Iter.
        '''
        from proveit.logic import InSet
        from proveit.number import Interval

        if _lambda_map is not None:
            # Use the provided 'lambda_map' instead of creating one.
            lambda_map = _lambda_map
            pos_args = (parameter_or_parameters, body, start_index_or_indices,
                        end_index_or_indices)
            if pos_args != (None, None, None, None):
                raise ValueError(
                    "Positional arguments of the Init constructor "
                    "should be None if lambda_map is provided.")
            parameters = lambda_map.parameters
            body = lambda_map.body
            conditions = lambda_map.conditions
            if len(conditions) != len(parameters):
                raise ValueError(
                    "Inconsistent number of conditions and lambda "
                    "map parameters")
            start_indices, end_indices = [], []
            for param, condition in zip(parameters, conditions):
                invalid_condition_msg = (
                    "Not the right kind of lambda_map condition "
                    "for an iteration")
                if not isinstance(condition,
                                  InSet) or condition.element != param:
                    raise ValueError(invalid_condition_msg)
                domain = condition.domain
                if not isinstance(domain, Interval):
                    raise ValueError(invalid_condition_msg)
                start_index, end_index = domain.lowerBound, domain.upperBound
                start_indices.append(start_index)
                end_indices.append(end_index)
            self.start_indices = ExprTuple(*start_indices)
            self.end_indices = ExprTuple(*end_indices)
            if len(parameters) == 1:
                self.start_index = self.start_indices[0]
                self.end_index = self.end_indices[0]
                self.start_index_or_indices = self.start_index
                self.end_index_or_indices = self.end_index
            else:
                self.start_index_or_indices = self.start_indices
                self.end_index_or_indices = self.end_indices
        else:
            parameters = compositeExpression(parameter_or_parameters)

            start_index_or_indices = singleOrCompositeExpression(
                start_index_or_indices)
            if isinstance(start_index_or_indices,
                          ExprTuple) and len(start_index_or_indices) == 1:
                start_index_or_indices = start_index_or_indices[0]
            self.start_index_or_indices = start_index_or_indices
            if isinstance(start_index_or_indices, Composite):
                # a composite of multiple indices
                self.start_indices = self.start_index_or_indices
            else:
                # a single index
                self.start_index = self.start_index_or_indices
                # wrap a single index in a composite for convenience
                self.start_indices = compositeExpression(
                    self.start_index_or_indices)

            end_index_or_indices = singleOrCompositeExpression(
                end_index_or_indices)
            if isinstance(end_index_or_indices,
                          ExprTuple) and len(end_index_or_indices) == 1:
                end_index_or_indices = end_index_or_indices[0]
            self.end_index_or_indices = end_index_or_indices
            if isinstance(self.end_index_or_indices, Composite):
                # a composite of multiple indices
                self.end_indices = self.end_index_or_indices
            else:
                # a single index
                self.end_index = self.end_index_or_indices
                # wrap a single index in a composite for convenience
                self.end_indices = compositeExpression(
                    self.end_index_or_indices)

            conditions = []
            for param, start_index, end_index in zip(parameters,
                                                     self.start_indices,
                                                     self.end_indices):
                conditions.append(
                    InSet(param, Interval(start_index, end_index)))

            lambda_map = Lambda(parameters, body, conditions=conditions)

        self.ndims = len(self.start_indices)
        if self.ndims != len(self.end_indices):
            raise ValueError(
                "Inconsistent number of 'start' and 'end' indices")

        if len(parameters) != len(self.start_indices):
            raise ValueError(
                "Inconsistent number of indices and lambda map parameters")

        Expression.__init__(self, ['Iter'], [lambda_map],
                            styles=styles,
                            requirements=requirements)
        self.lambda_map = lambda_map
        self._checkIndexedRestriction(body)

    """
    def __init__(self, lambda_map, start_index_or_indices, end_index_or_indices, styles=None, requirements=tuple()):
        if not isinstance(lambda_map, Lambda):
            raise TypeError('When creating an Iter Expression, the lambda_map argument must be a Lambda expression')
        
        if isinstance(start_index_or_indices, ExprTuple) and len(start_index_or_indices)==1:
            start_index_or_indices = start_index_or_indices[0]
        self.start_index_or_indices = singleOrCompositeExpression(start_index_or_indices)
        
        self.ndims = len(self.start_indices)
        if self.ndims != len(self.end_indices):
            raise ValueError("Inconsistent number of 'start' and 'end' indices")
        
        if len(lambda_map.parameters) != len(self.start_indices):
            raise ValueError("Inconsistent number of indices and lambda map parameters")
        
        Expression.__init__(self, ['Iter'], [lambda_map, self.start_index_or_indices, self.end_index_or_indices], styles=styles, requirements=requirements)
        self.lambda_map = lambda_map
    """

    def _checkIndexedRestriction(self, subExpr):
        '''
        An iteration is restricted to not contain any Indexed variable
        that is a complicated function of the iteration parameter.
        Specifically, for each parameter, the index of an Indexed 
        variable may  be a function solely of that parameter or that 
        parameter added with terms that don't contain the parameter.
        For example, you can have x_1, ..., x_n and you can have
        x_{1+k}, ..., x_{n+k} or x_{k+1}, ..., x_{k+n} but not
        x_{1^2}, ..., x_{n^2} or x_{2*1}, ..., x_{2*n}.
        '''
        from .indexed import Indexed
        from proveit.number import Add
        if isinstance(subExpr, Indexed):
            for index in subExpr.indices:
                for parameter in self.lambda_map.parameters:
                    if index == parameter:
                        # It is fine for the index to simply be the
                        # parameter.  That is a simple case.
                        continue
                    if isinstance(index, Add):
                        terms_to_check = list(index.operands)
                        if parameter in terms_to_check:
                            # Remove first occurrence of the parameter.
                            terms_to_check.remove(parameter)
                        for term in terms_to_check:
                            if parameter in term.freeVars():
                                # Invalid because the parameter occurs
                                # either in multiple terms of the index
                                # or in a non-trivial form (not simply
                                # as itself).
                                raise InvalidIterationError(index, parameter)
                        # Valid: the parameter occurs solely as a term
                        # of the index.
                        continue  # Move on to the next check.
                    if parameter in index.freeVars():
                        # The parameter occurs in the index in a form
                        # that is not valid:
                        raise InvalidIterationError(index, parameter)
        # Recursively check sub expressions of the sub expression.
        for sub_sub_expr in subExpr.subExprIter():
            self._checkIndexedRestriction(sub_sub_expr)

    @classmethod
    def _make(subClass, coreInfo, styles, subExpressions):
        if subClass != Iter:
            MakeNotImplemented(subClass)
        if len(coreInfo) != 1 or coreInfo[0] != 'Iter':
            raise ValueError(
                "Expecting Iter coreInfo to contain exactly one item: 'Iter'")
        lambda_map = subExpressions[0]
        return Iter(None, None, None, None,
                    _lambda_map=lambda_map).withStyles(**styles)

    def remakeArguments(self):
        '''
        Yield the argument values or (name, value) pairs
        that could be used to recreate the Indexed.
        '''
        yield self.lambda_map.parameter_or_parameters
        yield self.lambda_map.body
        yield self.start_index_or_indices
        yield self.end_index_or_indices

    def first(self):
        '''
        Return the first instance of the iteration (and store for future use).
        '''
        if not hasattr(self, '_first'):
            expr_map = {
                parameter: index
                for parameter, index in zip(self.lambda_map.parameters,
                                            self.start_indices)
            }
            self._first = self.lambda_map.body.substituted(expr_map)
        return self._first

    def last(self):
        '''
        Return the last instance of the iteration (and store for futurle use).
        '''
        if not hasattr(self, '_last'):
            expr_map = {
                parameter: index
                for parameter, index in zip(self.lambda_map.parameters,
                                            self.end_indices)
            }
            self._last = self.lambda_map.body.substituted(expr_map)
        return self._last

    def string(self, **kwargs):
        return self.formatted('string', **kwargs)

    def latex(self, **kwargs):
        return self.formatted('latex', **kwargs)

    def formatted(self,
                  formatType,
                  fence=False,
                  subFence=True,
                  operator=None,
                  **kwargs):
        outStr = ''
        if operator is None:
            formatted_operator = ','  # comma is the default formatted operator
        elif isinstance(operator, str):
            formatted_operator = operator
        else:
            formatted_operator = operator.formatted(formatType)
        formatted_sub_expressions = [
            subExpr.formatted(formatType, fence=subFence)
            for subExpr in (self.first(), self.last())
        ]
        formatted_sub_expressions.insert(
            1, '\ldots' if formatType == 'latex' else '...')
        # put the formatted operator between each of formattedSubExpressions
        if fence:
            outStr += '(' if formatType == 'string' else r'\left('
        outStr += formatted_operator.join(formatted_sub_expressions)
        if fence:
            outStr += ')' if formatType == 'string' else r'\right)'
        return outStr

    def getInstance(self,
                    index_or_indices,
                    assumptions=USE_DEFAULTS,
                    requirements=None):
        '''
        Return the iteration instance with the given indices
        as an Expression, using the given assumptions as needed
        to interpret the indices expression.  Required
        truths, proven under the given assumptions, that 
        were used to make this interpretation will be
        appended to the given 'requirements' (if provided).
        '''
        from proveit.number import LessEq

        if self.ndims == 1:
            indices = [index_or_indices]
        else:
            indices = index_or_indices

        if requirements is None:
            # requirements won't be passed back in this case
            requirements = []

        # first make sure that the indices are in the iteration range
        for index, start, end in zip(indices, self.start_indices,
                                     self.end_indices):
            for first, second in ((start, index), (index, end)):
                relation = None
                try:
                    relation = LessEq.sort([first, second],
                                           reorder=False,
                                           assumptions=assumptions)
                except:
                    raise IterationError(
                        "Indices not provably within the iteration range: %s <= %s"
                        % (first, second))
                requirements.append(relation)

        # map to the desired instance
        return self.lambda_map.mapped(*indices)

    def substituted(self,
                    exprMap,
                    relabelMap=None,
                    reservedVars=None,
                    assumptions=USE_DEFAULTS,
                    requirements=None):
        '''
        Returns this expression with the substitutions made 
        according to exprMap and/or relabeled according to relabelMap.
        Attempt to automatically expand the iteration if any Indexed 
        sub-expressions substitute their variable for a composite
        (list or tensor).  Indexed should index variables that represent
        composites, but substituting the composite is a signal that
        an outer iteration should be expanded.  An exception is
        raised if this fails.
        '''
        from .composite import _generateCoordOrderAssumptions
        from proveit import ProofFailure, ExprArray
        from proveit.logic import Equals, InSet
        from proveit.number import Less, LessEq, dist_add, \
            zero, one, dist_subtract, Naturals, Integers
        from .composite import _simplifiedCoord
        from proveit._core_.expression.expr import _NoExpandedIteration
        from proveit._core_.expression.label.var import safeDummyVars

        self._checkRelabelMap(relabelMap)
        if relabelMap is None: relabelMap = dict()

        assumptions = defaults.checkedAssumptions(assumptions)
        new_requirements = []
        iter_params = self.lambda_map.parameters
        iter_body = self.lambda_map.body
        ndims = self.ndims
        subbed_start = self.start_indices.substituted(exprMap, relabelMap,
                                                      reservedVars,
                                                      assumptions,
                                                      new_requirements)
        subbed_end = self.end_indices.substituted(exprMap, relabelMap,
                                                  reservedVars, assumptions,
                                                  new_requirements)

        #print("iteration substituted", self, subbed_start, subbed_end)

        # Need to handle the change in scope within the lambda
        # expression.  We won't use 'new_params'.  They aren't relavent
        # after an expansion, this won't be used.
        new_params, inner_expr_map, inner_assumptions, inner_reservations \
            = self.lambda_map._innerScopeSub(exprMap, relabelMap,
                  reservedVars, assumptions, new_requirements)

        # Get sorted substitution parameter start and end
        # values demarcating how the entry array must be split up for
        # each axis.
        all_entry_starts = [None] * ndims
        all_entry_ends = [None] * ndims
        do_expansion = False
        for axis in range(ndims):
            try:
                empty_eq = Equals(dist_add(subbed_end[axis], one),
                                  subbed_start[axis])
                try:
                    # Check if this is an empty iteration which
                    # happens when end+1=start.
                    empty_eq.prove(assumptions, automation=False)
                    all_entry_starts[axis] = all_entry_ends[axis] = []
                    do_expansion = True
                    continue
                except ProofFailure:
                    pass
                param_vals = \
                    iter_body._iterSubParamVals(axis, iter_params[axis],
                                                subbed_start[axis],
                                                subbed_end[axis],
                                                inner_expr_map, relabelMap,
                                                inner_reservations,
                                                inner_assumptions,
                                                new_requirements)
                assert param_vals[0] == subbed_start[axis]
                if param_vals[-1] != subbed_end[axis]:
                    # The last of the param_vals should either be
                    # subbed_end[axis] or known to be
                    # subbed_end[axis]+1.  Let's double-check.
                    eq = Equals(dist_add(subbed_end[axis], one),
                                param_vals[-1])
                    eq.prove(assumptions, automation=False)
                # Populate the entry starts and ends using the
                # param_vals which indicate that start of each contained
                # entry plus the end of this iteration.
                all_entry_starts[axis] = []
                all_entry_ends[axis] = []
                for left, right in zip(param_vals[:-1], param_vals[1:]):
                    all_entry_starts[axis].append(left)
                    try:
                        eq = Equals(dist_add(left, one), right)
                        eq.prove(assumptions, automation=False)
                        new_requirements.append(
                            eq.prove(assumptions, automation=False))
                        # Simple single-entry case: the start and end
                        # are the same.
                        entry_end = left
                    except:
                        # Not the simple case; perform the positive
                        # integrality check.
                        requirement = InSet(dist_subtract(right, left),
                                            Naturals)
                        # Knowing the simplification may help prove the
                        # requirement.
                        _simplifiedCoord(requirement, assumptions, [])
                        try:
                            new_requirements.append(
                                requirement.prove(assumptions))
                        except ProofFailure as e:
                            raise IterationError("Failed to prove requirement "
                                                 "%s:\n%s" % (requirement, e))
                        if right == subbed_end[axis]:
                            # This last entry is the inclusive end
                            # rather than past the end, so it is an
                            # exception.
                            entry_end = right
                        else:
                            # Subtract one from the start of the next
                            # entyr to get the end of this entry.
                            entry_end = dist_subtract(right, one)
                            entry_end = _simplifiedCoord(
                                entry_end, assumptions, requirements)
                    all_entry_ends[axis].append(entry_end)
                # See if we should add the end value as an extra
                # singular entry.  If param_vals[-1] is at the inclusive
                # end, then we have a singular final entry.
                if param_vals[-1] == subbed_end[axis]:
                    end_val = subbed_end[axis]
                    all_entry_starts[axis].append(end_val)
                    all_entry_ends[axis].append(end_val)
                else:
                    # Otherwise, the last param_val will be one after
                    # the inclusive end which we will want to use below
                    # when building the last iteration entry.
                    all_entry_starts[axis].append(param_vals[-1])
                do_expansion = True
            except EmptyIterException:
                # Indexing over a negative or empty range.  The only way this
                # should be allowed is if subbed_end+1=subbed_start.
                Equals(dist_add(subbed_end[axis], one),
                       subbed_start[axis]).prove(assumptions)
                all_entry_starts[axis] = all_entry_ends[axis] = []
                do_expansion = True
            except _NoExpandedIteration:
                pass

        if do_expansion:
            # There are Indexed sub-Expressions whose variable is
            # being replaced with a Composite, so let us
            # expand the iteration for all of the relevant
            # iteration ranges.
            # Sort the argument value ranges.

            # We must have "substition parameter values" along each
            # axis:
            if None in all_entry_starts or None in all_entry_ends:
                raise IterationError("Must expand all axes or none of the "
                                     "axes, when substituting %s" % str(self))

            # Generate the expanded tuple/array as the substition
            # of 'self'.
            shape = [len(all_entry_ends[axis]) for axis in range(ndims)]
            entries = ExprArray.make_empty_entries(shape)
            indices_by_axis = [range(extent) for extent in shape]
            #print('shape', shape, 'indices_by_axis', indices_by_axis, 'sub_param_vals', sub_param_vals)

            extended_inner_assumptions = list(inner_assumptions)
            for axis_starts in all_entry_starts:
                # Generate assumptions that order the
                # successive entry start parameter values
                # must be natural numbers. (This is a requirement for
                # iteration instances and is a simple fact of
                # succession for single entries.)
                extended_inner_assumptions.extend(
                    _generateCoordOrderAssumptions(axis_starts))

            # Maintain lists of parameter values that come before each given entry.
            #prev_param_vals = [[] for axis in range(ndims)]

            # Iterate over each of the new entries, obtaining indices
            # into sub_param_vals for the start parameters of the entry.
            for entry_indices in itertools.product(*indices_by_axis):
                entry_starts = [axis_starts[i] for axis_starts, i in \
                                zip(all_entry_starts, entry_indices)]
                entry_ends = [axis_ends[i] for axis_ends, i in \
                                zip(all_entry_ends, entry_indices)]

                is_singular_entry = True
                for entry_start, entry_end in zip(entry_starts, entry_ends):
                    # Note that empty ranges will be skipped because
                    # equivalent parameter values should be skipped in
                    # the param_vals above.
                    if entry_start != entry_end:
                        # Not a singular entry along this axis, so
                        # it is not a singular entry.  We must do an
                        # iteration for this entry.
                        is_singular_entry = False

                if is_singular_entry:
                    # Single element entry.

                    # Generate the entry by making appropriate
                    # parameter substitutions for the iteration body.
                    entry_inner_expr_map = dict(inner_expr_map)
                    entry_inner_expr_map.update({
                        param: arg
                        for param, arg in zip(iter_params, entry_starts)
                    })
                    for param in iter_params:
                        relabelMap.pop(param, None)
                    entry = iter_body.substituted(entry_inner_expr_map,
                                                  relabelMap,
                                                  inner_reservations,
                                                  extended_inner_assumptions,
                                                  new_requirements)
                else:
                    # Iteration entry.
                    # Shift the iteration parameter so that the
                    # iteration will have the same start-indices
                    # for this sub-range (like shifting a viewing
                    # window, moving the origin to the start of the
                    # sub-range).

                    # Generate "safe" new parameters (the Variables are
                    # not used for anything that might conflict).
                    # Avoid using free variables from these expressions:
                    unsafe_var_exprs = [self]
                    unsafe_var_exprs.extend(exprMap.values())
                    unsafe_var_exprs.extend(relabelMap.values())
                    unsafe_var_exprs.extend(entry_starts)
                    unsafe_var_exprs.extend(entry_ends)
                    new_params = safeDummyVars(ndims, *unsafe_var_exprs)

                    # Make assumptions that places the parameter(s) in the
                    # appropriate range and at an integral coordinate position.
                    # Note, it is possible that this actually represents an
                    # empty range and that these assumptions are contradictory;
                    # but this still suits our purposes regardless.
                    # Also, we will choose to shift the parameter so it
                    # starts at the start index of the iteration.
                    range_expr_map = dict(inner_expr_map)
                    range_assumptions = []
                    shifted_entry_ends = []
                    for axis, (param, new_param, entry_start, entry_end) \
                            in enumerate(zip(iter_params, new_params,
                                             entry_starts, entry_ends)):
                        start_idx = self.start_indices[axis]
                        shift = dist_subtract(entry_start, start_idx)
                        shift = _simplifiedCoord(shift, assumptions,
                                                 new_requirements)
                        if shift != zero:
                            shifted_param = dist_add(new_param, shift)
                        else:
                            shifted_param = new_param
                        range_expr_map[param] = shifted_param
                        shifted_end = dist_subtract(entry_end, shift)
                        shifted_end = _simplifiedCoord(shifted_end,
                                                       assumptions,
                                                       new_requirements)
                        shifted_entry_ends.append(shifted_end)
                        assumption = InSet(new_param, Integers)
                        range_assumptions.append(assumption)
                        assumption = LessEq(entry_start, shifted_param)
                        range_assumptions.append(assumption)
                        # Assume differences with each of the previous
                        # range starts are natural numbers as should be
                        # the case given requirements that have been
                        # met.
                        next_index = entry_indices[axis] + 1
                        prev_starts = all_entry_starts[axis][:next_index]
                        for prev_start in prev_starts:
                            assumption = InSet(
                                dist_subtract(shifted_param, prev_start),
                                Naturals)
                            range_assumptions.append(assumption)
                        next_start = all_entry_starts[axis][next_index]
                        assumption = Less(shifted_param, next_start)
                        range_assumptions.append(assumption)

                    # Perform the substitution.
                    # The fact that our "new parameters" are "safe"
                    # alleviates the need to reserve anything extra.
                    range_lambda_body = iter_body.substituted(
                        range_expr_map, relabelMap, reservedVars,
                        extended_inner_assumptions + range_assumptions,
                        new_requirements)
                    # Any requirements that involve the new parameters
                    # are a direct consequence of the iteration range
                    # and are not external requirements:
                    new_requirements = \
                        [requirement for requirement in new_requirements
                         if requirement.freeVars().isdisjoint(new_params)]
                    entry = Iter(new_params, range_lambda_body,
                                 self.start_indices, shifted_entry_ends)
                # Set this entry in the entries array.
                ExprArray.set_entry(entries, entry_indices, entry)
                '''      
                    # Iteration entry.
                    # Shift the iteration parameter so that the 
                    # iteration will have the same start-indices
                    # for this sub-range (like shifting a viewing 
                    # window, moving the origin to the start of the 
                    # sub-range).

                    # Generate "safe" new parameters (the Variables are
                    # not used for anything that might conflict).
                    # Avoid using free variables from these expressions:
                    unsafe_var_exprs = [self]
                    unsafe_var_exprs.extend(exprMap.values())
                    unsafe_var_exprs.extend(relabelMap.values())
                    unsafe_var_exprs.extend(entry_start_vals)
                    unsafe_var_exprs.extend(entry_end_vals)
                    new_params = safeDummyVars(len(iter_params), 
                                               *unsafe_var_exprs)
                    
                    # Make the appropriate substitution mapping
                    # and add appropriate assumptions for the iteration
                    # parameter(s).
                    range_expr_map = dict(inner_expr_map)
                    range_assumptions = []
                    for start_idx, param, new_param, range_start, range_end \
                            in zip(subbed_start, iter_params, new_params, 
                                   entry_start_vals, entry_end_vals):
                        shifted_param = Add(new_param, subtract(range_start, start_idx))
                        shifted_param = _simplifiedCoord(shifted_param, assumptions,
                                                         requirements)
                        range_expr_map[param] = shifted_param
                        # Include assumptions that the parameters are 
                        # in the proper range.
                        assumption = LessEq(start_idx, new_param)
                        range_assumptions.append(assumption)
                        assumption = InSet(subtract(new_param, start_idx), Naturals)
                        #assumption = LessEq(new_param,
                        #                    subtract(range_end, start_idx))
                        assumption = LessEq(new_param, range_end)
                        range_assumptions.append(assumption)
                    
                    # Perform the substitution.
                    # The fact that our "new parameters" are "safe" 
                    # alleviates the need to reserve anything extra.
                    range_lambda_body = iter_body.substituted(range_expr_map, 
                        relabelMap, reservedVars, 
                        inner_assumptions+range_assumptions, new_requirements)
                    # Any requirements that involve the new parameters 
                    # are a direct consequence of the iteration range 
                    # and are not external requirements:
                    new_requirements = \
                        [requirement for requirement in new_requirements 
                         if requirement.freeVars().isdisjoint(new_params)]
                    range_lambda_map = Lambda(new_params, range_lambda_body)
                    # Obtain the appropriate end indices.
                    end_indices = \
                        [_simplifiedCoord(subtract(range_end, start_idx), 
                                          assumptions, new_requirements) 
                         for start_idx, range_end in zip(subbed_start, 
                                                          entry_end_vals)]
                    entry = Iter(range_lambda_map, subbed_start, end_indices)
                # Set this entry in the entries array.
                ExprArray.set_entry(entries, entry_start_indices, entry)
                '''
            subbed_self = compositeExpression(entries)
        else:
            # No Indexed sub-Expressions whose variable is
            # replaced with a Composite, so let us not expand the
            # iteration.  Just do an ordinary substitution.
            new_requirements = []  # Fresh new requirements.
            subbed_map = self.lambda_map.substituted(exprMap, relabelMap,
                                                     reservedVars, assumptions,
                                                     new_requirements)
            subbed_self = Iter(subbed_map.parameters, subbed_map.body,
                               subbed_start, subbed_end)

        for requirement in new_requirements:
            # Make sure requirements don't use reserved variable in a
            # nested scope.
            requirement._restrictionChecked(reservedVars)
        if requirements is not None:
            requirements += new_requirements  # append new requirements

        return subbed_self
예제 #14
0
    def __init__(self,
                 parameter_or_parameters,
                 body,
                 start_index_or_indices,
                 end_index_or_indices,
                 styles=None,
                 requirements=tuple(),
                 _lambda_map=None):
        '''
        Create an Iter that represents an iteration of the body for the
        parameter(s) ranging from the start index/indices to the end 
        index/indices.  A Lambda expression will be created as its 
        sub-expression that maps the parameter(s) to the body with
        conditions that restrict the parameter(s) to the appropriate interval.
        
        _lambda_map is used internally for efficiently rebuilding an Iter.
        '''
        from proveit.logic import InSet
        from proveit.number import Interval

        if _lambda_map is not None:
            # Use the provided 'lambda_map' instead of creating one.
            lambda_map = _lambda_map
            pos_args = (parameter_or_parameters, body, start_index_or_indices,
                        end_index_or_indices)
            if pos_args != (None, None, None, None):
                raise ValueError(
                    "Positional arguments of the Init constructor "
                    "should be None if lambda_map is provided.")
            parameters = lambda_map.parameters
            body = lambda_map.body
            conditions = lambda_map.conditions
            if len(conditions) != len(parameters):
                raise ValueError(
                    "Inconsistent number of conditions and lambda "
                    "map parameters")
            start_indices, end_indices = [], []
            for param, condition in zip(parameters, conditions):
                invalid_condition_msg = (
                    "Not the right kind of lambda_map condition "
                    "for an iteration")
                if not isinstance(condition,
                                  InSet) or condition.element != param:
                    raise ValueError(invalid_condition_msg)
                domain = condition.domain
                if not isinstance(domain, Interval):
                    raise ValueError(invalid_condition_msg)
                start_index, end_index = domain.lowerBound, domain.upperBound
                start_indices.append(start_index)
                end_indices.append(end_index)
            self.start_indices = ExprTuple(*start_indices)
            self.end_indices = ExprTuple(*end_indices)
            if len(parameters) == 1:
                self.start_index = self.start_indices[0]
                self.end_index = self.end_indices[0]
                self.start_index_or_indices = self.start_index
                self.end_index_or_indices = self.end_index
            else:
                self.start_index_or_indices = self.start_indices
                self.end_index_or_indices = self.end_indices
        else:
            parameters = compositeExpression(parameter_or_parameters)

            start_index_or_indices = singleOrCompositeExpression(
                start_index_or_indices)
            if isinstance(start_index_or_indices,
                          ExprTuple) and len(start_index_or_indices) == 1:
                start_index_or_indices = start_index_or_indices[0]
            self.start_index_or_indices = start_index_or_indices
            if isinstance(start_index_or_indices, Composite):
                # a composite of multiple indices
                self.start_indices = self.start_index_or_indices
            else:
                # a single index
                self.start_index = self.start_index_or_indices
                # wrap a single index in a composite for convenience
                self.start_indices = compositeExpression(
                    self.start_index_or_indices)

            end_index_or_indices = singleOrCompositeExpression(
                end_index_or_indices)
            if isinstance(end_index_or_indices,
                          ExprTuple) and len(end_index_or_indices) == 1:
                end_index_or_indices = end_index_or_indices[0]
            self.end_index_or_indices = end_index_or_indices
            if isinstance(self.end_index_or_indices, Composite):
                # a composite of multiple indices
                self.end_indices = self.end_index_or_indices
            else:
                # a single index
                self.end_index = self.end_index_or_indices
                # wrap a single index in a composite for convenience
                self.end_indices = compositeExpression(
                    self.end_index_or_indices)

            conditions = []
            for param, start_index, end_index in zip(parameters,
                                                     self.start_indices,
                                                     self.end_indices):
                conditions.append(
                    InSet(param, Interval(start_index, end_index)))

            lambda_map = Lambda(parameters, body, conditions=conditions)

        self.ndims = len(self.start_indices)
        if self.ndims != len(self.end_indices):
            raise ValueError(
                "Inconsistent number of 'start' and 'end' indices")

        if len(parameters) != len(self.start_indices):
            raise ValueError(
                "Inconsistent number of indices and lambda map parameters")

        Expression.__init__(self, ['Iter'], [lambda_map],
                            styles=styles,
                            requirements=requirements)
        self.lambda_map = lambda_map
        self._checkIndexedRestriction(body)