The spreadsheet requires xlwings, Python (including Numpy and Scipy) and Alglib to be installed. All are available for free download, see Excel to Alglib via xlwings for more details.

In the examples below a non-linear function has been fitted to data generated using the same function plus some random scatter using two different Alglib modules:

- The lsfit module (least-squares fitting)
- The minlm module (Levenberg-Marquardt optimizer)

For each of these modules there is one user defined function (UDF) that uses a function entered as text on the spreadsheet, and another that calls a python function.

The lsfit versions ( xl_NLFitwFunc and xl_NLFitwText) are described with examples on the Fit NonLin sheet:

The minlm versions (xl_minlm_vb and xl_minlm_vbtext) are found on the LM Solvers sheet:

The example shown below uses all four functions to fit a function of the form:

a * exp(b*x) + c * x + d

to the data shown in the screenshot below:

The function xl_minlm_vb (shown below) has the following arguments:

- x (B42:B45), Initial guess of function parameters, single column range
- xydat (B22:C34), Data to be fitted, two column range
- Func (C37), Fitting function; a function in NLFuncs.py
- bndl (B48:E48), Lower bound values, single row range
- bndu (B49:E49), Upper bound values, single row range
- epsx (B51), Maximum error
- maxits (B52), Maximum iterations
- out (0), Out = 0 returns results, anything else returns termination type code

Results of the four functions are shown below:

Code for the xl_minlm_vb function is described below. Open source code for all the others is available in the download zip file.

The function xl_minlm_vb (automatically generated by xl wings) calls the Python function of the same name:

@xw.func @xw.arg('x', ndim=1) @xw.arg('bndl', ndim=1) @xw.arg('bndu', ndim=1) @xw.ret(transpose= True) def xl_minlm_vb(x, xydat, funcname, bndl, bndu, epsx, maxits, out = 0): """ Minimise a named function with Alglib minlmcreatev Levenberg–Marquardt algorithm Initial guess of function parameters, single column range Data to be fitted, two column range Fitting function; a function in NLFuncs.py Lower bound values, single row range Upper bound values, single row range Maximum error, 0 = automatic Maximum iterations, 0 = unlimited out = 0 returns results, anything else returns termination type code """ if type(x) == tuple: x = list(x) if type(bndl) == tuple: bndl = list(bndl) if type(bndu) == tuple: bndu = list(bndu) nx = len(x) nf = len(xydat) func = getattr(NLFuncs, funcname) state = xal.minlmcreatev(nx, nf, x, 0.00001) xal.minlmsetbc(state, bndl, bndu) xal.minlmsetcond(state, epsx, maxits) xal.minlmoptimize_v(state, func, None, xydat) x, rep = xal.minlmresults(state) if out == 0: return x else: return rep.terminationtype

- if type(x) == tuple: … Arrays are passed from Excel to Python as tuples, but Alglib requires a list.
- func = getattr(NLFuncs, funcname): looks in NLFuncs.py for a Python function with the name given by funcname, and assigns this function to “func”.
- state = xal.minlmcreatev( … x, rep = xal.minlmresults(state): calls Alglib minlm functions to adjust the parameters passed to func in the “state” object to minimize the square of the results.

“func” in this case is xl_expfunc:

def xl_expfunc(x, fi, func_dat): nf = len(func_dat) func_dat = np.array(func_dat) for i in range(0,nf): X = func_dat[i,0] fi[i] = x[0]*np.exp(x[1]*X)+x[2]*X+x[3] - func_dat[i,1] return

Note that Alglib minimises the square of the array returned by xl_expfunc by adjusting the values in the x array. To use this for curve fitting purposes we have to subtract the supplied Y value from the exponential function:

fi[i] = x[0]*np.exp(x[1]*X)+x[2]*X+x[3] – func_dat[i,1]

Alglib adjusts the function parameters until the error or maximum iterations criteria are satisfied, then extracts the required results from the “state” object with:

x, rep = xal.minlmresults(state)

The x array is then returned to the spreadsheet, via the VBA function.

]]>

I have now added some examples comparing reinforced shear capacity to the ACI 318 design code, using the “customary US units” and SI units versions of the code:

The examples shown below use the user defined functions (UDFs) Eval and EvalU. Eval returns the value of any function entered as text on the spreadsheet, substituting given values for any parameters. EvalU is a “unit aware” version, returning the result in base SI units, or in any specified units. EvalU returns the calculated units if they are not specified, or an error message if the specified output units are not consistent with those calculated from the input units.

