summaryrefslogtreecommitdiffstats
path: root/eval
diff options
context:
space:
mode:
authorHåvard Pettersen <havardpe@oath.com>2019-11-05 14:16:38 +0000
committerHåvard Pettersen <havardpe@oath.com>2019-11-08 10:22:11 +0000
commitc6418d267d1c6825ec0652a9b26178b7be527b9c (patch)
tree5f7b64ee60f9bc2af868fad937480d7fc14c51f3 /eval
parentb1b3da2b97d27e8efb796f3469ccd0a574dffacd (diff)
tensor create operation
Diffstat (limited to 'eval')
-rw-r--r--eval/src/tests/eval/function/function_test.cpp55
-rw-r--r--eval/src/tests/eval/node_types/node_types_test.cpp29
-rw-r--r--eval/src/tests/eval/tensor_function/tensor_function_test.cpp40
-rw-r--r--eval/src/vespa/eval/eval/CMakeLists.txt1
-rw-r--r--eval/src/vespa/eval/eval/basic_nodes.h17
-rw-r--r--eval/src/vespa/eval/eval/function.cpp180
-rw-r--r--eval/src/vespa/eval/eval/interpreted_function.h4
-rw-r--r--eval/src/vespa/eval/eval/key_gen.cpp1
-rw-r--r--eval/src/vespa/eval/eval/llvm/compiled_function.cpp4
-rw-r--r--eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp3
-rw-r--r--eval/src/vespa/eval/eval/make_tensor_function.cpp13
-rw-r--r--eval/src/vespa/eval/eval/node_types.cpp8
-rw-r--r--eval/src/vespa/eval/eval/node_visitor.h2
-rw-r--r--eval/src/vespa/eval/eval/string_stuff.cpp23
-rw-r--r--eval/src/vespa/eval/eval/string_stuff.h32
-rw-r--r--eval/src/vespa/eval/eval/tensor_function.cpp40
-rw-r--r--eval/src/vespa/eval/eval/tensor_function.h27
-rw-r--r--eval/src/vespa/eval/eval/tensor_nodes.cpp1
-rw-r--r--eval/src/vespa/eval/eval/tensor_nodes.h52
-rw-r--r--eval/src/vespa/eval/eval/test/eval_fixture.cpp1
-rw-r--r--eval/src/vespa/eval/eval/test/eval_spec.cpp1
-rw-r--r--eval/src/vespa/eval/eval/test/tensor_conformance.cpp14
22 files changed, 472 insertions, 76 deletions
diff --git a/eval/src/tests/eval/function/function_test.cpp b/eval/src/tests/eval/function/function_test.cpp
index c2561a86d6f..49793a62958 100644
--- a/eval/src/tests/eval/function/function_test.cpp
+++ b/eval/src/tests/eval/function/function_test.cpp
@@ -802,9 +802,9 @@ TEST("require that tensor rename dimension lists must have equal size") {
//-----------------------------------------------------------------------------
TEST("require that tensor lambda can be parsed") {
- EXPECT_EQUAL("tensor(x[10])(x)", Function::parse({""}, "tensor(x[10])(x)").dump());
- EXPECT_EQUAL("tensor(x[10],y[10])(x==y)", Function::parse({""}, "tensor(x[10],y[10])(x==y)").dump());
- EXPECT_EQUAL("tensor(x[10],y[10])(x==y)", Function::parse({""}, " tensor ( x [ 10 ] , y [ 10 ] ) ( x == y ) ").dump());
+ EXPECT_EQUAL("tensor(x[10])(x)", Function::parse({}, "tensor(x[10])(x)").dump());
+ EXPECT_EQUAL("tensor(x[10],y[10])(x==y)", Function::parse({}, "tensor(x[10],y[10])(x==y)").dump());
+ EXPECT_EQUAL("tensor(x[10],y[10])(x==y)", Function::parse({}, " tensor ( x [ 10 ] , y [ 10 ] ) ( x == y ) ").dump());
}
TEST("require that tensor lambda requires appropriate tensor type") {
@@ -819,6 +819,55 @@ TEST("require that tensor lambda can only use dimension names") {
//-----------------------------------------------------------------------------
+TEST("require that verbose tensor create can be parsed") {
+ auto dense = Function::parse("tensor(x[3]):{{x:0}:1,{x:1}:2,{x:2}:3}");
+ auto sparse = Function::parse("tensor(x{}):{{x:a}:1,{x:b}:2,{x:c}:3}");
+ auto mixed = Function::parse("tensor(x{},y[2]):{{x:a,y:0}:1,{x:a,y:1}:2}");
+ EXPECT_EQUAL("tensor(x[3]):{{x:0}:1,{x:1}:2,{x:2}:3}", dense.dump());
+ EXPECT_EQUAL("tensor(x{}):{{x:a}:1,{x:b}:2,{x:c}:3}", sparse.dump());
+ EXPECT_EQUAL("tensor(x{},y[2]):{{x:a,y:0}:1,{x:a,y:1}:2}", mixed.dump());
+}
+
+TEST("require that verbose tensor create can contain expressions") {
+ auto fun = Function::parse("tensor(x[2]):{{x:0}:1,{x:1}:2+a}");
+ EXPECT_EQUAL("tensor(x[2]):{{x:0}:1,{x:1}:(2+a)}", fun.dump());
+ ASSERT_EQUAL(fun.num_params(), 1u);
+ EXPECT_EQUAL(fun.param_name(0), "a");
+}
+
+TEST("require that verbose tensor create handles spaces and reordering of various elements") {
+ auto fun = Function::parse(" tensor ( y [ 2 ] , x [ 2 ] ) : { { x : 0 , y : 1 } : 2 , "
+ "{ y : 0 , x : 0 } : 1 , { y : 0 , x : 1 } : 3 , { x : 1 , y : 1 } : 4 } ");
+ EXPECT_EQUAL("tensor(x[2],y[2]):{{x:0,y:0}:1,{x:0,y:1}:2,{x:1,y:0}:3,{x:1,y:1}:4}", fun.dump());
+}
+
+TEST("require that verbose tensor create detects invalid tensor type") {
+ TEST_DO(verify_error("tensor(x[,y}):ignored",
+ "[tensor(x[,y}):]...[invalid tensor type]...[ignored]"));
+}
+
+TEST("require that verbose tensor create detects incomplete addresses") {
+ TEST_DO(verify_error("tensor(x[1],y[1]):{{x:0}:1}",
+ "[tensor(x[1],y[1]):{{x:0}]...[incomplete address: '{x:0}']...[:1}]"));
+}
+
+TEST("require that verbose tensor create detects invalid dimension names") {
+ TEST_DO(verify_error("tensor(x[1]):{{y:0}:1}",
+ "[tensor(x[1]):{{y]...[invalid dimension name: 'y']...[:0}:1}]"));
+}
+
+TEST("require that verbose tensor create detects out-of-bounds indexes for indexed dimensions") {
+ TEST_DO(verify_error("tensor(x[1]):{{x:1}:1}",
+ "[tensor(x[1]):{{x:1]...[dimension index too large: 1]...[}:1}]"));
+}
+
+TEST("require that verbose tensor create detects non-numeric indexes for indexed dimensions") {
+ TEST_DO(verify_error("tensor(x[1]):{{x:foo}:1}",
+ "[tensor(x[1]):{{x:]...[expected number]...[foo}:1}]"));
+}
+
+//-----------------------------------------------------------------------------
+
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 256c7b85f72..5504bb33137 100644
--- a/eval/src/tests/eval/node_types/node_types_test.cpp
+++ b/eval/src/tests/eval/node_types/node_types_test.cpp
@@ -33,14 +33,18 @@ struct TypeSpecExtractor : public vespalib::eval::SymbolExtractor {
}
};
-void verify(const vespalib::string &type_expr_in, const vespalib::string &type_spec, bool replace = true) {
+void verify(const vespalib::string &type_expr_in, const vespalib::string &type_spec, bool replace_first = true) {
vespalib::string type_expr = type_expr_in;
- if (replace) {
- // replace 'tensor' with 'Tensor' in type expression, see hack above
- for (size_t idx = type_expr.find("tensor");
- idx != type_expr.npos;
- idx = type_expr.find("tensor"))
- {
+ // replace 'tensor' with 'Tensor' in type expression, see hack above
+ size_t tensor_cnt = 0;
+ for (size_t idx = type_expr.find("tensor");
+ idx != type_expr.npos;
+ idx = type_expr.find("tensor", idx + 1))
+ {
+ // setting 'replace_first' to false will avoid replacing the
+ // first 'tensor' instance to let the parser handle it as an
+ // actual tensor generator.
+ if ((tensor_cnt++ > 0) || replace_first) {
type_expr[idx] = 'T';
}
}
@@ -228,6 +232,17 @@ TEST("require that lambda tensor resolves correct type") {
TEST_DO(verify("tensor<float>(x[5],y[10],z[15])(1.0)", "tensor<float>(x[5],y[10],z[15])", false));
}
+TEST("require that tensor create resolves correct type") {
+ TEST_DO(verify("tensor(x[3]):{{x:0}:double,{x:1}:double,{x:2}:double}", "tensor(x[3])", false));
+ TEST_DO(verify("tensor(x{}):{{x:a}:double,{x:b}:double,{x:c}:double}", "tensor(x{})", false));
+ TEST_DO(verify("tensor(x{},y[2]):{{x:a,y:0}:double,{x:a,y:1}:double}", "tensor(x{},y[2])", false));
+ TEST_DO(verify("tensor<float>(x[3]):{{x:0}:double,{x:1}:double,{x:2}:double}", "tensor<float>(x[3])", false));
+ TEST_DO(verify("tensor(x[3]):{{x:0}:double+double,{x:1}:double-double,{x:2}:double/double}", "tensor(x[3])", false));
+ TEST_DO(verify("tensor(x[3]):{{x:0}:double,{x:1}:reduce(tensor(x[2]),sum),{x:2}:double}", "tensor(x[3])", false));
+ TEST_DO(verify("tensor(x[3]):{{x:0}:double,{x:1}:tensor(x[2]),{x:2}:double}", "error", false));
+ TEST_DO(verify("tensor(x[3]):{{x:0}:double,{x:1}:error,{x:2}:double}", "error", false));
+}
+
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 7bca3d14c28..c30438b551d 100644
--- a/eval/src/tests/eval/tensor_function/tensor_function_test.cpp
+++ b/eval/src/tests/eval/tensor_function/tensor_function_test.cpp
@@ -40,12 +40,22 @@ struct EvalCtx {
const TensorFunction &compile(const tensor_function::Node &expr) {
return engine.optimize(expr, stash);
}
+ Value::UP make_double(double value) {
+ return engine.from_spec(TensorSpec("double").add({}, value));
+ }
Value::UP make_true() {
return engine.from_spec(TensorSpec("double").add({}, 1.0));
}
Value::UP make_false() {
return engine.from_spec(TensorSpec("double").add({}, 0.0));
}
+ Value::UP make_simple_vector() {
+ return engine.from_spec(
+ TensorSpec("tensor(x[3])")
+ .add({{"x",0}}, 1)
+ .add({{"x",1}}, 2)
+ .add({{"x",2}}, 3));
+ }
Value::UP make_tensor_matrix_first_half() {
return engine.from_spec(
TensorSpec("tensor(x[2])")
@@ -225,6 +235,28 @@ TEST("require that tensor concat works") {
TEST_DO(verify_equal(*expect, ctx.eval(prog)));
}
+TEST("require that tensor create works") {
+ EvalCtx ctx(SimpleTensorEngine::ref());
+ 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();
+ 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);
+ const auto &fun = create(ValueType::from_spec("tensor(x[3])"),
+ {
+ {{{"x", 0}}, a},
+ {{{"x", 1}}, b},
+ {{{"x", 2}}, c}
+ },
+ 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 rename works") {
EvalCtx ctx(SimpleTensorEngine::ref());
size_t a_id = ctx.add_tensor(ctx.make_tensor_matrix());
@@ -341,7 +373,13 @@ TEST("require that tensor function can be dumped for debugging") {
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 = inject(ValueType::from_spec("tensor(x[2])"), 1, stash);
+ const auto &x2_0 = const_value(my_value_1, stash);
+ const auto &x2_1 = const_value(my_value_2, stash);
+ const auto &x2 = create(ValueType::from_spec("tensor(x[2])"),
+ {
+ {{{"x", 0}}, x2_0},
+ {{{"x", 1}}, x2_1}
+ }, stash);
const auto &a3y10 = inject(ValueType::from_spec("tensor(a[3],y[10])"), 2, stash);
const auto &a3 = reduce(a3y10, Aggr::SUM, {"y"}, stash);
const auto &x3 = rename(a3, {"a"}, {"x"}, stash);
diff --git a/eval/src/vespa/eval/eval/CMakeLists.txt b/eval/src/vespa/eval/eval/CMakeLists.txt
index 23aa504f31e..20a7141393f 100644
--- a/eval/src/vespa/eval/eval/CMakeLists.txt
+++ b/eval/src/vespa/eval/eval/CMakeLists.txt
@@ -19,6 +19,7 @@ vespa_add_library(eval_eval OBJECT
param_usage.cpp
simple_tensor.cpp
simple_tensor_engine.cpp
+ string_stuff.cpp
tensor.cpp
tensor_engine.cpp
tensor_function.cpp
diff --git a/eval/src/vespa/eval/eval/basic_nodes.h b/eval/src/vespa/eval/eval/basic_nodes.h
index 6af0a04b31a..01ac86e4472 100644
--- a/eval/src/vespa/eval/eval/basic_nodes.h
+++ b/eval/src/vespa/eval/eval/basic_nodes.h
@@ -2,6 +2,7 @@
#pragma once
+#include "string_stuff.h"
#include <vespa/vespalib/util/hdr_abort.h>
#include <vespa/vespalib/stllike/string.h>
#include <vespa/vespalib/util/stringfmt.h>
@@ -86,22 +87,6 @@ struct Leaf : public Node {
void detach_children(NodeHandler &) override {}
};
-/**
- * Helper class used to insert commas on the appropriate places in
- * comma-separated textual lists.
- **/
-struct CommaTracker {
- bool first;
- CommaTracker() : first(true) {}
- void maybe_comma(vespalib::string &dst) {
- if (first) {
- first = false;
- } else {
- dst.push_back(',');
- }
- }
-};
-
class Number : public Leaf {
private:
double _value;
diff --git a/eval/src/vespa/eval/eval/function.cpp b/eval/src/vespa/eval/eval/function.cpp
index 65c70a5bd7d..1cdd0478181 100644
--- a/eval/src/vespa/eval/eval/function.cpp
+++ b/eval/src/vespa/eval/eval/function.cpp
@@ -269,6 +269,29 @@ public:
size_t operator_mark() const { return _operator_mark; }
void operator_mark(size_t mark) { _operator_mark = mark; }
+ bool find_list_end() {
+ skip_spaces();
+ char c = get();
+ return (c == /* eof */ '\0' || c == ')' || c == ']' || c == '}');
+ }
+
+ bool find_expression_end() {
+ return (find_list_end() || (get() == ','));
+ }
+
+ size_t init_expression() {
+ size_t old_mark = operator_mark();
+ operator_mark(num_operators());
+ return old_mark;
+ }
+
+ void fini_expression(size_t old_mark) {
+ while (num_operators() > operator_mark()) {
+ apply_operator();
+ }
+ operator_mark(old_mark);
+ }
+
void apply_until(const nodes::Operator &op) {
while ((_operator_stack.size() > _operator_mark) &&
(_operator_stack.back()->do_before(op)))
@@ -293,6 +316,11 @@ public:
void parse_value(ParseContext &ctx);
void parse_expression(ParseContext &ctx);
+Node_UP get_expression(ParseContext &ctx) {
+ parse_expression(ctx);
+ return ctx.pop_expression();
+}
+
int unhex(char c) {
if (c >= '0' && c <= '9') {
return (c - '0');
@@ -403,15 +431,24 @@ vespalib::string get_ident(ParseContext &ctx, bool allow_empty) {
return ident;
}
+size_t get_size_t(ParseContext &ctx) {
+ ctx.skip_spaces();
+ vespalib::string num;
+ for (; isdigit(ctx.get()); ctx.next()) {
+ num.push_back(ctx.get());
+ }
+ if (num.empty()) {
+ ctx.fail("expected number");
+ }
+ return atoi(num.c_str());
+}
+
void parse_if(ParseContext &ctx) {
- parse_expression(ctx);
- Node_UP cond = ctx.pop_expression();
+ Node_UP cond = get_expression(ctx);
ctx.eat(',');
- parse_expression(ctx);
- Node_UP true_expr = ctx.pop_expression();
+ Node_UP true_expr = get_expression(ctx);
ctx.eat(',');
- parse_expression(ctx);
- Node_UP false_expr = ctx.pop_expression();
+ Node_UP false_expr = get_expression(ctx);
double p_true = 0.5;
if (ctx.get() == ',') {
ctx.eat(',');
@@ -430,8 +467,7 @@ void parse_call(ParseContext &ctx, Call_UP call) {
if (i > 0) {
ctx.eat(',');
}
- parse_expression(ctx);
- call->bind_next(ctx.pop_expression());
+ call->bind_next(get_expression(ctx));
}
ctx.push_expression(std::move(call));
}
@@ -444,7 +480,7 @@ std::vector<vespalib::string> get_ident_list(ParseContext &ctx, bool wrapped) {
ctx.skip_spaces();
ctx.eat('(');
}
- for (ctx.skip_spaces(); !ctx.eos() && (ctx.get() != ')'); ctx.skip_spaces()) {
+ while (!ctx.find_list_end()) {
if (!list.empty() || !wrapped) {
ctx.eat(',');
}
@@ -484,11 +520,10 @@ Function parse_lambda(ParseContext &ctx, size_t num_params) {
ctx.push_resolve_context(params, nullptr);
ctx.skip_spaces();
ctx.eat('(');
- parse_expression(ctx);
+ Node_UP lambda_root = get_expression(ctx);
ctx.eat(')');
ctx.skip_spaces();
ctx.pop_resolve_context();
- Node_UP lambda_root = ctx.pop_expression();
if (param_names.size() != num_params) {
ctx.fail(make_string("expected lambda with %zu parameter(s), was %zu",
num_params, param_names.size()));
@@ -497,27 +532,23 @@ Function parse_lambda(ParseContext &ctx, size_t num_params) {
}
void parse_tensor_map(ParseContext &ctx) {
- parse_expression(ctx);
- Node_UP child = ctx.pop_expression();
+ Node_UP child = get_expression(ctx);
ctx.eat(',');
Function lambda = parse_lambda(ctx, 1);
ctx.push_expression(std::make_unique<nodes::TensorMap>(std::move(child), std::move(lambda)));
}
void parse_tensor_join(ParseContext &ctx) {
- parse_expression(ctx);
- Node_UP lhs = ctx.pop_expression();
+ Node_UP lhs = get_expression(ctx);
ctx.eat(',');
- parse_expression(ctx);
- Node_UP rhs = ctx.pop_expression();
+ Node_UP rhs = get_expression(ctx);
ctx.eat(',');
Function lambda = parse_lambda(ctx, 2);
ctx.push_expression(std::make_unique<nodes::TensorJoin>(std::move(lhs), std::move(rhs), std::move(lambda)));
}
void parse_tensor_reduce(ParseContext &ctx) {
- parse_expression(ctx);
- Node_UP child = ctx.pop_expression();
+ Node_UP child = get_expression(ctx);
ctx.eat(',');
auto aggr_name = get_ident(ctx, false);
auto maybe_aggr = AggrNames::from_name(aggr_name);
@@ -530,8 +561,7 @@ void parse_tensor_reduce(ParseContext &ctx) {
}
void parse_tensor_rename(ParseContext &ctx) {
- parse_expression(ctx);
- Node_UP child = ctx.pop_expression();
+ Node_UP child = get_expression(ctx);
ctx.eat(',');
auto from = get_idents(ctx);
ctx.skip_spaces();
@@ -545,15 +575,66 @@ void parse_tensor_rename(ParseContext &ctx) {
ctx.skip_spaces();
}
-void parse_tensor_lambda(ParseContext &ctx) {
- vespalib::string type_spec("tensor");
- while(!ctx.eos() && (ctx.get() != ')')) {
- type_spec.push_back(ctx.get());
- ctx.next();
+// '{a:w,x:0}'
+TensorSpec::Address get_tensor_address(ParseContext &ctx, const ValueType &type) {
+ TensorSpec::Address addr;
+ ctx.skip_spaces();
+ ctx.eat('{');
+ while (!ctx.find_list_end()) {
+ if (!addr.empty()) {
+ ctx.eat(',');
+ }
+ auto dim_name = get_ident(ctx, false);
+ size_t dim_idx = type.dimension_index(dim_name);
+ if (dim_idx != ValueType::Dimension::npos) {
+ const auto &dim = type.dimensions()[dim_idx];
+ ctx.skip_spaces();
+ ctx.eat(':');
+ if (dim.is_mapped()) {
+ addr.emplace(dim_name, get_ident(ctx, false));
+ } else {
+ size_t idx = get_size_t(ctx);
+ if (idx < dim.size) {
+ addr.emplace(dim_name, idx);
+ } else {
+ ctx.fail(make_string("dimension index too large: %zu", idx));
+ }
+ }
+ } else {
+ ctx.fail(make_string("invalid dimension name: '%s'", dim_name.c_str()));
+ }
}
- ctx.eat(')');
- type_spec.push_back(')');
- ValueType type = ValueType::from_spec(type_spec);
+ ctx.eat('}');
+ if (addr.size() != type.dimensions().size()) {
+ ctx.fail(make_string("incomplete address: '%s'", as_string(addr).c_str()));
+ }
+ return addr;
+}
+
+// pre: 'tensor<float>(a{},x[3]):' -> type
+// expect: '{{a:w,x:0}:1,{a:w,x:1}:2,{a:w,x:2}:3}'
+void parse_tensor_create(ParseContext &ctx, const ValueType &type) {
+ if (type.is_error()) {
+ ctx.fail("invalid tensor type");
+ return;
+ }
+ ctx.skip_spaces();
+ ctx.eat('{');
+ nodes::TensorCreate::Spec create_spec;
+ while (!ctx.find_list_end()) {
+ if (!create_spec.empty()) {
+ ctx.eat(',');
+ }
+ auto address = get_tensor_address(ctx, type);
+ ctx.skip_spaces();
+ ctx.eat(':');
+ create_spec.emplace(address, get_expression(ctx));
+ }
+ ctx.eat('}');
+ ctx.push_expression(std::make_unique<nodes::TensorCreate>(type, std::move(create_spec)));
+}
+
+void parse_tensor_lambda(ParseContext &ctx, const ValueType &type) {
if (!type.is_dense()) {
ctx.fail("invalid tensor type");
return;
@@ -563,19 +644,34 @@ void parse_tensor_lambda(ParseContext &ctx) {
ctx.push_resolve_context(params, nullptr);
ctx.skip_spaces();
ctx.eat('(');
- parse_expression(ctx);
+ Function lambda(get_expression(ctx), std::move(param_names));
ctx.eat(')');
ctx.pop_resolve_context();
- Function lambda(ctx.pop_expression(), std::move(param_names));
ctx.push_expression(std::make_unique<nodes::TensorLambda>(std::move(type), std::move(lambda)));
}
+void parse_tensor_generator(ParseContext &ctx) {
+ vespalib::string type_spec("tensor");
+ while(!ctx.eos() && (ctx.get() != ')')) {
+ type_spec.push_back(ctx.get());
+ ctx.next();
+ }
+ ctx.eat(')');
+ type_spec.push_back(')');
+ ValueType type = ValueType::from_spec(type_spec);
+ ctx.skip_spaces();
+ if (ctx.get() == ':') {
+ ctx.eat(':');
+ parse_tensor_create(ctx, type);
+ } else {
+ parse_tensor_lambda(ctx, type);
+ }
+}
+
void parse_tensor_concat(ParseContext &ctx) {
- parse_expression(ctx);
- Node_UP lhs = ctx.pop_expression();
+ Node_UP lhs = get_expression(ctx);
ctx.eat(',');
- parse_expression(ctx);
- Node_UP rhs = ctx.pop_expression();
+ Node_UP rhs = get_expression(ctx);
ctx.eat(',');
auto dimension = get_ident(ctx, false);
ctx.skip_spaces();
@@ -622,7 +718,7 @@ void parse_symbol_or_call(ParseContext &ctx) {
ParseContext::InputMark before_name = ctx.get_input_mark();
vespalib::string name = get_ident(ctx, true);
if (name == "tensor") {
- parse_tensor_lambda(ctx);
+ parse_tensor_generator(ctx);
} else if (!try_parse_call(ctx, name)) {
size_t id = parse_symbol(ctx, name, before_name);
if (name.empty()) {
@@ -716,20 +812,14 @@ bool parse_operator(ParseContext &ctx) {
}
void parse_expression(ParseContext &ctx) {
- size_t old_mark = ctx.operator_mark();
- ctx.operator_mark(ctx.num_operators());
+ size_t old_mark = ctx.init_expression();
bool expect_value = true;
for (;;) {
if (expect_value) {
parse_value(ctx);
}
- ctx.skip_spaces();
- if (ctx.eos() || ctx.get() == ')' || ctx.get() == ',') {
- while (ctx.num_operators() > ctx.operator_mark()) {
- ctx.apply_operator();
- }
- ctx.operator_mark(old_mark);
- return;
+ if (ctx.find_expression_end()) {
+ return ctx.fini_expression(old_mark);
}
expect_value = parse_operator(ctx);
}
diff --git a/eval/src/vespa/eval/eval/interpreted_function.h b/eval/src/vespa/eval/eval/interpreted_function.h
index dbe5345fc9b..e3e8d18b44f 100644
--- a/eval/src/vespa/eval/eval/interpreted_function.h
+++ b/eval/src/vespa/eval/eval/interpreted_function.h
@@ -50,6 +50,10 @@ public:
stack.pop_back();
stack.back() = value;
}
+ void pop_n_push(size_t n, const Value &value) {
+ stack.resize(stack.size() - (n - 1), value);
+ stack.back() = value;
+ }
};
class Context {
friend class InterpretedFunction;
diff --git a/eval/src/vespa/eval/eval/key_gen.cpp b/eval/src/vespa/eval/eval/key_gen.cpp
index 86908b331ba..63cd39b5856 100644
--- a/eval/src/vespa/eval/eval/key_gen.cpp
+++ b/eval/src/vespa/eval/eval/key_gen.cpp
@@ -41,6 +41,7 @@ struct KeyGen : public NodeVisitor, public NodeTraverser {
void visit(const TensorRename &) override { add_byte(14); } // dimensions should be part of key
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 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 4c911ee6e44..1ebf64ad269 100644
--- a/eval/src/vespa/eval/eval/llvm/compiled_function.cpp
+++ b/eval/src/vespa/eval/eval/llvm/compiled_function.cpp
@@ -131,7 +131,9 @@ CompiledFunction::detect_issues(const Function &function)
nodes::TensorReduce,
nodes::TensorRename,
nodes::TensorLambda,
- nodes::TensorConcat>(node)) {
+ nodes::TensorConcat,
+ nodes::TensorCreate>(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 c613a5fa94b..083e5d8593c 100644
--- a/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp
+++ b/eval/src/vespa/eval/eval/llvm/llvm_wrapper.cpp
@@ -479,6 +479,9 @@ struct FunctionBuilder : public NodeVisitor, public NodeTraverser {
void visit(const TensorConcat &node) override {
make_error(node.num_children());
}
+ void visit(const TensorCreate &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 ebf065f32a1..f0afb34376d 100644
--- a/eval/src/vespa/eval/eval/make_tensor_function.cpp
+++ b/eval/src/vespa/eval/eval/make_tensor_function.cpp
@@ -97,6 +97,16 @@ struct TensorFunctionBuilder : public NodeVisitor, public NodeTraverser {
stack.back() = tensor_function::concat(a, b, dimension, stash);
}
+ void make_create(const TensorCreate &node) {
+ assert(stack.size() >= node.num_children());
+ std::map<TensorSpec::Address, tensor_function::Node::CREF> spec;
+ for (size_t idx = node.num_children(); idx-- > 0; ) {
+ spec.emplace(node.get_child_address(idx), stack.back());
+ stack.pop_back();
+ }
+ stack.push_back(tensor_function::create(node.type(), 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();
@@ -184,6 +194,9 @@ struct TensorFunctionBuilder : public NodeVisitor, public NodeTraverser {
void visit(const TensorConcat &node) override {
make_concat(node, node.dimension());
}
+ void visit(const TensorCreate &node) override {
+ make_create(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 29ae02b9e65..f802a9ab03f 100644
--- a/eval/src/vespa/eval/eval/node_types.cpp
+++ b/eval/src/vespa/eval/eval/node_types.cpp
@@ -114,6 +114,14 @@ struct TypeResolver : public NodeVisitor, public NodeTraverser {
void visit(const TensorConcat &node) override {
bind_type(ValueType::concat(state.peek(1), state.peek(0), 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);
+ }
+ }
+ bind_type(node.type(), node);
+ }
void visit(const Add &node) override { resolve_op2(node); }
void visit(const Sub &node) override { resolve_op2(node); }
diff --git a/eval/src/vespa/eval/eval/node_visitor.h b/eval/src/vespa/eval/eval/node_visitor.h
index 10b389db792..d0996f370c2 100644
--- a/eval/src/vespa/eval/eval/node_visitor.h
+++ b/eval/src/vespa/eval/eval/node_visitor.h
@@ -35,6 +35,7 @@ struct NodeVisitor {
virtual void visit(const nodes::TensorRename &) = 0;
virtual void visit(const nodes::TensorLambda &) = 0;
virtual void visit(const nodes::TensorConcat &) = 0;
+ virtual void visit(const nodes::TensorCreate &) = 0;
// operator nodes
virtual void visit(const nodes::Add &) = 0;
@@ -103,6 +104,7 @@ struct EmptyNodeVisitor : NodeVisitor {
void visit(const nodes::TensorRename &) override {}
void visit(const nodes::TensorLambda &) override {}
void visit(const nodes::TensorConcat &) override {}
+ void visit(const nodes::TensorCreate &) 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/string_stuff.cpp b/eval/src/vespa/eval/eval/string_stuff.cpp
new file mode 100644
index 00000000000..70331a38cca
--- /dev/null
+++ b/eval/src/vespa/eval/eval/string_stuff.cpp
@@ -0,0 +1,23 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "string_stuff.h"
+#include <vespa/vespalib/util/stringfmt.h>
+
+namespace vespalib::eval {
+
+vespalib::string as_string(const TensorSpec::Address &address) {
+ CommaTracker label_list;
+ vespalib::string str = "{";
+ for (const auto &label: address) {
+ label_list.maybe_comma(str);
+ if (label.second.is_mapped()) {
+ str += make_string("%s:%s", label.first.c_str(), label.second.name.c_str());
+ } else {
+ str += make_string("%s:%zu", label.first.c_str(), label.second.index);
+ }
+ }
+ str += "}";
+ return str;
+}
+
+}
diff --git a/eval/src/vespa/eval/eval/string_stuff.h b/eval/src/vespa/eval/eval/string_stuff.h
new file mode 100644
index 00000000000..2e60691fa28
--- /dev/null
+++ b/eval/src/vespa/eval/eval/string_stuff.h
@@ -0,0 +1,32 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "tensor_spec.h"
+#include <vespa/vespalib/stllike/string.h>
+
+namespace vespalib::eval {
+
+/**
+ * Helper class used to insert commas on the appropriate places in
+ * comma-separated textual lists.
+ **/
+struct CommaTracker {
+ bool first;
+ CommaTracker() : first(true) {}
+ void maybe_comma(vespalib::string &dst) {
+ if (first) {
+ first = false;
+ } else {
+ dst.push_back(',');
+ }
+ }
+};
+
+/**
+ * Convert a tensor spec address into a string on the form:
+ * '{dim1:label,dim2:index, ...}'
+ **/
+vespalib::string as_string(const TensorSpec::Address &address);
+
+}
diff --git a/eval/src/vespa/eval/eval/tensor_function.cpp b/eval/src/vespa/eval/eval/tensor_function.cpp
index 10e589c832c..951b9772505 100644
--- a/eval/src/vespa/eval/eval/tensor_function.cpp
+++ b/eval/src/vespa/eval/eval/tensor_function.cpp
@@ -7,6 +7,7 @@
#include "tensor_engine.h"
#include "simple_tensor_engine.h"
#include "visit_stuff.h"
+#include "string_stuff.h"
#include <vespa/vespalib/objects/objectdumper.h>
#include <vespa/log/log.h>
@@ -117,6 +118,17 @@ void op_tensor_concat(State &state, uint64_t param) {
state.pop_pop_push(state.engine.concat(state.peek(1), state.peek(0), dimension, state.stash));
}
+void op_tensor_create(State &state, uint64_t param) {
+ const Create &self = unwrap_param<Create>(param);
+ TensorSpec spec(self.result_type().to_spec());
+ size_t i = 0;
+ for (auto pos = self.spec().rbegin(); pos != self.spec().rend(); ++pos) {
+ spec.add(pos->first, state.peek(i++).as_double());
+ }
+ const Value &result = *state.stash.create<Value::UP>(state.engine.from_spec(spec));
+ state.pop_n_push(i, result);
+}
+
} // namespace vespalib::eval::tensor_function
//-----------------------------------------------------------------------------
@@ -266,6 +278,30 @@ Concat::visit_self(vespalib::ObjectVisitor &visitor) const
//-----------------------------------------------------------------------------
+void
+Create::push_children(std::vector<Child::CREF> &children) const
+{
+ for (const auto &cell: _spec) {
+ children.emplace_back(cell.second);
+ }
+}
+
+Instruction
+Create::compile_self(Stash &) const
+{
+ return Instruction(op_tensor_create, wrap_param<Create>(*this));
+}
+
+void
+Create::visit_children(vespalib::ObjectVisitor &visitor) const
+{
+ for (const auto &cell: _spec) {
+ ::visit(visitor, ::vespalib::eval::as_string(cell.first), cell.second.get());
+ }
+}
+
+//-----------------------------------------------------------------------------
+
Instruction
Rename::compile_self(Stash &stash) const
{
@@ -336,6 +372,10 @@ const Node &concat(const Node &lhs, const Node &rhs, const vespalib::string &dim
return stash.create<Concat>(result_type, lhs, rhs, dimension);
}
+const Node &create(const ValueType &type, const std::map<TensorSpec::Address,Node::CREF> &spec, Stash &stash) {
+ return stash.create<Create>(type, 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 628943e51b2..f82f1c80f0f 100644
--- a/eval/src/vespa/eval/eval/tensor_function.h
+++ b/eval/src/vespa/eval/eval/tensor_function.h
@@ -7,7 +7,7 @@
#include <vespa/vespalib/stllike/asciistream.h>
#include <vespa/vespalib/stllike/string.h>
#include <vespa/vespalib/util/arrayref.h>
-#include "make_tensor_function.h"
+#include "tensor_spec.h"
#include "lazy_params.h"
#include "value_type.h"
#include "value.h"
@@ -239,7 +239,7 @@ class Join : public Op2
{
using Super = Op2;
private:
- join_fun_t _function;
+ join_fun_t _function;
public:
Join(const ValueType &result_type_in,
const TensorFunction &lhs_in,
@@ -273,6 +273,28 @@ public:
//-----------------------------------------------------------------------------
+class Create : public Node
+{
+ using Super = Node;
+private:
+ std::map<TensorSpec::Address, Child> _spec;
+public:
+ Create(const ValueType &result_type_in, const std::map<TensorSpec::Address, Node::CREF> &spec_in)
+ : Node(result_type_in), _spec()
+ {
+ for (const auto &cell: spec_in) {
+ _spec.emplace(cell.first, Child(cell.second));
+ }
+ }
+ const std::map<TensorSpec::Address, Child> &spec() const { return _spec; }
+ 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;
@@ -326,6 +348,7 @@ const Node &reduce(const Node &child, Aggr aggr, const std::vector<vespalib::str
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 &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 6abcd8fb9bc..96e8fbef6a3 100644
--- a/eval/src/vespa/eval/eval/tensor_nodes.cpp
+++ b/eval/src/vespa/eval/eval/tensor_nodes.cpp
@@ -13,6 +13,7 @@ void TensorReduce::accept(NodeVisitor &visitor) const { visitor.visit(*this); }
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); }
} // 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 2a13f86f017..776275f5884 100644
--- a/eval/src/vespa/eval/eval/tensor_nodes.h
+++ b/eval/src/vespa/eval/eval/tensor_nodes.h
@@ -4,9 +4,12 @@
#include "basic_nodes.h"
#include "function.h"
+#include "tensor_spec.h"
+#include "aggr.h"
+#include "string_stuff.h"
#include <vespa/vespalib/stllike/string.h>
+#include <vector>
#include <map>
-#include "aggr.h"
namespace vespalib {
namespace eval {
@@ -209,6 +212,53 @@ public:
}
};
+class TensorCreate : public Node {
+public:
+ using Spec = std::map<TensorSpec::Address, Node_UP>;
+private:
+ using Child = std::pair<TensorSpec::Address, Node_UP>;
+ using ChildList = std::vector<Child>;
+ ValueType _type;
+ ChildList _cells;
+public:
+ TensorCreate(ValueType type_in, Spec spec)
+ : _type(std::move(type_in)), _cells()
+ {
+ for (auto &cell: spec) {
+ _cells.emplace_back(cell.first, std::move(cell.second));
+ }
+ }
+ const ValueType &type() const { return _type; }
+ vespalib::string dump(DumpContext &ctx) const override {
+ vespalib::string str = _type.to_spec();
+ str += ":{";
+ CommaTracker child_list;
+ for (const Child &child: _cells) {
+ child_list.maybe_comma(str);
+ str += as_string(child.first);
+ str += ":";
+ str += child.second->dump(ctx);
+ }
+ str += "}";
+ return str;
+ }
+ void accept(NodeVisitor &visitor) const override ;
+ size_t num_children() const override { return _cells.size(); }
+ const Node &get_child(size_t idx) const override {
+ assert(idx < _cells.size());
+ return *_cells[idx].second;
+ }
+ const TensorSpec::Address &get_child_address(size_t idx) const {
+ assert(idx < _cells.size());
+ return _cells[idx].first;
+ }
+ void detach_children(NodeHandler &handler) override {
+ for (Child &child: _cells) {
+ handler.handle(std::move(child.second));
+ }
+ }
+};
+
} // namespace vespalib::eval::nodes
} // namespace vespalib::eval
} // namespace vespalib
diff --git a/eval/src/vespa/eval/eval/test/eval_fixture.cpp b/eval/src/vespa/eval/eval/test/eval_fixture.cpp
index 3f5fa4d72bb..30e55d0418b 100644
--- a/eval/src/vespa/eval/eval/test/eval_fixture.cpp
+++ b/eval/src/vespa/eval/eval/test/eval_fixture.cpp
@@ -2,6 +2,7 @@
#include <vespa/vespalib/testkit/test_kit.h>
#include "eval_fixture.h"
+#include <vespa/eval/eval/make_tensor_function.h>
namespace vespalib::eval::test {
diff --git a/eval/src/vespa/eval/eval/test/eval_spec.cpp b/eval/src/vespa/eval/eval/test/eval_spec.cpp
index baa3ee989d4..f89cf5edd0d 100644
--- a/eval/src/vespa/eval/eval/test/eval_spec.cpp
+++ b/eval/src/vespa/eval/eval/test/eval_spec.cpp
@@ -177,6 +177,7 @@ EvalSpec::add_tensor_operation_cases() {
add_expression({}, "tensor(x[10],y[10])(x==y)");
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}");
}
void
diff --git a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
index dc39dfb04a7..b5426d9ca29 100644
--- a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
+++ b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
@@ -797,6 +797,19 @@ struct TestContext {
//-------------------------------------------------------------------------
+ void test_tensor_create(const vespalib::string &expr, double a, double b, const TensorSpec &expect) {
+ TEST_DO(verify_result(Expr_TT(expr).eval(engine, spec(a), spec(b)), expect));
+ }
+
+ void test_tensor_create() {
+ TEST_DO(test_tensor_create("tensor(x[3]):{{x:0}:a,{x:1}:b,{x:2}:3}", 1, 2, spec(x(3), N())));
+ TEST_DO(test_tensor_create("tensor<float>(x[3]):{{x:0}:a,{x:1}:b,{x:2}:3}", 1, 2, spec(float_cells({x(3)}), N())));
+ TEST_DO(test_tensor_create("tensor(x{}):{{x:a}:a,{x:b}:b,{x:c}:3}", 1, 2, spec(x({"a", "b", "c"}), N())));
+ TEST_DO(test_tensor_create("tensor(x{},y[2]):{{x:a,y:0}:a,{x:a,y:1}:b}", 1, 2, spec({x({"a"}),y(2)}, N())));
+ }
+
+ //-------------------------------------------------------------------------
+
void verify_encode_decode(const TensorSpec &spec,
const TensorEngine &encode_engine,
const TensorEngine &decode_engine)
@@ -879,6 +892,7 @@ struct TestContext {
TEST_DO(test_concat());
TEST_DO(test_rename());
TEST_DO(test_tensor_lambda());
+ TEST_DO(test_tensor_create());
TEST_DO(test_binary_format());
}
};