//  [python]
def whitney_innerproduct(complex,k):
    """
    For a given SimplicialComplex, compute a matrix representing the 
    innerproduct of Whitney k-forms
    """
    assert(k >= 0 and k <= complex.complex_dimension())    
        
    ## MASS MATRIX COO DATA
    rows,cols = massmatrix_rowcols(complex,k)
    data = empty(rows.shape)


    ## PRECOMPUTATION
    p = complex.complex_dimension()
   
    scale_integration = (scipy.special.factorial(k)**2)/((p + 2)*(p + 1))   
    
    k_forms = [tuple(x) for x in combinations(range(p+1),k)]
    k_faces = [tuple(x) for x in combinations(range(p+1),k+1)]

    num_k_forms = len(k_forms)
    num_k_faces = len(k_faces)

    k_form_pairs = [tuple(x) for x in combinations(k_forms,2)] + [(x,x) for x in k_forms]
    num_k_form_pairs = len(k_form_pairs)
    k_form_pairs_to_index = dict(zip(k_form_pairs,range(num_k_form_pairs)))
    k_form_pairs_to_index.update(zip([x[::-1] for x in k_form_pairs],range(num_k_form_pairs)))
    num_k_face_pairs = num_k_faces**2


    if k > 0:
        k_form_pairs_array = array(k_form_pairs)

    #maps flat vector of determinants to the flattened matrix entries
    dets_to_vals = scipy.sparse.lil_matrix((num_k_face_pairs,num_k_form_pairs))

    k_face_pairs = []
    for face1 in k_faces:
        for face2 in k_faces:
            row_index = len(k_face_pairs)

            k_face_pairs.append((face1,face2))

            for n in range(k+1):
               for m in range(k+1):
                   form1 = face1[:n] + face1[n+1:]
                   form2 = face2[:m] + face2[m+1:]

                   col_index = k_form_pairs_to_index[(form1,form2)]

                   dets_to_vals[row_index,col_index] += (-1)**(n+m)*((face1[n] == face2[m]) + 1)                 

    k_face_pairs_to_index = dict(zip(k_face_pairs,range(num_k_faces**2)))
    dets_to_vals = dets_to_vals.tocsr()
    ## END PRECOMPUTATION


    ## COMPUTATION
    if k == 1:
        Fdet = lambda x : x #det for 1x1 matrices - extend
    else:
        Fdet, = scipy.linalg.flinalg.get_flinalg_funcs(('det',),(complex.vertices,))



    dets = ones(num_k_form_pairs)


    for i,s in enumerate(complex[-1].simplices):

        # for k=1, dets is already correct i.e. dets[:] = 1
        if k > 0:  
            # lambda_i denotes the scalar barycentric basis function of the i-th vertex of this simplex
            # d(lambda_i) is the 1 form (gradient) of the i-th scalar basis function within this simplex
            pts      = complex.vertices[s,:]
            d_lambda = barycentric_gradients(pts)
            
            mtxs = d_lambda[k_form_pairs_array]     # these lines are equivalent to:
            for n,(A,B) in enumerate(mtxs):         #   for n,(form1,form2) in enumerate(k_form_pairs):
                dets[n] = Fdet(inner(A,B))[0]       #       dets[n] = det(dot(d_lambda[form1,:],d_lambda[form2,:].T))

        volume = complex[-1].primal_volume[i]
        vals = dets_to_vals * dets
        vals *= volume * scale_integration  #scale by the volume, barycentric weights, and account for the p! in each whitney form

        #put values into appropriate entries of the COO data array
        data[i*num_k_face_pairs:(i+1)*num_k_face_pairs] = vals


    #now rows,cols,data form a COOrdinate representation for the mass matrix
    shape = (complex[k].num_simplices,complex[k].num_simplices)  
    return coo_matrix((data,(rows,cols)), shape).tocsr()