def evaluate_deltas(e):
 accepted_functions = (Add,)
 if isinstance(e, accepted_functions):
  return e.func(*[evaluate_deltas(arg) for arg in e.args])
 elif isinstance(e, Mul):
  deltas = []
  indices = {}
  for i in e.args:
   for s in i.free_symbols:
    if (s in indices):
     indices[s] += 1
    else:
     indices[s] = 0
   if isinstance(i, KroneckerDelta):
    deltas.append(i)
  for d in deltas:
   if indices[d.killable_index]:
    e = e.subs(d.killable_index, d.preferred_index)
    if (len(deltas) > 1):
     return evaluate_deltas(e)
   elif (indices[d.preferred_index] and d.indices_contain_equal_information):
    e = e.subs(d.preferred_index, d.killable_index)
    if (len(deltas) > 1):
     return evaluate_deltas(e)
   else:
    pass
  return e
 else:
  return e

def evaluate_deltas ( e ) : accepted_functions = ( Add , ) if isinstance ( e , accepted_functions ) : return e . func ( * [ evaluate_deltas ( arg ) for arg in e . args ] ) elif isinstance ( e , Mul ) : deltas = [ ] indices = { } for i in e . args : for s in i . free_symbols : if ( s in indices ) : indices [ s ] += 1 else : indices [ s ] = 0 if isinstance ( i , KroneckerDelta ) : deltas . append ( i ) for d in deltas : if indices [ d . killable_index ] : e = e . subs ( d . killable_index , d . preferred_index ) if ( len ( deltas ) > 1 ) : return evaluate_deltas ( e ) elif ( indices [ d . preferred_index ] and d . indices_contain_equal_information ) : e = e . subs ( d . preferred_index , d . killable_index ) if ( len ( deltas ) > 1 ) : return evaluate_deltas ( e ) else : pass return e else : return e
we evaluate kroneckerdelta symbols in the expression assuming einstein summation .