Random numbers, palindromes and Numpy update

This post looks at some of the options for generating random numbers in Excel and Python. The code and example spreadsheets can be downloaded from:

Updated Numpy files at: py_SciPy.zip
Code added to the Pandigital files at: Pandigitals.zip

The py_SciPy download contains full open-source Python code and example spreadsheets for a wide range of Scipy functions, as well as the Numpy functions covered in this post. For more details, see the series of posts starting at: Scipy functions with Excel and pyxll. Also see Python and pyxll for details of the pyxll add-in, required to link my Python code to Excel.

The code below calls functions from the Python random or secret modules:

@xl_func(category="py-Maths", help_topic="https://docs.python.org/3/library/random.html#functions-for-integers!0")
@xl_arg('start', 'int')
@xl_arg('stop', 'int')
def py_RandBetween(start, stop):
    return randint(start, stop)

@xl_func(category="py-Maths", help_topic="https://docs.python.org/3/library/random.html#functions-for-integers!0")
@xl_arg('start', 'int')
@xl_arg('stop', 'int')
@xl_arg('step', 'int')
def py_RandRange(start, stop, step=1):
    return randrange(start, stop, step)

@xl_func(category="py-Maths", help_topic="https://docs.python.org/3/library/secrets.html#random-numbers!0")
@xl_arg('start', 'int')
@xl_arg('stop', 'int')
def py_RandBelow(stop):
    return randbelow(stop)

Function results are shown in the screenshot below, together with three Numpy functions and the Excel built-in Randbetween and Randarray functions:

  • py_RandBetween and Excel Randbetween operate in the same way, returning a single integer, between the specified inclusive limits.
  • py_RandRange and py_RandIntA operate in a similar way, returning integers between the lower limit and below the upper limit. py_RandRange also has a step input (default = 1), and returns only integers with the specified step interval above the minimum value. py_RandIntA is a Numpy function, and returns an array with the specified number of rows and (optionally) columns.
  • py_RandArray and py_RandUniform are Numpy functions and also return arrays with multiple rows and 1 or more columns. py_RandArray returns a normal distribution with zero mean and 1.0 variance. py_RandUniformA operates in a similar way to the Excel Randarray function, returning an array of floats of the specified size with uniform distribution over the specified range.
  • py_RandBelow returns an integer between zero and 1 less than the upper limit. It is part of the Python random.secrets module and provides greater security in the generation of random integers.

All the Python and Numpy functions provide direct access to the on-line Python help by clicking the “help on this function” link in the function dialogue:

The help is also available as text within Excel for the Numpy functions, using the py_Numpy spreadsheet:

The code below generates arrays of numerical “palindromes”, i.e. integers with the same value when read from right to left:

@xl_func()
@xl_arg('start', 'int')
@xl_arg('stop', 'int')
def PalindromeNum(start, stop):
    stime = time.perf_counter()
    palindromes = []
    for num in range(start, stop):
        if str(num) == str(num)[::-1]:
            palindromes.append(num)
    palindromes.insert(0, len(palindromes))
    palindromes.insert(0, time.perf_counter() - stime)
    return palindromes

@xl_func()
def PalindromeNum8():
    stime = time.perf_counter()
    palindromes = []
    for one in range(1, 10):
        for two in range(0, 10):
            for three in range(0, 10):
                for four in range(0, 10):
                    palindromes.append(int(f'{one}{two}{three}{four}{four}{three}{two}{one}'))
    palindromes.insert(0, len(palindromes))
    palindromes.insert(0, time.perf_counter() - stime)
    return palindromes

The code is based on this Quora post: How many 6 digit palindrome numbers can be formed?

