aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHÃ¥vard Pettersen <3535158+havardpe@users.noreply.github.com>2019-12-02 14:13:00 +0100
committerGitHub <noreply@github.com>2019-12-02 14:13:00 +0100
commitbca43d1646e9e63f65b92bed1e2f030966a410c3 (patch)
tree0161c1832ade10d569aafd013e96662367172380
parent27340646405f5aed96d8816ff3df3f787d9edbeb (diff)
parentda8f15347142a826aaea75a6e6d300f73ed4607b (diff)
Merge pull request #11467 from vespa-engine/havardpe/tensor-extract-value
tensor peek
-rw-r--r--eval/src/tests/eval/compiled_function/compiled_function_test.cpp3
-rw-r--r--eval/src/tests/eval/function/function_test.cpp28
-rw-r--r--eval/src/tests/eval/node_types/node_types_test.cpp25
-rw-r--r--eval/src/tests/eval/tensor_function/tensor_function_test.cpp90
-rw-r--r--eval/src/vespa/eval/eval/function.cpp34
-rw-r--r--eval/src/vespa/eval/eval/interpreted_function.cpp7
-rw-r--r--eval/src/vespa/eval/eval/key_gen.cpp1
-rw-r--r--eval/src/vespa/eval/eval/llvm/compiled_function.cpp3
-rw-r--r--eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp3
-rw-r--r--eval/src/vespa/eval/eval/make_tensor_function.cpp25
-rw-r--r--eval/src/vespa/eval/eval/node_types.cpp107
-rw-r--r--eval/src/vespa/eval/eval/node_visitor.h2
-rw-r--r--eval/src/vespa/eval/eval/simple_tensor_engine.h4
-rw-r--r--eval/src/vespa/eval/eval/string_stuff.cpp13
-rw-r--r--eval/src/vespa/eval/eval/string_stuff.h10
-rw-r--r--eval/src/vespa/eval/eval/tensor_engine.h10
-rw-r--r--eval/src/vespa/eval/eval/tensor_function.cpp104
-rw-r--r--eval/src/vespa/eval/eval/tensor_function.h34
-rw-r--r--eval/src/vespa/eval/eval/tensor_nodes.cpp1
-rw-r--r--eval/src/vespa/eval/eval/tensor_nodes.h74
-rw-r--r--eval/src/vespa/eval/eval/test/eval_spec.cpp1
-rw-r--r--eval/src/vespa/eval/eval/test/tensor_conformance.cpp20
-rw-r--r--eval/src/vespa/eval/eval/visit_stuff.h1
-rw-r--r--eval/src/vespa/eval/tensor/default_tensor_engine.h4
-rw-r--r--searchlib/src/vespa/searchlib/expression/resultvector.h2
-rw-r--r--staging_vespalib/src/vespa/vespalib/objects/visit.hpp4
26 files changed, 539 insertions, 71 deletions
diff --git a/eval/src/tests/eval/compiled_function/compiled_function_test.cpp b/eval/src/tests/eval/compiled_function/compiled_function_test.cpp
index 151a4cf5dd5..1d5a6929083 100644
--- a/eval/src/tests/eval/compiled_function/compiled_function_test.cpp
+++ b/eval/src/tests/eval/compiled_function/compiled_function_test.cpp
@@ -58,6 +58,9 @@ std::vector<vespalib::string> unsupported = {
};
bool is_unsupported(const vespalib::string &expression) {
+ if (expression.find("{") != vespalib::string::npos) {
+ return true;
+ }
for (const auto &prefix: unsupported) {
if (starts_with(expression, prefix)) {
return true;
diff --git a/eval/src/tests/eval/function/function_test.cpp b/eval/src/tests/eval/function/function_test.cpp
index 0e3100ae425..d93b57ab5b2 100644
--- a/eval/src/tests/eval/function/function_test.cpp
+++ b/eval/src/tests/eval/function/function_test.cpp
@@ -81,6 +81,12 @@ void verify_error(const vespalib::string &expr, const vespalib::string &expected
EXPECT_EQUAL(expected_error, function.get_error());
}
+void verify_parse(const vespalib::string &expr, const vespalib::string &expect) {
+ Function function = Function::parse(expr);
+ EXPECT_TRUE(!function.has_error());
+ EXPECT_EQUAL(function.dump_as_lambda(), expect);
+}
+
TEST("require that scientific numbers can be parsed") {
EXPECT_EQUAL(1.0, as_number(Function::parse(params, "1")));
EXPECT_EQUAL(2.5, as_number(Function::parse(params, "2.5")));
@@ -933,6 +939,28 @@ TEST("require that convenient tensor create detects under-specified cells") {
//-----------------------------------------------------------------------------
+TEST("require that tensor peek can be parsed") {
+ TEST_DO(verify_parse("t{x:1,y:foo}", "f(t)(t{x:1,y:foo})"));
+}
+
+TEST("require that tensor peek can contain expressions") {
+ TEST_DO(verify_parse("t{x:1+2,y:foo}", "f(t)(t{x:(1+2),y:foo})"));
+ TEST_DO(verify_parse("t{x:1+bar,y:foo}", "f(t,bar)(t{x:(1+bar),y:foo})"));
+ TEST_DO(verify_parse("t{x:1,y:foo+2}", "f(t,foo)(t{x:1,y:(foo+2)})"));
+ TEST_DO(verify_parse("t{x:1,y:(foo)}", "f(t,foo)(t{x:1,y:(foo)})"));
+}
+
+TEST("require that tensor peek can contain extra whitespace") {
+ TEST_DO(verify_parse(" t { x : 1 + bar , y : foo + 2 } ",
+ "f(t,bar,foo)(t{x:(1+bar),y:(foo+2)})"));
+}
+
+TEST("require that empty tensor peek is not allowed") {
+ TEST_DO(verify_error("x{}", "[x{}]...[empty peek spec]...[]"));
+}
+
+//-----------------------------------------------------------------------------
+
TEST("require that tensor concat can be parsed") {
EXPECT_EQUAL("concat(a,b,d)", Function::parse({"a", "b"}, "concat(a,b,d)").dump());
EXPECT_EQUAL("concat(a,b,d)", Function::parse({"a", "b"}, " concat ( a , b , d ) ").dump());
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 b2ad107f2aa..97a80d9b4b3 100644
--- a/eval/src/tests/eval/node_types/node_types_test.cpp
+++ b/eval/src/tests/eval/node_types/node_types_test.cpp
@@ -214,6 +214,31 @@ TEST("require that tensor create resolves correct type") {
TEST_DO(verify("tensor(x[3]):{{x:0}:double,{x:1}:error,{x:2}:double}", "error"));
}
+TEST("require that tensor peek resolves correct type") {
+ TEST_DO(verify("tensor(x[3]){x:1}", "double"));
+ TEST_DO(verify("tensor(x[3]){x:double}", "error"));
+ TEST_DO(verify("tensor(x[3]){x:(double)}", "double"));
+ TEST_DO(verify("tensor(x[3]){x:3}", "error"));
+ TEST_DO(verify("tensor(x{}){x:1}", "double"));
+ TEST_DO(verify("tensor(x{}){x:foo}", "double"));
+ TEST_DO(verify("tensor(x{}){x:(double)}", "double"));
+ TEST_DO(verify("tensor(x{}){x:tensor(x[3])}", "error"));
+ TEST_DO(verify("tensor(x{},y[3]){x:foo,y:2}", "double"));
+ TEST_DO(verify("tensor(x{},y[3]){x:foo}", "tensor(y[3])"));
+ TEST_DO(verify("tensor(x{},y[3]){y:2}", "tensor(x{})"));
+ TEST_DO(verify("tensor<float>(x[3]){x:1}", "double"));
+ TEST_DO(verify("tensor<float>(x[3]){x:double}", "error"));
+ TEST_DO(verify("tensor<float>(x[3]){x:(double)}", "double"));
+ TEST_DO(verify("tensor<float>(x[3]){x:3}", "error"));
+ TEST_DO(verify("tensor<float>(x{}){x:1}", "double"));
+ TEST_DO(verify("tensor<float>(x{}){x:foo}", "double"));
+ TEST_DO(verify("tensor<float>(x{}){x:(double)}", "double"));
+ TEST_DO(verify("tensor<float>(x{}){x:tensor(x[3])}", "error"));
+ TEST_DO(verify("tensor<float>(x{},y[3]){x:foo,y:2}", "double"));
+ TEST_DO(verify("tensor<float>(x{},y[3]){x:foo}", "tensor<float>(y[3])"));
+ TEST_DO(verify("tensor<float>(x{},y[3]){y:2}", "tensor<float>(x{})"));
+}
+
TEST("require that tensor concat resolves correct type") {
TEST_DO(verify("concat(double,double,x)", "tensor(x[2])"));
TEST_DO(verify("concat(tensor(x[2]),tensor(x[3]),x)", "tensor(x[5])"));
diff --git a/eval/src/tests/eval/tensor_function/tensor_function_test.cpp b/eval/src/tests/eval/tensor_function/tensor_function_test.cpp
index c30438b551d..1e59b35a01f 100644
--- a/eval/src/tests/eval/tensor_function/tensor_function_test.cpp
+++ b/eval/src/tests/eval/tensor_function/tensor_function_test.cpp
@@ -6,6 +6,7 @@
#include <vespa/eval/eval/tensor_function.h>
#include <vespa/eval/eval/value_type.h>
#include <vespa/vespalib/util/stash.h>
+#include <vespa/vespalib/util/stringfmt.h>
#include <map>
using namespace vespalib;
@@ -49,12 +50,27 @@ struct EvalCtx {
Value::UP make_false() {
return engine.from_spec(TensorSpec("double").add({}, 0.0));
}
- Value::UP make_simple_vector() {
+ Value::UP make_vector(std::initializer_list<double> cells, vespalib::string dim = "x", bool mapped = false) {
+ vespalib::string type_spec = mapped
+ ? make_string("tensor(%s{})", dim.c_str())
+ : make_string("tensor(%s[%zu])", dim.c_str(), cells.size());
+ TensorSpec spec(type_spec);
+ size_t idx = 0;
+ for (double cell_value: cells) {
+ TensorSpec::Label label = mapped
+ ? TensorSpec::Label(make_string("%zu", idx++))
+ : TensorSpec::Label(idx++);
+ spec.add({{dim, label}}, cell_value);
+ }
+ return engine.from_spec(spec);
+ }
+ Value::UP make_mixed_tensor(double a, double b, double c, double d) {
return engine.from_spec(
- TensorSpec("tensor(x[3])")
- .add({{"x",0}}, 1)
- .add({{"x",1}}, 2)
- .add({{"x",2}}, 3));
+ TensorSpec("tensor(x{},y[2])")
+ .add({{"x", "foo"}, {"y", 0}}, a)
+ .add({{"x", "foo"}, {"y", 1}}, b)
+ .add({{"x", "bar"}, {"y", 0}}, c)
+ .add({{"x", "bar"}, {"y", 1}}, d));
}
Value::UP make_tensor_matrix_first_half() {
return engine.from_spec(
@@ -240,7 +256,7 @@ TEST("require that tensor create works") {
size_t a_id = ctx.add_tensor(ctx.make_double(1.0));
size_t b_id = ctx.add_tensor(ctx.make_double(2.0));
Value::UP my_const = ctx.make_double(3.0);
- Value::UP expect = ctx.make_simple_vector();
+ Value::UP expect = ctx.make_vector({1.0, 2.0, 3.0});
const auto &a = inject(ValueType::from_spec("double"), a_id, ctx.stash);
const auto &b = inject(ValueType::from_spec("double"), b_id, ctx.stash);
const auto &c = const_value(*my_const, ctx.stash);
@@ -257,6 +273,56 @@ TEST("require that tensor create works") {
TEST_DO(verify_equal(*expect, ctx.eval(prog)));
}
+TEST("require that single value tensor peek works") {
+ EvalCtx ctx(SimpleTensorEngine::ref());
+ size_t a_id = ctx.add_tensor(ctx.make_double(1.0));
+ Value::UP my_const = ctx.make_mixed_tensor(1.0, 2.0, 3.0, 4.0);
+ Value::UP expect = ctx.make_vector({2.0, 3.0, 0.0});
+ const auto &a = inject(ValueType::from_spec("double"), a_id, ctx.stash);
+ const auto &t = const_value(*my_const, ctx.stash);
+ const auto &peek1 = peek(t, {{"x", "foo"}, {"y", a}}, ctx.stash);
+ const auto &peek2 = peek(t, {{"x", "bar"}, {"y", size_t(0)}}, ctx.stash);
+ const auto &peek3 = peek(t, {{"x", "bar"}, {"y", size_t(1000)}}, ctx.stash);
+ const auto &fun = create(ValueType::from_spec("tensor(x[3])"),
+ {
+ {{{"x", 0}}, peek1},
+ {{{"x", 1}}, peek2},
+ {{{"x", 2}}, peek3}
+ },
+ ctx.stash);
+ EXPECT_TRUE(fun.result_is_mutable());
+ EXPECT_EQUAL(expect->type(), fun.result_type());
+ const auto &prog = ctx.compile(fun);
+ TEST_DO(verify_equal(*expect, ctx.eval(prog)));
+}
+
+TEST("require that tensor subspace tensor peek works") {
+ EvalCtx ctx(SimpleTensorEngine::ref());
+ Value::UP my_const = ctx.make_mixed_tensor(1.0, 2.0, 3.0, 4.0);
+ Value::UP expect = ctx.make_vector({3.0, 4.0}, "y");
+ const auto &t = const_value(*my_const, ctx.stash);
+ const auto &fun = peek(t, {{"x", "bar"}}, ctx.stash);
+ EXPECT_TRUE(fun.result_is_mutable());
+ EXPECT_EQUAL(expect->type(), fun.result_type());
+ const auto &prog = ctx.compile(fun);
+ TEST_DO(verify_equal(*expect, ctx.eval(prog)));
+}
+
+TEST("require that automatic string conversion tensor peek works") {
+ EvalCtx ctx(SimpleTensorEngine::ref());
+ size_t a_id = ctx.add_tensor(ctx.make_double(1.0));
+ Value::UP my_const = ctx.make_vector({1.0, 2.0, 3.0}, "x", true);
+ const auto &a = inject(ValueType::from_spec("double"), a_id, ctx.stash);
+ const auto &t = const_value(*my_const, ctx.stash);
+ const auto &fun = peek(t, {{"x", a}}, ctx.stash);
+ EXPECT_TRUE(fun.result_is_mutable());
+ EXPECT_TRUE(fun.result_type().is_double());
+ const auto &prog = ctx.compile(fun);
+ const Value &result = ctx.eval(prog);
+ EXPECT_TRUE(result.is_double());
+ EXPECT_EQUAL(2.0, result.as_double());
+}
+
TEST("require that tensor rename works") {
EvalCtx ctx(SimpleTensorEngine::ref());
size_t a_id = ctx.add_tensor(ctx.make_tensor_matrix());
@@ -365,20 +431,20 @@ TEST("require that push_children works") {
TEST("require that tensor function can be dumped for debugging") {
Stash stash;
- auto my_value_1 = stash.create<DoubleValue>(5.0);
- auto my_value_2 = stash.create<DoubleValue>(1.0);
+ auto my_value_1 = stash.create<DoubleValue>(1.0);
+ auto my_value_2 = stash.create<DoubleValue>(2.0);
//-------------------------------------------------------------------------
const auto &x5 = inject(ValueType::from_spec("tensor(x[5])"), 0, stash);
const auto &mapped_x5 = map(x5, operation::Relu::f, stash);
const auto &const_1 = const_value(my_value_1, stash);
const auto &joined_x5 = join(mapped_x5, const_1, operation::Mul::f, stash);
//-------------------------------------------------------------------------
- const auto &x2_0 = const_value(my_value_1, stash);
- const auto &x2_1 = const_value(my_value_2, stash);
+ const auto &peek1 = peek(x5, {{"x", const_1}}, stash);
+ const auto &peek2 = peek(x5, {{"x", size_t(2)}}, stash);
const auto &x2 = create(ValueType::from_spec("tensor(x[2])"),
{
- {{{"x", 0}}, x2_0},
- {{{"x", 1}}, x2_1}
+ {{{"x", 0}}, peek1},
+ {{{"x", 1}}, peek2}
}, stash);
const auto &a3y10 = inject(ValueType::from_spec("tensor(a[3],y[10])"), 2, stash);
const auto &a3 = reduce(a3y10, Aggr::SUM, {"y"}, stash);
diff --git a/eval/src/vespa/eval/eval/function.cpp b/eval/src/vespa/eval/eval/function.cpp
index 44bb61abe17..56899debc26 100644
--- a/eval/src/vespa/eval/eval/function.cpp
+++ b/eval/src/vespa/eval/eval/function.cpp
@@ -734,6 +734,37 @@ bool maybe_parse_tensor_generator(ParseContext &ctx) {
return true;
}
+// tensor_value <-(bind)- '{d1:1,d2:foo,d3:(a+b)}'
+void parse_tensor_peek(ParseContext &ctx) {
+ ctx.skip_spaces();
+ ctx.eat('{');
+ nodes::TensorPeek::Spec peek_spec;
+ CommaTracker list;
+ while (!ctx.find_list_end()) {
+ list.maybe_eat_comma(ctx);
+ auto dim_name = get_ident(ctx, false);
+ ctx.skip_spaces();
+ ctx.eat(':');
+ if (!ctx.failed()) {
+ ParseContext::InputMark before_label = ctx.get_input_mark();
+ auto label = get_ident(ctx, false);
+ bool verbatim = !ctx.failed() && ctx.find_expression_end();
+ if (verbatim) {
+ peek_spec.emplace(dim_name, label);
+ } else {
+ ctx.restore_input_mark(before_label);
+ peek_spec.emplace(dim_name, get_expression(ctx));
+ }
+ }
+ }
+ ctx.eat('}');
+ if (peek_spec.empty()) {
+ return ctx.fail("empty peek spec");
+ }
+ auto peek = std::make_unique<nodes::TensorPeek>(ctx.pop_expression(), std::move(peek_spec));
+ ctx.push_expression(std::move(peek));
+}
+
void parse_tensor_concat(ParseContext &ctx) {
Node_UP lhs = get_expression(ctx);
ctx.eat(',');
@@ -858,6 +889,9 @@ bool parse_operator(ParseContext &ctx) {
if (op.get() != nullptr) {
ctx.push_operator(std::move(op));
ctx.skip(str.size());
+ } else if (ctx.get() == '{') {
+ parse_tensor_peek(ctx);
+ expect_value = false;
} else {
vespalib::string ident = get_ident(ctx, true);
if (ident == "in") {
diff --git a/eval/src/vespa/eval/eval/interpreted_function.cpp b/eval/src/vespa/eval/eval/interpreted_function.cpp
index 208b2db4c3a..5575f4ecb1c 100644
--- a/eval/src/vespa/eval/eval/interpreted_function.cpp
+++ b/eval/src/vespa/eval/eval/interpreted_function.cpp
@@ -6,6 +6,7 @@
#include "check_type.h"
#include "tensor_spec.h"
#include "operation.h"
+#include "tensor_nodes.h"
#include "tensor_engine.h"
#include <vespa/vespalib/util/classname.h>
#include <vespa/eval/eval/llvm/compile_cache.h>
@@ -21,13 +22,13 @@ namespace eval {
namespace {
const Function *get_lambda(const nodes::Node &node) {
- if (auto ptr = as<nodes::TensorMap>(node)) {
+ if (auto ptr = nodes::as<nodes::TensorMap>(node)) {
return &ptr->lambda();
}
- if (auto ptr = as<nodes::TensorJoin>(node)) {
+ if (auto ptr = nodes::as<nodes::TensorJoin>(node)) {
return &ptr->lambda();
}
- if (auto ptr = as<nodes::TensorLambda>(node)) {
+ if (auto ptr = nodes::as<nodes::TensorLambda>(node)) {
return &ptr->lambda();
}
return nullptr;
diff --git a/eval/src/vespa/eval/eval/key_gen.cpp b/eval/src/vespa/eval/eval/key_gen.cpp
index 63cd39b5856..f107ba9e59b 100644
--- a/eval/src/vespa/eval/eval/key_gen.cpp
+++ b/eval/src/vespa/eval/eval/key_gen.cpp
@@ -42,6 +42,7 @@ struct KeyGen : public NodeVisitor, public NodeTraverser {
void visit(const TensorLambda &) override { add_byte(15); } // type/lambda should be part of key
void visit(const TensorConcat &) override { add_byte(16); } // dimension should be part of key
void visit(const TensorCreate &) override { add_byte(17); } // type should be part of key
+ void visit(const TensorPeek &) override { add_byte(18); } // addr should be part of key
void visit(const Add &) override { add_byte(20); }
void visit(const Sub &) override { add_byte(21); }
void visit(const Mul &) override { add_byte(22); }
diff --git a/eval/src/vespa/eval/eval/llvm/compiled_function.cpp b/eval/src/vespa/eval/eval/llvm/compiled_function.cpp
index 1ebf64ad269..ba5b8e99254 100644
--- a/eval/src/vespa/eval/eval/llvm/compiled_function.cpp
+++ b/eval/src/vespa/eval/eval/llvm/compiled_function.cpp
@@ -132,7 +132,8 @@ CompiledFunction::detect_issues(const Function &function)
nodes::TensorRename,
nodes::TensorLambda,
nodes::TensorConcat,
- nodes::TensorCreate>(node))
+ nodes::TensorCreate,
+ nodes::TensorPeek>(node))
{
issues.push_back(make_string("unsupported node type: %s",
getClassName(node).c_str()));
diff --git a/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp b/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp
index 083e5d8593c..f62a7aeb7b1 100644
--- a/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp
+++ b/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp
@@ -482,6 +482,9 @@ struct FunctionBuilder : public NodeVisitor, public NodeTraverser {
void visit(const TensorCreate &node) override {
make_error(node.num_children());
}
+ void visit(const TensorPeek &node) override {
+ make_error(node.num_children());
+ }
// operator nodes
diff --git a/eval/src/vespa/eval/eval/make_tensor_function.cpp b/eval/src/vespa/eval/eval/make_tensor_function.cpp
index f0afb34376d..3b74a7a0e23 100644
--- a/eval/src/vespa/eval/eval/make_tensor_function.cpp
+++ b/eval/src/vespa/eval/eval/make_tensor_function.cpp
@@ -107,6 +107,28 @@ struct TensorFunctionBuilder : public NodeVisitor, public NodeTraverser {
stack.push_back(tensor_function::create(node.type(), spec, stash));
}
+ void make_peek(const TensorPeek &node) {
+ assert(stack.size() >= node.num_children());
+ const tensor_function::Node &param = stack[stack.size()-node.num_children()];
+ std::map<vespalib::string, std::variant<TensorSpec::Label, tensor_function::Node::CREF>> spec;
+ for (auto pos = node.dim_list().rbegin(); pos != node.dim_list().rend(); ++pos) {
+ if (pos->second.is_expr()) {
+ spec.emplace(pos->first, stack.back());
+ stack.pop_back();
+ } else {
+ size_t dim_idx = param.result_type().dimension_index(pos->first);
+ assert(dim_idx != ValueType::Dimension::npos);
+ const auto &param_dim = param.result_type().dimensions()[dim_idx];
+ if (param_dim.is_mapped()) {
+ spec.emplace(pos->first, pos->second.label);
+ } else {
+ spec.emplace(pos->first, as_number(pos->second.label));
+ }
+ }
+ }
+ stack.back() = tensor_function::peek(param, spec, stash);
+ }
+
void make_rename(const Node &, const std::vector<vespalib::string> &from, const std::vector<vespalib::string> &to) {
assert(stack.size() >= 1);
const auto &a = stack.back();
@@ -197,6 +219,9 @@ struct TensorFunctionBuilder : public NodeVisitor, public NodeTraverser {
void visit(const TensorCreate &node) override {
make_create(node);
}
+ void visit(const TensorPeek &node) override {
+ make_peek(node);
+ }
void visit(const Add &node) override {
make_join(node, operation::Add::f);
}
diff --git a/eval/src/vespa/eval/eval/node_types.cpp b/eval/src/vespa/eval/eval/node_types.cpp
index f802a9ab03f..3e8f06c0941 100644
--- a/eval/src/vespa/eval/eval/node_types.cpp
+++ b/eval/src/vespa/eval/eval/node_types.cpp
@@ -13,32 +13,25 @@ class State
private:
const std::vector<ValueType> &_params;
std::map<const Node *, ValueType> &_type_map;
- std::vector<ValueType> _types;
public:
State(const std::vector<ValueType> &params,
std::map<const Node *, ValueType> &type_map)
- : _params(params), _type_map(type_map), _types() {}
+ : _params(params), _type_map(type_map) {}
const ValueType &param_type(size_t idx) {
assert(idx < _params.size());
return _params[idx];
}
- const ValueType &peek(size_t ridx) const {
- assert(_types.size() > ridx);
- return _types[_types.size() - 1 - ridx];
- }
- void bind(size_t prune_cnt, const ValueType &type_ref, const Node &node) {
- ValueType type = type_ref; // need copy since type_ref might be inside _types
- assert(_types.size() >= prune_cnt);
- for (size_t i = 0; i < prune_cnt; ++i) {
- _types.pop_back();
- }
- _types.push_back(type);
+ void bind(const ValueType &type, const Node &node) {
+ auto pos = _type_map.find(&node);
+ assert(pos == _type_map.end());
_type_map.emplace(&node, type);
}
- void assert_valid_end_state() const {
- assert(_types.size() == 1);
+ const ValueType &type(const Node &node) {
+ auto pos = _type_map.find(&node);
+ assert(pos != _type_map.end());
+ return pos->second;
}
};
@@ -48,22 +41,24 @@ struct TypeResolver : public NodeVisitor, public NodeTraverser {
std::map<const Node *, ValueType> &type_map_out);
~TypeResolver();
- //-------------------------------------------------------------------------
-
- void assert_valid_end_state() const {
- state.assert_valid_end_state();
+ const ValueType &param_type(size_t idx) {
+ return state.param_type(idx);
}
- //-------------------------------------------------------------------------
+ void bind(const ValueType &type, const Node &node) {
+ state.bind(type, node);
+ }
- void bind_type(const ValueType &type, const Node &node) {
- state.bind(node.num_children(), type, node);
+ const ValueType &type(const Node &node) {
+ return state.type(node);
}
+ //-------------------------------------------------------------------------
+
bool check_error(const Node &node) {
for (size_t i = 0; i < node.num_children(); ++i) {
- if (state.peek(i).is_error()) {
- bind_type(ValueType::error_type(), node);
+ if (type(node.get_child(i)).is_error()) {
+ bind(ValueType::error_type(), node);
return true;
}
}
@@ -71,58 +66,87 @@ struct TypeResolver : public NodeVisitor, public NodeTraverser {
}
void resolve_op1(const Node &node) {
- bind_type(state.peek(0), node);
+ bind(type(node.get_child(0)), node);
}
void resolve_op2(const Node &node) {
- bind_type(ValueType::join(state.peek(1), state.peek(0)), node);
+ bind(ValueType::join(type(node.get_child(0)),
+ type(node.get_child(1))), node);
}
//-------------------------------------------------------------------------
void visit(const Number &node) override {
- bind_type(ValueType::double_type(), node);
+ bind(ValueType::double_type(), node);
}
void visit(const Symbol &node) override {
- bind_type(state.param_type(node.id()), node);
+ bind(param_type(node.id()), node);
}
void visit(const String &node) override {
- bind_type(ValueType::double_type(), node);
+ bind(ValueType::double_type(), node);
}
void visit(const In &node) override { resolve_op1(node); }
void visit(const Neg &node) override { resolve_op1(node); }
void visit(const Not &node) override { resolve_op1(node); }
void visit(const If &node) override {
- bind_type(ValueType::either(state.peek(1), state.peek(0)), node);
+ bind(ValueType::either(type(node.true_expr()),
+ type(node.false_expr())), node);
}
void visit(const Error &node) override {
- bind_type(ValueType::error_type(), node);
+ bind(ValueType::error_type(), node);
}
void visit(const TensorMap &node) override { resolve_op1(node); }
void visit(const TensorJoin &node) override { resolve_op2(node); }
void visit(const TensorReduce &node) override {
- const ValueType &child = state.peek(0);
- bind_type(child.reduce(node.dimensions()), node);
+ const ValueType &child = type(node.get_child(0));
+ bind(child.reduce(node.dimensions()), node);
}
void visit(const TensorRename &node) override {
- const ValueType &child = state.peek(0);
- bind_type(child.rename(node.from(), node.to()), node);
+ const ValueType &child = type(node.get_child(0));
+ bind(child.rename(node.from(), node.to()), node);
}
void visit(const TensorLambda &node) override {
- bind_type(node.type(), node);
+ bind(node.type(), node);
}
void visit(const TensorConcat &node) override {
- bind_type(ValueType::concat(state.peek(1), state.peek(0), node.dimension()), node);
+ bind(ValueType::concat(type(node.get_child(0)),
+ type(node.get_child(1)), node.dimension()), node);
}
void visit(const TensorCreate &node) override {
for (size_t i = 0; i < node.num_children(); ++i) {
- if (!state.peek(i).is_double()) {
- return bind_type(ValueType::error_type(), node);
+ if (!type(node.get_child(i)).is_double()) {
+ return bind(ValueType::error_type(), node);
}
}
- bind_type(node.type(), node);
+ bind(node.type(), node);
+ }
+ void visit(const TensorPeek &node) override {
+ const ValueType &param_type = type(node.param());
+ std::vector<vespalib::string> dimensions;
+ for (const auto &dim: node.dim_list()) {
+ dimensions.push_back(dim.first);
+ if (dim.second.is_expr()) {
+ if (!type(*dim.second.expr).is_double()) {
+ return bind(ValueType::error_type(), node);
+ }
+ } else {
+ size_t dim_idx = param_type.dimension_index(dim.first);
+ if (dim_idx == ValueType::Dimension::npos) {
+ return bind(ValueType::error_type(), node);
+ }
+ const auto &param_dim = param_type.dimensions()[dim_idx];
+ if (param_dim.is_indexed()) {
+ if (!is_number(dim.second.label)) {
+ return bind(ValueType::error_type(), node);
+ }
+ if (as_number(dim.second.label) >= param_dim.size) {
+ return bind(ValueType::error_type(), node);
+ }
+ }
+ }
+ }
+ bind(param_type.reduce(dimensions), node);
}
-
void visit(const Add &node) override { resolve_op2(node); }
void visit(const Sub &node) override { resolve_op2(node); }
void visit(const Mul &node) override { resolve_op2(node); }
@@ -202,7 +226,6 @@ NodeTypes::NodeTypes(const Function &function, const std::vector<ValueType> &inp
assert(input_types.size() == function.num_params());
nodes::TypeResolver resolver(input_types, _type_map);
function.root().traverse(resolver);
- resolver.assert_valid_end_state();
}
const ValueType &
diff --git a/eval/src/vespa/eval/eval/node_visitor.h b/eval/src/vespa/eval/eval/node_visitor.h
index d0996f370c2..bca4e00d72e 100644
--- a/eval/src/vespa/eval/eval/node_visitor.h
+++ b/eval/src/vespa/eval/eval/node_visitor.h
@@ -36,6 +36,7 @@ struct NodeVisitor {
virtual void visit(const nodes::TensorLambda &) = 0;
virtual void visit(const nodes::TensorConcat &) = 0;
virtual void visit(const nodes::TensorCreate &) = 0;
+ virtual void visit(const nodes::TensorPeek &) = 0;
// operator nodes
virtual void visit(const nodes::Add &) = 0;
@@ -105,6 +106,7 @@ struct EmptyNodeVisitor : NodeVisitor {
void visit(const nodes::TensorLambda &) override {}
void visit(const nodes::TensorConcat &) override {}
void visit(const nodes::TensorCreate &) override {}
+ void visit(const nodes::TensorPeek &) override {}
void visit(const nodes::Add &) override {}
void visit(const nodes::Sub &) override {}
void visit(const nodes::Mul &) override {}
diff --git a/eval/src/vespa/eval/eval/simple_tensor_engine.h b/eval/src/vespa/eval/eval/simple_tensor_engine.h
index 4cfd389dfa9..645ec4c4be7 100644
--- a/eval/src/vespa/eval/eval/simple_tensor_engine.h
+++ b/eval/src/vespa/eval/eval/simple_tensor_engine.h
@@ -20,10 +20,10 @@ public:
static const TensorEngine &ref() { return _engine; };
TensorSpec to_spec(const Value &value) const override;
- Value::UP from_spec(const TensorSpec &spec) const override;
+ std::unique_ptr<Value> from_spec(const TensorSpec &spec) const override;
void encode(const Value &value, nbostream &output) const override;
- Value::UP decode(nbostream &input) const override;
+ std::unique_ptr<Value> decode(nbostream &input) const override;
const Value &map(const Value &a, map_fun_t function, Stash &stash) const override;
const Value &join(const Value &a, const Value &b, join_fun_t function, Stash &stash) const override;
diff --git a/eval/src/vespa/eval/eval/string_stuff.cpp b/eval/src/vespa/eval/eval/string_stuff.cpp
index 782aacb5cea..1fd8649bb57 100644
--- a/eval/src/vespa/eval/eval/string_stuff.cpp
+++ b/eval/src/vespa/eval/eval/string_stuff.cpp
@@ -5,6 +5,19 @@
namespace vespalib::eval {
+bool is_number(const vespalib::string &str) {
+ for (char c: str) {
+ if (!isdigit(c)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+size_t as_number(const vespalib::string &str) {
+ return atoi(str.c_str());
+}
+
vespalib::string as_string(const TensorSpec::Address &address) {
CommaTracker label_list;
vespalib::string str = "{";
diff --git a/eval/src/vespa/eval/eval/string_stuff.h b/eval/src/vespa/eval/eval/string_stuff.h
index 820196fe959..68b45b0aa38 100644
--- a/eval/src/vespa/eval/eval/string_stuff.h
+++ b/eval/src/vespa/eval/eval/string_stuff.h
@@ -38,6 +38,16 @@ struct CommaTracker {
};
/**
+ * Is this string a positive integer (dimension index)
+ **/
+bool is_number(const vespalib::string &str);
+
+/**
+ * Convert this string to a positive integer (dimension index)
+ **/
+size_t as_number(const vespalib::string &str);
+
+/**
* Convert a tensor spec address into a string on the form:
* '{dim1:label,dim2:index, ...}'
**/
diff --git a/eval/src/vespa/eval/eval/tensor_engine.h b/eval/src/vespa/eval/eval/tensor_engine.h
index d9f6d35609a..b2b9faedadb 100644
--- a/eval/src/vespa/eval/eval/tensor_engine.h
+++ b/eval/src/vespa/eval/eval/tensor_engine.h
@@ -2,8 +2,6 @@
#pragma once
-#include "value_type.h"
-#include "tensor_function.h"
#include "aggr.h"
#include <vespa/vespalib/stllike/string.h>
#include <memory>
@@ -18,8 +16,9 @@ class nbostream;
namespace eval {
struct Value;
-class Tensor;
+class ValueType;
class TensorSpec;
+class TensorFunction;
/**
* Top-level API for a tensor implementation. All Tensor operations
@@ -33,7 +32,6 @@ class TensorSpec;
struct TensorEngine
{
using Aggr = eval::Aggr;
- using Tensor = eval::Tensor;
using TensorFunction = eval::TensorFunction;
using TensorSpec = eval::TensorSpec;
using Value = eval::Value;
@@ -42,10 +40,10 @@ struct TensorEngine
using map_fun_t = double (*)(double);
virtual TensorSpec to_spec(const Value &value) const = 0;
- virtual Value::UP from_spec(const TensorSpec &spec) const = 0;
+ virtual std::unique_ptr<Value> from_spec(const TensorSpec &spec) const = 0;
virtual void encode(const Value &value, nbostream &output) const = 0;
- virtual Value::UP decode(nbostream &input) const = 0;
+ virtual std::unique_ptr<Value> decode(nbostream &input) const = 0;
virtual const TensorFunction &optimize(const TensorFunction &expr, Stash &) const { return expr; }
diff --git a/eval/src/vespa/eval/eval/tensor_function.cpp b/eval/src/vespa/eval/eval/tensor_function.cpp
index 951b9772505..1f87c0390af 100644
--- a/eval/src/vespa/eval/eval/tensor_function.cpp
+++ b/eval/src/vespa/eval/eval/tensor_function.cpp
@@ -129,6 +129,63 @@ void op_tensor_create(State &state, uint64_t param) {
state.pop_n_push(i, result);
}
+const Value &extract_single_value(const TensorSpec &spec, const TensorSpec::Address &addr, State &state) {
+ auto pos = spec.cells().find(addr);
+ if (pos == spec.cells().end()) {
+ return state.stash.create<DoubleValue>(0.0);
+ }
+ return state.stash.create<DoubleValue>(pos->second);
+}
+
+const Value &extract_tensor_subspace(const ValueType &my_type, const TensorSpec &spec, const TensorSpec::Address &addr, State &state) {
+ TensorSpec my_spec(my_type.to_spec());
+ for (const auto &cell: spec.cells()) {
+ bool keep = true;
+ TensorSpec::Address my_addr;
+ for (const auto &binding: cell.first) {
+ auto pos = addr.find(binding.first);
+ if (pos == addr.end()) {
+ my_addr.emplace(binding.first, binding.second);
+ } else {
+ if (!(pos->second == binding.second)) {
+ keep = false;
+ }
+ }
+ }
+ if (keep) {
+ my_spec.add(my_addr, cell.second);
+ }
+ }
+ return *state.stash.create<Value::UP>(state.engine.from_spec(my_spec));
+}
+
+void op_tensor_peek(State &state, uint64_t param) {
+ const Peek &self = unwrap_param<Peek>(param);
+ TensorSpec::Address addr;
+ size_t child_cnt = 0;
+ for (auto pos = self.spec().rbegin(); pos != self.spec().rend(); ++pos) {
+ if (std::holds_alternative<TensorSpec::Label>(pos->second)) {
+ addr.emplace(pos->first, std::get<TensorSpec::Label>(pos->second));
+ } else {
+ assert(std::holds_alternative<TensorFunction::Child>(pos->second));
+ size_t index(state.peek(child_cnt++).as_double());
+ size_t dim_idx = self.param_type().dimension_index(pos->first);
+ assert(dim_idx != ValueType::Dimension::npos);
+ const auto &param_dim = self.param_type().dimensions()[dim_idx];
+ if (param_dim.is_mapped()) {
+ addr.emplace(pos->first, vespalib::make_string("%zu", index));
+ } else {
+ addr.emplace(pos->first, index);
+ }
+ }
+ }
+ TensorSpec spec = state.engine.to_spec(state.peek(child_cnt++));
+ const Value &result = self.result_type().is_double()
+ ? extract_single_value(spec, addr, state)
+ : extract_tensor_subspace(self.result_type(), spec, addr, state);
+ state.pop_n_push(child_cnt, result);
+}
+
} // namespace vespalib::eval::tensor_function
//-----------------------------------------------------------------------------
@@ -302,6 +359,44 @@ Create::visit_children(vespalib::ObjectVisitor &visitor) const
//-----------------------------------------------------------------------------
+void
+Peek::push_children(std::vector<Child::CREF> &children) const
+{
+ children.emplace_back(_param);
+ for (const auto &dim: _spec) {
+ if (std::holds_alternative<Child>(dim.second)) {
+ children.emplace_back(std::get<Child>(dim.second));
+ }
+ }
+}
+
+Instruction
+Peek::compile_self(Stash &) const
+{
+ return Instruction(op_tensor_peek, wrap_param<Peek>(*this));
+}
+
+void
+Peek::visit_children(vespalib::ObjectVisitor &visitor) const
+{
+ ::visit(visitor, "param", _param.get());
+ for (const auto &dim: _spec) {
+ if (std::holds_alternative<TensorSpec::Label>(dim.second)) {
+ const auto &label = std::get<TensorSpec::Label>(dim.second);
+ if (label.is_mapped()) {
+ ::visit(visitor, dim.first, label.name);
+ } else {
+ ::visit(visitor, dim.first, label.index);
+ }
+ } else {
+ assert(std::holds_alternative<Child>(dim.second));
+ ::visit(visitor, dim.first, std::get<Child>(dim.second).get());
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+
Instruction
Rename::compile_self(Stash &stash) const
{
@@ -376,6 +471,15 @@ const Node &create(const ValueType &type, const std::map<TensorSpec::Address,Nod
return stash.create<Create>(type, spec);
}
+const Node &peek(const Node &param, const std::map<vespalib::string, std::variant<TensorSpec::Label, Node::CREF>> &spec, Stash &stash) {
+ std::vector<vespalib::string> dimensions;
+ for (const auto &dim_spec: spec) {
+ dimensions.push_back(dim_spec.first);
+ }
+ ValueType result_type = param.result_type().reduce(dimensions);
+ return stash.create<Peek>(result_type, param, spec);
+}
+
const Node &rename(const Node &child, const std::vector<vespalib::string> &from, const std::vector<vespalib::string> &to, Stash &stash) {
ValueType result_type = child.result_type().rename(from, to);
return stash.create<Rename>(result_type, child, from, to);
diff --git a/eval/src/vespa/eval/eval/tensor_function.h b/eval/src/vespa/eval/eval/tensor_function.h
index f82f1c80f0f..7c7e8318305 100644
--- a/eval/src/vespa/eval/eval/tensor_function.h
+++ b/eval/src/vespa/eval/eval/tensor_function.h
@@ -4,6 +4,7 @@
#include <memory>
#include <vector>
+#include <variant>
#include <vespa/vespalib/stllike/asciistream.h>
#include <vespa/vespalib/stllike/string.h>
#include <vespa/vespalib/util/arrayref.h>
@@ -295,6 +296,38 @@ public:
//-----------------------------------------------------------------------------
+class Peek : public Node
+{
+ using Super = Node;
+public:
+ using MyLabel = std::variant<TensorSpec::Label, Child>;
+private:
+ Child _param;
+ std::map<vespalib::string, MyLabel> _spec;
+public:
+ Peek(const ValueType &result_type_in, const Node &param,
+ const std::map<vespalib::string, std::variant<TensorSpec::Label, Node::CREF>> &spec)
+ : Node(result_type_in), _param(param), _spec()
+ {
+ for (const auto &dim: spec) {
+ if (std::holds_alternative<TensorSpec::Label>(dim.second)) {
+ _spec.emplace(dim.first, std::get<TensorSpec::Label>(dim.second));
+ } else {
+ assert(std::holds_alternative<Node::CREF>(dim.second));
+ _spec.emplace(dim.first, std::get<Node::CREF>(dim.second).get());
+ }
+ }
+ }
+ const std::map<vespalib::string, MyLabel> &spec() const { return _spec; }
+ const ValueType &param_type() const { return _param.get().result_type(); }
+ bool result_is_mutable() const override { return true; }
+ InterpretedFunction::Instruction compile_self(Stash &stash) const final override;
+ void push_children(std::vector<Child::CREF> &children) const final override;
+ void visit_children(vespalib::ObjectVisitor &visitor) const final override;
+};
+
+//-----------------------------------------------------------------------------
+
class Rename : public Op1
{
using Super = Op1;
@@ -349,6 +382,7 @@ const Node &map(const Node &child, map_fun_t function, Stash &stash);
const Node &join(const Node &lhs, const Node &rhs, join_fun_t function, Stash &stash);
const Node &concat(const Node &lhs, const Node &rhs, const vespalib::string &dimension, Stash &stash);
const Node &create(const ValueType &type, const std::map<TensorSpec::Address, Node::CREF> &spec, Stash &stash);
+const Node &peek(const Node &param, const std::map<vespalib::string, std::variant<TensorSpec::Label, Node::CREF>> &spec, Stash &stash);
const Node &rename(const Node &child, const std::vector<vespalib::string> &from, const std::vector<vespalib::string> &to, Stash &stash);
const Node &if_node(const Node &cond, const Node &true_child, const Node &false_child, Stash &stash);
diff --git a/eval/src/vespa/eval/eval/tensor_nodes.cpp b/eval/src/vespa/eval/eval/tensor_nodes.cpp
index 96e8fbef6a3..cee7a1aeb11 100644
--- a/eval/src/vespa/eval/eval/tensor_nodes.cpp
+++ b/eval/src/vespa/eval/eval/tensor_nodes.cpp
@@ -14,6 +14,7 @@ void TensorRename::accept(NodeVisitor &visitor) const { visitor.visit(*this); }
void TensorLambda::accept(NodeVisitor &visitor) const { visitor.visit(*this); }
void TensorConcat::accept(NodeVisitor &visitor) const { visitor.visit(*this); }
void TensorCreate::accept(NodeVisitor &visitor) const { visitor.visit(*this); }
+void TensorPeek ::accept(NodeVisitor &visitor) const { visitor.visit(*this); }
} // namespace vespalib::eval::nodes
} // namespace vespalib::eval
diff --git a/eval/src/vespa/eval/eval/tensor_nodes.h b/eval/src/vespa/eval/eval/tensor_nodes.h
index 93c1da4f2a8..a471f027675 100644
--- a/eval/src/vespa/eval/eval/tensor_nodes.h
+++ b/eval/src/vespa/eval/eval/tensor_nodes.h
@@ -259,6 +259,80 @@ public:
}
};
+class TensorPeek : public Node {
+public:
+ struct MyLabel {
+ vespalib::string label;
+ Node_UP expr;
+ MyLabel(vespalib::string label_in)
+ : label(label_in), expr() {}
+ MyLabel(Node_UP node)
+ : label(""), expr(std::move(node)) {}
+ bool is_expr() const { return bool(expr); }
+ };
+ using Spec = std::map<vespalib::string, MyLabel>;
+ using Dim = std::pair<vespalib::string, MyLabel>;
+ using DimList = std::vector<Dim>;
+private:
+ using DimRefs = std::vector<size_t>;
+ Node_UP _param;
+ DimList _dim_list;
+ DimRefs _expr_dims;
+public:
+ TensorPeek(Node_UP param, Spec spec)
+ : _param(std::move(param)), _dim_list(), _expr_dims()
+ {
+ for (auto &dim: spec) {
+ if (dim.second.is_expr()) {
+ _expr_dims.push_back(_dim_list.size());
+ }
+ _dim_list.emplace_back(dim.first, std::move(dim.second));
+ }
+ }
+ const Node &param() const { return *_param; }
+ const DimList &dim_list() const { return _dim_list; }
+ vespalib::string dump(DumpContext &ctx) const override {
+ vespalib::string str = _param->dump(ctx);
+ str += "{";
+ CommaTracker dim_list;
+ for (const auto &dim: _dim_list) {
+ dim_list.maybe_add_comma(str);
+ str += dim.first;
+ str += ":";
+ if (dim.second.is_expr()) {
+ vespalib::string expr = dim.second.expr->dump(ctx);
+ if (starts_with(expr, "(")) {
+ str += expr;
+ } else {
+ str += "(";
+ str += expr;
+ str += ")";
+ }
+ } else {
+ str += dim.second.label;
+ }
+ }
+ str += "}";
+ return str;
+ }
+ void accept(NodeVisitor &visitor) const override ;
+ size_t num_children() const override { return (1 + _expr_dims.size()); }
+ const Node &get_child(size_t idx) const override {
+ assert(idx < num_children());
+ if (idx == 0) {
+ return *_param;
+ } else {
+ return *_dim_list[_expr_dims[idx-1]].second.expr;
+ }
+ }
+ void detach_children(NodeHandler &handler) override {
+ handler.handle(std::move(_param));
+ for (size_t idx: _expr_dims) {
+ handler.handle(std::move(_dim_list[idx].second.expr));
+ }
+ }
+};
+
} // namespace vespalib::eval::nodes
} // namespace vespalib::eval
} // namespace vespalib
diff --git a/eval/src/vespa/eval/eval/test/eval_spec.cpp b/eval/src/vespa/eval/eval/test/eval_spec.cpp
index f89cf5edd0d..dc1e3405a6b 100644
--- a/eval/src/vespa/eval/eval/test/eval_spec.cpp
+++ b/eval/src/vespa/eval/eval/test/eval_spec.cpp
@@ -178,6 +178,7 @@ EvalSpec::add_tensor_operation_cases() {
add_expression({"a","b"}, "concat(a,b,x)");
add_expression({"a","b"}, "concat(a,b,y)");
add_expression({}, "tensor(x[3]):{{x:0}:0,{x:1}:1,{x:2}:2}");
+ add_expression({"a"}, "a{x:3}");
}
void
diff --git a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
index b5426d9ca29..4eb13a1964c 100644
--- a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
+++ b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
@@ -810,6 +810,25 @@ struct TestContext {
//-------------------------------------------------------------------------
+ void test_tensor_peek(const vespalib::string &expr, const TensorSpec &param, const TensorSpec &expect) {
+ TEST_DO(verify_result(Expr_TT(expr).eval(engine, param, spec(1.0)), expect));
+ }
+
+ void test_tensor_peek() {
+ auto param_double = spec({x({"0", "1"}),y(2)}, Seq({1.0, 2.0, 3.0, 4.0}));
+ auto param_float = spec(float_cells({x({"0", "1"}),y(2)}), Seq({1.0, 2.0, 3.0, 4.0}));
+ TEST_DO(test_tensor_peek("tensor(x[2]):[a{x:1,y:1},a{x:b-1,y:b-1}]", param_double, spec(x(2), Seq({4.0, 1.0}))));
+ TEST_DO(test_tensor_peek("tensor(x[2]):[a{x:1,y:1},a{x:b-1,y:b-1}]", param_float, spec(x(2), Seq({4.0, 1.0}))));
+ TEST_DO(test_tensor_peek("tensor<float>(x[2]):[a{x:1,y:1},a{x:b-1,y:b-1}]", param_double, spec(float_cells({x(2)}), Seq({4.0, 1.0}))));
+ TEST_DO(test_tensor_peek("tensor<float>(x[2]):[a{x:1,y:1},a{x:b-1,y:b-1}]", param_float, spec(float_cells({x(2)}), Seq({4.0, 1.0}))));
+ TEST_DO(test_tensor_peek("a{x:(b)}", param_double, spec(y(2), Seq({3.0, 4.0}))));
+ TEST_DO(test_tensor_peek("a{x:(b)}", param_float, spec(float_cells({y(2)}), Seq({3.0, 4.0}))));
+ TEST_DO(test_tensor_peek("a{y:(b)}", param_double, spec(x({"0", "1"}), Seq({2.0, 4.0}))));
+ TEST_DO(test_tensor_peek("a{y:(b)}", param_float, spec(float_cells({x({"0", "1"})}), Seq({2.0, 4.0}))));
+ }
+
+ //-------------------------------------------------------------------------
+
void verify_encode_decode(const TensorSpec &spec,
const TensorEngine &encode_engine,
const TensorEngine &decode_engine)
@@ -893,6 +912,7 @@ struct TestContext {
TEST_DO(test_rename());
TEST_DO(test_tensor_lambda());
TEST_DO(test_tensor_create());
+ TEST_DO(test_tensor_peek());
TEST_DO(test_binary_format());
}
};
diff --git a/eval/src/vespa/eval/eval/visit_stuff.h b/eval/src/vespa/eval/eval/visit_stuff.h
index ef21352535d..29efff3b5ec 100644
--- a/eval/src/vespa/eval/eval/visit_stuff.h
+++ b/eval/src/vespa/eval/eval/visit_stuff.h
@@ -2,6 +2,7 @@
#pragma once
+#include <vespa/vespalib/objects/visit.h>
#include <vespa/vespalib/stllike/string.h>
#include <vector>
diff --git a/eval/src/vespa/eval/tensor/default_tensor_engine.h b/eval/src/vespa/eval/tensor/default_tensor_engine.h
index b7a9e4d43e7..29f9811e170 100644
--- a/eval/src/vespa/eval/tensor/default_tensor_engine.h
+++ b/eval/src/vespa/eval/tensor/default_tensor_engine.h
@@ -19,10 +19,10 @@ public:
static const TensorEngine &ref() { return _engine; };
TensorSpec to_spec(const Value &value) const override;
- Value::UP from_spec(const TensorSpec &spec) const override;
+ std::unique_ptr<Value> from_spec(const TensorSpec &spec) const override;
void encode(const Value &value, nbostream &output) const override;
- Value::UP decode(nbostream &input) const override;
+ std::unique_ptr<Value> decode(nbostream &input) const override;
const TensorFunction &optimize(const TensorFunction &expr, Stash &stash) const override;
diff --git a/searchlib/src/vespa/searchlib/expression/resultvector.h b/searchlib/src/vespa/searchlib/expression/resultvector.h
index f1f863edf12..cf2f0730c37 100644
--- a/searchlib/src/vespa/searchlib/expression/resultvector.h
+++ b/searchlib/src/vespa/searchlib/expression/resultvector.h
@@ -97,7 +97,7 @@ public:
void reserve(size_t sz) override { _result.reserve(sz); }
void negate() override;
private:
- void visitMembers(vespalib::ObjectVisitor &visitor) const override { visit(visitor, "Vector", _result); }
+ void visitMembers(vespalib::ObjectVisitor &visitor) const override { ::visit(visitor, "Vector", _result); }
size_t onSize() const override { return _result.size(); }
const vespalib::Identifiable::RuntimeClass & getBaseClass() const override { return B::_RTClass; }
int64_t onGetInteger(size_t index) const override { return _result[index].getInteger(index); }
diff --git a/staging_vespalib/src/vespa/vespalib/objects/visit.hpp b/staging_vespalib/src/vespa/vespalib/objects/visit.hpp
index 797e77a7244..688fdf82b87 100644
--- a/staging_vespalib/src/vespa/vespalib/objects/visit.hpp
+++ b/staging_vespalib/src/vespa/vespalib/objects/visit.hpp
@@ -48,7 +48,7 @@ template<typename T>
void visit(vespalib::ObjectVisitor &self, const vespalib::string &name, const std::vector<T> &list) {
self.openStruct(name, "std::vector");
for (uint32_t i = 0; i < list.size(); ++i) {
- visit(self, vespalib::make_string("[%u]", i), list[i]);
+ ::visit(self, vespalib::make_string("[%u]", i), list[i]);
}
self.closeStruct();
}
@@ -57,7 +57,7 @@ template<typename T>
void visit(vespalib::ObjectVisitor &self, const vespalib::string &name, const vespalib::Array<T> &list) {
self.openStruct(name, "vespalib::Array");
for (uint32_t i = 0; i < list.size(); ++i) {
- visit(self, vespalib::make_string("[%u]", i), list[i]);
+ ::visit(self, vespalib::make_string("[%u]", i), list[i]);
}
self.closeStruct();
}