which asked why Mathcad was generating LU matrices different to those calculated from the basic theory. The short answer was that there is more than one way to do LU decomposition. This post looks at the question in more detail, including examples of the basic procedures, and how and why the variations are used.

LU decomposition is an efficient method for solving systems of linear equations of the form Ax = b, where A is a square matrix and b is a vector. A is decomposed into a lower triangular matrix, L, and an Upper triangular matrix, U, such that LU = A. It is then a simple process to find the vector x such that LUx = b, for any values of b, without having to repeat the decomposition process.

Many of the on-line articles on the procedures for decomposition (including Wikipedia) are very hard to follow, but I found a clear and comprehensive tutorial at:

LU Decomposition for Solving Linear Equations

The code listed at the end of this post is a modified version of code at the link above.

The basic process to find x such that Ax = b is:

- Decompose the matrix A into a lower triangle, L, with a leading diagonal of 1s, and an upper triangle, U.
- Use forward substitution to find y such that Ly = b
- Use backward substitution to find x such that Ux = y

Python code for this procedure is shown below. Applying this procedure to a 3×3 matrix gives the results shown in the screenshot below, under Python unpivoted results:

- Triangular matrices L and U are generated from Matrix A.
- Multiplying L by U gives LU which is equal to A.
- The lu_solve function follows the full process to generate the solution with A and b as input.
- The lu_solve2 function has L, U, and b as input, and returns the same result.
- The Scipy function LUSolve returns the same results, but the Scipy decomposition functions generate additional P (pivot) and PL matrices. (Octave returns the same results).

The reason for the introduction of the additional matrices can be seen in the screenshot below:

The top left value of the Matrix A has been changed to zero. The calculation of the unpivoted L matrix now requires a division by zero, giving invalid results. The pivoting process moves the matrix rows and/or columns to avoid the zero at the top left, and remove the division by zero.

There are alternative methods of pivoting, as can be seen in the Scipy and Python pivoted results above, but both result in a PLU matrix identical to the original Matrix A, and the same results. Python code for the whole decomposition and solving process is shown below, without and with pivoting.

Without pivoting

Decompose Matrix A to Lower and Upper triangular matrices:

```
def lu_decomp(A):
"""(L, U) = lu_decomp(A) is the LU decomposition A = L U
A is any matrix
L will be a lower-triangular matrix with 1 on the diagonal, the same shape as A
U will be an upper-triangular matrix, the same shape as A
"""
n = A.shape[0]
if n == 1:
L = np.array([[1]])
U = A.copy()
return (L, U)
A11 = A[0,0]
A12 = A[0,1:]
A21 = A[1:,0]
A22 = A[1:,1:]
L11 = 1
U11 = A11
L12 = np.zeros(n-1)
U12 = A12.copy()
L21 = A21.copy() / U11
U21 = np.zeros(n-1)
S22 = A22 - np.outer(L21, U12)
(L22, U22) = lu_decomp(S22)
L = np.zeros((n,n))
U = np.zeros((n,n))
L[0,0] = L11
L[0,1:] = L12
L[1:,0] = L21
L[1:,1:] = L22
U[0,0] = U11
U[0,1:] = U12
U[1:,0] = U21
U[1:,1:] = U22
return (L, U)
```

Find y such that Ly = b

```
def forward_sub(L, b):
"""y = forward_sub(L, b) is the solution to L y = b
L must be a lower-triangular matrix
b must be a vector of the same leading dimension as L
"""
n = L.shape[0]
y = np.zeros(n)
for i in range(n):
tmp = b[i]
for j in range(i):
tmp -= L[i,j] * y[j]
y[i] = tmp / L[i,i]
return y
```

Find x such that Ux = y

```
def back_sub(U, y):
"""x = back_sub(U, y) is the solution to U x = y
U must be an upper-triangular matrix
y must be a vector of the same leading dimension as U
"""
n = U.shape[0]
x = np.zeros(n)
for i in range(n-1, -1, -1):
tmp = y[i]
for j in range(i+1, n):
tmp -= U[i,j] * x[j]
x[i] = tmp / U[i,i]
return x
```

Combine the process to solve Ax = b

```
def lu_solve(A, b):
"""x = lu_solve(A, b) is the solution to A x = b
A must be a square matrix
b must be a vector of the same leading dimension as L
"""
L, U = lu_decomp(A)
y = forward_sub(L, b)
x = back_sub(U, y)
return x
```

