def arg_is_dsl_binop(arg, upd=False):
     """If arg is dsl_binop, then the dsl_binop should represent the
     addition or subtraction of an integer literal from a index or
     auxiliary."""
     if arg.op() is op_add or arg.op() is op_sub:
         if isinstance_list(arg.left(), self._indextypes):
             if isinstance(arg.right(), int):
                 c = arg.right()
                 a = arg.left()
                 op = arg.op()
                 self.add_index(a, c, op, update=upd)
                 return None
             else:
                 raise Exception('Only integer literals '\
                                +'can be added to an index')
         elif isinstance_list(arg.left(), self._auxtypes):
             if isinstance(arg.right(), int):
                 c = arg.right()
                 a = arg.left()
                 op = arg.op()
                 self.add_auxiliary(a, c, op, update=upd)
                 return None
             else:
                 raise Exception('Only integer literals '\
                                +'can be added to an index')
         else:
             raise Exception('binary operation argument must take the form '+\
                             '[index_object] + [integer] or '+\
                             '[auxiliary_object] + [integer]')
     else:
         raise Exception('Can only accept addition or subtraction '\
                        +'for increments')
 def arg_is_dsl_binop(arg,upd=False):
     """If arg is dsl_binop, then the dsl_binop should represent the
     addition or subtraction of an integer literal from a index or
     auxiliary."""
     if arg.op() is op_add or arg.op() is op_sub:
         if isinstance_list(arg.left(),self._indextypes ):
             if isinstance(arg.right(),int):
                 c = arg.right()
                 a = arg.left()
                 op = arg.op()
                 self.add_index(a,c,op,update=upd)
                 return None
             else:
                 raise Exception('Only integer literals '\
                                +'can be added to an index')
         elif isinstance_list(arg.left(),self._auxtypes):
             if isinstance(arg.right(),int):
                 c = arg.right()
                 a = arg.left()
                 op = arg.op()
                 self.add_auxiliary(a,c,op,update=upd)
                 return None
             else:
                 raise Exception('Only integer literals '\
                                +'can be added to an index')
         else:
             raise Exception('binary operation argument must take the form '+\
                             '[index_object] + [integer] or '+\
                             '[auxiliary_object] + [integer]')
     else:
         raise Exception('Can only accept addition or subtraction '\
                        +'for increments')
    def add_auxiliary(self, aux,change=0,op=op_add,update=False):
        """Adds a DSL object (e.g. dsl_integer_index) to a list of 
        integral auxiliary indices and a dictionary of auxiliary indices.
        These are indices that define integral intermediates, but are not required
        to define the final end-result class of integrals.

        If update == True, do not add a new index, but modify and existing dsl_binop
        in self._index_dict with updated change and op.
        
        Implementation notes: 
        * Currently this set of indices must be all of the same type, e.g.
          all dsl_integer_index objects. In future this may be expanded to allow for
          integrals with mixed auxiliary index types.
        * Only a single dsl_integer_index object is currently supported, and this can
          only be incremented in RR expressions (no decrements).
        * It is assumed that the auxiliary indices are equal to zero in the 
          final end-result class of integrals.
        """
        assert isinstance_list(aux,self._auxtypes),\
                'aux must be one of the allowed types in self._auxtypes'
        self._add_obj(aux,self._aux_list,\
                      dsl_binop(op,aux,change),self._aux_dict,\
                      update)
