CAD 应用程序中的机器推理





5.00/5 (3投票s)
本文介绍了在CAD(计算机辅助设计)应用程序中使用推理引擎来实现自动检测设计错误和生成设计建议的机制。本文讨论了许多可以通过使用推理机制来避免的实现问题。本文
引言
如今,只有通过使用更先进的软件,例如CAD(计算机辅助设计)应用程序,才能提高经验丰富的设计师的生产力。从历史上看,20世纪60年代创建的第一批此类程序(例如Sketchpad)主要是为了绘图。然而,这些应用程序与纸张相比,最基本也是最重要的优势是能够轻松地编辑单个元素,而无需重绘整个项目。
随着计算机效率的提高,CAD应用程序中增加了新的功能,例如自动化某些设计步骤。供应商提供了许多现成的组件,并将越来越多的任务推给了机器——复杂的计算、分析等。同时,CAD应用程序在自动验证设计正确性方面也变得越来越好。如今,它们还强制用户使用良好的设计实践。
在当代的CAD应用程序中,用户界面设计和自动设计错误验证的进步是决定其商业成功的顶尖因素之一。问题在于,实现自动验证机制并非易事,如果不小心处理,可能会引入许多错误,并将性能拖至不可接受的水平。
WAN计算器
我们的公司ITI EMAG (www.emag.pl) 除了提供多种服务外,还提供广域网设计。为了降低成本,我们决定构建WAN计算器应用程序来自动化该任务的部分工作。
图1显示了应用程序的主窗口。该应用程序允许将广域网设计为图结构,其中顶点代表位置(IT基础设施所在的位置),边代表位置之间的连接。
分析摘要
在需求分析过程中,我们发现,对广域网设计人员工作最有帮助的一个重要因素是自动和交互式错误检测机制。
在实现阶段,我们决定,该机制生成的错误消息应显示在应用程序主窗口底部的专用区域(如图1所示),因为这种解决方案在许多集成设计环境中都很常见。
WAN计算器的内部架构
图2显示了我们应用程序的简化架构图。其中有三个主要组件可见
- 域模型,其中包含描述广域网的所有类;
- 错误检测模块,其中包含推理引擎,并且可以访问域模型和用户界面模块;
- 用户界面。
建模应用程序域最常见的方法是面向对象方法。这种方法使程序员将现实世界对象映射到编程语言对象。例如,在我们的应用程序中,我们将建筑物等物理位置建模为Location类对象,将位置之间的连接建模为Connection类对象。
对于此类CAD应用程序,一个非常重要的因素是错误检测机制的高效率,这使得它可以在后台运行而不会引起应用程序明显减速。由于我们在人工智能方面的经验,我们决定使用推理引擎作为错误检测机制的核心。选择Java作为实现语言使得JBoss Rules成为最合适的实现,因为它可以直接操作任意Java对象。
实现问题
在CAD应用程序中实现错误检测机制会带来一些需要解决的问题,才能带来积极的用户体验。其中最重要的问题是
- 错误检测速度;
- 模块之间的松耦合;
- 错误检测机制与域对象模型的分离;
- 源代码的可读性。
在分析阶段,我们发现在此类应用程序中,用户出错的概率非常高,但我们无法穷尽所有可能的错误空间。这使得我们只能定义用户可能犯的最常见设计错误的一个初始集,并准备好应用程序以便于扩展。
域对象图的复杂性和丰富性可能导致设计错误检测机制时出现一些非同寻常的障碍,例如
- 高效遍历对象图;
- 算法陷入无限循环;
- 域类接口的变形;
- 编程语言的泛滥。
高效遍历对象图
遍历域对象图的策略似乎是最重要的问题。其解决方案对错误检测速度有最大的影响。启动对象的选择以及错误检测算法沿图前进的路径对代码的效率和复杂性都有显著影响。图3展示了两种极端路径,其中路径a)是最优的,而b)是最差的,因为它涉及多次检查同一对象。
选择能够直接操作任意Java对象的JBoss Rules推理引擎,使得图遍历问题成为一个实现细节。所有域对象的引用都传递给推理引擎,其高度优化的算法使其能够交互式工作,而没有明显的计算开销。为JBoss Rules推理引擎提供支持的前向链接算法可能会导致相当大的内存开销。这种开销可能会严重影响应用程序的性能,但使用包含约200个互连本地化的项目进行的性能测试显示,即使在普通的台式机硬件上也没有发现明显的性能损失。
算法陷入无限循环
图遍历的第二个问题是错误检测算法可能陷入无限循环。这尤其适用于导航算法的“朴素实现”,这些实现存在松散的循环检测。图4展示了在处理具有循环的对象图时可能发生的情况。
像JBoss Rules这样经过充分测试、可投入生产使用的推理引擎,让我们完全可以忽略这个问题,因为它在内部处理了循环检测。
域类接口的变形
在域类中实现错误检测机制会通过引入错误检测方法来破坏它们的接口。这会引入所谓的“泄露的抽象”。另一个不希望的副作用是域类的紧耦合。将错误检测代码和域类代码混合通常是个坏主意,因为它们是两个应该分开处理的独立方面。
使用推理引擎使我们能够将错误检测代码与域类分开。这两个方面的物理分离对程序架构和代码可读性产生了积极影响。在域模型需要修改的情况下,这种解决方案也更具灵活性。添加新的错误定义也很简单。
然而,错误检测代码和域类代码的完全分离有时是不切实际的,甚至是不可行的。有时,在域类中实现一些命令式计算,并在逻辑公式中使用计算结果,比将这些计算强行纳入逻辑公式本身更简单、更优雅。在任何这种情况下,程序员都应该权衡所有利弊,并选择更明智、更易读的解决方案。
应用程序的源代码。
手动实现错误检测机制会导致代码复杂且覆盖错误定义的性低。将一个错误定义转换为
"negative value of price is incorrect"
一个简单的if-then语句,例如
if (price < 0) {
throw new Exception(…);
}.
很简单。然而,将一个定义转换为
"a network containing two localizations that are not directly or indirectly connected I errant"
可读且易于理解的代码,并且能够清楚地表明它是如何工作的以及是否有效,则更加困难。
使用推理引擎和逻辑编程解决了这个问题,因为所有错误定义,无论是简单的还是复杂的,都呈现为
If error condition Then present error message
例如,检测未连接网络点(位置)的公式如下
rule "Unconnected network point" when $np : NetworkPoint()| not (exists Connection (point1 == $np || point2 == $np)) then insertLogical (Hint.newInfo($np, "No connection with other network points.")); end,
并且可以轻松地将其翻译成自然语言为
if there exists a network point and there are no connections between it and other network points then show message „Unconnected network point”.
错误定义的声明性表达式更容易检测实现错误,这些错误否则会隐藏在许多Java过程中。
示例中使用的insertLogical
关键字会导致包含错误消息的对象被有条件地插入推理引擎的工作内存中。只要条件成立,该对象就会保留在该内存中并显示给用户。
此解决方案最大的缺点是必须使用两种编程语言和两种编程范例。域类用命令式Java表示,而错误条件用声明式逻辑表示。这种二元性迫使程序员掌握两种语言和范例。
摘要
所提出的解决方案在我们的应用程序中效果很好,并使我们能够实现一个高效且灵活的错误检测机制,该机制可用于任何CAD应用程序。相同的错误检测机制也可用于自动纠错,尽管其潜在的用户交互问题尚未得到研究。