static public TensorType concat(TensorType lhs, TensorType rhs, String concatDimension) {
        Value cellType = Value.DOUBLE;
        if (lhs.rank() > 0 && rhs.rank() > 0) {
            if (lhs.valueType() == rhs.valueType()) {
                cellType = lhs.valueType();
            } else {
                cellType = Value.largestOf(lhs.valueType(), rhs.valueType());
                // when changing cell type, make it at least float
                cellType = Value.largestOf(cellType, Value.FLOAT);
            }
        } else if (lhs.rank() > 0) {
            cellType = lhs.valueType();
        } else if (rhs.rank() > 0) {
            cellType = rhs.valueType();
        }
        Dimension first = Dimension.indexed(concatDimension, 1);
        Dimension second = Dimension.indexed(concatDimension, 1);
        Map<String, Dimension> map = new HashMap<>();
        for (Dimension dim : lhs.dimensions()) {
            if (dim.name().equals(concatDimension)) {
                first = dim;
            } else {
                map.put(dim.name(), dim);
            }
        }
        for (Dimension dim : rhs.dimensions()) {
            if (dim.name().equals(concatDimension)) {
                second = dim;
            } else if (map.containsKey(dim.name())) {
                Dimension other = map.get(dim.name());
                if (! other.equals(dim)) {
                    if (firstIsBoundSecond(dim, other)) {
                        map.put(dim.name(), other);  // [N] and [] -> []
                    } else if (firstIsBoundSecond(other, dim)) {
                        map.put(dim.name(), dim);  // [N] and [] -> []
                    } else if (firstIsSmaller(dim, other)) {
                        map.put(dim.name(), dim); // [N] and [M] -> [ min(N,M] ].
                    } else if (firstIsSmaller(other, dim)) {
                        map.put(dim.name(), other); // [N] and [M] -> [ min(N,M] ].
                    } else {
                        throw new IllegalArgumentException("Unequal dimension " + dim.name() + " in " + lhs+ " and "+rhs);
                    }
                }
            } else {
                map.put(dim.name(), dim);
            }
        }
        if (first.type() == Dimension.Type.mapped) {
            throw new IllegalArgumentException("Bad concat dimension "+concatDimension+" in lhs: "+lhs);
        }
        if (second.type() == Dimension.Type.mapped) {
            throw new IllegalArgumentException("Bad concat dimension "+concatDimension+" in rhs: "+rhs);
        }
        if (first.type() == Dimension.Type.indexedUnbound) {
            map.put(concatDimension, first);
        } else if (second.type() == Dimension.Type.indexedUnbound) {
            map.put(concatDimension, second);
        } else {
            long concatSize = first.size().get() + second.size().get();
            map.put(concatDimension, Dimension.indexed(concatDimension, concatSize));
        }
        return new TensorType(cellType, map.values());
    }
Common type resolving for basic tensor operations.

@author arnej
vespajlib/src/main/java/com/yahoo/tensor/TypeResolver.java