#        self._aux_list.append(aux)
#        self._aux_dict[ aux.name() ] = dsl_binop(op,aux,change)

        # [ Current implementation restriction ]
        assert len( self._aux_list ) <= 1, 'only one auxiliary allowed'
        assert op == op_add, 'only increments allowed in auxiliary index'
    def add_auxiliary(self, aux, change=0, op=op_add, update=False):
        """Adds a DSL object (e.g. dsl_integer_index) to a list of 
        integral auxiliary indices and a dictionary of auxiliary indices.
        These are indices that define integral intermediates, but are not required
        to define the final end-result class of integrals.

        If update == True, do not add a new index, but modify and existing dsl_binop
        in self._index_dict with updated change and op.
        
        Implementation notes: 
        * Currently this set of indices must be all of the same type, e.g.
          all dsl_integer_index objects. In future this may be expanded to allow for
          integrals with mixed auxiliary index types.
        * Only a single dsl_integer_index object is currently supported, and this can
          only be incremented in RR expressions (no decrements).
        * It is assumed that the auxiliary indices are equal to zero in the 
          final end-result class of integrals.
        """
        assert isinstance_list(aux,self._auxtypes),\
                'aux must be one of the allowed types in self._auxtypes'
        self._add_obj(aux,self._aux_list,\
                      dsl_binop(op,aux,change),self._aux_dict,\
                      update)
        #        self._aux_list.append(aux)
        #        self._aux_dict[ aux.name() ] = dsl_binop(op,aux,change)

        # [ Current implementation restriction ]
        assert len(self._aux_list) <= 1, 'only one auxiliary allowed'
        assert op == op_add, 'only increments allowed in auxiliary index'
 def add_argument(self, arg, update=False):
     """Adds a DSL object (e.g. dsl_scalar) to a list and dictionary of 
     arguments required to define the integral class in addition to the 
     indices and auxiliaries. For example, a program for evaluating 
     nuclear attraction integrals ( a | 1/rC | b ) requires an argument
     specifying the nuclear centre 'C', as well as arguments associated with the
     two indices 'a' and 'b'. 
     A DSL object added to the list/dictionary of arguments will be appended to 
     the arguments for the subroutine used to generate the integral class and can 
     therefore be passed in by the calling unit.
     """
     assert isinstance_list(arg,self._argtypes),\
             'arg must be one of the allowed types in self._argtypes'
     self._add_obj(arg,self._arg_list,\
                   arg,self._arg_dict,\
                   update)
 def add_argument(self, arg, update=False):
     """Adds a DSL object (e.g. dsl_scalar) to a list and dictionary of 
     arguments required to define the integral class in addition to the 
     indices and auxiliaries. For example, a program for evaluating 
     nuclear attraction integrals ( a | 1/rC | b ) requires an argument
     specifying the nuclear centre 'C', as well as arguments associated with the
     two indices 'a' and 'b'. 
     A DSL object added to the list/dictionary of arguments will be appended to 
     the arguments for the subroutine used to generate the integral class and can 
     therefore be passed in by the calling unit.
     """
     assert isinstance_list(arg,self._argtypes),\
             'arg must be one of the allowed types in self._argtypes'
     self._add_obj(arg,self._arg_list,\
                   arg,self._arg_dict,\
                   update)
    def add_index(self,index,change=0,op=op_add,update=False):
        """Adds a DSL object (e.g. dsl_cartesian_gaussian) to a list of 
        integral indices and a dictionary of indices.These are non-auxiliary indices, 
        i.e. they define the desired end-result class of integrals.

        If update == True, do not add a new index, but modify and existing dsl_binop
        in self._index_dict with updated change and op.

        Implementation notes: 
        * Currently this set of indices must be all of the same type, e.g.
          all dsl_cartesian_gaussian objects. In future this may be expanded to allow for
          integrals with mixed index types."""
        assert isinstance_list(index,self._indextypes),\
                'index must be one of the allowed types in self._indextypes'
        self._add_obj(index,self._index_list,\
                      dsl_binop(op,index,change),self._index_dict,\
                      update)
    def add_index(self, index, change=0, op=op_add, update=False):
        """Adds a DSL object (e.g. dsl_cartesian_gaussian) to a list of 
        integral indices and a dictionary of indices.These are non-auxiliary indices, 
        i.e. they define the desired end-result class of integrals.

        If update == True, do not add a new index, but modify and existing dsl_binop
        in self._index_dict with updated change and op.

        Implementation notes: 
        * Currently this set of indices must be all of the same type, e.g.
          all dsl_cartesian_gaussian objects. In future this may be expanded to allow for
          integrals with mixed index types."""
        assert isinstance_list(index,self._indextypes),\
                'index must be one of the allowed types in self._indextypes'
        self._add_obj(index,self._index_list,\
                      dsl_binop(op,index,change),self._index_dict,\
                      update)
    def parse_args(self,args,copy_from = None):
        """Goes through a tuple of arguments passed to the __init__() method
        and updates the corresponding object attributes.

        If copy_from is set to a dsl_integral object, parse_args additionally
        checks that the index, auxilary and argument objects in args are 
        in copy_from._index_list, copy_from._aux_list and copy_from._arg_list,
        i.e. if the new object is being copied from another, no new indexes,
        auxiliary or arguments may be added.
        """

        def arg_is_dsl_binop(arg,upd=False):
            """If arg is dsl_binop, then the dsl_binop should represent the
            addition or subtraction of an integer literal from a index or
            auxiliary."""
            if arg.op() is op_add or arg.op() is op_sub:
                if isinstance_list(arg.left(),self._indextypes ):
                    if isinstance(arg.right(),int):
                        c = arg.right()
                        a = arg.left()
                        op = arg.op()
                        self.add_index(a,c,op,update=upd)
                        return None
                    else:
                        raise Exception('Only integer literals '\
                                       +'can be added to an index')
                elif isinstance_list(arg.left(),self._auxtypes):
                    if isinstance(arg.right(),int):
                        c = arg.right()
                        a = arg.left()
                        op = arg.op()
                        self.add_auxiliary(a,c,op,update=upd)
                        return None
                    else:
                        raise Exception('Only integer literals '\
                                       +'can be added to an index')
                else:
                    raise Exception('binary operation argument must take the form '+\
                                    '[index_object] + [integer] or '+\
                                    '[auxiliary_object] + [integer]')
            else:
                raise Exception('Can only accept addition or subtraction '\
                               +'for increments')

        def copy_from_check(obj):
            """Raises and AssertionError if obj is not present in the the
            _index_list, _aux_list or _arg_list attributes of copy_from."""
            all_obj_list = copy_from._index_list + copy_from._aux_list +\
                           copy_from._arg_list
            assert obj in all_obj_list,\
                obj.name()+' not present in copy_from. If copy_from is set, '+\
                'obj must have been added to the copy_from dsl_integral instance.'
        if copy_from != None: 
            upd = True
        else:
            upd = False
        i = 1
        for arg in args:
            obj = arg
            if isinstance(arg,dsl_binop):
                arg_is_dsl_binop(arg,upd)
                obj = arg.left()
            elif isinstance_list(arg,self._indextypes):
                # No change in a Cartesian Gaussian
                self.add_index(arg,update=upd)
            elif isinstance_list(arg,self._auxtypes):
                # No change in an auxiliary index
                self.add_auxiliary(arg,update=upd)
            elif isinstance_list(arg,self._argtypes):
                self.add_argument(arg,update=upd)
            else:
                raise Exception('Argument number '+str(i)+': '+str(arg)+\
                                ' not understood.')
            if upd == True: copy_from_check(obj)
            i +=1

        if upd==True:
            # Additionally ensure that the same number of indexes, auxiliaries,
            # and arguments are present in a copy and original
            assert len( copy_from._index_list ) == len( self._index_list ),\
                'cannot change number of indexes in copy'
            assert len( copy_from._aux_list ) == len( self._aux_list ),\
                'cannot change number of auxiliaries in copy'
            assert len( copy_from._arg_list ) == len( self._arg_list ),\
                'cannot change number of arguments in copy'
