+ return is_dummy_pair(ex_to<idx>(e1), ex_to<idx>(e2));
+}
+
+void find_free_and_dummy(exvector::const_iterator it, exvector::const_iterator itend, exvector & out_free, exvector & out_dummy)
+{
+ out_free.clear();
+ out_dummy.clear();
+
+ // No indices? Then do nothing
+ if (it == itend)
+ return;
+
+ // Only one index? Then it is a free one if it's not numeric
+ if (itend - it == 1) {
+ if (ex_to<idx>(*it).is_symbolic())
+ out_free.push_back(*it);
+ return;
+ }
+
+ // Sort index vector. This will cause dummy indices come to lie next
+ // to each other (because the sort order is defined to guarantee this).
+ exvector v(it, itend);
+ shaker_sort(v.begin(), v.end(), ex_is_less(), ex_swap());
+
+ // Find dummy pairs and free indices
+ it = v.begin(); itend = v.end();
+ auto last = it++;
+ while (it != itend) {
+ if (is_dummy_pair(*it, *last)) {
+ out_dummy.push_back(*last);
+ it++;
+ if (it == itend)
+ return;
+ } else {
+ if (!it->is_equal(*last) && ex_to<idx>(*last).is_symbolic())
+ out_free.push_back(*last);
+ }
+ last = it++;
+ }
+ if (ex_to<idx>(*last).is_symbolic())
+ out_free.push_back(*last);
+}
+
+ex minimal_dim(const ex & dim1, const ex & dim2)
+{
+ if (dim1.is_equal(dim2) || dim1 < dim2 || (is_exactly_a<numeric>(dim1) && !is_a<numeric>(dim2)))
+ return dim1;
+ else if (dim1 > dim2 || (!is_a<numeric>(dim1) && is_exactly_a<numeric>(dim2)))
+ return dim2;
+ else {
+ std::ostringstream s;
+ s << "minimal_dim(): index dimensions " << dim1 << " and " << dim2 << " cannot be ordered";
+ throw (std::runtime_error(s.str()));
+ }