def decomp_4rdm_to_3rdm_sf(d4, d1, d2, d2_hom, d2_het, d3):
  """
  Returns the list of terms composing the decomposition of the 4 body spin free rdm d4
  in terms of one, two, and three body rdms"
  """

  # This decomposition assumes the density matrices correspond to a spin averaged ensemble state.

  # d4 -- Spin free 4rdm to be decomposed
  # d1 -- One body spin free rdm
  # d2 -- Two body spin free rdm
  # d2_hom -- Sum of all alpha and all beta two body rdms.  The "homogeneous spin" 2rdm.
  # d2_het -- Sum of abab and baba two body rdms.  The "heterogeneous spin" 2rdm.
  # d3 -- Three body spin free rdm

  # Note that d2 = d2_hom + d2_het

  # Check input
  if ( not isinstance(d4, tensor) ) or (len(d4.indices) != 8):
    raise TypeError, "d4 must be a tensor with 8 indices"
  if ( not isinstance(d1, tensor) ) or (len(d1.indices) != 2):
    raise TypeError, "d1 must be a tensor with 2 indices"
  if ( not isinstance(d2, tensor) ) or (len(d2.indices) != 4):
    raise TypeError, "d2 must be a tensor with 4 indices"
  if ( not isinstance(d2_hom, tensor) ) or (len(d2_hom.indices) != 4):
    raise TypeError, "d2_hom must be a tensor with 4 indices"
  if ( not isinstance(d2_het, tensor) ) or (len(d2_het.indices) != 4):
    raise TypeError, "d2_het must be a tensor with 4 indices"
  if ( not isinstance(d3, tensor) ) or (len(d3.indices) != 6):
    raise TypeError, "d3 must be a tensor with 6 indices"

  # Initialize the return value
  decomp = []

  # Compute terms from the sum involving the 3-body cumulant
  for p in range(4):
    ti = range(4)
    del(ti[p])
    ti.insert(0,p)
    for q in range(4):
      bi = range(4)
      del(bi[q])
      bi.insert(0,q)
      for i in range(1,4):
        for j in range(1,4):
          if i != j and ti[i] == bi[j]:
            temp = bi[i]
            bi[i] = bi[j]
            bi[j] = temp

      # Determine the number of index permutations
      n_perm = get_num_perms(ti,bi)

      # Determine the sign
      sign = (-1)**n_perm

      # Create the 1RDM 3RDM term
      bj = [i+4 for i in bi]
      coeff = sign * (0.5)**n_perm
      d1_copy_0 = d1.copy()
      d1_copy_0.indices = [d4.indices[i] for i in (ti[0:1] + bj[0:1])]
      d3_copy_0 = d3.copy()
      d3_copy_0.indices = [d4.indices[i] for i in (ti[1:4] + bj[1:4])]
      decomp.append(term(coeff, [], [d1_copy_0, d3_copy_0]))

      # Create terms not involving the 3RDM
      d3_decomp = decomp_3rdm_to_2rdm_sf(d3_copy_0, d1, d2)
      for t in d3_decomp:
        coeff = (-1) * sign * (0.5)**n_perm * t.numConstant
        decomp.append(term(coeff, [], [d1_copy_0] + t.tensors))
      
  # Compute terms from the sum involving two 2-body cumulants
  for p in range(1):
    for q in range(p+1,4):
      ti = [p,q]
      for i in range(4):
        if not (i in ti):
          ti.append(i)
      for r in range(4):
        for s in range(r+1,4):
          if s == p or r == q:
            bi = [s,r]
          else:
            bi = [r,s]
          temp = range(4)
          del temp[s]
          del temp[r]
          if temp[0] == ti[3] or temp[1] == ti[2]:
            temp = [temp[1], temp[0]]
          bi += temp
          
          # Determine the number of index permutations
          n_perm = get_num_perms(ti,bi)

          # Determine the sign
          sign = (-1)**n_perm

          # Create 2RDM 2RDM term
          bj = [i+4 for i in bi]
          if n_perm < 2:
            coeff = sign * (0.5)**n_perm
            d2_copy_0 = d2.copy()
            d2_copy_0.indices = [d4.indices[i] for i in (ti[0:2] + bj[0:2])]
            d2_copy_1 = d2.copy()
            d2_copy_1.indices = [d4.indices[i] for i in (ti[2:4] + bj[2:4])]
            decomp.append(term(coeff, [], [d2_copy_0, d2_copy_1]))
          elif n_perm == 2:
            coeff = sign * 0.5
            d2_hom_copy_0 = d2_hom.copy()
            d2_hom_copy_0.indices = [d4.indices[i] for i in (ti[0:2] + bj[0:2])]
            d2_hom_copy_1 = d2_hom.copy()
            d2_hom_copy_1.indices = [d4.indices[i] for i in (ti[2:4] + bj[2:4])]
            decomp.append(term(coeff, [], [d2_hom_copy_0, d2_hom_copy_1]))
            d2_het_copy_0 = d2_het.copy()
            d2_het_copy_0.indices = [d4.indices[i] for i in (ti[0:2] + bj[0:2])]
            d2_het_copy_1 = d2_het.copy()
            d2_het_copy_1.indices = [d4.indices[i] for i in (ti[2:4] + bj[2:4])]
            decomp.append(term(coeff, [], [d2_het_copy_0, d2_het_copy_1]))
            d2_het_copy_0 = d2_het.copy()
            d2_het_copy_0.indices = [d4.indices[i] for i in (ti[0:2] + [bj[1], bj[0]])]
            d2_het_copy_1 = d2_het.copy()
            d2_het_copy_1.indices = [d4.indices[i] for i in (ti[2:4] + [bj[3], bj[2]])]
            decomp.append(term(coeff, [], [d2_het_copy_0, d2_het_copy_1]))
          else:
            raise RuntimeError, "Did not expect a term with %i permutations" %n_perm

          # Create 'un-permuted' 2RDM 1RDM 1RDM terms
          coeff = sign * (-1) * (0.5)**n_perm 
          d2_copy_0 = d2.copy()
          d2_copy_0.indices = [d4.indices[i] for i in (ti[0:2] + bj[0:2])]
          d1_copy_0 = d1.copy()
          d1_copy_0.indices = [d4.indices[i] for i in (ti[2:3] + bj[2:3])]
          d1_copy_1 = d1.copy()
          d1_copy_1.indices = [d4.indices[i] for i in (ti[3:4] + bj[3:4])]
          decomp.append(term(coeff, [], [d2_copy_0, d1_copy_0, d1_copy_1]))
          d2_copy_0 = d2.copy()
          d2_copy_0.indices = [d4.indices[i] for i in (ti[2:4] + bj[2:4])]
          d1_copy_0 = d1.copy()
          d1_copy_0.indices = [d4.indices[i] for i in (ti[0:1] + bj[0:1])]
          d1_copy_1 = d1.copy()
          d1_copy_1.indices = [d4.indices[i] for i in (ti[1:2] + bj[1:2])]
          decomp.append(term(coeff, [], [d2_copy_0, d1_copy_0, d1_copy_1]))

          # Create 'permuted' 2RDM 1RDM 1RDM terms
          if n_perm < 2:
            bj = bi[0:2] + bi[3:4] + bi[2:3]
            n_perm = get_num_perms(ti,bj)
            bj = [i+4 for i in bj]
            coeff = sign * (0.5)**n_perm
            d2_copy_0 = d2.copy()
            d2_copy_0.indices = [d4.indices[i] for i in (ti[0:2] + bj[0:2])]
            d1_copy_0 = d1.copy()
            d1_copy_0.indices = [d4.indices[i] for i in (ti[2:3] + bj[2:3])]
            d1_copy_1 = d1.copy()
            d1_copy_1.indices = [d4.indices[i] for i in (ti[3:4] + bj[3:4])]
            decomp.append(term(coeff, [], [d2_copy_0, d1_copy_0, d1_copy_1]))
            bj = bi[1:2] + bi[0:1] + bi[2:4]
            n_perm = get_num_perms(ti,bj)
            bj = [i+4 for i in bj]
            coeff = sign * (0.5)**n_perm
            d2_copy_0 = d2.copy()
            d2_copy_0.indices = [d4.indices[i] for i in (ti[2:4] + bj[2:4])]
            d1_copy_0 = d1.copy()
            d1_copy_0.indices = [d4.indices[i] for i in (ti[0:1] + bj[0:1])]
            d1_copy_1 = d1.copy()
            d1_copy_1.indices = [d4.indices[i] for i in (ti[1:2] + bj[1:2])]
            decomp.append(term(coeff, [], [d2_copy_0, d1_copy_0, d1_copy_1]))
          elif n_perm == 2:
            bj = bi[1:2] + bi[0:1] + bi[3:4] + bi[2:3]
            n_perm = get_num_perms(ti,bj)
            bj = [i+4 for i in bj]
            coeff = sign * (-1) * (0.5)**n_perm
            d2_copy_0 = d2.copy()
            d2_copy_0.indices = [d4.indices[i] for i in (ti[0:2] + bj[0:2])]
            d1_copy_0 = d1.copy()
            d1_copy_0.indices = [d4.indices[i] for i in (ti[2:3] + bj[2:3])]
            d1_copy_1 = d1.copy()
            d1_copy_1.indices = [d4.indices[i] for i in (ti[3:4] + bj[3:4])]
            decomp.append(term(coeff, [], [d2_copy_0, d1_copy_0, d1_copy_1]))
            d2_copy_0 = d2.copy()
            d2_copy_0.indices = [d4.indices[i] for i in (ti[2:4] + bj[2:4])]
            d1_copy_0 = d1.copy()
            d1_copy_0.indices = [d4.indices[i] for i in (ti[0:1] + bj[0:1])]
            d1_copy_1 = d1.copy()
            d1_copy_1.indices = [d4.indices[i] for i in (ti[1:2] + bj[1:2])]
            decomp.append(term(coeff, [], [d2_copy_0, d1_copy_0, d1_copy_1]))
          else:
            raise RuntimeError, "Did not expect a term with %i permutations" %n_perm

          # Create 1RDM 1RDM 1RDM 1RDM terms
          bj = [i for i in bi]
          n_perm = get_num_perms(ti,bj)
          bj = [i+4 for i in bj]
          coeff = sign * (0.5)**n_perm
          d1_copies = []
          for j in range(4):
            d1_copies.append(d1.copy())
            d1_copies[-1].indices = [d4.indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
          decomp.append(term(coeff, [], d1_copies))
          bj = bi[0:2] + bi[3:4] + bi[2:3]
          n_perm = get_num_perms(ti,bj)
          bj = [i+4 for i in bj]
          coeff = sign * (-1) * (0.5)**n_perm
          d1_copies = []
          for j in range(4):
            d1_copies.append(d1.copy())
            d1_copies[-1].indices = [d4.indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
          decomp.append(term(coeff, [], d1_copies))
          bj = bi[1:2] + bi[0:1] + bi[2:4]
          n_perm = get_num_perms(ti,bj)
          bj = [i+4 for i in bj]
          coeff = sign * (-1) * (0.5)**n_perm
          d1_copies = []
          for j in range(4):
            d1_copies.append(d1.copy())
            d1_copies[-1].indices = [d4.indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
          decomp.append(term(coeff, [], d1_copies))
          bj = bi[1:2] + bi[0:1] + bi[3:4] + bi[2:3]
          n_perm = get_num_perms(ti,bj)
          bj = [i+4 for i in bj]
          coeff = sign * (0.5)**n_perm
          d1_copies = []
          for j in range(4):
            d1_copies.append(d1.copy())
            d1_copies[-1].indices = [d4.indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
          decomp.append(term(coeff, [], d1_copies))

  # Compute terms from the sum involving one 2-body cumulant
  for p in range(4):
    for q in range(p+1,4):
      ti = [p,q]
      for i in range(4):
        if not (i in ti):
          ti.append(i)
      for r in range(4):
        for s in range(4):
          if r == s:
            continue
          bi = [r,s]
          temp = range(4)
          del temp[max(r,s)]
          del temp[min(r,s)]
          if temp[0] == ti[3] or temp[1] == ti[2]:
            temp = [temp[1], temp[0]]
          bi += temp

          # Determine the number of index permutations
          n_perm = get_num_perms(ti,bi)

          # Determine the sign
          sign = (-1)**n_perm

          # Compute 1RDM 1RDM 2RDM term
          coeff = 1
          bj = [i for i in bi]
          pairs_rdm1 = [ [ti[i],bj[i]] for i in range(2)]
          pairs_rdm2 = [ [bj[i],ti[i]] for i in range(2,4)]
          for pair in pairs_rdm2:
            if pair[0] == pair[1]:
              pairs_rdm2 = []
              break
          for pair in pairs_rdm2:
            if not (pair in pairs_rdm1):
              #print "swapping bj[2] and bj[3]"
              (bj[3],bj[2]) = (bj[2],bj[3])
              coeff *= -1
              break
          n_perm = get_num_perms(ti,bj)
          bj = [i+4 for i in bj]
          coeff *= sign * (0.5)**n_perm
          d1_copy_0 = d1.copy()
          d1_copy_0.indices = [d4.indices[i] for i in (ti[0:1] + bj[0:1])]
          d1_copy_1 = d1.copy()
          d1_copy_1.indices = [d4.indices[i] for i in (ti[1:2] + bj[1:2])]
          d2_copy_0 = d2.copy()
          d2_copy_0.indices = [d4.indices[i] for i in (ti[2:4] + bj[2:4])]
          decomp.append(term(coeff, [], [d1_copy_0, d1_copy_1, d2_copy_0]))

          # Compute 'un-permuted' 1RDM 1RDM 1RDM 1RDM term
          bj = [i for i in bi]
          n_perm = get_num_perms(ti,bj)
          bj = [i+4 for i in bj]
          coeff = sign * (-1) * (0.5)**n_perm
          d1_copies = []
          for j in range(4):
            d1_copies.append(d1.copy())
            d1_copies[-1].indices = [d4.indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
          decomp.append(term(coeff, [], d1_copies))

          # Compute 'permuted' 1RDM 1RDM 1RDM 1RDM term
          bj = bi[0:2] + bi[3:4] + bi[2:3]
          n_perm = get_num_perms(ti,bj)
          bj = [i+4 for i in bj]
          coeff = sign * (0.5)**n_perm
          d1_copies = []
          for j in range(4):
            d1_copies.append(d1.copy())
            d1_copies[-1].indices = [d4.indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
          decomp.append(term(coeff, [], d1_copies))

  # Compute terms from the sum involving only one particle density matrices
  ti = range(4)
  for bi in makePermutations(4):

    # Determine the number of permutations
    n_perm = get_num_perms(ti,bi)

    # Determine the sign
    sign = (-1)**n_perm

    # Compute 1RDM 1RDM 1RDM 1RDM term
    bj = [i+4 for i in bi]
    coeff = sign * (0.5)**n_perm
    d1_copies = []
    for j in range(4):
      d1_copies.append(d1.copy())
      d1_copies[-1].indices = [d4.indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
    decomp.append(term(coeff, [], d1_copies))

  # Combine like terms
  for t in decomp:
    t.tensors.sort()
  decomp.sort()
  i = 0
  while i < len(decomp)-1:
    if decomp[i].tensors == decomp[i+1].tensors:
      decomp[i+1].numConstant += decomp[i].numConstant
      del(decomp[i])
    else:
      i += 1
  termChop(decomp)

  # Return the decomposition
  return decomp
def decomp_3rdm_to_2rdm_sf(d3, d1, d2, indexOrder = '1212'):
  """
  Returns the list of terms composing the decomposition of the 3 body spin free rdm d3
  in terms of one and two body rdms"
  """

  if ( not isinstance(d1, tensor) ) or (len(d1.indices) != 2):
    raise TypeError, "d1 must be a tensor with 2 indices"
  if ( not isinstance(d2, tensor) ) or (len(d2.indices) != 4):
    raise TypeError, "d2 must be a tensor with 4 indices"
  if ( not isinstance(d3, tensor) ) or (len(d3.indices) != 6):
    raise TypeError, "d3 must be a tensor with 6 indices"

  # sort the 3-body RDM's indices into 1212 ordering
  d3_indices = []
  if   indexOrder == '1212':
    d3_indices.extend(d3.indices)
  elif indexOrder == '1122':
    for i in range(3):
      d3_indices.append(d3.indices[2*i])
    for i in range(3):
      d3_indices.append(d3.indices[2*i+1])
  else:
    raise ValueError, "unexpected indexOrder parameter:  %s" %(indexOrder)

  decomp = []

  # Prepare terms from    sum (-1)^p %gamma^{i_1}_{i_4} %lambda^{i_2`i_3}_{i_5`i_6}
  for p0 in range(3):
    temp = range(3)
    del temp[p0]
    p1 = temp[0]
    p2 = temp[1]
    ti = [p0, p1, p2]
    for q0 in range(3):
      temp = range(3)
      del temp[q0]
      q1 = temp[0]
      q2 = temp[1]
      if q1 == p2 or q2 == p1:
        bi = [q0, q2, q1]
      else:
        bi = [q0, q1, q2]

      # determine the sign of the term based on the number of index permutations
      n_perm = get_num_perms(ti,bi)
      if n_perm > 1:
        raise ValueError, "n_perm = %i which is > 1.  This was unexpected." %n_perm
      sign = (-1)**n_perm

      # create the 1RDM 2RDM term
      bj = [i for i in bi]
      n_perm = get_num_perms(ti,bj)
      coeff = sign * (0.5)**n_perm
      bj = [i+3 for i in bj]
      d1_copy_0 = d1.copy()
      d1_copy_0.indices = [d3_indices[i] for i in (ti[0:1] + bj[0:1])]
      d2_copy_0 = d2.copy()
      d2_copy_0.indices = [d3_indices[i] for i in (ti[1:3] + bj[1:3])]
      decomp.append(term(coeff, [], [d1_copy_0, d2_copy_0]))

      # create the 1RDM 1RDM 1RDM terms
      bj = [i for i in bi]
      n_perm = get_num_perms(ti,bj)
      coeff = sign * (-1) * (0.5)**n_perm
      bj = [i+3 for i in bj]
      d1_copies = []
      for j in range(3):
        d1_copies.append(d1.copy())
        d1_copies[-1].indices = [d3_indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
      decomp.append(term(coeff, [], d1_copies))
      bj = bi[0:1] + bi[2:3] + bi[1:2]
      n_perm = get_num_perms(ti,bj)
      coeff = sign * (0.5)**n_perm
      bj = [i+3 for i in bj]
      d1_copies = []
      for j in range(3):
        d1_copies.append(d1.copy())
        d1_copies[-1].indices = [d3_indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
      decomp.append(term(coeff, [], d1_copies))

  # Compute terms from the sum involving only one particle density matrices
  ti = range(3)
  for bi in makePermutations(3):
    
    # Determine the number of permutations
    n_perm = get_num_perms(ti,bi)

    # Determine the sign
    sign = (-1)**n_perm

    # Compute 1RDM 1RDM 1RDM 1RDM term
    bj = [i+3 for i in bi]
    coeff = sign * (0.5)**n_perm
    d1_copies = []
    for j in range(3):
      d1_copies.append(d1.copy())
      d1_copies[-1].indices = [d3_indices[i] for i in (ti[j:j+1] + bj[j:j+1])]
    decomp.append(term(coeff, [], d1_copies))

  # Combine like terms
  for t in decomp:
    t.tensors.sort()
  decomp.sort()
  i = 0
  while i < len(decomp)-1:
    if decomp[i].tensors == decomp[i+1].tensors:
      decomp[i+1].numConstant += decomp[i].numConstant
      del(decomp[i])
    else:
      i += 1
  termChop(decomp)

  # if original ordering was 1122, convert back to it
  if indexOrder == '1122':
    for t in decomp:
      for ten in t.tensors:
        if ten.name == d2.name:
          ten.indices = [ten.indices[0], ten.indices[2], ten.indices[1], ten.indices[3]]

  # Return the decomposition
  return decomp
def contractCoreOps_sf(inTerm):
  "Returns a list of terms resulting from normal ordering the operators in inTerm."

  # check that inTerm is a term
  if not isinstance(inTerm, term):
    raise TypeError, "inTerm must be of class term"

  # Make separate lists of the spin free excitation operators and other tensors
  sfExOp_list = []
  other_list = []
  for t in inTerm.tensors:
    if isinstance(t, sfExOp):
      sfExOp_list.append(t.copy())
    else:
      other_list.append(t.copy())

  # Initialize n, the number of remaining spin free excitation operators
  if len(sfExOp_list) != 1:
    raise RuntimeError, "terms should have single sfExOp"

  # Give short names for the excitation operator and their order
  e1 = sfExOp_list[0]
  o1 = e1.order

  # list of offset of "core" type indices
  cIndsCre = []
  cIndsDes = []
  for i in range(o1):
    if e1.indices[i].indType == (options.core_type,):
      cIndsCre.append(i)
    if e1.indices[i+o1].indType == (options.core_type,):
      cIndsDes.append(i+o1)

  # all the "core" type indices should be contracted
  nc = len(cIndsCre)
  outTerms = []
  if nc != len(cIndsDes):
    outTerms.append(term(0.0, [], []))
    return outTerms
  if nc == 0:
    outTerms = [inTerm.copy()]
    return outTerms
  newOrder = o1 - nc

  # For each contraction, compute the resulting term
  perms = makePermutations(nc)
  for perm in perms:

    # Compute the pairs of indices to be contracted
    # Example: (conPairs[0][p], conPairs[1][p]) is the pth contraction pair.
    conPairs = [[],[]]
    for i in range(nc):
      conPairs[0].append(cIndsCre[perm[i]])
      conPairs[1].append(cIndsDes[i])

    # evaluate constant prefactor
    prefactor = 1
    inds = []
    for i in range(nc):
      if i not in inds:
        inds.append(i)
        i0 = conPairs[0][i]
        i1 = conPairs[1][i] - o1
        if i1 == i0:
          prefactor *= 2
          continue
#old        if i1 in conPairs[0]:
#old          inds.append(conPairs[0].index(i1))
#old          i2 = conPairs[1][conPairs[0].index(i1)] - o1
#old          if i2 == i0:
#old            prefactor *= 2
#old            continue
#old          if i2 in conPairs[0]:
#old            inds.append(conPairs[0].index(i2))
#old            i3 = conPairs[1][conPairs[0].index(i2)] - o1
#old            if i3 == i0:
#old              prefactor *= 2
#old              continue
#old            if i3 in conPairs[0]:
#old              inds.append(conPairs[0].index(i3))
#old              i4 = conPairs[1][conPairs[0].index(i3)] - o1
#old              if i4 == i0:
#old                prefactor *= 2
#old                continue
#old              else:
#old                raise RuntimeError, "NYI:recursive algorithm1"
        while i1 in conPairs[0]:
          inds.append(conPairs[0].index(i1))
          i1 = conPairs[1][conPairs[0].index(i1)] - o1
          if i1 == i0:
            prefactor *= 2
            break
#
        
    # Initialize the term's tensor list
    tensorList = other_list[:]

    # Initialize the index list for the new excitation operator
    indexList = [False] * (2*newOrder)

    # Populate the index list for the new excitation operator
    # Also, create a kronecker delta function for each contraction pair
    count = 0
    for i1 in range(o1):
      if i1 in conPairs[0]:#create a kronecker delta
        i2 = conPairs[1][conPairs[0].index(i1)]
        ind1 = e1.indices[i1]
        ind2 = e1.indices[i2]
        tensorList.insert(0, kroneckerDelta([ind1,ind2]))
      else:               #insert a pair of indices
        indexList[count] = e1.indices[i1]
        i2 = i1+o1
#old        if i2 in conPairs[1]:
#old          i2 = conPairs[0][conPairs[1].index(i2)] + o1
#old          if i2 in conPairs[1]:
#old            i2 = conPairs[0][conPairs[1].index(i2)] + o1
#old            if i2 in conPairs[1]:
#old              i2 = conPairs[0][conPairs[1].index(i2)] + o1
#old              if i2 in conPairs[1]:
#old                i2 = conPairs[0][conPairs[1].index(i2)] + o1
#old                if i2 in conPairs[1]:
#old                  raise RuntimeError, "NYI:recursive algorithm2"
#old              else:
#old                indexList[count+newOrder] = e1.indices[i2]
#old            else:
#old              indexList[count+newOrder] = e1.indices[i2]
#old          else:
#old            indexList[count+newOrder] = e1.indices[i2]
#old        else:
#old          indexList[count+newOrder] = e1.indices[i2]
        while i2 in conPairs[1]:
          i2 = conPairs[0][conPairs[1].index(i2)] + o1
        else: indexList[count+newOrder] = e1.indices[i2]
#
        count += 1

    # Ensure that all slots in the index list have been filled
    for ind in indexList:
      if ind is False:
        raise RuntimeError, "There is at least one unassigned index in the new spin free operator."

    # Add the new excitation operator to the tensor list
    if len(indexList) != 0:
      tensorList.append(sfExOp(indexList))

    # Determine the sign
    indPairs = [[],[]]
    for i in range(o1):
      indPairs[0].append(i)
      if i in conPairs[0]:
        indPairs[1].append(conPairs[1][conPairs[0].index(i)]-o1)
      else:
        a  = e1.indices[i]
        b = indexList[indexList.index(a)+newOrder]
        #indPairs[1].append(e1.indices.index(b)-o1)#is not correct when there are duplicate indices
        indPairs[1].append(e1.indices[o1:2*o1].index(b))

    #for i in range(o1): indPairs[1].index(i)
    n_perm = get_num_perms(indPairs[0], indPairs[1])
    sign = (-1)**n_perm

    # Add the resulting term to this iteration's list of output terms
    #print term(sign*prefactor*inTerm.numConstant, inTerm.constants, tensorList)
    #print "-------------------------------------------------------------------"
    outTerms.append(term(sign*prefactor*inTerm.numConstant, inTerm.constants, tensorList))

  return outTerms
def decomp_3op_to_2op_2rdm_sf(op, d1, d2, indexOrder = '1212'):
  """
  Returns the list of terms composing the decomposition of the 3 body sfExOp op
  in terms of one and two body operators"
  """

  # Check input
  if ( not isinstance(op, sfExOp) ) or (op.order != 3):
    raise TypeError, "op must be a 3-particle sfExOp"
  if ( not isinstance(d1, tensor) ) or (len(d1.indices) != 2):
    raise TypeError, "d1 must be a tensor with 2 indices"
  if ( not isinstance(d2, tensor) ) or (len(d2.indices) != 4):
    raise TypeError, "d2 must be a tensor with 4 indices"

  if indexOrder != '1212' and indexOrder != '1122':
    raise ValueError, "indexOrder must be '1212' or '1122'"

  # Initialize the return value
  decomp = []

  # Loop over the nine index permutations used in the decomposition
  for p0 in range(3):

    # Compute the top indices
    temp = range(3)
    del temp[p0]
    p1 = temp[0]
    p2 = temp[1]
    ti = [p0, p1, p2]

    for q0 in range(3):

      # Compute the bottom indices, remembering that the second and third index pairs
      # should retain original pairing as much as possible.
      temp = range(3)
      del temp[q0]
      q1 = temp[0]
      q2 = temp[1]
      if q1 == p2 or q2 == p1:
        bi = [q0, q2, q1]
      else:
        bi = [q0, q1, q2]

      # Determine the number of permutations from original pairing
      n_perms = get_num_perms(ti,bi)

      # Shift bi to match the bottom indices of op
      bi = [i+op.order for i in bi]

      # Create 1RDM 2op term
      d1_copy = d1.copy()
      d1_copy.indices = [op.indices[ti[0]], op.indices[bi[0]]]
      indexList = [op.indices[i] for i in ti[1:]] + [op.indices[i] for i in bi[1:]]
      decomp.append( term( (-0.5)**n_perms, [], [d1_copy, sfExOp(indexList)] ) )

      # Create 2RDM 1op term
      d2_copy = d2.copy()
      d2_copy.indices = [op.indices[i] for i in ti[1:]] + [op.indices[i] for i in bi[1:]]
      if indexOrder == '1122':
        d2_copy.indices = [d2_copy.indices[0], d2_copy.indices[2], d2_copy.indices[1], d2_copy.indices[3]]
      indexList = [op.indices[ti[0]], op.indices[bi[0]]]
      decomp.append( term( (-0.5)**n_perms, [], [d2_copy, sfExOp(indexList)] ) )

      # Create 1RDM 1RDM 1op terms
      d1_copy = d1.copy()
      d1_copy.indices = [op.indices[ti[1]], op.indices[bi[1]]]
      d1_copy_2 = d1.copy()
      d1_copy_2.indices = [op.indices[ti[2]], op.indices[bi[2]]]
      indexList = [op.indices[ti[0]], op.indices[bi[0]]]
      decomp.append( term( -2 * (-0.5)**n_perms, [], [d1_copy, d1_copy_2, sfExOp(indexList)] ) )
      d1_copy = d1.copy()
      d1_copy.indices = [op.indices[ti[2]], op.indices[bi[1]]]
      d1_copy_2 = d1.copy()
      d1_copy_2.indices = [op.indices[ti[1]], op.indices[bi[2]]]
      indexList = [op.indices[ti[0]], op.indices[bi[0]]]
      decomp.append( term( (-0.5)**n_perms, [], [d1_copy, d1_copy_2, sfExOp(indexList)] ) )

      # Create 1RDM 2RDM term
      d1_copy = d1.copy()
      d1_copy.indices = [op.indices[ti[0]], op.indices[bi[0]]]
      d2_copy = d2.copy()
      d2_copy.indices = [op.indices[i] for i in ti[1:]] + [op.indices[i] for i in bi[1:]]
      if indexOrder == '1122':
        d2_copy.indices = [d2_copy.indices[0], d2_copy.indices[2], d2_copy.indices[1], d2_copy.indices[3]]
      decomp.append( term( - (-0.5)**n_perms, [], [d1_copy, d2_copy] ) )

      # Create 1RDM 1RDM 1RDM terms
      d1_copy = d1.copy()
      d1_copy.indices = [op.indices[ti[0]], op.indices[bi[0]]]
      d1_copy_2 = d1.copy()
      d1_copy_2.indices = [op.indices[ti[1]], op.indices[bi[1]]]
      d1_copy_3 = d1.copy()
      d1_copy_3.indices = [op.indices[ti[2]], op.indices[bi[2]]]
      decomp.append( term( (4.0 / 3.0) * (-0.5)**n_perms, [], [d1_copy, d1_copy_2, d1_copy_3] ) )
      d1_copy = d1.copy()
      d1_copy.indices = [op.indices[ti[0]], op.indices[bi[0]]]
      d1_copy_2 = d1.copy()
      d1_copy_2.indices = [op.indices[ti[2]], op.indices[bi[1]]]
      d1_copy_3 = d1.copy()
      d1_copy_3.indices = [op.indices[ti[1]], op.indices[bi[2]]]
      decomp.append( term( (-2.0 / 3.0) * (-0.5)**n_perms, [], [d1_copy, d1_copy_2, d1_copy_3] ) )

  # Return the decomposition
  return decomp
def contractCoreOps_sf(inTerm):
    "Returns a list of terms resulting from normal ordering the operators in inTerm."

    # check that inTerm is a term
    if not isinstance(inTerm, term):
        raise TypeError, "inTerm must be of class term"

    # Make separate lists of the spin free excitation operators and other tensors
    sfExOp_list = []
    other_list = []
    for t in inTerm.tensors:
        if isinstance(t, sfExOp):
            sfExOp_list.append(t.copy())
        else:
            other_list.append(t.copy())

    # Initialize n, the number of remaining spin free excitation operators
    if len(sfExOp_list) != 1:
        raise RuntimeError, "terms should have single sfExOp"

    # Give short names for the excitation operator and their order
    e1 = sfExOp_list[0]
    o1 = e1.order

    # list of offset of "core" type indices
    cIndsCre = []
    cIndsDes = []
    for i in range(o1):
        if e1.indices[i].indType == (options.core_type, ):
            cIndsCre.append(i)
        if e1.indices[i + o1].indType == (options.core_type, ):
            cIndsDes.append(i + o1)

    # all the "core" type indices should be contracted
    nc = len(cIndsCre)
    outTerms = []
    if nc != len(cIndsDes):
        outTerms.append(term(0.0, [], []))
        return outTerms
    if nc == 0:
        outTerms = [inTerm.copy()]
        return outTerms
    newOrder = o1 - nc

    # For each contraction, compute the resulting term
    perms = makePermutations(nc)
    for perm in perms:

        # Compute the pairs of indices to be contracted
        # Example: (conPairs[0][p], conPairs[1][p]) is the pth contraction pair.
        conPairs = [[], []]
        for i in range(nc):
            conPairs[0].append(cIndsCre[perm[i]])
            conPairs[1].append(cIndsDes[i])

        # evaluate constant prefactor
        prefactor = 1
        inds = []
        for i in range(nc):
            if i not in inds:
                inds.append(i)
                i0 = conPairs[0][i]
                i1 = conPairs[1][i] - o1
                if i1 == i0:
                    prefactor *= 2
                    continue
#old        if i1 in conPairs[0]:
#old          inds.append(conPairs[0].index(i1))
#old          i2 = conPairs[1][conPairs[0].index(i1)] - o1
#old          if i2 == i0:
#old            prefactor *= 2
#old            continue
#old          if i2 in conPairs[0]:
#old            inds.append(conPairs[0].index(i2))
#old            i3 = conPairs[1][conPairs[0].index(i2)] - o1
#old            if i3 == i0:
#old              prefactor *= 2
#old              continue
#old            if i3 in conPairs[0]:
#old              inds.append(conPairs[0].index(i3))
#old              i4 = conPairs[1][conPairs[0].index(i3)] - o1
#old              if i4 == i0:
#old                prefactor *= 2
#old                continue
#old              else:
#old                raise RuntimeError, "NYI:recursive algorithm1"
                while i1 in conPairs[0]:
                    inds.append(conPairs[0].index(i1))
                    i1 = conPairs[1][conPairs[0].index(i1)] - o1
                    if i1 == i0:
                        prefactor *= 2
                        break
#

# Initialize the term's tensor list
        tensorList = other_list[:]

        # Initialize the index list for the new excitation operator
        indexList = [False] * (2 * newOrder)

        # Populate the index list for the new excitation operator
        # Also, create a kronecker delta function for each contraction pair
        count = 0
        for i1 in range(o1):
            if i1 in conPairs[0]:  #create a kronecker delta
                i2 = conPairs[1][conPairs[0].index(i1)]
                ind1 = e1.indices[i1]
                ind2 = e1.indices[i2]
                tensorList.insert(0, kroneckerDelta([ind1, ind2]))
            else:  #insert a pair of indices
                indexList[count] = e1.indices[i1]
                i2 = i1 + o1
                #old        if i2 in conPairs[1]:
                #old          i2 = conPairs[0][conPairs[1].index(i2)] + o1
                #old          if i2 in conPairs[1]:
                #old            i2 = conPairs[0][conPairs[1].index(i2)] + o1
                #old            if i2 in conPairs[1]:
                #old              i2 = conPairs[0][conPairs[1].index(i2)] + o1
                #old              if i2 in conPairs[1]:
                #old                i2 = conPairs[0][conPairs[1].index(i2)] + o1
                #old                if i2 in conPairs[1]:
                #old                  raise RuntimeError, "NYI:recursive algorithm2"
                #old              else:
                #old                indexList[count+newOrder] = e1.indices[i2]
                #old            else:
                #old              indexList[count+newOrder] = e1.indices[i2]
                #old          else:
                #old            indexList[count+newOrder] = e1.indices[i2]
                #old        else:
                #old          indexList[count+newOrder] = e1.indices[i2]
                while i2 in conPairs[1]:
                    i2 = conPairs[0][conPairs[1].index(i2)] + o1
                else:
                    indexList[count + newOrder] = e1.indices[i2]
                #
                count += 1

        # Ensure that all slots in the index list have been filled
        for ind in indexList:
            if ind is False:
                raise RuntimeError, "There is at least one unassigned index in the new spin free operator."

        # Add the new excitation operator to the tensor list
        if len(indexList) != 0:
            tensorList.append(sfExOp(indexList))

        # Determine the sign
        indPairs = [[], []]
        for i in range(o1):
            indPairs[0].append(i)
            if i in conPairs[0]:
                indPairs[1].append(conPairs[1][conPairs[0].index(i)] - o1)
            else:
                a = e1.indices[i]
                b = indexList[indexList.index(a) + newOrder]
                #indPairs[1].append(e1.indices.index(b)-o1)#is not correct when there are duplicate indices
                indPairs[1].append(e1.indices[o1:2 * o1].index(b))

        #for i in range(o1): indPairs[1].index(i)
        n_perm = get_num_perms(indPairs[0], indPairs[1])
        sign = (-1)**n_perm

        # Add the resulting term to this iteration's list of output terms
        #print term(sign*prefactor*inTerm.numConstant, inTerm.constants, tensorList)
        #print "-------------------------------------------------------------------"
        outTerms.append(
            term(sign * prefactor * inTerm.numConstant, inTerm.constants,
                 tensorList))

    return outTerms