Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(algorithm): support single source shortest path algorithm #285

Merged
merged 24 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
28691fd
feat: ListValue add clear() method
diaohancai Nov 21, 2023
d9c5c84
feat: Source Target Shortest Path
diaohancai Nov 21, 2023
3aff95d
test: test data is complicated
diaohancai Nov 21, 2023
b6c0f4c
test: ListValue.clear unit test
diaohancai Nov 21, 2023
f5a5de9
fix: ring loop
diaohancai Nov 21, 2023
4c37b2d
optimize: message combine
diaohancai Nov 21, 2023
15f95f6
Merge branch 'master' into pr/285
imbajin Dec 4, 2023
6630b82
refactor(algorithm): Single Source Shortest Path
diaohancai Dec 26, 2023
9524900
refactor(algorithm): output json
diaohancai Dec 26, 2023
2804bd7
feat(algorithm): multiple target optimization
diaohancai Dec 26, 2023
cdaba23
feat(core): IdList Merge Combiner
diaohancai Dec 26, 2023
125bba6
chore(algorithm): simple adjustments
diaohancai Dec 31, 2023
9587802
optimization(algorithm): change reachedTargets from IdList to IdSet
diaohancai Jan 1, 2024
01cde50
chore: json style key
diaohancai Jan 22, 2024
6b42bf5
improve: convert id from string to ID with type
diaohancai Jan 25, 2024
55b0ed3
chore: add IdUtilTest unit test
diaohancai Jan 25, 2024
2828bb7
fix: all targets reached
diaohancai Feb 6, 2024
a0a0103
improve: remove unnecessary member var
diaohancai Feb 6, 2024
c4ca8fe
improve: source vertex and target vertex specify idType
diaohancai Feb 6, 2024
174584e
chore: get properties
diaohancai Feb 7, 2024
aa8238d
improve: input vertex id parse
diaohancai Feb 29, 2024
0b1c720
chore: log improvement
diaohancai Feb 29, 2024
6510424
test: apply exception testing
diaohancai Mar 3, 2024
d55c7c7
test: parse empty id throws IllegalArgumentException
diaohancai Mar 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@

package org.apache.hugegraph.computer.algorithm.path.shortest;

import java.util.Arrays;
import java.util.Iterator;
import java.util.stream.Collectors;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.computer.core.common.exception.ComputerException;
Expand All @@ -32,17 +31,19 @@
import org.apache.hugegraph.computer.core.graph.value.Value;
import org.apache.hugegraph.computer.core.graph.vertex.Vertex;
import org.apache.hugegraph.computer.core.util.IdUtil;
import org.apache.hugegraph.computer.core.util.JsonUtilExt;
import org.apache.hugegraph.computer.core.worker.Computation;
import org.apache.hugegraph.computer.core.worker.ComputationContext;
import org.apache.hugegraph.computer.core.worker.WorkerContext;
import org.apache.hugegraph.rest.SerializeException;
import org.apache.hugegraph.util.JsonUtil;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;

