docs: public static void innerProductLower(DMatrixSparseCSC A , DMatrixSparseCSC B ,
                                           @Nullable IGrowArray gw, @Nullable DGrowArray gx )
      {
          double []x = adjust(gx, A.numRows);
          int []w = adjust(gw, A.numRows, A.numRows);
  
          // no idea what a reasonable initial length for nz_length is. This was picked arbitrarily
          B.reshape(A.numCols,A.numCols,A.nz_length/2);
          B.indicesSorted = true;
  
          for (int colI = 0; colI < A.numCols; colI++) {
              int idx0 = A.col_idx[colI];
              int idx1 = A.col_idx[colI+1];
  
              int mark = colI + 1;
  
              // Sparse copy into dense vector
              for (int i = idx0; i < idx1; i++) {
                  int row = A.nz_rows[i];
                  w[row] = mark;
                  x[row] = A.nz_values[i];
              }
  
              // Compute dot product along each column
              for (int colJ = colI; colJ < A.numCols; colJ++) {
                  double sum = 0;
                  idx0 = A.col_idx[colJ];
                  idx1 = A.col_idx[colJ+1];
  
                  for (int i = idx0; i < idx1; i++) {
                      int row = A.nz_rows[i];
                      if( w[row] == mark ) {
                          sum += x[row]*A.nz_values[i];
                      }
                  }
  
                  if( sum == 0 )
                      continue;
  
                  if( B.nz_length == B.nz_values.length ) {
                      B.growMaxLength(B.nz_length*2+1,true);
                  }
                  B.nz_values[B.nz_length] = sum;
                  B.nz_rows[B.nz_length++] = colJ;
              }
              B.col_idx[colI+1] = B.nz_length;
          }
      }