Example #1
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));
        }
    }
Example #2
0
        /// <summary>
        /// 异步探索作者的所有论文,顺便探索他/她在发表这些论文时所位于的机构。
        /// 如果其他线程正在探索此节点,则等待此节点探索完毕。
        /// </summary>
        private async Task FetchAuthorsPapersAsync(IReadOnlyCollection <AuthorNode> authorNodes)
        {
            // 有些类似于
            //      Task LocalExploreAsync(IEnumerable<PaperNode> paperNodes);
            // 探索 author 的所有论文。此处的探索还可以顺便确定 author 的所有组织。
            var nodesToExplore = authorNodes
                                 .Where(n => GetStatus(n.Id).TryMarkAsFetching(NodeStatus.AuthorPapersFetching))
                                 .ToArray();

            if (nodesToExplore.Length == 0)
            {
                goto WAIT_FOR_EXPLORATIONS;
            }
            // 随后,先把 authorNodes 注册一遍。
            var fetchTasks = nodesToExplore.Select(n => n.Id)
                             .Partition(SEB.MaxChainedAuIdCount)
                             .Select(async ids =>
            {
                // 一次探索若干作者。这意味着不同作者的文章是混在一起的。
                // 假定 Partition 返回的是 IList / ICollection
                var idc = (ICollection <long>)ids;
                //var explorationResult =
                await SearchClient.EvaluateAsync(SEB.AuthorIdIn(idc),
                                                 Assumptions.AuthorMaxPapers * idc.Count,
                                                 ConcurrentPagingMode.Optimistic, async page =>
                {
                    foreach (var et in page.Entities)
                    {
                        // 此处还可以注册 paper 与其所有作者之间的关系。
                        // 这样做的好处是,万一 author1 和 author2 同时写了一篇论文。
                        // 在这里就可以发现了。
                        RegisterNode(et);
                        var localExploreTask = ExplorePaperAsync(et);
                        // 为检索结果里的所有作者注册所有可能的机构。
                        // 这里比较麻烦,因为一个作者可以属于多个机构,所以
                        // 不能使用 LocalExploreAsync (会认为已经探索过。)
                        // 而需要手动建立节点关系。
                        foreach (var au in et.Authors)
                        {
                            if (au.AffiliationId == null)
                            {
                                continue;
                            }
                            var author      = RegisterAuthorNode(au);
                            var affiliation = RegisterAffiliationNode(au);
                            RegisterEdge(author, affiliation);
                        }
                        await localExploreTask;
                    }
                    //return page.Entities.Count;
                });
                // 实际情况应当是, SUM(er.Entities.Count) >> idc.Count
                //var pageSubtotal = explorationResult.Sum();
                //if (pageSubtotal < idc.Count)
                //    Logger.Magik.Warn(this, "批量查询实体 Id 时,返回结果数量不足。期望:>>{0},实际:{1}。", idc.Count, pageSubtotal);
                //return pageSubtotal;
            });
            //var subtotal =
            await Task.WhenAll(fetchTasks);

            //var total = subtotal.Sum();
            // 标记为“已经探索过”。
            foreach (var an in nodesToExplore)
            {
                GetStatus(an.Id).MarkAsFetched(NodeStatus.AuthorPapersFetching);
            }
WAIT_FOR_EXPLORATIONS:
            //确保返回前,所有 Fetching 的节点已经由此线程或其他线程处理完毕。
            var waitResult = await Task.WhenAll(authorNodes.Select(n =>
                                                                   GetStatus(n.Id).UntilFetchedAsync(NodeStatus.AuthorPapersFetching)));

            Debug.Assert(waitResult.All(r => r));
        }