VBA code generation on the spreadsheet

A previous post looked at using VBA to generate more VBA code.  This post looks at automatic code generation on the spreadsheet, which may be transferred into the a VBA project using the techniques discussed in the earlier post, or simply copied and pasted.

I will use as an example generation of a number of functions for the Strand7 (finite element analysis program) API, but don’t be put off if this is not your area of interest.  The methods discussed are useful for any case where a number of similar routines are required, with differences that can be summarised in tabular form.

The Strand7 API allows external programs (including VBA) to communicate with and control the analysis routines within the Strand7 package.  This is potentially of great value, but this approach has the drawback of requiring the implementation of a large number of functions in the chosen language; over 1250 for the complete package.  Documentation for a typical function is shown in the screenshot below:

St7GetNodeResponseFunction documentation

To implement a function such as this in VBA so that it can be called as an Excel User Defined Function (UDF) the necessary steps are:

  1. Read the required input data from the spreadsheet.
  2. Convert it to the required data type for the API function.
  3. Set up variables or arrays of the correct data type (and size for arrays) for the API output.
  4. Call the API function.
  5. Check that the API return value (iErr) equals 0.
  6. If not, convert the error number to a text message, and assign this to the VBA function return value.
  7. If iErr equals 0 then extract the required API function results from the appropriate array or value and assign them to the VBA function return value.

In the case of API functions with similar output the required code can be very similar, with differences only in the number, name and data type of function parameters, size of arrays, and the name of the function to be called.  Generation of the required code can be quickly and efficiently set up on a spreadsheet, allowing a large number of API functions to be generated in much reduced time.

The first stage is to tabulate the names and data types of the required function parameters, and the size of any output arrays.  For the Strand7 API I have first converted the manual pdf file to a text file, which I have then read into a spreadsheet.  From this I have tabulated the parameters and data types in the form shown below:

API Function Table (click for full size view)

This table lists parameter names, followed by their data type.  For Code generation purposes it is more convenient to extract only those functions with a similar purpose (all functions returning node attributes for instance), and to list all the parameter names in sequential columns, followed by the data types.  This rearrangement can be conveniently achieved using the Excel Index() function, as shown in the screenshot below:

Table of selected functions with rearranged parameters

The columns in this table are read directly from the main function table except for:

  • Col C: VBA function name – shortened version of the API function name.
  • Col B: Number of parameters – count of non-blank cells in columns F to K.
  • C0l R: API function call – generated by the UDF CallS7Funcr() (see below).

Code for CallS7Funcr function:

Public Function CallS7FuncR(FuncName As Variant) As Variant
' Generate an S7 function call with data arranged in rows
Dim NumRows As Long, NumCols As Long, RtnString As String, NumParam As Long
Dim i As Long

FuncName = Range2Array(FuncName, NumRows, NumCols)

If NumRows < 1 Then
CallS7FuncR = "FuncName range must be a one row range"
Exit Function
End If
NumParam = FuncName(1, 1)
RtnString = "iErr = " & FuncName(1, 2) & "("
For i = 3 To NumParam + 2
RtnString = RtnString & FuncName(1, i)
If i < NumParam + 2 Then
RtnString = RtnString & ", "
Else
RtnString = RtnString & ")"
End If
Next i

CallS7FuncR = RtnString

End Function

The code used to generate the VBA functions is shown in the screenshots below:
An index number is entered in Cell D27, which returns the VBA function name, the number of parameters in the API function, and the number of values in the output array (or 0 if the output is a single value).  Row 30 is generated by a simple string function:
=”Function “&E27&”(uID As Long, NodeList As Variant, Optional ParamList As Variant) As Variant”
The following lines are either common to all the functions (e.g. lines 37 to 48) or use string functions to either return the required data, or a blank line if the line is not required for the current function:

Function code generator - Part 1

The UDF uses two functions to collect the data from the spreadsheet.  GetInput() (Row 47) simply reads the data from the specified range (Nodelist) and converts it into an array, returning the number of rows and number of columns in the array.  GetParam() (Row 52) reads the value at the specified position from the range specified in ParamList.  If this is a numerical value this is assigned to the function return value, but if it is a text string the function reads the data in the specified range, and returns that as a variant array.

The GetParam function is called up to 3 times, followed by a For, Next loop calling the API function for each node specified in NodeList:

Function code generator - Part 2

