CS357 NUMERICAL METHODS I

Summer 2014

Machine Problem 2

Posted: Friday  July 5th, 2014

Due Date: 9:00 P.M., Friday, July 18th, 2014

This MP has three parts.


Handin is available here.


You will have to submit (using svn) the following files:

newton.py

halley.py

newtoni.py

newtonm.py

intervalnewton.py

fractal.py

 parta.txt (answer to question Q1 in Part A)

 


 Preparation


a) Before you start working on the MP make sure you have a good understanding of Python and NumPy and SciPy and Matplotlib.

b) Download the following file and save it to your home directory:

 Download the tar file  mp2.tar.gz to your Linux directory and open the file using the Linux command  

tar -xzvf mp2.tar.gz

There is no demo for this MP.

(26 points) Part A: Finding Roots for a function f: R ->R

 

Note: Due to various forms of roundoff error incurred by writing code in different ways your results may differ slightly from the test results
shown below. For this reason, you must send us your .py files.

Section 1. Newton's method

Write a Python function named newton. This function should use newton's method to find a root of the function y = f(x)  given x = guess. The function named newton must have five input parameters in the order shown below.


Implementation (newton.py file)

 Use the following function header.

 def newton(f, df, x, tol, display=False):

where f is the function, f: R -> R.
            df is the derivative of the function
            x is an approximation of the root and is a scalar value
            tol specifies that the search will iterate until  | f(xk)| < tol. You may assume that tol > 0.
            display has the value True  or False, True means display the values of the individual iterations, False means don't display.
                         If the user doesn't submit a value for the fifth argument then the default value is display = False. ( This is the purpose of display=False as your last argument in the function definition line.)

                         Use the following code to display values

               if display:
                   print('iteration'
                              '\tx(iteration)'
                              '\t\terror(iteration)')

            and
              print('{0:5d} {1:25.17f} {2:25.17f}'.format(counter, x, error))  <=== where you use your own three variable names and error equals abs(f(x))
          

You must have at least one comment in your program. Copy the function definition line (header) and paste this as a comment immediately below the function definition line.

You must count the number of iterations in your code.

We assume that the
x is counted as the first iteration. If the numer of iterations equals twenty then terminate your program and display the message 'no convergence' and newton should return the last value of its iteration.


Tests


At the Linux prompt type:

$ module load python3

$ python3  newton.py

Your code will also be graded by passing the following tests:


root = 0 
 (grader:  1 point)
( result for running
    root = newton(np.sin, np.cos, 0, 1.0e-1)
    print ('root =', root, end='\n\n')   )

root = 0

(grader:  1 point)
( result for running
   root = newton(np.sin, np.cos, 0, 1.0e-1, False)
    print ('root =', root, end='\n\n')
  )

 

iteration      x(iteration)                            error(iteration)
    1       0.00000000000000000       0.00000000000000000
root = 0

(grader:  2 points)
( result for running
     root = newton(np.sin, np.cos, 0, 1.0e-1, True)
    print ('root =', root, end='\n\n'))

 

iteration      x(iteration)                           error(iteration)
    1       0.50000000000000000       0.47942553860420301
    2      -0.04630248984379048       0.04628594680720108
    3       0.00003311802132639       0.00003311802132034
    4      -0.00000000000001211       0.00000000000001211
root = -1.21079836153e-14

(grader:  2 points)
( result for running
    root = newton(np.sin, np.cos, 0.5, 1.0e-6, True)
    print ('root =', root, end='\n\n')
)



iteration       x(iteration)                         error(iteration)
    1       0.50000000000000000       0.46364760900080609
    2      -0.07955951125100758       0.07939228286534084
    3       0.00033530220400547       0.00033530219143974
    4      -0.00000000002513147       0.00000000002513147
    5       0.00000000000000000       0.00000000000000000
root = 0.0

(grader:  2 points)
( result for running
    root = newton(np.arctan, lambda x: 1/(1 + x**2), 0.5, 1.0e-11, True)
    print ('root =', root, end='\n\n'))


root = 0.652922853427
(grader:  2 points)
( result for running
    root = newton(lambda x: np.exp(-x**2) - x, lambda x: -2*x*np.exp(-x**2) -1, 1.5, 1.0e-3)
    print ('root =', root, end='\n\n')
)