With pivoting

Form pivot matrix, and pivoted L and U matrices

```
def lup_decomp(A, out = 0):
"""(L, U, P) = lup_decomp(A) is the LUP decomposition P A = L U
A is any matrix
L will be a lower-triangular matrix with 1 on the diagonal, the same shape as A
U will be an upper-triangular matrix, the same shape as A
P will be a permutation matrix, the same shape as A
"""
n = A.shape[0]
if n == 1:
L = np.array([[1]])
U = A.copy()
P = np.array([[1]])
return (L, U, P)
# Form pivot matrix, P
P = np.identity(n, dtype = np.int)
A_bar = np.copy(A)
for row in range(0, n):
i = np.abs(A_bar[row:,row]).argmax()
if i > 0:
temp = A_bar[row+i,:].copy()
A_bar[row+1:row+i+1,:] = A_bar[row:row+i,:].copy()
A_bar[row,:] = temp
temp = P[row+i,:].copy()
P[row+1:row+i+1,:] = P[row:row+i,:].copy()
P[row,:] = temp
if out == 3: return P
if out == 4: return A_bar
(L, U) = lu_decomp(A_bar)
if out == 5: return L
if out == 6: return U
return (L, U, P)
```

Solve by forward and back substitution:

```
def lup_solve(A, b):
"""x = lup_solve(A, b) is the solution to A x = P b
A square matrix to be solved
b must be a vector of the same leading dimension as A
"""
L, U, P = lup_decomp(A)
z = np.dot(P, b)
y = forward_sub(L, z)
x = back_sub(U, y)
return x
```

Full code to link the Scipy linear algebra functions, and the code above, to Excel via pyxll will be posted in the near future.

]]>The recent post More long numbers with Python was also supposed to link to py_Numpy, but I had linked to the wrong file, which has now been fixed. That post noted that the Numba JIT compiler was not working with Python release 11. That has now been fixed, and Numba release 0.57 is working with the latest Python without a problem.

As before the py_Numpy.zip file contains two versions, py_Numpy-JIT.py and py_Numpy-noJIT.py. Just copy whichever one you want to the py_Numpy.py file.

The screen-shot below shows some of the polynomial functions available from py_Numpy. See the link at the top of the post for more details.

]]>Microsoft are continuing their quest to make downloading spreadsheets with macros ever more difficult, and now any downloaded spreadsheet has VBA code disabled by default. Unblocking the code (if you know and trust the source of the file) is quite easy. Find the file in File Explorer, right-click and select Properties, then select “unblock” down the bottom:

If you leave the file blocked however, and try to enable the macros, Microsoft will take you to a page on their website that says:

A potentially dangerous macro has been blocked

Macros can add a lot of functionality to Microsoft 365, but they are often used by people with bad intentions to distribute malware to unsuspecting victims.

Which is fair enough, but then it says:

Macros aren’t required for everyday use like reading or editing a document in Word or using Excel workbooks. In most cases you can do everything you need to do in Microsoft 365 without allowing macros to run.

Which is rather like saying you can do everything you need to do in your car without enabling top gear.

Coincidentally, the same day I had the problem with the download I found a new website dealing with using Excel with VBA for structural engineering design:

where the very first article was:

]]>https://en.wikipedia.org/wiki/IFilter

The working link is:

https://web.archive.org/web/20210227135128/https://supportdownloads.adobe.com/thankyou.jsp?ftpID=5542&fileID=5550 – Adobe PDF iFilter for 64bit Windows systems

Note that the link listed at Note 1 in the Wikipedia article didn’t work.

For details of installing the file, and updating the search settings, see Indexing pdf content.

]]>Note that the Python code comes in two version, with or without calls to the Numba just-in-time compiler. I am currently using the latter, since the standard installation of Numba does not yet support Python 3.11.

The first example uses Python integers to return the factors of integers of any length:

The code for the Factorization function was adapted from a Stackoverflow discussion:

Integer factorization in python

The function returns the factors of any given integer, followed by the calculation time. The first example (row 6) took about 20 seconds to return the results, so has been converted to text, but the example on row 9 is any active function, which takes less than 0.08 seconds to run.

