Cortex-A9 programming in Python

We show here an example of how to run Python with Pynq. Python is running exclusively on the ARM Cortex-A9 processor. This example, which is based on calculating the factors and primes of integer numbers, give us a sense of the performance available when running on a 650MHz ARM Cortex-A9 dual core processor running Linux.

The factors and primes example

Code is provided in the cell below for a function to calculate factors and primes. It contains some sample functions to calculate the factors and primes of integers. We will use three functions from the factors_and_primes module to demonstrate Python programming.


In [1]:
"""Factors-and-primes functions.

Find factors or primes of integers, int ranges and int lists
and sets of integers with most factors in a given integer interval

"""

from pprint import pprint

def factorize(n):
    """Calculate all factors of integer n.
        
    Parameters
    ----------
    n : int
        integer to factorize.
    
    Returns
    -------
    list
        A sorted set of integer factors of n.
        
    """
    factors = []
    if isinstance(n, int) and n > 0:
        if n == 1:
            factors.append(n)
            return factors
        else:
            for x in range(1, int(n**0.5)+1):
                if n % x == 0:
                    factors.append(x)
                    factors.append(n//x)
            return sorted(set(factors))
    else:
        print('factorize ONLY computes with one integer argument > 0')


def primes_between(interval_min, interval_max):
    """Find all primes in the interval.
    
    The interval is defined by interval_min and interval_max.
    
    Parameters
    ----------
    interval_min : int
        Start of the integer range.
    interval_max : int
        End of the integer range.
    
    Returns
    -------
    list
        A sorted set of integer primes in original range.
        
    """
    primes = []
    if (isinstance(interval_min, int) and interval_min > 0 and 
       isinstance(interval_max, int) and interval_max > interval_min):
        if interval_min == 1:
            primes = [1]
        for i in range(interval_min, interval_max):
            if len(factorize(i)) == 2:
                primes.append(i)
        return sorted(primes)
    else:
        print('primes_between ONLY computes over the specified range.')

        
def primes_in(integer_list):
    """Calculate all unique prime numbers.
    
    Calculate the prime numbers in a list of integers.

    Parameters
    ----------
    integer_list : list
        A list of integers to test for primality.
        
    Returns
    -------
    list
        A sorted set of integer primes from original list.
    
    """
    primes = []
    try:
        for i in (integer_list):
            if len(factorize(i)) == 2:
                primes.append(i)
        return sorted(set(primes))
    except TypeError:
        print('primes_in ONLY computes over lists of integers.')


def get_ints_with_most_factors(interval_min, interval_max):
    """Finds the integers with the most factors.
    
    Find the integer or integers with the most factors in a given 
    integer range.
    
    The returned result is a list of tuples, where each tuple is:
    [no_with_most_factors (int), no_of_factors (int), 
    factors (int list)].
    
    Parameters
    ----------
    interval_min : int
        Start of the integer range.
    interval_max : int
        End of the integer range.
    
    Returns
    -------
    list
        A list of tuples showing the results.
        
    """
    max_no_of_factors = 1
    all_ints_with_most_factors = []
    
    #: Find the lowest number with most factors between i_min and i_max
    if interval_check(interval_min, interval_max):
        for i in range(interval_min, interval_max):
            factors_of_i = factorize(i)
            no_of_factors = len(factors_of_i) 
            if no_of_factors > max_no_of_factors:
                max_no_of_factors = no_of_factors
                results = (i, max_no_of_factors, factors_of_i,\
                            primes_in(factors_of_i))
        all_ints_with_most_factors.append(results)
    
        #: Find any larger numbers with an equal number of factors
        for i in range(all_ints_with_most_factors[0][0]+1, interval_max):
            factors_of_i = factorize(i)
            no_of_factors = len(factors_of_i) 
            if no_of_factors == max_no_of_factors:
                results = (i, max_no_of_factors, factors_of_i, \
                            primes_in(factors_of_i))
                all_ints_with_most_factors.append(results)
        return all_ints_with_most_factors       
    else:
        print_error_msg() 

            
def print_ints_with_most_factors(interval_min, interval_max):
    """Reports integers with most factors in a given integer range.
    
    The results can consist of the following: 
    1.  All the integers with the most factors
    2.  The number of factors
    3.  The actual factors of each of the integers
    4.  Any prime numbers in the list of factors
    
    Parameters
    ----------
    interval_min : int
        Start of the integer range.
    interval_max : int
        End of the integer range.
        
    Returns
    -------
    list
        A list of tuples showing the integers and factors.
        
    """
    if interval_check(interval_min, interval_max):
        print('\nBetween {} and {} the number/s with the most factors:\n'.
           format(interval_min, interval_max))
        for results in (get_ints_with_most_factors(
                            interval_min, interval_max)):
            print('{} ... with the following {} factors:'
                      .format(results[0], results[1]))
            pprint(results[2])
            print('The prime number factors of {} are:'
                      .format(results[0]))
            pprint(results[3])
    else:
        print_error_msg()

        
def interval_check(interval_min, interval_max):
    """Check type and range of integer interval.
    
    Parameters
    ----------
    interval_min : int
        Start of the integer range.
    interval_max : int
        End of the integer range.
        
    Returns
    -------
    None
    
    """
    if (isinstance(interval_min, int) and interval_min > 0 and 
       isinstance(interval_max, int) and interval_max > interval_min):
        return True
    else:
        return False

def print_error_msg():
    """Print invalid integer interval error message.
    
    Returns
    -------
    None
    
    """
    print('ints_with_most_factors ONLY computes over integer intervals where'
            ' interval_min <= int_with_most_factors < interval_max and'
            ' interval_min >= 1')

Next we will call the factorize() function to calculate the factors of an integer.


In [2]:
factorize(1066)


Out[2]:
[1, 2, 13, 26, 41, 82, 533, 1066]

The primes_between() function can tell us how many prime numbers there are in an integer range. Let’s try it for the interval 1 through 1066. We can also use one of Python’s built-in methods len() to count them all.


In [3]:
len(primes_between(1, 1066))


Out[3]:
180

Additionally, we can combine len() with another built-in method, sum(), to calculate the average of the 180 prime numbers.


In [4]:
primes_1066 = primes_between(1, 1066)
primes_1066_average = sum(primes_1066) / len(primes_1066)
primes_1066_average


Out[4]:
486.2055555555556

This result makes sense intuitively because prime numbers are known to become less frequent for larger number intervals. These examples demonstrate how Python treats functions as first-class objects so that functions may be passed as parameters to other functions. This is a key property of functional programming and demonstrates the power of Python.

In the next code snippet, we can use list comprehensions (a ‘Pythonic’ form of the map-filter-reduce template) to ‘mine’ the factors of 1066 to find those factors that end in the digit ‘3’.


In [5]:
primes_1066_ends3 = [x for x in primes_between(1, 1066) if str(x).endswith('3')]
primes_1066_ends3


Out[5]:
[3,
 13,
 23,
 43,
 53,
 73,
 83,
 103,
 113,
 163,
 173,
 193,
 223,
 233,
 263,
 283,
 293,
 313,
 353,
 373,
 383,
 433,
 443,
 463,
 503,
 523,
 563,
 593,
 613,
 643,
 653,
 673,
 683,
 733,
 743,
 773,
 823,
 853,
 863,
 883,
 953,
 983,
 1013,
 1033,
 1063]

This code tells Python to first convert each prime between 1 and 1066 to a string and then to return those numbers whose string representation end with the number ‘3’. It uses the built-in str() and endswith() methods to test each prime for inclusion in the list.

And because we really want to know what fraction of the 180 primes of 1066 end in a ‘3’, we can calculate ...


In [6]:
len(primes_1066_ends3) / len(primes_1066)


Out[6]:
0.25

These examples demonstrate how Python is a modern, multi-paradigmatic language. More simply, it continually integrates the best features of other leading languages, including functional programming constructs. Consider how many lines of code you would need to implement the list comprehension above in C and you get an appreciation of the power of productivity-layer languages. Higher levels of programming abstraction really do result in higher programmer productivity!

More intensive calculations

To stress the ARM processor a little more, we will run a script to determine the integer number, or numbers, that have the most factors between 1 and 1066, using the print_ints_with_most_factors() function from the factors_and_primes module.


In [7]:
print_ints_with_most_factors(1, 1066)


Between 1 and 1066 the number/s with the most factors:

840 ... with the following 32 factors:
[1,
 2,
 3,
 4,
 5,
 6,
 7,
 8,
 10,
 12,
 14,
 15,
 20,
 21,
 24,
 28,
 30,
 35,
 40,
 42,
 56,
 60,
 70,
 84,
 105,
 120,
 140,
 168,
 210,
 280,
 420,
 840]
The prime number factors of 840 are:
[2, 3, 5, 7]

The ARM processor remains quite responsive. Running this for much larger numbers, say 50,000, will demonstrate noticeably slower responses as we would expect.