iteration    x(iteration)        error(iteration)
    1       1.50000000000000000       5.06250000000000000
    2       1.12500000000000000       1.60180664062500000
    3       0.84375000000000000       0.50682163238525391
    4       0.63281250000000000       0.16036153212189674
    5       0.47460937500000000       0.05073939102294389
    6       0.35595703125000000       0.01605426044085334
    7       0.26696777343750000       0.00507966834261375
    8       0.20022583007812500       0.00160723881153013
    9       0.15016937255859375       0.00050854040521071
   10       0.11262702941894531       0.00016090536258620
   11       0.08447027206420898       0.00005091146238079
root = 0.08447027206420898

(grader:  2 points)
( result for running
    root = newton(lambda x: x**4, lambda x: 4*x**3, 1.5, 1.0e-4, 1)
    print ('root =', root, end='\n\n')
)


iteration       x(iteration)            error(iteration)
    1       1.10000000000000009       2.20999999999999996
    2       0.09545454545454568       1.00911157024793385
    3      -5.19036796536795286      27.93991961591786222
    4      -2.49885169885820835       7.24425981288655407
    5      -1.04933394312331441       2.10110172419072327
    6      -0.04817423702591594       1.00232075711302904
    7      10.35490445183653385     108.22404620666407027
    8       5.12916592812328087      27.30834311822075833
    9       2.46710122784825447       7.08658846845036461
   10       1.03088361576609566       2.06272102925497913
   11       0.03042100402787384       1.00092543748606388
   12     -16.42080191696688019     270.64273559626315091
   13      -8.17995177564034393      67.91161105180161428
   14      -4.02885083308708847      17.23163903526652518
   15      -1.89032054875004585       4.57331177702667446
   16      -0.68065487060600627       1.46329105287967920
   17       0.39425924231062481       1.15544035014734803
   18      -1.07107146671180553       2.14719408680417834
   19      -0.06871347588787180       1.00472154176859307
   20       7.24223629623630583      53.44998657052256164
No convergence
root = 7.242236296236306

(grader:  0 points) test case
( result for running
              root = newton(lambda x: x**2 + 1,
                      lambda x: 2*x,
                      1.1, 1.0e-4, True)
      print('root=', root, end='\n\n')  )

Section 2. Halley's method(as in Halley's comet)

 Let's try a higher order method. Remember from Newton's method we started with a Taylor Series,

f(xk+1)  =  f( xk) + f'( xk)* ( xk+1  -  xk ) + higher order terms

and we discarded the higher order terms and set  f(xk+1) = 0 and then solved for xk+1  to obtain Newton's formula,

xk+1   = xk  -   f(xk ) / f'(xk )

In Halley's Method we will keep one more term of the Taylor Series approximation,

f(xk+1)  =  f( xk) + f'( xk)* ( xk+1  -  xk ) + 1/2 *f''( xk)* ( xk+1  -  xk )2   + higher order terms

next we will discard the higher order terms, set f(xk+1) = 0  and then factor (xk+1  -  xk ) to obtain,

0 f( xk) + (f'( xk) + 1/2 *f''( xk)* ( xk+1  -  xk )) * ( xk+1  -  xk )

We next approximate the inner term ( xk+1  -  xk )  by using Newton's Method  xk+1   = xk  -   f(xk ) / f'(xk ) or xk+1  xk = - f(xk ) / f'(xk ) and obtain,

0 f(xk) + (f'( xk)  - 1/2 *f''( xk)*  f(xk ) / f'(xk )  ) * ( xk+1  -  xk )

and now, solving the above for xk+1  gives Halley's formula,


xk+1   = xk  -   2*f(xk )* f'(xk ) /  ( 2* (f'(xk ))2 - f''(xk )*f(xk ) )

Implementation (halley.py file)
Assuming that you correctly coded the newton function in Section 1, copy and paste the body of your function to the provided file halley.py and make the necessary changes to change the program to implement Halley's Method.

  Note that the function named halley has one more input parameter ddf  than your newton function, which will contain the second derivative of f.

 def halley(f, df, ddf, x, tol, display=False):

Don't forget to modify the comment and make it agree with the new function halley.

 
Tests


At the Linux prompt type:

$ module load python3

$ python3  halley.py

Your code will also be graded by passing the following tests:

iteration    x(iteration)        error(iteration)
    1       1.50000000000000000       0.98279372324732905
    2       0.20904059691894705       0.20607313667962263
    3       0.00284490827222281       0.00284490059716879
    4       0.00000000767499190       0.00000000767499190
