summaryrefslogtreecommitdiffstats
path: root/eval
diff options
context:
space:
mode:
authorHåvard Pettersen <havardpe@oath.com>2020-09-10 10:34:22 +0000
committerHåvard Pettersen <havardpe@oath.com>2020-09-15 10:47:00 +0000
commita5e7fb555fc75678ba9a802f01549635895630a4 (patch)
treed69a67076e877e933ab96bd698aaf0289697037a /eval
parent0aa542afda6ad9a07c1db760b9a9e3ca3f1b5f86 (diff)
start work on value api
Diffstat (limited to 'eval')
-rw-r--r--eval/CMakeLists.txt1
-rw-r--r--eval/src/tests/eval/simple_value/CMakeLists.txt9
-rw-r--r--eval/src/tests/eval/simple_value/simple_value_test.cpp65
-rw-r--r--eval/src/tests/eval/value_type/value_type_test.cpp8
-rw-r--r--eval/src/vespa/eval/eval/CMakeLists.txt1
-rw-r--r--eval/src/vespa/eval/eval/simple_value.cpp261
-rw-r--r--eval/src/vespa/eval/eval/simple_value.h215
-rw-r--r--eval/src/vespa/eval/eval/value_type.cpp12
-rw-r--r--eval/src/vespa/eval/eval/value_type.h1
9 files changed, 573 insertions, 0 deletions
diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt
index fe9d9985c6a..76c1a63b881 100644
--- a/eval/CMakeLists.txt
+++ b/eval/CMakeLists.txt
@@ -24,6 +24,7 @@ vespa_define_module(
src/tests/eval/node_types
src/tests/eval/param_usage
src/tests/eval/simple_tensor
+ src/tests/eval/simple_value
src/tests/eval/tensor_function
src/tests/eval/tensor_lambda
src/tests/eval/tensor_spec
diff --git a/eval/src/tests/eval/simple_value/CMakeLists.txt b/eval/src/tests/eval/simple_value/CMakeLists.txt
new file mode 100644
index 00000000000..429d3ffaf3d
--- /dev/null
+++ b/eval/src/tests/eval/simple_value/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(eval_simple_value_test_app TEST
+ SOURCES
+ simple_value_test.cpp
+ DEPENDS
+ vespaeval
+ GTest::GTest
+)
+vespa_add_test(NAME eval_simple_value_test_app COMMAND eval_simple_value_test_app)
diff --git a/eval/src/tests/eval/simple_value/simple_value_test.cpp b/eval/src/tests/eval/simple_value/simple_value_test.cpp
new file mode 100644
index 00000000000..988e0a453bc
--- /dev/null
+++ b/eval/src/tests/eval/simple_value/simple_value_test.cpp
@@ -0,0 +1,65 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/eval/eval/simple_value.h>
+#include <vespa/eval/eval/test/tensor_model.hpp>
+#include <vespa/vespalib/gtest/gtest.h>
+
+using namespace vespalib::eval;
+using namespace vespalib::eval::test;
+
+std::vector<Layout> layouts = {
+ {},
+ {x(3)},
+ {x(3),y(5)},
+ {x(3),y(5),z(7)},
+ float_cells({x(3),y(5),z(7)}),
+ {x({"a","b","c"})},
+ {x({"a","b","c"}),y({"foo","bar"})},
+ {x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})},
+ float_cells({x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})}),
+ {x(3),y({"foo", "bar"}),z(7)},
+ {x({"a","b","c"}),y(5),z({"i","j","k","l"})},
+ float_cells({x({"a","b","c"}),y(5),z({"i","j","k","l"})})
+};
+
+TEST(SimpleValueTest, simple_values_can_be_converted_from_and_to_tensor_spec) {
+ for (const auto &layout: layouts) {
+ TensorSpec expect = spec(layout, N());
+ std::unique_ptr<NewValue> value = new_value_from_spec(expect, SimpleValueBuilderFactory());
+ TensorSpec actual = spec_from_new_value(*value);
+ EXPECT_EQ(actual, expect);
+ }
+}
+
+TEST(SimpleValueTest, simple_value_can_be_built_and_inspected) {
+ ValueType type = ValueType::from_spec("tensor<float>(x{},y[2],z{})");
+ SimpleValueBuilderFactory factory;
+ std::unique_ptr<ValueBuilder<float>> builder = factory.create_value_builder<float>(type);
+ float seq = 0.0;
+ for (vespalib::string x: {"a", "b", "c"}) {
+ for (vespalib::string y: {"aa", "bb"}) {
+ auto subspace = builder->add_subspace({x, y});
+ EXPECT_EQ(subspace.size(), 2);
+ subspace[0] = seq + 1.0;
+ subspace[1] = seq + 5.0;
+ seq += 10.0;
+ }
+ seq += 100.0;
+ }
+ std::unique_ptr<NewValue> value = builder->build(std::move(builder));
+ EXPECT_EQ(value->index().size(), 6);
+ auto view = value->index().create_view({0});
+ vespalib::stringref query = "b";
+ vespalib::stringref label;
+ size_t subspace;
+ view->lookup({&query});
+ EXPECT_TRUE(view->next_result({&label}, subspace));
+ EXPECT_EQ(label, "aa");
+ EXPECT_EQ(subspace, 2);
+ EXPECT_TRUE(view->next_result({&label}, subspace));
+ EXPECT_EQ(label, "bb");
+ EXPECT_EQ(subspace, 3);
+ EXPECT_FALSE(view->next_result({&label}, subspace));
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/eval/src/tests/eval/value_type/value_type_test.cpp b/eval/src/tests/eval/value_type/value_type_test.cpp
index 54bcd0694e2..c783c32e699 100644
--- a/eval/src/tests/eval/value_type/value_type_test.cpp
+++ b/eval/src/tests/eval/value_type/value_type_test.cpp
@@ -315,6 +315,14 @@ TEST("require that type-related predicate functions work as expected") {
TEST_DO(verify_predicates(type("tensor<float>(x[5],y{})"), false, false, true, false, false));
}
+TEST("require that mapped dimensions can be counted") {
+ EXPECT_EQUAL(type("double").count_mapped_dimensions(), 0u);
+ EXPECT_EQUAL(type("tensor(x[5],y[5])").count_mapped_dimensions(), 0u);
+ EXPECT_EQUAL(type("tensor(x{},y[5])").count_mapped_dimensions(), 1u);
+ EXPECT_EQUAL(type("tensor(x[5],y{})").count_mapped_dimensions(), 1u);
+ EXPECT_EQUAL(type("tensor(x{},y{})").count_mapped_dimensions(), 2u);
+}
+
TEST("require that dense subspace size calculation works as expected") {
EXPECT_EQUAL(type("error").dense_subspace_size(), 1u);
EXPECT_EQUAL(type("double").dense_subspace_size(), 1u);
diff --git a/eval/src/vespa/eval/eval/CMakeLists.txt b/eval/src/vespa/eval/eval/CMakeLists.txt
index 002a027c3a9..973245607de 100644
--- a/eval/src/vespa/eval/eval/CMakeLists.txt
+++ b/eval/src/vespa/eval/eval/CMakeLists.txt
@@ -20,6 +20,7 @@ vespa_add_library(eval_eval OBJECT
param_usage.cpp
simple_tensor.cpp
simple_tensor_engine.cpp
+ simple_value.cpp
string_stuff.cpp
tensor.cpp
tensor_engine.cpp
diff --git a/eval/src/vespa/eval/eval/simple_value.cpp b/eval/src/vespa/eval/eval/simple_value.cpp
new file mode 100644
index 00000000000..c2e1f2b5adc
--- /dev/null
+++ b/eval/src/vespa/eval/eval/simple_value.cpp
@@ -0,0 +1,261 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "simple_value.h"
+#include "tensor_spec.h"
+#include <vespa/vespalib/util/stash.h>
+#include <vespa/vespalib/util/typify.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP(".eval.simple_value");
+
+namespace vespalib::eval {
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+struct CreateSimpleValueBuilderBase {
+ template <typename T> static std::unique_ptr<ValueBuilderBase> invoke(const ValueType &type,
+ size_t num_mapped_in, size_t subspace_size_in)
+ {
+ assert(check_cell_type<T>(type.cell_type()));
+ return std::make_unique<SimpleValueT<T>>(type, num_mapped_in, subspace_size_in);
+ }
+};
+
+struct CreateValueFromTensorSpec {
+ template <typename T> static std::unique_ptr<NewValue> invoke(const ValueType &type, const TensorSpec &spec, const ValueBuilderFactory &factory) {
+ using SparseKey = std::vector<vespalib::stringref>;
+ using DenseMap = std::map<size_t,T>;
+ std::map<SparseKey,DenseMap> map;
+ for (const auto &entry: spec.cells()) {
+ SparseKey sparse_key;
+ size_t dense_key = 0;
+ for (const auto &dim: type.dimensions()) {
+ auto pos = entry.first.find(dim.name);
+ assert(pos != entry.first.end());
+ assert(pos->second.is_mapped() == dim.is_mapped());
+ if (dim.is_mapped()) {
+ sparse_key.emplace_back(pos->second.name);
+ } else {
+ dense_key = (dense_key * dim.size) + pos->second.index;
+ }
+ }
+ map[sparse_key][dense_key] = entry.second;
+ }
+ auto builder = factory.create_value_builder<T>(type, type.count_mapped_dimensions(), type.dense_subspace_size(), map.size());
+ for (const auto &entry: map) {
+ auto subspace = builder->add_subspace(entry.first);
+ for (const auto &cell: entry.second) {
+ subspace[cell.first] = cell.second;
+ }
+ }
+ return builder->build(std::move(builder));
+ }
+};
+
+struct CreateTensorSpecFromValue {
+ template <typename T> static TensorSpec invoke(const NewValue &value) {
+ auto cells = value.cells().typify<T>();
+ TensorSpec spec(value.type().to_spec());
+ size_t subspace_id = 0;
+ size_t subspace_size = value.type().dense_subspace_size();
+ std::vector<vespalib::stringref> labels(value.type().count_mapped_dimensions());
+ std::vector<vespalib::stringref*> label_refs;
+ for (auto &label: labels) {
+ label_refs.push_back(&label);
+ }
+ auto view = value.index().create_view({});
+ view->lookup({});
+ while (view->next_result(label_refs, subspace_id)) {
+ size_t label_idx = 0;
+ TensorSpec::Address addr;
+ for (const auto &dim: value.type().dimensions()) {
+ if (dim.is_mapped()) {
+ addr.emplace(dim.name, labels[label_idx++]);
+ }
+ }
+ for (size_t i = 0; i < subspace_size; ++i) {
+ size_t dense_key = i;
+ for (auto dim = value.type().dimensions().rbegin();
+ dim != value.type().dimensions().rend(); ++dim)
+ {
+ if (dim->is_indexed()) {
+ size_t label = dense_key % dim->size;
+ addr.emplace(dim->name, label).first->second = TensorSpec::Label(label);
+ dense_key /= dim->size;
+ }
+ }
+ spec.add(addr, cells[(subspace_size * subspace_id) + i]);
+ }
+ }
+ return spec;
+ }
+};
+
+class SimpleValueView : public NewValue::Index::View {
+private:
+ using Addr = std::vector<vespalib::string>;
+ using Map = std::map<Addr,size_t>;
+ using Itr = Map::const_iterator;
+
+ const Map &_index;
+ size_t _num_mapped;
+ std::vector<size_t> _match_dims;
+ std::vector<size_t> _extract_dims;
+ Addr _query;
+ Itr _pos;
+
+ bool is_direct_lookup() const { return (_match_dims.size() == _num_mapped); }
+ bool is_match() const {
+ assert(_pos->first.size() == _num_mapped);
+ for (size_t idx: _match_dims) {
+ if (_query[idx] != _pos->first[idx]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+public:
+ SimpleValueView(const Map &index, const std::vector<size_t> &match_dims, size_t num_mapped)
+ : _index(index), _num_mapped(num_mapped), _match_dims(match_dims), _extract_dims(), _query(num_mapped, ""), _pos(_index.end())
+ {
+ auto pos = _match_dims.begin();
+ for (size_t i = 0; i < _num_mapped; ++i) {
+ if ((pos == _match_dims.end()) || (*pos != i)) {
+ _extract_dims.push_back(i);
+ } else {
+ ++pos;
+ }
+ }
+ assert(pos == _match_dims.end());
+ assert((_match_dims.size() + _extract_dims.size()) == _num_mapped);
+ }
+
+ void lookup(const std::vector<const vespalib::stringref*> &addr) override {
+ assert(addr.size() == _match_dims.size());
+ for (size_t i = 0; i < _match_dims.size(); ++i) {
+ _query[_match_dims[i]] = *addr[i];
+ }
+ if (is_direct_lookup()) {
+ _pos = _index.find(_query);
+ } else {
+ _pos = _index.begin();
+ }
+ }
+
+ bool next_result(const std::vector<vespalib::stringref*> &addr_out, size_t &idx_out) override {
+ assert(addr_out.size() == _extract_dims.size());
+ while (_pos != _index.end()) {
+ if (is_match()) {
+ for (size_t i = 0; i < _extract_dims.size(); ++i) {
+ *addr_out[i] = _pos->first[_extract_dims[i]];
+ }
+ idx_out = _pos->second;
+ if (is_direct_lookup()) {
+ _pos = _index.end();
+ } else {
+ ++_pos;
+ }
+ return true;
+ }
+ ++_pos;
+ }
+ return false;
+ }
+};
+
+}
+
+//-----------------------------------------------------------------------------
+
+void
+SimpleValue::add_mapping(const std::vector<vespalib::stringref> &addr)
+{
+ size_t id = _index.size();
+ std::vector<vespalib::string> my_addr;
+ for (const auto &label: addr) {
+ my_addr.push_back(label);
+ }
+ auto res = _index.emplace(std::move(my_addr), id);
+ assert(res.second);
+}
+
+SimpleValue::SimpleValue(const ValueType &type, size_t num_mapped_in, size_t subspace_size_in)
+ : _type(type),
+ _num_mapped(num_mapped_in),
+ _subspace_size(subspace_size_in),
+ _index()
+{
+ assert(_type.count_mapped_dimensions() == _num_mapped);
+ assert(_type.dense_subspace_size() == _subspace_size);
+}
+
+SimpleValue::~SimpleValue() = default;
+
+std::unique_ptr<NewValue::Index::View>
+SimpleValue::create_view(const std::vector<size_t> &dims) const
+{
+ return std::make_unique<SimpleValueView>(_index, dims, _num_mapped);
+}
+
+//-----------------------------------------------------------------------------
+
+template <typename T>
+SimpleValueT<T>::SimpleValueT(const ValueType &type, size_t num_mapped_in, size_t subspace_size_in)
+ : SimpleValue(type, num_mapped_in, subspace_size_in),
+ _cells()
+{
+}
+
+template <typename T>
+SimpleValueT<T>::~SimpleValueT() = default;
+
+template <typename T>
+ArrayRef<T>
+SimpleValueT<T>::add_subspace(const std::vector<vespalib::stringref> &addr)
+{
+ size_t old_size = _cells.size();
+ assert(old_size == (index().size() * subspace_size()));
+ add_mapping(addr);
+ _cells.resize(old_size + subspace_size());
+ return ArrayRef<T>(&_cells[old_size], subspace_size());
+}
+
+//-----------------------------------------------------------------------------
+
+std::unique_ptr<ValueBuilderBase>
+SimpleValueBuilderFactory::create_value_builder_base(const ValueType &type,
+ size_t num_mapped_in, size_t subspace_size_in, size_t) const
+{
+ return typify_invoke<1,TypifyCellType,CreateSimpleValueBuilderBase>(type.cell_type(), type, num_mapped_in, subspace_size_in);
+}
+
+//-----------------------------------------------------------------------------
+
+std::unique_ptr<NewValue> new_join(const NewValue &a, const NewValue &b, join_fun_t function, const ValueBuilderFactory &factory) {
+ (void) a;
+ (void) b;
+ (void) function;
+ (void) factory;
+ return std::unique_ptr<NewValue>(nullptr);
+}
+
+//-----------------------------------------------------------------------------
+
+std::unique_ptr<NewValue> new_value_from_spec(const TensorSpec &spec, const ValueBuilderFactory &factory) {
+ ValueType type = ValueType::from_spec(spec.type());
+ assert(!type.is_error());
+ return typify_invoke<1,TypifyCellType,CreateValueFromTensorSpec>(type.cell_type(), type, spec, factory);
+}
+
+//-----------------------------------------------------------------------------
+
+TensorSpec spec_from_new_value(const NewValue &value) {
+ return typify_invoke<1,TypifyCellType,CreateTensorSpecFromValue>(value.type().cell_type(), value);
+}
+
+//-----------------------------------------------------------------------------
+
+}
diff --git a/eval/src/vespa/eval/eval/simple_value.h b/eval/src/vespa/eval/eval/simple_value.h
new file mode 100644
index 00000000000..d080a0e31c2
--- /dev/null
+++ b/eval/src/vespa/eval/eval/simple_value.h
@@ -0,0 +1,215 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "value.h"
+#include "value_type.h"
+#include <vespa/eval/tensor/dense/typed_cells.h>
+#include <vespa/vespalib/stllike/string.h>
+#include <vector>
+#include <map>
+
+namespace vespalib { class Stash; }
+
+namespace vespalib::eval {
+
+class TensorSpec;
+
+using TypedCells = ::vespalib::tensor::TypedCells;
+
+/**
+ * Experimental interface layer that will be moved into Value when all
+ * existing implementations are able to implement it. This interface
+ * will try to unify scalars, dense tensors, sparse tensors and mixed
+ * tensors while also enabling operations to be implemented
+ * efficiently using this interface without having knowledge about the
+ * actual implementation. Baseline operations will treat all values as
+ * mixed tensors. Simplified and optimized variants may replace them
+ * as done today based on type knowledge.
+ *
+ * All values are expected to be separated into a continuous area
+ * storing cells as concatenated dense subspaces, and an index
+ * structure used to look up label combinations; mapping them into a
+ * set of dense subspaces.
+ **/
+struct NewValue : Value {
+
+ // Root lookup structure for mapping labels to dense subspace indexes
+ struct Index {
+
+ // A view able to look up dense subspace indexes from labels
+ // specifying a partial address for the dimensions given to
+ // create_view. A view is re-usable. Lookups are performed by
+ // calling the lookup function and lookup results are
+ // extracted using the next_result function.
+ struct View {
+
+ // look up dense subspace indexes from labels specifying a
+ // partial address for the dimensions given to
+ // create_view. Results from the lookup is extracted using
+ // the next_result function.
+ virtual void lookup(const std::vector<const vespalib::stringref*> &addr) = 0;
+
+ // Extract the next result (if any) from the previous
+ // lookup into the given partial address and index. Only
+ // the labels for the dimensions NOT specified in
+ // create_view will be extracted here.
+ virtual bool next_result(const std::vector<vespalib::stringref*> &addr_out, size_t &idx_out) = 0;
+
+ virtual ~View() {}
+ };
+
+ // total number of mappings (equal to the number of dense subspaces)
+ virtual size_t size() const = 0;
+
+ // create a view able to look up dense subspaces based on
+ // labels from a subset of the mapped dimensions.
+ virtual std::unique_ptr<View> create_view(const std::vector<size_t> &dims) const = 0;
+
+ virtual ~Index() {}
+ };
+ virtual TypedCells cells() const = 0;
+ virtual const Index &index() const = 0;
+ virtual ~NewValue() {}
+};
+
+/**
+ * Tagging interface used as return type from factories before
+ * downcasting to actual builder with specialized cell type.
+ **/
+struct ValueBuilderBase {
+ virtual ~ValueBuilderBase() {}
+};
+
+/**
+ * Interface used to build a value one dense subspace at a
+ * time. Enables decoupling of what the value should contain from how
+ * to store the value.
+ **/
+template <typename T>
+struct ValueBuilder : ValueBuilderBase {
+ // add a dense subspace for the given address (label for all
+ // mapped dimensions in canonical order). Note that previously
+ // returned subspaces will be invalidated when new subspaces are
+ // added. Also note that adding the same subspace multiple times
+ // is not allowed.
+ virtual ArrayRef<T> add_subspace(const std::vector<vespalib::stringref> &addr) = 0;
+
+ // Given the ownership of the builder itself, produce the newly
+ // created value. This means that builders can only be used once,
+ // it also means values can build themselves.
+ virtual std::unique_ptr<NewValue> build(std::unique_ptr<ValueBuilder> self) = 0;
+};
+
+/**
+ * Factory able to create appropriate value builders. We do not really
+ * care about the full mathematical type here, but it needs to be
+ * passed since it is exposed in the value api. The expected number of
+ * subspaces is also passed since it enables the builder to pre-size
+ * internal structures appropriately. Note that since we are not able
+ * to have virtual templated functions we need to cast the created
+ * builder. With interoperability between all values.
+ **/
+struct ValueBuilderFactory {
+ template <typename T>
+ std::unique_ptr<ValueBuilder<T>> create_value_builder(const ValueType &type,
+ size_t num_mapped_in, size_t subspace_size_in, size_t expect_subspaces) const
+ {
+ assert(check_cell_type<T>(type.cell_type()));
+ auto base = create_value_builder_base(type, num_mapped_in, subspace_size_in, expect_subspaces);
+ ValueBuilder<T> *builder = dynamic_cast<ValueBuilder<T>*>(base.get());
+ assert(builder);
+ base.release();
+ return std::unique_ptr<ValueBuilder<T>>(builder);
+ }
+ template <typename T>
+ std::unique_ptr<ValueBuilder<T>> create_value_builder(const ValueType &type) const
+ {
+ return create_value_builder<T>(type, type.count_mapped_dimensions(), type.dense_subspace_size(), 1);
+ }
+ virtual ~ValueBuilderFactory() {}
+protected:
+ virtual std::unique_ptr<ValueBuilderBase> create_value_builder_base(const ValueType &type,
+ size_t num_mapped_in, size_t subspace_size_in, size_t expect_subspaces) const = 0;
+};
+
+/**
+ * A simple implementation of a generic value that can also be used to
+ * build new values. This class focuses on simplicity over speed and
+ * is intended as a reference implementation that can also be used to
+ * test the correctness of tensor operations as they are moved away
+ * from the implementation of individual tensor classes.
+ **/
+class SimpleValue : public NewValue, public NewValue::Index
+{
+private:
+ using Addr = std::vector<vespalib::string>;
+ ValueType _type;
+ size_t _num_mapped;
+ size_t _subspace_size;
+ std::map<Addr,size_t> _index;
+protected:
+ size_t subspace_size() const { return _subspace_size; }
+ void add_mapping(const std::vector<vespalib::stringref> &addr);
+public:
+ SimpleValue(const ValueType &type, size_t num_mapped_in, size_t subspace_size_in);
+ ~SimpleValue() override;
+ const ValueType &type() const override { return _type; }
+ const NewValue::Index &index() const override { return *this; }
+ size_t size() const override { return _index.size(); }
+ std::unique_ptr<View> create_view(const std::vector<size_t> &dims) const override;
+};
+
+/**
+ * Subclasses of SimpleValue handling cell type specialization.
+ **/
+template <typename T>
+class SimpleValueT : public SimpleValue, public ValueBuilder<T>
+{
+private:
+ std::vector<T> _cells;
+public:
+ SimpleValueT(const ValueType &type, size_t num_mapped_in, size_t subspace_size_in);
+ ~SimpleValueT() override;
+ TypedCells cells() const override { return TypedCells(ConstArrayRef<T>(_cells)); }
+ ArrayRef<T> add_subspace(const std::vector<vespalib::stringref> &addr) override;
+ std::unique_ptr<NewValue> build(std::unique_ptr<ValueBuilder<T>> self) override {
+ ValueBuilder<T>* me = this;
+ assert(me == self.get());
+ self.release();
+ return std::unique_ptr<NewValue>(this);
+ }
+};
+
+/**
+ * ValueBuilderFactory implementation for SimpleValue.
+ **/
+struct SimpleValueBuilderFactory : ValueBuilderFactory {
+ ~SimpleValueBuilderFactory() override {}
+protected:
+ std::unique_ptr<ValueBuilderBase> create_value_builder_base(const ValueType &type,
+ size_t num_mapped_in, size_t subspace_size_in, size_t expect_subspaces) const override;
+};
+
+/**
+ * Generic join operation treating both values as mixed
+ * tensors. Packaging will change, and while the baseline join will
+ * not have information about low-level value class implementations,
+ * it will have up-front knowledge about types, specifically
+ * dimensional overlap and result type.
+ **/
+using join_fun_t = double (*)(double, double);
+std::unique_ptr<NewValue> new_join(const NewValue &a, const NewValue &b, join_fun_t function, const ValueBuilderFactory &factory);
+
+/**
+ * Make a value from a tensor spec using a value builder factory
+ * interface, making it work with any value implementation.
+ **/
+std::unique_ptr<NewValue> new_value_from_spec(const TensorSpec &spec, const ValueBuilderFactory &factory);
+
+/**
+ * Convert a generic value to a tensor spec.
+ **/
+TensorSpec spec_from_new_value(const NewValue &value);
+
+}
diff --git a/eval/src/vespa/eval/eval/value_type.cpp b/eval/src/vespa/eval/eval/value_type.cpp
index 2287e8599eb..2b24a88f473 100644
--- a/eval/src/vespa/eval/eval/value_type.cpp
+++ b/eval/src/vespa/eval/eval/value_type.cpp
@@ -174,6 +174,18 @@ ValueType::is_dense() const
}
size_t
+ValueType::count_mapped_dimensions() const
+{
+ size_t cnt = 0;
+ for (const auto &dim : dimensions()) {
+ if (dim.is_mapped()) {
+ ++cnt;
+ }
+ }
+ return cnt;
+}
+
+size_t
ValueType::dense_subspace_size() const
{
size_t size = 1;
diff --git a/eval/src/vespa/eval/eval/value_type.h b/eval/src/vespa/eval/eval/value_type.h
index a3dbb901eb0..b89c5859593 100644
--- a/eval/src/vespa/eval/eval/value_type.h
+++ b/eval/src/vespa/eval/eval/value_type.h
@@ -60,6 +60,7 @@ public:
bool is_tensor() const { return (_type == Type::TENSOR); }
bool is_sparse() const;
bool is_dense() const;
+ size_t count_mapped_dimensions() const;
size_t dense_subspace_size() const;
const std::vector<Dimension> &dimensions() const { return _dimensions; }
std::vector<Dimension> nontrivial_dimensions() const;