Ejemplo n.º 1
0
        /// <summary>
        /// 简单地注册一个节点。不执行任何探索。
        /// </summary>
        private void RegisterNode(KgNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException(nameof(node));
            }
#if DEBUG
            var factoryCalled = false;
            var nn            = nodes.GetOrAdd(node.Id, id =>
            {
                factoryCalled = true;
                return(node);
            });
            graph.Add(node.Id);
            if (!factoryCalled)
            {
                // 此节点已经被发现
                // 断言节点类型。
                if (nn.GetType() != node.GetType())
                {
                    Debug.Fail(string.Format("试图注册的节点{0}与已注册的节点{1}具有不同的类型。", node, nn));
                }
            }
#else
            // 性能关键,不要构造复杂的工厂函数。
            nodes.GetOrAdd(node.Id, node);
            graph.Add(node.Id);
#endif
        }
Ejemplo n.º 2
0
        /// <summary>
        /// 根据给定的一对标识符所对应的对象,异步检索二者之间可能的路径。
        /// </summary>
        /// <param name="id1">源点对象标识符。此标识符可以是论文(Id)或作者(AA.AuId)。</param>
        /// <param name="id2">漏点对象标识符。此标识符可以是论文(Id)或作者(AA.AuId)。</param>
        /// <returns>
        /// 一个 Task 。其运行结果是一个集合(ICollection),集合中的每一个项目是一个数组,代表一条路径。
        /// 数组按照 id1 → id2 的顺序返回路径途经的所有节点。
        /// 合法的节点类型包括
        /// 论文(Id), 研究领域(F.Fid), 期刊(J.JId), 会议(C.CId), 作者(AA.AuId), 组织(AA.AfId)。
        /// </returns>
        /// <remarks>
        /// 此函数应该是线程安全的……吧?
        /// </remarks>
        public override async Task <IReadOnlyCollection <KgNode[]> > FindPathsAsync(long id1, long id2)
        {
            Logger.Magik.Enter(this, $"{id1} -> {id2}");
            var sw = Stopwatch.StartNew();
            // 先找到实体/作者再说。
            var nodes = await Task.WhenAll(BeginExplorationAt(id1), BeginExplorationAt(id2));

            var node1 = nodes[0];
            var node2 = nodes[1];

            if (node1 == null)
            {
                throw new ArgumentException($"在 MAG 中找不到指定的 Id/AuId:{id1}。", nameof(id1));
            }
            if (node2 == null)
            {
                throw new ArgumentException($"在 MAG 中找不到指定的 Id/AuId:{id2}。", nameof(id2));
            }
#if DEBUG
            _Node1 = node1;
            _Node2 = node2;
#endif
            // 开始搜索。
            var hops = await Task.WhenAll(
                FindHop12PathsAsync(node1, node2),
                FindHop3PathsAsync(node1, node2));

            var result = MultiCollectionView.Create(hops);
            Debug.Assert(result.IsDistinct(ArrayEqualityComparer <KgNode> .Default));
            Logger.Magik.Trace(this, "在 {0} - {1} 之间找到了 {2} 条路径。", node1, node2, result.Count);
            TimerLogger.TraceTimer("Analyzer", sw);
            Logger.Magik.Exit(this);
            return(result);
        }
