ADE 入门教程
此教程教你如何在一个Visual Basic程序中使用Analytica决策引擎(ADE)。
你的第一个ADE应用程序
首先先从头开始写一个简单的ADE程序,只要确认一切设置正常。按照以下步骤操作:
- Bring up Visual Studio.NET.1
- Select New Project, then select the Project Type “Visual Basic Projects” and the template “Console Application.” Select a project name, e.g., “FirstADEtry,” and an appropriate folder location.
- From the Project menu, select Add Reference and select the COM tab in the dialog. Find and select Analytica Decision Engine Local Server 4.6 (Ade.exe) and click OK.
- Note: If you have installed ADE 64-bit and do not have ADE 32-bit installed, Visual Studio may not show Analytica Decision Engine Local Server 4.6, even though it is properly installed. This occurs because Visual Studio itself is a 32-bit application. If this happens, select the Browse tab on the Add Reference dialog, navigate to the ADE install directory (usually C:\Program Files\Lumina\ADE 4.6), and select ADE64.exe.
- *Note: If you cannot find this entry in the list of COM servers, then ADE 4.6 is not properly installed. See Installation of ADE for instruction on how to install ADE before reading further.
- Add to the Module1 class as follows:
- Imports ADE
- Module Module1
- Public ADE As CAEngine
- Sub Main()
- Dim FileName, ModelName As String
- FileName = "C:\Program Files\Lumina\Analytica 4.6"
- FileName &= "\Example Models\Tutorial Models\Car Cost.ana"
- ADE = New CAEngine
- ADE.MonitorProcess( System.Diagnostics.Process.GetCurrentProcess().Id )
- ModelName = ADE.OpenModel(FileName)
- If ModelName = "" Then
- Console.Write(FileName & " not found")
- Else
- Console.Write("Congratulations on opening ")
- Console.WriteLine(ModelName)
- Console.WriteLine("Press 'enter' to exit")
- Console.ReadLine()
- End If
- End Sub
- End Module
- Now just run the program. If your program prints “Congratulations on opening Carcosts” you have just successfully written your first ADE program.
第一个程序执行了以下操作:
- 建立一个名称为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。
Monitoring the Process
When using the out-of-process ADE server, your own code must release the CAEngine COM object when it terminates. When this final CAEngine usage is released, the ADE.exe process automatically terminates. In the code seen so far, the VB language takes care releasing the object automatically when it reaches the end of the program. However, while you are debugging your own code, you may terminate your program prematurely to fix a bug, your program may be killed from Task Manager, or your own code may crash, causing your program’s process to terminate before it had a chance to release the object. Because the COM object is never released, the ADE.exe process cannot know that it is no longer in use, and you may get zombie ADE.exe processes lingering.
To avoid this, it is a good practice to call CAEngine::MonitorProcess immediately after obtaining a CAEngine instance. You pass the method the process id for your program’s own process. In this fashion, ADE can learn which process is using it, and will set up a thread to detect if your process terminates before ADE is fully released. If your program’s process does terminate, the ADE process immediately shuts itself down, eliminating the build-up of zombie ADE processes.
To obtain your process ID from a .NET application, use the GetCurrentProcess().Id found in theSystem.Diagnostics.Process namespace. In other languages, you can use the Windows SDK function GetProcessId( ).
MonitorProcess() can only be used to monitor processes running on the same computer as ADE’s process, so you can’t use this if running ADE through DCOM. There is no need to use this when using the in-process ADEW server.
Opening a model with ADE
We will now open the Txc.ana model, and show the main window of our application. Use the following call:
- theModelString = adeEngine.OpenModel(theModel)
- frmMain.DefInstance.Show
The OpenModel function of CAEngine opens the model. If successful, the variable theModelString contains the name of the model. Otherwise, it contains an empty string. Although we haven’t done so in this example for the sake of brevity, you should check to see that the string returned from OpenModel isn’t empty. If it is, there was an error in opening your model. You can find out what kind of error with the ErrorCode and ErrorText properties of CAEngine (adeEngine.ErrorCode and adeEngine.ErrorText). We will see how to use these two properties later on. For a listing of all the error codes, see ADE Error Codes.
Retrieving objects from the Analytica model
The next step is to retrieve objects (variables, modules, functions, etc.) from our model, so that we can access their attributes (definition, title, class, etc.). Our example model (Txc.ana) manipulates the Pop_exp and Cost objects. In particular, it modifies Pop_exp to see how this effects the Cost object.
The PrintAttributes function in the file frmMain.frm of our TxcTest.vbproj (TxcText.sln) project shows how to do this. This function is first called by the Form_Load function of frmMain.frm, when the application starts, to display the Cost table. It is also called whenever we wish to print out the current result of our Cost table. The function looks like this:
- 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
PrintAttributesworks with the variable identifiers Pop_exp passed as parameter inputIdentifier and Cost passed as parameter outputIdentifier. It fetches the corresponding objects using the GetObjectByName function of CAEngine as follows:
- inputObject = adeEngine.GetObjectByName(inputIdentifier)
- outputObject = adeEngine.GetObjectByName(outputIdentifier)
If GetObjectByName succeeds, it returns an object of type CAObject. You then use the functions of CAObject. See SendCommand(command) for a listing all CAObject functions. If GetObjectByName fails, the return value is Nothing. The code should check to make sure that the result from GetObjectByName is valid. If not, use the ErrorCode and ErrorText properties of CAEngine to get more information about the error. For example:
- Set inputObject = adeEngine.GetObjectByName(inputIdentifier)
- If inputObject Is Nothing Then
- Else
- 'inputObject valid
- End If
Getting object attributes
Each Analytica object has a set of attributes (analogous to properties), such as identifier, title, description, and class. You can use the GetAttribute function to obtain an attribute from an Analytica object. For example, to get the definition of inputObject (currently, the cost):
- definitionAttrInput = inputObject.GetAttribute("Definition")
In the Txc.ana model, the definition of Pop_exp is "Normal(30M, 3M)" which we store in definitionAttrInput.
Evaluating objects and retrieving results
Use Result or ResultTable methods of CAObject to get the value of a variable. ADE automatically evaluates the variable first, if necessary. Use the Result method if you are sure the result will be atomic, i.e., a single element. Otherwise, use ResultTable, which retrieves the result as an array. An atomic result is treated as a special case of an array, one with zero dimensions. If the value is atomic, the method AtomicValue returns its single value as a number or string.
By default, Result and ResultTable return the mid value of the result, i.e., the result of ADE evaluating it as deterministic. For a probabilistic value, set the ResultType property of CAObject to the desired uncertainty view — Mean, Sample, PDF, CDF, Confidence bands, or Statistics (see ResultType for details). We get the value of outputObject like this:
- resultTable = outputObject.ResultTable
The result is a CATable object, which lets us access individual elements in a table.
If you call Result to get an array (or table) value, it returns the array as a string, listing the indexes and elements separated by commas. It is usually easier to use ResultTable, so that you don’t have to parse elements of the table from the string.
Getting the index elements of a table
An Analytica table has zero or more indexes. If it has one index, then it is one-dimensional; if it has two indexes, it is two-dimensional, and so on. A zero-dimensional table holds a single atomic (or scalar) value. You can use the NumDims function of CATable to get the number of dimensions (same as number of indexes) of a table. To get at the individual indexes of a table, use methods IndexNames and GetIndexObject of CATable.
The function PrintResultTable in frmMain.frm shows the use of these two functions. PrintResultTable is called from PrintAttributes, and does the actual work of printing the table that shows up in our TestTxc application (for brevity, we show only the parts of this function related to 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
The lines of PrintResultTable that get an index of a table are as follows:
- theIndexName = resultTable.IndexNames(1)
- theIndexObj = resultTable.GetIndexObject(theIndexName)
We get the name of first index using the IndexNames function of CATable. We pass it into the GetIndexObject function of CATable to get a CAIndex object that represents our index. This automation object returns information about its corresponding index. If this function fails, it returns Nothing. In that case, use ErrorCode and ErrorText functions of CAEngine to find out why.
Getting information from CATable and CAIndex
PrintResultTable also shows how to get information from CATable and CAIndex objects. This code gets the index and table elements of the Cost table:
- numEls = theIndexObj.IndexElements
- For i = 1 To numEls
- theIndexElement = theIndexObj.GetValueByNumber(i)
- theTableElement = resultTable.GetDataByElements(i)
- ...
- Next i
The IndexElements property of CAIndex returns the number of elements in the (first) index. The GetValueByNumber function of CAIndex gets individual index elements.
To get the individual table elements of the Cost table object, resultTable, we use the GetDataByElements function of CATable, passing in the coordinates of the element in the table.
When we retrieve an individual element of our CATable object (resultTable), we take advantage of the fact that the table is one-dimensional. Therefore, we only need to pass GetDataByElements a single number representing the position in our table. If we were dealing with two or more dimensions, however, we would need to pass GetDataByElements an array specifying the coordinates of the element of our table to retrieve. So, if we want to retrieve the element at position (4,3) of a two-dimensional table, we would write:
- 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)
Controlling formats of atomic values
Each atomic value in a CATable can be a number, string, or one of a few other basic types (e.g,. Null, Undefined, Reference, or Handle). These are returned as variants, a data structure understood by Visual Basic, specifying the type and value. The RenderingStyle property of CATable controls how the underlying Analytica value is mapped to the Visual Basic variant.
For example, it can return a numeric value as a number, or a string using the Analytica model’s number format setting. If it is formatted, an option controls whether to truncate the number of digits or to return it with full precision.
In the PrintResultTable subroutine, located in frmMain.vb, the rendering style is explicitly specified:
- resultTable.RenderingStyle.NumberAsText = True
- resultTable.RenderingStyle.FullPrecision = False
- resultTable.RenderingStyle.StringQuotes = 2
The first line specifies that numeric values should be formatted as text according to the number format associated with the result object. For example, in the program output, we see 30.103M instead of 30102995.6639812, which would likely be displayed if we had let Visual Basic concatenate the numeric value to our result string. In the event that a string-valued cell occurs in the result, it returns with explicit double quotes around the value. See CARenderingStyle for additional properties available through the CARenderingStyle object.
Other ways to access tables
There are several ways to access the elements of a multi-dimensional CATable. Some might be more convenient in certain scenarios than others.
The first way is to use the GetDataByElements or GetDataByLabels methods of CATable, shown in the code example above. In this case, you supply the coordinates of the cell whose atomic value you wish to retrieve.
A second way is to use the Slice or Subscript methods of CATable to obtain a new CATable object having one less dimension. By repeatedly reducing the dimensionality, you eventually reach zero dimensions, in which case you have a single atomic value. At that point, the AtomicValue method of CATable returns this value. The AtomicValue method is the only way to access a scalar value (since it doesn’t have a coordinate). You must use this method if you need to generate a graph image of a slice of the full result.
A third way is to use the GetSafeArray method of CATable, to convert the multi-dimensional array into a safe array (or into a .NET array). You can then manipulate the multi-dimensional array directly in VB or other .NET language. Since there is no inherent ordering to Analytica dimensions, but safe arrays and .NET arrays have an explicit ordering, you must first use the SetIndexOrder of CATable to specify the ordering of dimensions before calling GetSafeArray. Note that this is not necessary if you know that your array is one-dimensional.
Modifying objects
A custom application often gets input from a user or other external source to transfer into input variables in the Analytica model. You can do this either by setting the definition of an input variable, or by using a definition table.
TestTxc shows how to modify the definition of Pop_exp, which is a model input that effects the Cost result variable. To set the definition in the example, select File > Change Population Exposed from the main menu. A dialog appears, as shown in the figure below:
Enter a new definition into the field and click Ok. The main window displays the new value of Cost. The OkButton_Click function in ChangeDef.frm is called when the Ok button is clicked in the dialog. It modifies the definition of Pop_exp, and then calls the PrintAttributes function that prints the result of Cost.
The function looks like this:
- 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
This function grabs the new definition typed into the New Definition for Population Exposed field and sets it to the Pop_exp object by using the SetAttribute function of CAObject object. It then calls PrintAttributes, which evaluates the Cost object, and prints the new table. To set a new definition for the pop_exp variable, we get the CAObject for Pop_exp, and set its definition to the definition typed in by the user. This is done with the following code:
- pop_exp_Object = adeEngine.GetObjectByName("pop_exp")
- pop_exp_Object.SetAttribute( "Definition", newDefinition )
Whenever you call SetAttribute, you should check the ErrorCode of the CAEngine automation object (adeEngine), in case the definition is illegal. Try entering a new definition such as Uniform(25M,35M)
and click Ok. When the definition of pop_exp is changed, the result for Cost gets recomputed by ADE when ResultTable is next called for the Cost variable (when the application window is repainted).
Graphing with ADE
Using the same graphing engine used by Analytica 4.6, you can generate a chart or graph to display an array-valued or uncertain result. In ADE 4.6, you can use the GraphToFile and GraphToStream methods of CATable. The graphs are returned in several possible image formats, such as image/png, image/bmp or image/jpeg.
The easiest way to select from the available graphing options is to open your model with Analytica. You can experiment with the settings for the various defaults or override selected variables to see how they look. When you’ve chosen the settings you want, save the model. ADE then uses these settings when producing result graphs for each variable.
For higher-dimensional results, some work might be necessary to select the slice of the result that will be plotted and the specific pivot (i.e., which dimensions appear on the X-axis versus in the key). The Subscript or Slice methods of CATable can be used to select the particular slice to be plotted and SetIndexOrder can be used to control the pivot. See CATable for details. In our Tutorial.NET example, we have a one-dimensional result (Cost), and do not need to worry about slicing or pivoting.
The GraphToStream method is used to transfer the graph image directly from ADE to a userinterface method. GraphToStream is a bit more complicated to use than GraphToFile, since GraphToFile requires little more than a file name to write the image to. To use GraphToStream, we must set up a stream in memory, allow ADE to write to that stream, and then reconstitute the image from that stream. Because .NET streams are not compatible with COM streams, you need to use the StreamConnector class provided with ADE. The GraphResult_Click routine in frmMain shows the use of GraphToStream. Select the Graph Result menu option from the main application window, and the results appear in a graph as shown in the graph below.
Conclusion
In this tutorial, we introduced several important aspects of the Analytica Decision Engine. We saw how to create the ADE server object, open a model with ADE, get at an individual object in a model, evaluate objects, access elements in a table, and modify objects in a model. But, ADE can do a lot more!
We hope that you have learned enough about the basics so that you can now explore the more advanced features on your own. We recommend that you now read the rest of this guide to learn about what ADE can do.
See Also
Installation of ADE <- | The ADE Tutorial | -> Using the ADE Server |
Enable comment auto-refresher