def expr_permute(expr,a,b) : 
    """
    Permute indices a and b in dsl_integral, dsl_cartesian_gaussian and
    dsl_variable objects in an RR expression. The returned expression is
    a copy of expr, but with the dsl_index objects a and b swapped.
    Additionally, any dsl_variable (or derived class) objects which are
    attached to the index objects (i.e. returned by dsl_index.attachment_list)
    are also swapped in the expression. 
    dsl_variable objects for which expr() returns a DSL expression containing 
    other dsl_variable objects will be replaced with new dsl_variable objects
    with the expr() permuted in the same way. 
    This is done recursively, so a dsl_variable.expr() is treated in the same
    way as the DSL expression containing the dsl_variable.
    If expr contains dsl_integral objects, then these must have both a and b
    associated with them. 
    For dsl_integral objects, the increments/decrements in a and b are swapped, so
    if a = ga, b = gb and the expression contains
        dsl_integral( ga+1, gb-1 )  
    This dsl_integral object is replaced by a new dsl_integral
        dsl_integral( ga-1, gb+1 )
    """
    ### Begin expr_permute code ###
    assert type(a) == type(b), 'type(a) != type (b)'
    assert isinstance(a,dsl_index), 'a is not a dsl_index object'
    assert isinstance(b,dsl_index), 'b is not a dsl_index object'
    if expr is a:   
        expr = b
    elif expr is b: 
        expr = a
    elif isinstance(expr,dsl_binop):
        expr = dsl_binop( expr.op(), expr_permute( expr.left(),a,b),\
                              expr_permute( expr.right(),a,b ) )