Evaluating formula 11-4 as given in the code produces strange results, even if consistent input units are used. If the input units are ignored (using the Eval function) the formula returns the correct results, provided that the input has consistent units, but using EvalU with no specified return unit finds a unit with non-integer exponents, and including m, rather than just N. If the return units are specified, as a force unit, this results in an error message, since the specified return units are different to those calculated from the input units:

The problem is that the code formula contains implied units, since Nu/(2000*Ag) has units of stress (rather than no units), and fc^0.5 has units of stress^0.5, rather than stress. The units can be made consistent by converting the 2000 (or 14 for the SI version) to an input parameter, Kc1, with stress units, and adding a new parameter, Kc2, also with stress units, as shown below:

EvalU now returns calculated units of N, as expected, and both versions of the formula return very similar results in N. Also note that when the output units are consistent with the input units (either lbf or N), EvalU and Eval return exactly the same value, as would be expected.

It is also possible to incorporate the initial constants (2 or 0.17) in the Kc1 and KC2 factors, and to use inconsistent input units. In the first example below Kc1 and Kc2 have been changed to ksi units, and Kc2 is factored by 2^2, so the initial 2 factor can be removed. In the SI example the units of Kc1 and Kc2 are unchanged, but Kc2 is factored by 0.17^2:

With the revised formulas the EvalA results are correct, but the Eval results, ignoring the input units, are now incorrect, because the input units are inconsistent (stresses are in ksi or MPa, but forces are in lbf or kN).

Formula 11-6 has no factors with implied units, so can be evaluated with no additional parameters, but as before, the input and output units must be consistent if Eval is used. EvalU will return correct results with any recognised moment output units:

The results of 11-6 can now be used to evaluate 11-5 for sections with combined axial load and shear:

Finally the upper limit to the shear force for combined axial load and shear is found with formula 11-7. Note that in this case the final expressions in the formula have a different form in the two versions of the code (1 + Nu/(500*Ag) for the US units version and 1 + 0.29*Nu/Ag for the SI version). Both have been converted to the form 1 + Nu/(Kc2*Ag), so Kc2 can have units of stress, so for the SI version the value of Kc2 becomes 1/0.29:

Two additional points to note when using EvalU:

- The units are case sensitive, so if Excel helpfully insists on correcting MPa to Mpa, the units will not be recognised. To avoid this problem go to File-Options-Proofing, click AutoCorrect Options, and either unselect “Correct TWo Initial CApitals, or click Exceptions and add MPa to the exceptions list on the Initial CAps tab. For a quick one-off you can also enter a space after MPa, then press Ctrl-Z.
- Both input units and output units (if used) must be recognised units with the correct abbreviation. The ListNonSI UDF will list all recognised units for any specified unit type, for example “area” as shown in the screen-shot below. To display all the results enter the function with the unit type, then press Ctrl-Shift-S to resize the output array.

]]>

The general procedure for setting up a 3D stiffness matrix, and rotating it to the global coordinate system is given at the link above. The 3DFrame spreadsheet (including full open-source code) can be downloaded from:

The main program for the 3D analysis is the sub-routine p44, found in the mFrameP44 module in the Visual Basic Editor (VBE), which can be accessed by:

- From the spreadsheet, press Alt-F11, or click the Visual Basic icon on the Developer tab*
- Double-click the mFrameP44 module in the Project Explorer window.
- Click the right-hand dropdown box above the editor window, and select P44

* If the Developer tab is not visible, go to File-Options-Customize Ribbon, and select it as one of the main tabs.

The code for setting up the global stiffness matrix is:

' !---------------------global stiffness matrix assembly------------------------ elements_2: ' Range("kga").ClearContents For iel = 1 To Nels For i = 1 To nod Num(i) = g_num(i, iel) For j = 1 To NDim Coord(i, j) = g_coord(j, Num(i)) Next j Next i ReDim km(ndof, ndof) i = BeamConER(iel, 6) If i = 0 Then ReDim R0(1 To 3, 1 To 3) Call rigid_jointed3(km, Prop, Gamma, Etype, iel, Coord, R0) Else For j = 1 To 12 SpringA(1, j) = BeamHinge(i, j + 1) Next j Call spring_jointed3(km, Prop, Gamma, Etype, iel, Coord, R0, SpringA) End If For i = 1 To ndof g(i) = g_g(i, iel) Next i Call formkv(kv, km, g, neq, iel, g_coord, Num) If iel = Range("kmbeam").Value2 Then Range("km") = km Next iel ' elements_2

