Project Euler: 10

check

Problem 10 of Project Euler is to find the sum of all primes under 2,000,000.

This isn’t a hard problem in most languages. I have recently been going through and re-solving problems in various languages for the fun of it. Just for fun, I decided to solve some problems using Bash scripting. Bash has all the constructs of a real programming language, however, the main draw back for problems like this is that it’s slow. VERY slow. Well, lets see what we can do to speed this problem up.

Brute Force

The brute force approach is pretty simple. Loop through every number, n, less than 2,000,000, and try dividing it by every number from 2 to n-1. If it is divisible, break out of the loop and try the next number.

sum=2
for (( i=3; i<2000000; i++ )); do
    prime=1
    for (( d=2; d<$i; d++ )); do
    if [ $(($i % $d)) -eq 0 ]; then
        prime=0
        break;
    fi
    done
    if [ $prime -eq 1 ]; then
    sum=$(($sum + $i))
    fi
done
echo $sum

This solution has no optimization, but will still yield a result in a relatively small time-frame for most languages. This would take incredibly long to process primes smaller than 2,000,000. For a benchmark, I have run this through primes less than 2,000, and it took 6.430 seconds. We need to optimize.

Optimize

There are a lot of simple changes that we can do to get our compute times down. For instance, if we only loop through odd numbers, we have half as many to process.

Another classic technique to optimize a prime test is to check for factors less than or equal to the square root of the number. The reason this works is that if a factor of our number, n, is larger than the square root of n, we will find the other factor has to be smaller than the square root of n, and will thus be detected.

sum=2
for (( i=3; i<2000; i+=2 )); do
    prime=1
    for (( d=2; d*d<=$i; d++ )); do
    if [ $(($i % $d)) -eq 0 ]; then
        prime=0
        break;
    fi
    done
    if [ $prime -eq 1 ]; then
    sum=$(($sum + $i))
    fi
done
echo $sum

These two changes alone bring our compute time down to .319 seconds for primes under 2,000! I still wouldn’t recommend trying computing all primes under 2,000,000. I tried, and killed it after 3 days without a result!

Sieve of Eratosthenes

Prime sieves are a super fast way of computing prime numbers without having to do any division. This method is attributed to Eratosthenes of Cyrene, a Greek mathematician living about 200 BC. (You can read more about the history on Wikipedia).

Basically, you have an array of every number you want to check. Start by making each element of the array equal to 1, indicating it is a prime.  Go through the array, starting at two. Whenever you hit a value that is 1, mark all the multiples of that value as 0’s. So, we look at element 2, which is prime because it’s value is 1. element whose index is a multiple of 2 should then get marked as 0, since that cannot be prime. You then advance to the next element, and repeat until you’ve visited every number in the array.

limit=2000
primes[$limit]=0
primeList[0]=2
primeIndex=0
sum=0
for (( i=2; i<$limit; i++ )); do
    if [[ ${primes[$i]} -eq \0 ]]; then
        sum=$(($sum+$i))
        primeList[$primeIndex]=$i
        primeIndex=$(($primeIndex+1))
        for (( n=$i; n<$limit; n+=$i )); do
            primes[$n]=1
        done
    fi
done

echo $sum

Implementing this method definitely improves results for our benchmark of primes less than 2,000; .186 seconds! However, this approach does not scale up to the desired 2,000,000 because Bash arrays are super slow as they get larger. Even though this approach worked great in our benchmark, it will still take upwards of 3 days to complete for all the primes under 2,000,000.

Combination of Methods

By using a sieve method combined with our optimized method, I was able to finally get the correct answer in 34 minutes using a Bash script.

I used a sieve to generate the primes between 1 and sqrt(2000000). This was fast since the array wasn’t nearly as big. I then used the array of primes that I generated to check every odd number between 2 and 2,000,000.

Another shortcut to reduce computation is to make a second array containing the squares of each prime. This means that we don’t need to perform multiplication in each loop when we are checking for primality.