root = 7.67499189723e-09

(grader:  2 points)
( result for running
   root = halley(np.arctan, lambda x: 1/(1+x**2), lambda x: -2*x/(1+x**2)**2, 1.5, 1.0e-6, True)
    print('root =', root, end='\n\n')
)



iteration    x(iteration)        error(iteration)
    1       1.50000000000000000       5.06250000000000000
    2       0.90000000000000002       0.65610000000000002
    3       0.54000000000000004       0.08503056000000002
    4       0.32400000000000001       0.01101996057600000
    5       0.19439999999999999       0.00142818689064960
    6       0.11664000000000001       0.00018509302102819
    7       0.06998400000000002       0.00002398805552525
root = 0.06998400000000002
(grader:  2 points)
( result for running
  root = halley(lambda x: x**4, lambda x: 4*x**3, lambda x: 12*x**2, 1.5, 1.0e-4, True)
    print('root =', root, end='\n\n')
)

 



 =====================================================IMPORTANT============================================================

 Q1 (grader:  2 points) Indicate the order of convergence for Halley's Method for roots f(r) = 0 where f'(r) is not equal to 0. Do NOT simply state the order, you must show by example why the order is the value you state. Hint: Try to find a function that takes several iterations to converge. Write your answer in a file named parta.txt. You will handin parta.txt along with your other files. You can use gedit to create this file. Make sure that this file is in the same directory as your .py files when you handin.

 

Section 3. Newton's Method using an approximation to the derivative.


The secant method approximates the derivative. One problem with the secant method is that we must compute the slope of the secant line which may involve subtracting terms that are almost equal. We can overcome this by using the following trick. Assuming that j represents the imaginary number, the square root of -1, every complex number can be written as  a+b*j (or just a+bj) where a and b are real numbers. Python implements complext numbers. Note that Python uses 'j' to represent the square root of -1, and so we have 'j' here, as opposed to the more commonly used 'i'.  For example,


>>> z = complex(2.0, 3.0)
>>> z
(2+3j)
>>> z.imag
3.0
>>> z.real
2.0
>>>

Consider the Taylor Series of the expression  f(x+hj) and forget that hj is complex. We write,