Ejemplo n.º 3
0
        //    /// <summary>
        //    /// 使用直方图方法计算一个作者的所有机构。
        //    /// </summary>
        //    private async Task FetchAuthorAffiliations(long authorId)
        //    {
        //        affiliationIds.Partition(SEB.MaxChainedAfIdCount)
        //            .Select(afids =>
        //            {
        //              SearchClient.CalcHistogramAsync(SEB.AuthorIdContains(authorId), "")

        //            });
        //        var nodesToExplore = authorNodes
        //            .Where(n => GetStatus(n.Id).TryMarkAsFetching(NodeStatus.AuthorPapersFetching))
        //            .ToArray();
        //        if (nodesToExplore.Length == 0) goto WAIT_FOR_EXPLORATIONS;
        //        WAIT_FOR_EXPLORATIONS:
        //        //确保返回前,所有 Fetching 的节点已经由此线程或其他线程处理完毕。
        //        var waitResult = await Task.WhenAll(authorNodes.Select(n =>
        //            GetStatus(n.Id).UntilFetchedAsync(NodeStatus.AuthorPapersFetching)));
        //        Debug.Assert(waitResult.All(r => r));
        //}

        /// <summary>
        /// 探索作者与指定机构 Id 列表之间的关系。
        /// 此操作已进行优化,仅用于探索作者与机构之间的关系。
        /// </summary>
        private async Task FetchAuthorAffiliationRelations(long authorId, IEnumerable <long> affiliationIds)
        {
            var    afidEnumerator     = affiliationIds.GetEnumerator();
            var    currentAfIds       = new HashSet <long>();
            Action FeedAffiliationIds = () =>
            {
                while (currentAfIds.Count < SEB.MaxChainedAfIdCount &&
                       afidEnumerator.MoveNext())
                {
                    // 我们只探索有必要探索的边。
                    if (!graph.Contains(authorId, afidEnumerator.Current))
                    {
                        currentAfIds.Add(afidEnumerator.Current);
                    }
                }
            };

            FeedAffiliationIds();
            while (currentAfIds.Count > 0)
            {
                // 直入主题,连多余的属性都不用包括。
                var er = await SearchClient.EvaluateAsync(SEB.AuthorIdContains(authorId, currentAfIds),
                                                          100, 0, GlobalServices.AuthorAffiliationASEvaluationAttributes);

                // 作者不属于 ids 中的任何一个机构。那就换一波吧。
                if (er.Entities.Count == 0)
                {
                    currentAfIds.Clear();
                }
                // 包含结果。把这些机构排除掉。
                // 顺便把搜到的论文小探索一下。
                foreach (var et in er.Entities)
                {
                    foreach (var au in et.Authors)
                    {
                        if (au.AffiliationId != null)
                        {
                            // 为检索结果里的所有作者注册所有可能的机构。
                            // 这里比较麻烦,因为一个作者可以属于多个机构,所以
                            // 不能使用 LocalExploreAsync (会认为已经探索过。)
                            // 而需要手动建立节点关系。
                            RegisterNode(KgNode.CreateAffiliation(au));
                            RegisterEdge(au.Id, au.AffiliationId.Value, true);
                            currentAfIds.Remove(au.AffiliationId.Value);
                        }
                    }
                    //await localExplorationTask;
                }
                // 然后看看还有没有其他的机构来探索一下。
                FeedAffiliationIds();
            }
        }
