'The algorithms performing the multiplication of two ``TIDS`` instances.
In short, it forms a new ``TIDS`` object, joining components and indices,
checking that abstract indices are compatible, and possibly contracting
them.'

@staticmethod
def mul(f, g):

    index_up = (lambda u: (u if u.is_up else (- u)))
    f_free = f.free[:]
    g_free = g.free[:]
    nc1 = len(f.components)
    dum = TIDS._check_matrix_indices(f_free, g_free, nc1)
    free_dict1 = {(i if i.is_up else (- i)): (pos, cpos, i) for (i, pos, cpos) in f_free}
    free_dict2 = {(i if i.is_up else (- i)): (pos, cpos, i) for (i, pos, cpos) in g_free}
    free_names = (set(free_dict1.keys()) & set(free_dict2.keys()))
    dum2 = [(i1, i2, (c1 + nc1), (c2 + nc1)) for (i1, i2, c1, c2) in g.dum]
    free1 = [(ind, i, c) for (ind, i, c) in f_free if (index_up(ind) not in free_names)]
    free2 = [(ind, i, (c + nc1)) for (ind, i, c) in g_free if (index_up(ind) not in free_names)]
    free = (free1 + free2)
    dum.extend((f.dum + dum2))
    for name in free_names:
        (ipos1, cpos1, ind1) = free_dict1[name]
        (ipos2, cpos2, ind2) = free_dict2[name]
        cpos2 += nc1
        if (ind1._is_up == ind2._is_up):
            raise ValueError('wrong    index    construction    {0}'.format(ind1))
        if ind1._is_up:
            new_dummy = (ipos1, ipos2, cpos1, cpos2)
        else:
            new_dummy = (ipos2, ipos1, cpos2, cpos1)
        dum.append(new_dummy)
    return ((f.components + g.components), free, dum)