f(x+hj) = f(x)  + f'(x)(hj) + (f''(x)/2!)(hj)2 + (f'''(x)/3!)(hj)3 + (f''''(x)/4!)(hj)4 + (f''''(x)/5!)(hj)5 +...

and using the fact that j2 = -1, we can re-write the above simplifying the powers of j,            

f(x+hj) = f(x)  + (f'(x) h ) j   -  (f''(x)/2!)(h)2 -  ((f'''(x)/3!)(h)3 ) j+ (f''''(x)/4!)(h)4 + ((f''''(x)/5!)(h)5 ) j+...

and collecting real and imaginary parts,

f(x+hj) =  f(x) -  (f''(x)/2!)(h)2 + (f''''(x)/4!)(h)4 - ...                 (real numbers)
                + (f'(x) h -  ((f'''(x)/3!)(h)3  + (f''''(x)/5!)(h)5 + ... ) * j

so we can say that

(f(x+hj)/h).imag  = (f'(x) h -  ((f'''(x)/3!)(h)3  + (f''''(x)/5!)(h)5 + ... )  / h =   f'(x) -  ((f'''(x)/3!)(h)2  + (f''''(x)/5!)(h)4 + ...

and thus

(f(x+ih)/h).imag approx equals f'(x)




 Implementation (newtoni.py file)

Assuming that you correctly coded the newton function in Section 1, copy and paste the function body into the provided file newtoni.py and make the necessary changes to change the program to implement this method.



def newtoni(f, x, tol,  display=False, h = 1.0e-16):

Don't forget to modify the comment and make it agree with the new function newtoni.


Use a default value of h = 1.0e-16 (user can specify a different value by passing h while calling the function) for computing the derivative using (f(x+ih)/h).imag.
Use the Python function named complex to compute   x+hj ,e.g.  complex(x,h).
If z = a + bj is a complex number then use the Python expression z.imag to extract the imaginary part  b of the number.

The abs function works for both real and complex numbers. The abs(z) = sqt(a**2 + b**2)where z = a + bj

Tests


At the Linux prompt type:

$ module load python3

$ python3  newtoni.py

(amended: 

At the Linux prompt type:

$ python /usr/bin/easy_install --user pyinterval

)

Your code will also be graded by passing the following tests:

 

iteration    x(iteration)        error(iteration)
    1       0.50000000000000000       0.47942553860420301
    2      -0.04630248984379048       0.04628594680720108
    3       0.00003311802132639       0.00003311802132034
    4      -0.00000000000001211       0.00000000000001211
root = -1.2107976839e-14
(grader:  2 points)
( result for running
  root = newtoni(np.sin, 0.5, 1.0e-6, True)
    print ('root =', root, end='\n\n')
)



iteration    x(iteration)        error(iteration)
    1       1.50000000000000000       5.06250000000000000
    2       1.12500000000000000       1.60180664062500000
    3       0.84375000000000000       0.50682163238525391
    4       0.63281250000000000       0.16036153212189674
    5       0.47460937500000000       0.05073939102294389
    6       0.35595703125000000       0.01605426044085334
    7       0.26696777343750000       0.00507966834261375
    8       0.20022583007812500       0.00160723881153013
    9       0.15016937255859375       0.00050854040521071
   10       0.11262702941894531       0.00016090536258620
   11       0.08447027206420898       0.00005091146238079
root = 0.08447027206420898
(grader:  2 points)
( result for running
  root = newtoni(lambda x: x**4, 1.5, 1.0e-4, True)
    print ('root =', root, end='\n\n')
)

 


 

Section 4. Newton's Method using an approximation to the derivative and improving the order of convergence when f'(root) = 0.

We showed in lecture that Newton's method converges at quadratic order when f'(root) is not equal to zero. But if  f'(root) = 0 then as seen in the last example above in the Section 3 we didn't get quadratic order. It can be shown that given y = f(x) and f(root) = 0 , f'(root) = 0, then if we define g(x) = f(x)/f'(x) we have g(root) = 0 (in the limit since f(root)/f'(root) = 0/0) and g'(root) does not equal zero.

Let's modify the results from Section 3 and compute the roots of g(x) = f(x)/f'(x). To make this example simple we will always use g(x) to compute
a root whether or not f'(root) = 0.

Implementation (newtonm.py file)

Assuming that you correctly coded the newtoni  function in Section 3, copy and paste the body of your function into newtonm.py and make the necessary changes to change the program to implement this method.

  Name the function newtonm .

 def newtonm(f, df, x, tol, display=False, h=1.0e-16):

Don't forget to modify the comment and make it agree with the new function newtonm .


Use a fixed value of h = 1.0e-16.
Do not bother to trap the error when f'(xk) = 0.

Create an auxiliary function with the following code.


g = lambda x: f(x)/df(x)



Important: You will need to make several changes in your code since we are evaluating the function g and not f.

Tests

At the Linux prompt type:

$ module load python3

$ python3  newtonm.py

Your code will also be graded by passing the following tests:

 

iteration    x(iteration)        error(iteration)
    1       1.50000000000000000       0.37500000000000000
    2       0.00000000000000022       0.00000000000000006
root = 2.220446049250313e-16

(grader:  2 points)
( result for running
  root = newtonm(lambda x: x**4, lambda x: 4*x**3, 1.5, 1.0e-4, 1)
    print ('root =', root, end='\n\n')
)

iteration    x(iteration)        error(iteration)
    1       5.00000000000000000    1350.34732126256380980
    2       4.82355247086866257     513.35142443708184601
    3       4.63469110445832122     196.11807400937146895
    4       4.43039809285584951      75.40760621598593616
    5       4.20631358998010896      29.24268852070842684
    6       3.95591163446007688      11.46776359049319538
    7       3.66919413964655483       4.55891144393153080
    8       3.33120185308977623       1.83412663090670769
    9       2.92410558774785478       0.72987134867967063
   10       2.45598204476744719       0.25342468810375218
   11       2.08072509932272753       0.04049434791362784
   12       2.00052320660784932       0.00026160333972116
   13       2.00000000014326451      -0.00000000000000000
root = 2.00000000014

(grader:  2 points)
( result for running
  root = newtonm(lambda x: 1 - np.exp(-(x-2)**2),  lambda x: -2*(x-2)*np.exp(-(x-2)**2),  5, 1.0e-4, 1)
    print ('root =', root, end='\n\n')
)

 





(12 points) Part B: Interval Analysis

Python 2.6.6

EWS has installed Python 2.6.6 (not to be confused with Python 2.6.6.6 which is an evil version and should NEVER be used) and you will use this version of Python for this part of the MP. Python 2.6.6 is NOT compatible with Python 3. EWS has already installed an interval package that runs on Python 2.6.6. You can run Python 2.6.6 by typing

$ python

at the Linux prompt on any EWS Linux computer.

We will use the interval package found at http://pyinterval.googlecode.com/svn/trunk/html/index.html

 
To play with the interval package you can type,

>>> from interval import interval, inf, imath

at the Python 2.6.6 prompt.

For example, note that

>>> a = interval[1.,1.] / interval[3.,3.]

>>> a
interval([0.33333333333333331, 0.33333333333333337])

which is an interval containing the exact value 1/3.

>>> a[0].sup
0.33333333333333337

>>> a[0].inf
0.33333333333333331


>>> a.midpoint
interval([0.33333333333333337])

>>> 0 in a
False

>>> 0 not in a
True

and some more examples you may find helpful,

>>> b = interval([1.,5.])
>>> c = interval([-1., 4.])
>>> d = b & c
>>> d
interval([1.0, 4.0])

 

 

You can use following feature of this interval package,

An interval f(X)  is returned which takes a function f and interval X and returns an interval f(X) = { f(x) | x in X}



Interval Newton's method

We will use an interval version of Newton's method as follows,

Assuming Xk  is an interval that contains the root, then 

Xk+1  =   Xk   intersect  ( mid( Xk) -  f(mid(Xk)/f'(Xk)) )

is an interval that contains the root. This method may not always converge though even if the initial interval contains the root.
This method could be improved but it should suffice for showing how to manipulate interval valued variables in Python.


Implementation (intervalnewton.py)

 Use the following function header.

def intervalnewton(f, df, x, tol, display=False):

Copy and paste the function bdy from newton.py and modify accordingly.

Include the code,

if 0 not in f(x):
    print 'bad init'
    return interval[-inf, inf]

 

Since each iteration  Xk  will be an interval, and you are using python 2.x instead of 3.x, you will have to modify the statement that displays Xk


print '{0:5d}\t [{1:20.17f}, {2:20.17f}] {3:20.17f}'.format(counter, x[0].inf, x[0].sup, error)


Hint: Use the length function we provide you to compute the error.

Also use the following line to print the header.

print 'iteration\t\tx(iteration)\t\t\terror(iteration)'


Tests

At the Linux prompt type:

$ python  intervalnewton.py    <======================  this is different!!!!

Your code will also be graded by passing the following tests:

 

iteration                                   x(iteration)                                    error(iteration)
    1     [-3.00000000000000000,  2.00000000000000000]  2.35619449019234484
    2     [-0.03635239099919391,  2.00000000000000000]  1.14348510829434957
    3     [-0.03635239099919391,  0.20559683273510232]  0.23910768247220335
    4     [-0.00336735693839608,  0.00020112754174981]  0.00356847174992958
    5     [-0.00000000132255955,  0.00000001662850985]  0.00000001795106940
root = [ -1.32255954907e-09 , 1.66285098544e-08 ]


(grader:  4 points)
( result for running
  root = intervalnewton(imath.atan, lambda x: 1/(1+x**2), interval[-3,2], 1.0e-6, True)
    print 'root =', '[', root[0].inf, ',', root[0].sup, ']'
    print
)


bad init
root = [ -inf , inf ]


(grader:  4 points)
( result for running
  root = intervalnewton(imath.atan, lambda x: 1/(1+x**2), interval[3,4], 1.0e-6, True)
    print 'root =', '[', root[0].inf, ',', root[0].sup, ']'
    print
)


iteration        x(iteration)            error(iteration)
    1     [ 0.00000000000000000,  1.00000000000000000]  1.63212055882855767
    2     [ 0.59293359435713489,  0.77880078307140488]  0.34421040026991678
    3     [ 0.64874573377346678,  0.65670384047162333]  0.01474285786648699
    4     [ 0.65291763772910505,  0.65291965225839799]  0.00000373212805393
    5     [ 0.65291864041919867,  0.65291864041921088]  0.00000000000002287
root = [ 0.652918640419 , 0.652918640419 ]


(grader:  4 points)
( result for running
  root = intervalnewton(lambda x: imath.exp(-x**2) - x,  lambda x: -2*x*imath.exp(-x**2)-1,  interval[0,1], 1.0e-6, True)
    print 'root =', '[', root[0].inf, ',', root[0].sup, ']'
    print
)






 

(12 points) Part C: Fractals

 

 

Part 1: Programming the 'gasket' function.

Instructions:

In this portion of the MP, we will be writing Python code to recursively draw the Sierpinski Gasket (aka, the Sierpinski Triangle). The Sierpinski gasket is a pattern formed by recursively removing an upside-down triangle from the center of a larger triangle. This yields 3 smaller triangles, to which the process is applied again and again. See the image below for an example of a Sierpinski Gasket.

gasket

You can imagine that the triangle specified by points (0, 0), (.5, .433) and (1, 0) was drawn first, in red. Then the "upside-down" triangle with points (.25, .225), (.5, 0), and (.75, .225) was removed. This left three remaining triangles, to which the algortihm was applied over and over until the picture we see above was left.

We are now going to write a pair of functions to plot this gasket! The first function just sets up our graph, plots our big red triangle and then makes a call to our second (recursive) function to remove the upside-down white triangles.

Implementation (fractal.py file)

You will need to write code for an auxiliary function named gasket. Note that we have included the following line of code in the file named fractal.py ,

import matplotlib.pyplot as pt

The following code is not quite finished. Copy/paste it into the fractal.py file skeleton we gave you and then fix the code.

def gasket(x, y, tol):
# function to draw the Sierpinski Gasket
# x is a vector of the left base, top, and right base x coordinates of the biggest triangle
# y is a vector of the left base, top, and right base y coordinates of the biggest triangle
# tolerance is a scalar tolerance.  if the base plus the height is smaller than this, quit
 
    fill(x,y,'r')                     # color in the big triangle in red

          title('...')                      # set the title of the figure

    show(block=False)                 # show the figure
    remove_triangle(x,y, tol)         # make the call to our recursive removal function

            

That code isn't very interesting, it just sets the stage for the recursive remove_triangle function that we're about to write.

We will build this function incrementally. As you answer questions, be sure to type your answers into your edit windows when you are told to.


We have given you the code for the remov_triangle function.

def remove_triangle(x, y, tol):

Copy and paste this skeleton code into your editor window:

# This is a function to recursively remove smaller and smaller upside-down triangles
 
# if triangle too small just quit
if max(np.diff(x)+np.diff(y)) < tol:
    return

            
 # calculate the points of the upside-down triangle
    xr = # your code goes here
    yr = # your code goes here
    fill(xr,yr,'w')   # remove our new triangle by coloring it white
    draw()            # update the figure to show newly removed triangle
    time.sleep(0.1)   # added to slow things down so we can see what's going on...
 
    #make three recursive calls to remove each of our new (smaller) red triangles
    #  3 statements need to be written here, each calls remove_triangle 

Next, we need to calculate the x and y values of the triangle that we're going to remove. Note that when we call our gasket function, we pass in two vectors: one containing x values and another containing y values. These points are ordered as follows: (x[0], y[0]) defines the lower left corner of the triangle, (x[1], y[1]) defines the top of the triangle and (x[2], y[2]) defines the lower right corner of the triangle. The xr and yr values that we will calculate are as follows: (xr[0], yr[0]) is the top left corner of the upside-down triangle that we wish to remove, (xr[1], yr[1]) is the bottom point of the upside-down triangle and (xr[2], yr[2]) defines the upper right corner of the upside-down triangle.

Use your basic geometry skills to calculate the entries of the xr and yr vectors, then fill in those lines (xr = ??? and yr = ???) in your editor window. Hint: the triangles are all equilateral, thus the base of the upside down triangle will touch the midpoints of the two legs of the bigger triangle and the point of the upside-down triangle will touch the midpoint of the base of the larger triangle.

 

Alright, this fuction is almost done! In fact, if we ran gasket now, it would draw our big triangle and then remove one upside down triangle from the middle of it. However, we want to call our removal function on each of the newly defined smaller triangles and subdivide them further.

How many new triangles are created when we remove the upside-down triangle from the largest triangle?

Now, write one recursive call for each of the new triangles that are created when we remove the upside-down triangle from the largest triangle. . Hint: the x and y values that you'll need to pass to into your recursive calls to remove_triangle are for right side up triangle.

Tests

 

At the Linux prompt type:

$ python3  fractal.py  

 

(grader:  12 points) You should see a figure identical to that presented above.




 

That's it! You're done!!!