limit=2000000
sum=0

# Generate primes up to sqrt of 2000000 using sieve
primeLimit=$(echo "scale=0;sqrt($limit*2)" | bc)
primes[$primeLimit]=0
primeList[0]=2
primeListSquared[0]=4
primeIndex=0
for (( i=2; i<$primeLimit; i++ )); do
    if [[ ${primes[$i]} -eq \0 ]]; then
        sum=$(($sum+$i))
        primeList[$primeIndex]=$i
        primeListSquared[$primeIndex]=$i*$i
        primeIndex=$(($primeIndex+1))
        for (( n=$i; n<$primeLimit; n+=$i )); do
            primes[$n]=1
        done
    fi
done

# Make sure primeLimit is an odd number
[[ $(($primeLimit % 2)) -eq 0 ]] && primeLimit=$(($primeLimit + 1))

# Use known primes from above to check each remaining number for primality
for (( i=$primeLimit; i<$limit; i+=2 )); do
    prime=1
    for (( p=0; ${primeListSquared[$p]}<=$i; p++ )); do
        if [[ $(($i % ${primeList[$p]})) -eq 0 ]]; then
            prime=0
            break
        fi
    done
    # Record!
    if [[ $prime -eq 1 ]]; then
        sum=$(($sum+$i))
    fi
done

echo $sum

34 minutes. Finally, a “reasonable” result (by some definitions of reasonable).

Multi-Processing

The last step I took was by splitting up the task of finding primes into multiple processes. This allowed all 8 of my cores to work on the solution. I moved my prime search method into a function that took a start and stopping point as parameters, and then echoed the sum for that portion back to a while loop that added them all together.

limit=2000000

# Generate primes up to sqrt of limit
primeLimit=$(echo "scale=0;sqrt($limit*2)" | bc)
primes[$primeLimit]=0
primeList[0]=2
primeListSquared[0]=4
primeIndex=0
for (( i=2; i<$primeLimit; i++ )); do
    if [[ ${primes[$i]} -eq \0 ]]; then
        primeList[$primeIndex]=$i
        primeListSquared[$primeIndex]=$i*$i
        primeIndex=$(($primeIndex+1))
        for (( n=$i; n<$primeLimit; n+=$i )); do
            primes[$n]=1
        done
    fi
done

function checkRange() {
    start=$1
    end=$2
    rangeSum=0

    # Make sure range starts at an odd number
    [[ $start -lt 2 ]] && start=3 && rangeSum=2
    [[ $(($start % 2)) -eq 0 ]] && start=$(($start + 1))

    # Use known primes to check all remaining numbers for primality
    for (( i=$start; i<$end; i+=2 )); do
        prime=1
        for (( p=0; ${primeListSquared[$p]}<=$i; p++ )); do
            if [[ $(($i % ${primeList[$p]})) -eq 0 ]]; then
                prime=0
                break
            fi
        done
        # Record!
        if [[ $prime -eq 1 ]]; then
            rangeSum=$(($rangeSum+$i))
        fi
    done
    echo $rangeSum
}

# Split up task into chunks, and check with multiple processes
sum=0
range=$(($limit / 50))
for (( start=0; start<$limit; start+=$range )); do
    checkRange $start $(($start+$range)) &
done | while read result; do
    sum=$(($sum+$result))
    echo $sum
done | tail -1

This method brought the compute time down to 3 minutes 22.075 seconds.

Conclusion

Project Euler gives the guideline that if your solution takes more than a minute, you’re probably doing it wrong. For this particular task though, I think 3 minutes is rather successful given the constraints of Bash. It was a fun project to try to optimize such a relatively simple algorithm within the constraints of this little scripting language.

If you can find any way to speed up the results, please let me know!

Visit the solution on my GitHub page.

Mac Pro 3,1 (Early 2008)

Mac Pro