Ejemplo n.º 4
0
        private async Task <IReadOnlyCollection <KgNode[]> > FindHop3PathsAsync(KgNode node1, KgNode node2)
        {
            Debug.Assert(node1 != null);
            Debug.Assert(node2 != null);
            Logger.Magik.Enter(this, $"{node1} -> {node2}");
            // Notation
            // Node1 -- Node3 -- Node4 -- Node2
            var paths   = new List <KgNode[]>();
            var author1 = node1 as AuthorNode;

            if (author1 != null)
            {
                // Author 还需要补刀。
                await FetchAuthorsPapersAsync(new[] { author1 });
            }
            // 获取 Node1 出发所有可能的 Node3
            var nodes3 = graph.AdjacentOutVertices(node1.Id)
                         .Select(id => nodes[id])
                         .ToArray();

            // 探索 Node4
            await ExploreInterceptionNodesAsync(nodes3, node2);

            // 计算路径。
            // 从 Id1 出发,寻找所有可能的 Id3 。
            var id4PredecessorsDict = new Dictionary <long, List <long> >();

            foreach (var id3 in graph.AdjacentOutVertices(node1.Id))
            {
                foreach (var id4 in graph.AdjacentOutVertices(id3))
                {
                    id4PredecessorsDict.GetOrAddNew(id4).Add(id3);
                }
            }
            foreach (var id4 in graph.AdjacentInVertices(node2.Id))
            {
                var id3Nodes = id4PredecessorsDict.TryGetValue(id4);
                if (id3Nodes != null)
                {
                    // 有路径!
                    paths.AddRange(id3Nodes.Select(id3 =>
                                                   new[] { node1, nodes[id3], nodes[id4], node2 }));
                }
            }
            Logger.Magik.Trace(this, "在 {0} - {1} 之间找到了 {2} 条 3-hop 路径。", node1.Id, node2.Id, paths.Count);
            Logger.Magik.Exit(this);
            return(paths);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// 直接进行局部探索,不加锁,不检查是否探索过。
        /// </summary>
        /// <remarks>此函数应当由 LocalExplore 系列函数调用。</remarks>
        private void ExplorePaperUnsafe(Entity entity)
        {
            // 探索是指从某节点向外延展。那么 某节点 肯定是已经注册过的。
            Debug.Assert(graph.Contains(entity.Id));
            // 记录被引用次数。
            if (entity.CitationCount > 0)
            {
                _CitationCountDict[entity.Id] = entity.CitationCount;
            }
            var node = nodes[entity.Id];

            foreach (var an in KgNode.EnumerateLocalAdjacents(entity))
            {
                RegisterNode(an);
                RegisterEdge(node, an);
            }
        }
Ejemplo n.º 6
0
        /// <summary>
        /// 根据给定的两个节点,探索能够与这两个节点相连的中间结点集合。
        /// </summary>
        private Task ExploreInterceptionNodesAsync(IEnumerable <KgNode> nodes1, KgNode node2)
        {
            var tasks = nodes1
                        .ToLookup(n => n.GetType())
                        .Select(g =>
            {
                if (g.Key == typeof(PaperNode) ||
                    g.Key == typeof(AuthorNode) ||
                    g.Key == typeof(AffiliationNode) ||
                    g.Key == typeof(FieldOfStudyNode))
                {
                    // 论文、作者、组织、领域 可以批量处理。
                    return(ExploreInterceptionNodesInternalAsync(g.ToArray(), node2));
                }
                // 其它节点只能一个一个来。
                return(Task.WhenAll(g.Select(node => ExploreInterceptionNodesAsync(node, node2))));
            });

            return(Task.WhenAll(tasks));
        }
Ejemplo n.º 7
0
        private async Task <IReadOnlyCollection <KgNode[]> > FindHop12PathsAsync(KgNode node1, KgNode node2)
        {
            Debug.Assert(node1 != null);
            Debug.Assert(node2 != null);
            Logger.Magik.Enter(this, $"{node1} -> {node2}");
            await ExploreInterceptionNodesAsync(node1, node2);

            var paths = new List <KgNode[]>();
            var out1  = graph.AdjacentOutVertices(node1.Id);
            var in2   = graph.AdjacentInVertices(node2.Id);

            // 1-hop
            if (out1.Contains(node2.Id))
            {
                paths.Add(new[] { node1, node2 });
            }
            // 2-hop
            var commonNodes = out1.Intersect(in2);

            paths.AddRange(commonNodes.Select(cn => new[] { node1, nodes[cn], node2 }));
            Logger.Magik.Trace(this, "在 {0} - {1} 之间找到了 {2} 条 1/2-hop 路径。", node1.Id, node2.Id, paths.Count);
            Logger.Magik.Exit(this);
            return(paths);
        }
Ejemplo n.º 8
0
        /// <summary>
        /// 根据给定的两个节点,探索能够与这两个节点相连的中间结点集合。
        /// 注意,在代码中请使用 ExploreInterceptionNodesAsync 的重载。
        /// 不要直接调用此函数。
        /// </summary>
        private async Task ExploreInterceptionNodesInternalAsync(IReadOnlyList <KgNode> nodes1, KgNode node2)
        {
            if (nodes1 == null)
            {
                throw new ArgumentNullException(nameof(nodes1));
            }
            if (node2 == null)
            {
                throw new ArgumentNullException(nameof(node2));
            }
            if (nodes1.Count == 0)
            {
                return;                    // Nothing to explore.
            }
            KgNode node1;
            var    explore12DomainKey = new InterceptionFetchingDomainKey(node2.Id);
            // 注意, node1 和 node2 的方向是不能互换的,
            // 所以不要想着把对侧也标记为已经探索过了。
            var nodes1ToExplore = nodes1
                                  .Where(n => GetStatus(n.Id).TryMarkAsFetching(explore12DomainKey))
                                  .ToArray();

            Debug.Assert(nodes1ToExplore.IsDistinct());
            if (nodes1ToExplore.Length == 0)
            {
                goto WAIT_FOR_EXPLORATIONS;
            }
            IReadOnlyCollection <PaperNode>        papers1       = null;
            IReadOnlyCollection <AuthorNode>       authors1      = null;
            IReadOnlyCollection <AffiliationNode>  affiliations1 = null;
            IReadOnlyCollection <FieldOfStudyNode> foss1         = null;

            if (nodes1ToExplore.Length == 1)
            {
                node1 = nodes1ToExplore[0];
                if (node1 is PaperNode)
                {
                    papers1 = new[] { (PaperNode)node1 }
                }
                ;
                else if (node1 is AuthorNode)
                {
                    authors1 = new[] { (AuthorNode)node1 }
                }
                ;
                else if (node1 is AffiliationNode)
                {
                    affiliations1 = new[] { (AffiliationNode)node1 }
                }
                ;
                else if (node1 is FieldOfStudyNode)
                {
                    foss1 = new[] { (FieldOfStudyNode)node1 }
                }
                ;
            }
            else
            {
                node1 = null;
                if (nodes1ToExplore[0] is PaperNode)
                {
                    papers1 = nodes1ToExplore.CollectionCast <PaperNode>();
                }
                else if (nodes1ToExplore[0] is AuthorNode)
                {
                    authors1 = nodes1ToExplore.CollectionCast <AuthorNode>();
                }
                else if (nodes1ToExplore[0] is AffiliationNode)
                {
                    affiliations1 = nodes1ToExplore.CollectionCast <AffiliationNode>();
                }
                else if (nodes1ToExplore[0] is FieldOfStudyNode)
                {
                    foss1 = nodes1ToExplore.CollectionCast <FieldOfStudyNode>();
                }
                else
                {
                    throw new ArgumentException("集合包含元素的类型过于复杂。请尝试使用单元素集合多次调用。", nameof(node1));
                }
            }
            if (papers1 != null)
            {
                // 需要调查 papers1 的 AuId JId CId FId 等联结关系。
                await FetchPapersAsync(papers1);
            }
            var paper2 = node2 as PaperNode;

            if (paper2 != null)
            {
                Debug.Assert(GetStatus(paper2.Id)
                             .GetFetchingStatus(NodeStatus.PaperFetching) == FetchingStatus.Fetched);
            }
            PaperNode[] papers1References = null;
            if (papers1 != null)
            {
                // 注意,参考文献(或是作者——尽管在这里不需要)很可能会重复。
                papers1References = papers1.SelectMany(p1 => graph
                                                       .AdjacentOutVertices(p1.Id))
                                    .Distinct()
                                    .Select(id => nodes[id])
                                    .OfType <PaperNode>()
                                    .ToArray();
            }
            // searchConstraint : 建议尽量简短,因为 FromPapers1 的约束可能
            // 会是 100 个条件的并。
            Func <string, Task> FetchFromPapers1References = searchConstraint =>
            {
                // 注意,我们是来做全局搜索的。像是 Id -> AuId -> Id
                // 这种探索在探索论文时应该已经解决了。
                Debug.Assert(papers1 != null);
                Debug.Assert(papers1References != null);
                var tasks = papers1References
                            .Partition(SEB.MaxChainedIdCount)
                            .Select(nodes3 => FetchPapersAsync(nodes3, searchConstraint));
                return(Task.WhenAll(tasks));
            };

            if (paper2 != null)
            {
                // 带有 作者/研究领域/会议/期刊 等属性限制,
                // 搜索 >引用< 中含有 paper2 的论文 Id。
                // attributeConstraint 可以长一些。
                if (papers1 != null)
                {
                    // Id1 -> Id3 -> Id2
                    //Trace.WriteLine("Fetch From Papers1 " + papers1.Count);
                    var citations     = CitationCount(paper2);
                    var papersToFetch = papers1References.Length;
                    if (citations < papersToFetch)
                    {
                        // 从 Id2 方向向左探索 Id1 可能会好一些。
                        // 注意到 Left to Right 一次只能探索约 100 篇
                        // Right to Left 一次可以探索约 1000 篇,就是有点儿慢。
                        await FetchCitationsToPaperAsync(paper2, null, ConcurrentPagingMode.Pessimistic);
                    }
                    else
                    {
                        await FetchFromPapers1References(SEB.ReferenceIdContains(paper2.Id));
                    }
                }
                else if (authors1 != null)
                {
                    // AA.AuId <-> Id -> Id
                    await Task.WhenAll(authors1
                                       .Select(n => n.Id)
                                       .Partition(SEB.MaxChainedAuIdCount)
                                       .Select(id1s => FetchCitationsToPaperAsync(paper2, SEB.AuthorIdIn(id1s),
                                                                                  ConcurrentPagingMode.Optimistic)));
                }
                else if (affiliations1 != null)
                {
                    // AA.AfId <-> AA.AuId <-> Id
                    // 注意,要确认 AuId 是否位于 nodes1 列表中。
                    // 这里的 AuId 是 Id2 论文的作者列表。
                    await Task.WhenAll(graph.AdjacentOutVertices(node2.Id)
                                       .Where(id => nodes[id] is AuthorNode)
                                       .Select(id => FetchAuthorAffiliationRelations(id, affiliations1.Select(af => af.Id))));
                }
                else if (foss1 != null)
                {
                    // F.FId <-> Id -> Id
                    await Task.WhenAll(foss1.Select(fos => fos.Id)
                                       .Partition(SEB.MaxChainedFIdCount)
                                       .Select(fids => FetchCitationsToPaperAsync(paper2, SEB.FieldOfStudyIdIn(fids),
                                                                                  ConcurrentPagingMode.Pessimistic)));
                }
                else if (node1 is ConferenceNode)
                {
                    // C.CId <-> Id -> Id
                    await FetchCitationsToPaperAsync(paper2, SEB.ConferenceIdEquals(node1.Id),
                                                     ConcurrentPagingMode.Optimistic);
                }
                else if (node1 is JournalNode)
                {
                    // J.JId <-> Id -> Id
                    await FetchCitationsToPaperAsync(paper2, SEB.JournalIdEquals(node1.Id),
                                                     ConcurrentPagingMode.Optimistic);
                }
                else
                {
                    Debug.WriteLine("Ignoreed: {0}-{1}", nodes1ToExplore[0], node2);
                }
            }
            else
            {
                Debug.Assert(node2 is AuthorNode);
                // 带有 研究领域/会议/期刊 等属性限制,
                // 搜索 author2 的论文 Id。
                // attributeConstraint 可以长一些。
                Func <string, Task> ExplorePapersOfAuthor2WithAttributes = async attributeConstraint =>
                {
                    // 如果作者的所有论文已经被探索过了,
                    // 那么很幸运,不需要再探索了。
                    if (await GetStatus(node2.Id).UntilFetchedAsync(NodeStatus.AuthorPapersFetching))
                    {
                        return;
                    }
                    await SearchClient.EvaluateAsync(SEB.And(
                                                         attributeConstraint, SEB.AuthorIdContains(node2.Id)),
                                                     Assumptions.AuthorMaxPapers, ConcurrentPagingMode.Optimistic, page =>
                                                     Task.WhenAll(page.Entities.Select(async entity =>
                    {
                        RegisterNode(entity);
                        await ExplorePaperAsync(entity);
                    })));
                };
                if (papers1 != null)
                {
                    // Id1 -> Id3 -> AA.AuId2
                    await FetchFromPapers1References(SEB.AuthorIdContains(node2.Id));
                }
                else if (authors1 != null)
                {
                    // AA.AuId1 <-> Id3 <-> AA.AuId2
                    // 探索 AA.AuId1 的所有论文。此处的探索还可以顺便确定 AuId1 的所有组织。
                    // 注意到每个作者都会写很多论文
                    // 不论如何,现在尝试从 Id1 向 Id2 探索。
                    // 我们需要列出 Id1 的所有文献,以获得其曾经位于的所有组织。
                    await FetchAuthorsPapersAsync(authors1);

                    // AA.AuId1 <-> AA.AfId3 <-> AA.AuId2
                    // AA.AuId1 <-> AA.AfId3 已经在前面探索完毕。
                    // 只需探索 AA.AfId3 <-> AA.AuId2 。
                    await FetchAuthorAffiliationRelations(node2.Id, authors1
                                                          .SelectMany(au1 => graph.AdjacentOutVertices(au1.Id)
                                                                      .Where(id2 => nodes[id2] is AffiliationNode)));
                }
                else if (affiliations1 != null)
                {
                    // 不可能和 Affiliations 扯上 2-hop 关系啦……
                    // AA.AfId - ? - AA.AuId
                }
                else if (foss1 != null)
                {
                    // F.FId <-> Id <-> AA.AuId
                    await Task.WhenAll(foss1.Select(fos => fos.Id)
                                       .Partition(SEB.MaxChainedFIdCount)
                                       .Select(fids => ExplorePapersOfAuthor2WithAttributes(SEB.FieldOfStudyIdIn(fids))));
                }
                else if (node1 is ConferenceNode)
                {
                    // C.CId <-> Id <-> AA.AuId
                    await ExplorePapersOfAuthor2WithAttributes(SEB.ConferenceIdEquals(node1.Id));
                }
                else if (node1 is JournalNode)
                {
                    // J.JId <-> Id <-> AA.AuId
                    await ExplorePapersOfAuthor2WithAttributes(SEB.JournalIdEquals(node1.Id));
                }
                else
                {
                    Debug.WriteLine("Ignoreed: {0}-{1}", nodes1ToExplore[0], node2);
                }
            }
            // 标记探索完毕
            foreach (var n in nodes1ToExplore)
            {
                GetStatus(n.Id).MarkAsFetched(explore12DomainKey);
            }
            // 等待其他线程(如果有)
WAIT_FOR_EXPLORATIONS:
            var waitResult =
                await Task.WhenAll(nodes1.Select(n => GetStatus(n.Id).UntilFetchedAsync(explore12DomainKey)));

            Debug.Assert(waitResult.All(r => r));
        }
    }