Function code generator - Part 3

The line calling the API function (Row 92) is simply called from the range R3:R24 using an Index function.

The code generated by the spreadsheet as presented here contains many blank lines when the function has less than the maximum number of input parameters.  This may be simply copied and pasted into a Visual Basic Editor code module, where it may be edited by hand.  Alternatively an edited code range could be set up on the spreadsheet, omitting blank lines, and this code could then be copied automatically using the techniques discussed in the earlier post, making the process entirely automatic.

The next post will look at how the code generated by this technique can be checked and then used in conjunction with the Strand7 FEA package.

Posted in Arrays, Excel, Finite Element Analysis, Newton, UDFs, VBA | Tagged , , , , , | Leave a comment

ShowReel …

… from my daughter

Kerinne Jenkins ShowReel from Kerinne Jenkins on Vimeo.

Including shots from:

Posted in Bach, Films | Tagged , , , , , | Leave a comment

Retrieving unique values from a range or array …

… using the Scripting Dictionary object.

in a previous post I looked at using the dictionary object to count connected elements in a finite element model.  This post looks at a more general (and simpler) usage to return unique values from a worksheet range or VBA array.  It also looks at some more features of the dictionary object.  It was prompted by a discussion at the LinkedIn (private) Excel Blackbelts forum.  A spreadsheet with the examples shown below, and full open source code, may be downloaded from: GetUnique.xlsb

The Unique() User Defined Function (UDF) shown below returns a single column array containing all the unique values (numbers or text) in DRange, which is defined as a Variant so that it will accept a spreadsheet range or an array from another VBA routine.

Function Unique(DRange As Variant) As Variant

Dim Dict As Object
Dim i As Long, j As Long, NumRows As Long, NumCols As Long

'Convert range to array and count rows and columns
If TypeName(DRange) = "Range" Then DRange = DRange.Value2
NumRows = UBound(DRange)
NumCols = UBound(DRange, 2)

'put unique data elements in a dictionay
Set Dict = CreateObject("Scripting.Dictionary")
For i = 1 To NumCols
For j = 1 To NumRows
Dict(DRange(j, i)) = 1
 Next j
Next i

'Dict.Keys() is a Variant array of the unique values in DRange
 'which can be written directly to the spreadsheet
 'but transpose to a column array first

Unique = WorksheetFunction.Transpose(Dict.keys)

End Function
 

The line that does all the work:
Dict(DRange(j, i)) = 1
simply creates a new dictionary key if the contents of DRange(j,1) do not yet exist in the dictionary object, or writes over the old key with an identical new one if it does exist. The result is an array with one copy of each unique value from DRange.

Note that in this function the Dictionary Object is created with the line:
Set Dict = CreateObject(“Scripting.Dictionary”)
Dict having been dimensioned as an Object.

Using this method allows the Dictionary object to be created and accessed without having a VBA reference to the Scripting Library (at least in Excel 2010), but it does restrict some of the functionality of the object.  I haven’t tested this function in earlier versions, but if you find it doesn’t work going into the VB Editor and enabling the reference to the Microsoft Scripting Runtime should fix it.

I have written a second UDF, UniqueR(), which requires a reference to the Scripting Library in VBA, but allows greater control of the dictionary object.  Details of adding a reference to the Scripting Library are given in the previous post.

The UniqueR function has two optional parameters:

=Unique(Datarange) or UniqueR(DataRange, Cmode, Out)

CMode Options:
0 = BinaryCompare
1 = TextCompare
2 = DatabaseCompare

Out Options:
0 = Array of unique items
1 = Number of unique items followed by array
>1 = Number of unique items only

Examples of the use of the two functions (including instructions for entering an array formula) are shown in the screenshots below:

UDF options and instructions for entering array functions

Unique Function and Uniquer Function with TextCompare option

Uniquer Function with Out option = 2 and an input range of 10,000 rows

Microsoft documentation of the Dictionary object is fragmented, and mostly related to VB rather than VBA, but a reasonable introduction is given at: http://support.microsoft.com/kb/246067

Another site with more detailed information, nicely presented, is: http://www.stealthbot.net/wiki/Scripting.Dictionary.

Posted in Arrays, Excel, UDFs, VBA | Tagged , , , , | 20 Comments

The Switch Function (VBA and UDF)