This code loops through each beam element in the frame and calls either the rigid_jointed3 or spring-jointed3 routines for beams with fixed or spring end conditions respectively. The 12×12 matrix returned for each beam is then added to the global stiffness matrix with the formkv routine.

To step through the code in the p44 routine simply make sure that the cursor is somewhere within the body of the routine and press F8, or Debug-Step Into. To run the routine to a selected point either:

- Set a breakpoint at the required location by clicking in the left hand margin, then press F5 or Run-Run or
- Select the line to run to, then press Ctrl-F8 or Debug-Run to cursor.

There is also a separate function, rigid_jointed3TT, that can be called from the spreadsheet for a specified beam, and returns the beam 12×12 stiffness matrix to the spreadsheet as an array function. The instructions below show how to step through this function line by line.

To step through a VBA function it must either be called from a sub-routine, or initiated from the spreadsheet. To do the latter:

- Create a breakpoint somewhere within the VBA code by clicking in the left-hand margin.
- Select a cell in the spreadsheet that calls the chosen function. The screenshots below use cell C35 in the Matrix examples spreadsheet.
- Press F2 (edit), then either enter for a function that returns a single value, or in this case Ctrl-Shift Enter to return an array. The function will then run to the selected breakpoint:

You can now step through the code one line at a time by pressing the F8 key (or Debug-Step Into). Note that the Locals Window, under the main Editor window displays the status and value of all variables, and you can also see the value of a variable by simply placing the cursor over the variable name anywhere in the code:

Procedures for stepping through the code are then the same as for a sub-routine. To run to a selected line press Ctrl-F8 or right click and Run to cursor:

At the end of the function it will either return the end result (the km2 array), or one of the four intermediate arrays, depending on the value of the Out argument:

The Debug menu provides further options, for example allowing control to jump directly to a specified point, or for called routines to be run in a single step.

For more details of VBA debugging options see Chip Pearson’s Debugging VBA.

]]>

To use a UDF, the VBA code for the function must either be included in the active workbook, or another open file. In most cases the VBA code can be copied from the master file to a new workbook, but in the case of the ConbeamU function the units related code refers to a database of units data in the master file. The easiest way to set up a new file for this case is to save the master file with a new name, and delete all the unwanted sheets. I have added a ConbeamU-template file to the ConbeamU zip file, that has all the required code and units data, two sheets with notes and instructions, and a single blank sheet. The new zip file can be downloaded from:

The screen shots below illustrate how to set up a new application of the ConbeamU function, starting from a blank worksheet.

First copy details of the function arguments from the Functions sheet, and enter or copy and paste the input data. Each data range may be placed in any convenient position. For the ConbeamU function the required data ranges are:

- Segments: details of each beam segment with different section properties, with distance of the segment end from the start (left hand end) of the beam, flexural stiffness (EI), and optionally shear stiffness (GA).
- Outpoints: a single column with either the number of output sections for each span, or a list of output point positions (see below for more details).
- Supports: position of each support, with optionally translational stiffness, rotational stiffness, and displacement.
- Dloads: extent and magnitude of distributed transverse loads.
- PLoads: location and magnitude of point transverse loads and moments.
- OutUnits: single row with units for output results: position, shear force, bending moment, slope (leave blank), and deflection.
- Out: output index, optional, default = 1 (output as listed above).
- ListOutPoints, optional, default = False, output at specified number of sections/span. See below for details of True option.

Note that for the ConBeamU function (and other unit aware functions) all data ranges with values that have a unit must be headed by a valid unit symbol:

After entering all required input data, enter the function in the top-left cell of the output range. The easiest way to ensure that all arguments are provided, in the right order, is to enter the function name, including the opening bracket, then click the “insert function” button, just to the left of the edit bar, as shown below

As each function argument is selected the contents of the cell or range are shown to the right of the data box. When all the required arguments are selected the function results appear under the data boxes. In this example the default values are what we want for the two optional arguments, so we can leave them blank. Click OK or press enter to enter the function:

At this stage only the first value of the results array is visible:

The standard Excel way to display all the contents of an array function is:

