Example #1
0
        private async Task FetchCitationsToPaperAsync(PaperNode paper, string constraint, ConcurrentPagingMode pagingMode)
        {
            Debug.Assert(paper != null);
            if (constraint == null)
            {
                if (!await GetStatus(paper.Id).MarkAsFetchingOrUntilFetched(NodeStatus.PaperCitationsFetching))
                {
                    return;
                }
            }
            // 一般来说, Paper2 肯定就是题目中的终结点,
            // 因此我们是知道其具体信息的。
            var maxPapers = CitationCount(paper);
            var strategy  = maxPapers == null
                ? ConcurrentPagingMode.Optimistic
                : pagingMode;

            if (maxPapers == null)
            {
                maxPapers = Assumptions.PaperMaxCitations;
            }
            var expr = SEB.ReferenceIdContains(paper.Id);

            if (constraint != null)
            {
                expr = SEB.And(expr, constraint);
            }
            await SearchClient.EvaluateAsync(expr, maxPapers.Value, strategy, async page =>
            {
                await Task.WhenAll(page.Entities.Select(entity =>
                {
                    RegisterNode(entity);
                    return(ExplorePaperAsync(entity));
                }));
            });

            if (constraint == null)
            {
                GetStatus(paper.Id).MarkAsFetched(NodeStatus.PaperCitationsFetching);
            }
        }
Example #2
0
        /// <summary>
        /// 同时探索多个节点。适用于文章节点。注意,此函数仅在查询方面进行了优化,
        /// 如果其他线程正在探索此节点,则等待此节点探索完毕。
        /// </summary>
        private async Task FetchPapersAsync(IReadOnlyCollection <KgNode> paperNodes, string constraint)
        {
            Debug.Assert(paperNodes != null);
            if (constraint == null)
            {
                await FetchPapersAsync(paperNodes);

                return;
            }
            // 1. 注意,有些节点可能仍处于 正在探索 的状态。
            //      需要在返回前等待这些正在探索的节点。
            // 2. 很不幸,一旦标记一个节点开始探索后,没有办法再标记为未探索,
            //      所以在有约束的情况下,只能在内层循环中对节点分别标记。
            var nodesToFetch = paperNodes.Where(n => GetStatus(n.Id).GetFetchingStatus(NodeStatus.PaperFetching) == FetchingStatus.Unfetched)
                               .ToArray();

            if (nodesToFetch.Length == 0)
            {
                goto WAIT_FOR_EXPLORATIONS;
            }
            await Task.WhenAll(nodesToFetch.Select(n => n.Id)
                               .Partition(SEB.MaxChainedIdCount)
                               .Select(ids =>
            {
                // 假定 Partition 返回的是 IList / ICollection
                var idc = (ICollection <long>)ids;
                return(SearchClient.EvaluateAsync(SEB.And(SEB.EntityIdIn(idc), constraint),
                                                  SEB.MaxChainedIdCount,
                                                  ConcurrentPagingMode.Optimistic,
                                                  async page =>
                {
                    foreach (var entity in page.Entities)
                    {
                        await ExplorePaperAsync(entity);
                    }
                }));
            }));

WAIT_FOR_EXPLORATIONS:
            ;
        }
Example #3
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));
        }
    }