Ejemplo n.º 1
0
 def __init__(self, parameter_or_parameters, body, conditions=tuple(), styles=None, requirements=tuple()):
     '''
     Initialize a Lambda function expression given parameter(s) and a body.
     Each parameter must be a Variable.
     When there is a single parameter, there will be a 'parameter'
     attribute. Either way, there will be a 'parameters' attribute
     that bundles the one or more Variables into an ExprList.
     The 'body' attribute will be the lambda function body
     Expression (that may or may not be a Composite).  Zero or
     more expressions may be provided.
     '''
     from proveit._core_.expression.composite import compositeExpression, singleOrCompositeExpression, Iter
     if styles is None: styles = dict()
     self.parameters = compositeExpression(parameter_or_parameters)
     parameterVars = [getParamVar(parameter) for parameter in self.parameters]
     if len(self.parameters) == 1:
         # has a single parameter
         self.parameter = self.parameters[0]
         self.parameter_or_parameters = self.parameter
     else:
         self.parameter_or_parameters = self.parameters
     self.parameterVars = tuple(parameterVars)
     self.parameterVarSet = frozenset(parameterVars)
     if len(self.parameterVarSet) != len(self.parameters):
         raise ValueError('Lambda parameters Variables must be unique with respect to each other.')
     body = singleOrCompositeExpression(body)
     if not isinstance(body, Expression):
         raise TypeError('A Lambda body must be of type Expression')
     if isinstance(body, Iter):
         raise TypeError('An Iter must be within an ExprList or ExprTensor, not directly as a Lambda body')
     self.body = body
     self.conditions = compositeExpression(conditions)
     for requirement in self.body.getRequirements():
         if not self.parameterVarSet.isdisjoint(requirement.freeVars()):
             raise LambdaError("Cannot generate a Lambda expression with parameter variables involved in Lambda body requirements: " + str(requirement))
     sub_exprs = [self.parameter_or_parameters, self.body]
     if len(self.conditions)>0: sub_exprs.append(self.conditions)
     
     # Create a "generic" version (if not already) of the Lambda expression since the 
     # choice of parameter labeling is irrelevant.
     generic_body_vars = self.body._genericExpr.usedVars()
     generic_condition_vars = self.conditions._genericExpr.usedVars()
     used_generic_vars = generic_body_vars.union(generic_condition_vars)
     generic_params = tuple(safeDummyVars(len(self.parameterVars), *(used_generic_vars-self.parameterVarSet)))
     if generic_params != self.parameterVars:
         relabel_map = {param:generic_param for param, generic_param in zip(self.parameterVars, generic_params)}
         # temporarily disable automation during the relabeling process
         prev_automation = defaults.automation
         defaults.automation = False
         generic_parameters = self.parameters._genericExpr.relabeled(relabel_map)
         generic_body = self.body._genericExpr.relabeled(relabel_map)
         generic_conditions = self.conditions._genericExpr.relabeled(relabel_map)
         self._genericExpr = Lambda(generic_parameters, generic_body, generic_conditions, styles=dict(styles), requirements=requirements)
         defaults.automation = prev_automation # restore to previous value
     
     Expression.__init__(self, ['Lambda'], sub_exprs, styles=styles, requirements=requirements)
Ejemplo n.º 2
0
    def __init__(self,
                 lambda_map,
                 start_index_or_indices,
                 end_index_or_indices,
                 styles=dict(),
                 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,
                      ExprList) 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)
        if isinstance(self.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)

        if isinstance(end_index_or_indices,
                      ExprList) and len(end_index_or_indices) == 1:
            end_index_or_indices = end_index_or_indices[0]
        self.end_index_or_indices = singleOrCompositeExpression(
            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)

        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
Ejemplo n.º 3
0
 def __init__(self, operator_or_operators, operand_or_operands, styles=dict(), requirements=tuple()):
     '''
     Create an operation with the given operator(s) and operand(s).
     The operator(s) must be Label(s) (a Variable or a Literal).
     When there is a single operator, there will be an 'operator'
     attribute.  When there is a single operand, there will be
     an 'operand' attribute.  In any case, there will be
     'operators' and 'operands' attributes that bundle
     the one or more Expressions into a composite Expression.
     '''
     from proveit._core_.expression.composite import Composite, compositeExpression, singleOrCompositeExpression, Iter, Indexed
     from proveit._core_.expression.label.label import Label
     from proveit import Context
     if hasattr(self.__class__, '_operator_') and operator_or_operators==self.__class__._operator_:
         operator = operator_or_operators
         context = Context(inspect.getfile(self.__class__))
         if Expression.contexts[operator] != context:
             raise OperationError("Expecting '_operator_' Context to match the Context of the Operation sub-class.  Use 'context=__file__'.")
     self.operator_or_operators = singleOrCompositeExpression(operator_or_operators)
     self.operand_or_operands = singleOrCompositeExpression(operand_or_operands)
     if isinstance(self.operator_or_operators, Composite):
         # a composite of multiple operators:
         self.operators = self.operator_or_operators 
         for operator in self.operators:
             if isinstance(operator, Iter):
                 if not isinstance(operator.lambda_map.body, Indexed):
                     raise TypeError('operators must be Labels, Indexed variables, or iteration (Iter) over Indexed variables.')                
             elif not isinstance(operator, Label) and not isinstance(operator, Indexed):
                 raise TypeError('operator must be a Label, Indexed variable, or iteration (Iter) over Indexed variables.')
     else:
         # a single operator
         self.operator = self.operator_or_operators
         if not isinstance(self.operator, Label) and not isinstance(self.operator, Indexed):
             raise TypeError('operator must be a Label, Indexed variable, or iteration (Iter) over Indexed variables.')
         # wrap a single operator in a composite for convenience
         self.operators = compositeExpression(self.operator)
     if isinstance(self.operand_or_operands, Composite):
         # a composite of multiple operands
         self.operands = self.operand_or_operands
     else:
         # a single operand
         self.operand = self.operand_or_operands
         # wrap a single operand in a composite for convenience
         self.operands = compositeExpression(self.operand)
     if 'operation' not in styles:
         styles['operation'] = 'normal' # vs 'function
     if 'wrapPositions' not in styles:
         styles['wrapPositions'] = '()' # no wrapping by default
     if 'justification' not in styles:
         styles['justification'] = 'center'
     Expression.__init__(self, ['Operation'], [self.operator_or_operators, self.operand_or_operands], styles=styles, requirements=requirements)