#        print('out:', type(expr.left() ), type(expr.right() ) )
    elif isinstance(expr,dsl_unop):
        expr = dsl_unop( expr.op(), expr_permute( expr.arg(),a,b) )
    elif isinstance(expr,dsl_integral):
        new_int = expr.int()
        if isinstance_list( a,expr.indextypes() ) and\
           isinstance_list( b,expr.indextypes() ):
               if a in expr.index_list() and b in expr.index_list():
                    a_binop = expr.index_binop( a )
                    b_binop = expr.index_binop( b )
                    new_int.add_index(a,change=b_binop.right(),op=b_binop.op(),update=True)
                    new_int.add_index(b,change=a_binop.right(),op=a_binop.op(),update=True)
               if a in expr.index_list(): 
                   assert b in expr.index_list(), 'dsl_integral instance only has '+\
                        a.name()+'index. Cannot permute.'
               elif b in expr.index_list(): 
                   assert a in expr.index_list(), 'dsl_integral instance only has '+\
                        b.name()+'index. Cannot permute.'
        elif isinstance_list( a,expr.auxtypes() ) and\
             isinstance_list( b,expr.auxtypes() ):
               if a in expr.aux_list() and b in expr.aux_list():
                    a_binop = expr.aux_binop( a )
                    b_binop = expr.aux_binop( b )
                    new_int.add_auxiliary(a,change=b_binop.right(),op=b_binop.op(),\
                            update=True)
                    new_int.add_auxiliary(b,change=a_binop.right(),op=a_binop.op(),\
                            update=True)
        expr = new_int
    elif isinstance(expr,dsl_variable):
        # If dsl_variable objects are directly attached to dsl_index objects a,b
        # then simply swap the objects in the DSL expression.
        if expr in a.attachment_list() :
            for k, v in a.attachment_dict().items():
                if expr is v: 
                    key = k 
                    break
            expr = b.attachment( key )
        elif expr in b.attachment_list() :
            for k, v in b.attachment_dict().items():
                if expr is v: 
                    key = k 
                    break
            expr = a.attachment( key )
        # If dsl_variable objects are not directly attached, then do a recursive
        # search of the variable dependencies to see if they carry objects attached
        # to dsl_index object a,b. If they do, create a new dsl_variable object
        # of the appropriate derived type and permute the expression of that.
        elif expr.expr() != None:
            # Permutation symmetry check
            if expr_basic_symmetry_check( expr.expr(), a, b ) == False:
                new_variable = expr.__class__(expr = expr_permute( expr.expr(), a, b ), vartype=expr.type() )
                expr = new_variable
    return expr
    def parse_args(self, args, copy_from=None):
        """Goes through a tuple of arguments passed to the __init__() method
        and updates the corresponding object attributes.

        If copy_from is set to a dsl_integral object, parse_args additionally
        checks that the index, auxilary and argument objects in args are 
        in copy_from._index_list, copy_from._aux_list and copy_from._arg_list,
        i.e. if the new object is being copied from another, no new indexes,
        auxiliary or arguments may be added.
        """
        def arg_is_dsl_binop(arg, upd=False):
            """If arg is dsl_binop, then the dsl_binop should represent the
            addition or subtraction of an integer literal from a index or
            auxiliary."""
            if arg.op() is op_add or arg.op() is op_sub:
                if isinstance_list(arg.left(), self._indextypes):
                    if isinstance(arg.right(), int):
                        c = arg.right()
                        a = arg.left()
                        op = arg.op()
                        self.add_index(a, c, op, update=upd)
                        return None
                    else:
                        raise Exception('Only integer literals '\
                                       +'can be added to an index')
                elif isinstance_list(arg.left(), self._auxtypes):
                    if isinstance(arg.right(), int):
                        c = arg.right()
                        a = arg.left()
                        op = arg.op()
                        self.add_auxiliary(a, c, op, update=upd)
                        return None
                    else:
                        raise Exception('Only integer literals '\
                                       +'can be added to an index')
                else:
                    raise Exception('binary operation argument must take the form '+\
                                    '[index_object] + [integer] or '+\
                                    '[auxiliary_object] + [integer]')
            else:
                raise Exception('Can only accept addition or subtraction '\
                               +'for increments')

        def copy_from_check(obj):
            """Raises and AssertionError if obj is not present in the the
            _index_list, _aux_list or _arg_list attributes of copy_from."""
            all_obj_list = copy_from._index_list + copy_from._aux_list +\
                           copy_from._arg_list
            assert obj in all_obj_list,\
                obj.name()+' not present in copy_from. If copy_from is set, '+\
                'obj must have been added to the copy_from dsl_integral instance.'

        if copy_from != None:
            upd = True
        else:
            upd = False
        i = 1
        for arg in args:
            obj = arg
            if isinstance(arg, dsl_binop):
                arg_is_dsl_binop(arg, upd)
                obj = arg.left()
            elif isinstance_list(arg, self._indextypes):
                # No change in a Cartesian Gaussian
                self.add_index(arg, update=upd)
            elif isinstance_list(arg, self._auxtypes):
                # No change in an auxiliary index
                self.add_auxiliary(arg, update=upd)
            elif isinstance_list(arg, self._argtypes):
                self.add_argument(arg, update=upd)
            else:
                raise Exception('Argument number '+str(i)+': '+str(arg)+\
                                ' not understood.')
            if upd == True: copy_from_check(obj)
            i += 1

        if upd == True:
            # Additionally ensure that the same number of indexes, auxiliaries,
            # and arguments are present in a copy and original
            assert len( copy_from._index_list ) == len( self._index_list ),\
                'cannot change number of indexes in copy'
            assert len( copy_from._aux_list ) == len( self._aux_list ),\
                'cannot change number of auxiliaries in copy'
            assert len( copy_from._arg_list ) == len( self._arg_list ),\
                'cannot change number of arguments in copy'
