def report_function(e: float, params: np.array, X: spa.csr_matrix,
                    Z: spa.csr_matrix, X_val: spa.csr_matrix,
                    Z_val: spa.csr_matrix, penalty: float):
    '''Reports loss and accuracy given parameters, data and labels'''
    D = X.shape[1]
    K = Z.shape[1]
    b = params[:K]
    W = params[K:].reshape((K, D))
    loss = val_func(params, X, Z, penalty)
    class_probs = logisticClassProbability(X, b, W)
    Y_pred = class_probs.argmax(axis=1)
    Y = Z.argmax(axis=1)
    train_accuracy = np.mean(Y_pred == Y)
    print("\t", e, "Loss =", loss, "Train_Accuracy", train_accuracy, end=" ")
    if not (X_val is None):  # if we have a valuation set we can report
        # how well we are doing out of sample
        val_e = val_func(params, X_val, Z_val, penalty)
        class_probs = logisticClassProbability(X_val, b, W)
        Y_pred = class_probs.argmax(axis=1)
        Y_val = Z_val.argmax(axis=1)
        val_accuracy = np.mean(Y_pred == Y_val)
        print("Evaluation Loss =", val_e, "Accuracy =", val_accuracy)
    else:
        print()