Ejemplo n.º 4
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
Ejemplo n.º 5
0
 def _expandingIterRanges(self, iterParams, startArgs, endArgs, exprMap, relabelMap = None, reservedVars = None, assumptions=USE_DEFAULTS, requirements=None):
     from proveit import Variable, compositeExpression
     # Can't substitute the lambda parameter variables; they are in a new scope.
     innerExprMap = {key:value for (key, value) in exprMap.items() if key not in self.parameterVarSet}
     # Can't use assumptions involving lambda parameter variables
     innerAssumptions = [assumption for assumption in assumptions if self.parameterVarSet.isdisjoint(assumption.freeVars())]
     # Handle relabeling and variable reservations consistent with relabeling.
     innerReservations = dict() if reservedVars is None else dict(reservedVars)
     for parameterVar in self.parameterVars:
         # Note that lambda parameters introduce a new scope and don't need to,
         # themselves, be restriction checked.  But they generate new inner restrictions
         # that disallow any substitution from a variable that isn't in the new scope
         # to a variable that is in the new scope. 
         # For example, we can relabel y to z in (x, y) -> f(x, y), but not f to x. 
         if parameterVar in relabelMap:
             relabeledParams = compositeExpression(relabelMap[parameterVar])
             for relabeledParam in relabeledParams:
                 if not isinstance(relabeledParam, Variable):
                     raise ImproperSubstitution('May only relabel a Variable to another Variable or list of Variables')
                 innerReservations[relabeledParam] = parameterVar
         else:
             # Not relabeled
             innerReservations[parameterVar] = parameterVar
     
     # collect the iter ranges from the body and all conditions
     iter_ranges = set()
     for iter_range in self.body.expandingIterRanges(iterParams, startArgs, endArgs, innerExprMap, relabelMap, innerReservations, innerAssumptions, requirements):
         iter_ranges.add(iter_range)
     for iter_range in self.conditions.expandingIterRanges(iterParams, startArgs, endArgs, innerExprMap, relabelMap, innerReservations, innerAssumptions, requirements):
         iter_ranges.add(iter_range)
     for iter_range in iter_ranges:
         yield iter_range
Ejemplo n.º 6
0
 def substituted(self, exprMap, relabelMap=None, reservedVars=None, assumptions=USE_DEFAULTS, requirements=None):
     '''
     Return this expression with the variables substituted 
     according to subMap and/or relabeled according to relabelMap.
     '''
     from proveit._core_.expression.composite.composite import compositeExpression
     from proveit._core_.expression.lambda_expr.lambda_expr import Lambda
     self._checkRelabelMap(relabelMap)
     if len(exprMap)>0 and (self in exprMap):
         return exprMap[self]._restrictionChecked(reservedVars)        
     subbed_operand_or_operands = self.operand_or_operands.substituted(exprMap, relabelMap, reservedVars, assumptions, requirements)
     subbed_operands = compositeExpression(subbed_operand_or_operands)
     subbed_operator_or_operators = self.operator_or_operators.substituted(exprMap, relabelMap, reservedVars, assumptions, requirements)
     subbed_operators = compositeExpression(subbed_operator_or_operators)
     if len(subbed_operators)==1:
         subbedOperator = subbed_operators[0]
         if isinstance(subbedOperator, Lambda):
             # Substitute the entire operation via a Lambda body
             # For example, f(x, y) -> x + y.
             if len(subbed_operands) != len(subbedOperator.parameters):
                 raise ImproperSubstitution('Cannot substitute an Operation with the wrong number of parameters')
             if len(subbedOperator.conditions) != 0:
                 raise ImproperSubstitution('Operation substitution must be defined via an Unconditioned Lambda expression')
             operandSubMap = {param:operand for param, operand in zip(subbedOperator.parameters, subbed_operands)}
             if not reservedVars is None:
                 # the reserved variables of the lambda body excludes the lambda parameters
                 # (i.e., the parameters mask externally reserved variables).
                 lambdaExprReservedVars = {k:v for k, v in reservedVars.items() if k not in subbedOperator.parameterVarSet}
             else: lambdaExprReservedVars = None
             return subbedOperator.body._restrictionChecked(lambdaExprReservedVars).substituted(operandSubMap, assumptions=assumptions, requirements=requirements)
     # 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.
                 return op_class._make(['Operation'], styles=None, 
                                       subExpressions=[operator, 
                                                       subbed_operand_or_operands])
     return self.__class__._make(['Operation'], self.getStyles(), 
                                 [subbed_operator_or_operators, 
                                  subbed_operand_or_operands])
Ejemplo n.º 7
0
    def _innerScopeSub(self, exprMap, relabelMap, reservedVars, assumptions, requirements):
        '''
        Helper method for substituted (and used by Iter.substituted) which handles the change in
        scope properly as well as parameter relabeling (or iterated parameter expansion).
        '''
        
        from proveit import compositeExpression, Iter, ExprList
        
        # Can't substitute the lambda parameter variables; they are in a new scope.
        inner_expr_map = {key:value for (key, value) in exprMap.items() if key not in self.parameterVarSet}
        # Handle relabeling and variable reservations consistent with relabeling.
        inner_reservations = dict() if reservedVars is None else dict(reservedVars)
        new_params = []
        
        for parameter, parameterVar in zip(self.parameters, self.parameterVars):
            # Note that lambda parameters introduce a new scope and don't need to,
            # themselves, be restriction checked.  But they generate new inner restrictions
            # that disallow any substitution from a variable that isn't in the new scope
            # to a variable that is in the new scope. 
            # For example, we can relabel y to z in (x, y) -> f(x, y), but not f to x. 
            if parameterVar in relabelMap:
                if isinstance(parameter, Iter):
                    relabeledParams = parameter.substituted(exprMap, relabelMap, reservedVars, assumptions, requirements)
                    if isinstance(relabeledParams, ExprList):
                        # expanding an iteration.  For example: x_1, ..., x_n -> a, b, c, d 
                        if len(relabeledParams) != len(relabelMap[parameterVar]):
                            raise ImproperSubstitution("Relabeling of iterated parameters incomplete: %d length expansion versus %d length substitution"%(len(relabeledParams), len(relabelMap[parameterVar])))
                    else:
                        # e.g., x_1, ..., x_n -> y_1, ..., y_n
                        relabeledParams = compositeExpression(relabeledParams)
                else:
                    relabeledParams = compositeExpression(relabelMap[parameterVar])
                for relabeledParam in relabeledParams:
                    new_params.append(relabeledParam)
                    inner_reservations[relabeledParam] = parameterVar
            else:
                # can perform a substition in indices of a parameter iteration: x_1, ..., x_n
                new_params.append(parameter.substituted(inner_expr_map, relabelMap, reservedVars, assumptions, requirements))
                inner_reservations[parameterVar] = parameterVar

        # Can't use assumptions involving lambda parameter variables
        inner_assumptions = [assumption for assumption in assumptions if assumption.freeVars().isdisjoint(new_params)]
                                      
        return new_params, inner_expr_map, inner_assumptions, inner_reservations
