diff options
author | Håvard Pettersen <havardpe@oath.com> | 2019-05-02 11:15:24 +0000 |
---|---|---|
committer | Håvard Pettersen <havardpe@oath.com> | 2019-05-03 11:22:02 +0000 |
commit | b915a61385e9e6d3b686fa949a76b763f89dbbe3 (patch) | |
tree | 4386ba8494a3d8a643b3fc179d5e4d33b533e219 /eval | |
parent | e2ef6e41a4fedb4b02cec4946e37f28b1b5d8dd8 (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.cpp | 2 | ||||
-rw-r--r-- | eval/src/tests/eval/value_type/value_type_test.cpp | 405 | ||||
-rw-r--r-- | eval/src/vespa/eval/eval/node_types.cpp | 6 | ||||
-rw-r--r-- | eval/src/vespa/eval/eval/value_type.cpp | 33 | ||||
-rw-r--r-- | eval/src/vespa/eval/eval/value_type.h | 17 | ||||
-rw-r--r-- | eval/src/vespa/eval/eval/value_type_spec.cpp | 73 |
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(); |