我该如何实施" weak"与NetworkX的边缘?

时间:2014-12-09 21:30:21

标签: python networkx topological-sort

背景

我正在编写一个Python模块来收集和安排夜间自动更新的任务,并可选择以交互方式执行各个任务。 (夜间更新无条件地执行所有任务。)

  • 其中一些单独的任务依赖于另一个任务的执行来正常运行:例如如果任务A被安排(自动或手动)但任务B没有安排,那么必须在A之前将任务B添加到计划中。我将这些称为强依赖,因为他们& #34;拉入"必要时开展新任务。

  • 其他任务取决于另一个任务的输出,但如果未安排该任务,也可以对旧输出进行操作;例如如果任务C已安排但任务D未安排,则D 添加,C运行旧数据。我将这些称为弱依赖,因为它们太弱了"更改时间表。

我使用nx.DiGraph成功实现了强依赖关系来构建依赖关系,并nx.topological_sort生成最终任务调度(将请求的任务作为起始节点)。但是,我不确定如何实现弱依赖,因为topological_sort似乎总是添加由任何边连接的节点。我甚至试过设置边缘' weight为0,但没有运气。

实施

编辑:以下是我目前实施的一个粗略示例。

# tasklib.py
task_graph = nx.DiGraph()

def run(start_tasks):
    global task_graph

    for modname in glob.glob('modules/*.py'):
        __import__(os.path.splitext(modname)[0].replace('/','.'))

    task_queue = nx.topological_sort(task_graph, start_tasks)

    # Remove weakly-linked tasks here

    for t in task_queue:
        task_graph.node[t]['func']()

def task(func):
    task_graph.add_node(func.__name__, func=func)
    return func

def depends(*args): # strong dependency
    def add_deps(func):
        for dep in args:
            task_graph.add_edge(func.__name__, dep)
        return func
    return add_deps

def after(*args): # weak dependency
    def add_deps(func):
        for dep in args:
            task_graph.add_edge(func.__name__, dep, weak=True)
        return func
    return add_deps


# modules/mytasks.py
from tasklib import task, depends, after

@task
def process_data():
    # do some lengthy processing

@task
@depends('process_data')
def process_more_data():
    # do even more processing on the above output

@task
@after('process_data')
def generate_report():
    # generate a report on the processed data

此处,process_more_data需要process_data的最新输出才能继续工作,因此它使用强依赖性。另一方面,generate_report可以使用旧数据自行运行,但如果还安排了process_data,则在新结果生成旧数据报告时没有意义即将生产;它不应该运行到之后。

根据@ BrenBarn的建议,我可能会尝试向run添加一个额外步骤,以便从task_queue删除弱链接的任务,除非它们也存在于start_tasks中。

问题

我的问题归结为:如果两个节点都在起始节点集中,我是否可以创建参与排序的边缘?如果是这样,怎么样?如果没有,是否有实现此效果的替代方法或库?

我考虑过为自己的目的编写一个带有此功能的精简网络库,但我希望有更好的方法。

额外学分

除此之外,我还想支持"后依赖关系,"其中一个任务意味着依赖任务在之后运行 - 但我担心这超出了topo排序的范围。

1 个答案:

答案 0 :(得分:0)

一种方法是构建包含AB之间的有向边的依赖关系图,当且仅当A强烈依赖于BA时弱依赖于BB强烈依赖于其他任务。

这里有一些代码可以做到这一点。在此示例中,我们将考虑六个任务,af

import networkx as nx

deps = nx.DiGraph()
deps.add_nodes_from("abcdef")
deps.add_edges_from([("a","b"), ("c","a"), ("e","c")], is_strong=True)
deps.add_edges_from([("f","c"), ("d","c")], is_strong=False)

我们可以理解为a强烈要求b等,而fc的弱要求。用户将选择要运行的任务ac。由于任务a已运行,我们需要c才能在其之前运行,因此还需要e才能在c之前运行。任务d没有强依赖关系,但它是c的弱依赖关系。因此,我们希望按顺序运行任务:d, e, c, a

首先,我们将通过抛弃所有弱边缘来提取诱导的子图。我们将在此图中查看每个计划任务的祖先:这是我们需要运行的依赖集。然后我们将采用由这些节点引起的完整依赖图的子图。此图表上的topo排序产生了我们想要的解决方案。

以下是代码:

def construct_task_graph(deps, tasks):
    required = set(tasks)
    strong_subgraph = extract_strong_subgraph(deps)
    for task in tasks:
        ancestors = nx.ancestors(strong_subgraph, task)
        required.update(ancestors)

    return nx.subgraph(deps, required)

def extract_strong_subgraph(deps):
    strong_deps = nx.DiGraph()
    strong_deps.add_nodes_from(deps)

    strong_edges = (e for e in deps.edges_iter(data=True) if e[2]["is_strong"])
    strong_deps.add_edges_from(strong_edges)

    return strong_deps

请注意construct_task_graph效率有点低:任务可能是在早期迭代中处理的任务的祖先,产生重复的要求。这些被集合抛出,但更有效的方法是实现类似DFS的东西。

无论如何,我们得到:

>>> task_graph = construct_task_graph(deps, ["a", "d"])
>>> task_graph.nodes()
['a', 'c', 'e', 'd']
>>> task_graph.edges()
[('c', 'a'), ('e', 'c'), ('d', 'c')]

最重要的是:

>>> nx.topological_sort(task_graph)
['d', 'e', 'c', 'a']