Warmup

Part A: First Steps (Up a Pyramid!)

One of the main activities this week will be building a pyramid with the picture module. This exercise will get you through some of the basic math involved. Here are some examples of the pyramids you’ll be drawing. All three images use a 400 x 400 pixel canvas. The first pyramid has 3 bricks in the base, the second has 17, and the last one has 123. The input of your program will be the number n of bricks in height and the width of the canvas.

400 x 400, 3 bricks

400 x 400, 17 bricks

400 x 400, 123 bricks

Notice that the pyramid doesn’t necessarily fill the entire canvas to the right and to the top; if the canvas width is not evenly divisible by the number of bricks, then there will be extra blank space.

Extra

Why is there so much blank space in the third example? Seems like you could fit lots of extra bricks both to the right and up top.

For each of the following questions, work on the question yourself for a few minutes, then discuss with your partner.

  1. Suppose you have a canvas that is 7 units wide and 7 units high. It may be useful to imagine that the canvas is made of graph paper and is 7 squares wide by 7 squares tall. Suppose that you want a pyramid that is 3 bricks tall (that is, n = 3). You’d like the bricks to be as large as possible, but they should all be of the same size, and they must use a whole number of units (i.e., squares on our imaginary graph paper). What is the side-length of each brick? That is, how many units wide and tall is each brick?

  2. How many bricks are:

    • in the 0th (bottom) row?
    • in the 1st (middle) row?
    • in the 2nd (top) row?
  3. More generally, suppose your canvas is size wide and size high, and that your pyramid is n bricks tall. What is the side-length s of a brick, in terms of size and n? Use Python’s integer division if necessary.

  4. If our pyramid is n bricks tall, then it is also n bricks wide at its base. Thus we have that

    • Row 0 contains n bricks
    • Row 1 (just above row 0) has n-1 bricks
    • Row 2 (just above row 1) has n-2 bricks
    • Row n-1 (the top row) has 1 brick.

    How many bricks does row r contain, in terms of n and r?

  5. If our pyramid is n bricks tall and each brick has side-length s, then the first brick of row 0 starts at x-coordinate 0. The first brick of row 1 starts at x-coordinate s//2, and the first brick of row 2 starts at x-coordinate s.

    What is the x-coordinate of the first brick of row r, in terms of s and r?

  6. If our pyramid is n bricks tall, our canvas is size wide and tall, and each brick has side-length s, then the bricks of row 0 have y-coordinate size - s. The bricks of row 1 have y-coordinate size - (2*s), and the bricks of row 2 have y-coordinate size - (3*s).

    What is the y-coordinate of row r, in terms of size, r, and s?

Part B: Cornerstone

To get you started with your pyramid, check out pyramid.py. It’ll give you some starter code for getting your canvas set up, with reminders of what things do. There is a ton more information of different commands (which you can and should use in Part 3 of this week’s lab) in the picture module documentation.

picture.py contains code for filling the canvas with the color "skyblue". You’ll draw your pyramid over top of it. Your job for now is just to draw the bottom-left square of the pyramid. You’ll draw the rest as part of the main lab.

  1. Put the following code where the comments say brick-drawing code goes here:

    picture.draw_filled_square(0,400,40)
    

    The canvas is 400 x 400 pixels, so this should draw a 40 x 40 brick in the bottom left corner, right? Run the code and see what happens. You should see… nothing. Read the documentation for picture.draw_filled_square and see if you and your partner can figure out what went wrong.

  2. What is the correct code to draw a 40 x 40 rectangle in the bottom left?

Part C: Bug Hunt

In a later part of this lab, you’ll be implementing a Monte Carlo method—that is, you’ll be using random number generation to estimate quantities that might be difficult to derive using formulas. This warmup gives you a trial run: we’ll look at a couple of programs which estimate what fraction of integers between 1 and 1,000,000,000 are divisible by 5 but not by 3. Note that you could compute this manually with a very long for loop, but Monte Carlo sampling will be faster. (There’s also an even simpler way of figuring this out, but that’s for you to think about.)

In montedivides1.py, montedivides2.py, and montedivides3.py, you’ll find three programs which all follow the same basic algorithm: (1) repeatedly generate random numbers between 1 and 1,000,000,000; (2) check if each number is divisible by 5 but not 3; and (3) count the number of times this property is satisfied, and divide by the number of random numbers overall. Unfortunately, each of these programs has a bug. This will be your chance to practice something called print debugging.

  1. Let’s first make sure that we understand the principles of our Monte Carlo method. On your own, go through the integers from 1 to 25 (inclusive) and determine how many are divisible by 5 but not 3. Then check your results with your partner. Dividing the count by 25 will give you the fraction of integers between 1 and 25 that are divisible by 5 but not by 3. This fraction is also a ballpark estimate of what you can expect as output from the Monte Carlo programs.

  2. Run the montedivides1.py program several times. Does the program return the same fraction every time it’s executed? Discuss with your partner why the value is changing.

  3. Run montedivides1.py one more time. It should give you a fraction that’s a bit lower than you expected based on your previous estimate (from Step 1). If you see why already, nice job! But it might not be so obvious, and it definitely won’t be obvious in more complicated programs. One way of helping you understand what’s going on is to temporarily add print() statements to track the values of the random integer x and the number of divisible integers count. You want to make sure count is incremented correctly based on each value of x.

    1. Add the line print(x, count) to the end, but still inside, of the for loop. This print() statement will yield a lot of output, but you should be able to predict generally what it will look like. Before running the new program, discuss with your partner what you would expect to be printed if the program was correct.

    2. Now run the program. Is count being incremented at the right times? Focus first on the numbers that a divisible by 5—these end in either a 0 or a 5. Now double-check whether these numbers are divisible by 3. If they are not divisible by 3, count should increase. (You can use the Console to check the divisibility of some of the printed numbers). Talk with your partner to identify what is going wrong.

  4. Now do the same thing for montedivides2.py. First identify the variables of interest. Then add a print() statements and analyze the output. Then try to pinpoint the bug and fix it.

  5. One more: montedivides3.py. Oh no! This one one doesn’t even run. You should be getting an error message like the following:

      File "montedivides3.py", line 11
        is_good = (x % 3 != 0) and (x % 5 == 0)
        ^
    SyntaxError: invalid syntax
    

    As you may have heard in class, the Python interpreter’s approach to catching errors is generally as follows: it reads your code under the assumption that it is correct for as long as possible. Only when it has read enough that what you typed earlier could not possibly be correct under any circumstances will it crash, and it will tell you the line where it realized all hope was lost. That means that the real bug is often earlier in the code.

    Take a look at the line highlighted by the error message: it’s a little complex, but it’s correct. That means the error is earlier. With your partner, find it and fix it.