summaryrefslogtreecommitdiffstats
path: root/eval
diff options
context:
space:
mode:
authorHåvard Pettersen <havardpe@oath.com>2019-05-02 11:15:24 +0000
committerHåvard Pettersen <havardpe@oath.com>2019-05-03 11:22:02 +0000
commitb915a61385e9e6d3b686fa949a76b763f89dbbe3 (patch)
tree4386ba8494a3d8a643b3fc179d5e4d33b533e219 /eval
parente2ef6e41a4fedb4b02cec4946e37f28b1b5d8dd8 (diff)
add concept of float cells in value type
scalar values are always double preserve cell type when combining tensor with scalar result(float cells, double cells) -> double cells require '()' in tensor type spec clean up and update value type test
Diffstat (limited to 'eval')
-rw-r--r--eval/src/tests/eval/node_types/node_types_test.cpp2
-rw-r--r--eval/src/tests/eval/value_type/value_type_test.cpp405
-rw-r--r--eval/src/vespa/eval/eval/node_types.cpp6
-rw-r--r--eval/src/vespa/eval/eval/value_type.cpp33
-rw-r--r--eval/src/vespa/eval/eval/value_type.h17
-rw-r--r--eval/src/vespa/eval/eval/value_type_spec.cpp73
6 files changed, 305 insertions, 231 deletions
diff --git a/eval/src/tests/eval/node_types/node_types_test.cpp b/eval/src/tests/eval/node_types/node_types_test.cpp
index c18470887b2..d5c0ed995f1 100644
--- a/eval/src/tests/eval/node_types/node_types_test.cpp
+++ b/eval/src/tests/eval/node_types/node_types_test.cpp
@@ -76,7 +76,7 @@ TEST("require that leaf constants have appropriate type") {
TEST("require that input parameters preserve their type") {
TEST_DO(verify("error", "error"));
TEST_DO(verify("double", "double"));
- TEST_DO(verify("tensor", "double"));
+ TEST_DO(verify("tensor()", "double"));
TEST_DO(verify("tensor(x{},y[10],z[5])", "tensor(x{},y[10],z[5])"));
}
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 a755eac965f..cf61da00cca 100644
--- a/eval/src/tests/eval/value_type/value_type_test.cpp
+++ b/eval/src/tests/eval/value_type/value_type_test.cpp
@@ -8,16 +8,32 @@
using namespace vespalib::eval;
+using CellType = ValueType::CellType;
+
const size_t npos = ValueType::Dimension::npos;
+ValueType type(const vespalib::string &type_str) {
+ ValueType ret = ValueType::from_spec(type_str);
+ ASSERT_TRUE(!ret.is_error() || (type_str == "error"));
+ return ret;
+}
+
+std::vector<vespalib::string> str_list(const std::vector<vespalib::string> &list) {
+ return list;
+}
+
+//-----------------------------------------------------------------------------
+
TEST("require that ERROR value type can be created") {
ValueType t = ValueType::error_type();
+ EXPECT_TRUE(t.cell_type() == CellType::DOUBLE);
EXPECT_TRUE(t.type() == ValueType::Type::ERROR);
EXPECT_EQUAL(t.dimensions().size(), 0u);
}
TEST("require that DOUBLE value type can be created") {
ValueType t = ValueType::double_type();
+ EXPECT_TRUE(t.cell_type() == CellType::DOUBLE);
EXPECT_TRUE(t.type() == ValueType::Type::DOUBLE);
EXPECT_EQUAL(t.dimensions().size(), 0u);
}
@@ -25,6 +41,18 @@ TEST("require that DOUBLE value type can be created") {
TEST("require that TENSOR value type can be created") {
ValueType t = ValueType::tensor_type({{"x", 10},{"y"}});
EXPECT_TRUE(t.type() == ValueType::Type::TENSOR);
+ EXPECT_TRUE(t.cell_type() == CellType::DOUBLE);
+ ASSERT_EQUAL(t.dimensions().size(), 2u);
+ EXPECT_EQUAL(t.dimensions()[0].name, "x");
+ EXPECT_EQUAL(t.dimensions()[0].size, 10u);
+ EXPECT_EQUAL(t.dimensions()[1].name, "y");
+ EXPECT_EQUAL(t.dimensions()[1].size, npos);
+}
+
+TEST("require that float TENSOR value type can be created") {
+ ValueType t = ValueType::tensor_type({{"x", 10},{"y"}}, CellType::FLOAT);
+ EXPECT_TRUE(t.type() == ValueType::Type::TENSOR);
+ EXPECT_TRUE(t.cell_type() == CellType::FLOAT);
ASSERT_EQUAL(t.dimensions().size(), 2u);
EXPECT_EQUAL(t.dimensions()[0].name, "x");
EXPECT_EQUAL(t.dimensions()[0].size, 10u);
@@ -35,6 +63,7 @@ TEST("require that TENSOR value type can be created") {
TEST("require that TENSOR value type sorts dimensions") {
ValueType t = ValueType::tensor_type({{"x", 10}, {"z", 30}, {"y"}});
EXPECT_TRUE(t.type() == ValueType::Type::TENSOR);
+ EXPECT_TRUE(t.cell_type() == CellType::DOUBLE);
ASSERT_EQUAL(t.dimensions().size(), 3u);
EXPECT_EQUAL(t.dimensions()[0].name, "x");
EXPECT_EQUAL(t.dimensions()[0].size, 10u);
@@ -44,26 +73,23 @@ TEST("require that TENSOR value type sorts dimensions") {
EXPECT_EQUAL(t.dimensions()[2].size, 30u);
}
-TEST("require that dimension names can be obtained") {
- EXPECT_EQUAL(ValueType::double_type().dimension_names(),
- std::vector<vespalib::string>({}));
- EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}, {"x", 30}}).dimension_names(),
- std::vector<vespalib::string>({"x", "y"}));
- EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}, {"x", 30}, {"z"}}).dimension_names(),
- std::vector<vespalib::string>({"x", "y", "z"}));
+TEST("require that 'tensor<float>()' is normalized to 'double'") {
+ ValueType t = ValueType::tensor_type({}, CellType::FLOAT);
+ EXPECT_TRUE(t.cell_type() == CellType::DOUBLE);
+ EXPECT_TRUE(t.type() == ValueType::Type::DOUBLE);
+ EXPECT_EQUAL(t.dimensions().size(), 0u);
}
-TEST("require that dimension index can be obtained") {
- EXPECT_EQUAL(ValueType::error_type().dimension_index("x"), ValueType::Dimension::npos);
- EXPECT_EQUAL(ValueType::double_type().dimension_index("x"), ValueType::Dimension::npos);
- EXPECT_EQUAL(ValueType::tensor_type({}).dimension_index("x"), ValueType::Dimension::npos);
- auto my_type = ValueType::tensor_type({{"y", 10}, {"x"}, {"z", 5}});
- EXPECT_EQUAL(my_type.dimension_index("x"), 0u);
- EXPECT_EQUAL(my_type.dimension_index("y"), 1u);
- EXPECT_EQUAL(my_type.dimension_index("z"), 2u);
- EXPECT_EQUAL(my_type.dimension_index("w"), ValueType::Dimension::npos);
+TEST("require that use of unbound dimensions result in error types") {
+ EXPECT_TRUE(ValueType::tensor_type({{"x", 0}}).is_error());
}
+TEST("require that duplicate dimension names result in error types") {
+ EXPECT_TRUE(ValueType::tensor_type({{"x"}, {"x"}}).is_error());
+}
+
+//-----------------------------------------------------------------------------
+
void verify_equal(const ValueType &a, const ValueType &b) {
EXPECT_EQUAL(a, b);
EXPECT_EQUAL(b, a);
@@ -94,149 +120,46 @@ TEST("require that value types can be compared") {
TEST_DO(verify_equal(ValueType::tensor_type({{"x", 10}, {"y", 20}}), ValueType::tensor_type({{"y", 20}, {"x", 10}})));
TEST_DO(verify_not_equal(ValueType::tensor_type({{"x", 10}, {"y", 20}}), ValueType::tensor_type({{"x", 10}, {"y", 10}})));
TEST_DO(verify_not_equal(ValueType::tensor_type({{"x", 10}}), ValueType::tensor_type({{"x"}})));
+ TEST_DO(verify_equal(ValueType::tensor_type({{"x", 10}}, CellType::FLOAT), ValueType::tensor_type({{"x", 10}}, CellType::FLOAT)));
+ TEST_DO(verify_not_equal(ValueType::tensor_type({{"x", 10}}, CellType::DOUBLE), ValueType::tensor_type({{"x", 10}}, CellType::FLOAT)));
}
-void verify_predicates(const ValueType &type,
- bool expect_error, bool expect_double, bool expect_tensor,
- bool expect_sparse, bool expect_dense)
-{
- EXPECT_EQUAL(type.is_error(), expect_error);
- EXPECT_EQUAL(type.is_double(), expect_double);
- EXPECT_EQUAL(type.is_tensor(), expect_tensor);
- EXPECT_EQUAL(type.is_sparse(), expect_sparse);
- EXPECT_EQUAL(type.is_dense(), expect_dense);
-}
-
-TEST("require that type-related predicate functions work as expected") {
- TEST_DO(verify_predicates(ValueType::error_type(), true, false, false, false, false));
- TEST_DO(verify_predicates(ValueType::double_type(), false, true, false, false, false));
- TEST_DO(verify_predicates(ValueType::tensor_type({}), false, true, false, false, false));
- TEST_DO(verify_predicates(ValueType::tensor_type({{"x"}}), false, false, true, true, false));
- TEST_DO(verify_predicates(ValueType::tensor_type({{"x"},{"y"}}), false, false, true, true, false));
- TEST_DO(verify_predicates(ValueType::tensor_type({{"x", 5}}), false, false, true, false, true));
- TEST_DO(verify_predicates(ValueType::tensor_type({{"x", 5},{"y", 10}}), false, false, true, false, true));
- TEST_DO(verify_predicates(ValueType::tensor_type({{"x", 5}, {"y"}}), false, false, true, false, false));
-}
-
-TEST("require that dimension predicates work as expected") {
- ValueType::Dimension x("x");
- ValueType::Dimension y("y", 10);
- ValueType::Dimension z("z", 0);
- EXPECT_TRUE(x.is_mapped());
- EXPECT_TRUE(!x.is_indexed());
- EXPECT_TRUE(!x.is_bound());
- EXPECT_TRUE(!y.is_mapped());
- EXPECT_TRUE(y.is_indexed());
- EXPECT_TRUE(y.is_bound());
- EXPECT_TRUE(!z.is_mapped());
- EXPECT_TRUE(z.is_indexed());
- EXPECT_TRUE(!z.is_bound());
-}
-
-TEST("require that use of unbound dimensions result in error types") {
- EXPECT_TRUE(ValueType::tensor_type({{"x", 0}}).is_error());
-}
-
-TEST("require that duplicate dimension names result in error types") {
- EXPECT_TRUE(ValueType::tensor_type({{"x"}, {"x"}}).is_error());
-}
-
-TEST("require that removing dimensions from non-tensor types gives error type") {
- EXPECT_TRUE(ValueType::error_type().reduce({"x"}).is_error());
- EXPECT_TRUE(ValueType::double_type().reduce({"x"}).is_error());
-}
-
-TEST("require that dimensions can be removed from tensor value types") {
- ValueType type = ValueType::tensor_type({{"x", 10}, {"y", 20}, {"z", 30}});
- EXPECT_EQUAL(ValueType::tensor_type({{"y", 20}, {"z", 30}}), type.reduce({"x"}));
- EXPECT_EQUAL(ValueType::tensor_type({{"x", 10}, {"z", 30}}), type.reduce({"y"}));
- EXPECT_EQUAL(ValueType::tensor_type({{"x", 10}, {"y", 20}}), type.reduce({"z"}));
- EXPECT_EQUAL(ValueType::tensor_type({{"y", 20}}), type.reduce({"x", "z"}));
- EXPECT_EQUAL(ValueType::tensor_type({{"y", 20}}), type.reduce({"z", "x"}));
-}
-
-TEST("require that removing an empty set of dimensions means removing them all") {
- EXPECT_EQUAL(ValueType::tensor_type({{"x", 10}, {"y", 20}, {"z", 30}}).reduce({}), ValueType::double_type());
-}
-
-TEST("require that removing non-existing dimensions gives error type") {
- EXPECT_TRUE(ValueType::tensor_type({{"y"}}).reduce({"x"}).is_error());
- EXPECT_TRUE(ValueType::tensor_type({{"y", 10}}).reduce({"x"}).is_error());
-}
-
-TEST("require that removing all dimensions gives double type") {
- ValueType type = ValueType::tensor_type({{"x", 10}, {"y", 20}, {"z", 30}});
- EXPECT_EQUAL(ValueType::double_type(), type.reduce({"x", "y", "z"}));
-}
-
-TEST("require that dimensions can be combined for value types") {
- ValueType tensor_type_xy = ValueType::tensor_type({{"x"}, {"y"}});
- ValueType tensor_type_yz = ValueType::tensor_type({{"y"}, {"z"}});
- ValueType tensor_type_xyz = ValueType::tensor_type({{"x"}, {"y"}, {"z"}});
- ValueType tensor_type_y = ValueType::tensor_type({{"y"}});
- ValueType tensor_type_a10 = ValueType::tensor_type({{"a", 10}});
- ValueType tensor_type_a10xyz = ValueType::tensor_type({{"a", 10}, {"x"}, {"y"}, {"z"}});
- ValueType scalar = ValueType::double_type();
- EXPECT_EQUAL(ValueType::join(scalar, scalar), scalar);
- EXPECT_EQUAL(ValueType::join(tensor_type_xy, tensor_type_yz), tensor_type_xyz);
- EXPECT_EQUAL(ValueType::join(tensor_type_yz, tensor_type_xy), tensor_type_xyz);
- EXPECT_EQUAL(ValueType::join(tensor_type_y, tensor_type_y), tensor_type_y);
- EXPECT_EQUAL(ValueType::join(scalar, tensor_type_y), tensor_type_y);
- EXPECT_EQUAL(ValueType::join(tensor_type_a10, tensor_type_a10), tensor_type_a10);
- EXPECT_EQUAL(ValueType::join(tensor_type_a10, scalar), tensor_type_a10);
- EXPECT_EQUAL(ValueType::join(tensor_type_xyz, tensor_type_a10), tensor_type_a10xyz);
-}
-
-void verify_not_combinable(const ValueType &a, const ValueType &b) {
- EXPECT_TRUE(ValueType::join(a, b).is_error());
- EXPECT_TRUE(ValueType::join(b, a).is_error());
-}
-
-TEST("require that mapped and indexed dimensions are not combinable") {
- verify_not_combinable(ValueType::tensor_type({{"x", 10}}), ValueType::tensor_type({{"x"}}));
-}
-
-TEST("require that indexed dimensions of different sizes are not combinable") {
- verify_not_combinable(ValueType::tensor_type({{"x", 10}}), ValueType::tensor_type({{"x", 20}}));
-}
-
-TEST("require that error type combined with anything produces error type") {
- verify_not_combinable(ValueType::error_type(), ValueType::error_type());
- verify_not_combinable(ValueType::error_type(), ValueType::double_type());
- verify_not_combinable(ValueType::error_type(), ValueType::tensor_type({{"x"}}));
- verify_not_combinable(ValueType::error_type(), ValueType::tensor_type({{"x", 10}}));
-}
+//-----------------------------------------------------------------------------
TEST("require that value type can make spec") {
EXPECT_EQUAL("error", ValueType::error_type().to_spec());
EXPECT_EQUAL("double", ValueType::double_type().to_spec());
EXPECT_EQUAL("double", ValueType::tensor_type({}).to_spec());
+ EXPECT_EQUAL("double", ValueType::tensor_type({}, CellType::FLOAT).to_spec());
EXPECT_EQUAL("tensor(x{})", ValueType::tensor_type({{"x"}}).to_spec());
EXPECT_EQUAL("tensor(y[10])", ValueType::tensor_type({{"y", 10}}).to_spec());
EXPECT_EQUAL("tensor(x{},y[10],z[5])", ValueType::tensor_type({{"x"}, {"y", 10}, {"z", 5}}).to_spec());
+ EXPECT_EQUAL("tensor<float>(x{})", ValueType::tensor_type({{"x"}}, CellType::FLOAT).to_spec());
+ EXPECT_EQUAL("tensor<float>(y[10])", ValueType::tensor_type({{"y", 10}}, CellType::FLOAT).to_spec());
+ EXPECT_EQUAL("tensor<float>(x{},y[10],z[5])", ValueType::tensor_type({{"x"}, {"y", 10}, {"z", 5}}, CellType::FLOAT).to_spec());
}
+//-----------------------------------------------------------------------------
+
TEST("require that value type spec can be parsed") {
EXPECT_EQUAL(ValueType::double_type(), ValueType::from_spec("double"));
- EXPECT_EQUAL(ValueType::tensor_type({}), ValueType::from_spec("tensor"));
EXPECT_EQUAL(ValueType::tensor_type({}), ValueType::from_spec("tensor()"));
EXPECT_EQUAL(ValueType::tensor_type({{"x"}}), ValueType::from_spec("tensor(x{})"));
EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}}), ValueType::from_spec("tensor(y[10])"));
EXPECT_EQUAL(ValueType::tensor_type({{"x"}, {"y", 10}, {"z", 5}}), ValueType::from_spec("tensor(x{},y[10],z[5])"));
EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}}), ValueType::from_spec("tensor<double>(y[10])"));
- EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}}), ValueType::from_spec("tensor<float>(y[10])"));
+ EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}}, CellType::FLOAT), ValueType::from_spec("tensor<float>(y[10])"));
}
TEST("require that value type spec can be parsed with extra whitespace") {
EXPECT_EQUAL(ValueType::double_type(), ValueType::from_spec(" double "));
- EXPECT_EQUAL(ValueType::tensor_type({}), ValueType::from_spec(" tensor "));
EXPECT_EQUAL(ValueType::tensor_type({}), ValueType::from_spec(" tensor ( ) "));
EXPECT_EQUAL(ValueType::tensor_type({{"x"}}), ValueType::from_spec(" tensor ( x { } ) "));
EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}}), ValueType::from_spec(" tensor ( y [ 10 ] ) "));
EXPECT_EQUAL(ValueType::tensor_type({{"x"}, {"y", 10}, {"z", 5}}),
ValueType::from_spec(" tensor ( x { } , y [ 10 ] , z [ 5 ] ) "));
EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}}), ValueType::from_spec(" tensor < double > ( y [ 10 ] ) "));
- EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}}), ValueType::from_spec(" tensor < float > ( y [ 10 ] ) "));
+ EXPECT_EQUAL(ValueType::tensor_type({{"y", 10}}, CellType::FLOAT), ValueType::from_spec(" tensor < float > ( y [ 10 ] ) "));
}
TEST("require that malformed value type spec is parsed as error") {
@@ -244,7 +167,9 @@ TEST("require that malformed value type spec is parsed as error") {
EXPECT_TRUE(ValueType::from_spec(" ").is_error());
EXPECT_TRUE(ValueType::from_spec("error").is_error());
EXPECT_TRUE(ValueType::from_spec("any").is_error());
- EXPECT_TRUE(ValueType::from_spec("tensor tensor").is_error());
+ EXPECT_TRUE(ValueType::from_spec("tensor").is_error());
+ EXPECT_TRUE(ValueType::from_spec("tensor<double>").is_error());
+ EXPECT_TRUE(ValueType::from_spec("tensor() tensor()").is_error());
EXPECT_TRUE(ValueType::from_spec("tensor(x{10})").is_error());
EXPECT_TRUE(ValueType::from_spec("tensor(x{},)").is_error());
EXPECT_TRUE(ValueType::from_spec("tensor(,x{})").is_error());
@@ -277,9 +202,8 @@ ParseResult::ParseResult(const vespalib::string &spec_in)
pos(spec.data()),
end(pos + spec.size()),
after(nullptr),
- type(value_type::parse_spec(pos, end, after))
-{ }
-ParseResult::~ParseResult() { }
+ type(value_type::parse_spec(pos, end, after)) {}
+ParseResult::~ParseResult() = default;
TEST("require that we can parse a partial string into a type with the low-level API") {
ParseResult result("tensor(a[5]) , ");
@@ -297,56 +221,177 @@ TEST("require that 'error' is the valid representation of the error type") {
EXPECT_TRUE(invalid.after == nullptr); // parse not ok
}
+//-----------------------------------------------------------------------------
+
+TEST("require that value types preserve cell type") {
+ EXPECT_TRUE(type("tensor(x[10])").cell_type() == CellType::DOUBLE);
+ EXPECT_TRUE(type("tensor<double>(x[10])").cell_type() == CellType::DOUBLE);
+ EXPECT_TRUE(type("tensor<float>(x[10])").cell_type() == CellType::FLOAT);
+}
+
+TEST("require that dimension names can be obtained") {
+ EXPECT_EQUAL(type("double").dimension_names(), str_list({}));
+ EXPECT_EQUAL(type("tensor(y[30],x[10])").dimension_names(), str_list({"x", "y"}));
+ EXPECT_EQUAL(type("tensor<float>(y[10],x[30],z{})").dimension_names(), str_list({"x", "y", "z"}));
+}
+
+TEST("require that dimension index can be obtained") {
+ EXPECT_EQUAL(type("error").dimension_index("x"), ValueType::Dimension::npos);
+ EXPECT_EQUAL(type("double").dimension_index("x"), ValueType::Dimension::npos);
+ EXPECT_EQUAL(type("tensor()").dimension_index("x"), ValueType::Dimension::npos);
+ EXPECT_EQUAL(type("tensor(y[10],x{},z[5])").dimension_index("x"), 0u);
+ EXPECT_EQUAL(type("tensor<float>(y[10],x{},z[5])").dimension_index("y"), 1u);
+ EXPECT_EQUAL(type("tensor(y[10],x{},z[5])").dimension_index("z"), 2u);
+ EXPECT_EQUAL(type("tensor(y[10],x{},z[5])").dimension_index("w"), ValueType::Dimension::npos);
+}
+
+void verify_predicates(const ValueType &type,
+ bool expect_error, bool expect_double, bool expect_tensor,
+ bool expect_sparse, bool expect_dense)
+{
+ EXPECT_EQUAL(type.is_error(), expect_error);
+ EXPECT_EQUAL(type.is_double(), expect_double);
+ EXPECT_EQUAL(type.is_tensor(), expect_tensor);
+ EXPECT_EQUAL(type.is_sparse(), expect_sparse);
+ EXPECT_EQUAL(type.is_dense(), expect_dense);
+}
+
+TEST("require that type-related predicate functions work as expected") {
+ TEST_DO(verify_predicates(type("error"), true, false, false, false, false));
+ TEST_DO(verify_predicates(type("double"), false, true, false, false, false));
+ TEST_DO(verify_predicates(type("tensor()"), false, true, false, false, false));
+ TEST_DO(verify_predicates(type("tensor(x{})"), false, false, true, true, false));
+ TEST_DO(verify_predicates(type("tensor(x{},y{})"), false, false, true, true, false));
+ TEST_DO(verify_predicates(type("tensor(x[5])"), false, false, true, false, true));
+ TEST_DO(verify_predicates(type("tensor(x[5],y[10])"), false, false, true, false, true));
+ TEST_DO(verify_predicates(type("tensor(x[5],y{})"), false, false, true, false, false));
+ TEST_DO(verify_predicates(type("tensor<float>(x{})"), false, false, true, true, false));
+ TEST_DO(verify_predicates(type("tensor<float>(x[5])"), false, false, true, false, true));
+ TEST_DO(verify_predicates(type("tensor<float>(x[5],y{})"), false, false, true, false, false));
+}
+
+TEST("require that dimension predicates work as expected") {
+ ValueType::Dimension x("x");
+ ValueType::Dimension y("y", 10);
+ ValueType::Dimension z("z", 0);
+ EXPECT_TRUE(x.is_mapped());
+ EXPECT_TRUE(!x.is_indexed());
+ EXPECT_TRUE(!x.is_bound());
+ EXPECT_TRUE(!y.is_mapped());
+ EXPECT_TRUE(y.is_indexed());
+ EXPECT_TRUE(y.is_bound());
+ EXPECT_TRUE(!z.is_mapped());
+ EXPECT_TRUE(z.is_indexed());
+ EXPECT_TRUE(!z.is_bound());
+}
+
+TEST("require that removing dimensions from non-tensor types gives error type") {
+ EXPECT_TRUE(type("error").reduce({"x"}).is_error());
+ EXPECT_TRUE(type("double").reduce({"x"}).is_error());
+}
+
+TEST("require that dimensions can be removed from tensor value types") {
+ EXPECT_EQUAL(type("tensor(x[10],y[20],z[30])").reduce({"x"}), type("tensor(y[20],z[30])"));
+ EXPECT_EQUAL(type("tensor(x[10],y[20],z[30])").reduce({"y"}), type("tensor(x[10],z[30])"));
+ EXPECT_EQUAL(type("tensor<float>(x[10],y[20],z[30])").reduce({"z"}), type("tensor<float>(x[10],y[20])"));
+ EXPECT_EQUAL(type("tensor(x[10],y[20],z[30])").reduce({"x", "z"}), type("tensor(y[20])"));
+ EXPECT_EQUAL(type("tensor<float>(x[10],y[20],z[30])").reduce({"z", "x"}), type("tensor<float>(y[20])"));
+}
+
+TEST("require that removing an empty set of dimensions means removing them all") {
+ EXPECT_EQUAL(type("tensor(x[10],y[20],z[30])").reduce({}), type("double"));
+ EXPECT_EQUAL(type("tensor<float>(x[10],y[20],z[30])").reduce({}), type("double"));
+}
+
+TEST("require that removing non-existing dimensions gives error type") {
+ EXPECT_TRUE(type("tensor(y{})").reduce({"x"}).is_error());
+ EXPECT_TRUE(type("tensor<float>(y[10])").reduce({"x"}).is_error());
+}
+
+TEST("require that removing all dimensions gives double type") {
+ EXPECT_EQUAL(type("tensor(x[10],y[20],z[30])").reduce({"x", "y", "z"}), type("double"));
+ EXPECT_EQUAL(type("tensor<float>(x[10],y[20],z[30])").reduce({"x", "y", "z"}), type("double"));
+}
+
+void verify_join(const ValueType &a, const ValueType b, const ValueType &res) {
+ EXPECT_EQUAL(ValueType::join(a, b), res);
+ EXPECT_EQUAL(ValueType::join(b, a), res);
+}
+
+TEST("require that dimensions can be combined for value types") {
+ TEST_DO(verify_join(type("double"), type("double"), type("double")));
+ TEST_DO(verify_join(type("tensor(x{},y{})"), type("tensor(y{},z{})"), type("tensor(x{},y{},z{})")));
+ TEST_DO(verify_join(type("tensor(y{})"), type("tensor(y{})"), type("tensor(y{})")));
+ TEST_DO(verify_join(type("tensor(y{})"), type("double"), type("tensor(y{})")));
+ TEST_DO(verify_join(type("tensor(a[10])"), type("tensor(a[10])"), type("tensor(a[10])")));
+ TEST_DO(verify_join(type("tensor(a[10])"), type("double"), type("tensor(a[10])")));
+ TEST_DO(verify_join(type("tensor(a[10])"), type("tensor(x{},y{},z{})"), type("tensor(a[10],x{},y{},z{})")));
+}
+
+TEST("require that cell type is handled correctly for join") {
+ TEST_DO(verify_join(type("tensor(x{})"), type("tensor<float>(y{})"), type("tensor(x{},y{})")));
+ TEST_DO(verify_join(type("tensor<float>(x{})"), type("tensor<float>(y{})"), type("tensor<float>(x{},y{})")));
+ TEST_DO(verify_join(type("tensor<float>(x{})"), type("double"), type("tensor<float>(x{})")));
+}
+
+void verify_not_joinable(const ValueType &a, const ValueType &b) {
+ EXPECT_TRUE(ValueType::join(a, b).is_error());
+ EXPECT_TRUE(ValueType::join(b, a).is_error());
+}
+
+TEST("require that mapped and indexed dimensions are not joinable") {
+ verify_not_joinable(type("tensor(x[10])"), type("tensor(x{})"));
+}
+
+TEST("require that indexed dimensions of different sizes are not joinable") {
+ verify_not_joinable(type("tensor(x[10])"), type("tensor(x[20])"));
+}
+
+TEST("require that error type combined with anything produces error type") {
+ verify_not_joinable(type("error"), type("error"));
+ verify_not_joinable(type("error"), type("double"));
+ verify_not_joinable(type("error"), type("tensor(x{})"));
+ verify_not_joinable(type("error"), type("tensor(x[10])"));
+}
+
TEST("require that tensor dimensions can be renamed") {
- EXPECT_EQUAL(ValueType::from_spec("tensor(x{})").rename({"x"}, {"y"}),
- ValueType::from_spec("tensor(y{})"));
- EXPECT_EQUAL(ValueType::from_spec("tensor(x{},y[5])").rename({"x","y"}, {"y","x"}),
- ValueType::from_spec("tensor(y{},x[5])"));
- EXPECT_EQUAL(ValueType::from_spec("tensor(x{})").rename({"x"}, {"x"}),
- ValueType::from_spec("tensor(x{})"));
- EXPECT_EQUAL(ValueType::from_spec("tensor(x{})").rename({}, {}), ValueType::error_type());
- EXPECT_EQUAL(ValueType::double_type().rename({}, {}), ValueType::error_type());
- EXPECT_EQUAL(ValueType::from_spec("tensor(x{},y{})").rename({"x"}, {"y","z"}), ValueType::error_type());
- EXPECT_EQUAL(ValueType::from_spec("tensor(x{},y{})").rename({"x","y"}, {"z"}), ValueType::error_type());
- EXPECT_EQUAL(ValueType::double_type().rename({"a"}, {"b"}), ValueType::error_type());
- EXPECT_EQUAL(ValueType::error_type().rename({"a"}, {"b"}), ValueType::error_type());
+ EXPECT_EQUAL(type("tensor(x{})").rename({"x"}, {"y"}), type("tensor(y{})"));
+ EXPECT_EQUAL(type("tensor(x{},y[5])").rename({"x","y"}, {"y","x"}), type("tensor(y{},x[5])"));
+ EXPECT_EQUAL(type("tensor(x{})").rename({"x"}, {"x"}), type("tensor(x{})"));
+ EXPECT_EQUAL(type("tensor(x{})").rename({}, {}), type("error"));
+ EXPECT_EQUAL(type("double").rename({}, {}), type("error"));
+ EXPECT_EQUAL(type("tensor(x{},y{})").rename({"x"}, {"y","z"}), type("error"));
+ EXPECT_EQUAL(type("tensor(x{},y{})").rename({"x","y"}, {"z"}), type("error"));
+ EXPECT_EQUAL(type("double").rename({"a"}, {"b"}), type("error"));
+ EXPECT_EQUAL(type("error").rename({"a"}, {"b"}), type("error"));
+}
+
+void verify_concat(const ValueType &a, const ValueType b, const vespalib::string &dim, const ValueType &res) {
+ EXPECT_EQUAL(ValueType::concat(a, b, dim), res);
+ EXPECT_EQUAL(ValueType::concat(b, a, dim), res);
}
TEST("require that types can be concatenated") {
- ValueType error = ValueType::error_type();
- ValueType scalar = ValueType::double_type();
- ValueType vx_2 = ValueType::from_spec("tensor(x[2])");
- ValueType vx_m = ValueType::from_spec("tensor(x{})");
- ValueType vx_3 = ValueType::from_spec("tensor(x[3])");
- ValueType vx_5 = ValueType::from_spec("tensor(x[5])");
- ValueType vy_7 = ValueType::from_spec("tensor(y[7])");
- ValueType mxy_22 = ValueType::from_spec("tensor(x[2],y[2])");
- ValueType mxy_52 = ValueType::from_spec("tensor(x[5],y[2])");
- ValueType mxy_29 = ValueType::from_spec("tensor(x[2],y[9])");
- ValueType cxyz_572 = ValueType::from_spec("tensor(x[5],y[7],z[2])");
- ValueType cxyz_m72 = ValueType::from_spec("tensor(x{},y[7],z[2])");
-
- EXPECT_EQUAL(ValueType::concat(error, vx_2, "x"), error);
- EXPECT_EQUAL(ValueType::concat(vx_2, error, "x"), error);
- EXPECT_EQUAL(ValueType::concat(vx_m, vx_2, "x"), error);
- EXPECT_EQUAL(ValueType::concat(vx_2, vx_m, "x"), error);
- EXPECT_EQUAL(ValueType::concat(vx_m, vx_m, "x"), error);
- EXPECT_EQUAL(ValueType::concat(vx_m, scalar, "x"), error);
- EXPECT_EQUAL(ValueType::concat(scalar, vx_m, "x"), error);
- EXPECT_EQUAL(ValueType::concat(vx_2, vx_3, "y"), error);
- EXPECT_EQUAL(ValueType::concat(vy_7, vx_m, "z"), cxyz_m72);
- EXPECT_EQUAL(ValueType::concat(scalar, scalar, "x"), vx_2);
- EXPECT_EQUAL(ValueType::concat(vx_2, scalar, "x"), vx_3);
- EXPECT_EQUAL(ValueType::concat(scalar, vx_2, "x"), vx_3);
- EXPECT_EQUAL(ValueType::concat(vx_2, vx_3, "x"), vx_5);
- EXPECT_EQUAL(ValueType::concat(scalar, vx_2, "y"), mxy_22);
- EXPECT_EQUAL(ValueType::concat(vx_2, scalar, "y"), mxy_22);
- EXPECT_EQUAL(ValueType::concat(vx_2, vx_2, "y"), mxy_22);
- EXPECT_EQUAL(ValueType::concat(mxy_22, vx_3, "x"), mxy_52);
- EXPECT_EQUAL(ValueType::concat(vx_3, mxy_22, "x"), mxy_52);
- EXPECT_EQUAL(ValueType::concat(mxy_22, vy_7, "y"), mxy_29);
- EXPECT_EQUAL(ValueType::concat(vy_7, mxy_22, "y"), mxy_29);
- EXPECT_EQUAL(ValueType::concat(vx_5, vy_7, "z"), cxyz_572);
+ TEST_DO(verify_concat(type("error"), type("tensor(x[2])"), "x", type("error")));
+ TEST_DO(verify_concat(type("tensor(x{})"), type("tensor(x[2])"), "x", type("error")));
+ TEST_DO(verify_concat(type("tensor(x{})"), type("tensor(x{})"), "x", type("error")));
+ TEST_DO(verify_concat(type("tensor(x{})"), type("double"), "x", type("error")));
+ TEST_DO(verify_concat(type("tensor(x[3])"), type("tensor(x[2])"), "y", type("error")));
+ TEST_DO(verify_concat(type("tensor(y[7])"), type("tensor(x{})"), "z", type("tensor(x{},y[7],z[2])")));
+ TEST_DO(verify_concat(type("double"), type("double"), "x", type("tensor(x[2])")));
+ TEST_DO(verify_concat(type("tensor(x[2])"), type("double"), "x", type("tensor(x[3])")));
+ TEST_DO(verify_concat(type("tensor(x[3])"), type("tensor(x[2])"), "x", type("tensor(x[5])")));
+ TEST_DO(verify_concat(type("tensor(x[2])"), type("double"), "y", type("tensor(x[2],y[2])")));
+ TEST_DO(verify_concat(type("tensor(x[2])"), type("tensor(x[2])"), "y", type("tensor(x[2],y[2])")));
+ TEST_DO(verify_concat(type("tensor(x[2],y[2])"), type("tensor(x[3])"), "x", type("tensor(x[5],y[2])")));
+ TEST_DO(verify_concat(type("tensor(x[2],y[2])"), type("tensor(y[7])"), "y", type("tensor(x[2],y[9])")));
+ TEST_DO(verify_concat(type("tensor(x[5])"), type("tensor(y[7])"), "z", type("tensor(x[5],y[7],z[2])")));
+}
+
+TEST("require that cell type is handled correctly for concat") {
+ TEST_DO(verify_concat(type("tensor<float>(x[3])"), type("tensor(x[2])"), "x", type("tensor(x[5])")));
+ TEST_DO(verify_concat(type("tensor<float>(x[3])"), type("tensor<float>(x[2])"), "x", type("tensor<float>(x[5])")));
+ TEST_DO(verify_concat(type("tensor<float>(x[3])"), type("double"), "x", type("tensor<float>(x[4])")));
}
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/eval/src/vespa/eval/eval/node_types.cpp b/eval/src/vespa/eval/eval/node_types.cpp
index 94e69aadf55..29ae02b9e65 100644
--- a/eval/src/vespa/eval/eval/node_types.cpp
+++ b/eval/src/vespa/eval/eval/node_types.cpp
@@ -4,8 +4,7 @@
#include "node_traverser.h"
#include "node_types.h"
-namespace vespalib {
-namespace eval {
+namespace vespalib::eval {
namespace nodes {
namespace {
@@ -208,5 +207,4 @@ NodeTypes::get_type(const nodes::Node &node) const
return pos->second;
}
-} // namespace vespalib::eval
-} // namespace vespalib
+}
diff --git a/eval/src/vespa/eval/eval/value_type.cpp b/eval/src/vespa/eval/eval/value_type.cpp
index 269f17b71c5..fc0f3cc5414 100644
--- a/eval/src/vespa/eval/eval/value_type.cpp
+++ b/eval/src/vespa/eval/eval/value_type.cpp
@@ -8,9 +8,27 @@ namespace vespalib::eval {
namespace {
+using CellType = ValueType::CellType;
using Dimension = ValueType::Dimension;
using DimensionList = std::vector<Dimension>;
+CellType unify(CellType a, CellType b) {
+ if (a == b) {
+ return a;
+ } else {
+ return CellType::DOUBLE;
+ }
+}
+
+CellType unify_cell_type(const ValueType &a, const ValueType &b) {
+ if (a.is_double()) {
+ return b.cell_type();
+ } else if (b.is_double()) {
+ return a.cell_type();
+ }
+ return unify(a.cell_type(), b.cell_type());
+}
+
size_t my_dimension_index(const std::vector<Dimension> &list, const vespalib::string &name) {
for (size_t idx = 0; idx < list.size(); ++idx) {
if (list[idx].name == name) {
@@ -184,7 +202,7 @@ ValueType::reduce(const std::vector<vespalib::string> &dimensions_in) const
if (removed != dimensions_in.size()) {
return error_type();
}
- return tensor_type(std::move(result));
+ return tensor_type(std::move(result), _cell_type);
}
ValueType
@@ -202,11 +220,11 @@ ValueType::rename(const std::vector<vespalib::string> &from,
if (!renamer.matched_all()) {
return error_type();
}
- return tensor_type(dim_list);
+ return tensor_type(dim_list, _cell_type);
}
ValueType
-ValueType::tensor_type(std::vector<Dimension> dimensions_in)
+ValueType::tensor_type(std::vector<Dimension> dimensions_in, CellType cell_type)
{
if (dimensions_in.empty()) {
return double_type();
@@ -215,7 +233,7 @@ ValueType::tensor_type(std::vector<Dimension> dimensions_in)
if (!verify_dimensions(dimensions_in)) {
return error_type();
}
- return ValueType(Type::TENSOR, std::move(dimensions_in));
+ return ValueType(Type::TENSOR, cell_type, std::move(dimensions_in));
}
ValueType
@@ -244,7 +262,7 @@ ValueType::join(const ValueType &lhs, const ValueType &rhs)
if (result.mismatch) {
return error_type();
}
- return tensor_type(std::move(result.dimensions));
+ return tensor_type(std::move(result.dimensions), unify(lhs._cell_type, rhs._cell_type));
}
ValueType
@@ -260,12 +278,11 @@ ValueType::concat(const ValueType &lhs, const ValueType &rhs, const vespalib::st
if (!find_dimension(result.dimensions, dimension)) {
result.dimensions.emplace_back(dimension, 2);
}
- return tensor_type(std::move(result.dimensions));
+ return tensor_type(std::move(result.dimensions), unify_cell_type(lhs, rhs));
}
ValueType
-ValueType::either(const ValueType &one, const ValueType &other)
-{
+ValueType::either(const ValueType &one, const ValueType &other) {
if (one != other) {
return error_type();
}
diff --git a/eval/src/vespa/eval/eval/value_type.h b/eval/src/vespa/eval/eval/value_type.h
index 6e30a5c0a47..81788c933d7 100644
--- a/eval/src/vespa/eval/eval/value_type.h
+++ b/eval/src/vespa/eval/eval/value_type.h
@@ -16,6 +16,7 @@ class ValueType
{
public:
enum class Type { ERROR, DOUBLE, TENSOR };
+ enum class CellType { FLOAT, DOUBLE };
struct Dimension {
using size_type = uint32_t;
static constexpr size_type npos = -1;
@@ -35,14 +36,15 @@ public:
};
private:
- Type _type;
+ Type _type;
+ CellType _cell_type;
std::vector<Dimension> _dimensions;
ValueType(Type type_in)
- : _type(type_in), _dimensions() {}
+ : _type(type_in), _cell_type(CellType::DOUBLE), _dimensions() {}
- ValueType(Type type_in, std::vector<Dimension> &&dimensions_in)
- : _type(type_in), _dimensions(std::move(dimensions_in)) {}
+ ValueType(Type type_in, CellType cell_type_in, std::vector<Dimension> &&dimensions_in)
+ : _type(type_in), _cell_type(cell_type_in), _dimensions(std::move(dimensions_in)) {}
public:
ValueType(ValueType &&) = default;
@@ -51,6 +53,7 @@ public:
ValueType &operator=(const ValueType &) = default;
~ValueType();
Type type() const { return _type; }
+ CellType cell_type() const { return _cell_type; }
bool is_error() const { return (_type == Type::ERROR); }
bool is_double() const { return (_type == Type::DOUBLE); }
bool is_tensor() const { return (_type == Type::TENSOR); }
@@ -60,7 +63,9 @@ public:
size_t dimension_index(const vespalib::string &name) const;
std::vector<vespalib::string> dimension_names() const;
bool operator==(const ValueType &rhs) const {
- return ((_type == rhs._type) && (_dimensions == rhs._dimensions));
+ return ((_type == rhs._type) &&
+ (_cell_type == rhs._cell_type) &&
+ (_dimensions == rhs._dimensions));
}
bool operator!=(const ValueType &rhs) const { return !(*this == rhs); }
@@ -70,7 +75,7 @@ public:
static ValueType error_type() { return ValueType(Type::ERROR); }
static ValueType double_type() { return ValueType(Type::DOUBLE); }
- static ValueType tensor_type(std::vector<Dimension> dimensions_in);
+ static ValueType tensor_type(std::vector<Dimension> dimensions_in, CellType cell_type = CellType::DOUBLE);
static ValueType from_spec(const vespalib::string &spec);
vespalib::string to_spec() const;
static ValueType join(const ValueType &lhs, const ValueType &rhs);
diff --git a/eval/src/vespa/eval/eval/value_type_spec.cpp b/eval/src/vespa/eval/eval/value_type_spec.cpp
index 737943f902e..bbfa6f4fa28 100644
--- a/eval/src/vespa/eval/eval/value_type_spec.cpp
+++ b/eval/src/vespa/eval/eval/value_type_spec.cpp
@@ -8,8 +8,18 @@
namespace vespalib::eval::value_type {
+using CellType = ValueType::CellType;
+
namespace {
+const char *to_name(CellType cell_type) {
+ switch (cell_type) {
+ case CellType::DOUBLE: return "double";
+ case CellType::FLOAT: return "float";
+ }
+ abort();
+}
+
class ParseContext
{
public:
@@ -130,23 +140,21 @@ ValueType::Dimension parse_dimension(ParseContext &ctx) {
std::vector<ValueType::Dimension> parse_dimension_list(ParseContext &ctx) {
std::vector<ValueType::Dimension> list;
ctx.skip_spaces();
- if (ctx.get() == '(') {
- ctx.eat('(');
- ctx.skip_spaces();
- while (!ctx.eos() && (ctx.get() != ')')) {
- if (!list.empty()) {
- ctx.eat(',');
- }
- list.push_back(parse_dimension(ctx));
- ctx.skip_spaces();
+ ctx.eat('(');
+ ctx.skip_spaces();
+ while (!ctx.eos() && (ctx.get() != ')')) {
+ if (!list.empty()) {
+ ctx.eat(',');
}
- ctx.eat(')');
+ list.push_back(parse_dimension(ctx));
+ ctx.skip_spaces();
}
+ ctx.eat(')');
ctx.skip_spaces();
return list;
}
-vespalib::string parse_cell_type(ParseContext &ctx) {
+CellType parse_cell_type(ParseContext &ctx) {
auto mark = ctx.mark();
ctx.skip_spaces();
ctx.eat('<');
@@ -155,9 +163,14 @@ vespalib::string parse_cell_type(ParseContext &ctx) {
ctx.eat('>');
if (ctx.failed()) {
ctx.revert(mark);
- cell_type = "double";
+ return CellType::DOUBLE;
+ }
+ if (cell_type == "float") {
+ return CellType::FLOAT;
+ } else if (cell_type != "double") {
+ ctx.fail();
}
- return cell_type;
+ return CellType::DOUBLE;
}
} // namespace vespalib::eval::value_type::<anonymous>
@@ -172,13 +185,10 @@ parse_spec(const char *pos_in, const char *end_in, const char *&pos_out)
} else if (type_name == "double") {
return ValueType::double_type();
} else if (type_name == "tensor") {
- vespalib::string cell_type = parse_cell_type(ctx);
- if ((cell_type != "double") && (cell_type != "float")) {
- ctx.fail();
- }
+ ValueType::CellType cell_type = parse_cell_type(ctx);
std::vector<ValueType::Dimension> list = parse_dimension_list(ctx);
if (!ctx.failed()) {
- return ValueType::tensor_type(std::move(list));
+ return ValueType::tensor_type(std::move(list), cell_type);
}
} else {
ctx.fail();
@@ -212,22 +222,21 @@ to_spec(const ValueType &type)
break;
case ValueType::Type::TENSOR:
os << "tensor";
- if (!type.dimensions().empty()) {
- os << "(";
- for (const auto &d: type.dimensions()) {
- if (cnt++ > 0) {
- os << ",";
- }
- if (d.size == ValueType::Dimension::npos) {
- os << d.name << "{}";
- } else if (d.size == 0) {
- os << d.name << "[]";
- } else {
- os << d.name << "[" << d.size << "]";
- }
+ if (type.cell_type() != CellType::DOUBLE) {
+ os << "<" << to_name(type.cell_type()) << ">";
+ }
+ os << "(";
+ for (const auto &d: type.dimensions()) {
+ if (cnt++ > 0) {
+ os << ",";
+ }
+ if (d.size == ValueType::Dimension::npos) {
+ os << d.name << "{}";
+ } else {
+ os << d.name << "[" << d.size << "]";
}
- os << ")";
}
+ os << ")";
break;
}
return os.str();