Ejemplo n.º 9
0
 /// <summary>
 /// 根据给定的两个节点,探索能够与这两个节点相连的中间结点集合。
 /// </summary>
 private Task ExploreInterceptionNodesAsync(KgNode node1, KgNode node2)
 {
     return(ExploreInterceptionNodesInternalAsync(new[] { node1 }, node2));
 }
Ejemplo n.º 10
0
 /// <summary>
 /// 根据给定的两个节点,探索能够与这两个节点相连的中间结点集合。
 /// </summary>
 private Task ExploreInterceptionNodesAsync(IReadOnlyList <AuthorNode> nodes1, KgNode node2)
 {
     // 多个作者只能一个一个搜。反正一篇文章应该没几个作者……吧?
     return(Task.WhenAll(nodes1.Select(au => ExploreInterceptionNodesInternalAsync(new[] { au }, node2))));
 }
Ejemplo n.º 11
0
 /// <summary>
 /// 根据给定的两个节点,探索能够与这两个节点相连的中间结点集合。
 /// </summary>
 private Task ExploreInterceptionNodesAsync(IReadOnlyList <PaperNode> nodes1, KgNode node2)
 {
     return(ExploreInterceptionNodesInternalAsync(nodes1, node2));
 }
Ejemplo n.º 12
0
 /// <summary>
 /// 注册一条边。
 /// </summary>
 private void RegisterEdge(KgNode node1, KgNode node2)
 {
     // 已知的单向边只有一种: Id1 --> Id2 (Id1.RId = Id2)
     RegisterEdge(node1.Id, node2.Id, !(node1 is PaperNode && node2 is PaperNode));
 }