diff options
author | Jon Bratseth <bratseth@oath.com> | 2019-06-11 10:53:04 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-11 10:53:04 +0200 |
commit | e26488df7ce7107a932282dc34c5b5d3108d7cb9 (patch) | |
tree | 0f58716cebe90fead1b1a5eb4888a9d6a659ce1d | |
parent | b2a957120472ee6b9a6fb22239ad428fed94ec8e (diff) | |
parent | a7e6b478c536dee7abc14b62fa2700df2b9df93f (diff) |
Merge pull request #9730 from vespa-engine/bratseth/dense-string-form
Dense string form
12 files changed, 253 insertions, 95 deletions
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorTestCase.java index 80440ac8eb4..1b03825eef1 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorTestCase.java @@ -2,9 +2,10 @@ package com.yahoo.searchdefinition.processing; import com.yahoo.searchdefinition.parser.ParseException; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; /** * @author geirst @@ -138,23 +139,29 @@ public class RankingExpressionWithTensorTestCase { f.assertRankProperty("tensor(x{})", "constant(my_tensor).type", "my_profile"); } - @Rule - public ExpectedException exception = ExpectedException.none(); - @Test public void requireThatInvalidTensorTypeSpecThrowsException() throws ParseException { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("For constant tensor 'my_tensor' in rank profile 'my_profile': Illegal tensor type spec: A tensor type spec must be on the form tensor[<valuetype>]?(dimensionidentifier[{}|[length?]*), but was 'tensor(x)'. Dimension 'x' is on the wrong format. Examples: tensor(x[]), tensor<float>(name{}, x[10])"); - RankProfileSearchFixture f = new RankProfileSearchFixture( - " rank-profile my_profile {\n" + - " constants {\n" + - " my_tensor {\n" + - " value: { {x:1}:1 }\n" + - " type: tensor(x)\n" + - " }\n" + - " }\n" + - " }"); - f.compileRankProfile("my_profile"); + try { + RankProfileSearchFixture f = new RankProfileSearchFixture( + " rank-profile my_profile {\n" + + " constants {\n" + + " my_tensor {\n" + + " value: { {x:1}:1 }\n" + + " type: tensor(x)\n" + + " }\n" + + " }\n" + + " }"); + f.compileRankProfile("my_profile"); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertStartsWith("For constant tensor 'my_tensor' in rank profile 'my_profile': Illegal tensor type spec", + e.getMessage()); + } + } + + private void assertStartsWith(String prefix, String string) { + assertEquals(prefix, string.substring(0, Math.min(prefix.length(), string.length()))); } } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorFieldTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorFieldTestCase.java index f53ca15635f..b6569357495 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorFieldTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorFieldTestCase.java @@ -3,48 +3,68 @@ package com.yahoo.searchdefinition.processing; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.parser.ParseException; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; /** * @author geirst */ public class TensorFieldTestCase { - @Rule - public ExpectedException exception = ExpectedException.none(); - @Test public void requireThatTensorFieldCannotBeOfCollectionType() throws ParseException { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("For search 'test', field 'f1': A field with collection type of tensor is not supported. Use simple type 'tensor' instead."); - SearchBuilder.createFromString(getSd("field f1 type array<tensor(x{})> {}")); + try { + SearchBuilder.createFromString(getSd("field f1 type array<tensor(x{})> {}")); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("For search 'test', field 'f1': A field with collection type of tensor is not supported. Use simple type 'tensor' instead.", + e.getMessage()); + } } @Test public void requireThatTensorFieldCannotBeIndexField() throws ParseException { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("For search 'test', field 'f1': A field of type 'tensor' cannot be specified as an 'index' field."); - SearchBuilder.createFromString(getSd("field f1 type tensor(x{}) { indexing: index }")); + try { + SearchBuilder.createFromString(getSd("field f1 type tensor(x{}) { indexing: index }")); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("For search 'test', field 'f1': A field of type 'tensor' cannot be specified as an 'index' field.", + e.getMessage()); + } } @Test public void requireThatTensorAttributeCannotBeFastSearch() throws ParseException { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("For search 'test', field 'f1': An attribute of type 'tensor' cannot be 'fast-search'."); - SearchBuilder.createFromString(getSd("field f1 type tensor(x{}) { indexing: attribute \n attribute: fast-search }")); + try { + SearchBuilder.createFromString(getSd("field f1 type tensor(x{}) { indexing: attribute \n attribute: fast-search }")); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("For search 'test', field 'f1': An attribute of type 'tensor' cannot be 'fast-search'.", e.getMessage()); + } } @Test public void requireThatIllegalTensorTypeSpecThrowsException() throws ParseException { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("Field type: Illegal tensor type spec: A tensor type spec must be on the form tensor[<valuetype>]?(dimensionidentifier[{}|[length?]*), but was 'tensor(invalid)'. Dimension 'invalid' is on the wrong format. Examples: tensor(x[]), tensor<float>(name{}, x[10])"); - SearchBuilder.createFromString(getSd("field f1 type tensor(invalid) { indexing: attribute }")); + try { + SearchBuilder.createFromString(getSd("field f1 type tensor(invalid) { indexing: attribute }")); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertStartsWith("Field type: Illegal tensor type spec:", e.getMessage()); + } } private static String getSd(String field) { return "search test {\n document test {\n" + field + "}\n}\n"; } + private void assertStartsWith(String prefix, String string) { + assertEquals(prefix, string.substring(0, Math.min(prefix.length(), string.length()))); + } + } diff --git a/container-search/src/main/java/com/yahoo/search/query/Model.java b/container-search/src/main/java/com/yahoo/search/query/Model.java index d2f59e0710e..5ed58a74627 100644 --- a/container-search/src/main/java/com/yahoo/search/query/Model.java +++ b/container-search/src/main/java/com/yahoo/search/query/Model.java @@ -377,7 +377,7 @@ public class Model implements Cloneable { * from a sources */ public void setRestrict(String restrictString) { - setFromString(restrictString,restrict); + setFromString(restrictString, restrict); } /** diff --git a/vespajlib/abi-spec.json b/vespajlib/abi-spec.json index 04e68e60178..b2b895040bc 100644 --- a/vespajlib/abi-spec.json +++ b/vespajlib/abi-spec.json @@ -1352,7 +1352,8 @@ "public static com.yahoo.tensor.TensorType$Value[] values()", "public static com.yahoo.tensor.TensorType$Value valueOf(java.lang.String)", "public static com.yahoo.tensor.TensorType$Value largestOf(java.util.List)", - "public static com.yahoo.tensor.TensorType$Value largestOf(com.yahoo.tensor.TensorType$Value, com.yahoo.tensor.TensorType$Value)" + "public static com.yahoo.tensor.TensorType$Value largestOf(com.yahoo.tensor.TensorType$Value, com.yahoo.tensor.TensorType$Value)", + "public java.lang.String toString()" ], "fields": [ "public static final enum com.yahoo.tensor.TensorType$Value DOUBLE", diff --git a/vespajlib/src/main/java/com/yahoo/tensor/IndexedDoubleTensor.java b/vespajlib/src/main/java/com/yahoo/tensor/IndexedDoubleTensor.java index 7f1351cc42b..219a3fa2278 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/IndexedDoubleTensor.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/IndexedDoubleTensor.java @@ -108,7 +108,13 @@ class IndexedDoubleTensor extends IndexedTensor { @Override public void cellByDirectIndex(long index, double value) { - values[(int)index] = value; + try { + values[(int) index] = value; + } + catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException("Can not set the cell at position " + index + " in a tensor " + + "of type " + type + ": Index is too large"); + } } } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java b/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java index aeb3da8ac40..aca2bfc1b0f 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java @@ -16,7 +16,7 @@ import java.util.Set; import java.util.function.DoubleBinaryOperator; /** - * An indexed (dense) tensor backed by a double array. + * An indexed (dense) tensor backed by an array. * * @author bratseth */ @@ -143,9 +143,8 @@ public abstract class IndexedTensor implements Tensor { long valueIndex = 0; for (int i = 0; i < indexes.length; i++) { - if (indexes[i] >= sizes.size(i)) { - throw new IllegalArgumentException(indexes + " are not within bounds"); - } + if (indexes[i] >= sizes.size(i)) + throw new IllegalArgumentException(Arrays.toString(indexes) + " are not within bounds"); valueIndex += productOfDimensionsAfter(i, sizes) * indexes[i]; } return valueIndex; diff --git a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java index 22ff793e6fa..c2aa155d6bb 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java @@ -333,7 +333,7 @@ public interface Tensor { } else { x = Math.nextAfter(x, y); } - return x==y; + return x == y; } // ----------------- Factories @@ -367,9 +367,7 @@ public interface Tensor { return TensorParser.tensorFrom(tensorString, Optional.empty()); } - /** - * Returns a double as a tensor: A dimensionless tensor containing the value as its cell - */ + /** Returns a double as a tensor: A dimensionless tensor containing the value as its cell */ static Tensor from(double value) { return Tensor.Builder.of(TensorType.empty).cell(value).build(); } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/TensorParser.java b/vespajlib/src/main/java/com/yahoo/tensor/TensorParser.java index 45a9992c9ad..4d8b34b7dcf 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/TensorParser.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/TensorParser.java @@ -8,44 +8,59 @@ import java.util.Optional; */ class TensorParser { - static Tensor tensorFrom(String tensorString, Optional<TensorType> type) { + static Tensor tensorFrom(String tensorString, Optional<TensorType> explicitType) { + Optional<TensorType> type; + String valueString; + tensorString = tensorString.trim(); - try { - if (tensorString.startsWith("tensor")) { - int colonIndex = tensorString.indexOf(':'); - String typeString = tensorString.substring(0, colonIndex); - String valueString = tensorString.substring(colonIndex + 1); - TensorType typeFromString = TensorTypeParser.fromSpec(typeString); - if (type.isPresent() && ! type.get().equals(typeFromString)) - throw new IllegalArgumentException("Got tensor with type string '" + typeString + "', but was " + - "passed type " + type.get()); - return tensorFromValueString(valueString, typeFromString); - } - else if (tensorString.startsWith("{")) { - return tensorFromValueString(tensorString, type.orElse(typeFromValueString(tensorString))); - } - else { - if (type.isPresent() && ! type.get().equals(TensorType.empty)) - throw new IllegalArgumentException("Got zero-dimensional tensor '" + tensorString + - "' where type " + type.get() + " is required"); + if (tensorString.startsWith("tensor")) { + int colonIndex = tensorString.indexOf(':'); + String typeString = tensorString.substring(0, colonIndex); + TensorType typeFromString = TensorTypeParser.fromSpec(typeString); + if (explicitType.isPresent() && ! explicitType.get().equals(typeFromString)) + throw new IllegalArgumentException("Got tensor with type string '" + typeString + "', but was " + + "passed type " + explicitType.get()); + type = Optional.of(typeFromString); + valueString = tensorString.substring(colonIndex + 1); + } + else { + type = explicitType; + valueString = tensorString; + } + + valueString = valueString.trim(); + if (valueString.startsWith("{")) { + return tensorFromSparseValueString(valueString, type); + } + else if (valueString.startsWith("[")) { + return tensorFromDenseValueString(valueString, type); + } + else { + if (explicitType.isPresent() && ! explicitType.get().equals(TensorType.empty)) + throw new IllegalArgumentException("Got a zero-dimensional tensor value ('" + tensorString + + "') where type " + explicitType.get() + " is required"); + try { return Tensor.Builder.of(TensorType.empty).cell(Double.parseDouble(tensorString)).build(); } - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Excepted a number or a string starting by { or tensor(, got '" + - tensorString + "'"); + catch (NumberFormatException e) { + throw new IllegalArgumentException("Excepted a number or a string starting by {, [ or tensor(...):, got '" + + tensorString + "'"); + } } } - /** Derive the tensor type from the first address string in the given tensor string */ - private static TensorType typeFromValueString(String s) { - s = s.substring(1).trim(); // remove tensor start + /** Derives the tensor type from the first address string in the given tensor string */ + private static TensorType typeFromSparseValueString(String valueString) { + String s = valueString.substring(1).trim(); // remove tensor start int firstKeyOrTensorEnd = s.indexOf('}'); + if (firstKeyOrTensorEnd < 0) + throw new IllegalArgumentException("Excepted a number or a string starting by {, [ or tensor(...):, got '" + + valueString + "'"); String addressBody = s.substring(0, firstKeyOrTensorEnd).trim(); if (addressBody.isEmpty()) return TensorType.empty; // Empty tensor if ( ! addressBody.startsWith("{")) return TensorType.empty; // Single value tensor - addressBody = addressBody.substring(1); // remove key start + addressBody = addressBody.substring(1, addressBody.length()); // remove key start if (addressBody.isEmpty()) return TensorType.empty; // Empty key TensorType.Builder builder = new TensorType.Builder(TensorType.Value.DOUBLE); @@ -60,21 +75,76 @@ class TensorParser { return builder.build(); } - private static Tensor tensorFromValueString(String tensorValueString, TensorType type) { - Tensor.Builder builder = Tensor.Builder.of(type); - tensorValueString = tensorValueString.trim(); + private static Tensor tensorFromSparseValueString(String valueString, Optional<TensorType> type) { try { - if (tensorValueString.startsWith("{")) - return fromCellString(builder, tensorValueString); - else - return builder.cell(Double.parseDouble(tensorValueString)).build(); + valueString = valueString.trim(); + Tensor.Builder builder = Tensor.Builder.of(type.orElse(typeFromSparseValueString(valueString))); + return fromCellString(builder, valueString); } catch (NumberFormatException e) { throw new IllegalArgumentException("Excepted a number or a string starting by { or tensor(, got '" + - tensorValueString + "'"); + valueString + "'"); } } + private static Tensor tensorFromDenseValueString(String valueString, Optional<TensorType> type) { + if (type.isEmpty()) + throw new IllegalArgumentException("The dense tensor form requires an explicit tensor type " + + "on the form 'tensor(dimensions):..."); + if (type.get().dimensions().stream().anyMatch(d -> ( d.size().isEmpty()))) + throw new IllegalArgumentException("The dense tensor form requires a tensor type containing " + + "only dense dimensions with a given size"); + + IndexedTensor.BoundBuilder builder = (IndexedTensor.BoundBuilder)IndexedTensor.Builder.of(type.get()); + long index = 0; + int currentChar; + int nextNumberEnd = 0; + // Since we know the dimensions the brackets are just syntactic sugar: + while ((currentChar = nextStartCharIndex(nextNumberEnd + 1, valueString)) < valueString.length()) { + nextNumberEnd = nextStopCharIndex(currentChar, valueString); + if (currentChar == nextNumberEnd) return builder.build(); + + TensorType.Value cellValueType = builder.type().valueType(); + String cellValueString = valueString.substring(currentChar, nextNumberEnd); + try { + if (cellValueType == TensorType.Value.DOUBLE) + builder.cellByDirectIndex(index, Double.parseDouble(cellValueString)); + else if (cellValueType == TensorType.Value.FLOAT) + builder.cellByDirectIndex(index, Float.parseFloat(cellValueString)); + else + throw new IllegalArgumentException(cellValueType + " is not supported"); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("At index " + index + ": '" + + cellValueString + "' is not a valid " + cellValueType); + } + index++; + } + return builder.build(); + } + + /** Returns the position of the next character that should contain a number, or if none the string length */ + private static int nextStartCharIndex(int charIndex, String valueString) { + for (; charIndex < valueString.length(); charIndex++) { + if (valueString.charAt(charIndex) == ']') continue; + if (valueString.charAt(charIndex) == '[') continue; + if (valueString.charAt(charIndex) == ',') continue; + if (valueString.charAt(charIndex) == ' ') continue; + return charIndex; + } + return valueString.length(); + } + + private static int nextStopCharIndex(int charIndex, String valueString) { + while (charIndex < valueString.length()) { + if (valueString.charAt(charIndex) == ',') return charIndex; + if (valueString.charAt(charIndex) == ']') return charIndex; + charIndex++; + } + throw new IllegalArgumentException("Malformed tensor value '" + valueString + + "': Expected a ',' or ']' after position " + charIndex); + } + private static Tensor fromCellString(Tensor.Builder builder, String s) { int index = 1; index = skipSpace(index, s); @@ -97,8 +167,21 @@ class TensorParser { } TensorAddress address = addressBuilder.build(); - Double value = asDouble(address, s.substring(index, valueEnd).trim()); - builder.cell(address, value); + TensorType.Value cellValueType = builder.type().valueType(); + String cellValueString = s.substring(index, valueEnd).trim(); + try { + if (cellValueType == TensorType.Value.DOUBLE) + builder.cell(address, Double.parseDouble(cellValueString)); + else if (cellValueType == TensorType.Value.FLOAT) + builder.cell(address, Float.parseFloat(cellValueString)); + else + throw new IllegalArgumentException(cellValueType + " is not supported"); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("At " + address.toString(builder.type()) + ": '" + + cellValueString + "' is not a valid " + cellValueType); + } + index = valueEnd+1; index = skipSpace(index, s); } @@ -130,13 +213,4 @@ class TensorParser { } } - private static Double asDouble(TensorAddress address, String s) { - try { - return Double.valueOf(s); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("At " + address + ": Expected a floating point number, got '" + s + "'"); - } - } - } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/TensorType.java b/vespajlib/src/main/java/com/yahoo/tensor/TensorType.java index b1c7a2341c0..8e566fac0b6 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/TensorType.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/TensorType.java @@ -48,6 +48,9 @@ public class TensorType { return FLOAT; } + @Override + public String toString() { return name().toLowerCase(); } + }; /** The empty tensor type - which is the same as a double */ diff --git a/vespajlib/src/main/java/com/yahoo/tensor/TensorTypeParser.java b/vespajlib/src/main/java/com/yahoo/tensor/TensorTypeParser.java index d5f77be0dd0..1f426942c5f 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/TensorTypeParser.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/TensorTypeParser.java @@ -24,6 +24,7 @@ public class TensorTypeParser { private static final Pattern mappedPattern = Pattern.compile("(\\w+)\\{\\}"); public static TensorType fromSpec(String specString) { + specString = specString.trim(); if ( ! specString.startsWith(START_STRING) || ! specString.endsWith(END_STRING)) throw formatException(specString); String specBody = specString.substring(START_STRING.length(), specString.length() - END_STRING.length()); @@ -112,9 +113,9 @@ public class TensorTypeParser { private static IllegalArgumentException formatException(String spec, Optional<String> errorDetail) { throw new IllegalArgumentException("A tensor type spec must be on the form " + - "tensor[<valuetype>]?(dimensionidentifier[{}|[length?]*), but was '" + spec + "'. " + + "tensor[<valuetype>]?(dimensionidentifier[{}|[length]*), but was '" + spec + "'. " + errorDetail.map(s -> s + ". ").orElse("") + - "Examples: tensor(x[]), tensor<float>(name{}, x[10])"); + "Examples: tensor(x[3]), tensor<float>(name{}, x[10])"); } } diff --git a/vespajlib/src/test/java/com/yahoo/tensor/TensorParserTestCase.java b/vespajlib/src/test/java/com/yahoo/tensor/TensorParserTestCase.java index 04ea118280c..63fe40565bd 100644 --- a/vespajlib/src/test/java/com/yahoo/tensor/TensorParserTestCase.java +++ b/vespajlib/src/test/java/com/yahoo/tensor/TensorParserTestCase.java @@ -9,13 +9,58 @@ import static org.junit.Assert.fail; public class TensorParserTestCase { @Test - public void testParsing() { + public void testSparseParsing() { assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor()")).build(), Tensor.from("{}")); assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor(x{})")).cell(1.0, 0).build(), Tensor.from("{{x:0}:1.0}")); assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor(x{})")).cell().label("x", "l0").value(1.0).build(), Tensor.from("{{x:l0}:1.0}")); + assertEquals("If the type is specified, a dense tensor can be created from the sparse text form", + Tensor.Builder.of(TensorType.fromSpec("tensor(x[1])")).cell(1.0, 0).build(), + Tensor.from("tensor(x[1]):{{x:0}:1.0}")); + } + + @Test + public void testDenseParsing() { + assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor()")).build(), + Tensor.from("tensor():[]")); + assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor(x[1])")).cell(1.0, 0).build(), + Tensor.from("tensor(x[1]):[1.0]")); + assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor(x[2])")).cell(1.0, 0).cell(2.0, 1).build(), + Tensor.from("tensor(x[2]):[1.0, 2.0]")); + assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor(x[2],y[3])")) + .cell(1.0, 0, 0) + .cell(2.0, 0, 1) + .cell(3.0, 0, 2) + .cell(4.0, 1, 0) + .cell(5.0, 1, 1) + .cell(6.0, 1, 2).build(), + Tensor.from("tensor(x[2],y[3]):[[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]")); + assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor(x[1],y[2],z[3])")) + .cell(1.0, 0, 0, 0) + .cell(2.0, 0, 0, 1) + .cell(3.0, 0, 0, 2) + .cell(4.0, 0, 1, 0) + .cell(5.0, 0, 1, 1) + .cell(6.0, 0, 1, 2).build(), + Tensor.from("tensor(x[1],y[2],z[3]):[[[1.0], [2.0]], [[3.0], [4.0]], [[5.0], [6.0]]]")); + assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor(x[3],y[2],z[1])")) + .cell(1.0, 0, 0, 0) + .cell(2.0, 0, 1, 0) + .cell(3.0, 1, 0, 0) + .cell(4.0, 1, 1, 0) + .cell(5.0, 2, 0, 0) + .cell(6.0, 2, 1, 0).build(), + Tensor.from("tensor(x[3],y[2],z[1]):[[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]]")); + assertEquals(Tensor.Builder.of(TensorType.fromSpec("tensor(x[3],y[2],z[1])")) + .cell( 1.0, 0, 0, 0) + .cell( 2.0, 0, 1, 0) + .cell( 3.0, 1, 0, 0) + .cell( 4.0, 1, 1, 0) + .cell( 5.0, 2, 0, 0) + .cell(-6.0, 2, 1, 0).build(), + Tensor.from("tensor( x[3],y[2],z[1]) : [ [ [1.0, 2.0, 3.0] , [4.0, 5,-6.0] ] ]")); } @Test @@ -26,6 +71,10 @@ public class TensorParserTestCase { "{{'x':\"l0\"}:1.0}"); assertIllegal("dimension must be an identifier or integer, not '\"x\"'", "{{\"x\":\"l0\", \"y\":\"l0\"}:1.0, {\"x\":\"l0\", \"y\":\"l1\"}:2.0}"); + assertIllegal("At {x:0}: '1-.0' is not a valid double", + "{{x:0}:1-.0}"); + assertIllegal("At index 0: '1-.0' is not a valid double", + "tensor(x[1]):[1-.0]"); } private void assertIllegal(String message, String tensor) { diff --git a/vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java b/vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java index b01d171792c..c53db160806 100644 --- a/vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java +++ b/vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java @@ -54,7 +54,7 @@ public class TensorTestCase { fail("Expected parse error"); } catch (IllegalArgumentException expected) { - assertEquals("Excepted a number or a string starting by { or tensor(, got '--'", expected.getMessage()); + assertEquals("Excepted a number or a string starting by {, [ or tensor(...):, got '--'", expected.getMessage()); } } |