I have now modified the Beam Design Functions spreadsheet to carry out a biaxial moment analysis with linear elastic material properties. The spreadsheet, including full open-source code, can be downloaded from:

Beam Design Functions Biax.zip

Input for a square section is shown below (the cross section is plotted with an xy graph, so x and y axes are not plotted to equal scale). The Biax function adjusts the position of the neutral axis so that the stress at one end is close to zero. The other stresses are then calculated based on the input neutral axis angle. Clicking the “Adjust NA Angle” button adjusts the angle so that the stress at both ends are near equal.

Maximum concrete compression and steel tension are plotted below for a resultant input moment angle to the X axis between 0 and 90 degrees.

Input and results below are for a rectangular section, 2000 mm wide by 1000 mm deep.

Non-rectangular sections may also be analysed. In some cases the “Adjust NA Angle” routine may fail to find a solution. Adjusting the initial input angle, so that the NA stresses are both close to zero should allow the routine to work:

Detailed output results are given on the “Elastic Out” sheet, including:

- Section properties for the concrete in compression, reinforcement, and the combined section. Note that the listed properties are calculated with the section rotated so that the neutral axis is parallel to the X axis.
- Concrete stresses at each node of the section in compression.
- Reinforcement stresses at each end of each layer.

The spreadsheet also allows for input of steel prestress forces:

The image below shows the same section input with coordinates rotated through 90 degrees, and with the same moment applied about the Y axis. The neutral axis angle is also rotated through 90 degrees, giving identical results for concrete and reinforcement stresses:

The results have also been checked against the “Elastic” single axis bending function, showing exact agreement:

A prestressed example with biaxial bending is shown below. Note that for complex shapes the Excel goal-seek function (used by the Adjust NA Angle routine) is more likely to fail to find a solution, and some initial manual adjustment of the NA angle may be required:

]]>prompted me to look at how these numbers are handled in Excel, in VBA called from Excel, and in Python called from Excel via pyxll. The results are shown in the screenshot below:

Column A shows the operation performed on each line, with results from on-sheet calculations, VBA user defined functions (UDFs), Python UDFs, and Python UDFs using Numpy float64 data type, in the following columns.

For the operations using (0.1+0.2)+0.3 and 0.1+(0.2+0.3):

- Both Excel and VBA show the two expressions as being equal to 0.6, with the difference exactly 0 and the two expressions as equal.
- Both Python and Numpy, when called from Excel, show the two expressions as exactly equal to 0.6, but a small difference between them, and the expressions as unequal.
- When the expressions are entered directly in Python, (0.1+0.2)+0.3 returns 0.600000000000001, with the other results as when called from Excel (see screen-shot below).

For the operations involving a division by zero:

- The Excel on-sheet calculation returns #DIV/0! for all three cases.
- The VBA code returns #VALUE! for all three. Note that it is possible to check for zero divisions, and return #DIV/0! with additional code.
- Python using Python float values returns a zero division error message for all three cases.
- Python using Numpy float values returns 1.7977E+308 for the first two cases and #NUM! for the third when called from Excel.
- Entering the divisions by zero directly in Python returns the same error message as when called from Excel, but when using Numpy Float64 values, it returns inf, -inf, and nan, respectively, together with a warning (see screen-shot below).

Finally, comparing or subtracting two large numbers, with more than 15 significant figures, creates an inconsistency in Excel:

- The numbers actually entered (formatted as text) are shown in column A, but when these numbers are entered as values the final 9 of the smaller one is truncated to 0, and the displayed number is 9999999999999990. Nonetheless, subtracting the two numbers returns zero, but checking for equality returns FALSE.
- VBA, Python and Numpy all return the same results with these numbers. When the numbers are passed from Excel the truncated value of the smaller number is preserved, and subtraction returns 10.0 (rather than the exact result of 1.0), and checking for equality returns FALSE. When the numbers are entered directly in VBA or Python code the smaller number is rounded up to 1.0E16, so subtraction returns 0 and the equality check returns TRUE.
- When entered in the VBA editor, the editor updates the display automatically changes the rounded up value to display as 1E16 (see screen-shots below).

In summary:

