ADE 入门教程
此教程教你如何在一个Visual Basic程序中使用Analytica决策引擎(ADE)。
你的第一个ADE应用程序
首先先从头开始写一个简单的ADE程序,只要确认一切设置正常。按照以下步骤操作:
test
第一个程序执行了以下操作:
- 建立一个名称为ADE的CAEngine自动化对象(通过使用
new CAEngine
). - 打开Analytica模型 (通过使用CAEngine的OpenModel 方法。
- 显示模型名称返回OpenModel的值。
我们将在后面的章节详细了解这些信息。
接下来讲什么呢?
我们不会尝试在此入门教程中解释ADE的特征。本指南后面几个章节会描述这些特征。
从现在开始,我们我们将使用名称为Txc.ana的示例模型。打开 Analytica安装目录下 Example Models文件夹里面的Risk Analysis文件夹便可找到Txc.ana文件。如果你找不到,或者你安装Analytica的时候未选择安装examples(示例)文件,在ADE安装目录下面的Examples\Tutorial.NET文件家中有一个副本。
Txc模型演示了减少污染物TXC排放的risk-benefit (风险-效益)分析。请用Analytica打开Txc查看它是如何工作的。
ADE Examples\Tutorial.NET 文件夹中名称为TestTxc的Visual Basic.NET程序示例显示了ADE很多方面。该程序建立了一个ADE自动化对象,打开包含该对象的Txc.ana模型,获取变量 Population Exposed的定义,计算变量Total Cost,通过获取每个表格的单独组件以表格的方式打印输出变量Total Cost 的结果,然后改变变量Population Exposed 的定义。再次获取变量Total Cost 的值,看一下 变量Population Exposed定义的改变对变量Total Cost有什么影响。如果设置正确的话,TestTxc将显示在“Text Txc window”中显示的窗口。
应用程序显示变量Population Exposed 的定义("Normal (30M, 3M)"),以及与Total Cost关联的表格,这都基于Population Exposed的定义。你可以通过从主菜单选择File > Change Population Exposed改变Population Exposed的定义,然后查看改变对Total Cost表格的影响。
区分title(名称) 和 identifier(标识符)
无论什么时候,一个ADE函数都需要一个变量你必须传达变量的标识符 ,而不是它的名称。这可能会引起混淆,因为在Analytica影响图中一般显示每一个变量的名称。默认情况下,在你最初建立每一个对象的时候,Analytica自动根据名称建立一个标识符。用(_)代替名称中的每一个空格或者其他非字母和数字字符。
你可以通过点击“Control+y”(或者从Object(对象)菜单选择Show by identifier :显示标识符) 来以标识符显示变量。对于模型Txc.ana,你可以看到名称为Population Exposed变量的标识符为 Pop_exp。将变量传递给ADE函数时,使用标识符很重要。如果你传递Population Exposed,ADE无法找到变量,并且将返回一个错误。
从Visual Basic中建立一个ADE 对象
如果你还没准备好,将名称为tt>Examples\Tutorial\TestTxc.sln载入到Visual Basic.NET中,查看名称为TestTxc.vb的文件的代码。其代码看起来像:
- Imports ADEW
- . . .
- Public adeEngine As CAEngine
- Public Sub Main()
- End Sub
在每一个文件的顶部的代码将自动化对象 adeEngine 声明为一个CAEngine 对象。使用此对象,我们能够访问CAEngine 展示的所有公共函数(参考 ADE服务器类型参考 )
变量adeEngine包含我们的进程内对象CAEngine。如果我们想使用ADE的本地(进程外)服务器版本,我们可以将一个项目参考添加到Analytica Decision Engine Server 4.6 COM 组件并将顶部行从Imports ADEW改成Imports ADE即可。
这里是另一种获得一个先CAEngine对象的方法。此循序不需要给项目添加参考。
- adeEngine = CreateObject("ADEW4.6.CAEngine") ’ in-process
- adeEngine = CreateObject("ADE4.6.CAEngine") ’ out-of-process
如果想理解使用进程内和进程外(或本地)服务器的利与弊,以及哪一种自动化服务器用于不同的场景,参考 In-process vs. out-of-process
COM和 Automation 接口比较
在上面的例子当中,我们使用COM接口调用ADE。在一个COM 接口中,对象 在本例中为CAEngine 被声明为CAEngine ,编译器解决每一个成员函数,并且在编译时间内能探测几个明显的错误。另外,Visual Studio可以提供方法和参数类型列表当,在你编程的时候可以当做一个提示工具,在编写使用ADE程序是非常有用。COM调用比Automation调用要快一点,但是在ADE应用程序中速度差异通常不是很重要。对于ADE 4.6我们推荐使用COM结构,只要你的语言支持。
在VB Automation中,你可以将一个对象简单声明为Object(对象),而不是像CAEngine、CAObject等更加具体的类型。当使用Automation调用ADE方法时,在运行时间内确定方法。在编译时间内,编译器不知道你的 ADE 对象是否有一个名称为OpenModel的函数。在VB中,调用COM方法或Automation 方法的语法是相通的——唯一的不同在于对象类型是否明确被声明。
在VC++和C#中,调用COM的语法和调用Automatio的语法不同。在这些情况下,COM要方便的多,Automation能够获得相冗余。然而,同样的语言,包括VBScript和其他脚本语言,只支持Automation不支持COM。
监视Process(进程)
当使用进程外ADE服务器时,你的代码必须在终止时释放CAEngineCOM对象。当最终CAEngine使用被释放时,ADE.exe 进程自动终止。到目前为止所看到的代码,VB语言在达到程序末端时,自动处理释放对象。但是,当你调试你的代码是,你可以提前终止你的程序来修复bug,你的程序可能被Task Manager(任务管理器)终止,或者你自己的代码也可能奔溃,导致你的程序进程在它有机会释放对象之前终止。因为COM对象从来不会被释放,ADE.exe进程不知道它是否在使用中,你的ADE.exe 进程没有任何进展。
为了避免这种情况发生,一种好的习惯就是在调用CAEngine实例后 立即调用CAEngine::MonitorProcess。通过这种方式,ADE能够得知哪一个进程正在使用它,同时将设置一个程序去探测你的进程是否在ADE被完全释放前终止。如果你的程序进程没有终止,ADE进程会立即将它关闭,消除了ADE没有进展的应用。
要从一个.NET应用程序获得进程ID,使用System.Diagnostics.Process namespace中的GetCurrentProcess().Id 。在其他语言中,你可以使用Windows SDK 的GetProcessId( )函数。
MonitorProcess() 只能用来监视运行ADE进程的同一台计算机的进程,一次当通过DCOM运行ADE时不能使用。当使用进程内ADEW服务器时没有必要使用。
使用ADE打开模型
我们现在打开Txc.ana模型,显示我们的应用程序主窗口。使用以下调用:tt>
- theModelString = adeEngine.OpenModel(theModel)
- frmMain.DefInstance.Show
使用CAEngine的OpenModel函数打开模型。如果成功的话,变量theModelString将包含模型的名字。否则,将包含一个空的串。虽然由于为了简介的缘故,我们没有这样做,但是你应该检查从OpenModel函数返回的是不是一个空的串。如果是,说明在你打开的模型中存在一个错误。你可以使用CAEngine的ErrorCode和ErrorText属性找出是哪种错误 (adeEngine.ErrorCode 和adeEngine.ErrorText)。
从Analytica模型中检索对象
下一步就是从我们的模型中检索对象(变量、模块、函数等),以便我们能获取他们的属性(定义、名称、类型等等 )。我们的示例模型(Txc.ana)操作Pop_exp和Cost对象。尤其是,修改Pop_exp可以查看这是如何影响Cost 对象。
我们的TxcTest.vbproj (TxcText.sln)项目中的frmMain.frm文件里的 PrintAttributes 函数演示了如何检索对象。该函数首先被frmMain.frm中的Form_Load 函数调用,当应用程序启动后,可以显示Cost表格。当然无论什么时候,只要我们想输出Cost表格的当前值,都可以调用。该函数如下所示:
- Public Sub PrintAttributes(ByRef inputIdentifier As String, ByRef outputIdentifier As String)
- Dim inputObject, outputObject As CAObject
- Dim resultTable As CATable
- Dim definitionAttrInput As String
- inputObject = adeEngine.GetObjectByName(inputIdentifier)
- outputObject = adeEngine.GetObjectByName(outputIdentifier)
- definitionAttrInput = inputObject. GetAttribute("definition")
- resultTable = outputObject.ResultTable
- Call PrintResultTable(resultTable, inputIdentifier, definitionAttrInput, outputIdentifier)
- ReleaseComObject(resultTable)
- ReleaseComObject(inputObject)
- ReleaseComObject(outputObject)
- End Sub
PrintAttributes和作为inputIdentifier参数的Pop_exp变量标识符以及作为outputIdentifier参数的Cost变量一起使用。如下所示通过使用CAEngine的GetObjectByName函数提取相应对象:
- inputObject = adeEngine.GetObjectByName(inputIdentifier)
- outputObject = adeEngine.GetObjectByName(outputIdentifier)
如果函数GetObjectByName提取成功,将返回一个CAObject类型对象。如果使用CAObject的函数,参考SendCommand(command),可以获得一个CAObject的完整函数列表。如果函数GetObjectByName提取失败,返回值为Nothing。代码应该检查以确保GetObjectByName提取的结果是有效的。如果找不到,可使用CAEngine的 ErrorCode和 ErrorText属性获取错误信息。例如:
- Set inputObject = adeEngine.GetObjectByName(inputIdentifier)
- If inputObject Is Nothing Then
- Else
- 'inputObject valid
- End If
获取对象属性
每一个Analytica对象都有一组属性,例如标识符、名称、 描述和类型。你可以使用GetAttribute函数来获去Analytica对象的属性。例如,如要获取inputObject的定义(当前为cost):
- definitionAttrInput = inputObject.GetAttribute("定义")
在模型Txc.ana 中,Pop_exp的定义为"Normal(30M, 3M)" ,我们将它储存在definitionAttrInput当中。.
计算对象和检索结果
使用CAObject的Result或ResultTable方法来获取变量的值。如果有必要的话,ADE先自动计算变量。如果你确定结果为基元(只有一个元素),使用Result方法。否则使用ResultTable,检索结果为一个数组。基元结果当作一个特殊数组案例来处理,没有维度的数组。如果值为基元AtomicValue方法将返回一个数字或者字符串单一值。
默认情况下,Result和ResultTable返回结果的中值,也就是说,ADE以确定性方式计算结果。对于一个概率值,将CAObject的ResultType属性设置为想要的不确定性视图——Mean(平均值) Sample(样本)、PDF(概率密度函数)、CDF(累计分布函数)、Confidence bands(置信带)、或者Statistics(统计数据)详情请参考ResultType。我们通过如下的方式获取outputObject 输出对象 的值:
- resultTable = outputObject.ResultTable
结果为一个CATable对象,允许我们访问一个表格中的单个元素。
如果你通过调用Result来获取一个数组(或者表格)的值,将返回数组为一个字符串,列出索引和元素,以逗号隔开。通常使用ResultTable更加简单,这样你没有必要从字符串中解析表格的元素。
获取表格索引元素
一个Analytica表格包含0个或者多个索引。如果包含一个索引,那么该表格是1维表格;如果有2个索引,说他是2维表格,依此类推。0维度表格包含一个“单一基元”(或标量)值。你可以使用CATable的NumDims获取表格的维数(也就是索引数)。要想获取表格的单个索引,可使用CATable的 IndexNames和 GetIndexObject函数。
frmMain.frm中的PrintResultTable函数说明了这两个函数的使用。从PrintAttributes中调用PrintResultTable,执行打印TestTxc中的表格的实际工作(为了简洁起见,我们只给出了关联ADE的该函数部分)。
- Public Sub PrintResultTable(ByRef resultTable As CATable, ByRef inputIdentifier As String, ByRef definitionAttrInput As String, ByRef outputIdentifier As String)
- Dim theIndexName, theTableName As String
- Dim theIndexElement As String
- Dim theTableElement
- Dim theIndexObj As CAIndex
- Dim numEls As Integer
- Dim spaces, i As Integer
- Dim lenStr As Short
- Dim OutputStr As Short
- Dim spaceString, underlineString As String
- ...
- theIndexName = resultTable.IndexNames(1)
- theTableName = resultTable.Name
- theIndexObj = resultTable.GetIndexObject(theIndexName)
- numEls = theIndexObj.IndexElements
- ...
- For i = 1 To numEls
- theIndexElement = theIndexObj.GetValueByNumber(i)
- theTableElement = resultTable.GetDataByElements(i)
- ...
- Next i
- InformationPane.Text = outputString
- End Sub
通过PrintResultTable获取一个表格的索引方法如下:
- theIndexName = resultTable.IndexNames(1)
- theIndexObj = resultTable.GetIndexObject(theIndexName)
我们通过使用CATable的IndexNames函数获取第一个索引的名字。我们将它传递给CATable的GetIndexObject函数以获取表示我们的索引的CAIndex。该自动化对象返回其相应索引的相关信息。如果此函数调用失败,将返回Nothing信息。在此情况下,使用CAEngine的ErrorCode和ErrorText 函数找出原因。
从CATable和CAIndex中获取信息
PrintResultTable说明了如何从 CATable和CAIndex 对象获取信息。该代码获取Cost表格的索引和表格元素:
- numEls = theIndexObj.IndexElements
- For i = 1 To numEls
- theIndexElement = theIndexObj.GetValueByNumber(i)
- theTableElement = resultTable.GetDataByElements(i)
- ...
- Next i
CAIndex的IndexElements属性返回第一个索引中的元素个数。CAIndex的GetValueByNumber函数获取单个索引元素。
我们通过传递表格中元素的坐标,使用 CATable的GetDataByElements函数获取Cost表格对象的单个表格元素resultTable。
当我检索 CATable对象中单个元素(resultTable)的时候,我们利用了该表格是一维的事实。因此,我们只需要传递给GetDataByElements一个单一数(这个数代表表格中的位置)。但是,如果我们处理两个或者更多维度,我们必须传递给GetDataByElements一个数组函数,并且指明我们要检索的表格元素的位置。因此,如果我们不想检索一个二维数组的position (4,3)位置的元素,我们可以这样写:
- Dim W as Variant 'return element
- Dim IndexPtrs(1 To 2) As Variant 'position in table
- ...
- IndexPtrs(1) = 4
- IndexPtrs(2) = 3
- W = resultTable.GetDataByElements(IndexPtrs)
控制基元值格式
CATable中的每个基元值可以是一个数、一个字符串,或者其它一些基本类型(例如:空值、未定义、参考,或 句柄。通过说明类型和值,它们作为变体被返回,这是一种Visual Basic能读懂的数据结构。CATable的RenderingStyle属性控制Analytica基础值是如何映射到Visual Basic变体的。
例如:可以通过使用Analytica 模型的数字格式设置将一个数值返回成一个数字或者字符串。如果被指定格式,有一个选线控制是截断数字的位数,还是精确返回。
子程序PrintResultTable在frmMain.vb中载入,其渲染风格直接指明:
- resultTable.RenderingStyle.NumberAsText = True
- resultTable.RenderingStyle.FullPrecision = False
- resultTable.RenderingStyle.StringQuotes = 2
第一行指明数值应该根据与结果对象有关的数字格式被格式化为文本。例如在其程序输出中,我们将看到30.103M,而非30102995.6639812,如果我们让Visual Basic将数值串联我们的结果字符串,将显示该结果。在此事件中,一个值为字符串的单元格出现在结果中,返回值将明确带双引号。对于CARenderingStyle对象中的所有可用属性,请参考CARenderingStyle。
其它访问表格的方式
有几种访问多维表格CATable中元素的方式。每一种都有可能在特定的场合使用起来比较方便。
第一种方式就是使用 CATable的 GetDataByElements或GetDataByLabels方法,上面代码示例中有显示。在此情况下,利用你想获取的基元值的单元格位置。
第二种方式就是使用CATableSlice或 Subscript方法获取一个少一个维度的新CATable对象。通过重复减少维度,最终将获得0维度,这时你将获得一个单一基元值。此时,CATable的AtomicValue方法返回该值 。AtomicValue方法是唯一获取一个标值的方法(因为没有坐标位置)。如果你需要建立完整结果其中一个切片的图形图像,必须使用此方法。
第三种方式就是使用CATable的GetSafeArray方法,将多维数组转化成一个安全数组(或者一个.NET数组)。你可以在使用VB或者其它.NET语言直接处理多维数组。因为对于Analytica维度没有固定顺序,但是安全数组和.NET数组有一个明确的顺序,你必须先使用CATable对象的SetIndexOrder方法在调用GetSafeArray之前指明维度的顺序。注意如果你知道你的数组是一维的,那就没必要了。
修改对象
一个自定义用户程序通常从一个用户或者其它外部源获取输入,以便转化成Analytica模型中的输入变量。你可以设定输入变量的定义或者通过使用定义表格来这样做。
TestTxc 说明了如何修改Pop_exp定义,此模型是一个影响结果变量Cost的模型输入。要想在该示例中设定该定义,从主菜单上选择File > Change Population。将出现一个对话框,如下图所示:
在信息栏中输入一个新定义并点击Ok确认。主窗口将显示一个新的Cost值。当点击该对话框中的Ok按钮时,调用了ChangeDef.frm函数,然后调用PrintAttributes函数打印Cost的结果。
此函数类似于:
- Private Sub OkButton_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles OkButton.Click
- Dim errorText As String
- Dim pop_exp_Object As CAObject
- Dim errorCode As Short
- Dim errorString As String
- newDefinition = PopExposedDef.Text
- pop_exp_Object = adeEngine.GetObjectByName("pop_exp")
- pop_exp_Object.SetAttribute("Definition", newDefinition)
- errorCode = adeEngine.ErrorCode
- If errorCode <> 0 Then
- MsgBox("This error occurred while processing your definition: " & vbCrLf & vbCrLf & adeEngine.ErrorText) :::PopExposedDef.Focus()
- Else
- Me.Close()
- frmMain.DefInstance.PrintAttributes("Pop_exp", "Cost")
- End If
- ReleaseComObject(pop_exp_Object)
- End Sub
该函数抓取了输入到New Definition 新定义给Population Exposed栏,并通过使用CAObject对象中的SetAttribute函数将它设置给Pop_exp对象。然后调用PrintAttributes,以计算Cost object,并打印新的结果。要想给变量pop_exp设定一个新定义,我们可以为Pop_exp获取CAObject对象,然后将其定义设置成用户输入的定义。通过使用下面的代码来实现:
- pop_exp_Object = adeEngine.GetObjectByName("pop_exp")
- pop_exp_Object.SetAttribute( "Definition", newDefinition )
无论什么时候调用SetAttribute,都应该检查CAEngine自动化对象(adeEngine)的ErrorCode,以免它的定义是非法的。尝试一下输入一个新定义,比如Uniform(25M,35M)
,然后点击Ok。当pop_exp的定义被改变时, Cost的结果由ADE重新计算,此时ResultTable接下来调用给Cost变量(在应用程序窗口重新显示时)。
使用ADE绘图
通过Analytica 4.6使用相同的绘图引擎,你可以建立一个图表或者图形以显示一个数组值或者不确定值结果。在ADE4.6中,你可以使用 CATable的GraphToFile和GraphToStream方法。图形可以以几种可能的图像格式返回,例如image/png、 image/bmp或者 image/jpeg。
从可用图形选项中选择的最简单方式就是使用Analytica打开你的模型。你可以实验设置各种默认设置或者替代选择的变量,看它们是如何工作的。但你选择了你想设置的选项,保存模型。当为每一个生成结果图形时,ADE将使用这些设置。
对于更高维度的结果,可能需要做一些必要工作,以便选择将绘制结果的切片和具体的轴(也就是相对于图例哪个维度作为X轴)。CATable的Subscript或Slice方法可以用来控制轴选择特殊的切片用来绘制,能够用来控制轴。详情请参考CATable。在我们的Tutorial.NET示例中,我们有一个一维结果(Cost),因此我们没有必要担心截取或者绕轴旋转。
GraphToStream方法被用来从ADE中直接转化图形图形到一个用户界面中。使用GraphToStream比起使用GraphToFile来要稍微复杂一点,因为GraphToFile除了需要文件名以外还需要其它一些信息,以便编写图形。要使用GraphToStream,我们必须在内存内设置一个流,允许ADE写入该留,然后从该留重建。因为.NET流和COM流不兼容,你必须使用ADE一同提供的StreamConnector类型.frmMain 中的GraphResult_Click子程序演示了GraphToStream的使用。从主应用程序窗口选择 Graph Result菜单选项,结果将出现在一个如下所示的图形当中:
结论
在此教程中,我们介绍了Analytica决策引擎的几个重要方面。我们看到了如何建立一个ADE服务器对象,使用ADE打开模型,获取模型中的单个对象、计算对象、获取表格中的元素,以及修改模型中的对象。但是ADE可以做的远不止这些。
我们希望你已经足够了解其基本特征,这样你现在才能独自开始探索更多新的高级特征。我们建议你现在开始阅读字教程的剩余部分以了解ADE能做什么。
另请参考
ADE 安装 <- | ADE 入门教程 | -> 使用ADE服务器 |
Enable comment auto-refresher