Ejemplo n.º 8
0
 def _formattedOperation(formatType, operatorOrOperators, operands, wrapPositions, justification, implicitFirstOperator=False, **kwargs):
     from proveit import Iter, ExprTuple, compositeExpression
     if isinstance(operatorOrOperators, Expression) and not isinstance(operatorOrOperators, ExprTuple):
         operator = operatorOrOperators
         # Single operator case.
         # Different formatting when there is 0 or 1 element, unless it is an Iter
         if len(operands) < 2:
             if len(operands) == 0 or not isinstance(operands[0], Iter):
                 if formatType == 'string':
                     return '[' + operator.string(fence=True) +  '](' + operands.string(fence=False, subFence=False) + ')'
                 else:
                     return '\left[' + operator.latex(fence=True) +  r'\right]\left(' + operands.latex(fence=False, subFence=False) + r'\right)'
                 raise ValueError("Unexpected formatType: " + str(formatType))  
         fence =  kwargs['fence'] if 'fence' in kwargs else False
         subFence =  kwargs['subFence'] if 'subFence' in kwargs else True
         do_wrapping = len(wrapPositions)>0
         formatted_str = ''
         if fence: formatted_str = '(' if formatType=='string' else  r'\left('
         if do_wrapping and formatType=='latex': 
             formatted_str += r'\begin{array}{%s} '%justification[0]
         formatted_str += operands.formatted(formatType, fence=False, subFence=subFence, operatorOrOperators=operator, wrapPositions=wrapPositions)
         if do_wrapping and formatType=='latex': 
             formatted_str += r' \end{array}'
         if fence: formatted_str += ')' if formatType=='string' else  r'\right)'
         return formatted_str
     else:
         operators = operatorOrOperators
         operands = compositeExpression(operands)
         # Multiple operator case.
         # Different formatting when there is 0 or 1 element, unless it is an Iter
         if len(operands) < 2:
             if len(operands) == 0 or not isinstance(operands[0], Iter):
                 raise OperationError("No defaut formatting with multiple operators and zero operands")
         fence =  kwargs['fence'] if 'fence' in kwargs else False
         subFence =  kwargs['subFence'] if 'subFence' in kwargs else True
         do_wrapping = len(wrapPositions)>0
         formatted_str = ''
         if fence: formatted_str = '(' if formatType=='string' else  r'\left('
         if do_wrapping and formatType=='latex': 
             formatted_str += r'\begin{array}{%s} '%justification[0]
         formatted_str += operands.formatted(formatType, fence=False, subFence=subFence, operatorOrOperators=operators, implicitFirstOperator=implicitFirstOperator, wrapPositions=wrapPositions)
         if do_wrapping and formatType=='latex': 
             formatted_str += r' \end{array}'
         if fence: formatted_str += ')' if formatType=='string' else  r'\right)'
         return formatted_str            
