summaryrefslogtreecommitdiffstats
path: root/eval
diff options
context:
space:
mode:
authorHaavard <havardpe@yahoo-inc.com>2017-05-09 17:26:21 +0000
committerHaavard <havardpe@yahoo-inc.com>2017-05-10 15:22:31 +0000
commit2144879b22fdd47972a53009401eb6061648887a (patch)
treecf9599e0b852316e4dac37c9d5c7951d2b0a574b /eval
parent70ab982ead81e15260e16a1448ce027a50aad875 (diff)
test tensor binary format
Diffstat (limited to 'eval')
-rw-r--r--eval/CMakeLists.txt2
-rw-r--r--eval/src/apps/make_tensor_binary_format_test_spec/.gitignore1
-rw-r--r--eval/src/apps/make_tensor_binary_format_test_spec/CMakeLists.txt7
-rw-r--r--eval/src/apps/make_tensor_binary_format_test_spec/make_tensor_binary_format_test_spec.cpp320
-rw-r--r--eval/src/apps/make_tensor_binary_format_test_spec/test_spec.json336
-rw-r--r--eval/src/tests/eval/tensor_spec/CMakeLists.txt8
-rw-r--r--eval/src/tests/eval/tensor_spec/tensor_spec_test.cpp22
-rw-r--r--eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp7
-rw-r--r--eval/src/vespa/eval/eval/tensor_spec.cpp54
-rw-r--r--eval/src/vespa/eval/eval/tensor_spec.h10
-rw-r--r--eval/src/vespa/eval/eval/test/tensor_conformance.cpp99
-rw-r--r--eval/src/vespa/eval/eval/test/tensor_conformance.h3
12 files changed, 862 insertions, 7 deletions
diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt
index 79998163249..d4528cc8d28 100644
--- a/eval/CMakeLists.txt
+++ b/eval/CMakeLists.txt
@@ -5,6 +5,7 @@ vespa_define_module(
APPS
src/apps/eval_expr
+ src/apps/make_tensor_binary_format_test_spec
TESTS
src/tests/eval/aggr
@@ -18,6 +19,7 @@ vespa_define_module(
src/tests/eval/param_usage
src/tests/eval/simple_tensor
src/tests/eval/tensor_function
+ src/tests/eval/tensor_spec
src/tests/eval/value_cache
src/tests/eval/value_type
src/tests/tensor/dense_dot_product_function
diff --git a/eval/src/apps/make_tensor_binary_format_test_spec/.gitignore b/eval/src/apps/make_tensor_binary_format_test_spec/.gitignore
new file mode 100644
index 00000000000..aff1914d6bf
--- /dev/null
+++ b/eval/src/apps/make_tensor_binary_format_test_spec/.gitignore
@@ -0,0 +1 @@
+/eval_make_tensor_binary_format_test_spec_app
diff --git a/eval/src/apps/make_tensor_binary_format_test_spec/CMakeLists.txt b/eval/src/apps/make_tensor_binary_format_test_spec/CMakeLists.txt
new file mode 100644
index 00000000000..b12bd919124
--- /dev/null
+++ b/eval/src/apps/make_tensor_binary_format_test_spec/CMakeLists.txt
@@ -0,0 +1,7 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(eval_make_tensor_binary_format_test_spec_app
+ SOURCES
+ make_tensor_binary_format_test_spec.cpp
+ DEPENDS
+ vespaeval
+)
diff --git a/eval/src/apps/make_tensor_binary_format_test_spec/make_tensor_binary_format_test_spec.cpp b/eval/src/apps/make_tensor_binary_format_test_spec/make_tensor_binary_format_test_spec.cpp
new file mode 100644
index 00000000000..4e274aa513d
--- /dev/null
+++ b/eval/src/apps/make_tensor_binary_format_test_spec/make_tensor_binary_format_test_spec.cpp
@@ -0,0 +1,320 @@
+// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/data/slime/slime.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/eval/eval/tensor_spec.h>
+#include <vespa/eval/eval/value_type.h>
+#include <iostream>
+
+using namespace vespalib;
+using namespace vespalib::eval;
+using namespace vespalib::slime::convenience;
+
+using Opts = std::initializer_list<std::reference_wrapper<const nbostream>>;
+using Dict = std::vector<vespalib::string>;
+
+//-----------------------------------------------------------------------------
+
+nbostream make_sparse() {
+ nbostream data;
+ data << uint8_t(0x1);
+ return data;
+}
+
+nbostream make_dense() {
+ nbostream data;
+ data << uint8_t(0x2);
+ return data;
+}
+
+nbostream make_mixed() {
+ nbostream data;
+ data << uint8_t(0x3);
+ return data;
+}
+
+void set_tensor(Cursor &test, const TensorSpec &spec) {
+ const Inspector &old_tensor = test["tensor"];
+ if (old_tensor.valid()) {
+ TensorSpec old_spec = TensorSpec::from_slime(old_tensor);
+ if (!(old_spec == spec)) {
+ fprintf(stderr, "inconsistent specs\n");
+ std::cerr << old_spec;
+ std::cerr << spec;
+ abort(); // inconsistent specs across binary permutations
+ }
+ } else {
+ Cursor &tensor = test.setObject("tensor");
+ spec.to_slime(tensor);
+ }
+}
+
+void add_binary(Cursor &test, const nbostream &data) {
+ if (!test["binary"].valid()) {
+ test.setArray("binary");
+ }
+ test["binary"].addData(Memory(data.peek(), data.size()));
+}
+
+void add_binary(Cursor &test, Opts opts) {
+ for (const nbostream &opt: opts) {
+ add_binary(test, opt);
+ }
+}
+
+std::vector<Dict> perm(const Dict &dict) {
+ std::vector<Dict> list;
+ if (dict.empty()) {
+ } else if (dict.size() == 1) {
+ list.push_back(dict);
+ } else if (dict.size() == 2) {
+ list.push_back({dict[0], dict[1]});
+ list.push_back({dict[1], dict[0]});
+ } else if (dict.size() == 3) {
+ list.push_back({dict[0], dict[1], dict[2]});
+ list.push_back({dict[0], dict[2], dict[1]});
+ list.push_back({dict[1], dict[0], dict[2]});
+ list.push_back({dict[1], dict[2], dict[0]});
+ list.push_back({dict[2], dict[0], dict[1]});
+ list.push_back({dict[2], dict[1], dict[0]});
+ } else {
+ fprintf(stderr, "unsupported permutation size: %zu\n", dict.size());
+ abort(); // perm only implemented for sizes (0,1,2,3)
+ }
+ return list;
+};
+
+const std::map<std::string, double> val_map{
+ {"a", 1.0},
+ {"b", 2.0},
+ {"c", 3.0},
+ {"foo", 1.0},
+ {"bar", 2.0}};
+
+double val(size_t idx) { return double(idx + 1); }
+double val(const vespalib::string &label) {
+ auto res = val_map.find(label);
+ if (res == val_map.end()) {
+ fprintf(stderr, "unsupported label: '%s'\n", label.c_str());
+ abort(); // unsupported label
+ }
+ return res->second;
+}
+double mix(std::initializer_list<double> vals) {
+ double value = 0.0;
+ for (double val: vals) {
+ value = ((value * 10) + val);
+ }
+ return value;
+}
+
+//-----------------------------------------------------------------------------
+
+void make_number_test(Cursor &test, double value) {
+ TensorSpec spec("double");
+ spec.add({{}}, value);
+ nbostream sparse = make_sparse();
+ sparse.putInt1_4Bytes(0);
+ sparse.putInt1_4Bytes(1);
+ sparse << value;
+ nbostream dense = make_dense();
+ dense.putInt1_4Bytes(0);
+ dense << value;
+ nbostream mixed = make_mixed();
+ mixed.putInt1_4Bytes(0);
+ mixed.putInt1_4Bytes(0);
+ mixed << value;
+ set_tensor(test, spec);
+ add_binary(test, {sparse, dense, mixed});
+ if (value == 0.0) {
+ nbostream empty = make_sparse();
+ empty.putInt1_4Bytes(0);
+ empty.putInt1_4Bytes(0);
+ add_binary(test, empty);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void make_vector_test(Cursor &test, size_t x_size) {
+ TensorSpec spec(vespalib::make_string("tensor(x[%zu])", x_size));
+ nbostream dense = make_dense();
+ dense.putInt1_4Bytes(1);
+ dense.writeSmallString("x");
+ dense.putInt1_4Bytes(x_size);
+ nbostream mixed = make_mixed();
+ mixed.putInt1_4Bytes(0);
+ mixed.putInt1_4Bytes(1);
+ mixed.writeSmallString("x");
+ mixed.putInt1_4Bytes(x_size);
+ for (size_t x = 0; x < x_size; ++x) {
+ double value = val(x);
+ spec.add({{"x", x}}, value);
+ dense << value;
+ mixed << value;
+ }
+ set_tensor(test, spec);
+ add_binary(test, {dense, mixed});
+}
+
+void make_matrix_test(Cursor &test, size_t x_size, size_t y_size) {
+ TensorSpec spec(vespalib::make_string("tensor(x[%zu],y[%zu])", x_size, y_size));
+ nbostream dense = make_dense();
+ dense.putInt1_4Bytes(2);
+ dense.writeSmallString("x");
+ dense.putInt1_4Bytes(x_size);
+ dense.writeSmallString("y");
+ dense.putInt1_4Bytes(y_size);
+ nbostream mixed = make_mixed();
+ mixed.putInt1_4Bytes(0);
+ mixed.putInt1_4Bytes(2);
+ mixed.writeSmallString("x");
+ mixed.putInt1_4Bytes(x_size);
+ mixed.writeSmallString("y");
+ mixed.putInt1_4Bytes(y_size);
+ for (size_t x = 0; x < x_size; ++x) {
+ for (size_t y = 0; y < y_size; ++y) {
+ double value = mix({val(x), val(y)});
+ spec.add({{"x", x}, {"y", y}}, value);
+ dense << value;
+ mixed << value;
+ }
+ }
+ set_tensor(test, spec);
+ add_binary(test, {dense, mixed});
+}
+
+//-----------------------------------------------------------------------------
+
+void make_map_test(Cursor &test, const Dict &x_dict_in) {
+ TensorSpec spec("tensor(x{})");
+ nbostream sparse_base = make_sparse();
+ sparse_base.putInt1_4Bytes(1);
+ sparse_base.writeSmallString("x");
+ sparse_base.putInt1_4Bytes(x_dict_in.size());
+ nbostream mixed_base = make_mixed();
+ mixed_base.putInt1_4Bytes(1);
+ mixed_base.writeSmallString("x");
+ mixed_base.putInt1_4Bytes(0);
+ mixed_base.putInt1_4Bytes(x_dict_in.size());
+ auto x_perm = perm(x_dict_in);
+ for (const Dict &x_dict: x_perm) {
+ nbostream sparse = sparse_base;
+ nbostream mixed = mixed_base;
+ for (vespalib::string x: x_dict) {
+ double value = val(x);
+ spec.add({{"x", x}}, value);
+ sparse.writeSmallString(x);
+ mixed.writeSmallString(x);
+ sparse << value;
+ mixed << value;
+ }
+ set_tensor(test, spec);
+ add_binary(test, {sparse, mixed});
+ }
+ if (x_dict_in.empty()) {
+ set_tensor(test, spec);
+ add_binary(test, {sparse_base, mixed_base});
+ }
+}
+
+void make_mesh_test(Cursor &test, const Dict &x_dict_in, const vespalib::string &y) {
+ TensorSpec spec("tensor(x{},y{})");
+ nbostream sparse_base = make_sparse();
+ sparse_base.putInt1_4Bytes(2);
+ sparse_base.writeSmallString("x");
+ sparse_base.writeSmallString("y");
+ sparse_base.putInt1_4Bytes(x_dict_in.size() * 1);
+ nbostream mixed_base = make_mixed();
+ mixed_base.putInt1_4Bytes(2);
+ mixed_base.writeSmallString("x");
+ mixed_base.writeSmallString("y");
+ mixed_base.putInt1_4Bytes(0);
+ mixed_base.putInt1_4Bytes(x_dict_in.size() * 1);
+ auto x_perm = perm(x_dict_in);
+ for (const Dict &x_dict: x_perm) {
+ nbostream sparse = sparse_base;
+ nbostream mixed = mixed_base;
+ for (vespalib::string x: x_dict) {
+ double value = mix({val(x), val(y)});
+ spec.add({{"x", x}, {"y", y}}, value);
+ sparse.writeSmallString(x);
+ sparse.writeSmallString(y);
+ mixed.writeSmallString(x);
+ mixed.writeSmallString(y);
+ sparse << value;
+ mixed << value;
+ }
+ set_tensor(test, spec);
+ add_binary(test, {sparse, mixed});
+ }
+ if (x_dict_in.empty()) {
+ set_tensor(test, spec);
+ add_binary(test, {sparse_base, mixed_base});
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void make_vector_map_test(Cursor &test,
+ const vespalib::string &mapped_name, const Dict &mapped_dict,
+ const vespalib::string &indexed_name, size_t indexed_size)
+{
+ auto type_str = vespalib::make_string("tensor(%s{},%s[%zu])",
+ mapped_name.c_str(), indexed_name.c_str(), indexed_size);
+ ValueType type = ValueType::from_spec(type_str);
+ TensorSpec spec(type.to_spec()); // ensures type string is normalized
+ nbostream mixed_base = make_mixed();
+ mixed_base.putInt1_4Bytes(1);
+ mixed_base.writeSmallString(mapped_name);
+ mixed_base.putInt1_4Bytes(1);
+ mixed_base.writeSmallString(indexed_name);
+ mixed_base.putInt1_4Bytes(indexed_size);
+ mixed_base.putInt1_4Bytes(mapped_dict.size());
+ auto mapped_perm = perm(mapped_dict);
+ for (const Dict &dict: mapped_perm) {
+ nbostream mixed = mixed_base;
+ for (vespalib::string label: dict) {
+ mixed.writeSmallString(label);
+ for (size_t idx = 0; idx < indexed_size; ++idx) {
+ double value = mix({val(label), val(idx)});
+ spec.add({{mapped_name, label}, {indexed_name, idx}}, value);
+ mixed << value;
+ }
+ }
+ set_tensor(test, spec);
+ add_binary(test, mixed);
+ }
+ if (mapped_dict.empty()) {
+ set_tensor(test, spec);
+ add_binary(test, mixed_base);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void make_tests(Cursor &tests) {
+ make_number_test(tests.addObject(), 0.0);
+ make_number_test(tests.addObject(), 42.0);
+ make_vector_test(tests.addObject(), 3);
+ make_matrix_test(tests.addObject(), 2, 3);
+ make_map_test(tests.addObject(), {});
+ make_map_test(tests.addObject(), {"a", "b", "c"});
+ make_mesh_test(tests.addObject(), {}, "a");
+ make_mesh_test(tests.addObject(), {"foo", "bar"}, "a");
+ make_vector_map_test(tests.addObject(), "x", {}, "y", 10);
+ make_vector_map_test(tests.addObject(), "y", {}, "x", 10);
+ make_vector_map_test(tests.addObject(), "x", {"a", "b"}, "y", 3);
+ make_vector_map_test(tests.addObject(), "y", {"a", "b"}, "x", 3);
+}
+
+int main(int, char **) {
+ Slime slime;
+ Cursor &top = slime.setObject();
+ Cursor &tests = top.setArray("tests");
+ make_tests(tests);
+ top.setLong("num_tests", tests.entries());
+ fprintf(stdout, "%s", slime.toString().c_str());
+ return 0;
+}
diff --git a/eval/src/apps/make_tensor_binary_format_test_spec/test_spec.json b/eval/src/apps/make_tensor_binary_format_test_spec/test_spec.json
new file mode 100644
index 00000000000..1b74b4b8838
--- /dev/null
+++ b/eval/src/apps/make_tensor_binary_format_test_spec/test_spec.json
@@ -0,0 +1,336 @@
+{
+ "tests": [
+ {
+ "tensor": {
+ "type": "double",
+ "cells": [
+ {
+ "address": {
+ },
+ "value": 0
+ }
+ ]
+ },
+ "binary": [
+ "0x0100010000000000000000",
+ "0x02000000000000000000",
+ "0x0300000000000000000000",
+ "0x010000"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "double",
+ "cells": [
+ {
+ "address": {
+ },
+ "value": 42
+ }
+ ]
+ },
+ "binary": [
+ "0x0100014045000000000000",
+ "0x02004045000000000000",
+ "0x0300004045000000000000"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x[3])",
+ "cells": [
+ {
+ "address": {
+ "x": 0
+ },
+ "value": 1
+ },
+ {
+ "address": {
+ "x": 1
+ },
+ "value": 2
+ },
+ {
+ "address": {
+ "x": 2
+ },
+ "value": 3
+ }
+ ]
+ },
+ "binary": [
+ "0x02010178033FF000000000000040000000000000004008000000000000",
+ "0x0300010178033FF000000000000040000000000000004008000000000000"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x[2],y[3])",
+ "cells": [
+ {
+ "address": {
+ "x": 0,
+ "y": 0
+ },
+ "value": 11
+ },
+ {
+ "address": {
+ "x": 0,
+ "y": 1
+ },
+ "value": 12
+ },
+ {
+ "address": {
+ "x": 0,
+ "y": 2
+ },
+ "value": 13
+ },
+ {
+ "address": {
+ "x": 1,
+ "y": 0
+ },
+ "value": 21
+ },
+ {
+ "address": {
+ "x": 1,
+ "y": 1
+ },
+ "value": 22
+ },
+ {
+ "address": {
+ "x": 1,
+ "y": 2
+ },
+ "value": 23
+ }
+ ]
+ },
+ "binary": [
+ "0x020201780201790340260000000000004028000000000000402A000000000000403500000000000040360000000000004037000000000000",
+ "0x03000201780201790340260000000000004028000000000000402A000000000000403500000000000040360000000000004037000000000000"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x{})",
+ "cells": [
+ ]
+ },
+ "binary": [
+ "0x0101017800",
+ "0x030101780000"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x{})",
+ "cells": [
+ {
+ "address": {
+ "x": "a"
+ },
+ "value": 1
+ },
+ {
+ "address": {
+ "x": "b"
+ },
+ "value": 2
+ },
+ {
+ "address": {
+ "x": "c"
+ },
+ "value": 3
+ }
+ ]
+ },
+ "binary": [
+ "0x010101780301613FF00000000000000162400000000000000001634008000000000000",
+ "0x03010178000301613FF00000000000000162400000000000000001634008000000000000",
+ "0x010101780301613FF00000000000000163400800000000000001624000000000000000",
+ "0x03010178000301613FF00000000000000163400800000000000001624000000000000000",
+ "0x01010178030162400000000000000001613FF000000000000001634008000000000000",
+ "0x0301017800030162400000000000000001613FF000000000000001634008000000000000",
+ "0x0101017803016240000000000000000163400800000000000001613FF0000000000000",
+ "0x030101780003016240000000000000000163400800000000000001613FF0000000000000",
+ "0x01010178030163400800000000000001613FF000000000000001624000000000000000",
+ "0x0301017800030163400800000000000001613FF000000000000001624000000000000000",
+ "0x0101017803016340080000000000000162400000000000000001613FF0000000000000",
+ "0x030101780003016340080000000000000162400000000000000001613FF0000000000000"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x{},y{})",
+ "cells": [
+ ]
+ },
+ "binary": [
+ "0x01020178017900",
+ "0x0302017801790000"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x{},y{})",
+ "cells": [
+ {
+ "address": {
+ "x": "bar",
+ "y": "a"
+ },
+ "value": 21
+ },
+ {
+ "address": {
+ "x": "foo",
+ "y": "a"
+ },
+ "value": 11
+ }
+ ]
+ },
+ "binary": [
+ "0x0102017801790203666F6F016140260000000000000362617201614035000000000000",
+ "0x030201780179000203666F6F016140260000000000000362617201614035000000000000",
+ "0x01020178017902036261720161403500000000000003666F6F01614026000000000000",
+ "0x0302017801790002036261720161403500000000000003666F6F01614026000000000000"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x{},y[10])",
+ "cells": [
+ ]
+ },
+ "binary": [
+ "0x030101780101790A00"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x[10],y{})",
+ "cells": [
+ ]
+ },
+ "binary": [
+ "0x030101790101780A00"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x{},y[3])",
+ "cells": [
+ {
+ "address": {
+ "x": "a",
+ "y": 0
+ },
+ "value": 11
+ },
+ {
+ "address": {
+ "x": "a",
+ "y": 1
+ },
+ "value": 12
+ },
+ {
+ "address": {
+ "x": "a",
+ "y": 2
+ },
+ "value": 13
+ },
+ {
+ "address": {
+ "x": "b",
+ "y": 0
+ },
+ "value": 21
+ },
+ {
+ "address": {
+ "x": "b",
+ "y": 1
+ },
+ "value": 22
+ },
+ {
+ "address": {
+ "x": "b",
+ "y": 2
+ },
+ "value": 23
+ }
+ ]
+ },
+ "binary": [
+ "0x030101780101790302016140260000000000004028000000000000402A0000000000000162403500000000000040360000000000004037000000000000",
+ "0x0301017801017903020162403500000000000040360000000000004037000000000000016140260000000000004028000000000000402A000000000000"
+ ]
+ },
+ {
+ "tensor": {
+ "type": "tensor(x[3],y{})",
+ "cells": [
+ {
+ "address": {
+ "x": 0,
+ "y": "a"
+ },
+ "value": 11
+ },
+ {
+ "address": {
+ "x": 0,
+ "y": "b"
+ },
+ "value": 21
+ },
+ {
+ "address": {
+ "x": 1,
+ "y": "a"
+ },
+ "value": 12
+ },
+ {
+ "address": {
+ "x": 1,
+ "y": "b"
+ },
+ "value": 22
+ },
+ {
+ "address": {
+ "x": 2,
+ "y": "a"
+ },
+ "value": 13
+ },
+ {
+ "address": {
+ "x": 2,
+ "y": "b"
+ },
+ "value": 23
+ }
+ ]
+ },
+ "binary": [
+ "0x030101790101780302016140260000000000004028000000000000402A0000000000000162403500000000000040360000000000004037000000000000",
+ "0x0301017901017803020162403500000000000040360000000000004037000000000000016140260000000000004028000000000000402A000000000000"
+ ]
+ }
+ ],
+ "num_tests": 12
+}
diff --git a/eval/src/tests/eval/tensor_spec/CMakeLists.txt b/eval/src/tests/eval/tensor_spec/CMakeLists.txt
new file mode 100644
index 00000000000..65e927f8b61
--- /dev/null
+++ b/eval/src/tests/eval/tensor_spec/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(eval_tensor_spec_test_app TEST
+ SOURCES
+ tensor_spec_test.cpp
+ DEPENDS
+ vespaeval
+)
+vespa_add_test(NAME eval_tensor_spec_test_app COMMAND eval_tensor_spec_test_app)
diff --git a/eval/src/tests/eval/tensor_spec/tensor_spec_test.cpp b/eval/src/tests/eval/tensor_spec/tensor_spec_test.cpp
new file mode 100644
index 00000000000..ecbaf292037
--- /dev/null
+++ b/eval/src/tests/eval/tensor_spec/tensor_spec_test.cpp
@@ -0,0 +1,22 @@
+// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/eval/eval/tensor_spec.h>
+#include <vespa/vespalib/data/slime/slime.h>
+
+using vespalib::Slime;
+using vespalib::eval::TensorSpec;
+
+TEST("require that a tensor spec can be converted to and from slime") {
+ TensorSpec spec("tensor(x[2],y{})");
+ spec.add({{"x", 0}, {"y", "xxx"}}, 1.0)
+ .add({{"x", 0}, {"y", "yyy"}}, 2.0)
+ .add({{"x", 1}, {"y", "xxx"}}, 3.0)
+ .add({{"x", 1}, {"y", "yyy"}}, 4.0);
+ Slime slime;
+ spec.to_slime(slime.setObject());
+ fprintf(stderr, "tensor spec as slime: \n%s\n", slime.get().toString().c_str());
+ EXPECT_EQUAL(TensorSpec::from_slime(slime.get()), spec);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp b/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp
index 1865f863e4d..4ba5e1180cd 100644
--- a/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp
+++ b/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp
@@ -8,12 +8,15 @@ using vespalib::eval::SimpleTensorEngine;
using vespalib::eval::test::TensorConformance;
using vespalib::tensor::DefaultTensorEngine;
+vespalib::string module_path(TEST_PATH("../../../../"));
+
+
TEST("require that reference tensor implementation passes all conformance tests") {
- TEST_DO(TensorConformance::run_tests(SimpleTensorEngine::ref(), true));
+ TEST_DO(TensorConformance::run_tests(module_path, SimpleTensorEngine::ref(), true));
}
IGNORE_TEST("require that production tensor implementation passes non-mixed conformance tests") {
- TEST_DO(TensorConformance::run_tests(DefaultTensorEngine::ref(), false));
+ TEST_DO(TensorConformance::run_tests(module_path, DefaultTensorEngine::ref(), false));
}
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/eval/src/vespa/eval/eval/tensor_spec.cpp b/eval/src/vespa/eval/eval/tensor_spec.cpp
index ac59f0d4424..9f07f5fe7b2 100644
--- a/eval/src/vespa/eval/eval/tensor_spec.cpp
+++ b/eval/src/vespa/eval/eval/tensor_spec.cpp
@@ -2,11 +2,33 @@
#include "tensor_spec.h"
#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/data/slime/slime.h>
#include <ostream>
namespace vespalib {
namespace eval {
+namespace {
+
+TensorSpec::Address extract_address(const slime::Inspector &address) {
+ struct Extractor : slime::ObjectTraverser {
+ TensorSpec::Address address;
+ void field(const Memory &dimension, const slime::Inspector &label) override {
+ if (label.type().getId() == slime::STRING::ID) {
+ address.emplace(dimension.make_string(), TensorSpec::Label(label.asString().make_string()));
+ } else if (label.type().getId() == slime::LONG::ID) {
+ address.emplace(dimension.make_string(), TensorSpec::Label(label.asLong()));
+ }
+ }
+ };
+ Extractor extractor;
+ address.traverse(extractor);
+ return extractor.address;
+}
+
+} // namespace vespalib::eval::<unnamed>
+
+
TensorSpec::TensorSpec(const vespalib::string &type_spec)
: _type(type_spec),
_cells()
@@ -40,6 +62,38 @@ TensorSpec::to_string() const
return out;
}
+void
+TensorSpec::to_slime(slime::Cursor &tensor) const
+{
+ tensor.setString("type", _type);
+ slime::Cursor &cells = tensor.setArray("cells");
+ for (const auto &my_cell: _cells) {
+ slime::Cursor &cell = cells.addObject();
+ slime::Cursor &address = cell.setObject("address");
+ for (const auto &label: my_cell.first) {
+ if (label.second.is_mapped()) {
+ address.setString(label.first, label.second.name);
+ } else {
+ address.setLong(label.first, label.second.index);
+ }
+ }
+ cell.setDouble("value", my_cell.second.value);
+ }
+}
+
+TensorSpec
+TensorSpec::from_slime(const slime::Inspector &tensor)
+{
+ TensorSpec spec(tensor["type"].asString().make_string());
+ const slime::Inspector &cells = tensor["cells"];
+ for (size_t i = 0; i < cells.entries(); ++i) {
+ const slime::Inspector &cell = cells[i];
+ Address address = extract_address(cell["address"]);
+ spec.add(address, cell["value"].asDouble());
+ }
+ return spec;
+}
+
bool
operator==(const TensorSpec &lhs, const TensorSpec &rhs)
{
diff --git a/eval/src/vespa/eval/eval/tensor_spec.h b/eval/src/vespa/eval/eval/tensor_spec.h
index 268b870aab9..27ca0060ecb 100644
--- a/eval/src/vespa/eval/eval/tensor_spec.h
+++ b/eval/src/vespa/eval/eval/tensor_spec.h
@@ -8,6 +8,14 @@
#include <map>
namespace vespalib {
+
+namespace slime {
+
+class Cursor;
+class Inspector;
+
+} // namespace vespalib::slime
+
namespace eval {
/**
@@ -65,6 +73,8 @@ public:
const vespalib::string &type() const { return _type; }
const Cells &cells() const { return _cells; }
vespalib::string to_string() const;
+ void to_slime(slime::Cursor &tensor) const;
+ static TensorSpec from_slime(const slime::Inspector &tensor);
};
bool operator==(const TensorSpec &lhs, const TensorSpec &rhs);
diff --git a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
index 74dfeaebe31..9adcf77a052 100644
--- a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
+++ b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
@@ -11,12 +11,18 @@
#include <vespa/eval/eval/interpreted_function.h>
#include <vespa/eval/eval/aggr.h>
#include <vespa/vespalib/objects/nbostream.h>
+#include <vespa/vespalib/data/slime/slime.h>
+#include <vespa/vespalib/io/mapped_file_input.h>
namespace vespalib {
namespace eval {
namespace test {
namespace {
+using slime::Cursor;
+using slime::Inspector;
+using slime::JsonFormat;
+
// Random access sequence of numbers
struct Sequence {
virtual double operator[](size_t i) const = 0;
@@ -598,16 +604,57 @@ void verify_result(const Eval::Result &result, const TensorSpec &expect) {
}
}
+uint8_t unhex(char c) {
+ if (c >= '0' && c <= '9') {
+ return (c - '0');
+ }
+ if (c >= 'A' && c <= 'F') {
+ return ((c - 'A') + 10);
+ }
+ TEST_ERROR("bad hex char");
+ return 0;
+}
+
+nbostream extract_data(const Memory &hex_dump) {
+ nbostream data;
+ if ((hex_dump.size > 2) && (hex_dump.data[0] == '0') && (hex_dump.data[1] == 'x')) {
+ for (size_t i = 2; i < (hex_dump.size - 1); i += 2) {
+ data << uint8_t((unhex(hex_dump.data[i]) << 4) | unhex(hex_dump.data[i + 1]));
+ }
+ }
+ return data;
+}
+
+bool is_mixed(const vespalib::string &type_str) {
+ bool dense = false;
+ bool sparse = false;
+ ValueType type = ValueType::from_spec(type_str);
+ for (const auto &dim: type.dimensions()) {
+ dense = (dense || dim.is_indexed());
+ sparse = (sparse || dim.is_mapped());
+ }
+ return (dense && sparse);
+}
+
+bool is_mixed(nbostream &data) {
+ return ((data.size() > 0) && (data.peek()[0] == 0x3));
+}
+
+bool is_same(const nbostream &a, const nbostream &b) {
+ return (Memory(a.peek(), a.size()) == Memory(b.peek(), b.size()));
+}
+
// Test wrapper to avoid passing global test parameters around
struct TestContext {
+ vespalib::string module_path;
const TensorEngine &ref_engine;
const TensorEngine &engine;
bool test_mixed_cases;
size_t skip_count;
- TestContext(const TensorEngine &engine_in, bool test_mixed_cases_in)
- : ref_engine(SimpleTensorEngine::ref()), engine(engine_in),
+ TestContext(const vespalib::string &module_path_in, const TensorEngine &engine_in, bool test_mixed_cases_in)
+ : module_path(module_path_in), ref_engine(SimpleTensorEngine::ref()), engine(engine_in),
test_mixed_cases(test_mixed_cases_in), skip_count(0) {}
std::unique_ptr<Tensor> tensor(const TensorSpec &spec) {
@@ -1237,7 +1284,50 @@ struct TestContext {
}
}
+ void test_binary_format_spec(Cursor &test) {
+ Stash stash;
+ TensorSpec spec = TensorSpec::from_slime(test["tensor"]);
+ const Inspector &binary = test["binary"];
+ EXPECT_GREATER(binary.entries(), 0u);
+ if (!is_mixed(spec.type()) || mixed(binary.entries() + 1)) {
+ nbostream encoded;
+ engine.encode(make_value(engine, spec, stash), encoded, stash);
+ test.setData("encoded", Memory(encoded.peek(), encoded.size()));
+ bool matched_encode = false;
+ for (size_t i = 0; i < binary.entries(); ++i) {
+ nbostream data = extract_data(binary[i].asString());
+ matched_encode = (matched_encode || is_same(encoded, data));
+ if (!is_mixed(data) || mixed(1)) {
+ TEST_DO(verify_result(Eval::Result(engine.decode(data, stash)), spec));
+ EXPECT_EQUAL(data.size(), 0u);
+ }
+ }
+ EXPECT_TRUE(matched_encode);
+ }
+ }
+
+ void test_binary_format_spec() {
+ vespalib::string path = module_path;
+ path.append("src/apps/make_tensor_binary_format_test_spec/test_spec.json");
+ MappedFileInput file(path);
+ Slime slime;
+ EXPECT_TRUE(file.valid());
+ EXPECT_EQUAL(JsonFormat::decode(file, slime), file.get().size);
+ int64_t num_tests = slime.get()["num_tests"].asLong();
+ Cursor &tests = slime.get()["tests"];
+ EXPECT_GREATER(num_tests, 0u);
+ EXPECT_EQUAL(size_t(num_tests), tests.entries());
+ for (size_t i = 0; i < tests.entries(); ++i) {
+ size_t fail_cnt = TEST_MASTER.getProgress().failCnt;
+ TEST_DO(test_binary_format_spec(tests[i]));
+ if (TEST_MASTER.getProgress().failCnt > fail_cnt) {
+ fprintf(stderr, "failed:\n%s", tests[i].toString().c_str());
+ }
+ }
+ }
+
void test_binary_format() {
+ TEST_DO(test_binary_format_spec());
TEST_DO(verify_encode_decode(spec(42)));
TEST_DO(verify_encode_decode(spec({x(3)}, N())));
TEST_DO(verify_encode_decode(spec({x(3),y(5)}, N())));
@@ -1271,9 +1361,10 @@ struct TestContext {
} // namespace vespalib::eval::test::<unnamed>
void
-TensorConformance::run_tests(const TensorEngine &engine, bool test_mixed_cases)
+TensorConformance::run_tests(const vespalib::string &module_path, const TensorEngine &engine, bool test_mixed_cases)
{
- TestContext ctx(engine, test_mixed_cases);
+ TestContext ctx(module_path, engine, test_mixed_cases);
+ fprintf(stderr, "module path: '%s'\n", ctx.module_path.c_str());
ctx.run_tests();
if (ctx.skip_count > 0) {
fprintf(stderr, "WARNING: skipped %zu mixed test cases\n", ctx.skip_count);
diff --git a/eval/src/vespa/eval/eval/test/tensor_conformance.h b/eval/src/vespa/eval/eval/test/tensor_conformance.h
index 054a1bb8faa..91abd54b7ee 100644
--- a/eval/src/vespa/eval/eval/test/tensor_conformance.h
+++ b/eval/src/vespa/eval/eval/test/tensor_conformance.h
@@ -3,6 +3,7 @@
#pragma once
#include <vespa/eval/eval/tensor_engine.h>
+#include <vespa/vespalib/stllike/string.h>
namespace vespalib {
namespace eval {
@@ -13,7 +14,7 @@ namespace test {
* implementations of the TensorEngine interface.
**/
struct TensorConformance {
- static void run_tests(const TensorEngine &engine, bool test_mixed_cases);
+ static void run_tests(const vespalib::string &module_path, const TensorEngine &engine, bool test_mixed_cases);
};
} // namespace vespalib::eval::test