- Excel does not fully comply with the IEEE 754 format, since it displays results close to zero as zero, and it has no equivalent of inf, -inf, or nan.
- The Excel rounding or truncating of numbers is preserved when the values are passed to VBA or external code.
- Checking for equality in Excel, the results are not always consistent with the displayed values.
- When checking for equality VBA returned the same results as Excel (at least for the examples used here), even though it did not always return the same subtraction result.
- The VBA equality and subtraction results were self-consistent (for the values checked).
- When checking if two numbers are equal, either in on-sheet calculations or VBA or external code, always either used rounded values, or check that the relative absolute difference is below a specified tolerance.

For more examples, details, and discussion see: When does 35 not equal 35?

]]>It is also what happened at Uluru this week:

]]>The file has now been updated, with the offending module deleted, and I’m told that Malwarebyte no longer raises a warning.

The message is, always check downloaded files, even from trusted sources, and please let me know if you get any malware or other warnings on files downloaded from here.

Updated download file: CSpline2

]]>ExcelJet has a detailed post on how to use the new function, including code for several examples. Extracts from two of these examples are shown below, together with VBA code for UDFs with the same functionality.

The screen-shots below show code for a Lambda function to evaluate the volume of a sphere. First by entering the function in a worksheet cell:

Then by entering the function in the Name Manager, and giving it a convenient name:

A slightly more complex example counts the number of words in any text string:

Results for the same two examples are shown below, using VBA UDFs:

Here is the VBA code for these two functions:

```
Const Pi As Double = 3.14159265358979
Function SphereVol(r As Double)
SphereVol = 4 / 3 * Pi * (r) ^ 3
End Function
Function CountWords(Text As String)
If Len(Trim(Text)) = 0 Then
CountWords = 0
Else
CountWords = Len(Trim(Text)) - Len(WorksheetFunction.Substitute(Text, " ", "")) + 1
End If
End Function
```

From my brief comparison, I would list the advantages of the alternatives as:

Lambda Functions:

- Can be used in on-line versions of Excel that don’t support VBA
- No knowledge of VBA required

VBA User Defined Functions:

- Useful functions can be created with minimal VBA knowledge
- A good way to learn useful VBA skills, without getting bogged down in the details of the complex Excel VBA object model
- Much easier debugging and documentation for complex functions
- Link to existing VBA libraries
- Link to compiled functions for maths intensive functions
- Save as an add-in to use across other workbooks

There are many examples of the use of the new Let function on the web (see my previous post on this topic for links). This post compares use of Let with my Eval user defined function (UDF). More details on the Eval UDF can be found at Evaluating text with units and Evaluating Text – Update.

The screenshot below shows the function “F*L^3/(3**E*I)” evaluated with the Eval UDF and the Let function:

The Eval function evaluates a function entered as text on the spreadsheet (or entered as a text string within the function), and reads a list of parameters and the corresponding values from the spreadsheet:

Using the Let function, each parameter is entered directly in the function, followed by the value, which may be entered in the function, or refer to a spreadsheet cell (or range). The function to be evaluated must be entered as the last argument of the Let function:

Note that if the final argument of the Let function is a cell reference, the function just returns the text, rather than evaluating it:

A more complex example is shown below. Using the Eval function the parameters are listed in Column A, and the values in Colum I:

