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)
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
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)
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
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
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])
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
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
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))
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)
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.''' """
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)
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)
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
def __init__(self, parameter_or_parameters, body, start_index_or_indices, end_index_or_indices, styles=None, requirements=tuple(), _lambda_map=None): ''' Create an Iter that represents an iteration of the body for the parameter(s) ranging from the start index/indices to the end index/indices. A Lambda expression will be created as its sub-expression that maps the parameter(s) to the body with conditions that restrict the parameter(s) to the appropriate interval. _lambda_map is used internally for efficiently rebuilding an Iter. ''' from proveit.logic import InSet from proveit.number import Interval if _lambda_map is not None: # Use the provided 'lambda_map' instead of creating one. lambda_map = _lambda_map pos_args = (parameter_or_parameters, body, start_index_or_indices, end_index_or_indices) if pos_args != (None, None, None, None): raise ValueError( "Positional arguments of the Init constructor " "should be None if lambda_map is provided.") parameters = lambda_map.parameters body = lambda_map.body conditions = lambda_map.conditions if len(conditions) != len(parameters): raise ValueError( "Inconsistent number of conditions and lambda " "map parameters") start_indices, end_indices = [], [] for param, condition in zip(parameters, conditions): invalid_condition_msg = ( "Not the right kind of lambda_map condition " "for an iteration") if not isinstance(condition, InSet) or condition.element != param: raise ValueError(invalid_condition_msg) domain = condition.domain if not isinstance(domain, Interval): raise ValueError(invalid_condition_msg) start_index, end_index = domain.lowerBound, domain.upperBound start_indices.append(start_index) end_indices.append(end_index) self.start_indices = ExprTuple(*start_indices) self.end_indices = ExprTuple(*end_indices) if len(parameters) == 1: self.start_index = self.start_indices[0] self.end_index = self.end_indices[0] self.start_index_or_indices = self.start_index self.end_index_or_indices = self.end_index else: self.start_index_or_indices = self.start_indices self.end_index_or_indices = self.end_indices else: parameters = compositeExpression(parameter_or_parameters) start_index_or_indices = singleOrCompositeExpression( start_index_or_indices) if isinstance(start_index_or_indices, ExprTuple) and len(start_index_or_indices) == 1: start_index_or_indices = start_index_or_indices[0] self.start_index_or_indices = start_index_or_indices if isinstance(start_index_or_indices, Composite): # a composite of multiple indices self.start_indices = self.start_index_or_indices else: # a single index self.start_index = self.start_index_or_indices # wrap a single index in a composite for convenience self.start_indices = compositeExpression( self.start_index_or_indices) end_index_or_indices = singleOrCompositeExpression( end_index_or_indices) if isinstance(end_index_or_indices, ExprTuple) and len(end_index_or_indices) == 1: end_index_or_indices = end_index_or_indices[0] self.end_index_or_indices = end_index_or_indices if isinstance(self.end_index_or_indices, Composite): # a composite of multiple indices self.end_indices = self.end_index_or_indices else: # a single index self.end_index = self.end_index_or_indices # wrap a single index in a composite for convenience self.end_indices = compositeExpression( self.end_index_or_indices) conditions = [] for param, start_index, end_index in zip(parameters, self.start_indices, self.end_indices): conditions.append( InSet(param, Interval(start_index, end_index))) lambda_map = Lambda(parameters, body, conditions=conditions) self.ndims = len(self.start_indices) if self.ndims != len(self.end_indices): raise ValueError( "Inconsistent number of 'start' and 'end' indices") if len(parameters) != len(self.start_indices): raise ValueError( "Inconsistent number of indices and lambda map parameters") Expression.__init__(self, ['Iter'], [lambda_map], styles=styles, requirements=requirements) self.lambda_map = lambda_map self._checkIndexedRestriction(body)
def __init__(self, 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)
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
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
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)
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)