I would generally say there are three types of computer user:

  • Consumer: People who wants something that will get them online, print documents, and just work without any hassle.
  • Enthusiast: (AKA Prosumer) People who like to buy professional products, enjoy tinkering, and dabble in the occasional professional-level project.
  • Professional: People who legitimately needs power for rendering, computing, and getting things done on a daily basis.

I’d put myself in the enthusiast column. I don’t do tons of 3D renderings, or giant Final Cut projects, and I don’t game. I do occasionally encode videos, CAD, and do lots of work with Adobe products. I certainly don’t need a top of the line machine, but I certainly enjoy having the best!

Computers are expensive. At least, they have been historically. For those of us who like Macs, they still are! Sure, the MacMini will only set you back $600, but that’s not the category of computer I am interested in.

Mac Pro 2013

The Mac Pro (and the Power Mac before it) is the line of computer that enthusiasts and professionals have loved owning. To do so, you have to be ready to be ready to part with beaucoup bucks! These machines costs thousands of dollars. For a point of reference, The current iteration of the MacPro is selling today at bargain basement entry price of $2999.00!

Professionals can usually justify the costs of such a workhorse (since it’s a business expense and ideally increases productivity, affecting the bottom line in a positive way). The rest of us have to find some other way to justify the cost…

From my anecdotal evidence (a sample size of 1…), I have found that buying the lowest end configuration of the high-end tier to be the least financially devastating for two reasons:

  1. More durable – They can withstand being used day in and day out for years
  2. More upgradable – Usefulness can be augmented through gradual upgrades over time

In October of 2008, I convinced my wife to let me buy a Mac Pro. Not just any Mac Pro, the lowest-end Mac Pro they made. A quad core 2.8 GHz with 2 Gigs of RAM. All for the paltry sum of $2,200 (Which was actually a lot at the time since we were poor college kids). In order to spend the money, I had to promise that this would be the only computer I purchase for the next 5 years.

I have been a huge proponent of the tower form factor due to the power, longevity, and upgradability. It’s now been over 7 years since that initial purchase, and I still use this computer every day. It’s still wonderfully capable. Over the years, I’ve been able to exploit it’s upgradability, and I believe I have saved a lot of money by doing so.

Since the initial purchase, I’ve upgraded the RAM, the processors, the graphics card, the optical drives, the hard drives, the wireless card, and installed an SSD. I’ve tabulated the cost over the years to maintain and upgrade:

Year Upgrade Cost
2008 Base Purchase $2,200.00
2009 Second 2.8 GHz CPU $350.00
2009 12GB RAM $250.00
2013 SSD Drive $120.00
2013 BluRay Drive $80.00
2015 PCIe SATA3 controller for SSD $45.00
2015 New AMD GPU $120.00
2016 2x 3.2 GHz CPUs $90.00

Total Cost: $3255.00
Cost per Year: $406.86

You can see how steeply component costs go down over time. The additional CPU was quite costly just a year or so after the initial purchase. The price has since plummeted to the point that I replaced both CPUs for under $100.

Slightly relevant XKCD.

To test my thesis that the route of gradually upgrading a low-end high-end Mac is cost effective, I am going to compare against two alternative philosophies I’ve entertained and do a really quick and dirty, back-of-the-envelope comparison of hypothetical cost.

Single High End Purchase

The first alternative is to buy the biggest, best computer upfront with the intent of using it for years on end, and never upgrade it.

Using the WayBack machine, I am able to look at Apple’s store site from the month when I originally purchased my Mac Pro. Matching the specs as closely as possible to my current specs, we have the following (ignoring sales tax):

Item Cost
Base System $2,799.00
3.2 GHz Upgrade $1,600.00
16 GB RAM $3,500.00
Upgraded Video Card $2,850.00

Total Cost: $10,749.00
Cost per Year: $1,535.57

