summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@oath.com>2019-06-11 10:53:04 +0200
committerGitHub <noreply@github.com>2019-06-11 10:53:04 +0200
commite26488df7ce7107a932282dc34c5b5d3108d7cb9 (patch)
tree0f58716cebe90fead1b1a5eb4888a9d6a659ce1d
parentb2a957120472ee6b9a6fb22239ad428fed94ec8e (diff)
parenta7e6b478c536dee7abc14b62fa2700df2b9df93f (diff)
Merge pull request #9730 from vespa-engine/bratseth/dense-string-form
Dense string form
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/processing/RankingExpressionWithTensorTestCase.java41
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorFieldTestCase.java54
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Model.java2
-rw-r--r--vespajlib/abi-spec.json3
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/IndexedDoubleTensor.java8
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/IndexedTensor.java7
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/Tensor.java6
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/TensorParser.java166
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/TensorType.java3
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/TensorTypeParser.java5
-rw-r--r--vespajlib/src/test/java/com/yahoo/tensor/TensorParserTestCase.java51
-rw-r--r--vespajlib/src/test/java/com/yahoo/tensor/TensorTestCase.java2
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());
}
}