Ejemplo n.º 9
0
    def _extractInitArgs(operationClass, operator_or_operators,
                         operand_or_operands):
        '''
        For a particular Operation class and given operator(s) and operand(s),
        yield (name, value) pairs to pass into the initialization method
        for creating the operation consistent with the given operator(s) and operand(s).
        
        First attempt to call 'extractInitArgValue' 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 proveit._core_.expression.composite.composite import compositeExpression
        implicit_operator = operationClass._implicitOperator()
        matches_implicit_operator = (
            operator_or_operators == implicit_operator)
        if implicit_operator is not None and not matches_implicit_operator:
            raise OperationError("An implicit operator may not be changed")
        operands = compositeExpression(operand_or_operands)
        args, varargs, varkw, defaults = inspect.getargspec(
            operationClass.__init__)
        args = args[1:]  # skip over the 'self' arg
        if len(args) > 0 and args[-1] == 'requirements':
            args = args[:
                        -1]  # NOT TREATING 'requirements' FULLY AT THIS TIME; THIS NEEDS WORK.
            defaults = defaults[:-1]
        if len(args) > 0 and args[-1] == 'styles':
            args = args[:
                        -1]  # NOT TREATING 'styles' FULLY AT THIS TIME; THIS NEEDS WORK.
            defaults = defaults[:-1]

        try:
            arg_vals = [
                operationClass.extractInitArgValue(arg, operator_or_operators,
                                                   operand_or_operands)
                for arg in args
            ]
            if varargs is not None:
                arg_vals += operationClass.extractInitArgValue(
                    varargs, operator_or_operators, operand_or_operands)
            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(
                            "extractInitArgVal 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 = operationClass.extractInitArgValue(
                    varkw, operator_or_operators, operand_or_operands)
                for arg, val in kw_arg_vals.iteritems():
                    yield (arg, val)
        except NotImplementedError:
            if (varkw is None) and (operationClass.extractInitArgValue
                                    == Operation.extractInitArgValue):
                # 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)==len(operands) and varargs is None)):
                    # yield each operand separately
                    for operand in operands:
                        yield operand
                    return

                # handle default explicit operator(s) case
                if (not implicit_operator) and (varkw is None):
                    if varargs is None and len(args) == 2:
                        # assume one argument for the operator(s) and one argument for the operand(s)
                        yield operator_or_operators
                        yield operand_or_operands
                        return
                    elif (varargs is not None and len(args)
                          == 1) or (len(args) == len(operands) + 1
                                    and varargs is None):
                        # yield the operator(s) and each operand separately
                        yield operator_or_operators
                        for operand in operands:
                            yield operand
                        return
                raise NotImplementedError(
                    "Must implement 'extractInitArgValue' for the Operation of type %s if it does not fall into one of the default cases for 'extractInitArgs'"
                    % str(operationClass))
Ejemplo n.º 10
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)
Ejemplo n.º 11
0
    def __init__(self, operator, instanceVarOrVars, instanceExpr, domain=None, domains=None, conditions=tuple(), nestMultiIvars=False, styles=None):
        '''
        Create an Operation for the given operator that is applied over instances of the 
        given instance Variable(s), instanceVarOrVars, 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.
        instanceVars may be singular or plural (iterable).  An OperationOverInstances is
        effected as an Operation over a conditional Lambda map.
        
        If nestMultiIvars is True do the following:
        When there are multiple instanceVars, this will generate a nested structure in 
        actuality and simply set the style to display these instance variables together.
        In other words, whether instance variables are joined together, like
        "forall_{x, y} P(x, y)" or split in a nested structure like
        "forall_{x} [forall_y P(x, y)]"
        is deemed to be a matter of style, not substance.  Internally it is treated as the
        latter.
              
        If a 'domain' is supplied, additional conditions are generated that each instance 
        Variable is in the domain "set": InSet(x_i, domain), where x_i is for each instance 
        variable.  If, instead, 'domains' are supplied, then each instance variable is supplied
        with its own domain (one for each instance variable).  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)".  
        '''
        from proveit.logic import InSet
        from proveit._core_.expression.lambda_expr.lambda_expr import getParamVar
        instanceVars = compositeExpression(instanceVarOrVars)
        
        if styles is None: styles=dict()
        
        if len(instanceVars)==0:
            raise ValueError("Expecting at least one instance variable when constructing an OperationOverInstances")
        
        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(instanceVars)
        
        
        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(instanceVars):
                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")
            conditions = [InSet(instanceVar, domain) for instanceVar, domain in zip(instanceVars, domains)] + 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.
        if len(conditions)>=len(instanceVars) and all(isinstance(cond, InSet) and cond.element==ivar for ivar, cond in zip(instanceVars, conditions)):
            domains = [cond.domain for cond in conditions[:len(instanceVars)]]
            domain = domains[0] # used if we have a single instance variable or nestMultiIvars is True
            nondomain_conditions = conditions[len(instanceVars):]
        else:
            domain = domains = None
            nondomain_conditions = conditions
                
        if len(instanceVars) > 1:
            if nestMultiIvars:
                # "inner" instance variable are all but the first one.
                inner_instance_vars = instanceVars[1:]
                inner_instance_param_vars = set(getParamVar(ivar) for ivar in inner_instance_vars)

                # start with the domain conditions
                if domains is None:
                    conditions = [] # no domain conditions
                    inner_conditions = [] # inner or outer
                else:
                    inner_conditions = conditions[1:len(domains)] # everything but the outer domain condition
                    conditions = [conditions[0]] # outer domain condition
                
                # Apart from the domain conditions, the "inner" conditions start with the
                # first one that has any free variables that include any of the "inner" instance variables.
                for k, cond in enumerate(nondomain_conditions):
                    if not cond.freeVars().isdisjoint(inner_instance_param_vars):
                        inner_conditions += nondomain_conditions[k:]
                        nondomain_conditions = nondomain_conditions[:k]
                        break
                
                # Include the outer non-domain conditions.
                conditions += nondomain_conditions
                
                # the instance expression at this level should be the OperationOverInstances at the next level.
                innerOperand = self._createOperand(inner_instance_vars, instanceExpr, conditions=inner_conditions)
                instanceExpr = self.__class__._make(['Operation'], dict(styles), [operator, innerOperand])
                styles['instance_vars'] = 'join_next' # combine instance variables in the style
                instanceVarOrVars = instanceVar = instanceVars[0]
            else:
                self.instanceVars = instanceVarOrVars = instanceVars
                self.domains = domains # Domain for each instance variable
        else:
            instanceVarOrVars = instanceVar = instanceVars[0]
            styles['instance_vars'] = 'no_join' # no combining instance variables in the style

        Operation.__init__(self, operator, OperationOverInstances._createOperand(instanceVarOrVars, instanceExpr, conditions), styles=styles)

        self.instanceExpr = instanceExpr
        '''Expression corresponding to each 'instance' in the OperationOverInstances'''
        
        if not hasattr(self, 'instanceVars'):
            self.instanceVar = instanceVar
            '''Outermost instance variable (or iteration of indexed variables) of the OperationOverInstance.'''
            self.domain = domain
            '''Domain of the outermost instance variable (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.'''
        
        """
Ejemplo n.º 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)
Ejemplo n.º 13
0
    def __init__(self,
                 operator_or_operators,
                 operand_or_operands,
                 styles=None):
        '''
        Create an operation with the given operator(s) and operand(s).
        The operator(s) must be Label(s) (a Variable or a Literal).
        When there is a single operator, there will be an 'operator'
        attribute.  When there is a single operand, there will be
        an 'operand' attribute.  In any case, there will be
        'operators' and 'operands' attributes that bundle
        the one or more Expressions into a composite Expression.
        '''
        from proveit._core_.expression.composite import (
            Composite, compositeExpression, singleOrCompositeExpression,
            ExprTuple, ExprRange)
        from proveit._core_.expression.label.label import Label
        from .indexed_var import IndexedVar
        if styles is None: styles = dict()
        if hasattr(self.__class__, '_operator_'
                   ) and operator_or_operators == self.__class__._operator_:
            operator = operator_or_operators
            #if Expression.contexts[operator._style_id] != operator.context:
            #    raise OperationError("Expecting '_operator_' Context to match the Context of the Operation sub-class.  Use 'context=__file__'.")
        if isinstance(operator_or_operators, ExprRange):
            operator_or_operators = [operator_or_operators]
        if isinstance(operand_or_operands, ExprRange):
            operand_or_operands = [operand_or_operands]
        self.operator_or_operators = singleOrCompositeExpression(
            operator_or_operators)

        # Reduce to a single operand if it is just a tuple with
        # one non-ExprRange and non-ExprTuple element.
        self.operand_or_operands = singleOrCompositeExpression(
            operand_or_operands, do_singular_reduction=True)

        def raiseBadOperatorType(operator):
            raise TypeError('operator(s) must be a Label, an indexed variable '
                            '(IndexedVar), or iteration (Iter) over indexed'
                            'variables (IndexedVar). %s is none of those.' %
                            str(operator))

        if isinstance(self.operator_or_operators, Composite):
            # a composite of multiple operators:
            self.operators = self.operator_or_operators
            for operator in self.operators:
                if isinstance(operator, ExprRange):
                    if not isinstance(operator.body, IndexedVar):
                        raiseBadOperatorType(operator)
                elif not isinstance(operator, Label) and not isinstance(
                        operator, IndexedVar):
                    raiseBadOperatorType(operator)
        else:
            # a single operator
            self.operator = self.operator_or_operators
            if not isinstance(self.operator, Label) and not isinstance(
                    self.operator, IndexedVar):
                raiseBadOperatorType(self.operator)
            # wrap a single operator in a composite for convenience
            self.operators = compositeExpression(self.operator)
        if isinstance(self.operand_or_operands, Composite):
            # a composite of multiple operands
            self.operands = self.operand_or_operands
            if (isinstance(self.operands, ExprTuple)
                    and len(self.operands) == 1
                    and isinstance(self.operands[0], ExprTuple)):
                # This is a single operand that is an ExprTuple.
                self.operand = self.operands[0]
        else:
            # a single operand
            self.operand = self.operand_or_operands
            # wrap a single operand in a composite for convenience
            self.operands = compositeExpression(self.operand)
        if 'operation' not in styles:
            styles['operation'] = 'infix'  # vs 'function'
        if 'wrapPositions' not in styles:
            styles['wrapPositions'] = '()'  # no wrapping by default
        if 'justification' not in styles:
            styles['justification'] = 'center'
        sub_exprs = (self.operator_or_operators, self.operand_or_operands)
        if isinstance(self, IndexedVar):
            core_type = 'IndexedVar'
        else:
            core_type = 'Operation'
        Expression.__init__(self, [core_type], sub_exprs, styles=styles)
Ejemplo n.º 14
0
    def substituted(self,
                    exprMap,
                    relabelMap=None,
                    reservedVars=None,
                    assumptions=USE_DEFAULTS,
                    requirements=None):
        '''
        Return this expression with its variables substituted 
        according to subMap and/or relabeled according to relabelMap.
        The Lambda parameters have their own scope within the Lambda 
        body and do not get substituted.  They may be relabeled, however. 
        Substitutions within the Lambda body are restricted to 
        exclude the Lambda parameters themselves (these Variables 
        are reserved), consistent with any relabeling.
        '''
        from proveit import compositeExpression, Iter
        if (exprMap is not None) and (self in exprMap):
            # the full expression is to be substituted
            return exprMap[self]._restrictionChecked(reservedVars)
        if relabelMap is None: relabelMap = dict()
        assumptions = defaults.checkedAssumptions(assumptions)
        # Can't substitute the lambda parameter variables; they are in a new scope.
        innerExprMap = {
            key: value
            for (key, value) in exprMap.iteritems()
            if key not in self.parameterVarSet
        }
        # Can't use assumptions involving lambda parameter variables
        innerAssumptions = [
            assumption for assumption in assumptions
            if self.parameterVarSet.isdisjoint(assumption.freeVars())
        ]
        # Handle relabeling and variable reservations consistent with relabeling.
        innerReservations = dict() if reservedVars is None else dict(
            reservedVars)
        newParams = []

        for parameter, parameterVar in zip(self.parameters,
                                           self.parameterVars):
            # Note that lambda parameters introduce a new scope and don't need to,
            # themselves, be restriction checked.  But they generate new inner restrictions
            # that disallow any substitution from a variable that isn't in the new scope
            # to a variable that is in the new scope.
            # For example, we can relabel y to z in (x, y) -> f(x, y), but not f to x.
            if parameterVar in relabelMap:
                if isinstance(parameter, Iter):
                    # expanding an iteration.  For example: x_1, ..., x_n -> a, b, c, d
                    relabeledParams = parameter.substituted(
                        exprMap, relabelMap, reservedVars, assumptions,
                        requirements)
                    if len(relabeledParams) != len(relabelMap[parameterVar]):
                        raise ImproperSubstitution(
                            "Relabeling of iterated parameters incomplete: %d length expansion versus %d length substitution"
                            % (len(relabeledParams),
                               len(relabelMap[parameterVar])))
                else:
                    relabeledParams = compositeExpression(
                        relabelMap[parameterVar])
                for relabeledParam in relabeledParams:
                    newParams.append(relabeledParam)
                    innerReservations[relabeledParam] = parameterVar
            else:
                # can perform a substition in indices of a parameter iteration: x_1, ..., x_n
                newParams.append(
                    parameter.substituted(innerExprMap, relabelMap,
                                          reservedVars, assumptions,
                                          requirements))
                innerReservations[parameterVar] = parameterVar
        # the lambda body with the substitution:
        subbedBody = self.body.substituted(innerExprMap, relabelMap,
                                           innerReservations, innerAssumptions,
                                           requirements)
        # conditions with substitutions:
        subbedConditions = self.conditions.substituted(innerExprMap,
                                                       relabelMap,
                                                       innerReservations,
                                                       innerAssumptions,
                                                       requirements)
        try:
            newLambda = Lambda(newParams, subbedBody, subbedConditions)
        except TypeError as e:
            raise ImproperSubstitution(e.message)
        except ValueError as e:
            raise ImproperSubstitution(e.message)
        return newLambda
Ejemplo n.º 15
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)
Ejemplo n.º 16
0
    def __init__(self,
                 operator,
                 instanceVarOrVars,
                 instanceExpr,
                 domain=None,
                 domains=None,
                 conditions=tuple(),
                 styles=dict()):
        '''
        Create an Operation for the given operator that is applied over instances of the 
        given instance Variables, instanceVars, 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.
        instanceVars may be singular or plural (iterable).
              
        If a domain is supplied, additional conditions are generated that each instance 
        Variable is in the domain "set": InSet(x_i, domain), where x_i is for each instance 
        variable.  If, instead, domains are supplied, then each instance variable is supplied
        with its own domain (one for each instance variable).  Internally, this is represented
        as an Operation with a single Lambda expression operand with conditions matching
        the OperationOverInstances conditions (a conditional mapping).
        
        The set of "domain" conditions come at the beginning of this conditions list and
        can be accessed via the 'implicitConditions' attribute, and the 'domain' or 'domains'
        can be accessed via attributes of the same name.  Whether the OperationOverInstances
        is constructed with domain/domains explicitly, or they are provided at the beginning
        of the conditions list (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)".  The non-domain-conditions 
        are accessible via the 'explicitConditions' attribute.  The 'conditions' attribute has the 
        full proper list of conditions.
        '''
        from proveit.logic import InSet
        instanceVars = compositeExpression(instanceVarOrVars)
        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(instanceVars)
        if domains is not None:
            # prepend domain conditions
            if len(domains) != len(instanceVars):
                raise ValueError(
                    "When specifying multiple domains, the number should be the same as the number of instance variables."
                )
            conditions = [
                InSet(instanceVar, domain)
                for instanceVar, domain in zip(instanceVars, domains)
            ] + list(conditions)
        conditions = compositeExpression(conditions)
        Operation.__init__(self,
                           operator,
                           OperationOverInstances._createOperand(
                               instanceVars, instanceExpr, conditions),
                           styles=styles)
        self.instanceVars = instanceVars
        if len(self.instanceVars) == 1:
            self.instanceVar = self.instanceVars[0]
        self.instanceExpr = instanceExpr
        self.conditions = conditions

        # extract the domain or domains from the condition (regardless of whether the domain/domains was explicitly provided
        # or implicit through the conditions).
        if len(conditions) >= len(instanceVars):
            domains = []
            for instanceVar, condition in zip(instanceVars, conditions):
                if isinstance(condition,
                              InSet) and condition.element == instanceVar:
                    domains.append(condition.domain)
            if len(domains) == len(instanceVars):
                # There is a domain condition for each instance variable.
                if all(domain == domains[0] for domain in domains):
                    self.domain_or_domains = self.domain = domains[
                        0]  # all the same domain
                else:
                    self.domain_or_domains = self.domains = ExprList(*domains)
Ejemplo n.º 17
0
    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 proveit.logic import Equals
        from proveit.number import Less, LessEq, Subtract, Add, one
        from composite import _simplifiedCoord
        from proveit._core_.expression.expr import _NoExpandedIteration

        assumptions = defaults.checkedAssumptions(assumptions)
        arg_sorting_assumptions = list(assumptions)

        new_requirements = []

        # Collect the iteration ranges from Indexed sub-Expressions
        # whose variable is being replaced with a Composite (list or tensor).
        # If there are not any, we won't expand the iteration at this point.
        # While we are at it, get all of the end points of the
        # ranges along each axis (as well as end points +/-1 that may be
        # needed if there are overlaps): 'special_points'.
        iter_ranges = set()
        iter_params = self.lambda_map.parameters
        special_points = [set() for _ in xrange(len(iter_params))]
        subbed_start = self.start_indices.substituted(exprMap, relabelMap,
                                                      reservedVars,
                                                      assumptions,
                                                      new_requirements)
        subbed_end = self.end_indices.substituted(exprMap, relabelMap,
                                                  reservedVars, assumptions,
                                                  new_requirements)
        try:
            for iter_range in self.lambda_map.body._expandingIterRanges(
                    iter_params, subbed_start, subbed_end, exprMap, relabelMap,
                    reservedVars, assumptions, new_requirements):
                iter_ranges.add(iter_range)
                for axis, (start, end) in enumerate(zip(*iter_range)):
                    special_points[axis].add(start)
                    special_points[axis].add(end)
                    # Preemptively include start-1 and end+1 in case it is required for splitting up overlapping ranges
                    # (we won't add simplification requirements until we find we actually need them.)
                    # Not necesary in the 1D case.
                    # Add the coordinate simplification to argument sorting assumtions -
                    # after all, this sorting does not go directly into the requirements.
                    start_minus_one = _simplifiedCoord(
                        Subtract(start, one),
                        assumptions=assumptions,
                        requirements=arg_sorting_assumptions)
                    end_plus_one = _simplifiedCoord(
                        Add(end, one),
                        assumptions=assumptions,
                        requirements=arg_sorting_assumptions)
                    special_points[axis].update(
                        {start_minus_one, end_plus_one})
                    # Add start-1<start and end<end+1 assumptions to ease argument sorting -
                    # after all, this sorting does not go directly into the requirements.
                    arg_sorting_assumptions.append(Less(
                        start_minus_one, start))
                    arg_sorting_assumptions.append(Less(end, end_plus_one))
                    arg_sorting_assumptions.append(
                        Equals(end, Subtract(end_plus_one, one)))
                    # Also add start<=end to ease the argument sorting requirement even though it
                    # may not strictly be true if an empty range is possible.  In such a case, we
                    # still want things sorted this way while we don't know if the range is empty or not
                    # and it does not go directly into the requirements.
                    arg_sorting_assumptions.append(LessEq(start, end))

            # 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.

            arg_sorting_relations = []
            for axis in xrange(self.ndims):
                if len(special_points[axis]) == 0:
                    arg_sorting_relation = None
                else:
                    arg_sorting_relation = Less.sort(
                        special_points[axis],
                        assumptions=arg_sorting_assumptions)
                arg_sorting_relations.append(arg_sorting_relation)

            # Put the iteration ranges in terms of indices of the sorting relation operands
            # (relative indices w.r.t. the sorting relation order).
            rel_iter_ranges = set()
            for iter_range in iter_ranges:
                range_start, range_end = iter_range
                rel_range_start = tuple([
                    arg_sorting_relation.operands.index(arg)
                    for arg, arg_sorting_relation in zip(
                        range_start, arg_sorting_relations)
                ])
                rel_range_end = tuple([
                    arg_sorting_relation.operands.index(arg)
                    for arg, arg_sorting_relation in zip(
                        range_end, arg_sorting_relations)
                ])
                rel_iter_ranges.add((rel_range_start, rel_range_end))

            rel_iter_ranges = sorted(
                self._makeNonoverlappingRangeSet(rel_iter_ranges,
                                                 arg_sorting_relations,
                                                 assumptions,
                                                 new_requirements))

            # Generate the expanded list/tensor to replace the iterations.
            if self.ndims == 1: lst = []
            else: tensor = dict()
            for rel_iter_range in rel_iter_ranges:
                # get the starting location of this iteration range
                start_loc = tuple(
                    arg_sorting_relation.operands[idx]
                    for arg_sorting_relation, idx in zip(
                        arg_sorting_relations, rel_iter_range[0]))
                if rel_iter_range[0] == rel_iter_range[1]:
                    # single element entry (starting and ending location the same)
                    inner_expr_map = dict(exprMap)
                    inner_expr_map.update({
                        param: arg
                        for param, arg in zip(self.lambda_map.parameters,
                                              start_loc)
                    })
                    for param in self.lambda_map.parameters:
                        relabelMap.pop(param, None)
                    entry = self.lambda_map.body.substituted(
                        inner_expr_map, relabelMap, reservedVars, assumptions,
                        new_requirements)
                else:
                    # iterate over a sub-range
                    end_loc = tuple(
                        arg_sorting_relation.operands[idx]
                        for arg_sorting_relation, idx in zip(
                            arg_sorting_relations, rel_iter_range[1]))
                    # 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).
                    # Include assumptions that the lambda_map parameters are in the shifted start_loc to end_loc range.
                    range_expr_map = dict(exprMap)
                    range_assumptions = list(assumptions)
                    for start_idx, param, range_start, range_end in zip(
                            self.start_indices, self.lambda_map.parameters,
                            start_loc, end_loc):
                        range_expr_map[param] = Add(
                            param, Subtract(range_start, start_idx))
                        range_assumptions += Less.sort((start_idx, param),
                                                       reorder=False,
                                                       assumptions=assumptions)
                        range_assumptions += Less.sort(
                            (param, Subtract(range_end, start_idx)),
                            reorder=False,
                            assumptions=assumptions)
                    range_lambda_body = self.lambda_map.body.substituted(
                        range_expr_map, relabelMap, reservedVars,
                        range_assumptions, new_requirements)
                    range_lambda_map = Lambda(self.lambda_map.parameters,
                                              range_lambda_body)
                    # Add the shifted sub-range iteration to the appropriate starting location.
                    end_indices = [
                        _simplifiedCoord(Subtract(range_end, start_idx),
                                         assumptions, new_requirements)
                        for start_idx, range_end in zip(
                            self.start_indices, end_loc)
                    ]
                    entry = Iter(range_lambda_map, self.start_indices,
                                 end_indices)
                if self.ndims == 1: lst.append(entry)
                else: tensor[start_loc] = entry

            if self.ndims == 1:
                subbed_self = compositeExpression(lst)
            else:
                subbed_self = compositeExpression(tensor)

        except _NoExpandedIteration:
            # No Indexed sub-Expressions whose variable is
            # replaced with a Composite, so let us not expand the
            # iteration.  Just do an ordinary substitution.
            subbed_map = self.lambda_map.substituted(exprMap, relabelMap,
                                                     reservedVars, assumptions,
                                                     new_requirements)
            subbed_self = Iter(subbed_map, subbed_start, subbed_end)

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

        return subbed_self
Ejemplo n.º 18
0
    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
Ejemplo n.º 19
0
    def __init__(self,
                 operator,
                 instanceVarOrVars,
                 instanceExpr,
                 domain=None,
                 domains=None,
                 conditions=tuple(),
                 nestMultiIvars=False,
                 styles=None,
                 _lambda_map=None):
        '''
        Create an Operation for the given operator that is applied over instances of the 
        given instance Variable(s), instanceVarOrVars, 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.
        instanceVars may be singular or plural (iterable).  An OperationOverInstances is
        effected as an Operation over a conditional Lambda map.
        
        If nestMultiIvars is True do the following:
        When there are multiple instanceVars, this will generate a nested structure in 
        actuality and simply set the style to display these instance variables together.
        In other words, whether instance variables are joined together, like
        "forall_{x, y} P(x, y)" or split in a nested structure like
        "forall_{x} [forall_y P(x, y)]"
        is deemed to be a matter of style, not substance.  Internally it is treated as the
        latter.
              
        If a 'domain' is supplied, additional conditions are generated that each instance 
        Variable is in the domain "set": InSet(x_i, domain), where x_i is for each instance 
        variable.  If, instead, 'domains' are supplied, then each instance variable is supplied
        with its own domain (one for each instance variable).  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 _lambda_map is not None:
            # Use the provided 'lambda_map' instead of creating one.
            lambda_map = _lambda_map
            instanceVars = lambda_map.parameters
            instanceExpr = lambda_map.body
            conditions = lambda_map.conditions
            if len(instanceVars) > 1 and nestMultiIvars:
                raise ValueError(
                    "Invalid 'lambda_map' for %s: multiple parameters "
                    "(%s) are not allowed when 'nestMultiIvars' is True." %
                    (str(self.__class__), str(instanceVars)))
        else:
            # We will need to generate the Lambda sub-expression.
            # Do some initial preparations w.r.t. instanceVars, domain(s), and
            # conditions.
            instanceVars = compositeExpression(instanceVarOrVars)
            if len(instanceVars) == 0:
                raise ValueError(
                    "Expecting at least one instance variable 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(instanceVars)

            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(instanceVars):
                    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")
                conditions = [
                    InSet(instanceVar, domain)
                    for instanceVar, domain in zip(instanceVars, domains)
                ] + 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.
        if (len(conditions) >= len(instanceVars) and all(
                isinstance(cond, InSet) and cond.element == ivar
                for ivar, cond in zip(instanceVars, conditions))):
            domains = [cond.domain for cond in conditions[:len(instanceVars)]]
            # Used if we have a single instance variable
            # or nestMultiIvars is True:
            domain = domains[0]
            nondomain_conditions = conditions[len(instanceVars):]
        else:
            domain = domains = None
            nondomain_conditions = conditions

        if _lambda_map is None:
            # Now do the actual lambda_map creation after handling
            # nesting.

            # Handle nesting of multiple instance variables if needed.
            if len(instanceVars) > 1 and nestMultiIvars:

                # Figure out how many "non-domain" conditions belong at
                # each level.  At each level, "non-domain" conditions are
                # included up to the first on that has any free variables that
                # include any of the "inner" instance variable parameters.
                cond_free_vars = {
                    cond: cond.freeVars()
                    for cond in nondomain_conditions
                }
                num_nondomain_conditions_vs_level = [0] * len(instanceVars)
                remaining_nondomain_conditions = list(nondomain_conditions)
                for i in range(len(instanceVars)):
                    # Parameter variables correpsonding to 'inner' instance
                    # variables at this level:
                    inner_instance_params = set(
                        getParamVar(ivar) for ivar in instanceVars[i + 1:])
                    # Start with the default # of non-domain conditions:
                    num_nondomain_conditions = len(
                        remaining_nondomain_conditions)
                    # Go until a condition contains any of the "inner"
                    # instance variable parameters as a free variable.
                    for k, cond in enumerate(remaining_nondomain_conditions):
                        if not cond_free_vars[cond].isdisjoint(
                                inner_instance_params):
                            num_nondomain_conditions = k
                            break
                    # Record the # of non-domain conditions and update the
                    # 'remaining' ones.
                    num_nondomain_conditions_vs_level[
                        i] = num_nondomain_conditions
                    remaining_nondomain_conditions = \
                        remaining_nondomain_conditions[num_nondomain_conditions:]

                # Generate the nested OperationOverInstances from the inside
                # out.
                remaining_nondomain_conditions = list(nondomain_conditions)
                for i in range(len(instanceVars) - 1, 0, -1):
                    inner_instance_var = instanceVars[i]

                    # Get the appropriate conditions for level i.
                    nconds = num_nondomain_conditions_vs_level[i]
                    if nconds > 0:
                        inner_conditions = remaining_nondomain_conditions[
                            -nconds:]
                        remaining_nondomain_conditions = \
                            remaining_nondomain_conditions[:-nconds]
                    else:
                        inner_conditions = []
                    if domains is not None:
                        # prepend the domain condition
                        inner_conditions.insert(0, conditions[i])

                    # create the instnaceExpr at level i.
                    innerOperand = self._createOperand(
                        [inner_instance_var],
                        instanceExpr,
                        conditions=inner_conditions)
                    inner_styles = dict(styles)
                    if i == len(instanceVars) - 1:
                        # Inner-most -- no joining further.
                        inner_styles['instance_vars'] = 'no_join'
                    else:
                        # Join with the next level.
                        inner_styles['instance_vars'] = 'join_next'
                    instanceExpr = self.__class__._make(
                        ['Operation'], inner_styles, [operator, innerOperand])

                assert num_nondomain_conditions_vs_level[0] \
                            == len(remaining_nondomain_conditions)

                # Get the appropriate top-level condition.
                if domains is None:
                    conditions = remaining_nondomain_conditions
                else:
                    # prepend the domain condition at the top level.
                    conditions = [conditions[0]
                                  ] + remaining_nondomain_conditions

                instanceVarOrVars = instanceVars[0]
                instanceVars = [instanceVarOrVars]
                # Combine instance variables in the style:
                styles['instance_vars'] = 'join_next'
            elif len(instanceVars) == 1:
                instanceVarOrVars = instanceVars[0]
                # No combining instance variables in the style:
                styles['instance_vars'] = 'no_join'

            # Generate the Lambda sub-expression.
            lambda_map = OperationOverInstances._createOperand(
                instanceVarOrVars, instanceExpr, conditions)

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

        if len(instanceVars) > 1:
            self.instanceVars = instanceVars
            self.domains = domains  # Domain for each instance variable
        else:
            self.instanceVar = instanceVars[0]
            '''Outermost instance variable (or iteration of indexed variables) of the OperationOverInstance.'''
            self.domain = domain
            '''Domain of the outermost instance variable (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.'''

        Operation.__init__(self, operator, lambda_map, styles=styles)
Ejemplo n.º 20
0
 def __init__(self,
              parameter_or_parameters,
              body,
              conditions=tuple(),
              styles=dict(),
              requirements=tuple()):
     '''
     Initialize a Lambda function expression given parameter(s) and a body.
     Each parameter must be a Variable.
     When there is a single parameter, there will be a 'parameter'
     attribute. Either way, there will be a 'parameters' attribute
     that bundles the one or more Variables into an ExprList.
     The 'body' attribute will be the lambda function body
     Expression (that may or may not be a Composite).  Zero or
     more expressions may be provided.
     '''
     from proveit._core_.expression.composite import compositeExpression, singleOrCompositeExpression, Iter, Indexed
     from proveit._core_.expression.label import Variable
     self.parameters = compositeExpression(parameter_or_parameters)
     parameterVars = list()
     for parameter in self.parameters:
         if isinstance(parameter, Iter) and isinstance(
                 parameter.lambda_map.body, Indexed):
             parameterVars.append(parameter.lambda_map.body.var)
         elif isinstance(parameter, Indexed):
             parameterVars.append(parameter.var)
         elif isinstance(parameter, Variable):
             parameterVars.append(parameter)
         else:
             raise TypeError(
                 'parameters must be a Variables, Indexed variable, or iteration (Iter) over Indexed variables.'
             )
     if len(self.parameters) == 1:
         # has a single parameter
         self.parameter = self.parameters[0]
         self.parameter_or_parameters = self.parameter
     else:
         self.parameter_or_parameters = self.parameters
     self.parameterVars = tuple(parameterVars)
     self.parameterVarSet = frozenset(parameterVars)
     if len(self.parameterVarSet) != len(self.parameters):
         raise ValueError(
             'Lambda parameters Variables must be unique with respect to each other.'
         )
     body = singleOrCompositeExpression(body)
     if not isinstance(body, Expression):
         raise TypeError('A Lambda body must be of type Expression')
     if isinstance(body, Iter):
         raise TypeError(
             'An Iter must be within an ExprList or ExprTensor, not directly as a Lambda body'
         )
     self.body = body
     self.conditions = compositeExpression(conditions)
     for requirement in self.body.requirements:
         if not self.parameterVarSet.isdisjoint(requirement.freeVars()):
             raise LambdaError(
                 "Cannot generate a Lambda expression with parameter variables involved in Lambda body requirements: "
                 + str(requirement))
     Expression.__init__(
         self, ['Lambda'],
         [self.parameter_or_parameters, self.body, self.conditions],
         styles=styles,
         requirements=requirements)