The functions return an array of all the palindromes between the specified limits (for the PalindromeNum function, or between 10,000,000 and 100,000,000 for PalindromeNum8. The functions also return the execution time and the number of palindromes found.

The PalindromeNum function works by generating all the integers in the specified range, then checking if they are palindromes, by comparing a string of each value with the reversed string. This works reasonably quickly for up to 1 million values, but rapidly slows down for bigger ranges:

The PalindromeNum8 function works by generating all the possible integers of half the required size, then appending the reversed value as a string, and converting the result back to an integer. For the example shown this is about 3500 times faster than the original code! The problem with the faster code is that it needs a variable number of nested loops, depending on the magnitude of the integers, so the code would need reworking to allow ranges of different magnitude. Since the purpose of the exercise is to display Python techniques for manipulating lists and strings, I will leave that as an exercise for the reader!

Posted in Arrays, Excel, Link to Python, Maths, Newton, NumPy and SciPy, PyXLL, UDFs | Tagged , , , , , , | Leave a comment

Python Match Case Statement

The Python match case statement was introduced in Python 3.10. It provides similar functionality to the VBA Select Case statement, but also has additional features. For detailed background with examples see:

geekforgeeks – Python Match Case Statement

The example below illustrates the basic functionality using code called from Excel with pyxll.

@xl_func()
def SelectVal(n):
    match n:
        case 1:
            return 'first odd'
        case 2:
            return 'first even'
        case 3 | 5 | 7:
            return 'odd'
        case 4 | 6 | 8:
            return 'even'
        case _ if 8 < n < 12:
            return 'between 9 and 11'
        case _ :
            return 'more than 11'

The code shows options for:

  • Matching a single value (1 or 2)
  • Matching 2 or more values with the or operator, e.g. 3 | 5 | 7:
  • Matching any other value in a specified range using _ if condition :
  • Matching any other value with _:

Calling from excel the results are:

Posted in Computing - general, Excel, Link to Python, PyXLL, UDFs | Tagged , , , | Leave a comment

Python Data Classes

I have recently been catching up with new Python features, starting with Data Classes which were introduced in Python version 3.7. For information and code examples I have referred to:

Data Classes in Python 3.7+ (Guide) at Real Python

The link provides detailed information on the basics of data classes, leading on to more advanced features and examples, including:

I have adapted this code to create Excel UDFs that will find the distance between two locations either defined by latitude and longitude, or by city names linked to a table with latitude and longitude. A spreadsheet with examples and location data for Australian cities can be downloaded from:

CityDist_au.zip

The code below should be copied to a pyxll module that is loaded at start-up:

from dataclasses import dataclass
from math import asin, cos, radians, sin, sqrt

@dataclass
class Position:
    name: str
    lat: float = 0.0
    lon: float = 0.0

    def distance_to(self, other):
        r = 6371  # Earth radius in kilometers
        lam_1, lam_2 = radians(self.lon), radians(other.lon)
        phi_1, phi_2 = radians(self.lat), radians(other.lat)
        h = (sin((phi_2 - phi_1) / 2)**2
             + cos(phi_1) * cos(phi_2) * sin((lam_2 - lam_1) / 2)**2)
        return 2 * r * asin(sqrt(h))
    
@xl_func()
@xl_arg('posn1', 'numpy_row')
@xl_arg('posn2', 'numpy_row')
def Sphere_dist(posn1, posn2):
    startpnt = Position('startpnt', *posn1)  
    endpnt = Position('endpnt', *posn2) 
    return startpnt.distance_to(endpnt)

@dataclass
class AusCities:
    name: str
    lat: float = 0.0
    lon: float = 0.0
    state: str = ''
    pop: int = 0

    def distance_to(self, other):
        r = 6371  # Earth radius in kilometers
        lam_1, lam_2 = radians(self.lon), radians(other.lon)
        phi_1, phi_2 = radians(self.lat), radians(other.lat)
        h = (sin((phi_2 - phi_1) / 2)**2
             + cos(phi_1) * cos(phi_2) * sin((lam_2 - lam_1) / 2)**2)
        return 2 * r * asin(sqrt(h))

@xl_func()
@xl_arg('cities', 'numpy_array<var>')
@xl_arg('posn1', 'str')
@xl_arg('posn2', 'str')
def City_dist(cities, posn1, posn2):
    try:
        i = np.where(cities[:,0] == posn1)[0][0]
    except:
        return "Name 1 not found"
    startpnt = AusCities(cities[i,0], cities[i,1], cities[i,2], cities[i,5], cities[i,7])
    try:
        i = np.where(cities[:,0] == posn2)[0][0]
    except:
        return "Name 2 not found"
    endpnt =  AusCities(cities[i,0], cities[i,1], cities[i,2], cities[i,5], cities[i,7])
    dist =  startpnt.distance_to(endpnt)
    return [[startpnt.name, startpnt.lat, startpnt.lon],[endpnt.name, endpnt.lat, endpnt.lon], 
            ['Distance', '', dist]]

Note that the position data is defined in the order of Latitude, Longitude, which is the standard order, rather than Longitude, Latitude as used in the Real Python code.

The Sphere_dist function has input of the latitude and longitude of two points and returns the spherical distance between them.

The City_dist function has input of a table of cities with position and other data (which must be in the same format as in the spreadsheet), and two city names. It returns the names of the two cities with their latitude and longitude, and the distance between them.

The results of the two functions are approximate, because the Earth is not an exact sphere. A more accurate approximation can be found with the Vincenty method; see Spherical Geometry and Vincenty’s Formulae.

Posted in Coordinate Geometry, Excel, Link to Python, Newton, PyXLL, UDFs | Tagged , , , , , , | 2 Comments

Ashley Hutchings 80th Birthday Bash

Ashley Hutchings 80th Birthday Bash – Who Knows Where The Time Goes? Encore #2. Kellie While – lead vocal. Richard Thompson – lead guitar. Plus the ensemble. Birmingham Town Hall – 18.04.2025.

The lead singer, Kellie While, has appeared here before accompanying Martin Simpson, but without mention either in my post or on You Tube:

Wikipedia on Kellie While

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

py_RC Design 2

Following the previous post, the py_RC Design spreadsheet has been updated with improved versions of the py_UMomp and py_UMomBiax functions. The revised version can be downloaded from:

py_RC Design.zip

The py_UMomp function is based on the Umom function, with the following changes:

  • The Eurocode 2 parabolic stress block is applied over the full range of axial loads and includes the required restrictions on maximum strain for neutral axis positions outside the concrete section. It may also be applied for calculations otherwise following the AS 3600 or AS 5100.5 codes.
  • The reinforcement may now be distributed over any number of layers.
  • The current version allows only for rectangular sections, but future versions will also have the option to specify any number of trapezoidal concrete layers.

py_UMomp input:

The results from the new function have been compared with the py_UmomBiax function (with the neutral axis angle set to zero) and also the VBA version of the UMom function.

With rectangular stress blocks used for all versions there is excellent agreement over most of the range, other than for high axial loads where the VBA version uses a different procedure for linear interpolation when the NA position is outside the concrete section:

Comparing results using the parabolic-linear stress block with the rectangular stress block, but applying Australian code stress limits in both cases, there are significant differences for axial loads above the balance load, especially for AS 3600:

The differences are increased when the maximum strain is increased to the Eurocode 2 limit of 0.0035:

The difference between the AS 3600 and AS 5100.5 results is largely due to the difference in the default reduction factors. In the graph below the results for both codes have the AS 3600 values: 0.85 for zero axial load, reducing to 0.65 for sections where the concrete compression controls the design capacity.

With 30 MPa concrete, results up to the balance load are now very close, with the AS 3600 results with a parabolic-linear stress block being higher than AS 5100.5 because of differences in the application of the transition of the reduction factors from 0.85 to 0.65. Above the balance load the two codes give identical results with the parabolic-linear stress block, but AS 3600 is significantly more conservative with the rectangular stress block.

With 50 MPa concrete the differences are similar but the differences between two stress blocks are increased, and the difference between AS 3600 and AS 5100.5 with the rectangular stress block is also increased.

Finally with 90 MPa concrete all four results are very close over the full range.

Posted in Beam Bending, Concrete, Excel, Link to Python, Newton, PyXLL, UDFs | Tagged , , , , , , , , , | Leave a comment