The Let function could be used in the same way, but it is also possible to evaluate parameters within the Let function. In the first Let example below (row 41) I used a nested Let function, in this case evaluating the Beta parameter within the function, but that isn’t necessary. Any parameter can be evaluated within the Let function, using the previously defined parameters. This is shown in Row 42, where Beta is defined with … Beta, (K*G/(Cw*E))^0.5. In the third Let example below (Row 43) all of the parameters are defined within the Let function, giving a complete (but not particularly readable) function, requiring no external evaluation of the intermediate parameters on the spreadsheet:

]]>… and read more about the whale at The 52 Hertz Whale.

]]>- LET (now available to Office 365 subscribers and
- LAMBDA (currently available to Office Insiders program only)

Microsoft documentation can be found at: Announcing LET

Have you ever had to repeat the same expression multiple times within a formula, created a

mega formulaor wished that you had a way to reuse portions of your formula for easier consumption? With the addition of the LET function, now you can!Introducing LET

LET allows you to associate a calculation or value in your formula with a name. It’s names except on a formula level.

In December 2020, we announced LAMBDA, which allows users to define new functions written in Excel’s own formula language, directly addressing our second challenge. These newly defined functions can call other LAMBDA-defined functions, to arbitrary depth, even recursively. With LAMBDA, Excel has become

Turing-complete. You can now, in principle, writeanycomputation in the Excel formula language. LAMBDA is available to members of the Insiders: Beta program. The initial release has some implementation restrictions that we expect to lift in the future. We discussed LAMBDA and some of our research on spreadsheets in a sponsored video presented at POPL 2021.

Further documentation and lengthy user discussion on the LAMBDA function

Examples of the LET function from ablebits.com:

Using LET function in Excel with formula examples

Discussion of the advantahes and limitations of the LAMBDA function:

What Makes Excel’s Lambda Functions so Awesome (and what doesn’t)?

I will follow up in later posts with my own comments, including comparison with my VBA Eval function, and linking to similar, and better functionality with Python and pyxll.

]]>- Functions in the active Python module
- Functions in any active loaded library
- Lambda functions

When the callable argument is passed from Excel, using pyxll, the function name will be passed as a text string, which must be converted to a function object in the Python code. Different procedures are required for the three types of function listed above:

- Functions in the active module can be called with the “globals” method (The globals () method returns the dictionary of the current global symbol table. )
- Functions in active loaded libraries can be called with the “getattr” method (The getattr() method returns the value of the attribute of an object.)
- Strings in lambda format can be converted to lambda functions with the eval() function

Examples of each of these methods are included in the code below:

```
import scipy as sp
import scipy.stats as stats
import numpy as np
mods = {'np': np, 'stats': stats, 'sp': sp}
def GetCallable(func, mod = None):
# Convert string to callable
# Remove spaces and = from start of string
func = func.replace('=', '')
func = func.strip()
# If string starts with 'lambda' convert ^ to **, then convert string to lambda function
if func[0:6] == 'lambda':
func = func.replace('^', '**')
func = eval(func)
# Else if module is not specified, convert string to function from globals
elif mod is None:
func = globals()[func]
# or if mod is specified, convert mod string to module, then func string to function from mod
else:
mod = mods[mod]
func = getattr(mod, func)
return func
```

The output from this function is a function object that can be passed to any function requiring callable arguments:

```
@xl_func
@xl_arg('x', 'numpy_array', ndim = 2)
@xl_arg('y', 'numpy_array', ndim = 2)
@xl_arg('rank', 'numpy_array<var>', ndim = 2)
@xl_arg('weigher', 'str')
@xl_arg('mod', 'str')
@xl_arg('additive', 'bool')
def py_weightedtau(x, y, rank = None, weigher = None, mod = None, additive = None):
"""
Compute a weighted version of Kendall's :math:`\tau`.
...
Function argument descriptions
...
"""
if weigher is not None: weigher = GetCallable(weigher, mod)
kwargs = {'rank': rank, 'weigher': weigher, 'additive': additive}
kwargs = {k:v for k,v in kwargs.items() if v is not None}
return stats.weightedtau(x, y, **kwargs)[0]
```

One condition where this does not work is for Python functions where “None” is a valid argument, but it is not the default. For instance, many Scipy functions working with multi-dimension array input have an “axis” argument that allows “None” as input, but the default is 0. In this case the default value for the Excel function should be set to 0, and the data type set to variant. Before calling the Python function two additional steps are then required:

- If the argument is a number, convert it from “float” to “int”.
- If the argument is the string “None” convert it to the Python None object.

Typical code is shown below:

```
@xl_func
@xl_arg('a', 'numpy_array', ndim = 2)
@xl_arg('axis', 'var')
@xl_arg('dtype', 'str')
@xl_return('numpy_array')
def py_hmean(a, axis = 0, dtype = None):
...
if axis != 'None': axis = int(axis)
kwargs = {'axis': axis, 'dtype': dtype}
kwargs = {k:v for k,v in kwargs.items() if v is not None}
if axis == 'None': kwargs['axis'] = None
res = stats.hmean(a, **kwargs)
```

]]>