def expr_permute(expr, a, b):
    """
    Permute indices a and b in dsl_integral, dsl_cartesian_gaussian and
    dsl_variable objects in an RR expression. The returned expression is
    a copy of expr, but with the dsl_index objects a and b swapped.
    Additionally, any dsl_variable (or derived class) objects which are
    attached to the index objects (i.e. returned by dsl_index.attachment_list)
    are also swapped in the expression. 
    dsl_variable objects for which expr() returns a DSL expression containing 
    other dsl_variable objects will be replaced with new dsl_variable objects
    with the expr() permuted in the same way. 
    This is done recursively, so a dsl_variable.expr() is treated in the same
    way as the DSL expression containing the dsl_variable.
    If expr contains dsl_integral objects, then these must have both a and b
    associated with them. 
    For dsl_integral objects, the increments/decrements in a and b are swapped, so
    if a = ga, b = gb and the expression contains
        dsl_integral( ga+1, gb-1 )  
    This dsl_integral object is replaced by a new dsl_integral
        dsl_integral( ga-1, gb+1 )
    """
    ### Begin expr_permute code ###
    assert type(a) == type(b), 'type(a) != type (b)'
    assert isinstance(a, dsl_index), 'a is not a dsl_index object'
    assert isinstance(b, dsl_index), 'b is not a dsl_index object'
    if expr is a:
        expr = b
    elif expr is b:
        expr = a
    elif isinstance(expr, dsl_binop):
        expr = dsl_binop( expr.op(), expr_permute( expr.left(),a,b),\
                              expr_permute( expr.right(),a,b ) )