Switch is a VBA function that operates in a similar way to the Select Case statement.  The function arguments are a series of pairs of values, the first of each pair being an expression that can be evaluated as TRUE or FALSE, and the second a value of any data type.  The function return value is the value immediately following the first expression that evaluates to TRUE.  If none of the expressions are true then the VBA function returns null.

I was recently reminded of an Excel User Defined Function (UDF) that works in a similar way, written by Eng-Tips regular “electricpete”.  The original version of the UDF appeared here: http://www.eng-tips.com/viewthread.cfm?qid=281108

This is both a useful function in it’s own right and a good illustration of the use of the VBA ParamArray function argument.  The code is shown below, and is also included in a sample spreadsheet that may be downloaded from SwitchPY. The spreadsheet includes several examples of the UDF used to generate soil PY curves for use in the analysis of piles under lateral loads.

Function SW(ParamArray invar()) As Variant

' Original function posted by "electricpete" on Eng-Tips forum: http://www.eng-tips.com/viewthread.cfm?qid=281108 12 Sep 2010
' Minor modifications by Doug Jenkins 27 Jan 2012

' implement logic similar to switch
' example call
'  =switch(boolean1, value1, boolean2, value2, boolean3, value3....)
'    returns the value corresponding to the first true boolean

' at least one of the boolean expressions must be true
' requires an even number of arguments
' the syntax is pretty much identical to vba switch, except that there is no explicit allowance for else value
' if you want an else clause, enter true for the next to last argument, followed by the associated value

' Note that indexing of invar starts at 0, regardless of Option Base statement

' Check to confirm even number of arguments (as required)

    Dim ctr As Long        ' loop counter
Dim tempswitch As Variant        ' variable which will hold the output value

If UBound(invar) Mod 2 <> 1 Then
        SW = "Error: Need even number of arguments for sw"
        Exit Function
    End If

    ctr = 0        ' initialize counter
    Do While True        ' loop until broken by exit command
        ' Check for boolean input
If VarType(invar(ctr)) <> vbBoolean Then
            SW = "Error 1st 3rd 5th etc arguments of sw must be boolean"
            Exit Function
        End If

If invar(ctr) Then        ' in this case have found a true value, assign function and exit
tempswitch = invar(ctr + 1)
SW = tempswitch
            Exit Do
        Else        ' Else have not found true yet, update counter and continue loop
ctr = ctr + 2
        End If

' Check for reaching end of invar without having found true
If ctr + 1 > UBound(invar) Then
            SW = "Error: sw needs at least one true boolean argument"
            Exit Function
        End If
    Loop

End Function

An example of the use of the UDF is shown in the screenshot below:

Switch Function used to generate PY curves

In Column D the UDF is used to select between two values, which has no real advantage over the use of a single IF function:

Simple use of the Sw UDF

A more complex example is shown below:

Complex Sw() UDF example

In this example the first expression checks if the depth of the section (D12) is below a calculated transition level (D16), and if so it returns the adjacent value in Column D:

  •  =sw($D$12>$D$16,D26,

The second statement checks if the pile deflection, B26, is less than 3 x the Y50 value (D15), and if so returns the adjacent value in Column D:

  • B26<=3*$D$15,D26,

The third statement checks if the pile deflection, is greater than 15 x the Y50 value (D15), and if so returns a calculated value:

  • B26>15*$D$15,$D$13*0.72*$D$12/$D$16,

Finally any remaining values are evaluated with a different formula:

  •  TRUE,$D$13*0.72*(1-(1-$D$12/$D$16)*(B26-$D$15*3)/(12*$D$15)))

The UDF includes error checking to check that there are an even number of inputs, the first input in each pair evaluates as a boolean (i.e. TRUE or FALSE), and that at least one of the boolean expressions is TRUE.  The only change I have made is to the message return method if an error is found.  The original function used a message box, which I have changed to the function returning a text error message (to avoid getting a million message boxes if a function with an error was copied to a million cells).

Posted in Excel, Geotechnical Engineering, Newton, UDFs, VBA | Tagged , , , , | 9 Comments

LatPilePY 1.03

The LatPilePY spreadsheet has been updated with detailed corrections to the function for generating PY curves.  The updated version can be downloaded from LatPilePY.zip.

See LatPilePY 1.02 for more details of the spreadsheet content and background.

Posted in Excel, Geotechnical Engineering, Newton, UDFs, VBA | Tagged , , , , , | 5 Comments