public class SingleSourceShortestPath implements Computation<SingleSourceShortestPathValue> {

private static final Logger LOG = Log.logger(SingleSourceShortestPath.class);

public static final String OPTION_VERTEX_ID_TYPE = "single_source_shortest_path.vertex_id_type";
public static final String OPTION_SOURCE_ID = "single_source_shortest_path.source_id";
public static final String OPTION_TARGET_ID = "single_source_shortest_path.target_id";
public static final String OPTION_WEIGHT_PROPERTY =
Expand All @@ -51,27 +52,20 @@
"single_source_shortest_path.default_weight";

/**
* id type of vertex.
* string|number|uuid
* source vertex id.
* {"id": "", "idType": ""}
*/
// todo improve: automatic inference
private String vertexIdTypeStr;
private IdCategory vertexIdType;

/**
* source vertex id
*/
private String sourceIdStr;
// todo improve: automatic inference idType
private Id sourceId;

/**
* target vertex id.
* 1. single target: one vertex id
* 2. multiple target: multiple vertex ids separated by comma
* 3. all: *
* 1. single target: [{"id": "", "idType": ""}]
* 2. multiple target: [{"id": "", "idType": ""}, {"id": "", "idType": ""}]
* 3. all: []
*/
private String targetIdStr;
private IdSet targetIdSet; // empty when targetIdStr == "*"
// todo improve: automatic inference idType
private IdSet targetIdSet; // empty when targetId is all
/**
* target quantity type
*/
Expand All @@ -93,7 +87,7 @@
/**
* reached targets
*/
private IdSet reachedTargets; // empty when targetIdStr == "*"
private IdSet reachedTargets; // empty when targetId is all

@Override
public String category() {
Expand All @@ -107,28 +101,35 @@

@Override
public void init(Config config) {
this.vertexIdTypeStr = config.getString(OPTION_VERTEX_ID_TYPE, "");
this.vertexIdType = IdCategory.parse(this.vertexIdTypeStr);

this.sourceIdStr = config.getString(OPTION_SOURCE_ID, "");
if (StringUtils.isBlank(this.sourceIdStr)) {
String sourceIdStr = config.getString(OPTION_SOURCE_ID, "");
if (StringUtils.isBlank(sourceIdStr)) {
throw new ComputerException("The param '%s' must not be blank", OPTION_SOURCE_ID);

Check warning on line 106 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L106

Added line #L106 was not covered by tests
}
this.sourceId = IdUtil.parseId(this.vertexIdType, this.sourceIdStr);
VertexInputJson sourceVertex;
try {
sourceVertex = JsonUtil.fromJson(sourceIdStr, VertexInputJson.class);
} catch (SerializeException e) {
throw new ComputerException("The param '%s' is unexpected format", OPTION_SOURCE_ID);

Check warning on line 112 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L111-L112

Added lines #L111 - L112 were not covered by tests
}
this.sourceId = IdUtil.parseId(IdCategory.parse(sourceVertex.getIdType()),
sourceVertex.getId());

this.targetIdStr = config.getString(OPTION_TARGET_ID, "");
if (StringUtils.isBlank(this.targetIdStr)) {
String targetIdStr = config.getString(OPTION_TARGET_ID, "");
if (StringUtils.isBlank(targetIdStr)) {
throw new ComputerException("The param '%s' must not be blank", OPTION_TARGET_ID);

Check warning on line 119 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L119

Added line #L119 was not covered by tests
}
// remove spaces
this.targetIdStr = Arrays.stream(this.targetIdStr.split(","))
.map(e -> e.trim())
.collect(Collectors.joining(","));
this.targetQuantityType = this.getQuantityType();
List<VertexInputJson> targetVertices;
try {
targetVertices = JsonUtilExt.fromJson2List(targetIdStr, VertexInputJson.class);
} catch (SerializeException e) {
throw new ComputerException("The param '%s' is unexpected format", OPTION_TARGET_ID);

Check warning on line 125 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L124-L125

Added lines #L124 - L125 were not covered by tests
}
this.targetQuantityType = this.getQuantityType(targetVertices);
if (this.targetQuantityType != QuantityType.ALL) {
this.targetIdSet = new IdSet();
for (String targetIdStr : this.targetIdStr.split(",")) {
targetIdSet.add(IdUtil.parseId(this.vertexIdType, targetIdStr));
for (VertexInputJson targetVertex : targetVertices) {
targetIdSet.add(IdUtil.parseId(IdCategory.parse(targetVertex.getIdType()),
targetVertex.getId()));
}
}

Expand All @@ -136,7 +137,7 @@

this.defaultWeight = config.getDouble(OPTION_DEFAULT_WEIGHT, 1);
if (this.defaultWeight <= 0) {
throw new ComputerException("The param '%s' must be greater than 0, " +

Check warning on line 140 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L140

Added line #L140 was not covered by tests
"actual got '%s'",
OPTION_DEFAULT_WEIGHT, this.defaultWeight);
}
Expand All @@ -157,19 +158,18 @@

// single target && source == target
if (this.targetQuantityType == QuantityType.SINGLE &&
this.sourceIdStr.equals(this.targetIdStr)) {
this.sourceId.equals(this.targetIdSet.value().iterator().next())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a private method targetIdOne() for this.targetIdSet.value().iterator().next()? we can also check there exist one element in targetIdOne()

LOG.debug("source vertex {} equals target vertex {}",
this.sourceIdStr, this.targetIdStr);
this.sourceId, this.targetIdSet.value().iterator().next());
vertex.inactivate();
return;

Check warning on line 165 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L162-L165

Added lines #L162 - L165 were not covered by tests
}

if (vertex.numEdges() <= 0) {
// isolated vertex
LOG.debug("source vertex {} can not reach target vertex {}",
this.sourceIdStr, this.targetIdStr);
LOG.debug("source vertex {} is isolated", this.sourceId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this format is more convenient for retrieving logs: The source vertex is isolated: {}

vertex.inactivate();
return;

Check warning on line 172 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L170-L172

Added lines #L170 - L172 were not covered by tests
}

vertex.edges().forEach(edge -> {
Expand Down Expand Up @@ -201,8 +201,9 @@
continue;
}

// reach all target or nowhere to go
if (this.isAllTargetsReached(vertex) || vertex.numEdges() <= 0) {
// target vertex finds all targets reached or nowhere to go
if ((this.isTarget(vertex) && this.isAllTargetsReached(vertex)) ||
vertex.numEdges() <= 0) {
continue;
}

Expand Down Expand Up @@ -232,15 +233,15 @@
}

/**
* get QuantityType by this.targetId
* get quantityType by targetId
*/
private QuantityType getQuantityType() {
if (this.targetIdStr.equals("*")) {
private QuantityType getQuantityType(List<VertexInputJson> targetVertices) {
if (targetVertices.size() == 0) {
return QuantityType.ALL;

Check warning on line 240 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L240

Added line #L240 was not covered by tests
} else if (this.targetIdStr.contains(",")) {
return QuantityType.MULTIPLE;
} else {
} else if (targetVertices.size() == 1) {
return QuantityType.SINGLE;
} else {
return QuantityType.MULTIPLE;

Check warning on line 244 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L244

Added line #L244 was not covered by tests
}
}

Expand All @@ -253,16 +254,16 @@
Value property = edge.property(this.weightProperty);
if (property != null) {
if (!property.isNumber()) {
throw new ComputerException("The value of %s must be a numeric value, " +

Check warning on line 257 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L257

Added line #L257 was not covered by tests
"actual got '%s'",
this.weightProperty, property.string());

Check warning on line 259 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L259

Added line #L259 was not covered by tests
}

weight = ((DoubleValue) property).doubleValue();
if (weight <= 0) {
throw new ComputerException("The value of %s must be greater than 0, " +

Check warning on line 264 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L264

Added line #L264 was not covered by tests
"actual got '%s'",
this.weightProperty, property.string());

Check warning on line 266 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L266

Added line #L266 was not covered by tests
}
}
return weight;
Expand All @@ -281,17 +282,38 @@
*/
private boolean isAllTargetsReached(Vertex vertex) {
if (this.targetQuantityType == QuantityType.ALL) {
return false;

Check warning on line 285 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L285

Added line #L285 was not covered by tests
}

if (this.targetIdSet.size() == this.reachedTargets.size()) {
for (Id targetId : this.targetIdSet.value()) {
if (!this.reachedTargets.contains(targetId)) {
return false;

Check warning on line 291 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L291

Added line #L291 was not covered by tests
}
}
return true;
}
return false;

Check warning on line 296 in computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java

View check run for this annotation

Codecov / codecov/patch

computer-algorithm/src/main/java/org/apache/hugegraph/computer/algorithm/path/shortest/SingleSourceShortestPath.java#L296

Added line #L296 was not covered by tests
}

static class VertexInputJson {
private String id;
private String idType;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getIdType() {
return idType;
}

public void setIdType(String idType) {
this.idType = idType;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@
case UUID:
return (Id) BYTES_ID_UUID_METHOD.invoke(null, value);
default:
throw new ComputerException("Can't parse Id for %s", type.name());
throw new ComputerException("Unexpected id type %s", type.name());

Check warning on line 93 in computer-api/src/main/java/org/apache/hugegraph/computer/core/graph/id/IdFactory.java

View check run for this annotation

Codecov / codecov/patch

computer-api/src/main/java/org/apache/hugegraph/computer/core/graph/id/IdFactory.java#L93

Added line #L93 was not covered by tests
}
} catch (Exception e) {
throw new ComputerException("Failed to parse Id", e);
throw new ComputerException("Failed to parse %s Id: '%s'", e, type, value);

Check warning on line 96 in computer-api/src/main/java/org/apache/hugegraph/computer/core/graph/id/IdFactory.java

View check run for this annotation

Codecov / codecov/patch

computer-api/src/main/java/org/apache/hugegraph/computer/core/graph/id/IdFactory.java#L95-L96

Added lines #L95 - L96 were not covered by tests
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package org.apache.hugegraph.computer.core.util;

import java.io.IOException;
import java.util.List;

import org.apache.hugegraph.rest.SerializeException;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

// todo move to org.apache.hugegraph.util.JsonUtil later
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expect format // TODO: xx

public class JsonUtilExt {

Check warning on line 29 in computer-api/src/main/java/org/apache/hugegraph/computer/core/util/JsonUtilExt.java

View check run for this annotation

Codecov / codecov/patch

computer-api/src/main/java/org/apache/hugegraph/computer/core/util/JsonUtilExt.java#L29

Added line #L29 was not covered by tests

private static final ObjectMapper MAPPER = new ObjectMapper();

public static <T> List<T> fromJson2List(String json, Class<T> clazz) {
try {
return MAPPER.readValue(json, getCollectionType(List.class, clazz));
} catch (IOException e) {
throw new SerializeException("Failed to deserialize json '%s'",

Check warning on line 37 in computer-api/src/main/java/org/apache/hugegraph/computer/core/util/JsonUtilExt.java

View check run for this annotation

Codecov / codecov/patch

computer-api/src/main/java/org/apache/hugegraph/computer/core/util/JsonUtilExt.java#L36-L37

Added lines #L36 - L37 were not covered by tests
e, json);
}
}

private static JavaType getCollectionType(Class<?> collectionClass,
Class<?>... elementClasses) {
return MAPPER.getTypeFactory().constructParametricType(collectionClass, elementClasses);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public class SingleSourceShortestPathTest extends AlgorithmTestBase {
public static final String EL = "road";
public static final String PROPERTY_KEY = "distance";

public static final String SOURCE_ID = "A";
public static final String TARGET_ID = "E";
public static final String SOURCE_ID = "{\"id\": \"A\", \"idType\": \"string\"}";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use id format like this (ref VertexAPI.checkAndParseVertexId()):

String SOURCE_ID = "\"abc\"";
String SOURCE_ID = "\"123\"";
String SOURCE_ID = "123";
String SOURCE_ID = "U\"uuid-xxxx\"";

public static final String TARGET_ID = "[{\"id\": \"E\", \"idType\": \"string\"}]";
public static final String SHORTEST_PATH = "[A, C, B, E]";
public static final double TOTAL_WEIGHT = 28;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@ public void testParseId() {
Assert.assertEquals(IdType.LONG, IdUtil.parseId(IdCategory.NUMBER, "222").idType());
Assert.assertEquals(IdType.UTF8, IdUtil.parseId(IdCategory.STRING, "aaa222").idType());
diaohancai marked this conversation as resolved.
Show resolved Hide resolved
Assert.assertEquals(IdType.UUID, IdUtil.parseId(IdCategory.UUID, uuid).idType());

Assert.assertEquals(IdType.UTF8, IdUtil.parseId(IdCategory.STRING, "222").idType());
Assert.assertEquals(IdType.UTF8, IdUtil.parseId(IdCategory.STRING, uuid).idType());
}
}
Loading