The ADE Tutorial
Release: |
4.6 • 5.0 • 5.1 • 5.2 • 5.3 • 5.4 • 6.0 • 6.1 • 6.2 • 6.3 • 6.4 • 6.5 |
---|
This tutorial shows you how to use the Analytica Decision Engine (ADE) from within a Visual Basic program.
Your first ADE application
First let’s write a simple ADE application from scratch, just to be sure that everything is set up correctly. Follow these steps:
- 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 6.4 (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 6.4, 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 6.4), and select ADE.exe.
- *Note: If you cannot find this entry in the list of COM servers, then ADE 6.4 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 6.4"
- 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.
This first program did the following:
- Created a CAEngine automation object called ADE (using
new CAEngine
). - Opened an Analytica model (using the OpenModel method of CAEngine).
- Displayed the name of the model (the return value of OpenModel).
We go into the details of these and other functions in the next section.
What's next?
We will not attempt to explain all of the features of ADE in this tutorial. These are described in the following chapters of this guide. Here, we give you the background to explore the more advanced features of ADE on your own.
From this point, we use the example model called Txc.ana. You can find Txc.ana in the Risk Analysis folder under the Example Models folder installed with Analytica. If you cannot find it, or if you opted not to install the examples when you originally installed Analytica, there is a copy in the Examples\Tutorial.NET folder in the directory where you installed ADE.
The Txc model demonstrates risk-benefit analysis of reducing the emissions of the fictitious air pollutant TXC. Please open the Txc model with Analytica to see how it works.
The example Visual Basic.NET program called TestTxc in your ADE Examples\Tutorial.NET folder shows many aspects of ADE. This program creates an ADE automation object, opens the Txc.ana model with this object, gets the definition of the Population Exposed variable, evaluates the Total Cost variable, prints out the result of the Total Cost variable as a table by getting at the individual components of the table, and changes the definition of the Population Exposed variable. It then gets the result of the Total Cost variable again, to see what effect the change of definition for Population Exposed had on the Total Cost variable. If things are set up properly, TestTxc displays the window shown in “Text Txc window”.
The application displays the definition of the Population Exposed variable ("Normal (30M, 3M)"), and the table associated with Total Cost, based on the definition of Population Exposed. You can change the definition of Population Exposed by selecting File > Change Population Exposed from the main menu and seeing the effect this has on the Total Cost table.
Distinguishing title from identifier
Whenever an ADE function requires a variable, you must pass it the Identifier of the variable, not its Title. This can be confusing since Analytica normally displays the titles of each variable in an influence diagram. By default, when you first create each object, Analytica automatically creates an identifier based on the title. It substitutes an underscore (_) for each blank or other character in the title that is not a letter or number.
You can show the identifiers in an influence diagram by pressing Control+y (or by selecting Show by identifier from the Object menu). For model Txc.ana, you can see that the identifier of the variable titled Population Exposed is Pop_exp. It is important to use Pop_exp as the identifier when passing this variable to ADE functions. ADE would not be able to find the variable if you pass Population Exposed, and would return an error.
Creating an ADE object from within Visual Basic
If you haven’t already, load the project called Examples\Tutorial\TestTxc.sln into Visual Basic.NET, and view the code for the file called TestTxc.vb. The code looks like this:
- Imports ADEW
- . . .
- Public adeEngine As CAEngine
- Public Sub Main()
- End Sub
At the very top of the file, the code declares the automation object adeEngine as a CAEngine object. Using this object, we can access all of the public functions exposed by CAEngine (see ADE Server Class Reference for a complete listing). This line then creates the CAEngine object.
- adeEngine = New CAEngine
The adeEngine variable now holds our in-process CAEngine object. If we want to use the local (out-of-process) server version of ADE, we can add a reference to the project to the Analytica Decision Engine Server 6.4 COM component and change the top line from Imports ADEW to Imports ADE.
Here is another way to obtain a new CAEngine object. This sequence does not require adding a reference to the project.
- adeEngine = CreateObject("ADEW6.4.CAEngine") ’ in-process
- adeEngine = CreateObject("ADE6.4.CAEngine") ’ out-of-process
To understand the pros and cons of using an in-process server versus as out-of-process (or local) server, and which automation server to use for different scenarios, see In-process vs. out-of-process, as well as other books related to COM servers.
COM vs. Automation interface
In the example above, we used a COM interface to call ADE. In a COM interface, the object (CAEngine in this case) is declared as CAEngine, and the compiler resolves each member function and can detect several obvious errors at compile time. In addition, Visual Studio can provide a list of methods and parameter types as tool tips as you program, which is helpful when writing programs that use ADE. COM calls are slightly faster than Automation calls, but the speed difference is not usually significant in applications of ADE. With ADE 6.4, we recommend using the COM interface if your programming language supports it.
In VB Automation, you can declare an object simply as Object, rather than a more specific types such as CAEngine, CAObject, and so on. When ADE methods are called using Automation, the methods are resolved at run time. At compile time, the compiler does not know whether your ADE object has a function named OpenModel. In VB, the syntax for calling a COM method or an Automation method is identical — the only difference is whether the object’s type is declared explicitly.
In VC++ and C#, the syntax for calling COM is not the same as for Automation. In these cases, COM is much more convenient, while Automation can get rather tedious. However, some languages, including VBScript and other scripting languages, support only Automation and not 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