#        print('out:', type(expr.left() ), type(expr.right() ) )
    elif isinstance(expr, dsl_unop):
        expr = dsl_unop(expr.op(), expr_permute(expr.arg(), a, b))
    elif isinstance(expr, dsl_integral):
        new_int = expr.int()
        if isinstance_list( a,expr.indextypes() ) and\
           isinstance_list( b,expr.indextypes() ):
            if a in expr.index_list() and b in expr.index_list():
                a_binop = expr.index_binop(a)
                b_binop = expr.index_binop(b)
                new_int.add_index(a,
                                  change=b_binop.right(),
                                  op=b_binop.op(),
                                  update=True)
                new_int.add_index(b,
                                  change=a_binop.right(),
                                  op=a_binop.op(),
                                  update=True)
            if a in expr.index_list():
                assert b in expr.index_list(), 'dsl_integral instance only has '+\
                     a.name()+'index. Cannot permute.'
            elif b in expr.index_list():
                assert a in expr.index_list(), 'dsl_integral instance only has '+\
                     b.name()+'index. Cannot permute.'
        elif isinstance_list( a,expr.auxtypes() ) and\
             isinstance_list( b,expr.auxtypes() ):
            if a in expr.aux_list() and b in expr.aux_list():
                a_binop = expr.aux_binop(a)
                b_binop = expr.aux_binop(b)
                new_int.add_auxiliary(a,change=b_binop.right(),op=b_binop.op(),\
                        update=True)
                new_int.add_auxiliary(b,change=a_binop.right(),op=a_binop.op(),\
                        update=True)
        expr = new_int
    elif isinstance(expr, dsl_variable):
        # If dsl_variable objects are directly attached to dsl_index objects a,b
        # then simply swap the objects in the DSL expression.
        if expr in a.attachment_list():
            for k, v in a.attachment_dict().items():
                if expr is v:
                    key = k
                    break
            expr = b.attachment(key)
        elif expr in b.attachment_list():
            for k, v in b.attachment_dict().items():
                if expr is v:
                    key = k
                    break
            expr = a.attachment(key)
        # If dsl_variable objects are not directly attached, then do a recursive
        # search of the variable dependencies to see if they carry objects attached
        # to dsl_index object a,b. If they do, create a new dsl_variable object
        # of the appropriate derived type and permute the expression of that.
        elif expr.expr() != None:
            # Permutation symmetry check
            if expr_basic_symmetry_check(expr.expr(), a, b) == False:
                new_variable = expr.__class__(expr=expr_permute(
                    expr.expr(), a, b),
                                              vartype=expr.type())
                expr = new_variable
    return expr