- Select the required output range, with the array function in the top-left corner.
- Press F2 (edit).
- Press Ctrl-Shift-Enter

However the ConbeamU spreadsheet has a macro that will do this automatically, so with the cell containing the entered function still selected, just press Ctrl-Shift-S.

The default output option is to specify the number of output sections for each span. An alternative is to list the position of each section where output is required. In the example below two points are entered at each internal support, and each point with a point load or moment. To display the output set the final “ListOutPoints” argument to True, and select the list of locations range as the “OutPoints” argument. Having edited the function, press Ctrl-Shift-Enter to enter:

As before, to re-set the output range to display only the cells with valid output values, press Ctrl-Alt-S:

Note that the list of output point locations does not need to be adjacent to the output range, it can be anywhere in the workbook, or even in another workbook.

The procedures described above are applicable to any Excel function or UDF that returns an array function, except that resetting the output range to display the whole array by pressing Ctrl-Shift-S only works in those workbooks where the required macro has been added.

]]>

I have now added three user defined functions (UDFs) to do the same job:

- Addr2Val1 takes a text string as input and returns the formula with all cell references or range names converted to the value in the referenced cell. Cell references may be anywhere on any spreadsheet.
- The Eval function has been modified to work with cell references and range names, as well as optional lists of parameter symbols, and their associated values.
- Addr2Val2 works the same as Addr2Val1, except the input is an active cell formula, rather than a text string.

The new file may be downloaded from:

Examples of each function are shown in the screenshot below, followed by source code for the Addr2Val2 function. Full open-source code for the other functions is included in the download file.

Updated 3 Sep 2017: There seem to be issues with the WordPress system removing line breaks in some places, and inserting them in others. I have corrected the code below, but if you want to copy the code I recommend doing so from the download spreadsheet, rather than from the listing below, which probably still has some errors.

Function Addr2Val2(FuncRng As Range, Optional CommaDec As Boolean = False) As Variant Dim NumChar As Long, ParamDict As Scripting.Dictionary, i As Long, CheckC As String, AscCheckC As Long, CheckP As String, NewFunc As String, iErr As Long Dim ParamRng As Range, NumParam As Long, PVal As Variant, IsRng As Boolean, CheckRng As String, Func As String ' Evaluate a cell formula (Func), replacing cell addresses or range names with the values in the referenced cells. 'Func is a single cell containing the formula to be evaluated ' CommaDec = True to convert commas to decimal point and semi-colons to commas ' CommaDec = False (default) for no convertion. Func = Trim(FuncRng.Formula) Func = Replace(Func, "$", "") If CommaDec = True Then ' Replace all , with . and ; with , Func = Replace(Func, ",", ".") Func = Replace(Func, ";", ",") End If NumChar = Len(Func) i = 1 Do While i <= NumChar CheckP = "" Do CheckC = Mid(Func, i, 1) AscCheckC = Asc(CheckC) If (AscCheckC > 64 And AscCheckC < 91) Or (AscCheckC > 96 _ And AscCheckC < 123) Or AscCheckC = 95 Or AscCheckC = 33 Then CheckP = CheckP & CheckC i = i + 1 ElseIf CheckP <> "" And Asc(CheckC) > 47 And Asc(CheckC) < 58 Then CheckP = CheckP & CheckC i = i + 1 Else ' Check if CheckP is a cell address or range name On Error Resume Next CheckRng = "" IsRng = False CheckRng = TypeName(Range(CheckP)) If CheckRng = "Range" Then IsRng = True If IsRng Then NewFunc = NewFunc & Range(CheckP).Value2 & CheckC ' else leave it unchanged Else NewFunc = NewFunc & CheckP & CheckC End If i = i + 1 Exit Do End If Loop Loop Addr2Val2 = NewFunc End Function

]]>

Bett’s Dance; Bert Jansch live at the 12 bar:

Bert Jansch and John Renbourn; un-named instrumental, live from Massachusetts, 1990

From the same concert, John Renbourn plays So Early in the Spring:

]]>

whose playing of Scottish fiddle music reminded me of Robin Williamson and the Incredible String Band:

and lead to the discovery that Marcus Holden also plays in a string band, The Jugalug String Band:

]]>

As before the following software is required:

- Python, including Numpy and Scipy
- xlwings ver 0.11.04 or later
- Alglib Python version 3.11 or later

The Anaconda Python package includes Numpy, Scipy and xlwings (now updated to ver 0.11.04). Alglib must be installed separately, and comes in free and commercial versions.

