diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/AllShortestPathsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/AllShortestPathsAPI.java index 1f88ad4654..62859abdc4 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/AllShortestPathsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/AllShortestPathsAPI.java @@ -19,6 +19,8 @@ package com.baidu.hugegraph.api.traversers; +import java.util.List; + import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; @@ -45,6 +47,7 @@ import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import com.google.common.collect.ImmutableList; @Path("graphs/{graph}/traversers/allshortestpaths") @Singleton @@ -81,9 +84,10 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); ShortestPathTraverser traverser = new ShortestPathTraverser(g); + List edgeLabels = ImmutableList.of(edgeLabel); HugeTraverser.PathSet paths = traverser.allShortestPaths( - sourceId, targetId, dir, edgeLabel, depth, - degree, skipDegree, capacity); + sourceId, targetId, dir, edgeLabels, + depth, degree, skipDegree, capacity); return manager.serializer(g).writePaths("paths", paths, false); } } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java index 26a4cf00de..293905a9fd 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java @@ -84,7 +84,7 @@ public String post(@Context GraphManager manager, request.capacity, request.limit); HugeGraph g = graph(manager, graph); - Iterator sources = request.sources.sourcesVertices(g); + Iterator sources = request.sources.vertices(g); List patterns; patterns = pathPatterns(g, request); @@ -132,7 +132,7 @@ public String post(@Context GraphManager manager, private static class CrosspointsRequest { @JsonProperty("sources") - public SourceVertices sources; + public Vertices sources; @JsonProperty("path_patterns") public List pathPatterns; @JsonProperty("capacity") diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedKneighborAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedKneighborAPI.java new file mode 100644 index 0000000000..b6e0224f3d --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedKneighborAPI.java @@ -0,0 +1,145 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.api.traversers; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.backend.query.QueryResults; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.CustomizedKneighborTraverser; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.*; + +@Path("graphs/{graph}/traversers/customizedkneighbor") +@Singleton +public class CustomizedKneighborAPI extends TraverserAPI { + + private static final Logger LOG = Log.logger(RestServer.class); + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.source, + "The source of request can't be null"); + E.checkArgument(request.step != null, + "The steps of request can't be null"); + if (request.countOnly) { + E.checkArgument(!request.withVertex && !request.withPath, + "Can't return vertex or path when count only"); + } + + LOG.debug("Graph [{}] get customized kneighbor from source vertex " + + "'{}', with step '{}', limit '{}', count_only '{}', " + + "with_vertex '{}' and with_path '{}'", + graph, request.source, request.step, request.limit, + request.countOnly, request.withVertex, request.withPath); + + HugeGraph g = graph(manager, graph); + Id sourceId = HugeVertex.getIdValue(request.source); + + EdgeStep step = step(g, request.step); + + CustomizedKneighborTraverser traverser = + new CustomizedKneighborTraverser(g); + Set results = traverser.customizedKneighbor(sourceId, step, + request.maxDepth, + request.limit); + Set neighbors = new HashSet<>(); + for (Node node : results) { + neighbors.add(node.id()); + } + + List paths = new ArrayList<>(); + if (request.withPath) { + for (Node node : results) { + paths.add(new HugeTraverser.Path(node.path())); + } + } + Iterator iter = QueryResults.emptyIterator(); + if (request.withVertex) { + Set ids = new HashSet<>(); + for (HugeTraverser.Path p : paths) { + ids.addAll(p.vertices()); + } + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + } + return manager.serializer(g).writeNodes("kneighbor", neighbors, + paths, iter, request.countOnly); + } + + private static class Request { + + @JsonProperty("source") + public Object source; + @JsonProperty("step") + public TraverserAPI.Step step; + @JsonProperty("max_depth") + public int maxDepth; + @JsonProperty("limit") + public long limit = Long.valueOf(DEFAULT_PATHS_LIMIT); + @JsonProperty("count_only") + public boolean countOnly = false; + @JsonProperty("with_vertex") + public boolean withVertex = false; + @JsonProperty("with_path") + public boolean withPath = false; + + @Override + public String toString() { + return String.format("PathRequest{source=%s,step=%s,maxDepth=%s" + + "limit=%s,countOnly=%s,withVertex=%s," + + "withPath=%s}", this.source, this.step, + this.maxDepth, this.limit, this.countOnly, + this.withVertex, this.withPath); + } + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedKoutAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedKoutAPI.java new file mode 100644 index 0000000000..be1f885802 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedKoutAPI.java @@ -0,0 +1,154 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.api.traversers; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.backend.query.QueryResults; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.CustomizedKoutTraverser; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.*; + +@Path("graphs/{graph}/traversers/customizedkout") +@Singleton +public class CustomizedKoutAPI extends TraverserAPI { + + private static final Logger LOG = Log.logger(RestServer.class); + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.source, + "The source of request can't be null"); + E.checkArgument(request.step != null, + "The steps of request can't be null"); + if (request.countOnly) { + E.checkArgument(!request.withVertex && !request.withPath, + "Can't return vertex or path when count only"); + } + + LOG.debug("Graph [{}] get customized kout from source vertex '{}', " + + "with step '{}', max_depth '{}', nearest '{}', " + + "count_only '{}', capacity '{}', limit '{}', " + + "with_vertex '{}' and with_path '{}'", + graph, request.source, request.step, request.maxDepth, + request.nearest, request.countOnly, request.capacity, + request.limit, request.withVertex, request.withPath); + + HugeGraph g = graph(manager, graph); + Id sourceId = HugeVertex.getIdValue(request.source); + + EdgeStep step = step(g, request.step); + + CustomizedKoutTraverser traverser = new CustomizedKoutTraverser(g); + System.out.println(request); + Set results = traverser.customizedKout(sourceId, step, + request.maxDepth, + request.nearest, + request.capacity, + request.limit); + Set neighbors = new HashSet<>(); + for (Node node : results) { + neighbors.add(node.id()); + } + + List paths = new ArrayList<>(); + if (request.withPath) { + for (Node node : results) { + paths.add(new HugeTraverser.Path(node.path())); + } + } + Iterator iter = QueryResults.emptyIterator(); + if (request.withVertex) { + Set ids = new HashSet<>(); + for (HugeTraverser.Path p : paths) { + ids.addAll(p.vertices()); + } + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + } + return manager.serializer(g).writeNodes("kout", neighbors, + paths, iter, request.countOnly); + } + + private static class Request { + + @JsonProperty("source") + public Object source; + @JsonProperty("step") + public TraverserAPI.Step step; + @JsonProperty("max_depth") + public int maxDepth; + @JsonProperty("nearest") + public boolean nearest = true; + @JsonProperty("count_only") + public boolean countOnly = false; + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + @JsonProperty("limit") + public long limit = Long.valueOf(DEFAULT_PATHS_LIMIT); + @JsonProperty("with_vertex") + public boolean withVertex = false; + @JsonProperty("with_path") + public boolean withPath = false; + + @Override + public String toString() { + return String.format("KoutRequest{source=%s,step=%s,maxDepth=%s" + + "nearest=%s,countOnly=%s,capacity=%s," + + "limit=%s,withVertex=%s,withPath=%s}", + this.source, this.step, this.maxDepth, + this.nearest, this.countOnly, this.capacity, + this.limit, this.withVertex, this.withPath); + } + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java index 8dfac69cd2..e49f808e0c 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java @@ -86,7 +86,7 @@ public String post(@Context GraphManager manager, request.withVertex); HugeGraph g = graph(manager, graph); - Iterator sources = request.sources.sourcesVertices(g); + Iterator sources = request.sources.vertices(g); List steps = step(g, request); boolean sorted = request.sortBy != SortBy.NONE; @@ -129,7 +129,7 @@ private static List step(HugeGraph graph, private static class PathRequest { @JsonProperty("sources") - public SourceVertices sources; + public Vertices sources; @JsonProperty("steps") public List steps; @JsonProperty("sort_by") diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/FusiformSimilarityAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/FusiformSimilarityAPI.java index 0a56f56b82..e6ef2858b7 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/FusiformSimilarityAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/FusiformSimilarityAPI.java @@ -97,7 +97,7 @@ public String post(@Context GraphManager manager, request.groupProperty, request.minGroups); HugeGraph g = graph(manager, graph); - Iterator sources = request.sources.sourcesVertices(g); + Iterator sources = request.sources.vertices(g); E.checkArgument(sources != null && sources.hasNext(), "The source vertices can't be empty"); EdgeLabel edgeLabel = request.label == null ? @@ -125,7 +125,7 @@ public String post(@Context GraphManager manager, private static class FusiformSimilarityRequest { @JsonProperty("sources") - public SourceVertices sources; + public Vertices sources; @JsonProperty("label") public String label; @JsonProperty("direction") diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/JaccardSimilarAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/JaccardSimilarAPI.java new file mode 100644 index 0000000000..5fc54796b0 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/JaccardSimilarAPI.java @@ -0,0 +1,105 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.api.traversers; + +import java.util.Map; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; + +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.JaccardSimilarTraverser; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser + .DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_LIMIT; + +@Path("graphs/{graph}/traversers/jaccardsimilar") +@Singleton +public class JaccardSimilarAPI extends TraverserAPI { + + private static final Logger LOG = Log.logger(RestServer.class); + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.vertex, + "The source vertex of request can't be null"); + E.checkArgument(request.step != null, + "The steps of request can't be null"); + E.checkArgument(request.top >= 0, + "The top must be >= 0, but got: %s", request.top); + + LOG.debug("Graph [{}] get jaccard similars from source vertex '{}', " + + "with step '{}', top '{}' and capacity '{}'", + graph, request.vertex, request.step, request.top); + + HugeGraph g = graph(manager, graph); + Id sourceId = HugeVertex.getIdValue(request.vertex); + + EdgeStep step = step(g, request.step); + + JaccardSimilarTraverser traverser = new JaccardSimilarTraverser(g); + Map results = traverser.jaccardSimilars(sourceId, step, + request.top, + request.capacity); + return manager.serializer(g).writeMap(results); + } + + private static class Request { + + @JsonProperty("vertex") + public Object vertex; + @JsonProperty("step") + public TraverserAPI.Step step; + @JsonProperty("top") + public int top = Integer.valueOf(DEFAULT_LIMIT); + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + + @Override + public String toString() { + return String.format("Request{vertex=%s,step=%s,top=%s," + + "capacity=%s}", this.vertex, this.step, + this.top, this.capacity); + } + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/MultiNodeShortestPathAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/MultiNodeShortestPathAPI.java new file mode 100644 index 0000000000..c3cebec720 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/MultiNodeShortestPathAPI.java @@ -0,0 +1,126 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.api.traversers; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.backend.query.QueryResults; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.MultiNodeShortestPathTraverser; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; + +@Path("graphs/{graph}/traversers/multinodeshortestpath") +@Singleton +public class MultiNodeShortestPathAPI extends TraverserAPI { + + private static final Logger LOG = Log.logger(RestServer.class); + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.vertices, + "The vertices of request can't be null"); + E.checkArgument(request.step != null, + "The steps of request can't be null"); + + LOG.debug("Graph [{}] get multiple node shortest path from " + + "vertices '{}', with step '{}', max_depth '{}', capacity " + + "'{}' and with_vertex '{}'", + graph, request.vertices, request.step, request.maxDepth, + request.capacity, request.withVertex); + + HugeGraph g = graph(manager, graph); + Iterator vertices = request.vertices.vertices(g); + + EdgeStep step = step(g, request.step); + + MultiNodeShortestPathTraverser traverser = + new MultiNodeShortestPathTraverser(g); + List paths; + paths = traverser.multiNodeShortestPath(vertices, step, + request.maxDepth, + request.capacity); + + if (!request.withVertex) { + return manager.serializer(g).writePaths("paths", paths, false); + } + + Set ids = new HashSet<>(); + for (HugeTraverser.Path p : paths) { + ids.addAll(p.vertices()); + } + Iterator iter = QueryResults.emptyIterator(); + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + return manager.serializer(g).writePaths("paths", paths, false, iter); + } + + private static class Request { + + @JsonProperty("vertices") + public Vertices vertices; + @JsonProperty("step") + public Step step; + @JsonProperty("max_depth") + public int maxDepth; + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + @JsonProperty("with_vertex") + public boolean withVertex = false; + + @Override + public String toString() { + return String.format("Request{vertices=%s,step=%s,maxDepth=%s" + + "capacity=%s,withVertex=%s}", + this.vertices, this.step, this.maxDepth, + this.capacity, this.withVertex); + } + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java index ab10da79cd..282ed22db1 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java @@ -19,6 +19,8 @@ package com.baidu.hugegraph.api.traversers; +import java.util.List; + import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; @@ -45,6 +47,7 @@ import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import com.google.common.collect.ImmutableList; @Path("graphs/{graph}/traversers/shortestpath") @Singleton @@ -81,8 +84,9 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); ShortestPathTraverser traverser = new ShortestPathTraverser(g); + List edgeLabels = ImmutableList.of(edgeLabel); HugeTraverser.Path path = traverser.shortestPath(sourceId, targetId, - dir, edgeLabel, depth, + dir, edgeLabels, depth, degree, skipDegree, capacity); return manager.serializer(g).writeList("path", path.vertices()); diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TemplatePathAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TemplatePathAPI.java new file mode 100644 index 0000000000..02c1dc84e9 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TemplatePathAPI.java @@ -0,0 +1,140 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.api.traversers; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; + + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.backend.query.QueryResults; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.TemplatePathsTraverser; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +@Path("graphs/{graph}/traversers/templatepaths") +@Singleton +public class TemplatePathAPI extends TraverserAPI { + + private static final Logger LOG = Log.logger(RestServer.class); + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.sources, + "The sources of request can't be null"); + E.checkArgumentNotNull(request.targets, + "The targets of request can't be null"); + E.checkArgument(request.steps != null && !request.steps.isEmpty(), + "The steps of request can't be empty"); + + LOG.debug("Graph [{}] get template paths from source vertices '{}', " + + "target vertices '{}', with steps '{}', " + + "capacity '{}', limit '{}' and with_vertex '{}'", + graph, request.sources, request.targets, request.steps, + request.capacity, request.limit, request.withVertex); + + HugeGraph g = graph(manager, graph); + Iterator sources = request.sources.vertices(g); + Iterator targets = request.targets.vertices(g); + List steps = steps(g, request.steps); + + TemplatePathsTraverser traverser = new TemplatePathsTraverser(g); + HugeTraverser.PathSet pathSet; + pathSet = traverser.templatePaths(sources, targets, steps, + request.capacity, request.limit); + + if (!request.withVertex) { + return manager.serializer(g).writePaths("paths", pathSet, false); + } + + Set ids = new HashSet<>(); + for (HugeTraverser.Path p : pathSet) { + ids.addAll(p.vertices()); + } + Iterator iter = QueryResults.emptyIterator(); + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + return manager.serializer(g).writePaths("paths", pathSet, + false, iter); + } + + private static List steps(HugeGraph g, List steps) { + List edgeSteps = new ArrayList<>(steps.size()); + for (TraverserAPI.Step step : steps) { + edgeSteps.add(step(g, step)); + } + return edgeSteps; + } + + private static class Request { + + @JsonProperty("sources") + public Vertices sources; + @JsonProperty("targets") + public Vertices targets; + @JsonProperty("steps") + public List steps; + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + @JsonProperty("limit") + public long limit = Long.valueOf(DEFAULT_PATHS_LIMIT); + @JsonProperty("with_vertex") + public boolean withVertex = false; + + @Override + public String toString() { + return String.format("PathRequest{sources=%s,targets=%s,steps=%s," + + "capacity=%s,limit=%s,withVertex=%s}", + this.sources, this.targets, this.steps, + this.capacity, this.limit, this.withVertex); + } + } +} \ No newline at end of file diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TraverserAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TraverserAPI.java new file mode 100644 index 0000000000..c1fc086660 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TraverserAPI.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.api.traversers; + +import java.util.List; +import java.util.Map; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.api.API; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.type.define.Directions; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; + +public class TraverserAPI extends API { + + protected static EdgeStep step(HugeGraph graph, Step step) { + return new EdgeStep(graph, step.direction, step.labels, step.properties, + step.degree, step.skipDegree); + } + + protected static class Step { + + @JsonProperty("direction") + public Directions direction; + @JsonProperty("labels") + public List labels; + @JsonProperty("properties") + public Map properties; + @JsonProperty("degree") + public long degree = Long.valueOf(DEFAULT_DEGREE); + @JsonProperty("skip_degree") + public long skipDegree = 0L; + + @Override + public String toString() { + return String.format("Step{direction=%s,labels=%s,properties=%s," + + "degree=%s,skipDegree=%s}", + this.direction, this.labels, this.properties, + this.degree, this.skipDegree); + } + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SourceVertices.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Vertices.java similarity index 97% rename from hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SourceVertices.java rename to hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Vertices.java index 65b24d48b4..4c0a0f5685 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SourceVertices.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Vertices.java @@ -37,7 +37,7 @@ import com.baidu.hugegraph.util.E; import com.fasterxml.jackson.annotation.JsonProperty; -public class SourceVertices { +public class Vertices { @JsonProperty("ids") public Set ids; @@ -46,7 +46,7 @@ public class SourceVertices { @JsonProperty("properties") public Map properties; - public Iterator sourcesVertices(HugeGraph g) { + public Iterator vertices(HugeGraph g) { Map props = this.properties; E.checkArgument(!((this.ids == null || this.ids.isEmpty()) && (props == null || props.isEmpty()) && diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java index 3d19d09f07..7b2254da39 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.structure.Edge; @@ -50,6 +51,7 @@ import com.baidu.hugegraph.util.JsonUtil; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; public class JsonSerializer implements Serializer { @@ -286,4 +288,22 @@ public String writeWeightedPaths(WeightedPaths paths, return JsonUtil.toJson(ImmutableMap.of("paths", paths.toMap(), "vertices", vertices)); } + + @Override + public String writeNodes(String name, Set nodes, + Collection paths, + Iterator iterator, boolean countOnly) { + List> pathList; + pathList = new ArrayList<>(); + for (HugeTraverser.Path path : paths) { + pathList.add(path.toMap(false)); + } + + Map results; + results = ImmutableMap.of("size", nodes.size(), + name, countOnly ? ImmutableSet.of() : nodes, + "paths", pathList, + "vertices", iterator); + return JsonUtil.toJson(results); + } } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java index d04849655e..4ae0376f73 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java @@ -23,6 +23,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; @@ -34,6 +35,7 @@ import com.baidu.hugegraph.schema.PropertyKey; import com.baidu.hugegraph.schema.VertexLabel; import com.baidu.hugegraph.traversal.algorithm.CustomizedCrosspointsTraverser.CrosspointsPaths; + import com.baidu.hugegraph.traversal.algorithm.FusiformSimilarityTraverser.SimilarsMap; import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; import com.baidu.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.NodeWithWeight; @@ -98,4 +100,8 @@ public String writeWeightedPath(NodeWithWeight path, public String writeWeightedPaths(WeightedPaths paths, Iterator vertices); + + public String writeNodes(String name, Set nodes, + Collection paths, + Iterator iterator, boolean countOnly); } diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizedKneighborTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizedKneighborTraverser.java new file mode 100644 index 0000000000..718853cd14 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizedKneighborTraverser.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.Set; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.util.CollectionUtil; +import com.baidu.hugegraph.util.E; + +public class CustomizedKneighborTraverser extends HugeTraverser { + + public CustomizedKneighborTraverser(HugeGraph graph) { + super(graph); + } + + public Set customizedKneighbor(Id source, EdgeStep step, + int maxDepth, long limit) { + E.checkNotNull(source, "source vertex id"); + this.checkVertexExist(source, "source vertex"); + checkPositive(maxDepth, "k-neighbor max_depth"); + checkLimit(limit); + + Set latest = newSet(); + Set all = newSet(); + + Node sourceV = new KNode(source, null); + + latest.add(sourceV); + all.add(sourceV); + + while (maxDepth-- > 0) { + long remaining = limit == NO_LIMIT ? NO_LIMIT : limit - all.size(); + latest = this.adjacentVertices(latest, step, all, remaining); + int size = all.size() + latest.size(); + if (limit != NO_LIMIT && size >= limit) { + int subLength = (int) limit - all.size(); + all.addAll(CollectionUtil.subSet(latest, 0, subLength)); + break; + } else { + all.addAll(latest); + } + } + + return all; + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizedKoutTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizedKoutTraverser.java new file mode 100644 index 0000000000..f1ccf5283b --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizedKoutTraverser.java @@ -0,0 +1,76 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.Set; + +import com.baidu.hugegraph.HugeException; +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.util.E; + +public class CustomizedKoutTraverser extends HugeTraverser { + + public CustomizedKoutTraverser(HugeGraph graph) { + super(graph); + } + + public Set customizedKout(Id source, EdgeStep step, int maxDepth, + boolean nearest, long capacity, + long limit) { + E.checkNotNull(source, "source vertex id"); + this.checkVertexExist(source, "source vertex"); + checkPositive(maxDepth, "k-out max_depth"); + checkCapacity(capacity); + checkLimit(limit); + + Set latest = newSet(); + Set all = newSet(); + + Node sourceV = new KNode(source, null); + + latest.add(sourceV); + all.add(sourceV); + + int depth = maxDepth; + long remaining = capacity == NO_LIMIT ? + NO_LIMIT : capacity - latest.size(); + while (depth-- > 0) { + if (nearest) { + latest = this.adjacentVertices(latest, step, all, remaining); + all.addAll(latest); + } else { + latest = this.adjacentVertices(latest, step, null, remaining); + } + if (capacity != NO_LIMIT) { + // Update 'remaining' value to record remaining capacity + remaining -= latest.size(); + + if (remaining <= 0 && depth > 0) { + throw new HugeException( + "Reach capacity '%s' while remaining depth '%s'", + capacity, depth); + } + } + } + + return latest; + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/EdgeStep.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/EdgeStep.java index bb545fa435..439f877b15 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/EdgeStep.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/EdgeStep.java @@ -37,7 +37,7 @@ public class EdgeStep { - protected final Directions direction; + protected Directions direction; protected final Map labels; protected final Map properties; protected final long degree; @@ -84,6 +84,10 @@ public Id[] edgeLabels() { return edgeLabels; } + public void swithDirection() { + this.direction = this.direction.opposite(); + } + public long limit() { long limit = this.skipDegree > 0L ? this.skipDegree : this.degree; return limit; diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java index 0cc21abf50..32e4617a41 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java @@ -46,6 +46,7 @@ import com.baidu.hugegraph.backend.tx.GraphTransaction; import com.baidu.hugegraph.exception.NotFoundException; import com.baidu.hugegraph.iterator.ExtendableIterator; +import com.baidu.hugegraph.iterator.FilterIterator; import com.baidu.hugegraph.iterator.MapperIterator; import com.baidu.hugegraph.schema.SchemaLabel; import com.baidu.hugegraph.structure.HugeEdge; @@ -76,6 +77,8 @@ public class HugeTraverser { public static final String DEFAULT_MAX_DEPTH = "50"; public static final String DEFAULT_WEIGHT = "0"; + protected static final int MAX_VERTICES = 10; + // Empirical value of scan limit, with which results can be returned in 3s public static final String DEFAULT_PAGE_LIMIT = "100000"; @@ -217,10 +220,12 @@ public double jaccardSimilarity(Id vertex, Id other, Directions dir, vertex, dir, labelId, degree)); Set targetNeighbors = IteratorUtils.set(this.adjacentVertices( other, dir, labelId, degree)); - int interNum = CollectionUtil.intersect(sourceNeighbors, - targetNeighbors).size(); - int unionNum = CollectionUtil.union(sourceNeighbors, - targetNeighbors).size(); + return jaccardSimilarity(sourceNeighbors, targetNeighbors); + } + + public double jaccardSimilarity(Set set1, Set set2) { + int interNum = CollectionUtil.intersect(set1, set2).size(); + int unionNum = CollectionUtil.union(set1, set2).size(); return (double) interNum / unionNum; } @@ -259,6 +264,35 @@ protected Iterator adjacentVertices(Id source, Directions dir, }); } + protected Set adjacentVertices(Id source, EdgeStep step) { + Set neighbors = new HashSet<>(); + Iterator edges = this.edgesOfVertex(source, step); + while (edges.hasNext()) { + neighbors.add(((HugeEdge) edges.next()).id().otherVertexId()); + } + return neighbors; + } + + protected Set adjacentVertices(Set vertices, EdgeStep step, + Set excluded, long remaining) { + Set neighbors = newSet(); + for (Node source : vertices) { + Iterator edges = this.edgesOfVertex(source.id(), step); + while (edges.hasNext()) { + Id target = ((HugeEdge) edges.next()).id().otherVertexId(); + KNode kNode = new KNode(target, (KNode) source); + if (excluded != null && excluded.contains(kNode)) { + continue; + } + neighbors.add(kNode); + if (--remaining <= 0L) { + return neighbors; + } + } + } + return neighbors; + } + protected Iterator edgesOfVertex(Id source, Directions dir, Id label, long limit) { Id[] labels = {}; @@ -309,16 +343,24 @@ private Iterator edgesOfVertex(Id source, EdgeStep edgeStep, Query query = GraphTransaction.constructEdgesQuery(source, edgeStep.direction, edgeLabels); + ConditionQuery filter = null; if (mustAllSK) { this.fillFilterBySortKeys(query, edgeLabels, edgeStep.properties); } else { - this.fillFilterByProperties(query, edgeStep.properties); + filter = (ConditionQuery) query.copy(); + this.fillFilterByProperties(filter, edgeStep.properties); } query.capacity(Query.NO_CAPACITY); if (edgeStep.limit() != NO_LIMIT) { query.limit(edgeStep.limit()); } Iterator edges = this.graph().edges(query); + if (filter != null) { + ConditionQuery finalFilter = filter; + edges = new FilterIterator<>(edges, (e) -> { + return finalFilter.test((HugeEdge) e); + }); + } return edgeStep.skipSuperNodeIfNeeded(edges); } @@ -590,6 +632,22 @@ public boolean equals(Object object) { } } + public static class KNode extends Node { + + public KNode(Id id, KNode parent) { + super(id, parent); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof KNode)) { + return false; + } + KNode other = (KNode) object; + return Objects.equals(this.id(), other.id()); + } + } + public static class Path { public static final Path EMPTY_PATH = new Path(ImmutableList.of()); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java new file mode 100644 index 0000000000..cecbef55ba --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.baidu.hugegraph.HugeException; +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.util.E; +import com.google.common.collect.ImmutableMap; + +public class JaccardSimilarTraverser extends HugeTraverser { + + public JaccardSimilarTraverser(HugeGraph graph) { + super(graph); + } + + public Map jaccardSimilars(Id source, EdgeStep step, + int top, long capacity) { + E.checkNotNull(source, "source vertex id"); + this.checkVertexExist(source, "source vertex"); + checkCapacity(capacity); + + long count = 0L; + Set accessed = new HashSet<>(); + accessed.add(source); + reachCapacity(++count, capacity); + + // Query neighbors + Set layer1s = this.adjacentVertices(source, step); + reachCapacity(count + layer1s.size(), capacity); + count += layer1s.size(); + if (layer1s.isEmpty()) { + return ImmutableMap.of(); + } + + Map results = new HashMap<>(); + Set layer2s; + Set layer2All = new HashSet<>(); + double jaccardSimilarity; + for (Id neighbor : layer1s) { + // Skip if accessed already + if (accessed.contains(neighbor)) { + continue; + } + layer2s = this.adjacentVertices(neighbor, step); + if (layer2s.isEmpty()) { + continue; + } + + layer2All.addAll(layer2s); + reachCapacity(count + layer2All.size(), capacity); + jaccardSimilarity = this.jaccardSimilarity(layer1s, layer2s); + results.put(neighbor, jaccardSimilarity); + accessed.add(neighbor); + } + count += layer2All.size(); + + Set layer3s; + for (Id neighbor : layer2All) { + // Skip if accessed already + if (accessed.contains(neighbor)) { + continue; + } + layer3s = this.adjacentVertices(neighbor, step); + reachCapacity(count + layer3s.size(), capacity); + if (layer3s.isEmpty()) { + continue; + } + + jaccardSimilarity = this.jaccardSimilarity(layer1s, layer3s); + results.put(neighbor, jaccardSimilarity); + accessed.add(neighbor); + } + + if (top > 0) { + results = HugeTraverser.topN(results, true, top); + } + return results; + } + + private static void reachCapacity(long count, long capacity) { + if (capacity != NO_LIMIT && count > capacity) { + throw new HugeException("Reach capacity '%s'", capacity); + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java new file mode 100644 index 0000000000..3db9c79b72 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java @@ -0,0 +1,91 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.util.E; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.Path.EMPTY_PATH; + +public class MultiNodeShortestPathTraverser extends HugeTraverser { + + public MultiNodeShortestPathTraverser(HugeGraph graph) { + super(graph); + } + + public List multiNodeShortestPath(Iterator vertices, + EdgeStep step, int maxDepth, + long capacity) { + List vertexList = IteratorUtils.list(vertices); + int size = vertexList.size(); + E.checkState(size >= 2 && size <= MAX_VERTICES, + "The number of vertices of multiple node shortest path " + + "must in [2, %s], but got: %s", + MAX_VERTICES, vertexList.size()); + List results = new ArrayList<>(); + cmn(vertexList, size, 2, 0, null, r -> { + Id source = ((HugeVertex) r.get(0)).id(); + Id target = ((HugeVertex) r.get(1)).id(); + ShortestPathTraverser traverser = + new ShortestPathTraverser(this.graph()); + Path path = traverser.shortestPath(source, target, step, + maxDepth, capacity); + if (!EMPTY_PATH.equals(path)) { + results.add(path); + } + }); + return results; + } + + private static void cmn(List all, int m, int n, int current, + List result, Consumer> consumer) { + assert m <= all.size(); + assert current < all.size(); + if (result == null) { + result = new ArrayList<>(n); + } + if (n == 0) { + // All n items are selected + consumer.accept(result); + return; + } + if (m < n || current >= all.size()) { + return; + } + + // Select current item, continue to select C(m-1, n-1) + int index = result.size(); + result.add(all.get(current)); + cmn(all, m - 1, n - 1, ++current, result, consumer); + // Not select current item, continue to select C(m-1, n) + result.remove(index); + cmn(all, m - 1, n, current, result, consumer); + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java index e72cc96139..aaec88379b 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java @@ -19,8 +19,11 @@ package com.baidu.hugegraph.traversal.algorithm; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import org.apache.tinkerpop.gremlin.structure.Edge; @@ -40,7 +43,7 @@ public ShortestPathTraverser(HugeGraph graph) { } public Path shortestPath(Id sourceV, Id targetV, Directions dir, - String label, int depth, long degree, + List labels, int depth, long degree, long skipDegree, long capacity) { E.checkNotNull(sourceV, "source vertex id"); E.checkNotNull(targetV, "target vertex id"); @@ -56,8 +59,11 @@ public Path shortestPath(Id sourceV, Id targetV, Directions dir, return new Path(ImmutableList.of(sourceV)); } - Id labelId = this.getEdgeLabelId(label); - Traverser traverser = new Traverser(sourceV, targetV, dir, labelId, + Map labelMap = new HashMap<>(labels.size()); + for (String label : labels) { + labelMap.put(this.getEdgeLabelId(label), label); + } + Traverser traverser = new Traverser(sourceV, targetV, dir, labelMap, degree, skipDegree, capacity); PathSet paths; while (true) { @@ -81,8 +87,15 @@ public Path shortestPath(Id sourceV, Id targetV, Directions dir, return paths.isEmpty() ? Path.EMPTY_PATH : paths.iterator().next(); } + public Path shortestPath(Id sourceV, Id targetV, EdgeStep step, + int depth, long capacity) { + return this.shortestPath(sourceV, targetV, step.direction, + new ArrayList<>(step.labels.values()), + depth, step.degree, step.skipDegree, capacity); + } + public PathSet allShortestPaths(Id sourceV, Id targetV, Directions dir, - String label, int depth, long degree, + List labels, int depth, long degree, long skipDegree, long capacity) { E.checkNotNull(sourceV, "source vertex id"); E.checkNotNull(targetV, "target vertex id"); @@ -100,8 +113,11 @@ public PathSet allShortestPaths(Id sourceV, Id targetV, Directions dir, return paths; } - Id labelId = this.getEdgeLabelId(label); - Traverser traverser = new Traverser(sourceV, targetV, dir, labelId, + Map labelMap = new HashMap<>(labels.size()); + for (String label : labels) { + labelMap.put(this.getEdgeLabelId(label), label); + } + Traverser traverser = new Traverser(sourceV, targetV, dir, labelMap, degree, skipDegree, capacity); while (true) { // Found, reach max depth or reach capacity, stop searching @@ -130,18 +146,19 @@ private class Traverser { private Map targets = newMap(); private final Directions direction; - private final Id label; + private final Map labels; private final long degree; private final long skipDegree; private final long capacity; private long size; - public Traverser(Id sourceV, Id targetV, Directions dir, Id label, - long degree, long skipDegree, long capacity) { + public Traverser(Id sourceV, Id targetV, Directions dir, + Map labels, long degree, + long skipDegree, long capacity) { this.sources.put(sourceV, new Node(sourceV)); this.targets.put(targetV, new Node(targetV)); this.direction = dir; - this.label = label; + this.labels = labels; this.degree = degree; this.skipDegree = skipDegree; this.capacity = capacity; @@ -158,7 +175,7 @@ public PathSet forward(boolean all) { // Traversal vertices of previous level for (Node v : this.sources.values()) { Iterator edges = edgesOfVertex(v.id(), this.direction, - this.label, degree); + this.labels, degree); edges = skipSuperNodeIfNeeded(edges, this.degree, this.skipDegree); while (edges.hasNext()) { @@ -209,7 +226,7 @@ public PathSet backward(boolean all) { // Traversal vertices of previous level for (Node v : this.targets.values()) { Iterator edges = edgesOfVertex(v.id(), opposite, - this.label, degree); + this.labels, degree); edges = skipSuperNodeIfNeeded(edges, this.degree, this.skipDegree); while (edges.hasNext()) { @@ -254,7 +271,7 @@ private boolean superNode(Id vertex, Directions direction) { return false; } Iterator edges = edgesOfVertex(vertex, direction, - this.label, this.skipDegree); + this.labels, this.skipDegree); return IteratorUtils.count(edges) >= this.skipDegree; } } diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/TemplatePathsTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/TemplatePathsTraverser.java new file mode 100644 index 0000000000..bf4504bc81 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/TemplatePathsTraverser.java @@ -0,0 +1,260 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeEdge; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.util.CollectionUtil; +import com.baidu.hugegraph.util.E; + +public class TemplatePathsTraverser extends HugeTraverser { + + public TemplatePathsTraverser(HugeGraph graph) { + super(graph); + } + + @SuppressWarnings("unchecked") + public PathSet templatePaths(Iterator sources, + Iterator targets, + List steps, + long capacity, long limit) { + checkCapacity(capacity); + checkLimit(limit); + + List sourceList = new ArrayList<>(); + while (sources.hasNext()) { + sourceList.add(((HugeVertex) sources.next()).id()); + } + int sourceSize = sourceList.size(); + E.checkState(sourceSize >= 1 && sourceSize <= MAX_VERTICES, + "The number of source vertices must in [1, %s], " + + "but got: %s", MAX_VERTICES, sourceList.size()); + List targetList = new ArrayList<>(); + while (targets.hasNext()) { + targetList.add(((HugeVertex) targets.next()).id()); + } + int targetSize = targetList.size(); + E.checkState(targetSize >= 1 && targetSize <= MAX_VERTICES, + "The number of target vertices must in [1, %s], " + + "but got: %s", MAX_VERTICES, sourceList.size()); + + Set nodes = (Set) CollectionUtil.intersect(sourceList, + targetList); + Collection remainSources = CollectionUtils.subtract(sourceList, + nodes); + if (remainSources.isEmpty()) { + return new PathSet(); + } + Collection remaintargets = CollectionUtils.subtract(targetList, + nodes); + if (remaintargets.isEmpty()) { + return new PathSet(); + } + + Traverser traverser = new Traverser(remainSources, remaintargets, steps, + capacity, limit); + PathSet paths; + do { + // Forward + paths = traverser.forward(); + if (traverser.finish()) { + return paths; + } + + // Backward + paths = traverser.backward(); + if (traverser.finish()) { + for (Path path : paths) { + path.reverse(); + } + return paths; + } + } while (true); + } + + private class Traverser { + + private MultivaluedMap sources = newMultivalueMap(); + private MultivaluedMap targets = newMultivalueMap(); + private final List steps; + private final long capacity; + private final long limit; + + private long pathCount; + private int stepCount; + + public Traverser(Collection sources, Collection targets, + List steps, long capacity, long limit) { + for (Id id : sources) { + this.sources.add(id, new Node(id)); + } + for (Id id : targets) { + this.targets.add(id, new Node(id)); + } + this.steps = steps; + this.capacity = capacity; + this.limit = limit; + + this.pathCount = 0L; + this.stepCount = 0; + } + + /** + * Search forward from sources + */ + public PathSet forward() { + PathSet paths = new PathSet(); + MultivaluedMap newVertices = newMultivalueMap(); + EdgeStep step = this.steps.get(this.stepCount / 2); + Iterator edges; + // Traversal vertices of previous level + for (Map.Entry> entry : this.sources.entrySet()) { + Id vid = entry.getKey(); + edges = edgesOfVertex(vid, step); + + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + + for (Node n : entry.getValue()) { + // If have loop, skip target + if (n.contains(target)) { + continue; + } + + // If cross point exists, path found, concat them + if (this.lastStep() && + this.targets.containsKey(target)) { + for (Node node : this.targets.get(target)) { + List path = n.joinPath(node); + if (!path.isEmpty()) { + paths.add(new Path(target, path)); + ++this.pathCount; + if (this.reachLimit()) { + return paths; + } + } + } + } + + // Add node to next start-nodes + newVertices.add(target, new Node(target, n)); + } + } + } + // Re-init sources + this.sources = newVertices; + + this.stepCount++; + + return paths; + } + + /** + * Search backward from target + */ + public PathSet backward() { + PathSet paths = new PathSet(); + MultivaluedMap newVertices = newMultivalueMap(); + int index = this.steps.size() - stepCount / 2 - 1; + EdgeStep step = this.steps.get(index); + step.swithDirection(); + Iterator edges; + // Traversal vertices of previous level + for (Map.Entry> entry : this.targets.entrySet()) { + Id vid = entry.getKey(); + edges = edgesOfVertex(vid, step); + + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + + for (Node n : entry.getValue()) { + // If have loop, skip target + if (n.contains(target)) { + continue; + } + + // If cross point exists, path found, concat them + if (this.lastStep() && + this.sources.containsKey(target)) { + for (Node node : this.sources.get(target)) { + List path = n.joinPath(node); + if (!path.isEmpty()) { + paths.add(new Path(target, path)); + ++this.pathCount; + if (this.reachLimit()) { + return paths; + } + } + } + } + + // Add node to next start-nodes + newVertices.add(target, new Node(target, n)); + } + } + } + + // Re-init targets + this.targets = newVertices; + + this.stepCount++; + + return paths; + } + + private boolean finish() { + return this.stepCount == this.steps.size(); + } + + private boolean lastStep() { + return this.stepCount == this.steps.size() - 1; + } + + private int accessedNodes() { + return this.sources.size() + this.targets.size(); + } + + private boolean reachLimit() { + checkCapacity(this.capacity, this.accessedNodes(), + "template paths"); + if (this.limit == NO_LIMIT || this.pathCount < this.limit) { + return false; + } + return true; + } + } +}