VisTrails1是一个支持数据探索和可视化的开源系统。它包含并大幅扩展了科学工作流和可视化系统的有用功能。与 Kepler 和 Taverna 等科学工作流系统一样,VisTrails 允许指定计算过程,这些过程根据一组规则集成现有的应用程序、松散耦合的资源和库。与 AVS 和 ParaView 等可视化系统一样,VisTrails 使高级科学和信息可视化技术可供用户使用,允许他们探索和比较数据的不同视觉表示。因此,用户可以创建复杂的工作流,涵盖科学发现的重要步骤,从数据收集和处理到复杂的分析和可视化,所有这些都集成在一个系统中。
VisTrails 的一个显著特征是其来源基础架构 [FSC+06]。VisTrails 捕获并维护探索任务过程中遵循的步骤和派生数据的详细历史记录。工作流传统上用于自动化重复性任务,但在本质上具有探索性的应用程序(例如数据分析和可视化)中,很少重复——变化是常态。当用户生成和评估关于其数据的假设时,随着他们迭代地调整,会创建一系列不同但相关的工作流。
VisTrails 旨在管理这些快速发展的工作流:它维护数据产品(例如,可视化、图表)、派生这些产品的流程及其执行的来源。该系统还提供注释功能,以便用户可以丰富自动捕获的来源。
除了实现可重复的结果外,VisTrails 还通过一系列操作和直观的用户界面利用来源信息来帮助用户协作分析数据。值得注意的是,该系统通过存储临时结果支持反思性推理,允许用户检查导致结果的操作并向前和向后跟踪推理链。用户可以以直观的方式浏览工作流版本,撤消更改而不会丢失结果,以可视化的方式比较多个工作流并在可视化电子表格中并排显示其结果。
VisTrails 解决了阻碍工作流和可视化系统更广泛应用的重要可用性问题。为了满足更广泛的用户群体(包括许多没有编程专业知识的用户),它提供了一系列操作和用户界面,简化了工作流设计和使用 [FSC+06],包括通过类比创建和改进工作流、通过示例查询工作流以及在用户使用推荐系统交互式构建工作流时建议工作流完成 [SVK+07]。我们还开发了一个新框架,允许创建自定义应用程序,这些应用程序可以更容易地部署到(非专家)最终用户。
VisTrails 的可扩展性来自一个基础架构,该基础架构使用户可以轻松地集成工具和库,以及快速原型化新功能。这有助于在广泛的应用领域中使用该系统,包括环境科学、精神病学、天文学、宇宙学、高能物理学、量子物理学和分子建模。
为了保持系统开源并免费供所有人使用,我们仅使用免费的开源软件包构建了 VisTrails。VisTrails 使用 Python 编写,并使用 Qt 作为其 GUI 工具包(通过 PyQt Python 绑定)。由于用户和应用程序范围广泛,我们从一开始就考虑到可移植性设计了系统。VisTrails 可以在 Windows、Mac 和 Linux 上运行。
图 23.1:VisTrails 用户界面的组件
数据探索本质上是一个创造性的过程,需要用户找到相关数据、集成和可视化这些数据、在探索不同解决方案时与同行合作,以及传播结果。鉴于科学探索中常见的数据量和分析的复杂性,需要能够更好地支持创造力的工具。
这些工具有两个基本需求,它们是相辅相成的。首先,能够使用正式描述指定探索过程非常重要,理想情况下,这些描述是可执行的。其次,为了重现这些过程的结果以及推理解决问题的不同步骤,这些工具必须能够系统地捕获来源。VisTrails 正是在考虑这些需求的情况下设计的。
工作流系统支持创建将多个工具组合在一起的管道(工作流)。因此,它们能够自动化重复性任务并实现结果可重复性。工作流正在迅速取代各种任务中的原始 shell 脚本,大量基于工作流的应用程序(包括商业应用程序(例如 Apple 的 Mac OS X Automator 和 Yahoo! Pipes)和学术应用程序(例如 NiPype、Kepler 和 Taverna))证明了这一点。
与用高级语言编写的脚本和程序相比,工作流具有一些优势。它们提供了一个简单的编程模型,通过该模型,可以通过将一个任务的输出连接到另一个任务的输入来组成一系列任务。图 23.1 显示了一个工作流,该工作流读取包含天气观测值的 CSV 文件并创建值的散点图。
这种更简单的编程模型允许工作流系统提供直观的可视化编程界面,这使得它们更适合没有大量编程专业知识的用户。工作流还具有明确的结构:它们可以被视为图形,其中节点表示过程(或模块)及其参数,边捕获过程之间的数据流。在图 23.1的示例中,模块CSVReader
将文件名(/weather/temp_precip.dat
)作为参数,读取文件,并将内容馈送到模块GetTemperature
和GetPrecipitation
,然后依次将温度和降水值发送到生成散点图的 matplotlib 函数。
大多数工作流系统都是为特定应用领域设计的。例如,Taverna 面向生物信息学工作流,而 NiPype 允许创建神经影像工作流。虽然 VisTrails 支持其他工作流系统提供的许多功能,但它是为了支持广泛领域的通用探索性任务而设计的,它集成了多个工具、库和服务。
科学界充分认识到保留结果(和数据产品)来源信息的重要性。数据产品的来源(也称为审计跟踪、谱系和血统)包含有关用于派生数据产品的过程和数据的信息。来源提供了重要的文档,对于保存数据、确定数据的质量和作者身份以及复制和验证结果至关重要 [FKSS08]。
来源的一个重要组成部分是关于因果关系的信息,即对过程(步骤序列)的描述,该过程与输入数据和参数一起导致数据产品的创建。因此,来源的结构反映了用于派生给定结果集的工作流(或工作流集)的结构。
事实上,工作流系统在科学领域得到广泛使用的催化剂是它们可以轻松用于自动捕获来源。虽然早期工作流系统已被扩展以捕获来源,但 VisTrails 是设计来支持来源的。
图 23.2:通过注释增强的探索来源
系统的不同用户界面组件在图 23.1和图 23.2中进行了说明。用户使用工作流编辑器创建和编辑工作流。
要构建工作流图,用户可以从模块注册表中拖动模块并将其放到工作流编辑器画布中。VisTrails 提供了一系列内置模块,用户也可以添加自己的模块(有关详细信息,请参见第 23.3 节)。当选择模块时,VisTrails 会显示其参数(在参数编辑区域中),用户可以在其中设置和修改其值。
随着工作流规范的完善,系统会捕获更改并将它们呈现给用户,如以下所述的版本树视图。用户可以在 VisTrails 电子表格中与工作流及其结果进行交互。电子表格中的每个单元格都表示对应于工作流实例的视图。在图 23.1中,工作流编辑器中显示的工作流的结果显示在电子表格的左上角单元格中。用户可以直接修改工作流的参数以及同步电子表格中不同单元格的参数。
版本树视图帮助用户浏览不同的工作流版本。如图 23.2所示,通过点击版本树中的节点,用户可以查看工作流、其关联结果(可视化预览)和元数据。一些元数据是自动捕获的,例如创建特定工作流的用户 ID 和创建日期,但用户也可以提供其他元数据,包括用于标识工作流的标签和书面描述。
图 23.3:VisTrails 架构
VisTrails 的早期版本是用 Java 和 C++ 编写的 [BCC+05]。C++ 版本分发给了一些早期采用者,他们的反馈对塑造我们对系统需求至关重要。
在观察到多个科学社区中基于 Python 的库和工具数量增加的趋势后,我们选择使用 Python 作为 VisTrails 的基础。Python 正在迅速成为科学软件的通用现代粘合语言。许多用不同语言(如 Fortran、C 和 C++)编写的库使用 Python 绑定作为提供脚本功能的一种方式。由于 VisTrails 旨在促进在工作流中协调许多不同的软件库,因此纯 Python 实现使这变得容易得多。特别是,Python 具有类似于 LISP 环境中看到的动态代码加载功能,同时拥有更大的开发人员社区和极其丰富的标准库。2005 年后期,我们开始使用 Python/PyQt/Qt 开发当前系统。此选择极大地简化了对系统的扩展,特别是添加新的模块和包。
VisTrails 系统的测试版于 2007 年 1 月首次发布。从那时起,该系统已被下载超过 25,000 次。
如图23.3所示的高级架构图中描述了支持上述用户界面功能的内部组件。工作流执行由执行引擎控制,该引擎跟踪调用的操作及其相应的参数,并捕获工作流执行的来源(执行来源)。作为执行的一部分,VisTrails还允许将中间结果缓存到内存和磁盘中。正如我们在23.3节中讨论的那样,只有模块和参数的新组合才会重新运行,这些组合通过调用底层库(例如,matplotlib)中的相应函数来执行。然后,与来源相关联的工作流结果可以包含在电子文档中(23.4节)。
有关工作流更改的信息捕获在版本树中,该版本树可以使用不同的存储后端持久化,包括本地目录中的XML文件存储和关系数据库。VisTrails还提供了一个查询引擎,允许用户浏览来源信息。
我们注意到,尽管VisTrails被设计为一个交互式工具,但它也可以在服务器模式下使用。创建工作流后,可以由VisTrails服务器执行。此功能在许多场景中很有用,包括创建允许用户与工作流交互的基于Web的界面以及在高性能计算环境中运行工作流的能力。
图23.4:基于变更的来源模型
我们使用VisTrails引入了一个新概念,即工作流演变的来源概念[FSC+06]。与以前的基于工作流的可视化系统不同,这些系统仅维护派生数据产品的来源,VisTrails将工作流视为一等数据项,并捕获其来源。工作流演变来源的可用性支持反思性推理。用户可以探索多条推理链,而不会丢失任何结果,并且由于系统存储了中间结果,用户可以推理并从这些信息中进行推断。它还支持一系列简化探索过程的操作。例如,用户可以轻松浏览为特定任务创建的工作流空间,直观地比较工作流及其结果(参见图23.4),并探索(大型)参数空间。此外,用户可以查询来源信息并通过示例学习。
工作流演变使用基于变更的来源模型来捕获。如图23.4所示,VisTrails存储应用于工作流的操作或更改(例如,添加模块、修改参数等),类似于数据库事务日志。此信息被建模为一棵树,其中每个节点对应一个工作流版本,父节点和子节点之间的边表示应用于父节点以获得子节点的更改。我们使用术语版本树和vistrail(视觉轨迹的简称)来交替指代这棵树。请注意,基于变更的模型统一捕获了参数值和工作流定义的更改。此更改序列足以确定数据产品的来源,并且还捕获有关工作流如何随时间演变的信息。该模型既简单又紧凑——它使用的空间远小于存储工作流多个版本的替代方案。
使用此模型会带来许多好处。图23.4显示了VisTrails提供的用于比较两个工作流的可视化差异功能。尽管工作流表示为图形,但使用基于变更的模型,比较两个工作流变得非常简单:只需导航版本树并识别将一个工作流转换为另一个工作流所需的一系列操作即可。
基于变更的来源模型的另一个重要好处是,底层版本树可以作为支持协作的机制。由于设计工作流是一项众所周知且困难的任务,因此它通常需要多个用户协作。版本树不仅提供了一种直观的方式来可视化不同用户的贡献(例如,根据创建相应工作流的用户对节点进行着色),而且模型的单调性允许用于同步多个用户执行的更改的简单算法。
在执行工作流时可以轻松捕获来源信息。执行完成后,维护数据产品与其来源(即用于派生数据产品的工作流、参数和输入文件)之间的强链接也很重要。当数据文件或来源被移动或修改时,可能难以找到与来源关联的数据或找到与数据关联的来源。VisTrails提供了一个持久存储机制来管理输入、中间和输出数据文件,从而加强来源和数据之间的链接。此机制为可重复性提供了更好的支持,因为它确保可以在来源信息中轻松(且正确)地找到引用的数据。这种管理的另一个重要好处是它允许缓存中间数据,然后可以与其他用户共享。
VisTrails中的执行引擎旨在允许集成新的和现有的工具和库。我们尝试适应常用的一些用于包装第三方科学可视化和计算软件的样式。特别是,VisTrails可以与作为预编译二进制文件(在shell上执行并使用文件作为输入/输出)或作为传递内部对象作为输入/输出的C++/Java/Python类库存在的应用程序库集成。
VisTrails采用数据流执行模型,其中每个模块执行计算,并且模块产生的数据通过模块之间存在的连接流动。模块以自下而上的方式执行;每个输入都是通过递归执行上游模块按需生成的(当有一系列连接从A到B时,我们说模块A是B的上游)。中间数据临时存储在内存中(作为Python对象)或磁盘上(由包含访问数据信息的Python对象包装)。
为了允许用户向VisTrails添加他们自己的功能,我们构建了一个可扩展的包系统(参见23.3节)。包允许用户将其自己的或第三方的模块包含在VisTrails工作流中。包开发人员必须识别一组计算模块,并为每个模块识别输入和输出端口,以及定义计算。对于现有库,计算方法需要指定从输入端口到现有函数的参数的转换以及从结果值到输出端口的映射。
在探索性任务中,经常会紧密连续地执行共享公共子结构的类似工作流。为了提高工作流执行效率,VisTrails缓存中间结果以最大程度地减少重新计算。因为我们重用以前的执行结果,所以我们隐式地假设可缓存模块是函数式的:给定相同的输入,模块将产生相同的输出。此要求对类施加了明确的行为限制,但我们认为它们是合理的。
但是,在某些情况下,这种行为显然是无法实现的。例如,将文件上传到远程服务器或将文件保存到磁盘的模块具有明显的副作用,而其输出相对不重要。其他模块可能会使用随机化,并且它们的非确定性可能是可取的;此类模块可以标记为不可缓存。但是,一些不是自然函数的模块可以进行转换;一个将数据写入两个文件的函数可能会被包装以输出文件的內容。
任何支持来源的系统的关键组件之一是数据的序列化和存储。VisTrails最初通过其内部对象(例如,版本树、每个模块)中嵌入的简单fromXML
和toXML
方法将数据存储在XML中。为了支持这些对象的架构演变,这些函数还对架构版本之间的任何转换进行编码。随着项目的进展,我们的用户群不断增长,我们决定支持不同的序列化,包括关系存储。此外,随着架构对象的演变,我们需要维护更好的基础设施来处理常见的数据管理问题,例如版本化架构、在版本之间进行转换以及支持实体关系。为此,我们添加了一个新的数据库(db)层。
db层由三个核心组件组成:域对象、服务逻辑和持久化方法。域和持久化组件是版本化的,因此每个架构版本都有自己的一组类。这样,我们维护代码来读取架构的每个版本。还有一些类定义了从一个架构版本到另一个架构版本的对象的转换。服务类提供与数据交互并处理架构版本检测和转换的方法。
由于编写大部分此代码既乏味又重复,因此我们使用模板和元架构来定义对象布局(以及任何内存索引)和序列化代码。元架构是用XML编写的,并且是可扩展的,因为可以添加除VisTrails定义的默认XML和关系映射之外的其他序列化。这类似于对象关系映射和Hibernate2和SQLObject3等框架,但添加了一些特殊例程来自动执行重新映射标识符和将对象从一个架构版本转换为下一个架构版本等任务。此外,我们还可以使用相同的元架构为多种语言生成序列化代码。在最初编写meta-Python(其中域和持久化代码是通过运行具有从元架构获得的变量的Python代码生成的)之后,我们最近迁移到了Mako模板4。
对于需要将其数据迁移到系统更新版本的用户的自动转换是关键。我们的设计添加了挂钩,使开发人员的此转换稍微不那么痛苦。因为我们维护每个版本的代码副本,所以转换代码只需要将一个版本映射到另一个版本即可。在根级别,我们定义一个映射来识别如何将任何版本转换为任何其他版本。对于较远的版本,这通常涉及通过多个中间版本的链。最初,这是一个单向映射,这意味着新版本不能转换为旧版本,但对于更新的架构映射,已添加反向映射。
每个对象都有一个update_version
方法,该方法采用对象的另一个版本并返回当前版本。默认情况下,它执行递归转换,其中每个对象都通过将旧对象的字段映射到新版本中的字段来升级。此映射默认为将每个字段复制到名称相同的字段,但可以定义一个方法来“覆盖”任何字段的默认行为。覆盖是一个采用旧对象并返回新版本的方法。由于架构的大多数更改仅影响少量字段,因此默认映射涵盖了大多数情况,但覆盖提供了灵活的方法来定义本地更改。
VisTrails的第一个原型具有一组固定的模块。它是开发有关VisTrails版本树和缓存多个执行运行的基本想法的理想环境,但它严重限制了长期实用性。
我们将VisTrails视为计算科学的基础设施,这意味着该系统应该为其他工具和流程的开发提供支架。这种场景的一个基本要求是可扩展性。实现这一点的一种典型方法是定义目标语言并编写相应的解释器。这很有吸引力,因为它提供了对执行的精确控制。鉴于我们的缓存需求,这种吸引力得到了增强。然而,实现一个成熟的编程语言是一项巨大的工程,这从来都不是我们的首要目标。更重要的是,强迫那些只是想使用VisTrails的用户学习一门全新的语言是不可取的。
我们希望有一个系统,可以让用户轻松添加自定义功能。同时,我们需要这个系统足够强大,能够表达相当复杂的软件片段。例如,VisTrails支持VTK可视化库5。VTK包含大约1000个类,这些类会根据编译、配置和操作系统而改变。由于为所有这些情况编写不同的代码路径似乎适得其反且最终毫无希望,因此我们决定有必要动态确定任何给定包提供的VisTrails模块集,而VTK自然成为了我们复杂包的模型目标。
计算科学是我们最初的目标领域之一,在我们设计系统的时候,Python正作为这些科学家之间的“粘合代码”而流行起来。通过使用Python本身来指定用户定义的VisTrails模块的行为,我们将几乎消除采用的一大障碍。事实证明,Python提供了一个很好的基础设施来实现动态定义的类和反射。Python中的几乎每个定义都有一个等价的一级表达式形式。对于我们的包系统来说,Python的两个重要的反射特性是
type
可调用对象进行函数调用来动态定义。返回值是类的表示,可以使用与典型定义的Python类完全相同的方式使用。__import__
进行函数调用来导入,并且结果值的行为与标准import
语句中的标识符相同。这些模块来自的路径也可以在运行时指定。当然,使用Python作为我们的目标也有一些缺点。首先,Python的这种动态特性意味着,虽然我们希望确保VisTrails包的一些东西(比如类型安全),但这通常是不可能的。更重要的是,VisTrails模块的一些需求,特别是关于引用透明性(稍后会详细介绍)的需求,在Python中无法强制执行。尽管如此,我们认为通过文化机制限制Python中允许的构造是值得的,并且有了这个警告,Python对于软件扩展性来说是一个极其有吸引力的语言。
VisTrails包封装了一组模块。它在磁盘中最常见的表示形式与Python包的表示形式相同(可能存在不幸的命名冲突)。Python包由一组定义Python值(如函数和类)的Python文件组成。VisTrails包是一个遵守特定接口的Python包。它包含定义特定函数和变量的文件。在最简单的形式中,VisTrails包应该是一个包含两个文件的目录:__init__.py
和init.py
。
第一个文件__init__.py
是Python包的要求,并且应该只包含几个应该保持不变的定义。虽然无法保证这一点,但未能遵守此要求的VisTrails包被认为是有错误的。该文件中定义的值包括包的全局唯一标识符,用于在工作流序列化时区分模块,以及包版本(在处理工作流和包升级时,包版本变得很重要,参见第23.4节)。此文件还可以包含名为package_dependencies
和package_requirements
的函数。由于我们允许VisTrails模块从除了根Module
类之外的其他VisTrails模块继承,因此可以想象一个VisTrails包扩展另一个包的行为,因此需要先初始化一个包,然后再初始化另一个包。这些包间依赖关系由package_dependencies
指定。另一方面,package_requirements
函数指定系统级库需求,VisTrails在某些情况下可以通过其捆绑包抽象尝试自动满足这些需求。
捆绑包是VisTrails通过特定于系统的工具(如RedHat的RPM或Ubuntu的APT)管理的系统级包。当满足这些属性时,VisTrails可以通过直接导入Python模块并访问相应的变量来确定包属性。
第二个文件init.py
包含所有实际VisTrails模块定义的入口点。此文件最重要的特性是定义了两个函数:initialize
和finalize
。当启用包后(在所有依赖包本身都启用后),会调用initialize
函数。它为包中的所有模块执行设置任务。另一方面,finalize
函数通常用于释放运行时资源(例如,可以清理包创建的临时文件)。
每个VisTrails模块在包中都由一个Python类表示。为了在VisTrails中注册此类,包开发者需要为每个VisTrails模块调用一次add_module
函数。这些VisTrails模块可以是任意的Python类,但必须满足一些要求。第一个要求是每个模块都必须是VisTrails定义的基本Python类的子类,这个类可能很无聊,叫做Module
。VisTrails模块可以使用多重继承,但只有一个类应该是VisTrails模块——在VisTrails模块树中不允许菱形继承。多重继承在定义类混合时特别有用:由父类编码的简单行为可以组合在一起以创建更复杂的行为。
可用端口集确定VisTrails模块的接口,因此不仅会影响这些模块的显示,还会影响它们与其他模块的连接。然后,必须将这些端口显式地描述给VisTrails基础设施。这可以通过在调用initialize
时对add_input_port
和add_output_port
进行适当的调用来完成,或者为每个VisTrails模块指定每个类的列表_input_ports
和_output_ports
来完成。
每个模块通过覆盖compute
方法来指定要执行的计算。数据通过端口在模块之间传递,并通过get_input_from_port
和set_result
方法访问。在传统的数据流环境中,执行顺序由数据请求按需指定。在我们的例子中,执行顺序由工作流模块的拓扑排序指定。由于缓存算法需要一个无环图,因此我们按照反拓扑排序顺序安排执行,因此对这些函数的调用不会触发上游模块的执行。我们故意做出这个决定:它使单独考虑每个模块的行为变得更简单,从而使我们的缓存策略更简单、更健壮。
作为一般准则,VisTrails模块在compute
方法的评估期间应避免使用具有副作用的函数。如第23.3节所述,此要求使部分工作流运行的缓存成为可能:如果模块遵守此属性,则其行为是上游模块输出的函数。然后,每个无环子图只需要计算一次,并且可以重用结果。
VisTrails模块及其通信的一个特殊特征是,在VisTrails模块之间传递的数据本身就是VisTrails模块。在VisTrails中,模块和数据类只有一个层次结构。例如,一个模块可以提供自身作为计算的输出(事实上,每个模块都提供一个默认的“self”输出端口)。主要缺点是失去了在基于数据流的体系结构中有时可以看到的计算和数据之间的概念分离。但是,有两个主要优点。首先,这非常类似于Java和C++的对象类型系统,并且这个选择并非偶然:对我们来说,支持大型类库(如VTK)的自动包装非常重要。这些库允许对象生成其他对象作为计算结果,这使得区分计算和数据的包装变得更加复杂。
此决定带来的第二个优点是,在工作流中定义常量值和用户可设置参数变得更容易,并且与系统的其余部分更加统一地集成。例如,考虑一个从Web上指定位置加载文件的工作流,该位置由一个常量指定。这目前由一个GUI指定,其中URL可以作为参数指定(参见图23.1中的参数编辑区域)。此工作流的自然修改是使用它来获取在某个上游计算的URL。我们希望工作流的其余部分尽可能少地发生变化。通过假设模块可以输出自身,我们可以简单地将具有正确值的字符串连接到对应于参数的端口。由于常量的输出评估为自身,因此行为与值实际上被指定为常量的情况完全相同。
图23.5:使用PythonSource
模块进行原型设计
设计常量还涉及其他考虑因素。每种常量类型都有一个不同的理想GUI界面来指定值。例如,在VisTrails中,文件常量模块提供了一个文件选择器对话框;布尔值由复选框指定;颜色值具有每个操作系统本地的颜色选择器。为了实现这种通用性,开发者必须从Constant
基类派生一个自定义常量,并提供覆盖,这些覆盖定义了合适的GUI小部件和字符串表示(以便可以将任意常量序列化到磁盘)。
我们注意到,对于简单的原型设计任务,VisTrails提供了一个内置的PythonSource
模块。PythonSource
模块可用于将脚本直接插入工作流中。PythonSource
的配置窗口(参见图23.5)允许指定多个输入和输出端口以及要执行的Python代码。
如上所述,VisTrails提供了一组功能和用户界面,简化了探索性计算任务的创建和执行。下面,我们将描述其中的一些。我们还简要讨论了VisTrails如何作为支持创建富血统出版物基础设施的基础。有关VisTrails及其功能的更全面描述,请参见VisTrails的在线文档6。
图23.6:可视化电子表格
VisTrails允许用户使用可视化电子表格(参见图23.6)来探索和比较来自多个工作流的结果。电子表格是一个VisTrails包,它拥有自己的界面,由工作表和单元格组成。每个工作表包含一组单元格,并具有可自定义的布局。单元格包含工作流生成的结果的可视化表示,并且可以自定义以显示各种类型的数据。
要在电子表格上显示单元格,工作流必须包含一个从基本SpreadsheetCell
模块派生的模块。每个SpreadsheetCell
模块对应于电子表格中的一个单元格,因此一个工作流可以生成多个单元格。SpreadsheetCell
模块的compute
方法处理执行引擎(图23.3)和电子表格之间的通信。在执行过程中,电子表格会根据其类型按需创建单元格,方法是利用Python的动态类实例化。因此,可以通过创建SpreadsheetCell
的子类并使其compute
方法向电子表格发送自定义单元格类型来实现自定义的可视化表示。例如,图23.1中的工作流MplFigureCell
是一个SpreadsheetCell
模块,旨在显示由matplotlib创建的图像。
由于电子表格使用 PyQt 作为其 GUI 后端,因此自定义单元格小部件必须从 PyQt 的QWidget
派生。它们还必须定义updateContents
方法,该方法由电子表格调用以在收到新数据时更新小部件。每个单元格小部件可以选择通过实现toolbar
方法来定义自定义工具栏;当单元格被选中时,它将显示在电子表格工具栏区域。
图 23.6显示了选中 VTK 单元格时的电子表格,在这种情况下,工具栏提供了用于导出 PDF 图像、将相机位置保存回工作流以及创建动画的特定小部件。电子表格包定义了一个可自定义的QCellWidget
,它提供了诸如历史回放(动画)和多点触控事件转发等常见功能。这可以用来代替QWidget
,从而更快地开发新的单元格类型。
即使电子表格仅接受 PyQt 小部件作为单元格类型,也可以集成使用其他 GUI 工具包编写的小部件。为此,小部件必须将其元素导出到本机平台,然后可以使用 PyQt 来获取它。我们对VTKCell
小部件使用了这种方法,因为实际的小部件是用 C++ 编写的。在运行时,VTKCell
获取窗口 ID(一个 Win32、X11 或 Cocoa/Carbon 句柄,具体取决于系统),并将其映射到电子表格画布。
与单元格类似,工作表也可以自定义。默认情况下,每个工作表都位于选项卡式视图中,并具有表格布局。但是,任何工作表都可以从电子表格窗口中分离出来,允许同时显示多个工作表。还可以通过对StandardWidgetSheet
(也是一个 PyQt 小部件)进行子类化来创建不同的工作表布局。StandardWidgetSheet
管理单元格布局以及在编辑模式下与电子表格的交互。在编辑模式下,用户可以操作单元格布局并在单元格上执行高级操作,而不是与单元格内容交互。此类操作包括应用类比(参见第 23.4 节)以及根据参数探索创建新的工作流版本。
在我们设计 VisTrails 时,我们希望能够使用谱系信息,而不仅仅是捕获它。首先,我们希望用户能够看到不同版本之间的确切差异,但随后我们意识到,一个更有用的功能是能够将这些差异应用于其他工作流。由于 VisTrails 跟踪工作流的演变,因此这两项任务都是可能的。
由于版本树捕获了所有更改,并且我们可以反转每个操作,因此我们可以找到一个完整的操作序列,将一个版本转换为另一个版本。请注意,某些更改会相互抵消,从而可以压缩此序列。例如,在计算差异时,无需检查后来删除的模块的添加。最后,我们有一些启发式方法来进一步简化序列:当同一模块出现在两个工作流中但通过单独的操作添加时,我们会取消添加和删除操作。
从更改集中,我们可以创建一个可视化表示,显示相似和不同的模块、连接和参数。这在图 23.4中进行了说明。出现在两个工作流中的模块和连接为灰色,仅出现在一个工作流中的模块和连接根据它们出现的那个工作流着色。参数不同的匹配模块为浅灰色阴影,用户可以在一个表格中检查特定模块的参数差异,该表格显示每个工作流中的值。
类比操作允许用户获取这些差异并将其应用于其他工作流。如果用户对现有工作流进行了一系列更改(例如,更改输出图像的分辨率和文件格式),则可以通过类比将相同的更改应用于其他工作流。为此,用户选择一个源工作流和一个目标工作流,这限定了所需更改的集合,以及他们希望将类比应用到的工作流。VisTrails 将前两个工作流之间的差异计算为模板,然后确定如何重新映射此差异以便将其应用于第三个工作流。由于可以将差异应用于与起始工作流不完全匹配的工作流,因此我们需要一种软匹配,允许在相似模块之间进行对应。通过这种匹配,我们可以重新映射差异,以便将更改序列应用于所选工作流 [SVK+07]。该方法并非万无一失,可能会生成与预期不完全相同的新工作流。在这种情况下,用户可以尝试修复任何引入的错误,或者返回到上一个版本并手动应用更改。
为了计算类比中使用的软匹配,我们希望在局部匹配(相同或非常相似的模块)与整体工作流结构之间取得平衡。请注意,即使是相同匹配的计算效率也很低,因为子图同构的难度很大,因此我们需要采用启发式方法。简而言之,如果两个工作流中两个有点相似的模块共享相似的邻居,我们可能会得出结论,这两个模块的功能相似,也应该进行匹配。更正式地说,我们构建一个产品图,其中每个节点都是原始工作流中模块的可能配对,并且边表示共享连接。然后,我们运行步骤,将每个节点的分数扩散到相邻节点的边上。这是一个类似于 Google 的 PageRank 的马尔可夫过程,最终将收敛,留下现在包含一些全局信息的分数集。根据这些分数,我们可以确定最佳匹配,使用阈值将非常不相似的模块留作未配对。
VisTrails 捕获的谱系包括一组工作流,每个工作流都有自己的结构、元数据和执行日志。用户能够访问和探索这些数据非常重要。VisTrails 提供了基于文本和可视化(所见即所得)的查询接口。对于标签、注释和日期等信息,用户可以使用带有可选标记的关键字搜索。例如,查找由user:~dakoop
创建的所有包含关键字plot
的工作流。但是,对工作流特定子图的查询更易于通过可视化查询示例界面表示,用户可以在其中从头构建查询或复制和修改管道中的现有部分。
在设计此查询示例界面时,我们保留了现有工作流编辑器的大部分代码,并对参数构造进行了一些更改。对于参数,搜索范围或关键字通常比搜索精确值更有用。因此,我们在参数值字段中添加了修饰符;当用户添加或编辑参数值时,可以选择其中一个修饰符,这些修饰符默认为精确匹配。除了可视化查询构建之外,查询结果也以可视化方式显示。版本树中突出显示匹配的版本,并且任何选定的工作流都将显示突出显示的匹配部分。用户可以通过发起另一个查询或单击重置按钮来退出查询结果模式。
VisTrails 保存了结果是如何导出的以及每个步骤的规范的谱系。但是,如果工作流所需的数据不再可用,则重新生成工作流运行可能会很困难。此外,对于长时间运行的工作流,将中间数据存储为跨会话的持久缓存可能很有用,以避免重新计算。
许多工作流系统将文件系统路径存储为数据的谱系,但这种方法存在问题。用户可能会重命名文件、将工作流移动到另一个系统而不复制数据或更改数据内容。在任何这些情况下,将路径存储为谱系都不够。对数据进行哈希并将其哈希存储为谱系有助于确定数据是否可能已更改,但如果数据存在,则无助于查找数据。为了解决这个问题,我们创建了持久性包,这是一个 VisTrails 包,它使用版本控制基础架构来存储可以从谱系中引用的数据。目前我们使用 Git 来管理数据,尽管可以轻松使用其他系统。
我们使用通用唯一标识符 (UUID) 来标识数据,并使用 git 的提交哈希来引用版本。如果数据从一次执行更改为另一次执行,则将新版本检入存储库。因此,(uuid, version)
元组是检索任何状态下数据的复合标识符。此外,我们还存储数据的哈希以及生成它的工作流上游部分的签名(如果它不是输入)。这允许链接可能以不同方式标识的数据,以及在再次运行相同计算时重用数据。
设计此包时,主要关注的是用户选择和检索数据的方式。此外,我们希望将所有数据保存在同一个存储库中,无论它是用作输入、输出还是中间数据(一个工作流的输出可能用作另一个工作流的输入)。用户可能使用两种主要模式来标识数据:选择创建新引用或使用现有引用。请注意,在第一次执行后,新的引用将成为现有的引用,因为它已在执行期间持久化;用户以后可以选择创建另一个引用,但这种情况很少见。由于用户通常希望始终使用数据的最新版本,因此未指定特定版本的引用将默认为最新版本。
回想一下,在执行模块之前,我们会递归更新其所有输入。如果上游计算已经运行,则持久数据模块不会更新其输入。为了确定这一点,我们将上游子工作流的签名与持久存储库进行比较,如果签名存在,则检索预先计算的数据。此外,我们将数据标识符和版本记录为谱系,以便可以重现特定执行。
随着谱系成为 VisTrails 的核心,升级旧工作流使其能够与新版本的包一起运行的能力是一个关键问题。由于包可以由第三方创建,因此我们需要升级工作流的基础架构以及包开发人员指定升级路径的挂钩。工作流升级中涉及的核心操作是用新版本替换一个模块。请注意,此操作很复杂,因为我们必须替换旧模块的所有连接和参数。此外,升级可能需要重新配置、重新分配或重命名模块的这些参数或连接,例如,当模块接口更改时。
每个包(及其关联的模块)都由版本标记,如果该版本发生更改,我们假设该包中的模块可能已更改。请注意,某些甚至大多数模块可能没有更改,但如果不进行我们自己的代码分析,我们就无法检查这一点。但是,我们尝试自动升级接口未更改的任何模块。为此,我们尝试用新版本替换模块,如果它不起作用,则抛出异常。当开发人员更改了模块的接口或重命名了模块时,我们允许他们明确指定这些更改。为了使这更容易管理,我们创建了一个remap_module
方法,允许开发人员仅定义需要修改默认升级行为的位置。例如,将输入端口“file”重命名为“value”的开发人员可以指定该特定重新映射,以便在创建新模块时,旧模块中与“file”的任何连接现在将连接到“value”。以下是一个内置 VisTrails 模块的升级路径示例
def handle_module_upgrade_request(controller, module_id, pipeline): module_remap = {'GetItemsFromDirectory': [(None, '1.6', 'Directory', {'dst_port_remap': {'dir': 'value'}, 'src_port_remap': {'itemlist': 'itemList'}, })], } return UpgradeWorkflowHandler.remap_module(controller, module_id, pipeline, module_remap)
这段代码将使用旧版GetItemsFromDirectory
(任何版本,直到1.6)模块的工作流升级为使用Directory
模块。它将旧模块的dir
端口映射到value
,并将itemlist
端口映射到itemList
。
任何升级都会在版本树中创建新版本,以便区分和比较升级前后的执行情况。升级可能会改变工作流的执行(例如,如果软件包开发者修复了某个bug),我们需要将此作为来源信息进行跟踪。请注意,在较旧的Vistrails中,可能需要升级树中的每个版本。为了减少混乱,我们只升级用户已访问过的版本。此外,我们提供了一个偏好设置,允许用户延迟任何升级的持久化,直到工作流被修改或执行;如果用户只是查看该版本,则无需持久化升级。
虽然可重复性是科学方法的基石,但描述计算实验的当前出版物往往未能提供足够的信息来使结果能够被重复或推广。最近,人们对可重复结果的发表重新产生了兴趣。更广泛采用这种实践的一个主要障碍是,很难创建一个包含所有组件(例如,数据、代码、参数设置)的包,这些组件需要重现结果以及验证结果。
通过捕获详细的来源信息,以及通过上面描述的许多功能,VisTrails简化了在系统内进行的计算实验的这一过程。但是,需要机制来将文档链接到并共享来源信息。
我们开发了VisTrails软件包,使论文中存在的成果能够链接到其来源,就像一个深层标题。使用我们开发的LaTeX软件包,用户可以包含链接到VisTrails工作流的图形。以下LaTeX代码将生成一个包含工作流结果的图形
\begin{figure}[t] { \vistrail[wfid=119,buildalways=false]{width=0.9\linewidth} } \caption{Visualizing a binary star system simulation. This is an image that was generated by embedding a workflow directly in the text.} \label{fig:astrophysics} \end{figure}
当使用pdflatex编译文档时,\vistrail
命令将使用接收到的参数调用Python脚本,该脚本向VisTrails服务器发送XML-RPC消息以使用id 119
执行工作流。此相同的Python脚本从服务器下载工作流的结果,并通过使用指定的布局选项(width=0.9\linewidth
)生成超链接的LaTeX \includegraphics
命令将其包含在生成的PDF文档中。
也可以将VisTrails结果包含到网页、wiki、Word文档和PowerPoint演示文稿中。Microsoft PowerPoint和VisTrails之间的链接是通过组件对象模型(COM)和对象链接与嵌入(OLE)接口完成的。为了使对象与PowerPoint交互,至少必须实现COM的IOleObject
、IDataObject
和IPersistStorage
接口。由于我们使用Qt的QAxAggregated
类(它是实现COM接口的抽象),来构建我们的OLE对象,因此Qt会自动处理IDataObject
和IPersistStorage
。因此,我们只需要实现IOleObject
接口。此接口中最重要的调用是DoVerb
。它允许VisTrails对PowerPoint的某些操作做出反应,例如对象激活。在我们的实现中,当激活VisTrails对象时,我们加载VisTrails应用程序并允许用户打开、交互并选择他们想要插入的管道。在他们关闭VisTrails之后,管道结果将显示在PowerPoint中。管道信息也与OLE对象一起存储。
为了使用户能够自由地共享其结果以及相关的来源信息,我们创建了crowdLabs。7 crowdLabs是一个社交网站,它集成了可用的工具集和可扩展的基础设施,为科学家提供了一个协作分析和可视化数据的环境。crowdLabs与VisTrails紧密集成。如果用户想共享在VisTrails中获得的任何结果,她可以直接从VisTrails连接到crowdLabs服务器上传信息。信息上传后,用户可以通过Web浏览器与工作流交互并执行工作流——这些工作流由为crowdLabs提供支持的VisTrails服务器执行。有关如何使用VisTrails创建可重复出版物的更多详细信息,请参阅http://www.vistrails.org
。
幸运的是,早在2004年,当我们开始考虑构建一个支持来源的数据探索和可视化系统时,我们从未预料到它会如此具有挑战性,或者需要多长时间才能达到我们现在的水平。如果我们预料到了,我们可能永远不会开始。
早期,一种行之有效的方法是快速原型化新功能并将其展示给一组选定的用户。我们从这些用户那里获得的初步反馈和鼓励对推动项目向前发展至关重要。如果没有用户的反馈,设计VisTrails是不可能的。如果项目中有一个方面我们想强调,那就是系统中的大多数功能都是作为对用户反馈的直接回应而设计的。但是,值得注意的是,许多时候用户要求的并不是满足其需求的最佳解决方案——对用户做出回应并不一定意味着完全按照他们的要求去做。一次又一次,我们不得不设计和重新设计功能,以确保它们有用并且在系统中得到正确集成。
鉴于我们以用户为中心的方法,人们可能会期望我们开发的每个功能都会被大量使用。不幸的是,情况并非如此。有时,其原因在于该功能非常“不寻常”,因为它在其他工具中找不到。例如,类比甚至版本树都不是大多数用户熟悉的概念,他们需要一段时间才能适应它们。另一个重要的问题是文档,或者说缺乏文档。与许多其他开源项目一样,我们在开发新功能方面做得比记录现有功能要好得多。这种文档滞后不仅导致有用功能的利用不足,而且还导致我们的邮件列表中出现许多问题。
使用像VisTrails这样的系统面临的挑战之一是它非常通用。尽管我们尽最大努力提高可用性,但VisTrails是一个复杂的工具,对于某些用户来说需要较高的学习曲线。我们相信,随着时间的推移,随着文档的改进、系统进一步完善以及更多特定于应用程序和领域的示例,任何给定领域的采用门槛都会降低。此外,随着来源概念的日益普及,用户将更容易理解我们在开发VisTrails时采用的理念。
我们要感谢所有为VisTrails做出贡献的天才开发人员:Erik Anderson、Louis Bavoil、Clifton Brooks、Jason Callahan、Steve Callahan、Lorena Carlo、Lauro Lins、Tommy Ellkvist、Phillip Mates、Daniel Rees和Nathan Smith。特别感谢Antonio Baptista,他帮助我们制定了项目的愿景;以及Matthias Troyer,他的合作帮助我们改进了系统,特别是为开发和发布富来源出版功能提供了很大的动力。VisTrails系统的研究和开发得到了国家科学基金会IIS 1050422、IIS-0905385、IIS 0844572、ATM-0835821、IIS-0844546、IIS-0746500、CNS-0751152、IIS-0713637、OCE-0424602、IIS-0534628、CNS-0514485、IIS-0513692、CNS-0524096、CCF-0401498、OISE-0405402、CCF-0528201、CNS-0551724、能源部SciDAC(VACET和SDM中心)以及IBM教师奖的资助。
http://www.vistrails.org
http://www.hibernate.org
https://sqlobject.pythonlang.cn
http://www.makotemplates.org
http://www.vtk.org
http://www.vistrails.org/usersguide
http://www.crowdlabs.org