To deal with the need to pass any number of arguments, the function arguments are listed in a two (or three) column range; column 1 lists argument names, and column 2 either single values, or range addresses, entered as a text string. This data is read into a VBA variant array, then converted to a Python dictionary. Examples of the usage of xl_CallFuncD are shown in the screen-shots below:

A formula entered as text on the spreadsheet, with any number of listed variables. Note that exponents may be entered using either VBA notation (^) or Python/Fortran notation (**):

Function results may be used as input values for another formula:

On-sheet formulas may include any built-in Python function, or functions from the imported Numpy, Scipy or Alglib modules:

Both on-sheet formulas and external function calls may have array arguments. In this case the argument range must have three columns, with the third column specifying the number of dimensions for the array (1 or 2). The range address may be entered as text, but to ensure that the address is updated if the range is moved the UDF RngAddress should be used:

xlCallFunc will call user defined Python functions from the xlAlgLib module without any dedicated VBA interface function:

… or user defined functions from another module:

… or functions from other Python libraries. If the called function returns 2 or more array results the Out argument must be used to select the array to be returned. The Python dictionary of arguments passes arrays as tuples, which will cause problems with some functions. In this case enter True in the Convert argument, which will convert all passed tuples to lists, or lists of lists.

Note that argument names must be entered exactly, including correct case. To list all arguments for any function, use the get_argnames UDF:

]]>

It turns out it’s not easy to do with VBA either. I have added a macro to do the job to the Eval2 spreadsheet:

Sub Addr2Val() Dim PrecCells As Variant, CellRng As Range, NumP As Long, i As Long, Form As String Dim CellAddr() As String, CellVal() As Double, xCell As Range, OutCols As Long Set CellRng = Application.ActiveCell Form = CellRng.Formula Form = Replace(Form, "$", "") Set PrecCells = CellRng.Precedents NumP = PrecCells.Count ReDim CellAddr(1 To NumP, 1 To 1) ReDim CellVal(1 To NumP, 1 To 1) i = 1 For Each xCell In PrecCells CellAddr(i, 1) = xCell.Address CellAddr(i, 1) = Replace(CellAddr(i, 1), "$", "") CellVal(i, 1) = xCell.Value i = i + 1 Next Form = Eval(Form, CellAddr, CellVal, 0) OutCols = Selection.Columns.Count If OutCols > 1 Then OutCols = OutCols - 1 CellRng.Offset(0, OutCols).Value = " " & Form Set CellRng = Nothing Set PrecCells = Nothing End Sub

The macro uses the Range.Precedents method to return the address and value of each cell reference in the formula, then calls the Eval user defined function (UDF) to convert the cell addresses in the formula to their numerical values. It would be convenient to put this in a function, which could be called from the spreadsheet, but when entered in a UDF the .Precedents method does not return information on the cell precedents, it returns the information for the cell itself. If anyone has any suggestions for converting the macro to a working UDF, please leave a comment.

The macro has been set up to return the results in the cell to the right of the selected cell, or if three or more columns are selected, in the top right cell of the selected range. Note that the macro will write over any contents in the results cell.

An example of the macro in use (with instructions) is shown in the screen-shot below:

The updated spreadsheet, including full open-source code and the example above, can be downloaded from:

]]>

An Alglib/Python based spreadsheet has previously been presented here. I have now updated this to the latest versions of xlwings and Alglib, added additional solver and linear algebra functions, and updated the Python code to make better use of the xlwings auto-import functionality, which generates the necessary VBA code at the touch of a button. The new spreadsheet can be downloaded from:

As usual, the download files include full open source code.

To install the necessary files:

- Install Python, either version 2 or 3. The Anaconda Python package will also install xlwings, but see below.
- Install or update to the latest version of xlwings (0.11.04 at the time of writing). If the automatic install delivers an earlier version, see here.
- Install Alglib.
- Copy the contents of the xlAlglib zip file to any convenient location.
- Any problems, please let me know.

The screenshots below show the functions available in the new version. Future posts will look in more detail at specific examples, and procedures for modifying the Python code.

List of functions:

Rational Interpolation Function:

Fit a spline to scattered data:

Fit Polynomial to scattered data:

Radial Basis Function Interpolation:

Levenberg-Marquardt optimisation:

Differentiation of Cubic Splines:

]]>