The instant gratification of having the most up-to-date and fastest machine that Apple makes comes at a cost! Clearly this is not the most economical way to roll. And of course always remember to never buy RAM from Apple.

The reason this is so much more expensive is because, as computers depreciate over time, so do the cost for components. So in order to have the goods up front, you have to be ready to pay a premium for cutting-edge hardware.

Frequent Replacement

Another philosophy I’ve considered is to purchase a more modest machine, but instead of upgrading components, upgrade the entire system every few years. The thought process is that you can sell the old machine while it still has value to offset the cost of the newer machine.

Lets say that we replace the machine every three years. This gives the advantage of always being in warranty (assuming you purchase AppleCare). Based on evidence from a quick browsing of eBay, I will assume that a machine 3 years old will sell for about 1/3 of it’s original value (super rough estimation!).

Year Computer Buy Price Sell Price Net Cost
2008 Mac Pro 4 x 2.8 GHz $2,299.00 $765.00 $1,534.00
2011 Mac Pro 4 x 3.2 GHz Nahalem $2,499.00 $833.00 $1,666.00
2014 Mac Pro 4 x 3.7 GHz $2,999.00

Total Cost: $6,199.00
Cost per Year: $885.57

As you can see from this hypothetical, and very rough example, the difference in yearly cost to own an updated computer is about double of the approach of upgrading components.

Perhaps though we should consider the performance difference between the current low-end high-end computer versus the performance of the upgraded 7 year old computer?

Thanks to GeekBench, we have a huge pool of data to look at. Just doing a cursory search, I found these results:

Model Speed Single Core Score Multi Core Score
Mac Pro 2008 8x 3.2 GHz 1743 12149
Mac Pro 2013 4x 3.7 GHz 3235 12780

The single core performance of the new machine is tremendously better than the 7 year old Mac. The multi-core performance isn’t significantly better though. There are obviously many other improvements, including GPUs, bus speeds, and SSD performance to consider as well.

Conclusion

The question is, how much are you willing to spend? This is different for everyone. For me, I believe that there is value in upgrading and keeping products as long as possible. I am very happy with the gains I’ve been able to make over the years by performing gradual updates in order to keep the machine somewhat modern. There is no single right answer for everyone, but for me, this suits me just fine.

Microbolometer

My uncle is a physicist, and did some pioneering work in the field of thermal camera photo-receptors. He even wrote a book about the field in the 90’s, Fundamentals of Infrared and Visible Detector Operation and Testing (1990, James David Vincent).

512JMVWtmkL._SX331_BO1,204,203,200_

The publisher wanted an updated manuscript for a second edition of the book. He’s been working on that for the past couple of years, but ran into a problem. He needed a diagram of a microbolometer.

A microbolometer is a small sensor that is sensitive to infrared radiation. The radiation hits a sensitive material that then heats up, and its resistive value changes. By measuring the resistive change, a temperature can be determined. Thermal cameras, such as FLIR cameras, use an array of microbolometers to generate a thermal image.

Although diagrams exist, the most well known (and oft plagiarized) is in fact under copyright, and not available to be republished without authorization.

He asked me to create a new diagram that would convey the same information but be visually distinguishable from the other well-known image.

After some back and forth, this is the image we came up with. It had to be black and white for printing, and easily readable. He also wanted extraneous information removed, so that there were fewer parts of the image to focus on.

Microbolometer_3

For example, the thicknesses and dimensions, though accurate, are not labeled. To facilitate not having a labeled dimension between the ROIC and the temperature sensitive resistive element, there is a visible shadow. The image was created in Illustrator, and sent to the publisher as an EPS file.

The book, Fundamentals of Infrared and Visible Detector Operation and Testing second Edition (2015, John David Vincent, Steve Hodges, John Vampola, Mark Stegall, Greg Pierce), has been published, and you can see my diagram on page 94, Figure 3.3.

 

 

 

© 2007-2015 Michael Caldwell