summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--vespalib/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp12
-rw-r--r--vespalib/src/vespa/vespalib/eval/test/tensor_conformance.cpp417
-rw-r--r--vespalib/src/vespa/vespalib/eval/test/tensor_conformance.h2
-rw-r--r--vespalib/src/vespa/vespalib/eval/value.cpp8
-rw-r--r--vespalib/src/vespa/vespalib/eval/value.h8
5 files changed, 315 insertions, 132 deletions
diff --git a/vespalib/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp b/vespalib/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp
index 91dea9fb0ce..1f96b64d170 100644
--- a/vespalib/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp
+++ b/vespalib/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp
@@ -8,12 +8,16 @@ using vespalib::eval::SimpleTensorEngine;
using vespalib::eval::test::TensorConformance;
using vespalib::tensor::DefaultTensorEngine;
-TEST("require that reference tensor implementation passes conformance test") {
- TEST_DO(TensorConformance::run_tests(SimpleTensorEngine::ref()));
+TEST("require that reference tensor implementation passes all conformance tests") {
+ TEST_DO(TensorConformance::run_tests(SimpleTensorEngine::ref(), true));
}
-IGNORE_TEST("require that production tensor implementation passes conformance test") {
- TEST_DO(TensorConformance::run_tests(DefaultTensorEngine::ref()));
+IGNORE_TEST("require that production tensor implementation passes non-mixed conformance tests") {
+ TEST_DO(TensorConformance::run_tests(DefaultTensorEngine::ref(), false));
+}
+
+IGNORE_TEST("require that production tensor implementation passes all conformance tests") {
+ TEST_DO(TensorConformance::run_tests(DefaultTensorEngine::ref(), true));
}
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.cpp b/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.cpp
index c844a59ca47..5d9501a609e 100644
--- a/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.cpp
+++ b/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.cpp
@@ -6,6 +6,7 @@
#include <vespa/vespalib/eval/simple_tensor_engine.h>
#include <vespa/vespalib/eval/tensor_spec.h>
#include <vespa/vespalib/eval/function.h>
+#include <vespa/vespalib/eval/tensor_function.h>
#include <vespa/vespalib/eval/interpreted_function.h>
namespace vespalib {
@@ -26,20 +27,20 @@ namespace {
// Random access sequence of numbers
struct Sequence {
- virtual double get(size_t i) const = 0;
+ virtual double operator[](size_t i) const = 0;
virtual ~Sequence() {}
};
// Sequence of natural numbers (starting at 1)
struct N : Sequence {
- double get(size_t i) const override { return (1.0 + i); }
+ double operator[](size_t i) const override { return (1.0 + i); }
};
// Sequence of another sequence divided by 10
struct Div10 : Sequence {
const Sequence &seq;
Div10(const Sequence &seq_in) : seq(seq_in) {}
- double get(size_t i) const override { return (seq.get(i) / 10.0); }
+ double operator[](size_t i) const override { return (seq[i] / 10.0); }
};
// Sequence of a unary operator applied to a sequence
@@ -47,46 +48,86 @@ struct OpSeq : Sequence {
const Sequence &seq;
const UnaryOperation &op;
OpSeq(const Sequence &seq_in, const UnaryOperation &op_in) : seq(seq_in), op(op_in) {}
- double get(size_t i) const override { return op.eval(seq.get(i)); }
+ double operator[](size_t i) const override { return op.eval(seq[i]); }
};
// pre-defined sequence of numbers
struct Seq : Sequence {
std::vector<double> seq;
+ Seq() : seq() {}
Seq(const std::vector<double> &seq_in) : seq(seq_in) {}
- double get(size_t i) const override {
+ double operator[](size_t i) const override {
ASSERT_LESS(i, seq.size());
return seq[i];
}
};
+// Random access bit mask
+struct Mask {
+ virtual bool operator[](size_t i) const = 0;
+ virtual ~Mask() {}
+};
+
+// Mask with all bits set
+struct All : Mask {
+ bool operator[](size_t) const override { return true; }
+};
+
+// Mask with no bits set
+struct None : Mask {
+ bool operator[](size_t) const override { return false; }
+};
+
+// pre-defined mask
+struct Bits : Mask {
+ std::vector<bool> bits;
+ Bits(const std::vector<bool> &bits_in) : bits(bits_in) {}
+ bool operator[](size_t i) const override {
+ ASSERT_LESS(i, bits.size());
+ return bits[i];
+ }
+};
+
// custom op1
struct MyOp : CustomUnaryOperation {
- double eval(double b) const override { return ((b + 1) * 2); }
+ double eval(double a) const override { return ((a + 1) * 2); }
};
// A collection of labels for a single dimension
-struct Space {
- vespalib::string name;
+struct Domain {
+ vespalib::string dimension;
size_t size; // indexed
std::vector<vespalib::string> keys; // mapped
- Space(const vespalib::string &name_in, size_t size_in)
- : name(name_in), size(size_in), keys() {}
- Space(const vespalib::string &name_in, const std::vector<vespalib::string> &keys_in)
- : name(name_in), size(0), keys(keys_in) {}
+ Domain(const vespalib::string &dimension_in, size_t size_in)
+ : dimension(dimension_in), size(size_in), keys() {}
+ Domain(const vespalib::string &dimension_in, const std::vector<vespalib::string> &keys_in)
+ : dimension(dimension_in), size(0), keys(keys_in) {}
};
+using Layout = std::vector<Domain>;
+
+Domain x() { return Domain("x", {}); }
+Domain x(size_t size) { return Domain("x", size); }
+Domain x(const std::vector<vespalib::string> &keys) { return Domain("x", keys); }
+
+Domain y() { return Domain("y", {}); }
+Domain y(size_t size) { return Domain("y", size); }
+Domain y(const std::vector<vespalib::string> &keys) { return Domain("y", keys); }
+
+Domain z() { return Domain("z", {}); }
+Domain z(size_t size) { return Domain("z", size); }
+Domain z(const std::vector<vespalib::string> &keys) { return Domain("z", keys); }
// Infer the tensor type spanned by the given spaces
-vespalib::string infer_type(const std::vector<Space> &spaces) {
- if (spaces.empty()) {
+vespalib::string infer_type(const Layout &layout) {
+ if (layout.empty()) {
return "double";
}
std::vector<ValueType::Dimension> dimensions;
- for (const auto &space: spaces) {
- if (space.size == 0) {
- dimensions.emplace_back(space.name); // mapped
+ for (const auto &domain: layout) {
+ if (domain.size == 0) {
+ dimensions.emplace_back(domain.dimension); // mapped
} else {
- dimensions.emplace_back(space.name, space.size); // indexed
+ dimensions.emplace_back(domain.dimension, domain.size); // indexed
}
}
return ValueType::tensor_type(dimensions).to_spec();
@@ -99,47 +140,192 @@ private:
using Label = TensorSpec::Label;
using Address = TensorSpec::Address;
- const std::vector<Space> &_spaces;
- const Sequence &_seq;
- TensorSpec _spec;
- Address _addr;
- size_t _gen_idx;
-
- void generate(size_t space_idx) {
- if (space_idx == _spaces.size()) {
- _spec.add(_addr, _seq.get(_gen_idx++));
+ const Layout &_layout;
+ const Sequence &_seq;
+ const Mask &_mask;
+ TensorSpec _spec;
+ Address _addr;
+ size_t _idx;
+
+ void generate(size_t layout_idx) {
+ if (layout_idx == _layout.size()) {
+ if (_mask[_idx]) {
+ _spec.add(_addr, _seq[_idx]);
+ }
+ ++_idx;
} else {
- const Space &space = _spaces[space_idx];
- if (space.size > 0) { // indexed
- for (size_t i = 0; i < space.size; ++i) {
- _addr.emplace(space.name, Label(i)).first->second = Label(i);
- generate(space_idx + 1);
+ const Domain &domain = _layout[layout_idx];
+ if (domain.size > 0) { // indexed
+ for (size_t i = 0; i < domain.size; ++i) {
+ _addr.emplace(domain.dimension, Label(i)).first->second = Label(i);
+ generate(layout_idx + 1);
}
} else { // mapped
- for (const vespalib::string &key: space.keys) {
- _addr.emplace(space.name, Label(key)).first->second = Label(key);
- generate(space_idx + 1);
+ for (const vespalib::string &key: domain.keys) {
+ _addr.emplace(domain.dimension, Label(key)).first->second = Label(key);
+ generate(layout_idx + 1);
}
}
}
}
public:
- TensorSpecBuilder(const std::vector<Space> &spaces, const Sequence &seq)
- : _spaces(spaces), _seq(seq), _spec(infer_type(spaces)), _addr(), _gen_idx(0) {}
+ TensorSpecBuilder(const Layout &layout, const Sequence &seq, const Mask &mask)
+ : _layout(layout), _seq(seq), _mask(mask), _spec(infer_type(layout)), _addr(), _idx(0) {}
TensorSpec build() {
generate(0);
return _spec;
}
};
+using Tensor_UP = std::unique_ptr<Tensor>;
+
+// small utility used to capture passed tensor references for uniform handling
+struct TensorRef {
+ const Tensor &ref;
+ TensorRef(const Tensor &ref_in) : ref(ref_in) {}
+ TensorRef(const Tensor_UP &up_ref) : ref(*(up_ref.get())) {}
+};
+
+// abstract evaluation verification wrapper
+struct Eval {
+ virtual void verify(const TensorEngine &engine, TensorRef expect) const {
+ (void) engine;
+ (void) expect;
+ TEST_ERROR("wrong signature");
+ }
+ virtual void verify(const TensorEngine &engine, TensorRef a, TensorRef expect) const {
+ (void) engine;
+ (void) a;
+ (void) expect;
+ TEST_ERROR("wrong signature");
+ }
+ virtual ~Eval() {}
+};
+
+// expression(void) -> tensor
+struct Expr_V_T : Eval {
+ const vespalib::string &expr;
+ Expr_V_T(const vespalib::string &expr_in) : expr(expr_in) {}
+ void verify(const TensorEngine &engine, TensorRef expect) const override {
+ InterpretedFunction::Context ctx;
+ InterpretedFunction ifun(engine, Function::parse(expr));
+ const Value &result = ifun.eval(ctx);
+ if (EXPECT_TRUE(result.is_tensor())) {
+ const Tensor *actual = result.as_tensor();
+ EXPECT_EQUAL(*actual, expect.ref);
+ }
+ }
+};
+
+// expression(tensor) -> tensor
+struct Expr_T_T : Eval {
+ const vespalib::string &expr;
+ Expr_T_T(const vespalib::string &expr_in) : expr(expr_in) {}
+ void verify(const TensorEngine &engine, TensorRef a, TensorRef expect) const override {
+ TensorValue va(a.ref);
+ InterpretedFunction::Context ctx;
+ InterpretedFunction ifun(engine, Function::parse(expr));
+ ctx.add_param(va);
+ const Value &result = ifun.eval(ctx);
+ if (EXPECT_TRUE(result.is_tensor())) {
+ const Tensor *actual = result.as_tensor();
+ EXPECT_EQUAL(*actual, expect.ref);
+ }
+ }
+};
+
+// evaluate tensor map operation using tensor engine immediate api
+struct ImmediateMap : Eval {
+ const UnaryOperation &op;
+ ImmediateMap(const UnaryOperation &op_in) : op(op_in) {}
+ void verify(const TensorEngine &engine, TensorRef a, TensorRef expect) const override {
+ Stash stash;
+ const Value &result = engine.map(op, a.ref, stash);
+ if (EXPECT_TRUE(result.is_tensor())) {
+ const Tensor *actual = result.as_tensor();
+ EXPECT_EQUAL(*actual, expect.ref);
+ }
+ }
+};
+
+// input needed to evaluate a map operation in retained mode
+struct TensorMapInput : TensorFunction::Input {
+ TensorValue tensor;
+ const UnaryOperation &map_op;
+ TensorMapInput(TensorRef in, const UnaryOperation &op) : tensor(in.ref), map_op(op) {}
+ const Value &get_tensor(size_t id) const override {
+ ASSERT_EQUAL(id, 11u);
+ return tensor;
+ }
+ const UnaryOperation &get_map_operation(size_t id) const {
+ ASSERT_EQUAL(id, 22u);
+ return map_op;
+ }
+};
+
+// evaluate tensor map operation using tensor engine retained api
+struct RetainedMap : Eval {
+ const UnaryOperation &op;
+ RetainedMap(const UnaryOperation &op_in) : op(op_in) {}
+ void verify(const TensorEngine &engine, TensorRef a, TensorRef expect) const override {
+ auto a_type = a.ref.engine().type_of(a.ref);
+ auto ir = tensor_function::map(22, tensor_function::inject(a_type, 11));
+ auto fun = engine.compile(std::move(ir));
+ TensorMapInput input(a, op);
+ Stash stash;
+ const Value &result = fun->eval(input, stash);
+ if (EXPECT_TRUE(result.is_tensor())) {
+ const Tensor *actual = result.as_tensor();
+ EXPECT_EQUAL(*actual, expect.ref);
+ }
+ }
+};
+
+// placeholder used for unused values in a sequence
+const double X = 31212.0;
+
// Test wrapper to avoid passing global test parameters around
struct TestContext {
+
const TensorEngine &engine;
- TestContext(const TensorEngine &engine_in) : engine(engine_in) {}
+ bool test_mixed_cases;
+ TestContext(const TensorEngine &engine_in, bool test_mixed_cases_in)
+ : engine(engine_in), test_mixed_cases(test_mixed_cases_in) {}
+
+ bool mixed() {
+ if (!test_mixed_cases) {
+ fprintf(stderr, "skipping some tests since mixed testing is disabled\n");
+ }
+ return test_mixed_cases;
+ }
- std::unique_ptr<Tensor> tensor(const std::vector<Space> &spaces, const Sequence &seq) {
- return engine.create(TensorSpecBuilder(spaces, seq).build());
+ Tensor_UP tensor(const Layout &layout, const Sequence &seq, const Mask &mask) {
+ TensorSpec spec = TensorSpecBuilder(layout, seq, mask).build();
+ Tensor_UP result = engine.create(spec);
+ EXPECT_EQUAL(spec.type(), engine.type_of(*result).to_spec());
+ return result;
+ }
+ Tensor_UP tensor(const Layout &layout, const Sequence &seq) {
+ return tensor(layout, seq, All());
+ }
+ Tensor_UP tensor(const Layout &layout) {
+ return tensor(layout, Seq(), None());
+ }
+ Tensor_UP tensor(const Domain &domain, const Sequence &seq, const Mask &mask) {
+ return tensor(Layout({domain}), seq, mask);
+ }
+ Tensor_UP tensor(const Domain &domain, const Sequence &seq) {
+ return tensor(Layout({domain}), seq);
+ }
+ Tensor_UP tensor(const Domain &domain) {
+ return tensor(Layout({domain}));
+ }
+ Tensor_UP tensor(double value) {
+ return tensor(Layout({}), Seq({value}));
+ }
+ Tensor_UP tensor() {
+ return tensor(Layout({}));
}
void verify_create_type(const vespalib::string &type_spec) {
@@ -148,19 +334,13 @@ struct TestContext {
EXPECT_EQUAL(type_spec, engine.type_of(*tensor).to_spec());
}
- void verify_not_equal(const Tensor &a, const Tensor &b) {
- EXPECT_FALSE(a == b);
- EXPECT_FALSE(b == a);
+ void verify_not_equal(TensorRef a, TensorRef b) {
+ EXPECT_FALSE(a.ref == b.ref);
+ EXPECT_FALSE(b.ref == a.ref);
}
- void verify_verbatim_tensor(const vespalib::string &tensor_expr, const Tensor &expect) {
- InterpretedFunction::Context ctx;
- InterpretedFunction ifun(engine, Function::parse(tensor_expr));
- const Value &result = ifun.eval(ctx);
- if (EXPECT_TRUE(result.is_tensor())) {
- const Tensor *actual = result.as_tensor();
- EXPECT_EQUAL(*actual, expect);
- }
+ void verify_verbatim_tensor(const vespalib::string &tensor_expr, TensorRef expect) {
+ Expr_V_T(tensor_expr).verify(engine, expect);
}
void test_tensor_create_type() {
@@ -169,85 +349,82 @@ struct TestContext {
TEST_DO(verify_create_type("tensor(x{},y{})"));
TEST_DO(verify_create_type("tensor(x[5])"));
TEST_DO(verify_create_type("tensor(x[5],y[10])"));
- TEST_DO(verify_create_type("tensor(x{},y[10])"));
- TEST_DO(verify_create_type("tensor(x[5],y{})"));
+ if (mixed()) {
+ TEST_DO(verify_create_type("tensor(x{},y[10])"));
+ TEST_DO(verify_create_type("tensor(x[5],y{})"));
+ }
}
void test_tensor_inequality() {
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("double")),
- *engine.create(TensorSpec("tensor(x{})"))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("double")),
- *engine.create(TensorSpec("tensor(x[1])"))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})")),
- *engine.create(TensorSpec("tensor(y{})"))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])")),
- *engine.create(TensorSpec("tensor(x[2])"))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])")),
- *engine.create(TensorSpec("tensor(y[1])"))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})")),
- *engine.create(TensorSpec("tensor(x[1])"))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("double").add({}, 1)),
- *engine.create(TensorSpec("double").add({}, 2))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})").add({{"x", "a"}}, 1)),
- *engine.create(TensorSpec("tensor(x{})").add({{"x", "a"}}, 2))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})").add({{"x", "a"}}, 1)),
- *engine.create(TensorSpec("tensor(x{})").add({{"x", "b"}}, 1))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})").add({{"x", "a"}}, 1)),
- *engine.create(TensorSpec("tensor(x{},y{})").add({{"x", "a"},{"y", "a"}}, 1))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])").add({{"x", 0}}, 1)),
- *engine.create(TensorSpec("tensor(x[1])").add({{"x", 0}}, 2))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])").add({{"x", 0}}, 1)),
- *engine.create(TensorSpec("tensor(x[2])").add({{"x", 0}}, 1))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[2])").add({{"x", 0}}, 1)),
- *engine.create(TensorSpec("tensor(x[2])").add({{"x", 1}}, 1))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])").add({{"x", 0}}, 1)),
- *engine.create(TensorSpec("tensor(x[1],y[1])").add({{"x", 0},{"y", 0}}, 1))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{},y[1])").add({{"x", "a"},{"y", 0}}, 1)),
- *engine.create(TensorSpec("tensor(x{},y[1])").add({{"x", "a"},{"y", 0}}, 2))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{},y[1])").add({{"x", "a"},{"y", 0}}, 1)),
- *engine.create(TensorSpec("tensor(x{},y[1])").add({{"x", "b"},{"y", 0}}, 1))));
- TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[2],y{})").add({{"x", 0},{"y", "a"}}, 1)),
- *engine.create(TensorSpec("tensor(x[2],y{})").add({{"x", 1},{"y", "a"}}, 1))));
+ TEST_DO(verify_not_equal(tensor(1.0), tensor(2.0)));
+ TEST_DO(verify_not_equal(tensor(), tensor(x())));
+ TEST_DO(verify_not_equal(tensor(), tensor(x(1))));
+ TEST_DO(verify_not_equal(tensor(x()), tensor(x(1))));
+ TEST_DO(verify_not_equal(tensor(x()), tensor(y())));
+ TEST_DO(verify_not_equal(tensor(x(1)), tensor(x(2))));
+ TEST_DO(verify_not_equal(tensor(x(1)), tensor(y(1))));
+ TEST_DO(verify_not_equal(tensor(x({"a"}), Seq({1})), tensor(x({"a"}), Seq({2}))));
+ TEST_DO(verify_not_equal(tensor(x({"a"}), Seq({1})), tensor(x({"b"}), Seq({1}))));
+ TEST_DO(verify_not_equal(tensor(x({"a"}), Seq({1})), tensor({x({"a"}),y({"a"})}, Seq({1}))));
+ TEST_DO(verify_not_equal(tensor(x(1), Seq({1})), tensor(x(1), Seq({2}))));
+ TEST_DO(verify_not_equal(tensor(x(1), Seq({1})), tensor(x(2), Seq({1}), Bits({1,0}))));
+ TEST_DO(verify_not_equal(tensor(x(2), Seq({1,1}), Bits({1,0})),
+ tensor(x(2), Seq({1,1}), Bits({0,1}))));
+ TEST_DO(verify_not_equal(tensor(x(1), Seq({1})), tensor({x(1),y(1)}, Seq({1}))));
+ if (mixed()) {
+ TEST_DO(verify_not_equal(tensor({x({"a"}),y(1)}, Seq({1})), tensor({x({"a"}),y(1)}, Seq({2}))));
+ TEST_DO(verify_not_equal(tensor({x({"a"}),y(1)}, Seq({1})), tensor({x({"b"}),y(1)}, Seq({1}))));
+ TEST_DO(verify_not_equal(tensor({x(2),y({"a"})}, Seq({1}), Bits({1,0})),
+ tensor({x(2),y({"a"})}, Seq({X,1}), Bits({0,1}))));
+ }
}
void test_verbatim_tensors() {
- TEST_DO(verify_verbatim_tensor("{}", *engine.create(TensorSpec("double"))));
- TEST_DO(verify_verbatim_tensor("{{}:5}", *engine.create(TensorSpec("double").add({}, 5))));
- TEST_DO(verify_verbatim_tensor("{{x:foo}:1,{x:bar}:2,{x:baz}:3}", *engine.create(TensorSpec("tensor(x{})")
- .add({{"x", "foo"}}, 1)
- .add({{"x", "bar"}}, 2)
- .add({{"x", "baz"}}, 3))));
- TEST_DO(verify_verbatim_tensor("{{x:foo,y:a}:1,{y:b,x:bar}:2}", *engine.create(TensorSpec("tensor(x{},y{})")
- .add({{"x", "foo"}, {"y", "a"}}, 1)
- .add({{"x", "bar"}, {"y", "b"}}, 2))));
- }
-
- void verify_map_op(const UnaryOperation &op, const Tensor &input, const Tensor &expect) {
- Stash stash;
- const Value &result = engine.map(op, input, stash);
- if (EXPECT_TRUE(result.is_tensor())) {
- const Tensor &actual = *result.as_tensor();
- EXPECT_EQUAL(actual, expect);
- }
+ TEST_DO(verify_verbatim_tensor("{}", tensor()));
+ TEST_DO(verify_verbatim_tensor("{{}:5}", tensor(5.0)));
+ TEST_DO(verify_verbatim_tensor("{{x:foo}:1,{x:bar}:2,{x:baz}:3}", tensor(x({"foo","bar","baz"}), Seq({1,2,3}))));
+ TEST_DO(verify_verbatim_tensor("{{x:foo,y:a}:1,{y:b,x:bar}:2}",
+ tensor({x({"foo","bar"}),y({"a","b"})}, Seq({1,X,X,2}), Bits({1,0,0,1}))));
}
- void test_map_op(const UnaryOperation &op, const Sequence &seq) {
- TEST_DO(verify_map_op(op,
- *tensor({Space("x", 10)}, seq),
- *tensor({Space("x", 10)}, OpSeq(seq, op))));
- TEST_DO(verify_map_op(op,
- *tensor({Space("x", {"a", "b", "c"})}, seq),
- *tensor({Space("x", {"a", "b", "c"})}, OpSeq(seq, op))));
+ void test_map_op(const Eval &eval, const UnaryOperation &ref_op, const Sequence &seq) {
+ std::vector<Layout> layouts = {
+ {},
+ {x(3)},
+ {x(3),y(5)},
+ {x(3),y(5),z(7)},
+ {x({"a","b","c"})},
+ {x({"a","b","c"}),y({"foo","bar"})},
+ {x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})}
+ };
+ if (mixed()) {
+ layouts.push_back({x(3),y({"foo", "bar"}),z(7)});
+ layouts.push_back({x({"a","b","c"}),y(5),z({"i","j","k","l"})});
+ }
+ for (const Layout &layout: layouts) {
+ TEST_DO(eval.verify(engine, tensor(layout, seq), tensor(layout, OpSeq(seq, ref_op))));
+ }
}
void test_tensor_map() {
- TEST_DO(test_map_op(operation::Floor(), Div10(N())));
- TEST_DO(test_map_op(operation::Ceil(), Div10(N())));
- TEST_DO(test_map_op(operation::Sqrt(), Div10(N())));
- TEST_DO(test_map_op(MyOp(), Div10(N())));
+ TEST_DO(test_map_op(ImmediateMap(operation::Floor()), operation::Floor(), Div10(N())));
+ TEST_DO(test_map_op(RetainedMap(operation::Floor()), operation::Floor(), Div10(N())));
+ TEST_DO(test_map_op(Expr_T_T("floor(a)"), operation::Floor(), Div10(N())));
+ //---------------------------------------------------------------------
+ TEST_DO(test_map_op(ImmediateMap(operation::Ceil()), operation::Ceil(), Div10(N())));
+ TEST_DO(test_map_op(RetainedMap(operation::Ceil()), operation::Ceil(), Div10(N())));
+ TEST_DO(test_map_op(Expr_T_T("ceil(a)"), operation::Ceil(), Div10(N())));
+ //---------------------------------------------------------------------
+ TEST_DO(test_map_op(ImmediateMap(operation::Sqrt()), operation::Sqrt(), Div10(N())));
+ TEST_DO(test_map_op(RetainedMap(operation::Sqrt()), operation::Sqrt(), Div10(N())));
+ TEST_DO(test_map_op(Expr_T_T("sqrt(a)"), operation::Sqrt(), Div10(N())));
+ //---------------------------------------------------------------------
+ TEST_DO(test_map_op(ImmediateMap(MyOp()), MyOp(), Div10(N())));
+ TEST_DO(test_map_op(RetainedMap(MyOp()), MyOp(), Div10(N())));
+ TEST_DO(test_map_op(Expr_T_T("(a+1)*2"), MyOp(), Div10(N())));
}
- void run_all_tests() {
+ void run_tests() {
TEST_DO(test_tensor_create_type());
TEST_DO(test_tensor_inequality());
TEST_DO(test_verbatim_tensors());
@@ -258,10 +435,10 @@ struct TestContext {
} // namespace vespalib::eval::test::<unnamed>
void
-TensorConformance::run_tests(const TensorEngine &engine)
+TensorConformance::run_tests(const TensorEngine &engine, bool test_mixed_cases)
{
- TestContext ctx(engine);
- ctx.run_all_tests();
+ TestContext ctx(engine, test_mixed_cases);
+ ctx.run_tests();
}
} // namespace vespalib::eval::test
diff --git a/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.h b/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.h
index 8d90f548b33..ed1ff618f49 100644
--- a/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.h
+++ b/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.h
@@ -13,7 +13,7 @@ namespace test {
* implementations of the TensorEngine interface.
**/
struct TensorConformance {
- static void run_tests(const TensorEngine &engine);
+ static void run_tests(const TensorEngine &engine, bool test_mixed_cases);
};
} // namespace vespalib::eval::test
diff --git a/vespalib/src/vespa/vespalib/eval/value.cpp b/vespalib/src/vespa/vespalib/eval/value.cpp
index ff72ac4c85c..859c91a59f5 100644
--- a/vespalib/src/vespa/vespalib/eval/value.cpp
+++ b/vespalib/src/vespa/vespalib/eval/value.cpp
@@ -23,23 +23,23 @@ Value::apply(const BinaryOperation &, const Value &, Stash &stash) const
bool
TensorValue::equal(const Value &rhs) const
{
- return (rhs.is_tensor() && _value->engine().equal(*_value, *rhs.as_tensor()));
+ return (rhs.is_tensor() && _tensor.engine().equal(_tensor, *rhs.as_tensor()));
}
const Value &
TensorValue::apply(const UnaryOperation &op, Stash &stash) const
{
- return _value->engine().map(op, *_value, stash);
+ return _tensor.engine().map(op, _tensor, stash);
}
const Value &
TensorValue::apply(const BinaryOperation &op, const Value &rhs, Stash &stash) const
{
const Tensor *other = rhs.as_tensor();
- if ((other == nullptr) || (&other->engine() != &_value->engine())) {
+ if ((other == nullptr) || (&other->engine() != &_tensor.engine())) {
return stash.create<ErrorValue>();
}
- return _value->engine().apply(op, *_value, *other, stash);
+ return _tensor.engine().apply(op, _tensor, *other, stash);
}
} // namespace vespalib::eval
diff --git a/vespalib/src/vespa/vespalib/eval/value.h b/vespalib/src/vespa/vespalib/eval/value.h
index 22e90b9327f..659e9ac6ec2 100644
--- a/vespalib/src/vespa/vespalib/eval/value.h
+++ b/vespalib/src/vespa/vespalib/eval/value.h
@@ -59,11 +59,13 @@ public:
class TensorValue : public Value
{
private:
- std::unique_ptr<Tensor> _value;
+ const Tensor &_tensor;
+ std::unique_ptr<Tensor> _stored;
public:
- TensorValue(std::unique_ptr<Tensor> value) : _value(std::move(value)) {}
+ TensorValue(const Tensor &value) : _tensor(value), _stored() {}
+ TensorValue(std::unique_ptr<Tensor> value) : _tensor(*value), _stored(std::move(value)) {}
bool is_tensor() const override { return true; }
- const Tensor *as_tensor() const override { return _value.get(); }
+ const Tensor *as_tensor() const override { return &_tensor; }
bool equal(const Value &rhs) const override;
const Value &apply(const UnaryOperation &op, Stash &stash) const override;
const Value &apply(const BinaryOperation &op, const Value &rhs, Stash &stash) const override;