The factorization function calls gcd(), which returns the greatest common divisor of two integers, and can be called from Excel using py_gcd:

```
@xl_func
@xl_arg('n1', 'str')
@xl_arg('n2', 'str')
@xl_return('str')
def py_gcd(n1, n2):
n1 = int(n1)
n2 = int(n2)
return gcd(n1, n2)
```

The two integers are passed as strings (since Excel can’t handle very long integers), then converted to integers and passed to gcd(). The integer return value is than converted to a string for return to Excel.

Three functions are provided for integer division:

int_mod(n1, n2) returns the remainder of n1 divided by n2. The example confirms that 112969501600351915928116

is indeed an exact factor of

11193123069125255733930404403495413078162092382549228

int_floor(n1, n2) returns the integer part of n1, divided by n2, using the Python // operator, whereas int_div(n1, n2) returns the complete result of the division, including any decimal part, using the / operator. Note that division of two integers using / in Python always returns a float, even if the divisor is an exact factor, as in this case. The code returns the resulting value to Excel as a string, but all digits after the 16th are lost. To retain the full precision with long integers use the int_floor function.

The multiplication, addition and subtraction functions are more straightforward, since the results of these operations will always be another integer:

]]>The spreadsheet has the same functionality as the VBA version, providing ULS analysis of any reinforced concrete section divided into trapezoidal layers, to Australian codes, Eurocode 2, and ACI 318. It also has the added functionality of design for shear (currently to Australian codes only), and a function allowing simplified input for rectangular sections.

Input is in the same format as the VBA version, with the optional addition of shear data input where required (click any image for full-size view):

Note that for shear design:

- The design shear force and torsion, and the associated bending moment must be entered in the range D5:F5.
- For any hollow or non-rectangular section, or a section with prestressing ducts, the effective web width must be entered in cell F8.
- For box sections the additional data in range T44:T47 is required.

The tendon angle entered in cell F9 is currently only used for shear design.

Output is also in similar format, with the addition of shear related results:

The py_UmomR function has simplified input for rectangular sections with two layers of reinforcement:

]]>The list has now been updated and there are now 189 files available for download, most with open source VBA and/or Python code.

The list is in Excel worksheet format, and can be displayed in full-screen view by clicking on the icon in the bottom-right corner:

The list can be sorted by any of the columns, and has hyperlinks to download the file (Column A) and to the latest blog post featuring the file (Column B):

Note that the linked blog posts also have download links, but those from early 2019 or before will often be in http: format (rather than https:), which will not work with modern browsers. In that case just return to the download list, where all the links should work.

]]>or to put it more mathematically, what three positive integers satisfy the equation:

It turns out the answer is not so simple. A full and detailed derivation can be found at:

Checking the results is not so simple either. The values have up to 80 digits, which is way beyond the standard precision available in Excel, but using the Python mpmath library, with the EvalU spreadsheet, it can be done. Enter the three values as text (or copy and paste!), enter the formula, set the number of significant figures to 85 or more, and use the mp_Eval function:

Changing the last digit of one of the values confirms that the evaluation is working to the required precision:

The spreadsheet can be downloaded from:

For more details see: Pint MPmath and implied units

]]>I find You-tube informative videos frustrating because you have to sit through 10-20 minutes of video to get maybe 1-2 minutes worth of information. If only you could display a transcript of the spoken words!

Well it turns out you can:

Click on the three dots below the bottom right corner of the video, and select “show transcript”

… and the transcript is displayed to the right. You can then click on the three vertical dots to the top-right of the transcript and select toggle time-stamp.

You can then read the transcript on-line, or select and copy to a word-processor or text editor.

Here is the video featured above:

]]>The main new feature is the option to design for prestress loads applied to circular tanks. Due to axi-symmetric effects, when circumferential prestress is applied to a circular tank structure this generates a uniform axial stress across the cross section, in spite of the eccentric position of the prestress cables. This is handled by applying a virtual reaction moment, equal and opposite to the moment due to the eccentricity of the prestress. Use of this feature is illustrated in the screenshots below (click any image for full-size view):

If the “Adjust for circular tank loading” is set to “False”, or left blank, the section capacity is calculated in the usual way, allowing for the eccentricity of the prestress force:

If the option is set to True, the virtual moment is applied to the section, reducing the section capacity:

The correction for the circular tank effect is shown in the function output:

]]>