diff --git a/autolab/__pycache__/autolab.cpython-38.pyc b/autolab/__pycache__/autolab.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..86ad4a1d06d4f3267ad22b37b7839b186984db04
Binary files /dev/null and b/autolab/__pycache__/autolab.cpython-38.pyc differ
diff --git a/autolab/autolab.py b/autolab/autolab.py
index 801602c21304c2bad6f705b37c9baaacd5f4e9e7..6f9fc05652e3aba3599a51ac30b120ede1707d19 100644
--- a/autolab/autolab.py
+++ b/autolab/autolab.py
@@ -18,8 +18,10 @@ import inspect
 from unitgrade_private2 import docker_helpers
 
 COURSES_BASE = "/home/tuhe/Autolab/courses/AutoPopulated"
-TEMPLATE_BASE = "/home/tuhe/Documents/unitgrade_private/autolab/lab_template"
+
 CURDIR = os.path.dirname(__file__)
+TEMPLATE_BASE = CURDIR + "/lab_template"
+
 
 def jj(source, dest, data):
     if os.path.exists(dest) and os.path.samefile(source, dest):
@@ -34,7 +36,7 @@ def jj(source, dest, data):
 
 
 def docker_build_image():
-    os.system("cd docker_tango_python && docker build --tag tango_python_tue .")
+    os.system(f"cd {CURDIR}/docker_tango_python && docker build --tag tango_python_tue .")
     pass
 
 def jj_handout(source, dest, data):
@@ -101,21 +103,27 @@ import inspect
 #         return 3
 
 
-def deploy_assignment(base_name):
+def deploy_assignment(base_name, INSTRUCTOR_BASE, INSTRUCTOR_GRADE_FILE, STUDENT_BASE, STUDENT_GRADE_FILE,
+                      output_tar=None,
+                      COURSES_BASE=None):
 
-    docker_build_image()
-    LAB_DEST = os.path.join(COURSES_BASE, base_name)
+    assert os.path.isfile(INSTRUCTOR_GRADE_FILE)
+    assert os.path.isfile(STUDENT_GRADE_FILE)
+    assert os.path.isdir(INSTRUCTOR_BASE)
+    assert os.path.isdir(STUDENT_BASE)
 
-    INSTRUCTOR_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101/report1_grade.py"
-    INSTRUCTOR_BASE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor"
+    deploy_directly = COURSES_BASE != None
 
-    STUDENT_BASE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students"
-    STUDENT_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students/cs101/report1_grade.py"
+    if COURSES_BASE == None:
+        COURSES_BASE = os.getcwd() + "/tmp"
+        if not os.path.exists(COURSES_BASE):
+            os.mkdir(COURSES_BASE)
+
+    LAB_DEST = os.path.join(COURSES_BASE, base_name)
 
     # STUDENT_HANDOUT_DIR = os.path.dirname(STUDENT_GRADE_FILE) #"/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students/cs101"
     # INSTRUCTOR_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101/report1.py"
     # Make instructor token file.
-
     # Get the instructor result file.
     run_relative(INSTRUCTOR_GRADE_FILE, INSTRUCTOR_BASE)
     f = glob.glob(os.path.dirname(INSTRUCTOR_GRADE_FILE) + "/*.token")[0]
@@ -185,13 +193,30 @@ def deploy_assignment(base_name):
     # Copy the student grade file to remove.
     shutil.copyfile(INSTRUCTOR_GRADE_FILE, f"{LAB_DEST}/src/{os.path.basename(INSTRUCTOR_GRADE_FILE)}")
     shutil.copyfile(STUDENT_TOKEN_FILE, f"{LAB_DEST}/src/{handin_filename}")
-    shutil.make_archive(LAB_DEST + '/src/student_sources', 'zip', root_dir=STUDENT_BASE, base_dir='cs101')
+    shutil.make_archive(LAB_DEST + '/src/student_sources', 'zip', root_dir=STUDENT_BASE, base_dir=base_name)
     shutil.copyfile(docker_helpers.__file__, f"{LAB_DEST}/src/{os.path.basename(docker_helpers.__file__)}")
     os.mkdir(LAB_DEST +"/handin")
     os.mkdir(LAB_DEST +"/test-autograder") # Otherwise make clean will screw up.
     os.system(f"cd {LAB_DEST} && make && cd {CURDIR}")
-    print("Deploy", base_name)
+
+    if deploy_directly:
+        return None
+
+    if output_tar is None:
+        output_tar = os.getcwd() + "/" + base_name  + ".tar"
+
+    shutil.make_archive(output_tar[:-4], 'tar', root_dir=COURSES_BASE, base_dir=base_name)
+    return output_tar
+
 
 if __name__ == "__main__":
     print("Deploying to", COURSES_BASE)
-    deploy_assignment("hello4")
+    docker_build_image()
+
+    INSTRUCTOR_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101/report1_grade.py"
+    INSTRUCTOR_BASE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor"
+
+    STUDENT_BASE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students"
+    STUDENT_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students/cs101/report1_grade.py"
+
+    output_tar = deploy_assignment("hello4", INSTRUCTOR_BASE, INSTRUCTOR_GRADE_FILE, STUDENT_BASE, STUDENT_GRADE_FILE=STUDENT_GRADE_FILE)
diff --git a/autolab/docker_tango_python/Dockerfile b/autolab/docker_tango_python/Dockerfile
index 47ede9fd7e2348227c635695e95d324a0896c4da..e081c74465e0b3f3a0e933f16f5a1f180c8740c3 100644
--- a/autolab/docker_tango_python/Dockerfile
+++ b/autolab/docker_tango_python/Dockerfile
@@ -26,8 +26,8 @@ WORKDIR Tango/autodriver
 RUN make clean && make
 RUN cp autodriver /usr/bin/autodriver
 RUN chmod +s /usr/bin/autodriver
-# Do the python stuff.
 
+# Do the python stuff.
 COPY requirements.txt requirements.txt
 RUN pip3 install -r requirements.txt
 
diff --git a/autolab/lab_template/Makefile b/autolab/lab_template/Makefile
index 48023fea7db9f22c976bdfcfe840ebec896302fd..2e2158af2ed5e96f1412a880288e75c5d7a0f1df 100644
--- a/autolab/lab_template/Makefile
+++ b/autolab/lab_template/Makefile
@@ -3,25 +3,23 @@
 #
 
 # Get the name of the lab directory
-LAB = $(notdir $(PWD))
+# LAB = $(notdir $(PWD)) # Fail on windows for some reason...
 
 all: handout handout-tarfile
 
 handout: 
 	# Rebuild the handout directory that students download
-	(rm -rf $(LAB)-handout; mkdir $(LAB)-handout)
-	cp -p src/Makefile-handout $(LAB)-handout/Makefile
-	cp -p src/README-handout $(LAB)-handout/README
-	#	cp -p src/hello3.c-handout $(LAB)-handout/hello3.c
-	#cp -p src/driver.sh $(LAB)-handout
+	(rm -rf {{ base_name }}-handout; mkdir {{ base_name }}-handout)
+	cp -p src/Makefile-handout {{ base_name }}-handout/Makefile
+	cp -p src/README-handout {{ base_name }}-handout/README
 {%- for f in src_files_to_handout %}
-	cp -p src/{{f}} $(LAB)-handout
+	cp -p src/{{f}} {{ base_name }}-handout
 {% endfor %}
 
 handout-tarfile: handout
 	# Build *-handout.tar and autograde.tar
-	tar cvf $(LAB)-handout.tar $(LAB)-handout
-	cp -p $(LAB)-handout.tar autograde.tar
+	tar cvf {{ base_name }}-handout.tar {{ base_name }}-handout
+	cp -p {{ base_name }}-handout.tar autograde.tar
 
 clean:
 	# Clean the entire lab directory tree.  Note that you can run
@@ -30,7 +28,7 @@ clean:
 	rm -f *~ *.tar
 	(cd src; make clean)
 	(cd test-autograder; make clean)
-	rm -rf $(LAB)-handout
+	rm -rf {{ base_name }}-handout
 	rm -f autograde.tar
 #
 # CAREFULL!!! This will delete all student records in the logfile and
diff --git a/autolab/lab_template/hello/README b/autolab/lab_template/hello/README
deleted file mode 100644
index 9b6008c7a88167be1d4e58adb10ea0d89882dcb0..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/README
+++ /dev/null
@@ -1,32 +0,0 @@
-This is an example of the simplest possible autograded lab, called "hello." It uses 
-the recommended file format that we've found helpful in the past. 
-
-To build the lab:
-linux> make clean
-linux> make 
-
-To test offline:
-linux> cd test-autograder
-linux> make clean
-linux> make
-
-# Basic files created by the lab author
-Makefile                Builds the lab from src/
-README                  
-autograde-Makefile      Makefile that runs the autograder 
-src/                    Contains all src files and solutions         
-test-autograder/        For testing autograder offline
-writeup/                Lab writeup that students view from Autolab    
-
-# Files created by running make
-hello-handout/          The directory that is handed out to students, created
-                        using files from src/. 
-hello-handout.tar       Archive of hello-handout directory
-autograde.tar           File that is copied to the autograding instance 
-                        (along with autograde-Makefile and student handin file)
-
-# Files created and managed by Autolab
-handin/    All students handin files
-hello.rb   Config file
-hello.yml  Database properties that persist from semester to semester
-log.txt    Log of autograded submissions
diff --git a/autolab/lab_template/hello/autograde-Makefile b/autolab/lab_template/hello/autograde-Makefile
deleted file mode 100644
index 3b7ebba5fd50934d9ecf768e47d22b9355e5af13..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/autograde-Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-all:
-	tar xvf autograde.tar
-	cp hello.c hello-handout
-	(cd hello-handout; ./driver.sh)
-
-clean:
-	rm -rf *~ hello-handout
diff --git a/autolab/lab_template/hello/autograde.tar b/autolab/lab_template/hello/autograde.tar
deleted file mode 100644
index 978f9540c094621f867623c1448d6ee704fdd40d..0000000000000000000000000000000000000000
Binary files a/autolab/lab_template/hello/autograde.tar and /dev/null differ
diff --git a/autolab/lab_template/hello/hello-handout.tar b/autolab/lab_template/hello/hello-handout.tar
deleted file mode 100644
index 978f9540c094621f867623c1448d6ee704fdd40d..0000000000000000000000000000000000000000
Binary files a/autolab/lab_template/hello/hello-handout.tar and /dev/null differ
diff --git a/autolab/lab_template/hello/hello-handout/Makefile b/autolab/lab_template/hello/hello-handout/Makefile
deleted file mode 100644
index f68bf2618e4c5e8b5e81373621f8b1f85f21b750..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/hello-handout/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
-# Student makefile for the Hello Lab
-all: 
-	gcc hello.c -o hello
-
-clean:
-	rm -rf *~ hello
-
-
diff --git a/autolab/lab_template/hello/hello-handout/README b/autolab/lab_template/hello/hello-handout/README
deleted file mode 100644
index c95cd257e5894556cce64721aec4971666bdd652..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/hello-handout/README
+++ /dev/null
@@ -1,15 +0,0 @@
-For this lab, you should write a tiny C program, called "hello.c",
-that prints "hello, world" to stdout and then indicates success by
-exiting with a status of zero.
-
-To test your work: 
-linux> make clean; make; ./hello
-
-To run the same autograder that Autolab will use when you submit:
-linux> ./driver.sh
-
-Files:
-README          This file
-Makefile        Compiles hello.c
-driver.sh       Autolab autograder
-hello.c         Empty C file that you will edit
diff --git a/autolab/lab_template/hello/hello-handout/driver.sh b/autolab/lab_template/hello/hello-handout/driver.sh
deleted file mode 100755
index 38ec60d0c32a9926c6051d9948eca0055d459cec..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/hello-handout/driver.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-
-# driver.sh - The simplest autograder we could think of. It checks
-#   that students can write a C program that compiles, and then
-#   executes with an exit status of zero.
-#   Usage: ./driver.sh
-
-# Compile the code
-echo "Compiling hello.c"
-(make clean; make)
-status=$?
-if [ ${status} -ne 0 ]; then
-    echo "Failure: Unable to compile hello.c (return status = ${status})"
-    echo "{\"scores\": {\"Correctness\": 0}}"
-    exit
-fi
-
-# Run the code
-echo "Running ./hello"
-./hello
-status=$?
-if [ ${status} -eq 0 ]; then
-    echo "Success: ./hello runs with an exit status of 0"
-    echo "{\"scores\": {\"Correctness\": 100}}"
-else
-    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
-    echo "{\"scores\": {\"Correctness\": 0}}"
-fi
-
-exit
-
diff --git a/autolab/lab_template/hello/hello-handout/hello.c b/autolab/lab_template/hello/hello-handout/hello.c
deleted file mode 100644
index f63ff42b7d28ef9ce023a64e88a10176920c5b9e..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/hello-handout/hello.c
+++ /dev/null
@@ -1,3 +0,0 @@
-/* 
- * Hello Lab 
- */
diff --git a/autolab/lab_template/hello/hello.yml b/autolab/lab_template/hello/hello.yml
deleted file mode 100644
index 6e4734e957150485f2b3e0ff31ac5c5ea6b52394..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/hello.yml
+++ /dev/null
@@ -1,24 +0,0 @@
----
-general:
-  name: hello
-  description: ''
-  display_name: Hello
-  handin_filename: hello.c
-  handin_directory: handin
-  max_grace_days: 0
-  handout: hello-handout.tar
-  writeup: writeup/hello.html
-  max_submissions: -1
-  disable_handins: false
-  max_size: 2
-  has_svn: false
-  category_name: Lab
-problems:
-- name: Correctness
-  description: ''
-  max_score: 100.0
-  optional: false
-autograder:
-  autograde_timeout: 180
-  autograde_image: autograding_image
-  release_score: true
diff --git a/autolab/lab_template/hello/src/Makefile b/autolab/lab_template/hello/src/Makefile
deleted file mode 100644
index c27bc04692a6f5946c1bfa90844cb823f6200628..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/src/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-# Makefile for the Hello Lab
-all: 
-	gcc hello.c -o hello
-
-clean:
-	rm -rf *~ hello
-
diff --git a/autolab/lab_template/hello/src/Makefile-handout b/autolab/lab_template/hello/src/Makefile-handout
deleted file mode 100644
index f68bf2618e4c5e8b5e81373621f8b1f85f21b750..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/src/Makefile-handout
+++ /dev/null
@@ -1,8 +0,0 @@
-# Student makefile for the Hello Lab
-all: 
-	gcc hello.c -o hello
-
-clean:
-	rm -rf *~ hello
-
-
diff --git a/autolab/lab_template/hello/src/README-handout b/autolab/lab_template/hello/src/README-handout
deleted file mode 100644
index c95cd257e5894556cce64721aec4971666bdd652..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/src/README-handout
+++ /dev/null
@@ -1,15 +0,0 @@
-For this lab, you should write a tiny C program, called "hello.c",
-that prints "hello, world" to stdout and then indicates success by
-exiting with a status of zero.
-
-To test your work: 
-linux> make clean; make; ./hello
-
-To run the same autograder that Autolab will use when you submit:
-linux> ./driver.sh
-
-Files:
-README          This file
-Makefile        Compiles hello.c
-driver.sh       Autolab autograder
-hello.c         Empty C file that you will edit
diff --git a/autolab/lab_template/hello/src/driver.sh b/autolab/lab_template/hello/src/driver.sh
deleted file mode 100755
index 38ec60d0c32a9926c6051d9948eca0055d459cec..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/src/driver.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-
-# driver.sh - The simplest autograder we could think of. It checks
-#   that students can write a C program that compiles, and then
-#   executes with an exit status of zero.
-#   Usage: ./driver.sh
-
-# Compile the code
-echo "Compiling hello.c"
-(make clean; make)
-status=$?
-if [ ${status} -ne 0 ]; then
-    echo "Failure: Unable to compile hello.c (return status = ${status})"
-    echo "{\"scores\": {\"Correctness\": 0}}"
-    exit
-fi
-
-# Run the code
-echo "Running ./hello"
-./hello
-status=$?
-if [ ${status} -eq 0 ]; then
-    echo "Success: ./hello runs with an exit status of 0"
-    echo "{\"scores\": {\"Correctness\": 100}}"
-else
-    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
-    echo "{\"scores\": {\"Correctness\": 0}}"
-fi
-
-exit
-
diff --git a/autolab/lab_template/hello/src/hello.c b/autolab/lab_template/hello/src/hello.c
deleted file mode 100644
index 8863e27128a7b15bda1051ade030d3ea5acb1c78..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/src/hello.c
+++ /dev/null
@@ -1,9 +0,0 @@
-/* Solution for the Hello Lab */
-#include <stdio.h>
-
-int main()
-{
-    printf("Hello, world\n");
-    return 0; /* important to return zero here */
-}
-
diff --git a/autolab/lab_template/hello/src/hello.c-handout b/autolab/lab_template/hello/src/hello.c-handout
deleted file mode 100644
index f63ff42b7d28ef9ce023a64e88a10176920c5b9e..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/src/hello.c-handout
+++ /dev/null
@@ -1,3 +0,0 @@
-/* 
- * Hello Lab 
- */
diff --git a/autolab/lab_template/hello/test-autograder/Makefile b/autolab/lab_template/hello/test-autograder/Makefile
deleted file mode 100644
index 3b7ebba5fd50934d9ecf768e47d22b9355e5af13..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/test-autograder/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-all:
-	tar xvf autograde.tar
-	cp hello.c hello-handout
-	(cd hello-handout; ./driver.sh)
-
-clean:
-	rm -rf *~ hello-handout
diff --git a/autolab/lab_template/hello/test-autograder/autograde.tar b/autolab/lab_template/hello/test-autograder/autograde.tar
deleted file mode 100644
index fa96fcf301077c37769d440fd31ff170f7c8552b..0000000000000000000000000000000000000000
Binary files a/autolab/lab_template/hello/test-autograder/autograde.tar and /dev/null differ
diff --git a/autolab/lab_template/hello/test-autograder/hello.c b/autolab/lab_template/hello/test-autograder/hello.c
deleted file mode 100644
index fa3a965c09401f6f46ff8241e3b495afeb1b71c6..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/test-autograder/hello.c
+++ /dev/null
@@ -1,9 +0,0 @@
-/* Solution for the Hello Lab */
-#include <stdio.h>
-
-int main()
-{
-    printf("Hello, world\n");
-    return 0;
-}
-
diff --git a/autolab/lab_template/hello/writeup/README b/autolab/lab_template/hello/writeup/README
deleted file mode 100644
index 123c9e6e63acf3fbc32bae73cb70911406beed81..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/writeup/README
+++ /dev/null
@@ -1,2 +0,0 @@
-Contains the HTML writeup for the Hello Lab that students using the
-"View writeup" link.
diff --git a/autolab/lab_template/hello/writeup/hello.html b/autolab/lab_template/hello/writeup/hello.html
deleted file mode 100644
index 0dcea4bc5ef92e6fc9250db1372c29289d85a2bf..0000000000000000000000000000000000000000
--- a/autolab/lab_template/hello/writeup/hello.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<h2>Hello Lab</h2>
-
-In this lab, you will write a C program, called <kbd>hello.c</kbd>, that prints "Hello, world" and
-then exits with a status of zero (the conventional way to indicate a
-successful termination).
-
-<p>
-Download the lab materials from Autolab using the "Download handout" link.
-
-<p>
-Submit your hello.c file to Autolab using the "Submit file" link.
-
-
diff --git a/autolab/lab_template/src/driver_python.py b/autolab/lab_template/src/driver_python.py
index 93ecb2c89dfc178f74f4507558d32ebddbe70ea6..1046485f3edb4a05bd67d6d134b38e0edfa95f4a 100644
--- a/autolab/lab_template/src/driver_python.py
+++ b/autolab/lab_template/src/driver_python.py
@@ -7,33 +7,36 @@ import subprocess
 import docker_helpers
 import time
 
-print("="*10)
+verbose = False
 tag = "[driver_python.py]"
-print(tag, "I am going to have a chamor of a time evaluating your stuff")
+
+if not verbose:
+    print("="*10)
+    print(tag, "Starting unitgrade evaluation...")
 
 sys.stderr = sys.stdout
 wdir = os.getcwd()
 
-# print(os.system("cd"))
 def pfiles():
     print("> Files in dir:")
     for f in glob.glob(wdir + "/*"):
         print(f)
     print("---")
 
-# shutil.unpack_archive("student_sources.zip")
 student_token_file = '{{handin_filename}}'
 instructor_grade_script = '{{instructor_grade_file}}'
 grade_file_relative_destination = "{{grade_file_relative_destination}}"
 with open(student_token_file, 'rb') as f:
     results = pickle.load(f)
 sources = results['sources'][0]
-pfiles()
-
 host_tmp_dir = wdir + "/tmp"
-print(f"{host_tmp_dir=}")
-print(f"{student_token_file=}")
-print(f"{instructor_grade_script=}")
+
+if not verbose:
+    pfiles()
+    print(f"{host_tmp_dir=}")
+    print(f"{student_token_file=}")
+    print(f"{instructor_grade_script=}")
+
 command, token = docker_helpers.student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination)
 command = f"cd tmp && {command} --noprogress --autolab"
 
@@ -42,8 +45,9 @@ def rcom(cm):
     # start = time.time()
     rs = subprocess.run(cm, capture_output=True, text=True, shell=True)
     print(rs.stdout)
+
     if len(rs.stderr) > 0:
-        print("There were errors in executing the file:")
+        print(tag, "There were errors in executing the file:")
         print(rs.stderr)
     # print(rs)
     # print("result of running command was", rs.stdout, "err", rs.stderr, "time", time.time() - start)
@@ -54,8 +58,6 @@ rcom(command)
 # for f in glob.glob(host_tmp_dir + "/cs101/*"):
 #     print("cs101/", f)
 # print("---")
-
-print(f"{token=}")
 ls = glob.glob(token)
 # print(ls)
 f = ls[0]
@@ -63,7 +65,9 @@ with open(f, 'rb') as f:
     results = pickle.load(f)
 # print("results")
 # print(results.keys())
-print(results['total'])
+if verbose:
+    print(f"{token=}")
+    print(results['total'])
 # if os.path.exists(host_tmp_dir):
 #     shutil.rmtree(host_tmp_dir)
 # with io.BytesIO(sources['zipfile']) as zb:
diff --git a/examples/02471/instructor/02471/report1.py b/examples/02471/instructor/02471/report1.py
index 67e5ab7f8951c74daf1d11626cda574026eb4c72..1ed131f5a1e075bba9bc14c97d92df21e7670081 100644
--- a/examples/02471/instructor/02471/report1.py
+++ b/examples/02471/instructor/02471/report1.py
@@ -2,10 +2,103 @@ from testbook import testbook
 
 nb = 'week02/Week_2_sol.ipynb'
 import asyncio
+import io, os, sys, types
+# from IPython.nbformat import current
+from IPython.core.interactiveshell import InteractiveShell
 # from nbconvert import W
+import asyncio
+import sys
+if sys.platform == 'win32' and sys.version_info > (3, 8, 0, 'alpha', 3) :
+    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+
+
+def find_notebook(fullname, path=None):
+    """find a notebook, given its fully qualified name and an optional path
+
+    This turns "foo.bar" into "foo/bar.ipynb"
+    and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar
+    does not exist.
+    """
+    name = fullname.rsplit('.', 1)[-1]
+    if not path:
+        path = ['']
+    for d in path:
+        nb_path = os.path.join(d, name + ".ipynb")
+        if os.path.isfile(nb_path):
+            return nb_path
+        # let import Notebook_Name find "Notebook Name.ipynb"
+        nb_path = nb_path.replace("_", " ")
+        if os.path.isfile(nb_path):
+            return nb_path
+
+
+class NotebookLoader(object):
+    """Module Loader for IPython Notebooks"""
+
+    def __init__(self, path=None):
+        self.shell = InteractiveShell.instance()
+        self.path = path
+
+    def load_module(self, fullname):
+        """import a notebook as a module"""
+        path = find_notebook(fullname, self.path)
+
+        print("importing IPython notebook from %s" % path)
+
+        # load the notebook object
+        with io.open(path, 'r', encoding='utf-8') as f:
+            nb = current.read(f, 'json')
+
+        # create the module and add it to sys.modules
+        # if name in sys.modules:
+        #    return sys.modules[name]
+        mod = types.ModuleType(fullname)
+        mod.__file__ = path
+        mod.__loader__ = self
+        sys.modules[fullname] = mod
+
+        # extra work to ensure that magics that would affect the user_ns
+        # actually affect the notebook module's ns
+        save_user_ns = self.shell.user_ns
+        self.shell.user_ns = mod.__dict__
+
+        try:
+            for cell in nb.worksheets[0].cells:
+                if cell.cell_type == 'code' and cell.language == 'python':
+                    # transform the input to executable Python
+                    code = self.shell.input_transformer_manager.transform_cell(cell.input)
+                    # run the code in themodule
+                    exec(code, mod.__dict__)
+        finally:
+            self.shell.user_ns = save_user_ns
+        return mod
+
+
+class NotebookFinder(object):
+    """Module finder that locates IPython Notebooks"""
+
+    def __init__(self):
+        self.loaders = {}
+
+    def find_module(self, fullname, path=None):
+        nb_path = find_notebook(fullname, path)
+        if not nb_path:
+            return
+
+        key = path
+        if path:
+            # lists aren't hashable
+            key = os.path.sep.join(path)
+
+        if key not in self.loaders:
+            self.loaders[key] = NotebookLoader(path)
+        return self.loaders[key]
+
+sys.meta_path.append(NotebookFinder())
+
 if __name__ == "__main__":
     # test_func()
-
+    from unittest import TestCase
     # if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy:
     #     # close the existing proactor loop so if anyone has a handle on it they get an error instead of hanging.
     #     # there doesn't appear to be a way to ask if this has been accessed already, so this
@@ -19,10 +112,41 @@ if __name__ == "__main__":
     import nbformat
     from nbconvert.preprocessors import ExecutePreprocessor
 
-    with open(nb) as f:
+    # from week02 import Week_2_sol
+    import importnb
+    file = "week02/week2.ipynb"
+    file2 = 'week02/Week_2_sol.ipynb'
+    m = importnb.Notebook.load(file)
+    # importnb.Notebook.l
+
+    import nbclient
+    import nbformat
+    from nbparameterise import extract_parameters, replace_definitions, parameter_values
+
+    with open(file) as f:
+        nb = nbformat.read(f, as_version=4)
+
+    # Get a list of Parameter objects
+    orig_parameters = extract_parameters(nb)
+    print(orig_parameters)
+    # Update one or more parameters
+    params = parameter_values(orig_parameters, var='GOOG')
+
+    # Make a notebook object with these definitions
+    new_nb = replace_definitions(nb, params)
+
+    # Execute the notebook with the new parameters
+    nbclient.execute(new_nb)
+
+
+    # with importnb.Notebook(file):
+    #     import model
+    # import nbformat
+
+    with open(file) as f:
         nb = nbformat.read(f, as_version=4)
     ep = ExecutePreprocessor(timeout=600, kernel_name='python3')
-    # ep.preprocess(nb, {'metadata': {'path': 'week02/'}})
+    ep.preprocess(nb, {'metadata': {'path': 'week02/'}})
 
     z = 234
     pass
\ No newline at end of file
diff --git a/examples/02471/instructor/02471/week02/.ipynb_checkpoints/Week_2_sol-checkpoint.ipynb b/examples/02471/instructor/02471/week02/.ipynb_checkpoints/Week_2_sol-checkpoint.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..417af02e33c700f0a8598906c9a4b8b1c4dbd0d2
--- /dev/null
+++ b/examples/02471/instructor/02471/week02/.ipynb_checkpoints/Week_2_sol-checkpoint.ipynb
@@ -0,0 +1,1420 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Exercise 2.2.1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/javascript": [
+       "/* Put everything inside the global mpl namespace */\n",
+       "/* global mpl */\n",
+       "window.mpl = {};\n",
+       "\n",
+       "mpl.get_websocket_type = function () {\n",
+       "    if (typeof WebSocket !== 'undefined') {\n",
+       "        return WebSocket;\n",
+       "    } else if (typeof MozWebSocket !== 'undefined') {\n",
+       "        return MozWebSocket;\n",
+       "    } else {\n",
+       "        alert(\n",
+       "            'Your browser does not have WebSocket support. ' +\n",
+       "                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+       "                'Firefox 4 and 5 are also supported but you ' +\n",
+       "                'have to enable WebSockets in about:config.'\n",
+       "        );\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+       "    this.id = figure_id;\n",
+       "\n",
+       "    this.ws = websocket;\n",
+       "\n",
+       "    this.supports_binary = this.ws.binaryType !== undefined;\n",
+       "\n",
+       "    if (!this.supports_binary) {\n",
+       "        var warnings = document.getElementById('mpl-warnings');\n",
+       "        if (warnings) {\n",
+       "            warnings.style.display = 'block';\n",
+       "            warnings.textContent =\n",
+       "                'This browser does not support binary websocket messages. ' +\n",
+       "                'Performance may be slow.';\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.imageObj = new Image();\n",
+       "\n",
+       "    this.context = undefined;\n",
+       "    this.message = undefined;\n",
+       "    this.canvas = undefined;\n",
+       "    this.rubberband_canvas = undefined;\n",
+       "    this.rubberband_context = undefined;\n",
+       "    this.format_dropdown = undefined;\n",
+       "\n",
+       "    this.image_mode = 'full';\n",
+       "\n",
+       "    this.root = document.createElement('div');\n",
+       "    this.root.setAttribute('style', 'display: inline-block');\n",
+       "    this._root_extra_style(this.root);\n",
+       "\n",
+       "    parent_element.appendChild(this.root);\n",
+       "\n",
+       "    this._init_header(this);\n",
+       "    this._init_canvas(this);\n",
+       "    this._init_toolbar(this);\n",
+       "\n",
+       "    var fig = this;\n",
+       "\n",
+       "    this.waiting = false;\n",
+       "\n",
+       "    this.ws.onopen = function () {\n",
+       "        fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+       "        fig.send_message('send_image_mode', {});\n",
+       "        if (fig.ratio !== 1) {\n",
+       "            fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n",
+       "        }\n",
+       "        fig.send_message('refresh', {});\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onload = function () {\n",
+       "        if (fig.image_mode === 'full') {\n",
+       "            // Full images could contain transparency (where diff images\n",
+       "            // almost always do), so we need to clear the canvas so that\n",
+       "            // there is no ghosting.\n",
+       "            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+       "        }\n",
+       "        fig.context.drawImage(fig.imageObj, 0, 0);\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onunload = function () {\n",
+       "        fig.ws.close();\n",
+       "    };\n",
+       "\n",
+       "    this.ws.onmessage = this._make_on_message_function(this);\n",
+       "\n",
+       "    this.ondownload = ondownload;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_header = function () {\n",
+       "    var titlebar = document.createElement('div');\n",
+       "    titlebar.classList =\n",
+       "        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+       "    var titletext = document.createElement('div');\n",
+       "    titletext.classList = 'ui-dialog-title';\n",
+       "    titletext.setAttribute(\n",
+       "        'style',\n",
+       "        'width: 100%; text-align: center; padding: 3px;'\n",
+       "    );\n",
+       "    titlebar.appendChild(titletext);\n",
+       "    this.root.appendChild(titlebar);\n",
+       "    this.header = titletext;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._init_canvas = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+       "    canvas_div.setAttribute(\n",
+       "        'style',\n",
+       "        'border: 1px solid #ddd;' +\n",
+       "            'box-sizing: content-box;' +\n",
+       "            'clear: both;' +\n",
+       "            'min-height: 1px;' +\n",
+       "            'min-width: 1px;' +\n",
+       "            'outline: 0;' +\n",
+       "            'overflow: hidden;' +\n",
+       "            'position: relative;' +\n",
+       "            'resize: both;'\n",
+       "    );\n",
+       "\n",
+       "    function on_keyboard_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.key_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keydown',\n",
+       "        on_keyboard_event_closure('key_press')\n",
+       "    );\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keyup',\n",
+       "        on_keyboard_event_closure('key_release')\n",
+       "    );\n",
+       "\n",
+       "    this._canvas_extra_style(canvas_div);\n",
+       "    this.root.appendChild(canvas_div);\n",
+       "\n",
+       "    var canvas = (this.canvas = document.createElement('canvas'));\n",
+       "    canvas.classList.add('mpl-canvas');\n",
+       "    canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+       "\n",
+       "    this.context = canvas.getContext('2d');\n",
+       "\n",
+       "    var backingStore =\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        this.context.webkitBackingStorePixelRatio ||\n",
+       "        this.context.mozBackingStorePixelRatio ||\n",
+       "        this.context.msBackingStorePixelRatio ||\n",
+       "        this.context.oBackingStorePixelRatio ||\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        1;\n",
+       "\n",
+       "    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+       "    if (this.ratio !== 1) {\n",
+       "        fig.send_message('set_dpi_ratio', { dpi_ratio: this.ratio });\n",
+       "    }\n",
+       "\n",
+       "    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+       "        'canvas'\n",
+       "    ));\n",
+       "    rubberband_canvas.setAttribute(\n",
+       "        'style',\n",
+       "        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+       "    );\n",
+       "\n",
+       "    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+       "    if (this.ResizeObserver === undefined) {\n",
+       "        if (window.ResizeObserver !== undefined) {\n",
+       "            this.ResizeObserver = window.ResizeObserver;\n",
+       "        } else {\n",
+       "            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+       "            this.ResizeObserver = obs.ResizeObserver;\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+       "        var nentries = entries.length;\n",
+       "        for (var i = 0; i < nentries; i++) {\n",
+       "            var entry = entries[i];\n",
+       "            var width, height;\n",
+       "            if (entry.contentBoxSize) {\n",
+       "                if (entry.contentBoxSize instanceof Array) {\n",
+       "                    // Chrome 84 implements new version of spec.\n",
+       "                    width = entry.contentBoxSize[0].inlineSize;\n",
+       "                    height = entry.contentBoxSize[0].blockSize;\n",
+       "                } else {\n",
+       "                    // Firefox implements old version of spec.\n",
+       "                    width = entry.contentBoxSize.inlineSize;\n",
+       "                    height = entry.contentBoxSize.blockSize;\n",
+       "                }\n",
+       "            } else {\n",
+       "                // Chrome <84 implements even older version of spec.\n",
+       "                width = entry.contentRect.width;\n",
+       "                height = entry.contentRect.height;\n",
+       "            }\n",
+       "\n",
+       "            // Keep the size of the canvas and rubber band canvas in sync with\n",
+       "            // the canvas container.\n",
+       "            if (entry.devicePixelContentBoxSize) {\n",
+       "                // Chrome 84 implements new version of spec.\n",
+       "                canvas.setAttribute(\n",
+       "                    'width',\n",
+       "                    entry.devicePixelContentBoxSize[0].inlineSize\n",
+       "                );\n",
+       "                canvas.setAttribute(\n",
+       "                    'height',\n",
+       "                    entry.devicePixelContentBoxSize[0].blockSize\n",
+       "                );\n",
+       "            } else {\n",
+       "                canvas.setAttribute('width', width * fig.ratio);\n",
+       "                canvas.setAttribute('height', height * fig.ratio);\n",
+       "            }\n",
+       "            canvas.setAttribute(\n",
+       "                'style',\n",
+       "                'width: ' + width + 'px; height: ' + height + 'px;'\n",
+       "            );\n",
+       "\n",
+       "            rubberband_canvas.setAttribute('width', width);\n",
+       "            rubberband_canvas.setAttribute('height', height);\n",
+       "\n",
+       "            // And update the size in Python. We ignore the initial 0/0 size\n",
+       "            // that occurs as the element is placed into the DOM, which should\n",
+       "            // otherwise not happen due to the minimum size styling.\n",
+       "            if (width != 0 && height != 0) {\n",
+       "                fig.request_resize(width, height);\n",
+       "            }\n",
+       "        }\n",
+       "    });\n",
+       "    this.resizeObserverInstance.observe(canvas_div);\n",
+       "\n",
+       "    function on_mouse_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.mouse_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousedown',\n",
+       "        on_mouse_event_closure('button_press')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseup',\n",
+       "        on_mouse_event_closure('button_release')\n",
+       "    );\n",
+       "    // Throttle sequential mouse events to 1 every 20ms.\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousemove',\n",
+       "        on_mouse_event_closure('motion_notify')\n",
+       "    );\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseenter',\n",
+       "        on_mouse_event_closure('figure_enter')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseleave',\n",
+       "        on_mouse_event_closure('figure_leave')\n",
+       "    );\n",
+       "\n",
+       "    canvas_div.addEventListener('wheel', function (event) {\n",
+       "        if (event.deltaY < 0) {\n",
+       "            event.step = 1;\n",
+       "        } else {\n",
+       "            event.step = -1;\n",
+       "        }\n",
+       "        on_mouse_event_closure('scroll')(event);\n",
+       "    });\n",
+       "\n",
+       "    canvas_div.appendChild(canvas);\n",
+       "    canvas_div.appendChild(rubberband_canvas);\n",
+       "\n",
+       "    this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+       "    this.rubberband_context.strokeStyle = '#000000';\n",
+       "\n",
+       "    this._resize_canvas = function (width, height, forward) {\n",
+       "        if (forward) {\n",
+       "            canvas_div.style.width = width + 'px';\n",
+       "            canvas_div.style.height = height + 'px';\n",
+       "        }\n",
+       "    };\n",
+       "\n",
+       "    // Disable right mouse context menu.\n",
+       "    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+       "        event.preventDefault();\n",
+       "        return false;\n",
+       "    });\n",
+       "\n",
+       "    function set_focus() {\n",
+       "        canvas.focus();\n",
+       "        canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    window.setTimeout(set_focus, 100);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'mpl-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'mpl-button-group';\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'mpl-button-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        var button = (fig.buttons[name] = document.createElement('button'));\n",
+       "        button.classList = 'mpl-widget';\n",
+       "        button.setAttribute('role', 'button');\n",
+       "        button.setAttribute('aria-disabled', 'false');\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "\n",
+       "        var icon_img = document.createElement('img');\n",
+       "        icon_img.src = '_images/' + image + '.png';\n",
+       "        icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+       "        icon_img.alt = tooltip;\n",
+       "        button.appendChild(icon_img);\n",
+       "\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    var fmt_picker = document.createElement('select');\n",
+       "    fmt_picker.classList = 'mpl-widget';\n",
+       "    toolbar.appendChild(fmt_picker);\n",
+       "    this.format_dropdown = fmt_picker;\n",
+       "\n",
+       "    for (var ind in mpl.extensions) {\n",
+       "        var fmt = mpl.extensions[ind];\n",
+       "        var option = document.createElement('option');\n",
+       "        option.selected = fmt === mpl.default_extension;\n",
+       "        option.innerHTML = fmt;\n",
+       "        fmt_picker.appendChild(option);\n",
+       "    }\n",
+       "\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+       "    // which will in turn request a refresh of the image.\n",
+       "    this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_message = function (type, properties) {\n",
+       "    properties['type'] = type;\n",
+       "    properties['figure_id'] = this.id;\n",
+       "    this.ws.send(JSON.stringify(properties));\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_draw_message = function () {\n",
+       "    if (!this.waiting) {\n",
+       "        this.waiting = true;\n",
+       "        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    var format_dropdown = fig.format_dropdown;\n",
+       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+       "    fig.ondownload(fig, format);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+       "    var size = msg['size'];\n",
+       "    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+       "        fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+       "        fig.send_message('refresh', {});\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+       "    var x0 = msg['x0'] / fig.ratio;\n",
+       "    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+       "    var x1 = msg['x1'] / fig.ratio;\n",
+       "    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+       "    x0 = Math.floor(x0) + 0.5;\n",
+       "    y0 = Math.floor(y0) + 0.5;\n",
+       "    x1 = Math.floor(x1) + 0.5;\n",
+       "    y1 = Math.floor(y1) + 0.5;\n",
+       "    var min_x = Math.min(x0, x1);\n",
+       "    var min_y = Math.min(y0, y1);\n",
+       "    var width = Math.abs(x1 - x0);\n",
+       "    var height = Math.abs(y1 - y0);\n",
+       "\n",
+       "    fig.rubberband_context.clearRect(\n",
+       "        0,\n",
+       "        0,\n",
+       "        fig.canvas.width / fig.ratio,\n",
+       "        fig.canvas.height / fig.ratio\n",
+       "    );\n",
+       "\n",
+       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+       "    // Updates the figure title.\n",
+       "    fig.header.textContent = msg['label'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+       "    var cursor = msg['cursor'];\n",
+       "    switch (cursor) {\n",
+       "        case 0:\n",
+       "            cursor = 'pointer';\n",
+       "            break;\n",
+       "        case 1:\n",
+       "            cursor = 'default';\n",
+       "            break;\n",
+       "        case 2:\n",
+       "            cursor = 'crosshair';\n",
+       "            break;\n",
+       "        case 3:\n",
+       "            cursor = 'move';\n",
+       "            break;\n",
+       "    }\n",
+       "    fig.rubberband_canvas.style.cursor = cursor;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+       "    fig.message.textContent = msg['message'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+       "    // Request the server to send over a new figure.\n",
+       "    fig.send_draw_message();\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+       "    fig.image_mode = msg['mode'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+       "    for (var key in msg) {\n",
+       "        if (!(key in fig.buttons)) {\n",
+       "            continue;\n",
+       "        }\n",
+       "        fig.buttons[key].disabled = !msg[key];\n",
+       "        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+       "    if (msg['mode'] === 'PAN') {\n",
+       "        fig.buttons['Pan'].classList.add('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    } else if (msg['mode'] === 'ZOOM') {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.add('active');\n",
+       "    } else {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Called whenever the canvas gets updated.\n",
+       "    this.send_message('ack', {});\n",
+       "};\n",
+       "\n",
+       "// A function to construct a web socket function for onmessage handling.\n",
+       "// Called in the figure constructor.\n",
+       "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+       "    return function socket_on_message(evt) {\n",
+       "        if (evt.data instanceof Blob) {\n",
+       "            /* FIXME: We get \"Resource interpreted as Image but\n",
+       "             * transferred with MIME type text/plain:\" errors on\n",
+       "             * Chrome.  But how to set the MIME type?  It doesn't seem\n",
+       "             * to be part of the websocket stream */\n",
+       "            evt.data.type = 'image/png';\n",
+       "\n",
+       "            /* Free the memory for the previous frames */\n",
+       "            if (fig.imageObj.src) {\n",
+       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
+       "                    fig.imageObj.src\n",
+       "                );\n",
+       "            }\n",
+       "\n",
+       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+       "                evt.data\n",
+       "            );\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        } else if (\n",
+       "            typeof evt.data === 'string' &&\n",
+       "            evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+       "        ) {\n",
+       "            fig.imageObj.src = evt.data;\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        var msg = JSON.parse(evt.data);\n",
+       "        var msg_type = msg['type'];\n",
+       "\n",
+       "        // Call the  \"handle_{type}\" callback, which takes\n",
+       "        // the figure and JSON message as its only arguments.\n",
+       "        try {\n",
+       "            var callback = fig['handle_' + msg_type];\n",
+       "        } catch (e) {\n",
+       "            console.log(\n",
+       "                \"No handler for the '\" + msg_type + \"' message type: \",\n",
+       "                msg\n",
+       "            );\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        if (callback) {\n",
+       "            try {\n",
+       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+       "                callback(fig, msg);\n",
+       "            } catch (e) {\n",
+       "                console.log(\n",
+       "                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+       "                    e,\n",
+       "                    e.stack,\n",
+       "                    msg\n",
+       "                );\n",
+       "            }\n",
+       "        }\n",
+       "    };\n",
+       "};\n",
+       "\n",
+       "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+       "mpl.findpos = function (e) {\n",
+       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+       "    var targ;\n",
+       "    if (!e) {\n",
+       "        e = window.event;\n",
+       "    }\n",
+       "    if (e.target) {\n",
+       "        targ = e.target;\n",
+       "    } else if (e.srcElement) {\n",
+       "        targ = e.srcElement;\n",
+       "    }\n",
+       "    if (targ.nodeType === 3) {\n",
+       "        // defeat Safari bug\n",
+       "        targ = targ.parentNode;\n",
+       "    }\n",
+       "\n",
+       "    // pageX,Y are the mouse positions relative to the document\n",
+       "    var boundingRect = targ.getBoundingClientRect();\n",
+       "    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+       "    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+       "\n",
+       "    return { x: x, y: y };\n",
+       "};\n",
+       "\n",
+       "/*\n",
+       " * return a copy of an object with only non-object keys\n",
+       " * we need this to avoid circular references\n",
+       " * http://stackoverflow.com/a/24161582/3208463\n",
+       " */\n",
+       "function simpleKeys(original) {\n",
+       "    return Object.keys(original).reduce(function (obj, key) {\n",
+       "        if (typeof original[key] !== 'object') {\n",
+       "            obj[key] = original[key];\n",
+       "        }\n",
+       "        return obj;\n",
+       "    }, {});\n",
+       "}\n",
+       "\n",
+       "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+       "    var canvas_pos = mpl.findpos(event);\n",
+       "\n",
+       "    if (name === 'button_press') {\n",
+       "        this.canvas.focus();\n",
+       "        this.canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    var x = canvas_pos.x * this.ratio;\n",
+       "    var y = canvas_pos.y * this.ratio;\n",
+       "\n",
+       "    this.send_message(name, {\n",
+       "        x: x,\n",
+       "        y: y,\n",
+       "        button: event.button,\n",
+       "        step: event.step,\n",
+       "        guiEvent: simpleKeys(event),\n",
+       "    });\n",
+       "\n",
+       "    /* This prevents the web browser from automatically changing to\n",
+       "     * the text insertion cursor when the button is pressed.  We want\n",
+       "     * to control all of the cursor setting manually through the\n",
+       "     * 'cursor' event from matplotlib */\n",
+       "    event.preventDefault();\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+       "    // Handle any extra behaviour associated with a key event\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.key_event = function (event, name) {\n",
+       "    // Prevent repeat events\n",
+       "    if (name === 'key_press') {\n",
+       "        if (event.which === this._key) {\n",
+       "            return;\n",
+       "        } else {\n",
+       "            this._key = event.which;\n",
+       "        }\n",
+       "    }\n",
+       "    if (name === 'key_release') {\n",
+       "        this._key = null;\n",
+       "    }\n",
+       "\n",
+       "    var value = '';\n",
+       "    if (event.ctrlKey && event.which !== 17) {\n",
+       "        value += 'ctrl+';\n",
+       "    }\n",
+       "    if (event.altKey && event.which !== 18) {\n",
+       "        value += 'alt+';\n",
+       "    }\n",
+       "    if (event.shiftKey && event.which !== 16) {\n",
+       "        value += 'shift+';\n",
+       "    }\n",
+       "\n",
+       "    value += 'k';\n",
+       "    value += event.which.toString();\n",
+       "\n",
+       "    this._key_event_extra(event, name);\n",
+       "\n",
+       "    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+       "    if (name === 'download') {\n",
+       "        this.handle_save(this, null);\n",
+       "    } else {\n",
+       "        this.send_message('toolbar_button', { name: name });\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+       "    this.message.textContent = tooltip;\n",
+       "};\n",
+       "\n",
+       "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+       "// prettier-ignore\n",
+       "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+       "\n",
+       "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+       "\n",
+       "mpl.default_extension = \"png\";/* global mpl */\n",
+       "\n",
+       "var comm_websocket_adapter = function (comm) {\n",
+       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
+       "    // object with the appropriate methods. Currently this is a non binary\n",
+       "    // socket, so there is still some room for performance tuning.\n",
+       "    var ws = {};\n",
+       "\n",
+       "    ws.close = function () {\n",
+       "        comm.close();\n",
+       "    };\n",
+       "    ws.send = function (m) {\n",
+       "        //console.log('sending', m);\n",
+       "        comm.send(m);\n",
+       "    };\n",
+       "    // Register the callback with on_msg.\n",
+       "    comm.on_msg(function (msg) {\n",
+       "        //console.log('receiving', msg['content']['data'], msg);\n",
+       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+       "        ws.onmessage(msg['content']['data']);\n",
+       "    });\n",
+       "    return ws;\n",
+       "};\n",
+       "\n",
+       "mpl.mpl_figure_comm = function (comm, msg) {\n",
+       "    // This is the function which gets called when the mpl process\n",
+       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+       "\n",
+       "    var id = msg.content.data.id;\n",
+       "    // Get hold of the div created by the display call when the Comm\n",
+       "    // socket was opened in Python.\n",
+       "    var element = document.getElementById(id);\n",
+       "    var ws_proxy = comm_websocket_adapter(comm);\n",
+       "\n",
+       "    function ondownload(figure, _format) {\n",
+       "        window.open(figure.canvas.toDataURL());\n",
+       "    }\n",
+       "\n",
+       "    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+       "\n",
+       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+       "    // web socket which is closed, not our websocket->open comm proxy.\n",
+       "    ws_proxy.onopen();\n",
+       "\n",
+       "    fig.parent_element = element;\n",
+       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
+       "    if (!fig.cell_info) {\n",
+       "        console.error('Failed to find cell for figure', id, fig);\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.cell_info[0].output_area.element.on(\n",
+       "        'cleared',\n",
+       "        { fig: fig },\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+       "    var width = fig.canvas.width / fig.ratio;\n",
+       "    fig.cell_info[0].output_area.element.off(\n",
+       "        'cleared',\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+       "\n",
+       "    // Update the output cell to use the data from the current canvas.\n",
+       "    fig.push_to_output();\n",
+       "    var dataURL = fig.canvas.toDataURL();\n",
+       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+       "    // the notebook keyboard shortcuts fail.\n",
+       "    IPython.keyboard_manager.enable();\n",
+       "    fig.parent_element.innerHTML =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "    fig.close_ws(fig, msg);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+       "    fig.send_message('closing', msg);\n",
+       "    // fig.ws.close()\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+       "    // Turn the data on the canvas into data in the output cell.\n",
+       "    var width = this.canvas.width / this.ratio;\n",
+       "    var dataURL = this.canvas.toDataURL();\n",
+       "    this.cell_info[1]['text/html'] =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Tell IPython that the notebook contents must change.\n",
+       "    IPython.notebook.set_dirty(true);\n",
+       "    this.send_message('ack', {});\n",
+       "    var fig = this;\n",
+       "    // Wait a second, then push the new image to the DOM so\n",
+       "    // that it is saved nicely (might be nice to debounce this).\n",
+       "    setTimeout(function () {\n",
+       "        fig.push_to_output();\n",
+       "    }, 1000);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'btn-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'btn-group';\n",
+       "    var button;\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'btn-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        button = fig.buttons[name] = document.createElement('button');\n",
+       "        button.classList = 'btn btn-default';\n",
+       "        button.href = '#';\n",
+       "        button.title = name;\n",
+       "        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    // Add the status bar.\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message pull-right';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "\n",
+       "    // Add the close button to the window.\n",
+       "    var buttongrp = document.createElement('div');\n",
+       "    buttongrp.classList = 'btn-group inline pull-right';\n",
+       "    button = document.createElement('button');\n",
+       "    button.classList = 'btn btn-mini btn-primary';\n",
+       "    button.href = '#';\n",
+       "    button.title = 'Stop Interaction';\n",
+       "    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n",
+       "    button.addEventListener('click', function (_evt) {\n",
+       "        fig.handle_close(fig, {});\n",
+       "    });\n",
+       "    button.addEventListener(\n",
+       "        'mouseover',\n",
+       "        on_mouseover_closure('Stop Interaction')\n",
+       "    );\n",
+       "    buttongrp.appendChild(button);\n",
+       "    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+       "    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+       "    var fig = event.data.fig;\n",
+       "    if (event.target !== this) {\n",
+       "        // Ignore bubbled events from children.\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.close_ws(fig, {});\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (el) {\n",
+       "    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+       "    // this is important to make the div 'focusable\n",
+       "    el.setAttribute('tabindex', 0);\n",
+       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
+       "    // off when our div gets focus\n",
+       "\n",
+       "    // location in version 3\n",
+       "    if (IPython.notebook.keyboard_manager) {\n",
+       "        IPython.notebook.keyboard_manager.register_events(el);\n",
+       "    } else {\n",
+       "        // location in version 2\n",
+       "        IPython.keyboard_manager.register_events(el);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+       "    var manager = IPython.notebook.keyboard_manager;\n",
+       "    if (!manager) {\n",
+       "        manager = IPython.keyboard_manager;\n",
+       "    }\n",
+       "\n",
+       "    // Check for shift+enter\n",
+       "    if (event.shiftKey && event.which === 13) {\n",
+       "        this.canvas_div.blur();\n",
+       "        // select the cell after this one\n",
+       "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+       "        IPython.notebook.select(index + 1);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    fig.ondownload(fig, null);\n",
+       "};\n",
+       "\n",
+       "mpl.find_output_cell = function (html_output) {\n",
+       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+       "    // IPython event is triggered only after the cells have been serialised, which for\n",
+       "    // our purposes (turning an active figure into a static one), is too late.\n",
+       "    var cells = IPython.notebook.get_cells();\n",
+       "    var ncells = cells.length;\n",
+       "    for (var i = 0; i < ncells; i++) {\n",
+       "        var cell = cells[i];\n",
+       "        if (cell.cell_type === 'code') {\n",
+       "            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+       "                var data = cell.output_area.outputs[j];\n",
+       "                if (data.data) {\n",
+       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
+       "                    data = data.data;\n",
+       "                }\n",
+       "                if (data['text/html'] === html_output) {\n",
+       "                    return [cell, data, j];\n",
+       "                }\n",
+       "            }\n",
+       "        }\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "// Register the function which deals with the matplotlib target/channel.\n",
+       "// The kernel may be null if the page has been refreshed.\n",
+       "if (IPython.notebook.kernel !== null) {\n",
+       "    IPython.notebook.kernel.comm_manager.register_target(\n",
+       "        'matplotlib',\n",
+       "        mpl.mpl_figure_comm\n",
+       "    );\n",
+       "}\n"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Javascript object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA8AAAALQCAYAAABfdxm0AAAAAXNSR0IArs4c6QAAIABJREFUeF7svWeMHNl99vuv6jiREzkzzGm5zMvlcjOX3CBphVe+hu0XsP36fnCCbQECnODwwfYHBxgCbOja14bDvYIc9Mq2dG3Zr17L2tWuNjFtIrmBS+4uMzmMQ3LyTOe+eE7zzNTUdHdVd1f3dHc9B2j0kFPhnN853VPP+Scjm81mhY0ESIAESIAESIAESIAESIAESIAEmpyAQQHc5DPM4ZEACZAACZAACZAACZAACZAACSgCFMBcCCRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiCBhieQzWYlk8lIOp1WYwkEAmKaphiG0fBj4wBIgARIgARIgAS8I0AB7B1LXokESIAESGAJCED8plIpJX5nZmaU6NUCGO94BYNB9f8UxEswQbwlCZAACZAACdQRAQrgOpoMdoUESIAESKA0ArD6JpPJOetvIpEQCGLdtBUY73hpMYx3CuLSWPNoEiABEiABEmgGAhTAzTCLHAMJkAAJ+IwARC4svrD8QgTrBjGsrbw4RrtGa1FcTBDjd2wkQAIkQAIkQALNTYACuLnnl6MjARIggaYjADGrrb4Qv9qSq63B+Sy7djFsFcRaFGt3af1OQdx0S4cDIgESIAESIAGhAOYiIAESIAESaBgC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AXjOnDmpT58+WQhesWLFf1JF+P3pp5/SL7/8kpo2bVrtnMZPgAABAntBQADeC+guSYAAAQIEylGgrgAcz7cOGDAgnXHGGWnBggX/ObxYFh1HKcWvtm3bliOBmgkQIECgzAUE4DKfQOUTIECAAIFSCQjApZJ2HQIECBDIS0AAzktWvwQIECBAoMIELIGusAk1HAIECFShgABchZNuyAQIECBAYHcEbIK1O2reQ4AAAQJFEhCAizQbaiFAgAABAgUW2NljkL7++uvUrl27LUbiGKQCT6zSCBAgUEUCAnAVTbahEiBAgACBPRGoKwBHv717904vvfRSmjBhQhoyZMgWlxo8eHCqqalJ48ePT0OHDt2TMryXAAECBAjstoAAvNt03kiAAAECBKpLYEcBeN68ealnz56pVatWafHixalDhw4ZUPy+e/fuqWHDhmnt2rWpefPm1QVntAQIECBQGAEBuDBToRACBAgQIFAsgblz56ZRo0bVFrVs2bK0cePG1Llz59q/Gz58eDr//PNr/xx3fidOnJgaNWqUheENGzak1157LXvfrFmzUt++fYs1SNUQIECAQFUJCMBVNd0GS4AAAQIEdl5g2rRp6corr6zzDVOnTk0DBw7c4jXxvkmTJqU1a9akBg0apNNOOy1FUO7SpcvOX9wrCRAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAv8HBVaBQBXk6uQAAAAASUVORK5CYII=\" width=\"640\">"
+      ],
+      "text/plain": [
+       "<IPython.core.display.HTML object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# %matplotlib inline\n",
+    "%matplotlib notebook\n",
+    "\n",
+    "import numpy as np\n",
+    "import matplotlib.pyplot as plt\n",
+    "from mpl_toolkits import mplot3d\n",
+    "np.random.seed(42)\n",
+    "\n",
+    "N = 50 # number of points\n",
+    "mu = 0 # mean value of noise\n",
+    "sig2 = 1 # variance of noise\n",
+    "theta = np.array([0.25, -0.25, 0.25])\n",
+    "x_min = 0  # min value of x for data generation\n",
+    "x_max = 10 # max value of x for data generation\n",
+    "\n",
+    "# Data generation\n",
+    "X = np.random.uniform(x_min,x_max,(N,3))\n",
+    "theta = theta[:,np.newaxis]\n",
+    "X[:,0] = 1\n",
+    "\n",
+    "# Noise with mean mu and std sigma\n",
+    "eta = np.random.normal(mu, np.sqrt(sig2), (X.shape[0],1))\n",
+    "y = np.matmul(X,theta) + eta\n",
+    "theta_hat = np.matmul(np.linalg.pinv(X),y)\n",
+    "\n",
+    "# Plot of plane generated by true parameters\n",
+    "fig = plt.figure()\n",
+    "x1,x2 = np.meshgrid(np.linspace(x_min,x_max,10), np.linspace(x_min,x_max,10))\n",
+    "z = theta[0] + theta[1]*x1 + theta[2]*x2\n",
+    "ax = plt.axes(projection='3d')\n",
+    "ax.scatter(X[:,1], X[:,2], y)\n",
+    "s = ax.plot_surface(x1,x2,z, facecolor='blue', label='True params', alpha=.5)\n",
+    "#s._facecolors2d = s._facecolors3d\n",
+    "#s._edgecolors2d = s._edgecolors3d\n",
+    "\n",
+    "# Plot of plane generated by true parameters\n",
+    "z = theta_hat[0] + theta_hat[1]*x1 + theta_hat[2]*x2\n",
+    "s = ax.plot_surface(x1,x2,z, facecolor='red', label='Estimated params', alpha=.5)\n",
+    "#s._facecolors2d = s._facecolors3d\n",
+    "#s._edgecolors2d = s._edgecolors3d\n",
+    "ax.set_xlabel(\"x1\")\n",
+    "ax.set_ylabel(\"x2\")\n",
+    "ax.set_zlabel(\"y\")\n",
+    "#ax.legend()\n",
+    "\n",
+    "plt.show()\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Exercise 2.2.2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of repetitions is  10\n",
+      "Repetition  10  of  10  repetitions done.\n"
+     ]
+    },
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtcAAAHgCAYAAABuGUHVAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAACEkElEQVR4nO3dd3xUVfrH8c9JISEQOgICUlWqBAwqihrXhrq2tWJZO+q6Krsrrq6svbv6s3exl7WgoqLCusTuKmAUsFAsCCg91FASzu+PZy4zCSmTZCYzSb7v1+u+Zs6dmTvP3EySZ86c8xznvUdERERERGovJdEBiIiIiIg0FEquRURERERiRMm1iIiIiEiMKLkWEREREYkRJdciIiIiIjGi5FpEREREJEbS4nVg51xX4CmgA+CBh733d5W5z8nA3wEHrAHO995/Fbrtp9C+EqDYe59b1XO2a9fOd+/ePYavIjrr1q2jWbNmdf68jYHObfzo3MaPzm386NzGj85t/Ojcxk+izu20adOWee/bl3db3JJroBj4m/d+unMuG5jmnJvsvf8m4j4/Avt671c65w4BHgZ2j7h9P+/9smifsHv37kydOjUmwVdHfn4+eXl5df68jYHObfzo3MaPzm386NzGj85t/Ojcxk+izq1z7ueKbotbcu29/xX4NXR9jXPuW6Az8E3EfT6JeMhnQJd4xSMiIiIiEm91MubaOdcdGAz8r5K7nQW8HdH2wCTn3DTn3Kg4hiciIiIiEhMu3sufO+eaA+8DN3jvx1dwn/2A+4Hh3vvloX2dvfcLnXPbAZOBC733H5Tz2FHAKIAOHTrs+sILL8TplVRs7dq1NG/evM6ftzHQuY0fndv40bmNH53b+NG5jR+d2/hJ1Lndb7/9plU0HzCuybVzLh14E3jXe39HBffZBXgVOMR7P7uC+1wNrPXe/6uy58vNzfUac92w6NzGj85t/Ojcxo/Obfzo3MZPfT23mzdvZsGCBWzYsCHRoVRow4YNZGZmxu34mZmZdOnShfT09FL7nXMVJtfxrBbigMeAbytJrHcAxgOnRibWzrlmQEporHYz4CDg2njFKiIiIiKlLViwgOzsbLp3746ldclnzZo1ZGdnx+XY3nuWL1/OggUL6NGjR9SPi2e1kL2AU4EZzrmC0L5/ADsAeO8fBK4E2gL3h35oQcm9DsCroX1pwHPe+3fiGKuIiIiIRNiwYUNSJ9bx5pyjbdu2LF26tFqPi2e1kI+w+tWV3eds4Oxy9v8ADIpTaCIiIiIShWon1sHwl/z8WIeSEDX5YKEVGkVEREQkKRUWFnL//fdX+3GHHnoohYWFld7npZdeon///qSkpMR0nRQl1yIiIiKSlCpKrouLiyt93MSJE2nVqlWl9xkwYADjx49nn332qU2I24jnmGsRERERkRq77LLLmDdvHjk5OaSnp5OZmUnr1q357rvvmD17NiNHjuTXX39lw4YNXHzxxYwaZUujBKt2r127lkMOOYThw4fzySef0LlzZ15//XWaNm1K37594xKzeq5FREREpGp5efDEE3Z982ZrP/OMtdevt/aSJdZetcra40NLnCxbZu033rD2b79F9ZQ333wzvXr1oqCggNtuu43p06dz1113MXu2FZm77777mDZtGlOnTuXuu+9m+fLl2xxjzpw5XHDBBcyaNYtWrVrxyiuv1ODFR0891yIiIiJSL+y2226lyuI9+OCDTJw4EYBffvmFOXPm0LZt21KP6dGjBzk5OQDsuuuu/PTTT3GNUcm1iIiIiFQtsgJIenrpdlaWtYNqIS1blr69XbvS7Y4daxRCs2bNIsLJJz8/n08//ZSsrCzy8vLKXfAmIyNj6/XU1FSKiopq9NzR0rAQEREREUlK2dnZrFmzptzbVq1aRatWrcjKyuK7777js88+q+PoyqfkWkRERESSUtu2bdlrr70YMGAAY8aMKXXbiBEjKC4upm/fvlx22WXsscce1Tr2q6++SpcuXfj000857LDDOPjgg2MSs4aFiIiIiEjSeu6558rdn5GRwfjx48td/jwYV92uXTtmzpy5df8ll1yy9frRRx/N0UcfHdtgUXItIiIiIrHSQFZmrA0NCxERERERiRH1XNdSXh4UFuZQUJDoSEREREQk0dRzLSIiIiISI0quRURERERiRMm1iIiIiMREXl54HZnGSsm1iIiIiCSlwsJC7r///mo/7tBDD6WwsLDS+4wZM4Y+ffqwyy67cPTRR1d5/2gpuRYRERGRpFRRcl1cXFzp4yZOnEirVq0qvc+BBx7IzJkz+frrr9lpp5246aabahPqVkquRURERCQpXXbZZcybN4+cnByGDh3K3nvvzRFHHEG/fv0AGDlyJLvuuiv9+/fn4Ycf3vq47t27s2zZMn766Sf69u3LOeecQ//+/TnooIMoKioC4KCDDiItzQrn7bHHHixYsCAmMSu5FhEREZEq5eXBE0/Y9c2brf3MM9Zev97aS5ZYe9Uqa48fb+1ly6z9xhvW/u236J7z5ptvplevXhQUFHDbbbcxffp07rrrLmbPng3Afffdx7Rp05g6dSp33303y5cv3+YYc+bM4YILLmDWrFm0atWKV155ZZv7jBs3jkMOOSS6oKqgOtciIiIiUi/stttu9OjRY2v7wQcfZOLEiQD88ssvzJkzh7Zt25Z6TI8ePcjJyQFg11133bo0euCGG24gLS2Nk08+OSYxKrkWERERkSpFrmyenl66nZVl7aBSSMuWpW9v1650u2PHmsXQrFmziHjyyc/P59NPPyUrK4u8vDw2bNiwzWMyMjK2Xk9NTd06LATgiSee4M033+S9997DOVezoMpQci0iIiIiSSk7O5s1a9aUe9uqVato1aoVWVlZfPfdd3z22WfVOvY777zDrbfeyvvvv09WVlYswgWUXIuIiIhIkmrbti177bUXAwYMoGnTpnTo0GHrbSNGjODee++lb9++7Lzzzuyxxx7VOvaf//xnNm7cyIEHHgjYpMYHH3yw1jEruRYRERGRpPXcc8+Vuz8jI4Px48eTnZ29zW3BuOp27doxc+bMrfsvueSSrdfnzp0b20BDlFyLiIiISExEjqturFSKT0REREQkRpRci4iIiIjEiJJrEREREZEYUXItIiIiIhIjSq5FREREJDby8sIryTRSSq5FREREJCkVFhZy//331+ixd955J+vXr9/avuKKK+jatSvNmzePVXjlUnItIiIiIkkplsn14Ycfzueffx6r0CqkOtciIiIikpQuu+wy5s2bR05ODgceeCDbbbcdL774Ihs3buToo4/mkksuYd26dRx//PEsWLCAkpIS/vnPf7J48WIWLVrEfvvtR7t27ZgyZUq1V3CsKSXXIiIiIlK50aOhoKDq+wX3iWbcdU4O3HlnpXe5+eabmTlzJgUFBUyaNImXX36Zzz//HO89RxxxBB9//DHr1q1j++2356233gJg1apVtGzZkjvuuIMpU6bQrl27qmOJIQ0LEREREZGkN2nSJCZNmsTgwYMZMmQI3333HfPmzWPgwIFMnjyZv//973z44Ye0bNkyoXGq51pEREREKldFD/NWQY91HNZB995z+eWXc+65527dt2bNGrKzs5k+fToTJ05k7Nix7L///lx55ZUxf/5oqedaRERERJJSdnY2a9asAeDggw9m3LhxrF27FoCFCxeydOlSFi1aRFZWFqeccgpjxoxh+vTp2zy2Lim5FhEREZGk1LZtW/baay8GDBjA5MmTOemkkxg2bBgDBw7k2GOPZc2aNcyYMYPddtuNnJwcrrnmGsaOHQvAqFGjGDFiBPvttx8Al156KV26dGH9+vV06dKFq6++Oi4xa1iIiIiIiCSt5557rlT74osv3np9zZo1DBo0iIMPPnibx1144YVceOGFW9u33nort956a/wCDVFyLSIiIiKxEYex1vVN3IaFOOe6OuemOOe+cc7Ncs5dXM59nHPubufcXOfc1865IRG3neacmxPaTotXnCIiIiIisRLPnuti4G/e++nOuWxgmnNusvf+m4j7HALsGNp2Bx4AdnfOtQGuAnIBH3rsBO/9yjjGKyIiIiJSK3Hrufbe/+q9nx66vgb4Fuhc5m5HAk958xnQyjnXCTgYmOy9XxFKqCcDI+IVq4iIiIhILNRJtRDnXHdgMPC/Mjd1Bn6JaC8I7atov4iIiIhI0or7hEbnXHPgFWC09351HI4/ChgF0KFDB/LreCB9YWEOJSUldf68jcXatWt1buNE5zZ+dG7jR+c2fnRu46e+ntuWLVtWu070oYc2BWDixKJ4hLSNkpKSuNey3rBhQ7V+fnFNrp1z6Vhi/az3fnw5d1kIdI1odwntWwjkldmfX95zeO8fBh4GyM3N9XnRrGUfQ61aQWFhIXX9vI1Ffn6+zm2c6NzGj85t/Ojcxo/ObfzU13P77bffkp2dXa3HpKbaZXUfV5HCwkKee+45/vSnP5V7e7BCY3nuvPNORo0aRVZWFuvXr+e4445j3rx5pKamcvjhh3PzzTdHFUNmZiaDBw+OOuZ4VgtxwGPAt977Oyq42wTgj6GqIXsAq7z3vwLvAgc551o751oDB4X2iYiIiEgjUVhYyP3331+jx955552sX79+a/uSSy7hu+++48svv+Tjjz/m7bffjlWYpcSz53ov4FRghnOuILTvH8AOAN77B4GJwKHAXGA9cEbothXOueuAL0KPu9Z7vyKOsYqIiIhIkrnsssuYN28eOTk5HHjggWy33Xa8+OKLbNy4kaOPPppLLrmEdevWcfzxx7NgwQJKSkr45z//yeLFi1m0aBH77bcf7dq1Y8qUKVtXamzSpAlDhgxhwYIFcYk5bsm19/4jwFVxHw9cUMFt44BxcQhNRERERKph9GgoKKj6fsF9ohkFk5MDd95Z+X1uvvlmZs6cSUFBAZMmTeLll1/m888/x3vPEUccwccff8y6devYfvvteeuttwBYtWoVLVu25I477mDKlCm0a9eu1DELCwt54403Sq30GEt1Ui1ERERERKQ2Jk2axKRJkxg8eDBDhgzhu+++Y968eQwcOJDJkyfz97//nQ8//JCWLVtWeIzi4mJGjhzJRRddRM+ePeMSp5Y/FxEREZFKVdXDHAh6rONRHMV7z+WXX8655567dV8woXH69OlMnDiRsWPHsv/++3PllVeWe4xRo0ax4447Mnr06NgHGKKeaxERERFJStnZ2VtL7R188MGMGzeOtWvXArBw4UKWLl3KokWLyMrK4pRTTmHMmDFMnz59m8cCjB07llWrVnFntJ8Uakg91yIiIiKSlNq2bctee+3FgAEDOOSQQzjppJMYNmwYAM2bN+fBBx9kzpw5jBkzhpSUFNLT03nggQcA66UeMWIE22+/PU8//TQ33HADffr0YciQIQD8+c9/5uyzz455zEquRURERCRpPffcc6XakRMR16xZw6BBgzj44IO3edyFF17IhRdeuLVtdTTiT8m1iIiIiMREPVyIMuY05lpEREREJEbUc11bBQU0Ly4GchMdiYiIiIgkmHquRURERKRcdTVOOVnV5PUruRYRERGRbWRmZrJ8+fJGm2B771m+fDmZmZnVepyGhYiIiIjINrp06cKCBQtYunRpokOp0IYNG6qd/FZHZmYmXbp0qdZjlFyLiIiIyDbS09Pp0aNHosOoVH5+PoMHD050GKVoWIiIiIiISIwouRYRERERiREl1yIiIiIiMaLkWkREREQkRpRci4iIiIjEiJJrEREREZEYUXItIiIiIhIjSq5FRERERGJEybWIiIiISIwouRYRERERiREl1yIiIiIiMaLkWkREREQkRpRci4iIiIjEiJJrEREREZEYUXItIiIiIhIjSq5FRERERGJEybWIiIiISIwouRYRERERiZG0RAdQ380t6kzJFp/oMEREREQkCSi5rqVNPo3VJVmJDkNEREREkoCGhdRSdup6NvoMlixJdCQiIiIikmhKrmspO3U9AFOnJjgQ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5CU2kCSXkgIDB9p23nm279dfSyfbt95qw1oA+veHFSsAMhg3zhL11q1LX7ZoUfUkzYrebt7D0qVWSnH+/PIvf/1120mlu+8evp6dHU60O3SwibAVtbOza3DSRCRpxeJfWQP7dxgX0QwLuT2y7Zz7F/Bu3CISqakzzrCE+phjLJt4881ER1RajP4iJcsftGSJI5aieU2dOsGxx9oGsG4dfPGFJdoffWSTK0tKmnLWWeU/3jn7YqV1620T7+By4UK735VXlk6eFyywai2RMjOha1d76x90UPh6167WA19SYgubLl5sPdmLF4e377+HDz6wii3BePOyx/beJsAOGlTxxNCqLr/+2i5PPtkm0Va0tW1b/lCburJpk01mXbFi263s/unT7TUdfDA0a1bx1rx5xbdt3mzflnhfd+P5i4ttjsLKleHXtHKlfduxbl0T7rknPNm37OTfqvYvXmyXr7++bdWfstfTounakwaroSfoNXl7ZwFdYh2ISEzsvz988olVEtlnH+jRo3YlMSTuYvHHNZF/oJs1s38UwT+LffeFFSsKefPNVqxcGU5kIhOasvu+/TbcLioKH/uGGyyZ32EHqxhz1FHhxDm4bNeu4sTsxhvt8ve/r/w1FBdblZfIxDtIxJ96yhL0nj2jmxgaeVlSEr70Hv73P3uespViIrVoEU60yybfixbBhg1NePRRO2bkVly87b7y7jNnjsVy3HHbJtBr11Ycl3P24adNG9vS0uw4hYX2YWjduvC2fn3l57us9PTSiXh5lxXdtmyZJbr//nc4US67Re5fs6aySLJqXPs+0lFHRfeaIxPuyMuZM+1D1o032p/wHj1sfkaHDtF/CEmmHtraDGcqKbEP1ytX2vv33XftdyTYWra094JKliaXaMZczwCCPo1UoD22mIxIcurXz0r1HXGEdSn26lW3XUPSqDlnvZHdutlWXRs2wO9+Z0nphx/WzfzctLRw3fGygpKLr75a8+OXTVI2bbKEb9myyrclS+Cbb+z6uq0DFLM455zonjf4WQRbWpolvs7ZWPo2bewDyqBB4aS57BYk1C1blk5gKku8tmyxD0mRCXd52003WfJ00kmW2K9bV/py+XIrDxm5f+PG8l/riSeGr2dmhuNu3do+iA0aFP62JPK2YDvrLFi9ehX5+S1LVc8pr6JORftPOskuH3qodHWfoqLS1yu6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\n",
+      "text/plain": [
+       "<Figure size 864x576 with 1 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# %%%%%%%%%%%%%%%%%%%%%%% Parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
+    "w_t = np.array([[1], [2], [0.5]]) # True weights\n",
+    "noiselevel = 0.75   # Standard deviation of Gaussian noise on data\n",
+    "d = len(w_t)        # Number of dimensions\n",
+    "Nmin=5              # Minimal training set size\n",
+    "Nmax=40             # Maximal training set size\n",
+    "Ntest= 10000        # Size of test set \n",
+    "repetitions=10      # number of repetitions\n",
+    "\n",
+    "# %%%%%%%%%% Make statistical sample of test errors for different N %%%%%%%%%%\n",
+    "# initilise arrays for test and train errors\n",
+    "np.random.seed(42)\n",
+    "\n",
+    "test1 = np.empty((repetitions, Nmax-Nmin+1))\n",
+    "test2 = np.empty((repetitions, Nmax-Nmin+1))\n",
+    "train1 = np.empty((repetitions, Nmax-Nmin+1))\n",
+    "train2 = np.empty((repetitions, Nmax-Nmin+1))\n",
+    "Ns = (Nmax-Nmin+1)*[None]\n",
+    "\n",
+    "print(\"Number of repetitions is \", str(repetitions))\n",
+    "for j in range(repetitions):\n",
+    "    # print(\"Repetition \", str(j+1), \" of \", str(repetitions), \" repetitions\")\n",
+    "    # test data\n",
+    "    # d-dimensional model data set\n",
+    "    X1test  = np.random.randn(Ntest, d) \n",
+    "    X1test[:,0] = 1\n",
+    "    Ttest = np.dot(X1test, w_t)\n",
+    "    noisetest = np.random.randn(Ntest, 1) * noiselevel\n",
+    "    \n",
+    "    Ttest = Ttest + noisetest\n",
+    "    # Small model (d-1) dimensional\n",
+    "    X2test=X1test[:,0:(d-1)]\n",
+    "    \n",
+    "    # training data\n",
+    "    # d-dimensional model data set\n",
+    "    XX1=np.random.randn(Nmax,d)\n",
+    "    XX1[:,0] = 1\n",
+    "    TT = np.dot(XX1, w_t)\n",
+    "    noise = np.random.randn(Nmax,1) * noiselevel\n",
+    "    TT = TT + noise\n",
+    "    # Small model (d-1) dimensional\n",
+    "    XX2=XX1[:,0:(d-1)]  \n",
+    "    \n",
+    "    n=0  # counter\n",
+    "\n",
+    "    for N in range(Nmin, Nmax+1):\n",
+    "        ###################################################\n",
+    "        # Pick the first N  input vectors in \n",
+    "        ###################################################\n",
+    "        X1 = XX1[:N, :]\n",
+    "        X2 = XX2[:N, :]\n",
+    "        \n",
+    "        # and the corresponding targets\n",
+    "        T=TT[:N];\n",
+    "        \n",
+    "        # Find optimal weights for the two models\n",
+    "        w1 = np.dot(np.linalg.pinv(X1), T)   # solution\n",
+    "        w2 = np.dot(np.linalg.pinv(X2), T)   # soltion\n",
+    "\n",
+    "        # compute training set predictions\n",
+    "        Y1 = np.dot(X1, w1)\n",
+    "        Y2 = np.dot(X2, w2)\n",
+    "        \n",
+    "        # compute training error\n",
+    "        err1 = np.mean(np.square(Y1-T)) # solution\n",
+    "        err2 = np.mean(np.square(Y2-T)) # solution\n",
+    "        \n",
+    "        # compute test set predictions\n",
+    "        Y1test = np.dot(X1test, w1)\n",
+    "        Y2test = np.dot(X2test, w2)\n",
+    "        err1test = np.mean(np.square(Y1test-Ttest)) # solution\n",
+    "        err2test = np.mean(np.square(Y2test-Ttest)) # solution\n",
+    "              \n",
+    "        # save the results for later \n",
+    "        test1[j,n]=err1test\n",
+    "        test2[j,n]=err2test\n",
+    "        train1[j,n]=err1\n",
+    "        train2[j,n]=err2\n",
+    "        Ns[n] =N\n",
+    "        n=n+1\n",
+    "        \n",
+    "print(\"Repetition \", str(j+1), \" of \", str(repetitions), \" repetitions done.\")\n",
+    "\n",
+    "# %%%%%%%%%%%%% Plot results %%%%%%%%%%%%%%%%%%%%%%%%%\n",
+    "%matplotlib inline\n",
+    "fig = plt.figure(figsize=(12, 8))\n",
+    "\n",
+    "plt.errorbar(Ns, np.mean(train1, axis=0), yerr= np.std(train1, axis=0)/np.sqrt(repetitions), label='train1', linestyle = \":\", color=\"red\")\n",
+    "plt.errorbar(Ns, np.mean(train2, axis=0), yerr= np.std(train2, axis=0)/np.sqrt(repetitions), label='train2', linestyle = \":\", color=\"blue\")\n",
+    "plt.errorbar(Ns, np.mean(test1, axis=0), yerr= np.std(test1, axis=0)/np.sqrt(repetitions), label='test1', color=\"red\")\n",
+    "plt.errorbar(Ns, np.mean(test2, axis=0), yerr= np.std(test2, axis=0)/np.sqrt(repetitions), label='test2', color=\"blue\")\n",
+    "plt.grid()\n",
+    "plt.xlabel(\"training set size\")\n",
+    "plt.ylabel(\"mean square errors (test and training)\")\n",
+    "\n",
+    "plt.legend(loc='upper right')\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Exercise 2.3.1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of weight decays is  100\n",
+      "Weight decay #  100  of  100  decays done.\n"
+     ]
+    },
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "<Figure size 720x576 with 1 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# %%%%%%%%%%%%%%%%%%%%%%% Parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
+    "import numpy as np\n",
+    "import matplotlib.pyplot as plt\n",
+    "\n",
+    "w_t = np.array([[1], [2], [0.5]]) # True weights\n",
+    "noiselevel = 1.0      # Standard deviation of Gaussian noise on data\n",
+    "d = len(w_t)          # Number of dimensions\n",
+    "N = 5                 # Training set size\n",
+    "Ntest= 1000           # Size of test set \n",
+    "repetitions=100       # number of repetitions\n",
+    "alfmin = 0.03;        # minimal weigth decay\n",
+    "alfmax = 100.0;       # maximal weigth decay\n",
+    "M=100;                # number of weight decays\n",
+    "\n",
+    "# %%%%%%%% Pre-allocation of variables %%%%%%%%\n",
+    "alfs = M*[None]\n",
+    "meanarr = M*[None]\n",
+    "biasarr = M*[None]\n",
+    "variancearr = M*[None]\n",
+    "\n",
+    "Ytest = np.zeros((Ntest, repetitions))\n",
+    "\n",
+    "# %%%%%%%%%% Make statistical sample of samples for bias&variance %%%%%%%%%%\n",
+    "np.random.seed(42)\n",
+    "\n",
+    "n = 0\n",
+    "print(\"Number of weight decays is \", str(M))\n",
+    "for k in range(M):\n",
+    "    alf = alfmin * (np.power((alfmax/alfmin), k/(M-1)))\n",
+    "#     print(\"Weight decay # \", str(k+1), \" of \", str(M), \" decays \")\n",
+    "    \n",
+    "    # d-dimensional model data set\n",
+    "    Xtest  = np.random.randn(Ntest, d) \n",
+    "    Xtest[:,0] = 1\n",
+    "    Ttest = np.matmul(Xtest, w_t)\n",
+    "    noisetest = np.random.randn(Ntest, 1) * noiselevel\n",
+    "    Ttest = Ttest + noisetest\n",
+    "    \n",
+    "    for j in range(repetitions):\n",
+    "        # Small model (d-1) dimensional        \n",
+    "        Xtrain=np.random.randn(N,d)\n",
+    "        Xtrain[:,0] = 1\n",
+    "        Ttrain = np.dot(Xtrain, w_t)\n",
+    "        noise = np.random.randn(N,1) * noiselevel\n",
+    "        Ttrain = Ttrain + noise\n",
+    "        \n",
+    "        # Find optimal weights for the regularized model        \n",
+    "        leftSide = np.linalg.pinv(np.dot(np.transpose(Xtrain),Xtrain) + alf*np.eye(d)) # solution\n",
+    "        rightSide = np.dot(np.transpose(Xtrain), Ttrain)                               # solution\n",
+    "        w = np.dot(leftSide,rightSide)                                                         \n",
+    "\n",
+    "        # compute test set predictions\n",
+    "        Ytest[:,j] = np.squeeze(np.dot(Xtest, w))\n",
+    "        \n",
+    "    Ybias = np.mean(Ytest,axis=1)\n",
+    "    Ybias = Ybias[:, np.newaxis]\n",
+    "    bias_error = np.mean(np.square(Ybias-Ttest))\n",
+    "    mean_error=0\n",
+    "\n",
+    "    for j in range(repetitions):\n",
+    "        mean_error = mean_error + np.mean(np.square(Ytest[:,j,np.newaxis]-Ttest))\n",
+    "    \n",
+    "    mean_error=mean_error/repetitions\n",
+    "    variance_error=mean_error-bias_error  \n",
+    "\n",
+    "    alfs[n] = alf \n",
+    "    meanarr[n]=mean_error\n",
+    "    biasarr[n]=bias_error\n",
+    "    variancearr[n]=variance_error\n",
+    "    n=n+1\n",
+    "print(\"Weight decay # \", str(k+1), \" of \", str(M), \" decays done.\")\n",
+    "\n",
+    "# %%%%%%%%%%%%% Plot results %%%%%%%%%%%%%%%%%%%%%%%%%\n",
+    "fig = plt.figure(figsize=(10, 8))\n",
+    "plt.semilogx(alfs, meanarr, label='mean test error', color = \"red\")\n",
+    "plt.semilogx(alfs, biasarr, label='bias', color = \"blue\")\n",
+    "plt.semilogx(alfs, variancearr, label='variance', color = \"green\")\n",
+    "\n",
+    "plt.xlabel(\"weight decay\")\n",
+    "plt.ylabel(\"mean square errors (test, bias and variance)\")\n",
+    "\n",
+    "plt.legend(loc='upper right')\n",
+    "plt.grid()\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Exercise 2.3.4"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<matplotlib.legend.Legend at 0x1d54722b430>"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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KEfjoyw5qgJWEmLRokVUqVKFhg0b4pwLOpyU5L3nr7/+YtGiRTRq1CjocCTNKEdjpxyVRFKOxk45KomkHI1dWXNUU4hDLjcXGjaErCy7zs0NOqLI/Pvvv9SsWVMJHQPnHDVr1tSZvZBTjmYu5WhqUI5mLuWoJJJyNHZlzVGNwIZYbi4MGAB5efb5ggX2OUBOTnBxRUoJHTv9DsNNOSr6HYabclT0O5RE0t9X7MryO9QIbIgNGrTpRbdAXp7dLiLBU46KhJtyVDLG1Knw2WdBRyGSFCpgQ2zhwuhul8T57LPPOOywwwB44403uO2224q979KlS3n44YejfozBgwczdOjQMscoyaccDQ/lqBRFORoeytEEu+oq6N8fvA86EklRqZSjKmBDrH796G6X6K1fvz7q7+nTpw9XX311sV8va1JL6lGOJp5yVGKhHE085WhIzJoFv/8OP/8cdCQSMumYoypgQ2zIEKhUafPbKlWy26V08+fPp1mzZuTk5NC8eXOOOeYY8vLyaNiwIVdddRXt2rXjpZde4oMPPqBjx460a9eOY489lpUrVwLw3nvv0axZM9q1a8err7668bjPPPMM559/PgB//PEHffv2ZY899mCPPfZg3LhxXH311fz444+0bduWK664AoA777yTDh060KZNG2688caNxxoyZAhNmjShc+fOzJkzJ4m/HYkH5WhslKOSaMrR2ChHU0Reni3wBvjqq2BjkaTK1BxVE6cQK2gwMWiQTXeqX99edFOh8cRmLr4YpkyJ7zHbtoV77y31bnPmzOHJJ5+kU6dOnH766RvPFtWsWZNvvvmGJUuWcNRRR/HRRx9RuXJlbr/9du6++26uvPJKzjrrLD755BN22203+vXrV+TxL7zwQg444ABGjx7N+vXrWblyJbfddhvTp09nSv7P/MEHH/D9998zceJEvPf06dOHMWPGULlyZV544QWmTJnCunXraNeuHXvttVecfkGSDMrREihHJQSUoyVQjqaP77/f9PHEiXDsscHFkqmUo0nNURWwIZeTk4IvtCFSr149OnXqBMCJJ57I/fffD7AxSSdMmMDMmTM33mfNmjV07NiR2bNn06hRI3bfffeN3zts2LCtjv/JJ5/w3HPPAZCdnU3VqlX5559/NrvPBx98wAcffMCee+4JwMqVK/n+++9ZsWIFffv2pVL+8ECfPn3i/eNLEihHY6MclURTjsZGOZoCZs+262rVNAKbgTIxR1XASuJFcPYoUbZszV3weeXKlQHbQPnggw9m5MiRm91vShzPonnvueaaaxg4cOBmt98b4O9FZDPKUeWohJtyVDlakjlzwDkbec3NhXXroJze4ieVcjSpOao1sJLWFi5cyPjx4wEYMWIEnTt33uzr++67L19++SU//PADAKtWrWLu3Lk0a9aM+fPn8+OPPwJslfQFunfvziOPPALYIvlly5ZRpUoVVqxYsfE+hxxyCE899dTG9Qa//PILf/75J126dOG1115j9erVrFixgjfffDO+P7xIClCOioSbcjQFzJ4NDRpA1662HnbGjKAjkiTKxBxVAStprWnTpjz00EM0b96cf/75h3POOWezr9eqVYtnnnmG/v3706ZNm41TKipUqMCwYcPo3bs37dq1o3bt2kUe/7777uPTTz+ldevW7LXXXsycOZOaNWvSqVMnWrVqxRVXXEGPHj044YQT6NixI61bt+aYY45hxYoVtGvXjn79+rHHHntw6KGH0qFDh2T8SkRCRTkqEm7K0RQwZw40awb77GOfaxpxRsnEHHU+xfaLat++vZ80aVLQYUgpZs2aRfPmzQONYf78+Rx22GFMnz490DhiVdTv0jk32XvfPqCQSqQcTQ3K0fhRjkoiKEfjJ61zdMMGqFIFzjoL7rkHata0qcSPPZbYIEU5GkfR5qhGYEVEREREUtEvv9i04WbNbB1sgwbw669BRyWSUCpgJW01bNgw5c9IiaQz5ahIuClHU0DBvppNm9r1jjvCH38EF48kVabmaMIKWOdcPefcp865mc65Gc65i4q4T1fn3DLn3JT8yw2JikdENqccFQk35aiIlGruXLtWASsZJJE9ttcBl3nvv3HOVQEmO+c+9N7P3OJ+Y733hyUwDhEpmnJUJNyUoyJSsr//tutatey6oID13qYUi6ShhI3Aeu9/895/k//xCmAWUCdRjyci0VGOioSbclRESrVqFZQvbxewAva//2D58mDjEkmgpKyBdc41BPYEiurr3dE5951z7l3nXMtivn+Ac26Sc27S4sWLExmqSEZSjoqEm3JUJHU55+Y756blT/OPbwvwVaugcuVNn++4o11rGrGksYQXsM657YBXgIu991ueDvoGaOC93wN4AHitqGN474d579t779vXKpgiIUmXmwsNG0JWll3n5gYdUXTmz59Pq1atgg5jK127diXILS2Uo+lDOZoYylGJF+VoYgSdoxHq5r1vG/ete/LyoFKlTZ+rgM1omZKjCS1gnXPlsRfdXO/9q1t+3Xu/3Hu/Mv/jd4DyzrkdEhmTlE1uLgwYAAsW2LKKBQvs81R78Y23devWBR1CTJSj6UM5WjTlqISFcrRoqZ6jgdMIrCRYGHM0kV2IHfAkMMt7f3cx99kp/3445/bOj+evmB54yRJYuzamQ8jWBg2yk3yF5eXZ7fGQiLPSd999N61ataJVq1bce++9gCVhTk4OzZs355hjjiEv/4e6+uqradGiBW3atOHyyy8HYPHixRx99NF06NCBDh068OWXXwIwePBgTjrpJDp16sRJJ53Evvvuy4wZMzY+bsFZplWrVnH66aez9957s+eee/L6668DsHr1ao4//niaN29O3759Wb16dew/bBkElqOSEMpR5ahyNNyUo+mXo1HwwAfOucnOuQFF3aHM0/xVwKYM5Wgcee8TcgE6Ywk7FZiSf+kFnA2cnX+f84EZwHfABGC/0o671157+RL16uV9ixbef/xxyfeTqDjnvZ0z3vziXNH3nzlzZsTHfv557ytV2vy4lSrZ7WU1adIk36pVK79y5Uq/YsUK36JFC//NN994wH/xxRfee+9PO+00f+edd/olS5b4Jk2a+A0bNnjvvf/nn3+8997379/fjx071nvv/YIFC3yzZs28997feOONvl27dj4vL8977/3dd9/tb7jhBu+997/++qtv0qSJ9977a665xg8fPnzjMXfffXe/cuVKf9ddd/nTTjvNe+/9d99957Ozs/3XX39d7M9S1O8SmORTNUclIZSjylHlaLgpR9MvRyO9AHXyr2vn52qXku4fVY527+59p06bPl+3zvusLO+vuy7yY0iZKEeDy9FEdiH+wnvvvPdtvM35b+u9f8d7/6j3/tH8+zzovW/pvd/De7+v935cjA8KAwfC6tXQvTv06wc//xyXnyfT1a8f3e3RSMRZ6S+++IK+fftSuXJltttuO4466ijGjh1LvXr16NSpEwAnnngiX3zxBVWrVqVChQqcccYZvPrqq1TKX0vy0Ucfcf7559O2bVv69OnD8uXLWblyJQB9+vShYsWKABx33HG8/PLLAIwaNYpjjjkGgA8++IDbbruNtm3b0rVrV/79918WLlzImDFjOPHEEwFo06YNbdq0KfsPGoNAclQSRjmqHFWOhptyNP1yNFLe+1/yr/8ERgN7x+3gq1ZtvgY2Oxt22EEjsCGjHI1vjialC3HSOAd9+sCMGTB4MLzxBjRrBrfeai3FkyjVGzVsaciQzf8/gn0+ZEjsx164MLrbY+G22BPNOUe5cuWYOHEixxxzDG+99RY9e/YEYMOGDUyYMIEpU6YwZcoUfvnlF7bbbjsAKhearlOnTh1q1qzJ1KlTefHFF+nXrx9gsxteeeWVjd+/cOFCmjdvHv8fSspEORo55agEQTkaOeVoeDnnKufv44xzrjLQA5getwfYcgoxbNoLVkJDORpf6VXAFqhYEW68EWbNgh494NproVUreOedpDx8OjZqyMmBYcOgQQM7T9CggX2ekxP7sRNxVnr//ffntddeIy8vj1WrVjF69Gj2339/Fi5cyPjx4wEYMWIEnTt3ZuXKlSxbtoxevXpxzz338N133wHQo0cPHnjggY3HnDJlSrGP169fP+644w6WLVu28SzTIYccwgMPPFAwfYhvv/0WgC5dujBixAgApk+fztSpU8v+g0qZBJqja9faxvO//mqXxYvjcoJNOaocTSd6HY2OcjTUOboj8IVz7jtgIvC29/69uB09L08FbApQjsY5R4ubWxzWS5nW7rz3nvdNmtiE88MP9/7HH6M/RhQaNCh6nUuDBgl92FAJel2A997fddddvmXLlr5ly5b+nnvu8fPmzfNNmzb1OTk5vlmzZv6oo47yq1at8r/++qvv0KGDb926tW/VqpV/5plnvPfeL1682B933HG+devWvnnz5n7gwIHee1sXcOedd272WL///rvPzs72gwcP3nhbXl6eHzBggG/VqpVv0aKF792798bb+/Xr55s1a+b79u3r995771Cv3Yn2kgrr6xKeoxs2eD9vnvcjR3p/zTXe9+njfevW3lerVvQDg/eVK9v/qR49vL/oIu+ffNL7qVNtPVMCKEeVo2EWdY6uWuX95Mnejxrl/b33en/DDd5feqn3l1zi/eWXez94sPf33+/9Sy95P3Gi9/nrv8JMOaocjchOO3l/1lmb35aT433DhpEfQ8pEORpcjjr7eupo3769L9M+QmvWwL33wi232AjIlVfC1VdvPZ8nDrKy7E9zS87Bhg1xf7hQmjVrVlRTCHJzbR3AwoV2NmrIkPiclU4HRf0unXOTfbz3kouTMudoEiUkR1etgvfeg7ffhg8/hEWL7PZy5aBJE2jc2P64a9eG7beHChXsAdesgRUrrIP6okXw008we7YdD6B6dejaFXr3hsMO29RhMkbK0fhRjsZfqTm6ahV8/LHl3JdfwrRpW39DpUq2HnDdOuuNsaU6daBtW9hnH+jcGTp2tLwMCeVo/KR1jm6/PZxxBtxzz6bbLr8cHn7Y8mSL6aQSP8rR+Ik2R8slJaow2GYbK1pzcuz6llvg2Wct4fv2jWuC169v052Kul2KlpOjJJbkiVuOrl8PH3wAzz0Hr79ub5KrV7cmcl272hviVq3s/080NmyAH36Ar76Czz6zgnj0aHtXf+CBcOKJcOyxCTkBVxzlqCRT0TnqObb2GDj5SXjlFZs6ud120KkTHHkktG4Nu+8OdetaHmYVWiW1bt2mqfsLFsCcOVb0fvONLS/y3orX/fe3k0V9+kCjRkn8iWOnHM1A3he/Bnb1ali5EqpUCSY22YpyNH4yp4AtUKeOnQIZOBDOPx+OPhoOPhjuv98aPsXBkCG2Vqdwt7F4NWoQkdjFnKNLl9ritYcftjfDNWvCaadZUdm5s426xiIry0ZtmzSBk06yNynTpsHLL8OIEXDqqXDxxfaYF15oHW5E0kjhHHVs4Che5Tr3P9r+8S28UXXTSZwuXSI7QVSunM1+qF3bRl0LW7rURnELRnQvvtgue+0Fxx8P/fvbeweRsFmzxk54FlXAgq2DVQEraSg9mzhFoksXO/N6//0wcaKdub3iCpvKF6NENmpIJak2PT2M9DtMjDLn6JIltvSgfn246ioboRk1ykZ1HnrIRl1jLV6L4hy0aQM33wzffw+ffw6HHgoPPAC77WZF7pw5UR9Wf1+x0+8wMQpy9Jgdx/I1HXiZY2m0U57d+Ntv8NhjcNBB0c9uKEq1ajbqevfdMHMm/Pgj3HmnnUi64gqoVw969rRcX7Mm9seLgv6+YpfWv8OCpSZbzsYpXMBKQqX131eSlOV3mLkFLNgbzQsugLlz4eSTYehQaNrURjhi/IPMyYH58+3E2Pz5mVe8VqhQgb/++kuJHQPvPX/99RcVQrQmK51ElaOrVlnx2KgR3HGHvdn95hv49FMbBYrHm+hIOWcn4EaMgHnz4KKLbDplixZw5pnwyy8RHUY5GjvlaAItXUrOR6fx0h9d2KveYnj2War+PAPOOst2GkikXXe1NYQTJ9r7g+uus8K2Xz87eXX99XbSKsGUo7FL+xwtKGCLG4H9/ffkxpNhlKOxK2uOZt4U4qLUrg1PPmnzlc47z97JPvooPPigjXpI1OrWrcuiRYtYvHhx0KGktAoVKlC3bt2gw8hc3sMLL9ib2V9/haOOsvXzLVoEHZmpWxfuustGg2+91UaBR460LhGXXQbbblvCtypH40E5mgCffWYnlX/91f62r79+6zfoybL77nby6sYbbb37ww/b/ObbbrPpxVdckbD3CcrR+EjrHC1YB7NlftSubdd//pnceDKMcjQ+ypKjKmAL22cfa5ry1FNwzTWw555W0N58s00xkoiVL1+eRinWAENkMz/+aCe1PvnE1sKNGmXNYsKodm1rSHfhhVZsDxpkTeoef9xGa4ugHJXQ8d6m7l5zjU2NHz8eOnQIOiqTnW3T9g891P433H+/nfh+/nno1ctGaTt2jOtDKkelVMWNwNaoYdd//53ceDKMcjQ4mT2FuCjZ2TZFae5cOOccG9Fo0sSK2kzZA0ckk23YAPfdZ+viJ02yEZevvgpv8VpYo0Y2nfi992y7sAMOsJNwBW9yRMLqv/+sMdNVV8Exx8DkyeEpXrfUuLH9j1i40GZkfPUV7Lcf9OgB48YFHZ1kkuLWwFaoYLepgJU0pQK2ODVq2BTiyZOtgD3jDDu7+vXXQUcmIonyyy9wyCHWgfTAA23d2znn2ImtVHLIIda1+OKLrQDfc0/975LwWr7cRjFHjLDpuS+8YNvjhF2NGjbyOn++jRx/952d6OrdG779NujoJBMUNwIL1h1fBaykKRWwpWnbFsaOtX0eFy60acYDBoDmu4ukl3ffhT32sBGUxx+HN99M7a0zKle2acWffAL//mtvrO++O+YGdSJxtWyZjVyOGQPDh8O118Z1X/ak2G47m7r/00+2Nnb8eGjXzvppzJsXdHSSzopbAwt2gkUFrKQpFbCRcG7TNhWXXgpPP22jsg89ZJuji0jq2rABbrjBRoDq1LHuwmeemXpvoovTrRtMmWKjQpddZl2T47BdmEjMli+34vWbb2zq+4knBh1RbCpXtinQ8+ZZIT56tO0vf8UVttesSLwVN4UYVMBKWlMBG43tt7etdr77zpq6nH8+tG8PX3wRdGQiUhbLlkGfPraO7bTTYMIE20or3dSoAa++av+/XnvNZpL88EPQUUkm++8/6NvXiteXX7Y8TBdVq9pU6O+/t1HYu+6ybsaPPQbr1wcdnaSTkqYQ16gBf/2V3HhEkkQFbFm0aAEffggvvWRnt/bf30Zof/st6MhEJFLz59u02vfft9kUTz6Z+P0lg+ScjcB++KFtbr/PPrZdiUiybdgAp5xi09ufeiq9itfC6tSxn2/yZGjeHM4+2xpTqdGTxIumEEuGUgFbVs5Zp8RZs2zLilGjbFrx0KGwZk3Q0YlISSZNsgLul1+sgD333PSZMlyabt1g4kTb6L5HD2ucI5JMN98ML74It99uJ3/T3Z57wuef2x7Nf/5pJ87OPBOWLAk6Mkl1JU0hLmjipL4HkoZUwMaqcmX4v/+DGTNsy4orrrBGMB99FHRkIlKU99+Hrl1ttHX8eOs2nGkaN4Yvv7StPwqmOIokwyuvwE03wamn2utlpnAOjj8eZs+2n/vZZ2197LPPqsCQslu1CrbZBsqV2/prNWrYdmraRk3SkArYeNltN3jrLetcunYtHHywjdAuWBB0ZCJS4KWX4PDDLV/Hj7c3kJmqenUr5o891jqoXnut3khLYs2da4XrvvvCo49mzqyHwrbbDu64wxqrNW1qv4+DDoIffww6MklFeXlFTx8GK2BB04glLamAjbfDDoPp060pzDvv2LqXW26xbSxEJDjPPmsjIHvvbWs/d9456IiCt+22Nq1xwAC49Va46CIVsZIY//4Lxx1no0WjRtnfXiZr2dK26Hv0UVvS0Lq1bXOlJk8SjVWrSi9g1chJ0pAK2ESoUME2N58927auuOEGe7F6662gIxPJTE8/bV2GDzzQRh2rVQs6ovDIzrY30ZdeCg88YOuBN2wIOipJN1deaR38n3sO6tULOppwyMqCgQNh5kzo3t2arO2/v41Ui0Ri1aqi17+CrYEFjcBKWlIBm0j169uUxY8+srPNhx9uBa22rxBJnmefhTPOsGn9b7xR/NnqTOacNaC76iorZi+4QCOxEj8ff2wnRy680F4DZXN16tj/puHD7cR327b2+9KJJClNJCOwKmAlDamATYbu3W29y9ChNmWoZUvrXJzEhfW5udCwoZ3wbdjQPhdJey+8AKefbmvMXn891NvkBJ6jztk04ssvh4cfttEgFbESq2XLbPZDkyb295WiEp6fzsGJJ9oSpG7drNjv2dM6pYsUR2tgJUOpgE2WbbaxN4Rz5tg6oP/9z9bHvvRSwt8k5ubaErcFC+yhFiywz1XESlp76y17Q9i5M7z2mk3tD6nQ5Khz1mDmggvgnnusw7pILK691oqw554rfqpjyCU1P3fZxf53PfaYdQpv0wZGj07AA0laKGkEtnp1u1YBK2lIBWyy7byzTRMaO9bOjh13nE1tnDkzYQ85aNCmva4L5OXZ7SJpaexY667btq11Bg/5G+dQ5ahzcO+9cMoptn7/oYcCCELSwoQJ8MgjdkJkn32CjqbMkp6fzlmF/O230KgRHHUUnH02rF6doAeUlFXSGtiKFe2iJk6ShlTABqVzZ5g82d4cTp5se8dedhksXx73h1q4MLrbRVLajBnQpw80aADvvgvbbx90RKUKXY5mZcETT8ARR1jx8corAQUiKWvdOmtQVKeOdeJPYYHlZ5MmMG6c7Rv72GPWQX3WrAQ/qKSUkqYQgzVy0gispCEVsEHKzraOn3Pn2hqhe+6xfeGGD4/rtOL69aO7XSRl/forHHqoTRd+/32oVSvoiCISyhwtV8622Nl3X8jJgS++CDAYSTmPPgpTp8L990OVKkFHE5NA83ObbWxa/7vvwh9/QIcOWv8jm5Q0hRhspp8KWElDKmDDoFYtGDYMvvrKXhFPPtla6X/7bVwOP2TI1jNMKlWy20XSxsqVtg/zP//YHswNGgQdUcRCm6MVK9oU7AYN4Mgj1UFdIvPXXzb9/KCD7O8mxYUiP3v2tPcE7drZ2v7zzoM1a5IYgISSCljJUCpgw6RDBxg/Hp580kZl27e3EdoY//nk5Fh93KCBLa1p0MA+z8mJU9wiQVu/3t7UffcdvPgi7Lln0BFFJdQ5WrMmvP22fdy7t50gECnJjTda9+F77rE/6BQXmvysUwc++WRTp/ADDrBZJ5KZvC95DSxYAas1sJKGVMCGTVaWbfsxdy6cf76te2nSxF4t168v82FzcmD+fNtWbv78kLwxFomX666zbXLuuQd69Qo6mjIJdY7utpt1cp43D/r1s/WNIkX5/nubPjxwILRqFXQ0cROa/CxXDu6803YwmDYN9trLTnxL5vnvPytitQZWMpAK2LCqVg3uu8+mDLVoYW8G9tnHujqKyCYjR8Jtt1nXzgsuCDqa9NW5sxUmH35oI0AiRbn+eluDfuONQUeS3o45xt4PVK4MXbvaNkWSWVatsutIphBrT29JMwkrYJ1z9ZxznzrnZjrnZjjnLiriPs45d79z7gfn3FTnXLtExZOy2rSBzz+3pg2//QYdO9oI7Z9/Bh2ZpLi0yNEpU+CMM6y4euCBtJiuGGqnnw4XXWQn14YPDzqatJdyOTp5sk3hv+QS2HHHwMLIGK1aWe+Mzp1t26trrrEhYskMBQVsaVOI16zZeh8okRSXyBHYdcBl3vsWwL7Aec65Flvc51Bg9/zLAOCRBMaTupyDE06A2bPhyivh+edtWvH992sqn8QitXP0779tf8QaNeDll61bpyTe0KE24lOwT6UkUmrl6PXXWz5qhD55ataE996zWVq33WZT/LVfbCg557Kdc986596KywELitLSRmBB04gl7SSsgPXe/+a9/yb/4xXALKDOFnc7AnjOmwlANefczomKKeVVqQK3327rXvbZx0ZC2rWzEVqRKKV0jm7YACedBIsWWfGq0Z7kKVfORtl22AGOPlpNnRIopXL0669tq5crroCqVZP+8BmtfHl45BE7ufTKK9b9WY17wugiLIfjI9IpxKC/B0k7SVkD65xrCOwJfLXFl+oAPxf6fBFbvzjLlpo2tTOur74Ky5fbaEj//vZmXqQMUi5Hb7vNtsq5917bp1SSq3ZtGDUKfv7Z9rDW+qqEC32O3nyzvVk+77ykP7RgM7Uuu8zycvJk2G8/WLAg6Kgkn3OuLtAbeCJuBy0Yaa9Ysfj7FBSwOtEoaSbhBaxzbjvgFeBi7/3yMh5jgHNuknNu0uLFi+MbYKpyDvr2hZkzbb+90aOhWTMbodXecBKFlMvRMWNsquLxx8M55yT2saR4HTtaN9TXX4e77w46mrQW+hz95ht46y249FKbKSTBOeYY+Ogj65Ox3342Y0vC4F7gSqDYRcpR52jBe72Sls9Ur27XmkIsaSahBaxzrjz2opvrvX+1iLv8AtQr9Hnd/Ns2470f5r1v771vX6tWrcQEm6oqVYKbbrJCtnt3uPpqaN0a3n8/6MgkBaRcji5ZYrMNGje2raXUtClYF11kJ9KuvtqmkErcpUSO3nabTRs+//z4HlfKpnNnO9EHtlfsxInBxpPhnHOHAX967yeXdL+oc3TtWrsuX774+2gEVtJUIrsQO+BJYJb3vrjT828AJ+d3UdwXWOa9/y1RMaW1XXe1kZB33rHpfD172hvLefOCjkxCKuVy1HvrgrtkiU2T00hP8JyDJ5+EXXaxEfFly4KOKK2kRI7+9JOtuzz7bK19DZPWreGLL2wErnt39coIViegj3NuPvACcKBz7vmYjxpNAasRWEkziRyB7QSchCXqlPxLL+fc2c65s/Pv8w7wE/AD8DhwbgLjyQyHHmpThv73P/jgA9tD9qab1JVQipJaOfrQQ/DmmzZttW3bwMKQLVSvDiNG2Ho7jcDFW/hz9O67ITsbLrwwqQ8rEWjUCMaOhXr17L3Bxx8HHVFG8t5f472v671vCBwPfOK9PzHmAxdMIS6pgK1Uyb6uAlbSTLlEHdh7/wVQ4vw+770H1PEh3rbd1vaDO/FE6wg5eDA88wzccw8ccYSmXQqQYjk6Y4b9LffqBRdcEHQ0sqVOnWxd8uDB9hz17x90RGkh9Dn611/w1FP2WrPLLoGEIKXYZRf47DPrTHzYYfDGG3DwwUFHJfFQMAJb0hpY52wUVlOIJc0kpQuxBKRePXjhBfjkE2uz3revnYWdOzfQsHJzoWFDyMqy69zcQMORsFuzxt4gV6lib5Z1AibhypSjgwZZY6dzzoGFCxMcoYTC44/b7J5LLw06kowTVY7Wrm3vA5o0gT59NBIbIO/9Z977w+JysEimEIPNktEIrKQZFbCZoFs3+PZb23Jk/Hho1cpGaFeuTHooubkwYIDNNvTergcMUBErJRg8GKZMgSee0H6vSVDmHC1XDp5/Htats7XKG4pttinpYN06ePhhOPBAe02RpClTju6wg3Un3m03OPxwm1osqS3SAlYjsJKGVMBmivLlrWPo3LmQk2NdI5s1sxHaJO7hOGgQ5OVtfltent0uspUJE2xrqDPOsJEDSbiYcnTXXW1N5Mcf25plSV9vvGH7AGtKf9KVOUdr1bLcrF8feveGSZMSFqMkgUZgJYOpgM00O+4ITz8N48bZx/372wjt9OlJefjiZhZqxqFsJS8PTjkF6tbVPqNJFHOOnnWWLVW46ir44Ye4xSUhc//90KCBjeZJUsWUo7Vr20hszZq2W8Hs2XGNTZIomhFYFbCSZlTAZqqOHW1vuEcfta7FbdvCxRfD0qUJfdj69aO7XTLYDTfYjIGnnoLttw86mowRc446Z2sjt9lGU4nT1cyZti3LOedYB2JJqphztG5d+PBDe+4OOQQWLYpbbJJEBV2IS2riBJpCLGlJBWwmy86GgQOtSDjrLDuj3rSpdSxO0JvOIUOsq3thlSrZ7SIbTZhgXbMHDrQ9DCVp4pKjderYmvuxY+HBB+MZnoTBE0/YqM9ppwUdSUaKS47uthu8+64VNr16aQ/nVBTNFOLlyzfdXyQNqIAVm0r0yCO2HqZxY3tT0qkTTJ4c94fKyYFhw2zmmXN2PWyY3S4CwH//2chdnTpwxx1BR5Nx4pajp5xiUxSvvRbmz09EqBKEf/+FZ5+FI4+06aiSdHHL0Xbt4JVXYNYsOProTSN6khqimUIMCZ9hJ5JMKmCLkLHbvLRrB198YW9O5s2DDh1sBGzJkrg+TE6OvZ/dsMGugyxeM/a5DrNbb7U3VI89VuzUYT1viRWXHHXOligAnH12mZvF6bkOmVdftfV0AwYUexc9Z4kXt9fRgw+2EfWPP7Yp4WXIUz3fASkoYMuVK/l+1avbtaYRSxpRAbuFjN/mJSsLTj4Z5syxNbFPPmnTih99FNavDzq6uMr45zqMZs6E//0PTjjBGgEVQc9bCmnQwE5IvP9+mZ4gPdch9Pjj0KiRbZ9TBD1nKeiUU+D6663fwF13RfWter4DtHatFa+l7Y1eMAKrRk6SRlTAbkHbvOSrWtU6v373Heyxh52Zbd/euhenCT3XIbNhg73zqVLF1r8WQ89bijn3XNh3X7jkEvjrr6i+Vc91yMyfD599ZlP8s4p++6DnLEUNHgzHHgtXXglvvx3xt+n5DtCaNaVPH4ZNBaxGYCWNqIDdgrZ52ULLlja16IUXbCpxp052tvb334OOLGZ6rkPmqafgyy9tBKCEtXV63lJMdrZNB//nH9taJwp6rkNm+HC7PumkYu+i5yxFZWVZA8e2bW0GzJw5EX2bnu8ArV1begdi2DSFWCOwkkZUwG5B27wUwTno18/WJV5zjRWzTZrYKFkKd7XTcx0if/5pZ/67dLETJCXQ85aC2rSByy6zJQlffBHxt+m5DhHv4bnnbN/wBg2KvZuesxRWqRKMHm1F0ZFHwooVpX6Lnu8ArV0b3QisClhJIypgt6BtXkqw3Xa2PnH6dOjcGS691M7WfvJJ0JGViZ7rELnqKli50tZal7KeR89birrhBntXe+65sG5dRN+i5zpExo2DH34o9QSTnrMU16ABjBpl2+udeWapTZ30fAco0gK2WjW71hRiSSMqYLegbV4isPvutkbm9ddh9Wrbp7NfP/j556Aji4qe65D48kubunbZZdC8eal31/OWoipXhvvug2nT4IEHIvoWPdchMny4VSZHH13i3fScpYFu3exk9ahRtj98CfR8ByjSArZ8eestoRFYSSPOl3Frg6C0b9/eT5o0KegwpMDq1TB0qL3YZWVZ54bLLoNttw06srTmnJvsvW8fdBxFiSpH162z5mB//21T1CtXTmxwEizv4bDDYMwYG+HZeeegI0qYtMlRsGYxO+8MhxwCI0YkLjAJD+9tGvG779pJxg4dgo4o7lI+R3Ny4KuvbGZEaRo0gK5dbZtEkRRRUo5qBFZiU7Gitd+fNcve3AwaBK1awTvvBB2ZpILHHrNO13ffreI1EzhnIzpr1kTd0EkC9OGHdpKpf/+gI5FkcQ6efhp22slmWC1bFnREsqVIR2DB1sFqBFbSiApYiY+GDW2D+w8+sK6jvXtDnz7w449BRyZhtWSJnfzo3r3UaYmSRho3hssvtympabQtV1obOdI6mR5ySNCRSDLVqGFNGxcutK30JFwi7UIMKmAl7aRVAZuba3VUVpZdayPtABx8MEydCnfeCZ9+atvw3HDD1hvFSUYqnKPPN7qeDcuW27rI0jZil/Ry7bVQpw5ccAGsXx90NFLIlq+jLzyVB6+9ZieZIn2zLOljv/1sj9iRI/WmKmyiGYGtXl1NnCStpE0Bm5sLAwbAggW2dGPBAvtc/28DsM02NsIye7a96bnlFmvO8+qrpXY0lPRVOEdb+an0XzmMR7POJXdKy6BDk2SrXBnuuAO++UZrskKkqNfRt855G1at0vThTHbNNbYH/Lnn2h+FhIOmEEsGS5sCdtCgrQf58vLsdglInTr2juizz6BqVStmDznEClvJOJty1HMPl7CUagxad5NyNFP17w8dO9pobAT7TUriFfU6evial1mcVRsOOCCYoCR42dnw/POwYQOcfrpdS/DKUsBqEEHSRNoUsAsXRne7JNEBB9hIywMPwMSJ0Lo1XHGF3rRmmIJcPJw36c4n3MhNLKW6cjRTOWfTx//4w7qYS+C2zMUKrKY3bzN6w5FWxEjmatjQmu198gk88kjQ0QhYM7xoCti1a22/dZE0kDYFbP360d0uSVauHJx/vm2dccoptvVO06Y2Qqszghmhfn3IYj1DuZyZNOcxBm68XTJUhw5w4olwzz2amhgCW+ZiDz5gO1YxpvYxwQQk4XLmmdCzJ1x5Jfz0U9DRSDQjsDVr2vVffyUuHpEkSpsCdsgQ22O9sEqV7HYJkdq14YknYMIEm2J84ok2Qvvdd0FHJgk2ZAhUqJTNGTzJAIaxjvLKUbHRV+dsKrEEasvX0WN4mb+pTq87ugYWk4SIc/D44zYaP2CATj4HLZouxCpgJc2kTQGbkwPDhtlezc7Z9bBhdruE0D772Abcjz9ue8i2a2cdSaPokqeu06mlIEd/brA/41xn5Wiaizg/69WDSy+FESPg66+TGKFsqfDr6Das4Qj3Jv90OZITTolwlEdSSpleQ+vWtQZsH38MTz2V4AilRBqBlQyWNgUs2Ivv/PnWX2D+fL0xDr2sLJuSNGeO7TH38MPQpAk8+WSpTSLUdTo1KUczQ9T5edVVUKuWTU3UqE6gCnL0v/c/Y3u/jMZXHBV0SJIAMb2GDhgAXbrYbgN//pnwWKUY0RSwO+xg1ypgJU2kVQErKapGDXjwQWv01KyZFbX77msNn4qhrtMi4RV1fm6/ve0X/dln8O67iQ5PIvH66zafuHv3oCORBIjpNTQrCx591LZXuvzyhMQnEYimiZNGYCXNqICV8NhjDxgzBoYPh59/tmnGZ54JixdvdVd1nRYJrzLl54AB0LixjcauX5+QuCRC3sMbb0CPHlCxYtDRSALE/BravLnNmBg+HD79NG5xSRSi3UYHVMBK2ihX0hedc+0iOMZa7/20OMUjmc45a+zUpw/cfLNts/HKK3DLLXD22dbNGOuWWVTT0kzraKsclTAqU35us401dOrXz9bDnnRSwuJLppTM0SlTYNEi+78raSkur6GDBlmunneeNWKMtJhKQaHM42gK2HLloGpVFbCSNkosYIHPga8BV8J9GgEN4xWQCGBTCocOhTPOsOZOF1xgDZ8efBD2358hQ2zApvAUqAztaKscldApc34ec4w1dLvhBjjuONh224TGmSSpl6Ovv24nE3v3DjoSSZC4vIZWrAj33gtHHGGvzZdcEu8wwyR8eRxNF2KwacQqYCVNlFbAfu29P7CkOzjnPoljPCKba94cPvwQXn3VXhy7dIGcHHLuuAOG7cKgQTblqX59e+HNwKZAylEJnYI8jDo/s7Lg1lvhkEOsHe4FFyQ81iRIvRx94w3Ybz9rrCVpqcw5uqXDD7e9YQcPhv79Yaed4h1qWJQ5j51zFYAxwLbY++6Xvfc3xhxRNCOwoAJW0kqJa2BLS9ZI7yMSE+fg6KNtu51Bg+Cll6BpU3J+G8r8uWsyuqOtclTCqswdpw8+GLp1g//7P2sSk+JSLkfXrIFGjWwqt6S1uHSFd86W+qxebTMn0lSMefwfcKD3fg+gLdDTObdvzEGVpYBdsiTmhxUJg4ibODnn2jjn+jjnjiq4JDIwka1UrmxvamfOtDe4V1xhjZ8+/DDoyEJBOSppwTnL8z//tGmJaSQlcnSbbazvQHqMfksyNGkC559vW+BNnRp0NAkXbR57szL/0/L5l9j3C4umCzFoBFbSSkQFrHPuKeAp4Gjg8PzLYaV9j3PuT+fc9GK+3tU5t8w5NyX/kr6n7iS+Gje2KW5vvWVnIHv0sBHaojpSZAjlqKSV/faDXr3g9tth2bKgo4kL5aikteuvh2rV4LLL0nov57Lkcf73ZTvnpgB/Ah9677+KKZANG+yiAlYyVGlrYAvs671vEeWxnwEeBJ4r4T5jvfelJr5IkXr3tj0K77rLFu+8+y5cc42NzFaoEHR0yaYclfRy883Qvj3cc4+tr0t9ylFJX9Wrw403wkUXwfvv27rY9FSWPMZ7vx5o65yrBox2zrXy3m92Yso5NwAYAFC/tHbQa9fadbRNnFassJHbaL5PJIQinUI83jkXVcJ678cAf0cfkkgUKlSwdbGzZ1tBe8MN0LIlvPlm0JElm3JU0stee8FRR1kB+88/QUcTD8pRSW9nnw277gpXX22jg+kp6jwuzHu/FPgU2KrC994P89639963r1VaA7WCAjbaEViAv/UvRVJfpAXsc1jSznHOTXXOTXPOxWOhQ0fn3HfOuXedcy2Lu5NzboBzbpJzbtLixYvj8LCSdurXt+ZOH31kW2/06WMF7Q8/BB1ZsihHJf3ceCMsX25FbOpTjkp622YbW7/+3XcwcmTQ0SRK1HnsnKuVP/KKc64icDAwO6YoYilgkz2NeNUquOkm2Hdf2x7tySfTepq5JEekBeyTwEnYGaOC+f6Hx/jY3wAN8ruyPQC8VtwdozorJZmte3d78Rw6FMaOtdHYQYPSoptpKZSjkn7atLG9Ye+9Nx1GDZSjkv769YM997Q1sWvWBB1NIpQlj3cGPs0vdL/G1sC+FVMUBb/bsBews2ZZk6/Bg61B36RJcOaZNrsmPWbWSEAiLWAXe+/f8N7P894vKLjE8sDe++UFXdm89+8A5Z1zO8RyTBHA/qFfdhnMmWMvpv/7HzRrBqNGpfNZP+WopKcbb4SVK+Huu4OOJFbKUUl/WVnWk2LePHj66aCjSYSo89h7P9V7v6f3vo33vpX3/uaYo0iFEdiVK63B5rp18OWXMH48/Pij/S9/6y1r1Pfff8mJRdJOpAXst865Ec65/vFq/++c28k55/I/3js/FrVHk/jZeWd47jkbia1Z04rZgw6ybXjSj3JU0lOrVjYK+8ADqX7GXjkqmaFnT+jY0aYT//tv0NHEW9zzuExSoYA95xzrTzJypHWWBxuFveQSu23CBG3XJWUWaQFbEduIuQeRt/8fCYwHmjrnFjnnznDOne2cOzv/LscA051z3wH3A8d7n77DYxKgzp1h8mR46CH49lvbO/ayy2xtXfpQjkr6uu46y9f77w86klgoRyUzOAe33AKLFsHjjwcdTbxFnccJUdYuxJCcAnb8eHj+eZtKfuCBW3/9mGNs14jHH4fhwxMfj6Qdl2qvde3bt/eTJk0KOgxJVUuWwLXXwhNPQO3acMcdcOKJNu0phTjnJnvv2wcdR1GUo5IQffvCZ5/B/PlQtWrQ0ZRKOSoZzXvo1g2+/96mjYZwa7uUztEZM2x2yqhRcOyxkR3Ue6hYES680N77JFKfPjBunP2/3m67ou+zfj3sv7/9jcyZAzVqJDYmSTkl5WiJ79rz96Qq7eCl3kckNHbYAYYNg4kToWFDOOUU+wf67bdBR1YmylHJGNdfD0uXwsMPBx1JVJSjkpGcs23tfv01LdbChi6PyzKF2DkbhU30COzUqbaV4UUXFV+8AmRnw6OP2tKQq69ObEySdsqV8vWrnXNLSvi6Ay4ChsUvJJEkaN/ezg4++yxcdZXtOXn22bZmJ7XOAipHJTO0aweHHmpb6lx0EVSqFHREkVKOSmbq1s3WPt52G5xxRnTTXcMnXHlcli7EkJwC9vbbrXA9//zS79umja2JHTqUw98cwNt/tKd+fesDlpOT2DAltZVWwH5O6e3BP4xTLCLJlZUFp51mUxNvvNHWyI4aZV2LzzjDzg6Gn3JUMsegQbam/fHHrYhNDcpRyUzO2cyJQw+1dY5nnBF0RLEIVx6XZQQWbBbakpLq8BgtXQqvvAIDBkD16hF9y4tNb+BAnuHi36/iLT5iwQLHgPyxbBWxUpwSC1jv/WnJCkQkMNWqwX332YvrBRfAwIE2zfiBB6yTYogpRyWjdOoEXbrAnXfajIlttw06olIpRyWjHXKIzXC64w449dRUOTG8ldDlcVkL2Nq14Ztv4h9PgdGjbWucKCrPq/6vCkdwHfdxMQfzIR/Sg7w8O1+pAlaKk1qda0QSqU0baxIzYgT89ptNfTrtNPjjj6AjE5ECgwbBL79Yh0sRCTfn4MorYe5ceP31oKNJH2XpQgxWwCbyPc3IkbDrrrD33hF/y8KF8ChnM4+G3MbVgN94u0hxVMCKFOYc9O9ve5dddRXk5kKTJjZCu25d0NGJyMEHw5572ojO+vVBRyMipTn6aGjc2NZGptjOF6FV1hHYHXe0LckSsT/v77/Dxx/beyjbnjoi9evDGrblRm6iHd9yJK9tvF2kOCpgRYpSpYo1npg2zaYRX3yxvWn+/POgIxPJbM7Z/oFz58JrrwUdjYiUJjsbLr/cuv/rNTQ+ytrEaccd7frPP+MbD1gPkQ0b4IQTovq2IUOsJ98ITmAuuzOYwVSuuIEhQ+IfoqSP0po4AeCc2xY4GmhY+Hu89zcnJiyRkGjaFN59194oX3IJdO0Kxx9va/Dq1g06uo2Uo5JRjjoKdtsNbr3VPo7ibH9QlKOS0U45xRo63XOPvY6mqNDkcSxrYMGmEcd7iPP116FFC7tEoWCd66BB5bhlwQ0M5yTeOWs0XXKOjm98klYiHYF9HTgCWAesKnQRSX/OWafimTNtX7vRo6FZMxuh/e+/oKMroByVzJGdDVdcAZMn27r11KAclcxVsSKcc47tDzp3btDRxCIceRzLFGKI/wjsypUwdiz06lWmb8/JgfnzYfi6/tC0KV0+GWyjuSLFiLSAreu97+e9v8N7f1fBJaGRiYRNpUpw001WyB50kE1jbNMG3n8/6MhAOSqZ5uSTbTThzjuDjiRSylHJbOeeawXXffcFHUkswpHH8RiBjadPP7WYDj00tuNkZ9tAwfTpth1PAHJzoWFD22mxYUP7XMIn0gJ2nHOudUIjEUkVu+5qU4rffdcaUvTsaSO08+YFGZVyVDJLhQpw4YWWh1OnBh1NJJSjktl22snWRz7zDPzzT9DRlFU48jiWLsQQ/xHYd9+FypVtq7NY9esHzZvbgEGSR2Fzc20L2wUL7O3dggX2uYrY8CmxgHXOTXPOTQU6A9845+Y456YWul0kc/XsaU2ebr0VPvzQ1n3cdBOsXp20EJSjktHOOcfeNN0V3oFM5ahIIRddBHl58PTTQUcSldDlcVlHYCtXtks8R2C9twK2e/f47M1dMAo7Ywa89FLsx4vCoEFQLm8Z+zCBw3iT9nzN2rw1DBqU1DAkAqU1cTosKVGIpKptt4Wrr4YTT7Qui4MH29nle+6BI45IRnMZ5ahkrho14Iwz4JFH7ETSLrsEHVFRlKMiBdq2hc6d4aGHrLt/VspshhGuPC5rF2KwdbBxGoHNzYUnrvyeT3+dz3XLr6R57qamTDE59lhrT3z99daoryw/Z7S+/po7F9zB4bxJBTb1N1lNBZ5ZcBr8el1YX2MyUon/Obz3C0q6JCtIkdCrWxdeeAE++cTObvbta2tB5sxJ6MMqRyXjXXyx7Qf7wANBR1Ik5ajIFs4/H376Cd57L+hIIha6PC7rCCzYNOI4jMAWTLdt+esHAIz4+5D4TbfNzob//Q++/z7xo/XLl9sPss8+dM/6lMcYSG/eYm++4lhGkUsOZ/I47L67bRUkkVu3zpYNjBwZ90OnzKkvkZTQrRt8+y3cey+MHw+tW9sI7cqVQUcmkp4aNbIz9I8+qjwTSQV9+8LOO4f2pFNKiKWA3XHHuBSwgwbZbPAD+JwF1Gceu5KXR/ym2x52GOy3ny3NysuL00G3MGMGdOgATz0Fl17Kh4/+xLWV7uMdevM1e/Myx3JRpSd45+450K6drc+96SabNi2lGzrUitcnnoj7oVXAisRb+fK2zmfuXJtLc/vttu3OCy/on55IIlx6KSxdmnLr6kQy0jbb2IjX++8H3fwwdcU6AhuHKcQLFwJ4ujCGzzlgi9vjwDnbrvDXXxPTbf6jj2DffWHZMps9N3Qo/c7anmHDoEEDe/gGDWDYMDjikl3t/qecYkvFhg6NfzzpZvp0uPFG+xv96isbjY0jFbAiibLjjvaGetw4+7h/fxuhnTYt6MhE0kvHjvZG5L77tHegSCo480yrEB5/POhIUtPatbZ+ODs7+u/dcUdYssSWXsSgfn1oxmx25M/NCtj69WM67Ob23x+OO84K2fnz43fcl16yPWsbNbL9xLt02filgj1pN2yw641rerfd1kZq+/WDK69Ua+LSXH89VKliTRZXrYLvvovr4VXAiiRax44wcaJNcZw2Dfbc00Zoly4NOjKR9HHJJfDjj/DWW0FHIiKlqVsXeve2gqBgNFEit2ZN2Rsb1a5t1dlff8UUwpAhcHD5zwEYgxWAlSrZ7XE1dKgV65dcEp/jDR8Oxx8P++wDY8ZAnTqRf29WFjz7LHTtCmedBTNnxiemdOM9jB1rzUz79rXbvvwyrg+hAlYkGbKzYeBAm1Z81lm29qdpUxuh1YiRSOyOOgrq1bMO4CISfgMH2lrM118POpLUs3Zt2QvYHXe06xjXwebkwKXtx/BH9s78yG4bp9vGpQtxYfXq2Wjea6/Fvq3Ok0/aNOBu3ayJWLVq0R9j221tXWeVKjYam8StE1PGjz/aCZJ997WTVfXrwxdfxPUhVMCKJFPNmrblx6RJ0LgxnH66NSmYNCnoyERSW7lycMEF8NlnMGVK0NGISGl69rQ3tsOGBR1J6olHARvrOljvabjgc3Y89gA2eLf5dNt4u+wyGzEdOBAWLSrbMR5+2Kau9+gBb75pO0aU1U47wXPP2TrPa64p+3HS1Vdf2fU++9h1p042AhvHPjAqYEWC0K6dnY169llbZLH33tbUYsmSoCMTSV1nnmlz2NTdVCT8srPhtNOsOc4C7SgVlVgK2Nq17TrWTsQ//WQNlgqtH02Y8uXh+edt6nRODvz3X+nfU8B72yf8vPPg8MNtxL9ixdhjOuQQOPdcuP9+WyYmm0yYYCcIWra0zzt1sr+VOOa5CliRoGRlwckn216xF19sa4GaNLER2hibK4hkpOrVLadyc2Hx4qCjEZHSnHqqFRjPPht0JKll7Vrr5lwWcZpCvHFN4/77x3acSO22m43Wjxlj/+cjeZ+0dq3tO3zttdZI8+WXbQpwvPzvf7Yl1Jlnai13YRMm2MBMQZOx/faz64KR2ThQASsStKpV4e67rUPbnnvaGb1PPw06KpHUdMEFdnZe3U1Fwq9hQ+jeXf0gohXLCGy1ava9v/8eWwzjxsH220OLFrEdJxonnGBb6owaZSc/Cu0Pm5trf05ZWXb92n0LrNnSww/DFVfYCG5Zi/7iVK0KDz1kDTrvvz++x05Vq1fbMp6C6cOwqTV1HPYfLqACViQsWra0qVSffmov6CISvRYt4OCD7U2LzoiLhN/pp9tSGp24jVwsXYizsqzzblnXkhYYN852WchKcilx+eVwyy1Wse6zD3z8MbnDNzBggM1Qreb/5vQFN9D94las/WYavPAC3HFH4uI84ghbz33zzXHZXzflffut7fm6776bbitolhVj5+vCVMCKxMGWZ/7KvD2Yc3bG0Ln4BSeSaS64AH75ZbPupnHLURGJr759oWpVfhr8nHI0UrGMwIJ19v3557J//7Jl1sCoYGposl13Hbz7rhWMBx3EAac14p28A5hMO5awAzdwC+9yKAfWmGKdghPJOet+n5dncWW6LRs4gU0lrlYN/v47bg+jAlYkRrm5bDzz571dDxigF1+RwPTqZe+AH3wQUI6KhFrFinzf7jhqf/EKSxasVI5GIugC9quv7J9pUAUsWBOlBQvg+eeZsL4DHsff1OBmbmAPptCPUXz5267JiaVZMztx+sQTthwsk02dal2ad9pp89tr1lQBKxImgwZttgwDsM8HDQomHpGMl51ta8k//xymTVOOioTcNTNPZjtW0ZfRG29Llxx1ztVzzn3qnJvpnJvhnLso5oPGWsDWr29TiMvaMHLcOBsq33vvsscQDxUqQE4Olzd4mW58xsF8xE0MZip7AJuWXibF9dfbKONVVyXxQUNo+nRo1Wrr22vU0BRikTBZuDC620UkCU4/3d7cPPigclQk5F79oxM/0YiTGL7Z7WmSo+uAy7z3LYB9gfOcc7F1PoqlCzHYCOy6dWVvqjNuHLRubU2cQmDIENtBrbBKlez2pKle3YrY99+HDz9M4gOHyIYNMGOG/W1sSSOwIuFS3Bm+pJ75E5HN1axp2yY8/zwt6y4r8i7KUZFwqN/AMZyTOIiP2IVfNt2eBjnqvf/Ne/9N/scrgFlAnZgOGksTJ9j0iy3LNOL1622blI4dy/74cZaTYzvsNGhgS1IbNLDPc3KSHMi550KjRtb1OBO7av/0k3Uh1gisSPiF4syfiGzt3HMhL4+nug1XjoqE2JAh8EqFE8nC048XgfTMUedcQ2BPYKsNMZ1zA5xzk5xzkxaXto91PNbAQtkK2BkzYMWKYNe/FiEnx5pZb9hg10kvXsH2mP3f/2wdbCYu4J4+3a6LKmA1AisSLqE58ycim2vfHjp0oMPXDzPsMa8cFQmpnBy46ond+W6b9vRnZFrmqHNuO+AV4GLv/fItv+69H+a9b++9b1+rVq2SDxavArYsc7THj7frEI3Ahspxx8Fee7Hqkuto2uDfzOqqXVDAFrU3cI0asHSpTV2Pg4QVsM65p5xzfzrnphfzdeecu98594Nzbqpzrl2iYhFJtFCc+YuSclQywrnnwqxZ5NT9XDkqEmI5ObDHrf3pwCTmf/h9SuRopJxz5bHiNdd7/2rMB4y1gK1eHSpXLtsI7PjxsMMO0Lhx2R8/nWVl8VGPO6j810IOX/hgZnXVnj7dplBvt93WX6tZ066XLo3LQyVyBPYZoGcJXz8U2D3/MgB4JIGxiMjWnkE5KumuXz878/tISv75PoNyVDJJv342TWLkyKAjiRvnnAOeBGZ57++Oy0FjLWCdK/tWOuPH2+ir9qsv1pkjDuQdDmUQQ6iBrftMl67aJZo+vegGTmCvwxC3acQJK2C992OAkqI8AnjOmwlANefczomKR0Q2pxyVjFCxIpxyCoweXfaOmwFRjkrGqVMHunSBESNsn9H00Ak4CTjQOTcl/9IrpiPG2oUYrICNdgrxX3/B3LmaPlyKhQvhCu5ke5ZzAzdvdntC/f473HIL9OwJXbvC2WfDtGkJftB8a9bAnDlFr3+FTSOwcWrkFOQa2DpA4VM/iyimK1tUC9tFJF6Uo5IeBg60N3xPPx10JPGmHJX007+/vRFO1hvvBPPef+G9d977Nt77tvmXd2I6aIxdiHNz4YXx9fnt65+jW585YYJdq4AtUf36MJOWPMGZnMvD7M7cjbcnxPr1VrjWrw833AB//mm3PfsstGnD7EMupFGDDYldjztnjq1vLa6ATZUR2HiKamG7iCSdclRCrWlT6NYNHnssM7c2QDkqKaRvX8jKgpdeCjqS8IphCnFurq3HnLWyHjvyB78uWBP5+szx4yE7Gzp0KNNjZ4qC3Slu4GZWU5F7uIRKFX1iumr//TccdJAVrsccYyPk33wDY8fCokXM7nEhzT54gFsXnkA5vyZx63FL6kAMaTUC+wtQr9DndfNvE5FwUI5K+jj7bOve9P77QUcST8pRST+1a9v0x1Gj0mkacXzFUMAOGmTrMX+mHll46vBL5Oszx42DNm2sAZQUq2B3iooNduRmbqQ37/D2OW/FvzHZ77/DAQfYiYVnnrGp97vvvunrNWvSc859XMEdHM+L3ML1QILW406bBuXK2QnjoqTRCOwbwMn5XRT3BZZ5738LMB4R2ZxyVNLHkUdCrVrw+ONBRxJPylFJT8ceayNJaTKNOO5iKGAL1mEuxOaz1mfhZrcXa80am0LcuXOZHjfTFOxOMXTNhdCiBV1HXwSrV8fvAf74w4rXefPg7bet10MRFi6EoVzBowzkCu6kG59svD2upk2DZs2KX5tdtarNrAj7CKxzbiQwHmjqnFvknDvDOXe2c+7s/Lu8A/wE/AA8DpybqFhEZGvKUcko22wDp50Gb7wBv6VGjacclYx11FGaRlySGArYgnWYP7ErALvxw2a3F+vbb60A23//Mj1uxipfHh580ArN66+PzzH//hsOPhgWLbJZRd27F3vXguf1Mu5iLk14jpPZjhXxX487bVrx04fB8rl69fCPwHrv+3vvd/bel/fe1/XeP+m9f9R7/2j+1733/jzvfWPvfWvv/aRExSIiW1OOSsY580xrbJEizZyUo5Kxate20aWXXw46ktDJzYW1eWu49a5tytSQp2B95gIakEdFWjCTSpUofX3m2LF2rQI2et26WTPBu++2adixWLUKeve2pkmvvw6dOpV494LnO4/KnMbT1OUXrip/d3zX4y5fbpvdFreFToEaNcJfwIqIiITK7rvDgQfaNOIMbeYkkjKOOgpmz4ZZs4KOJDRyc2HAWZ7yrGMN5cvUkKdgfWa9BtnMphntK8xg2DBKX585dizsthvstFNMP0PGuvNOGw499VQr+Mriv/8sLyZOhBdesOZNpSh4vhs0gK9cR96pdDRXZQ8l5+A/yxZDUWbMsOvSCtiaNcM/hVhERCR0zjrLFiZ9/HHQkYhISY480q5Hjw40jDAZNAjWrF4HwFpsCnFZGvIUrM9sd2JLDthhRunF64YN8OWXGn2NRZUq8Nxz8NNPtl41gpOoubm27U1WFuzWYC0LO/eHDz6AJ56wbt0RKni+N2yAXt8Mofza1fB//1f2n2VLBWvVNQIrIiKSAEceaS+iTzwRdCQiUpK6dWHvvVXAFrJwIawnmx68z0j6b3Z7mbRsaesoSxsRnD3bRs7UwCk2XbrA0KHw2mtw880l3rVgu6MFC6CcX8MdC/tRf9Jovj75fuvnUFZNm9r3DxtmjaDiYdo02G670hdSawRWRESkDCpUgJNOsjfFS5YEHY2IlKRvX5g0CX7+OehIQqF+ffBk8SE9+InGm91eJi1b2vXMmSXfT+tf4+eii2wE9qabShwFLdjuqAZ/8QE9OIrRXMh9HPv5BbHHcPnlNh354YdjPxbYHrCtWtlQcUk0AisiIlJGZ5xhXTyHDw86EhEpyVFH2bVGYYFNDXkKi6gBU3FatLDrgjWMxfnoI9hlF1sDK7FxDp580k6kXn+97VG+YsVWd1u4ELrxCRPZm46MJ4fneYAL47P9TdOmcPjhVsDGurWP9zYCW9r0YbAR2OXL7fU3RipgRUQks7RuDfvsY9OIvQ86GhEpTpMm0Ly5bX8lmzXkcc6uI2rAVJxGjaBixZJHYNetswK2Z097UIlddrZ1w7/8cnsCW7WC226Db76x0cwXX+T9Cn34hO54HN34lBHYkxy37W8uu8xmIT33XGzH+fVXmxbcpk3p961Rw67/+Se2x0QFrIiIZKIzzrA3bRMnBh2JiJTk8MPh889h2bKgIwmFwg155s+PoXgFm/LZvHnJI7ATJ8LSpVbASvxkZ1tn4i++sNHta66BvfayE6zHH0/nbSYypNyNtGYa49kPiHG0fUtdukC7dvDQQ7GdyP36a7tu3770+1avbtcqYEVERMqgXz8beXjqqaAjEZGS9Oljo4DvvRd0JOmpZcuSC9j33rNCN4ItW6QM9tsPxo+3OcMvvgijRsHYsVT86xcaPjOYHRtUjM9o+5acg3POsem/48eX/TiTJkG5crDHHqXft2JFu4512jIqYEVEJBNtvz0ceyyMHGmdMkQknPbdF3bYAd58M+hI0lOLFtaJuLhRsffftyUXBaNnkhj16sFxx9nrUufOkJ0d39H2ohx/vG3v8+ijZT/G11/bFOiC4rQkKmBFRERidPrp1jzjlVeCjkREipOdDb17wzvv2EisxFeXLnb94Ydbf23JEitQNH04PW23nTWTGjWqbNvbeG8jsJFMHwYVsCIiIjHr0gUaN9Y0YpGwO/xwGyH84ougI0k/++xj3WHfemvrr732mhUphx6a9LAkSQYOtC11ytLMad482xanQ4fI7q8CVkREJEbOwamnwmef2QuxiIRTjx5QvryNwkp8ZWdDr172u12/fvOvPfaYrZGNdIRNUk+bNnYS4/HHo2/mFE0DJ1ABKyIiEhcnn2yFbKxbCYhI4lSpAvvvrwI2UQ47zKaQTpiw6bZvvrHpoQMHavucdHfWWTBrFowbF933TZoE224b2R6woAJWREQkLurXhwMPhGeftU4ZIhJOvXpZt9wFC4KOJP0ccoh1ki08jXjYMCs4TjopuLgkOfr1s/Wwjz8e3fd9/TW0bWuzIyKhAlZERCROTjvNphCPGRN0JCJSnF697Prdd4ONIx1VrWo9AXJz4fff7URBbq4VNtWqBR2dJNp228EJJ1gzp6VLI/uevDwbsd9vv8gfRwWsiIhInPTta1MUn3026EhEpDjNmkHDhppGnCi33GINeQ44ALp2tf+J110XdFSSLAMHWmEZ6XKaMWOs+dMhh0T+GCpgRURE4qRSJdt77+WXYdWqoKMRkaI4Z6OwH38M//4bdDTpZ7/97OTAL7/Y/8QxY6xLu2SGdu1sz+WHHopsOc3770OFCpu2YYpEhQp2rQJWREQkDk45BVauhFdfDToSESnOoYfa1EVtp5MYXbrY9OFvv4Xddgs6Gkm2886DuXPtJFFp3nvP/l4KRlUj4ZwVsSpgRURE4qBzZ2jUSN2IRcKsa1drGPPBB0FHkr4aNIAaNYKOQoJw7LFQq5aNwpZk4UKYPTu66cMFKlZUASsiIhIXWVnWbfPjj+Hnn4OORkSKst12drLp/feDjkQk/Wy7ra2FfeMN21anOAX5pwJWREQkYCefbBu5jxgRdCQiUpwePWDqVPjtt6AjEUk/F10ElSvDzTcXf58XX7Qt6Fq0iP74KmBFRETiqHFj6NgRhg+3QlZEwqdg1Oejj4KNQyQd7bADXHCBFakzZmz2pdxc6L3Lt/Dxx9y6/DxyR7joj68CVkREJM5OOsletKdMCToSESnKHntA7dqaRiySKJddZtP1r7pq48nc3FwYMABO+G0oK9iO25cOYMAAuz0qKmBFRETi7LjjrEnM8OFBRyIiRcnKgoMPhg8/1EwJkUSoWdOmEL/9NtxzDwCDBkHtvHn040WGMYBlVCMvz26PigpYERGROKtZEw47zNbBrlsXdDQiUpSDDoI//4Tp04OOJCLOuaecc38651IjYJGLLoK+feHKK+Hxx9lpwVd8woGsYRvu46KNd1u4MMrjqoAVERFJgBNPhD/+gE8+CToSESlK9+52nTrrYJ8BegYdhEjEnIOnn4Y2bWDAACawLxX4l658xs/U33i3+vVLOEZRVMCKiIgkQK9eULUqPP980JGISFHq1YMmTWzbqxTgvR8D/B10HCJRqVoVJk+GceP45vg76FLha75m741frlQJhgyJ8pgqYEVERBKgQgXb0H30aFi1KuhoRKQo3bvD55/D2rVBRxIXzrkBzrlJzrlJixcvDjocEeMcdOxIu5FXMPiJujRoYDc1aADDhkFOTpTHUwErIiKSIDk5sHKlbeguIuHTvbvl6MSJQUcSF977Yd779t779rVq1Qo6HJGt5OTA/PmwYYNdR128ggpYERGRhOnSBerWLcMeASKSFN262VBQikwjFhFUwIqIiCRMVhaccILtNblkSdDRiMiWatSAdu1UwIqkkoICNsYtsBJawDrnejrn5jjnfnDOXV3E1091zi12zk3Jv5yZyHhEZHPKUZESnHCCbaXz8suBhaAcFSlB164wYUJcRnQSyTk3EhgPNHXOLXLOnRF0TCKBqFjRrv/7L6bDJKyAdc5lAw8BhwItgP7OuRZF3PVF733b/MsTiYpHRDanHBUpRZs20Lw5jBwZyMMrR0VK0a0brFljRWyIee/7e+939t6X997X9d4/GXRMIoEoKGBjPOmUyBHYvYEfvPc/ee/XAC8ARyTw8UQkOspRkZI4Z6OwY8bAzz8HEYFyVKQknTvbdP9PPw06EhGJRAoUsHWAwq/4i/Jv29LRzrmpzrmXnXP1ijqQWouLJIRyVKQ0/fvb9QsvBPHoylGRklStCnvtBZ99FnQkIhKJFChgI/Em0NB73wb4EHi2qDuptbhIYJSjktkaN4a99w5sGnEElKOS2QrWweblBR2JiJQmBQrYX4DCZ4Lr5t+2kff+L+99wSreJ4C9EhiPiGxOOSoSif794dtvYc6cZD+yclSkNN26wdq1MH580JGISGlSoID9GtjdOdfIObcNcDyw2Y7wzrmdC33aB5iVwHhEZHPKUZFIHHecrYdN/jRi5ahIaTp3huxsTSMWSQVhL2C99+uA84H3sRfUUd77Gc65m51zffLvdqFzboZz7jvgQuDURMUjIptTjopEaJdd4IADrICNce+6aChHRSJQpQrsuSeMHRt0JCJSmjgVsOXiEEqxvPfvAO9scdsNhT6+BrgmkTGISPGUoyIROv54OPts+O47aNs2aQ+rHBWJwP77w8MP296S224bdDQiUpywj8CKiIikjaOPtmmKL74YdCQisqUuXax4nTQp6EhEpCQqYEVERJJkhx3goIOsgE3iNGIRiUDnznY9ZkywcYhIyVTAioiIJFG/fjBvnkZ5RMJmhx2gRQutgxUJOxWwIiIiSXTkkVC+vKYRi4TR/vvDl1/C+vVBRyIixVEBKyIikkTVq0OPHjBqFGzYEHQ0IlJYly6wfLk1WhORcCooYPPyYjqMClgREZFI9esHv/4Ks2cHHYmIFFawDnbcuGDjEJHilStnF43AioiIJMnRR8Pvv9t6OxEJj3r1oE4dm0YsIuFVsWK494EVERFJK5Uq2UVEwsU56NRJBaxI2MWhgNUIrIiIiIikvk6d4Oef7SIi4aQCVkREREQEK2BB62BFwkwFrIiIiIgIsMceNsVf04hFwksFrIiIiIgI1t10n31UwIqEmQpYEREREZF8nTrZXrArVwYdiYgURV2IRURERETynXQS7L8/bLNN0JGISFEqVoS//orpEBqBFQmJ3Fxo2BCysuw6NzfoiESkMOWoSLjl5kLDHk3I6tmDhk22UY6KhExuLrz1SUVmT1kd0+uoRmBFQiA3FwYMgLw8+3zBAvscICcnuLhExChHRcJNOSoSbgU5+nBeRVqzOqYc1QisSAgMGrTpRbdAXp7dLiLBU46KhJtyVCTcCnJ0NRWpiK2BLWuOagRWJAQWLozudhFJLuWoSLgpR0XCrSAXJ7I32/LfVrdHQyOwIiFQv350t4tIcilHRcJNOSoSbgW5+DSnczpPb3V7NFTAioTAkCG293phlSrZ7SISPOWoSLgpR0XCLZ45qgJWJARycmDYMGjQAJyz62HD1HhCJCyUoyLhphwVCbd45qjWwIqERE6OXmhFwkw5KhJuylGRcItXjmoEVkREREQkQZxzPZ1zc5xzPzjnrg46HpFUpwJWRERERCQBnHPZwEPAoUALoL9zrkWwUYmkNhWwIiIiIiKJsTfwg/f+J+/9GuAF4IiAYxJJaSpgRUREREQSow7wc6HPF+Xfthnn3ADn3CTn3KTFixcnLTiRVKQCVkREREQkQN77Yd779t779rVq1Qo6HJFQc977oGOIinNuMbCglLvtACxJQjiRCFMsEK54FEvRIomlgfc+lK9wytGYhSkexVI05WhyKZbihSmeVIslKTnqnOsIDPbeH5L/+TUA3vtbS/ge5WjZhSkWCFc8qRZLsTmacgVsJJxzk7z37YOOA8IVC4QrHsVStDDFkihh+hnDFAuEKx7FUrQwxZIoYfoZFUvxwhSPYimac64cMBfoDvwCfA2c4L2fEeNxw/QzKpZihCmedIpF+8CKiIiIiCSA936dc+584H0gG3gq1uJVJNOpgBURERERSRDv/TvAO0HHIZIu0rWJ07CgAygkTLFAuOJRLEULUyyJEqafMUyxQLjiUSxFC1MsiRKmn1GxFC9M8SiW5ArTz6hYihemeNImlrRcAysiIiIiIiLpJ11HYEVERERERCTNqIAVERERERGRlJDSBaxzrqdzbo5z7gfn3NVFfH1b59yL+V//yjnXMMBYLnXOzXTOTXXOfeycaxBULIXud7RzzjvnEtpSO5J4nHPH5f9+ZjjnRgQVi3OuvnPuU+fct/nPVa8ExvKUc+5P59z0Yr7unHP358c61TnXLlGxJIpytGyxFLpfwnM0TPkZSTzK0fhSjpYtlkL3U45u/fWk5KjyM7n5GWE8ylHlaOHHSVyOeu9T8oK1Iv8R2BXYBvgOaLHFfc4FHs3/+HjgxQBj6QZUyv/4nCBjyb9fFWAMMAFoH/DztDvwLVA9//PaAcYyDDgn/+MWwPwE/m66AO2A6cV8vRfwLuCAfYGvEhVLgL9v5WiAORqm/IwiHuVocn/fylHlaLTxJCVHlZ/Jy88o4lGOKkcLP07CcjSVR2D3Bn7w3v/kvV8DvAAcscV9jgCezf/4ZaC7c84FEYv3/lPvfV7+pxOAugmII6JY8t0C3A78m6A4oonnLOAh7/0/AN77PwOMxQPb539cFfg1QbHgvR8D/F3CXY4AnvNmAlDNObdzouJJAOVoGWPJl4wcDVN+RhqPcjR+lKNljCWfcjTAHFV+AsnLz4jiUY4qRzd7kATmaCoXsHWAnwt9vij/tiLv471fBywDagYUS2FnYGccEqHUWPKH6Ot5799OUAxRxQM0AZo45750zk1wzvUMMJbBwInOuUXYnm0XJCiWSET7dxU2ytEyxpLEHA1TfkYaz2CUo/GiHC1jLMrREuMZTDhyVPmZ/HgKU45uohwtWplztFxCwpFiOedOBNoDBwT0+FnA3cCpQTx+Mcph0yu6YmfrxjjnWnvvlwYQS3/gGe/9Xc65jsBw51wr7/2GAGKRAChHtxKm/ATlaMZTjm5FOSqhohzdinI0zlJ5BPYXoF6hz+vm31bkfZxz5bBh8r8CigXn3EHAIKCP9/6/BMQRSSxVgFbAZ865+dic8zcSuLg9kt/NIuAN7/1a7/08YC6W6EHEcgYwCsB7Px6oAOyQgFgiEdHfVYgpR8sWSzJzNEz5GWk8ytH4UY6WLRblaMnxhCVHlZ/Jj0c5qhyNRtlztLRFsmG9YGczfgIasWmRcsst7nMemy9uHxVgLHtii6p3D/r3ssX9PyOxTZwi+d30BJ7N/3gHbDpBzYBieRc4Nf/j5ti6AJfA309Dil/c3pvNF7dPTOTfTkC/b+VogDkapvyMIh7laHJ/38pR5Wi08SQtR5WfycnPKOJRjipHt4wnITmasD+uZFyw7lVz85NlUP5tN2NnfcDOKLwE/ABMBHYNMJaPgD+AKfmXN4KKZYv7Jiypo/jdOGyqx0xgGnB8gLG0AL7MT/gpQI8ExjIS+A1Yi52dOwM4Gzi70O/lofxYpyX6eQro960cDThHw5SfEcajHE3u71s5qhyNNp6k5KjyM7n5GWE8ylHlaOE4EpajLv8AIiIiIiIiIqGWymtgRUREREREJIOogBUREREREZGUoAJWREREREREUoIKWBEREREREUkJKmBFREREREQkJaiAFRERERERkZSgAlY2cs5Vc86dW8zXGjrnVjvnppRyjFzn3N/OuWMSEqRIBlOOioSbclQk3JSj6UEFrBRWDSgyqfP96L1vW9IBvPc5wBtxjElENqmGclQkzKqhHBUJs2ooR1OeClgp7DagsXNuinPuzpLu6Jyr7Jx72zn3nXNuunOuX5JiFMlkylGRcFOOioSbcjQNlAs6AAmVq4FWpZ15ytcT+NV73xvAOVc1kYGJCKAcFQk75ahIuClH04BGYKWspgEHO+dud87t771fFnRAIrIZ5ahIuClHRcJNORpSKmClTLz3c4F2WHL/n3PuhoBDEpFClKMi4aYcFQk35Wh4aQqxFLYCqBLJHZ1zuwB/e++fd84tBc5MZGAiAihHRcJOOSoSbsrRNKACVjby3v/lnPvSOTcdeNd7f0UJd28N3Omc2wCsBc5JSpAiGUw5KhJuylGRcFOOpgcVsLIZ7/0JEd7vfeD9BIcjIltQjoqEm3JUJNyUo6lPa2AlUuuBqpFs7gwcAPybjKBEZCPlqEi4KUdFwk05miKc9z7oGERERERERERKpRFYERERERERSQkqYEVERERERCQlqIAVERERERGRlKACVkRERERERFLC/wPmLS1klawgcgAAAABJRU5ErkJggg==\n",
+      "text/plain": [
+       "<Figure size 1152x288 with 4 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "#create the dataset\n",
+    "t = np.linspace(0,1,11)\n",
+    "tp = np.linspace(0, 1, 100) # more densed grid for findign the polynomial\n",
+    "\n",
+    "h = np.array([1.68203, 1.80792, 2.38791,2.67408,2.12245, 2.44969,1.89843, 1.60447,1.80634,1.08810,0.22066])\n",
+    "\n",
+    "plt.figure(figsize=(16,4))\n",
+    "\n",
+    "# fig 1\n",
+    "plt.subplot(141)\n",
+    "degree = 1\n",
+    "fit = np.polyfit(t, h, degree)\n",
+    "model = np.poly1d(fit)\n",
+    "\n",
+    "#plot of observed and modeled data\n",
+    "plt.scatter(t,h, c='b', label='observed')\n",
+    "plt.plot(tp, model(tp),c='r', label='predicted')\n",
+    "plt.xlabel('t [s]')\n",
+    "plt.ylabel('h [m]')\n",
+    "plt.title('Degree = 1')\n",
+    "plt.legend()\n",
+    "\n",
+    "\n",
+    "# fig 2\n",
+    "plt.subplot(142)\n",
+    "#perform quadratic fit using pylab\n",
+    "degree = 2\n",
+    "fit = np.polyfit(t, h, degree)\n",
+    "model = np.poly1d(fit)\n",
+    "\n",
+    "#plot of observed and modeled data\n",
+    "plt.scatter(t,h, c='b', label='observed')\n",
+    "plt.plot(tp, model(tp),c='r', label='predicted')\n",
+    "plt.xlabel('t [s]')\n",
+    "plt.ylabel('h [m]')\n",
+    "plt.title('Degree = 2')\n",
+    "plt.legend()\n",
+    "\n",
+    "# fig 3\n",
+    "plt.subplot(143)\n",
+    "degree = 4\n",
+    "fit = np.polyfit(t, h, degree)\n",
+    "model = np.poly1d(fit)\n",
+    "\n",
+    "#plot of observed and modeled data\n",
+    "plt.scatter(t,h, c='b', label='observed')\n",
+    "plt.plot(tp, model(tp),c='r', label='predicted')\n",
+    "plt.xlabel('t [s]')\n",
+    "plt.ylabel('h [m]')\n",
+    "plt.title('Degree = 4')\n",
+    "plt.legend()\n",
+    "\n",
+    "# fig 4\n",
+    "plt.subplot(144)\n",
+    "#perform higher-degree fit using pylab\n",
+    "degree = 10\n",
+    "fit = np.polyfit(t, h, degree)\n",
+    "model = np.poly1d(fit)\n",
+    "\n",
+    "#plot of observed and modeled data\n",
+    "plt.scatter(t,h, c='b', label='observed')\n",
+    "plt.plot(tp, model(tp),c='r', label='predicted')\n",
+    "plt.xlabel('t [s]')\n",
+    "plt.ylabel('h [m]')\n",
+    "plt.title('Degree = 10')\n",
+    "plt.legend()\n",
+    "\n",
+    "def add_fun(a,b):\n",
+    "    return a+b"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/02471/instructor/02471/week02/.ipynb_checkpoints/week2-checkpoint.ipynb b/examples/02471/instructor/02471/week02/.ipynb_checkpoints/week2-checkpoint.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..6bc411a7d674f4520b58bb03f2d9f1abeb2f7476
--- /dev/null
+++ b/examples/02471/instructor/02471/week02/.ipynb_checkpoints/week2-checkpoint.ipynb
@@ -0,0 +1,69 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Exercise 2.2.1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "hello world\n",
+      "6\n"
+     ]
+    }
+   ],
+   "source": [
+    "var = \"hello world 2\"\n",
+    "def myfun(a,b):\n",
+    "    return a*b\n",
+    "\n",
+    "output = myfun(2,3) + 10\n",
+    "print(var)\n",
+    "print(output)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "z = 234 \n",
+    "def mymul(d):\n",
+    "    return myfun(d,2)+1"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/02471/instructor/02471/week02/Week_2_sol.ipynb b/examples/02471/instructor/02471/week02/Week_2_sol.ipynb
index 33cb1a369adbe9d3723c70d067592cd7623f2052..417af02e33c700f0a8598906c9a4b8b1c4dbd0d2 100644
--- a/examples/02471/instructor/02471/week02/Week_2_sol.ipynb
+++ b/examples/02471/instructor/02471/week02/Week_2_sol.ipynb
@@ -9,9 +9,983 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 4,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "data": {
+      "application/javascript": [
+       "/* Put everything inside the global mpl namespace */\n",
+       "/* global mpl */\n",
+       "window.mpl = {};\n",
+       "\n",
+       "mpl.get_websocket_type = function () {\n",
+       "    if (typeof WebSocket !== 'undefined') {\n",
+       "        return WebSocket;\n",
+       "    } else if (typeof MozWebSocket !== 'undefined') {\n",
+       "        return MozWebSocket;\n",
+       "    } else {\n",
+       "        alert(\n",
+       "            'Your browser does not have WebSocket support. ' +\n",
+       "                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+       "                'Firefox 4 and 5 are also supported but you ' +\n",
+       "                'have to enable WebSockets in about:config.'\n",
+       "        );\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+       "    this.id = figure_id;\n",
+       "\n",
+       "    this.ws = websocket;\n",
+       "\n",
+       "    this.supports_binary = this.ws.binaryType !== undefined;\n",
+       "\n",
+       "    if (!this.supports_binary) {\n",
+       "        var warnings = document.getElementById('mpl-warnings');\n",
+       "        if (warnings) {\n",
+       "            warnings.style.display = 'block';\n",
+       "            warnings.textContent =\n",
+       "                'This browser does not support binary websocket messages. ' +\n",
+       "                'Performance may be slow.';\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.imageObj = new Image();\n",
+       "\n",
+       "    this.context = undefined;\n",
+       "    this.message = undefined;\n",
+       "    this.canvas = undefined;\n",
+       "    this.rubberband_canvas = undefined;\n",
+       "    this.rubberband_context = undefined;\n",
+       "    this.format_dropdown = undefined;\n",
+       "\n",
+       "    this.image_mode = 'full';\n",
+       "\n",
+       "    this.root = document.createElement('div');\n",
+       "    this.root.setAttribute('style', 'display: inline-block');\n",
+       "    this._root_extra_style(this.root);\n",
+       "\n",
+       "    parent_element.appendChild(this.root);\n",
+       "\n",
+       "    this._init_header(this);\n",
+       "    this._init_canvas(this);\n",
+       "    this._init_toolbar(this);\n",
+       "\n",
+       "    var fig = this;\n",
+       "\n",
+       "    this.waiting = false;\n",
+       "\n",
+       "    this.ws.onopen = function () {\n",
+       "        fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+       "        fig.send_message('send_image_mode', {});\n",
+       "        if (fig.ratio !== 1) {\n",
+       "            fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n",
+       "        }\n",
+       "        fig.send_message('refresh', {});\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onload = function () {\n",
+       "        if (fig.image_mode === 'full') {\n",
+       "            // Full images could contain transparency (where diff images\n",
+       "            // almost always do), so we need to clear the canvas so that\n",
+       "            // there is no ghosting.\n",
+       "            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+       "        }\n",
+       "        fig.context.drawImage(fig.imageObj, 0, 0);\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onunload = function () {\n",
+       "        fig.ws.close();\n",
+       "    };\n",
+       "\n",
+       "    this.ws.onmessage = this._make_on_message_function(this);\n",
+       "\n",
+       "    this.ondownload = ondownload;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_header = function () {\n",
+       "    var titlebar = document.createElement('div');\n",
+       "    titlebar.classList =\n",
+       "        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+       "    var titletext = document.createElement('div');\n",
+       "    titletext.classList = 'ui-dialog-title';\n",
+       "    titletext.setAttribute(\n",
+       "        'style',\n",
+       "        'width: 100%; text-align: center; padding: 3px;'\n",
+       "    );\n",
+       "    titlebar.appendChild(titletext);\n",
+       "    this.root.appendChild(titlebar);\n",
+       "    this.header = titletext;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._init_canvas = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+       "    canvas_div.setAttribute(\n",
+       "        'style',\n",
+       "        'border: 1px solid #ddd;' +\n",
+       "            'box-sizing: content-box;' +\n",
+       "            'clear: both;' +\n",
+       "            'min-height: 1px;' +\n",
+       "            'min-width: 1px;' +\n",
+       "            'outline: 0;' +\n",
+       "            'overflow: hidden;' +\n",
+       "            'position: relative;' +\n",
+       "            'resize: both;'\n",
+       "    );\n",
+       "\n",
+       "    function on_keyboard_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.key_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keydown',\n",
+       "        on_keyboard_event_closure('key_press')\n",
+       "    );\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keyup',\n",
+       "        on_keyboard_event_closure('key_release')\n",
+       "    );\n",
+       "\n",
+       "    this._canvas_extra_style(canvas_div);\n",
+       "    this.root.appendChild(canvas_div);\n",
+       "\n",
+       "    var canvas = (this.canvas = document.createElement('canvas'));\n",
+       "    canvas.classList.add('mpl-canvas');\n",
+       "    canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+       "\n",
+       "    this.context = canvas.getContext('2d');\n",
+       "\n",
+       "    var backingStore =\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        this.context.webkitBackingStorePixelRatio ||\n",
+       "        this.context.mozBackingStorePixelRatio ||\n",
+       "        this.context.msBackingStorePixelRatio ||\n",
+       "        this.context.oBackingStorePixelRatio ||\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        1;\n",
+       "\n",
+       "    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+       "    if (this.ratio !== 1) {\n",
+       "        fig.send_message('set_dpi_ratio', { dpi_ratio: this.ratio });\n",
+       "    }\n",
+       "\n",
+       "    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+       "        'canvas'\n",
+       "    ));\n",
+       "    rubberband_canvas.setAttribute(\n",
+       "        'style',\n",
+       "        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+       "    );\n",
+       "\n",
+       "    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+       "    if (this.ResizeObserver === undefined) {\n",
+       "        if (window.ResizeObserver !== undefined) {\n",
+       "            this.ResizeObserver = window.ResizeObserver;\n",
+       "        } else {\n",
+       "            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+       "            this.ResizeObserver = obs.ResizeObserver;\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+       "        var nentries = entries.length;\n",
+       "        for (var i = 0; i < nentries; i++) {\n",
+       "            var entry = entries[i];\n",
+       "            var width, height;\n",
+       "            if (entry.contentBoxSize) {\n",
+       "                if (entry.contentBoxSize instanceof Array) {\n",
+       "                    // Chrome 84 implements new version of spec.\n",
+       "                    width = entry.contentBoxSize[0].inlineSize;\n",
+       "                    height = entry.contentBoxSize[0].blockSize;\n",
+       "                } else {\n",
+       "                    // Firefox implements old version of spec.\n",
+       "                    width = entry.contentBoxSize.inlineSize;\n",
+       "                    height = entry.contentBoxSize.blockSize;\n",
+       "                }\n",
+       "            } else {\n",
+       "                // Chrome <84 implements even older version of spec.\n",
+       "                width = entry.contentRect.width;\n",
+       "                height = entry.contentRect.height;\n",
+       "            }\n",
+       "\n",
+       "            // Keep the size of the canvas and rubber band canvas in sync with\n",
+       "            // the canvas container.\n",
+       "            if (entry.devicePixelContentBoxSize) {\n",
+       "                // Chrome 84 implements new version of spec.\n",
+       "                canvas.setAttribute(\n",
+       "                    'width',\n",
+       "                    entry.devicePixelContentBoxSize[0].inlineSize\n",
+       "                );\n",
+       "                canvas.setAttribute(\n",
+       "                    'height',\n",
+       "                    entry.devicePixelContentBoxSize[0].blockSize\n",
+       "                );\n",
+       "            } else {\n",
+       "                canvas.setAttribute('width', width * fig.ratio);\n",
+       "                canvas.setAttribute('height', height * fig.ratio);\n",
+       "            }\n",
+       "            canvas.setAttribute(\n",
+       "                'style',\n",
+       "                'width: ' + width + 'px; height: ' + height + 'px;'\n",
+       "            );\n",
+       "\n",
+       "            rubberband_canvas.setAttribute('width', width);\n",
+       "            rubberband_canvas.setAttribute('height', height);\n",
+       "\n",
+       "            // And update the size in Python. We ignore the initial 0/0 size\n",
+       "            // that occurs as the element is placed into the DOM, which should\n",
+       "            // otherwise not happen due to the minimum size styling.\n",
+       "            if (width != 0 && height != 0) {\n",
+       "                fig.request_resize(width, height);\n",
+       "            }\n",
+       "        }\n",
+       "    });\n",
+       "    this.resizeObserverInstance.observe(canvas_div);\n",
+       "\n",
+       "    function on_mouse_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.mouse_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousedown',\n",
+       "        on_mouse_event_closure('button_press')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseup',\n",
+       "        on_mouse_event_closure('button_release')\n",
+       "    );\n",
+       "    // Throttle sequential mouse events to 1 every 20ms.\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousemove',\n",
+       "        on_mouse_event_closure('motion_notify')\n",
+       "    );\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseenter',\n",
+       "        on_mouse_event_closure('figure_enter')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseleave',\n",
+       "        on_mouse_event_closure('figure_leave')\n",
+       "    );\n",
+       "\n",
+       "    canvas_div.addEventListener('wheel', function (event) {\n",
+       "        if (event.deltaY < 0) {\n",
+       "            event.step = 1;\n",
+       "        } else {\n",
+       "            event.step = -1;\n",
+       "        }\n",
+       "        on_mouse_event_closure('scroll')(event);\n",
+       "    });\n",
+       "\n",
+       "    canvas_div.appendChild(canvas);\n",
+       "    canvas_div.appendChild(rubberband_canvas);\n",
+       "\n",
+       "    this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+       "    this.rubberband_context.strokeStyle = '#000000';\n",
+       "\n",
+       "    this._resize_canvas = function (width, height, forward) {\n",
+       "        if (forward) {\n",
+       "            canvas_div.style.width = width + 'px';\n",
+       "            canvas_div.style.height = height + 'px';\n",
+       "        }\n",
+       "    };\n",
+       "\n",
+       "    // Disable right mouse context menu.\n",
+       "    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+       "        event.preventDefault();\n",
+       "        return false;\n",
+       "    });\n",
+       "\n",
+       "    function set_focus() {\n",
+       "        canvas.focus();\n",
+       "        canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    window.setTimeout(set_focus, 100);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'mpl-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'mpl-button-group';\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'mpl-button-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        var button = (fig.buttons[name] = document.createElement('button'));\n",
+       "        button.classList = 'mpl-widget';\n",
+       "        button.setAttribute('role', 'button');\n",
+       "        button.setAttribute('aria-disabled', 'false');\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "\n",
+       "        var icon_img = document.createElement('img');\n",
+       "        icon_img.src = '_images/' + image + '.png';\n",
+       "        icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+       "        icon_img.alt = tooltip;\n",
+       "        button.appendChild(icon_img);\n",
+       "\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    var fmt_picker = document.createElement('select');\n",
+       "    fmt_picker.classList = 'mpl-widget';\n",
+       "    toolbar.appendChild(fmt_picker);\n",
+       "    this.format_dropdown = fmt_picker;\n",
+       "\n",
+       "    for (var ind in mpl.extensions) {\n",
+       "        var fmt = mpl.extensions[ind];\n",
+       "        var option = document.createElement('option');\n",
+       "        option.selected = fmt === mpl.default_extension;\n",
+       "        option.innerHTML = fmt;\n",
+       "        fmt_picker.appendChild(option);\n",
+       "    }\n",
+       "\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+       "    // which will in turn request a refresh of the image.\n",
+       "    this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_message = function (type, properties) {\n",
+       "    properties['type'] = type;\n",
+       "    properties['figure_id'] = this.id;\n",
+       "    this.ws.send(JSON.stringify(properties));\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_draw_message = function () {\n",
+       "    if (!this.waiting) {\n",
+       "        this.waiting = true;\n",
+       "        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    var format_dropdown = fig.format_dropdown;\n",
+       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+       "    fig.ondownload(fig, format);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+       "    var size = msg['size'];\n",
+       "    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+       "        fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+       "        fig.send_message('refresh', {});\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+       "    var x0 = msg['x0'] / fig.ratio;\n",
+       "    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+       "    var x1 = msg['x1'] / fig.ratio;\n",
+       "    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+       "    x0 = Math.floor(x0) + 0.5;\n",
+       "    y0 = Math.floor(y0) + 0.5;\n",
+       "    x1 = Math.floor(x1) + 0.5;\n",
+       "    y1 = Math.floor(y1) + 0.5;\n",
+       "    var min_x = Math.min(x0, x1);\n",
+       "    var min_y = Math.min(y0, y1);\n",
+       "    var width = Math.abs(x1 - x0);\n",
+       "    var height = Math.abs(y1 - y0);\n",
+       "\n",
+       "    fig.rubberband_context.clearRect(\n",
+       "        0,\n",
+       "        0,\n",
+       "        fig.canvas.width / fig.ratio,\n",
+       "        fig.canvas.height / fig.ratio\n",
+       "    );\n",
+       "\n",
+       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+       "    // Updates the figure title.\n",
+       "    fig.header.textContent = msg['label'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+       "    var cursor = msg['cursor'];\n",
+       "    switch (cursor) {\n",
+       "        case 0:\n",
+       "            cursor = 'pointer';\n",
+       "            break;\n",
+       "        case 1:\n",
+       "            cursor = 'default';\n",
+       "            break;\n",
+       "        case 2:\n",
+       "            cursor = 'crosshair';\n",
+       "            break;\n",
+       "        case 3:\n",
+       "            cursor = 'move';\n",
+       "            break;\n",
+       "    }\n",
+       "    fig.rubberband_canvas.style.cursor = cursor;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+       "    fig.message.textContent = msg['message'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+       "    // Request the server to send over a new figure.\n",
+       "    fig.send_draw_message();\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+       "    fig.image_mode = msg['mode'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+       "    for (var key in msg) {\n",
+       "        if (!(key in fig.buttons)) {\n",
+       "            continue;\n",
+       "        }\n",
+       "        fig.buttons[key].disabled = !msg[key];\n",
+       "        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+       "    if (msg['mode'] === 'PAN') {\n",
+       "        fig.buttons['Pan'].classList.add('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    } else if (msg['mode'] === 'ZOOM') {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.add('active');\n",
+       "    } else {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Called whenever the canvas gets updated.\n",
+       "    this.send_message('ack', {});\n",
+       "};\n",
+       "\n",
+       "// A function to construct a web socket function for onmessage handling.\n",
+       "// Called in the figure constructor.\n",
+       "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+       "    return function socket_on_message(evt) {\n",
+       "        if (evt.data instanceof Blob) {\n",
+       "            /* FIXME: We get \"Resource interpreted as Image but\n",
+       "             * transferred with MIME type text/plain:\" errors on\n",
+       "             * Chrome.  But how to set the MIME type?  It doesn't seem\n",
+       "             * to be part of the websocket stream */\n",
+       "            evt.data.type = 'image/png';\n",
+       "\n",
+       "            /* Free the memory for the previous frames */\n",
+       "            if (fig.imageObj.src) {\n",
+       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
+       "                    fig.imageObj.src\n",
+       "                );\n",
+       "            }\n",
+       "\n",
+       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+       "                evt.data\n",
+       "            );\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        } else if (\n",
+       "            typeof evt.data === 'string' &&\n",
+       "            evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+       "        ) {\n",
+       "            fig.imageObj.src = evt.data;\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        var msg = JSON.parse(evt.data);\n",
+       "        var msg_type = msg['type'];\n",
+       "\n",
+       "        // Call the  \"handle_{type}\" callback, which takes\n",
+       "        // the figure and JSON message as its only arguments.\n",
+       "        try {\n",
+       "            var callback = fig['handle_' + msg_type];\n",
+       "        } catch (e) {\n",
+       "            console.log(\n",
+       "                \"No handler for the '\" + msg_type + \"' message type: \",\n",
+       "                msg\n",
+       "            );\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        if (callback) {\n",
+       "            try {\n",
+       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+       "                callback(fig, msg);\n",
+       "            } catch (e) {\n",
+       "                console.log(\n",
+       "                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+       "                    e,\n",
+       "                    e.stack,\n",
+       "                    msg\n",
+       "                );\n",
+       "            }\n",
+       "        }\n",
+       "    };\n",
+       "};\n",
+       "\n",
+       "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+       "mpl.findpos = function (e) {\n",
+       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+       "    var targ;\n",
+       "    if (!e) {\n",
+       "        e = window.event;\n",
+       "    }\n",
+       "    if (e.target) {\n",
+       "        targ = e.target;\n",
+       "    } else if (e.srcElement) {\n",
+       "        targ = e.srcElement;\n",
+       "    }\n",
+       "    if (targ.nodeType === 3) {\n",
+       "        // defeat Safari bug\n",
+       "        targ = targ.parentNode;\n",
+       "    }\n",
+       "\n",
+       "    // pageX,Y are the mouse positions relative to the document\n",
+       "    var boundingRect = targ.getBoundingClientRect();\n",
+       "    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+       "    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+       "\n",
+       "    return { x: x, y: y };\n",
+       "};\n",
+       "\n",
+       "/*\n",
+       " * return a copy of an object with only non-object keys\n",
+       " * we need this to avoid circular references\n",
+       " * http://stackoverflow.com/a/24161582/3208463\n",
+       " */\n",
+       "function simpleKeys(original) {\n",
+       "    return Object.keys(original).reduce(function (obj, key) {\n",
+       "        if (typeof original[key] !== 'object') {\n",
+       "            obj[key] = original[key];\n",
+       "        }\n",
+       "        return obj;\n",
+       "    }, {});\n",
+       "}\n",
+       "\n",
+       "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+       "    var canvas_pos = mpl.findpos(event);\n",
+       "\n",
+       "    if (name === 'button_press') {\n",
+       "        this.canvas.focus();\n",
+       "        this.canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    var x = canvas_pos.x * this.ratio;\n",
+       "    var y = canvas_pos.y * this.ratio;\n",
+       "\n",
+       "    this.send_message(name, {\n",
+       "        x: x,\n",
+       "        y: y,\n",
+       "        button: event.button,\n",
+       "        step: event.step,\n",
+       "        guiEvent: simpleKeys(event),\n",
+       "    });\n",
+       "\n",
+       "    /* This prevents the web browser from automatically changing to\n",
+       "     * the text insertion cursor when the button is pressed.  We want\n",
+       "     * to control all of the cursor setting manually through the\n",
+       "     * 'cursor' event from matplotlib */\n",
+       "    event.preventDefault();\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+       "    // Handle any extra behaviour associated with a key event\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.key_event = function (event, name) {\n",
+       "    // Prevent repeat events\n",
+       "    if (name === 'key_press') {\n",
+       "        if (event.which === this._key) {\n",
+       "            return;\n",
+       "        } else {\n",
+       "            this._key = event.which;\n",
+       "        }\n",
+       "    }\n",
+       "    if (name === 'key_release') {\n",
+       "        this._key = null;\n",
+       "    }\n",
+       "\n",
+       "    var value = '';\n",
+       "    if (event.ctrlKey && event.which !== 17) {\n",
+       "        value += 'ctrl+';\n",
+       "    }\n",
+       "    if (event.altKey && event.which !== 18) {\n",
+       "        value += 'alt+';\n",
+       "    }\n",
+       "    if (event.shiftKey && event.which !== 16) {\n",
+       "        value += 'shift+';\n",
+       "    }\n",
+       "\n",
+       "    value += 'k';\n",
+       "    value += event.which.toString();\n",
+       "\n",
+       "    this._key_event_extra(event, name);\n",
+       "\n",
+       "    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+       "    if (name === 'download') {\n",
+       "        this.handle_save(this, null);\n",
+       "    } else {\n",
+       "        this.send_message('toolbar_button', { name: name });\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+       "    this.message.textContent = tooltip;\n",
+       "};\n",
+       "\n",
+       "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+       "// prettier-ignore\n",
+       "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+       "\n",
+       "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+       "\n",
+       "mpl.default_extension = \"png\";/* global mpl */\n",
+       "\n",
+       "var comm_websocket_adapter = function (comm) {\n",
+       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
+       "    // object with the appropriate methods. Currently this is a non binary\n",
+       "    // socket, so there is still some room for performance tuning.\n",
+       "    var ws = {};\n",
+       "\n",
+       "    ws.close = function () {\n",
+       "        comm.close();\n",
+       "    };\n",
+       "    ws.send = function (m) {\n",
+       "        //console.log('sending', m);\n",
+       "        comm.send(m);\n",
+       "    };\n",
+       "    // Register the callback with on_msg.\n",
+       "    comm.on_msg(function (msg) {\n",
+       "        //console.log('receiving', msg['content']['data'], msg);\n",
+       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+       "        ws.onmessage(msg['content']['data']);\n",
+       "    });\n",
+       "    return ws;\n",
+       "};\n",
+       "\n",
+       "mpl.mpl_figure_comm = function (comm, msg) {\n",
+       "    // This is the function which gets called when the mpl process\n",
+       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+       "\n",
+       "    var id = msg.content.data.id;\n",
+       "    // Get hold of the div created by the display call when the Comm\n",
+       "    // socket was opened in Python.\n",
+       "    var element = document.getElementById(id);\n",
+       "    var ws_proxy = comm_websocket_adapter(comm);\n",
+       "\n",
+       "    function ondownload(figure, _format) {\n",
+       "        window.open(figure.canvas.toDataURL());\n",
+       "    }\n",
+       "\n",
+       "    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+       "\n",
+       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+       "    // web socket which is closed, not our websocket->open comm proxy.\n",
+       "    ws_proxy.onopen();\n",
+       "\n",
+       "    fig.parent_element = element;\n",
+       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
+       "    if (!fig.cell_info) {\n",
+       "        console.error('Failed to find cell for figure', id, fig);\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.cell_info[0].output_area.element.on(\n",
+       "        'cleared',\n",
+       "        { fig: fig },\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+       "    var width = fig.canvas.width / fig.ratio;\n",
+       "    fig.cell_info[0].output_area.element.off(\n",
+       "        'cleared',\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+       "\n",
+       "    // Update the output cell to use the data from the current canvas.\n",
+       "    fig.push_to_output();\n",
+       "    var dataURL = fig.canvas.toDataURL();\n",
+       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+       "    // the notebook keyboard shortcuts fail.\n",
+       "    IPython.keyboard_manager.enable();\n",
+       "    fig.parent_element.innerHTML =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "    fig.close_ws(fig, msg);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+       "    fig.send_message('closing', msg);\n",
+       "    // fig.ws.close()\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+       "    // Turn the data on the canvas into data in the output cell.\n",
+       "    var width = this.canvas.width / this.ratio;\n",
+       "    var dataURL = this.canvas.toDataURL();\n",
+       "    this.cell_info[1]['text/html'] =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Tell IPython that the notebook contents must change.\n",
+       "    IPython.notebook.set_dirty(true);\n",
+       "    this.send_message('ack', {});\n",
+       "    var fig = this;\n",
+       "    // Wait a second, then push the new image to the DOM so\n",
+       "    // that it is saved nicely (might be nice to debounce this).\n",
+       "    setTimeout(function () {\n",
+       "        fig.push_to_output();\n",
+       "    }, 1000);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'btn-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'btn-group';\n",
+       "    var button;\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'btn-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        button = fig.buttons[name] = document.createElement('button');\n",
+       "        button.classList = 'btn btn-default';\n",
+       "        button.href = '#';\n",
+       "        button.title = name;\n",
+       "        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    // Add the status bar.\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message pull-right';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "\n",
+       "    // Add the close button to the window.\n",
+       "    var buttongrp = document.createElement('div');\n",
+       "    buttongrp.classList = 'btn-group inline pull-right';\n",
+       "    button = document.createElement('button');\n",
+       "    button.classList = 'btn btn-mini btn-primary';\n",
+       "    button.href = '#';\n",
+       "    button.title = 'Stop Interaction';\n",
+       "    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n",
+       "    button.addEventListener('click', function (_evt) {\n",
+       "        fig.handle_close(fig, {});\n",
+       "    });\n",
+       "    button.addEventListener(\n",
+       "        'mouseover',\n",
+       "        on_mouseover_closure('Stop Interaction')\n",
+       "    );\n",
+       "    buttongrp.appendChild(button);\n",
+       "    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+       "    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+       "    var fig = event.data.fig;\n",
+       "    if (event.target !== this) {\n",
+       "        // Ignore bubbled events from children.\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.close_ws(fig, {});\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (el) {\n",
+       "    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+       "    // this is important to make the div 'focusable\n",
+       "    el.setAttribute('tabindex', 0);\n",
+       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
+       "    // off when our div gets focus\n",
+       "\n",
+       "    // location in version 3\n",
+       "    if (IPython.notebook.keyboard_manager) {\n",
+       "        IPython.notebook.keyboard_manager.register_events(el);\n",
+       "    } else {\n",
+       "        // location in version 2\n",
+       "        IPython.keyboard_manager.register_events(el);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+       "    var manager = IPython.notebook.keyboard_manager;\n",
+       "    if (!manager) {\n",
+       "        manager = IPython.keyboard_manager;\n",
+       "    }\n",
+       "\n",
+       "    // Check for shift+enter\n",
+       "    if (event.shiftKey && event.which === 13) {\n",
+       "        this.canvas_div.blur();\n",
+       "        // select the cell after this one\n",
+       "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+       "        IPython.notebook.select(index + 1);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    fig.ondownload(fig, null);\n",
+       "};\n",
+       "\n",
+       "mpl.find_output_cell = function (html_output) {\n",
+       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+       "    // IPython event is triggered only after the cells have been serialised, which for\n",
+       "    // our purposes (turning an active figure into a static one), is too late.\n",
+       "    var cells = IPython.notebook.get_cells();\n",
+       "    var ncells = cells.length;\n",
+       "    for (var i = 0; i < ncells; i++) {\n",
+       "        var cell = cells[i];\n",
+       "        if (cell.cell_type === 'code') {\n",
+       "            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+       "                var data = cell.output_area.outputs[j];\n",
+       "                if (data.data) {\n",
+       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
+       "                    data = data.data;\n",
+       "                }\n",
+       "                if (data['text/html'] === html_output) {\n",
+       "                    return [cell, data, j];\n",
+       "                }\n",
+       "            }\n",
+       "        }\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "// Register the function which deals with the matplotlib target/channel.\n",
+       "// The kernel may be null if the page has been refreshed.\n",
+       "if (IPython.notebook.kernel !== null) {\n",
+       "    IPython.notebook.kernel.comm_manager.register_target(\n",
+       "        'matplotlib',\n",
+       "        mpl.mpl_figure_comm\n",
+       "    );\n",
+       "}\n"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Javascript object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA8AAAALQCAYAAABfdxm0AAAAAXNSR0IArs4c6QAAIABJREFUeF7svWeMHNl99vuv6jiREzkzzGm5zMvlcjOX3CBphVe+hu0XsP36fnCCbQECnODwwfYHBxgCbOja14bDvYIc9Mq2dG3Zr17L2tWuNjFtIrmBS+4uMzmMQ3LyTOe+eE7zzNTUdHdVd1f3dHc9B2j0kFPhnN853VPP+Scjm81mhY0ESIAESIAESIAESIAESIAESIAEmpyAQQHc5DPM4ZEACZAACZAACZAACZAACZAACSgCFMBcCCRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiABEiABEiABEiABEiABXxCgAPbFNHOQJEACJEACJEACJEACJEACJEACFMBcAyRAAiRAAiRAAiRAAiRAAiRAAr4gQAHsi2nmIEmABEiABEiABEiABEiABEiABCiAuQZIgARIgARIgARIgARIgARIgAR8QYAC2BfTzEGSAAmQAAmQAAmQAAmQAAmQAAlQAHMNkAAJkAAJkAAJkAAJkAAJkAAJ+IIABbAvppmDJAESIAESIAESIAESIAESIAESoADmGiABEiABEiABEiABEiABEiABEvAFAQpgX0wzB0kCJEACJEACJEACJEACJEACJEABzDVAAiRAAiRAAiRAAiRAAiRAAiTgCwIUwL6YZg6SBEiABEiABEiABEiABEiABEiAAphrgARIgARIgARIgARIgARIgARIwBcEKIB9Mc0cJAmQAAmQAAmQAAmQAAmQAAmQAAUw1wAJkAAJkAAJkAAJkAAJkAAJkIAvCFAA+2KaOUgSIAESIAESIAESIAESIAESIAEKYK4BEiABEiCBhieQzWYlk8lIOp1WYwkEAmKaphiG0fBj4wBIgARIgARIgAS8I0AB7B1LXokESIAESGAJCED8plIpJX5nZmaU6NUCGO94BYNB9f8UxEswQbwlCZAACZAACdQRAQrgOpoMdoUESIAESKA0ArD6JpPJOetvIpEQCGLdtBUY73hpMYx3CuLSWPNoEiABEiABEmgGAhTAzTCLHAMJkAAJ+IwARC4svrD8QgTrBjGsrbw4RrtGa1FcTBDjd2wkQAIkQAIkQALNTYACuLnnl6MjARIggaYjADGrrb4Qv9qSq63B+Sy7djFsFcRaFGt3af1OQdx0S4cDIgESIAESIAGhAOYiIAESIAESaBgCVqsvRKw10RV+py3ATrG+FMQNM+XsKAmQAAmQAAl4SoAC2FOcvBgJkAAJkEA1CFgTXWmXZ3uW51IEsL2PVkGM6x85ckT6+vpky5YtcyKbFuJqzCyvSQIkQAIkQAK1JUABXFvevBsJkAAJkECJBCBIdZZnCFXt4my38moBjMtX4r6Me7z66qvS398vW7duVXHE1vvmc5nWWaZLHBoPJwESIAESIAESqDEBCuAaA+ftSIAESIAE3BHQCax0oiu7y7P9Kl4JYFz3lVdeUQJ4586d6jZ2l2mIcmtCLV16yV52yd1IeRQJkAAJkAAJkECtCFAA14o070MCJEACJOCagHZ5vnDhgjpn1apVC+J9811oampKbt++Le3t7dLR0VFRzV+7ALbfz0kQ67JLsAxrUewUl+waDg8kARIgARIgARIomwAFcNnoeCIJkAAJkEA1CFhdng8ePCgQkU888URRQXvjxg05efKkcpVGC4VC0t3dLV1dXeq9tbW1JEEMF+je3l7ZtWuXqyFayy2h/9pabbUS22OIKYhdoeVBJEACJEACJOApAQpgT3HyYiRAAiRAAuUS0LV94cqMFxqSUUEo7tu3L+9lcdzp06dleHhYid5169bJ7OysjI2NCSzCuoXDYSWEtShuaWkpKohLFcD2ztkFMX6PcVAQl7s6eB4JkAAJkAAJeEOAAtgbjrwKCZAACZBABQQK1fY9fPiwsqY+9dRTi64+OTkp77//vhK6sNZu3759LvkVhCZKIkEIj46Oqtf09PTcNSKRyJwghiiORqMLrv/aa6+p3z/wwAMVjGr+VApiTzDyIiRAAiRAAiRQMQEK4IoR8gIkQAIkQAKVELDX9tWWUlwTAhi/379//9wtICZh8YXlFz9v2rRJNmzYIHA9huhFy5cFOpFIKCGsRfHMzMzcNWER1u7SEL5Hjx71VADb+VAQV7JieC4JkAAJkAAJlE+AArh8djyTBEiABEigAgLa5VlnedbC1RobCxdoiNoDBw6oO+Hnjz76SBDzC6stLLQQrGilZoGOx+Nz1mEI41gstmA0sBLfd999ShjDhbpaDRzQdOywrnNcyGUaMdH2GsjV6huvSwIkQAIkQALNRoACuNlmlOMhARIggQYg4La2LyyxEKpPP/20stzC5RkxvgMDA7Jjxw4V96tbqQLYjknHDkMMQ2BbW1tb24IYYut9vcbtJIitdYh1lmkKYq9ngdcjARIgARJoVgIUwM06sxwXCZAACdQhgVJr+7755psCV+X169fLp59+qhJJbdmyRVavXr0oiVWlAtiK6/XXXxeI3qGhoTmXabhQ64YyS9plGu8QotVqVkGsrcS4l7YQ65JL1izTFMTVmg1elwRIgARIoNEJUAA3+gyy/yRAAiTQIAR0bV8IVe3m6yTUYAGemJhQsb4QpLt371Y1fvM1HQOsSxBVguWNN95Q9YT37NmjLoNrQojrhFqwRut4YwhR9ElnmV62bJmq/VutpmsQW+OIcS9rhmn8TEFcrRngdUmABEiABBqZAAVwI88e+04CJEACDULArcuzdTi3b9+WY8eOKfG5atUqZfktZmn1UgCj/jAEtxbAdszoE7JKWwWxrkEMQdzZ2TkniPEzBXGDLFR2kwRIgARIoOkJUAA3/RRzgCRAAiSwdATy1fZ1svpCyJ49e1bOnz+v3Hzx+tznPuc4CK8FcGtrqzz00EOO98UBGCfKMukM03jXtYwxXrsgzpel2tWNXBxktxDfuXNHsUS2bJSLosu0C4g8hARIgARIoGkJUAA37dRyYCRAAiSwtATsLs9azFqzPNt7iERUSHQFAQnRCLEGYfnZz37WcTBeCuBDhw4JSiO5FcD2zqEv6Le2EI+Pjy9w+7aWXIKrdTUF8c2bN1Xm7G3btikBrGOKC7lMw8qu58oROg8gARIgARIggQYjQAHcYBPG7pIACZBAIxDQYlQnbbLW9i3Uf2RePnnypMCVeN26dbJ582Y5ceKEwIJZawswBDDKLO3du9cT3OCAWGarINZCFO7RdkFcbJOg1A5pAYys2cuXL1cCWL+sSbXyCWKdZZqCuFTqPJ4ESIAESKBeCVAA1+vMsF8kQAIk0IAE3NT2tQ8LrsIff/yxXLlyRZU12rVrl/T396vDEAOMWODnn3/ekYaXFuDDhw8L6gB7JYDzjRlWYQhiWLt1oi8cB9GJhFpaFCMWuRJBbBfA9r7YxbDVQmwVxVoMQ7BX02LtONE8gARIgARIgAQqIEABXAE8nkoCJEACJDBPQCe6wjteblyep6am5L333hO89/T0KPELy6tusABDwH3+8593RO2lAD5y5IgS4w8//LDjfb04AFZvLYghiuE+rRv6oTNMQxQjNrkUQewkgMsRxNYM0xTEXqwAXoMESIAESKBWBCiAa0Wa9yEBEiCBJiVQam1fYMA5V69eldOnT6tkUffdd59s2LBhkbDTAhgWYCfR18gC2L40IIh1Qi0IYmwQ6BYOh+cEMYQxYpWLtVIFsBtBrDc3tIWYgrhJP9wcFgmQAAk0IQEK4CacVA6JBEiABGpFoJzavhB3SMp0/fp1Ze2F1RfW33wN1mHEBiMG2Mnt1ksBjPrDEHWPPPJIrVAWvQ9qDmt3abyjBJNuYGiNIbZa0HFMpQKYgrgulgA7QQIkQAIk4BEBCmCPQPIyJEACJOA3AuXU9oWbL7I8z8zMqIRMSMwEi2ahhmMhlJEF2qmWbjMLYDufRCKxoAYxeOoGi7A1hhiWZGw46CRYXq/TfDHExSzEOsu01/3g9UiABEiABEjADQEKYDeUeAwJkAAJkMAcgXJq++Kcixcvyqeffqqus2XLFlmzZo2jW/MHH3wg165dk8985jMqOVSx5qUAfvPNN1XfHn300YaY+Xg8PieIYSGOxWJz/cYGAwTz6tWrZe3atUU3HLwYrF0QY17sGabxb2xoWLNMe3FvXoMESIAESIAEnAhQADsR4u9JgARIgAQWiF+4MCNu122iK4ivDz/8UEZGRlQCp927d6sav24azkOs8HPPPaeSUjkJYPRNCy431y90TKMJYPs4UE9Zu0wjiza46Ia6w9plGu9OXCvhiHOtgliXXSpUg1jHEjvFe1faJ55PAiRAAiTgXwIUwP6de46cBEiABEoikK+2r1N9WNTwhRUXFsoVK1bItm3bHC251k6hLvDw8LA8++yzjpZLaxZqp3hhp4G/9dZbSrg99thjTofW/e/hQo5kY+APIQxhjJhi3To6OuaSai1btqyk+Sln8Dppmn7HNXSdaP1uT6pFQVwOaZ5DAiRAAiSQjwAFMNcFCZAACZBAUQLl1PaFGD137px6Qcxs375dCbBSG2JXUR/4mWeeUXV5izUK4Px0kETs1KlTsnPnTlVfGfOJmGEIYf3SFmIITbsgdoq9LnVO7cdTEFdKkOeTAAmQAAmUQoACuBRaPJYESIAEfEYA4gTWwlJq+8L9FlZfiCu4Oj/wwAPS1tZWFjkIt8uXL8vTTz+9oD5wvot5KYDffvtt5eb9+OOPl9XvejrJLoDzCVCUWbKWXcLY0SCIMYe6DjEsxJVa153YUBA7EeLvSYAESIAEKiFAAVwJPZ5LAiRAAk1MACIIoggW2IGBASWCnFxRUXIHbssQzUi4dP/991ckmOC6e+nSJTlw4IBjvVsnAXx6+LYkUxnZubbfcRwQwLCKPvHEEw0/w04COJ8gnpycnLMOI3O3FsQQvxDBWhDDWlxNQQwxjKZjh/GOhjFhftatW6fuT5fphl+mHAAJkAAJ1IwABXDNUPNGJEACJNAYBKy1fW/duiUnTpxQLszIIlyoQSB98sknylqLpEpwt0WZo0rbxx9/rLJH79+/XyXQKtaKCeDbEzPyrUOnJZXOyPJlrfL0zrUy2N1e8HLvvPOOEvF+FMB2KOBqF8RaiEJ42gWx0yZJJWtCC+Ljx48rN26dpTtfDLHOMK0TblVyX55LAiRAAiTQPAQogJtnLjkSEiABEqiYgL227927d+XYsWMqeRXKFuVrcJ9FvV6IJFgG4fIcjUYr7gsuAFF94cIFeeqppxzdqK19t8atJpJp+ZdDp2Rsar40kGGIbF3VJ09sXSWtkcXZpSGAkb36ySef9GQcS3mRUi3ATn0FZ1iFdZZp/KyFKbhbaxAj43Q1BDHWJFzt9fzYLcRaEEP86hcFsdPM8vckQAIk4A8CFMD+mGeOkgRIgASKEtBxl7qMEP4N4QCRA3fgrVu3Kpdme0OJIsTpwgK8adMm2bhxo6eCB3WDz58/L/v27ROIqWKtkAB+4dg5+fTa3bynRsNBeXTzCtm5drmYpjF3zLvvvqsyV1MAO39wMPdaEGO9YCNEC2KITu0ujZJLiAX3QhAXmh9dcskaR4wR2AWx1WWaFmLnOeYRJEACJNBMBCiAm2k2ORYSIAESKIOA1eVZu7ZqUQBBg5JAW7ZsUfGWukEoQ/heu3ZNZWeG1benp6eMuxc/5cyZMyqTNIQo4k1LFcAfXrolr35wybFf/XCL3rFWhnpyIhsCKxaLKeHd6M1rC7ATD6wNuyDW54TD4bkaxBDGLS0tZQlitxb6QoK4WB1iCmKnGebvSYAESKCxCVAAN/b8sfckQAIkUBEBu8uzruurrXQQMkePHpXNmzfLhg0b1L3wf3B5Rgwmyuog3hfCphrt7Nmzglc5AvjW+LT86+GPVdyvmwa36C2r+uTJravk9MkPlIttMwhgXQdYl0Fyw8LLYxBLjWRqOss0XOZ1w+YJLMPaSgxB7KaVG6NNQeyGLo8hARIggeYmQAHc3PPL0ZEACZBAXgK6ti/cV60Zfu3uqRMTE3LkyBG57777lABGRmbE5aIhwzPcor1waS00TbD+wgqMckRItlSsWcU8NO+/HPxIxqbjJa+ASCggXTIlg60iB/bvL/n8ejthqQWwnQdiq60ll7CRohtix7UYxnuh2s9elakqVRDDpVtvEtXbPLM/JEACJEAC7ghQALvjxKNIgARIoGkIlFLbF/Gchw8fVu7P09PTMjIyorIxw+XZSZB6AQzxv4gDLlUAv3jigpy9Plp2FyAaWwNZ+fkffVZW9BR3vS77JjU6sd4EsH3YiLW2CmJY3nWDRdgqiLWnAdzysY4fe+wxTymWIoh1Ui0KYk+ngBcjARIggaoToACuOmLegARIgATqhwCsvdZEVzo5UKEeQvQePHhQ1VnFuUNDQ6okEh7+a9GQARoWZ5S7gRAq1rQF+MS563Lw1HBF3btx47rE4wlZt26t3L+yV57culraoouzRVd0kxqdXO8C2I4Bsdc6wzTe8W/dkEQL6wDlubAmsTFSzaYFsc4yrZN72WOItRhGn6pZF7maY+W1SYAESMAvBCiA/TLTHCcJkICvCWiXZy1+AcMp2Q/OQR1euD1DKO/YsUNWrFhRVZdn+yShBjD64FYAX709Lt86dEqy2cqmWwtgnfkabtHIFr1r3cCCbNGV3aU2Z2sBvGvXLunr66vNTT28CyzCEML6BRdq3ZAZ3JplutobM8UEsf48WTNMUxB7uBB4KRIgARLwiAAFsEcgeRkSIAESqFcC2jKKd7zsia7y9RtWNyS6guhAg/CFgKp1g/g+ffq0PPzww9Lb21v09rPxhPzPVz6QselZMQyzoq4ic3I8HpO1a+czX+OCfZ0tcmDHWlnZ2zhu0Y0ugK0TCQEKQYw6wPgZaxlJtnRDpnAtiOGivxSCWH++KIgr+gjyZBIgARKoGgEK4Kqh5YVJgARIYGkJFKrt65S0Cu6lH374oRIWK1euFNT6XbNmjWzbtq3mA7p8+bIqt+RGAP+vNz+WM1fvSDYLkV8dAawB3L+qV/Y1iFt0MwlgzR+J2UKhkOzdu1fFpltjiOHlgIZ13tnZOZdlGoIYFtlqtnwWYgriahLntUmABEigdAIUwKUz4xkkQAIkUPcEitX2LdR5WIcRbwurK8QFXJ5hTXvllVdk1apV6t+1bleuXJGPPvpIHnroIVVyqVA7fu66/ODEWbl7d1QJn9bWFolEomW7a9+8eUNmZ2MLah/b7x0OBeSxBnCLbkYBjMRsSIiFjRFrw7pHmSXtLg1hrLOcY11ABGNNo/QSfq52vG6pglhnma7154z3IwESIAE/EaAA9tNsc6wkQAK+IOBU2zcfBFjR4PKMskcQCHB3RgZeWNNefvllZQlGHdlat+HhYTl58mRRAXxjdEr+/sV35Nr1G3PjLi5rAAAgAElEQVRiB/2E4IlGI2oc0SgEMWoVG66G4EYA6wvVu1t0swpglEiCBbhYw2fBKohRw9pa9ksLYqx5uE/XWhCjf9aEWlizOobYmmXa1aLlQSRAAiRAAq4IUAC7wsSDSIAESKD+Cbit7WsfybVr15SVFcJg48aN6qWFAP7vpZdeWrIYYLhfwx17z549snz58kWTMBtPyv/9b6/J5Ws3VXKq3t4+CQYDEovFJRabVe/WzL2oMwtB3NISVVbuQoL45s2bMjs7I+vWrXc98fev7JEnt62W9iiEdv20ZhTAhw4dUvMIz4BSGgQnNnl0lmkIYvwfGoQnLMN4aUHsFC5Qyr3zHWu1EMNajbWOetvYcMJnUL+sWaar3adKx8TzSYAESKDeCVAA1/sMsX8kQAIk4IKA3eXZTaIrWHcRXwsBDGsarL72RFO47osvviiDg4Oye/duFz3x9hD07YMPPpAHH3xQBgYGFlwc9WP/6t9+IGev3VXW3YGBQSVirDHA+HlODM9MSzI2I2kzJ1BxLIQwrMMQU9aESeUIYFwTbtHIFv1AHWWLbkYBjNJcqEddqgC2r05s8GhBDFGMn/WGCdaDFsN4R8bpaorPu3fvynvvvSebNm1SG05amOtSZfrdnmW6mn3y9tPMq5EACZBAfRCgAK6PeWAvSIAESKBsAnhQRsIqXavUqbYvboQHfTxsz8zMqNI4EL+IqczXXnjhBSU+IUJr3SDe4JoN8Q0RrtudO3fk314+Ku9fGZNlyzrVGDDu+UzXi5Ng9d06ItGpYRlp2SDXQ6sF1uNEYj6DcCgUvCeGo8ptdmZmVtavd28BtrLp7UC26DWyqq+z1sgW3Q+bCCgl1ahlkPIBfOONN5QghWeAlw2CGFZhHUM8OTk5J4jhMaAFMSzEEOBeik+saaz1LVu2KAGMphPZ6Xf8HwWxlzPOa5EACfiRAAWwH2edYyYBEmgKAuXW9kVmZSS7wvmbN29WiZ6KPcjDAgyBWam1rRzoKEcEof7AAw/I0NCQ6vO5c+fk7Q9Oy5uXJlVirPZ2XZIoW1AAd46dlu47J+a6kA62yFj3DhlvXSez99ylkfRKZxDWByKLcC6GOFpWfOj2NX3y6P0rl9QtulkFMGJ2q70pg/UA12SdZRqCWDdsGFkFMdZJJYL49u3bytth69ataq3naxTE5XyL8BwSIAESWEiAApgrggRIgAQakEA5tX0TiYRKKIUyR3hYh1UVSYCcGmKAYfFySjjkdJ1yfg9X5BMnTsy5Z0MgXL85Im9fnZXOnn4JhaxW6/wCODpzXQZuvC6SzcV6Wlsy1CljPbtkpn2N+u9UKqmyP4+NjUoqlZ471DBEuYnn3KWjrjJMt7eEZWo2odyiH7lvhexeP6DilGvdmlEAv/7662rt1totH54WWgzjHZ4CumF96BrEEMb4jJXS8LnE5xPlxqzeDsWuQUFcCmEeSwIkQAI5AhTAXAkkQAIk0EAEyq3tC5dOuFfGYjH1cI2SRtaY12IIkAUaYsNecqYW2CAKjh8/rlyRIeTQ/3PTIUkE4H5qd3NeLICDyUkZuvp9MdPxot2NR/tktOcBibfk4oxxX2TGhitqPB5TohhJtTKZbO6Pp0OGaYjeVDozdzzO6bnnFr26xm7RWgDDim6P8a7FHFbjHq+99poSmxjTUjZsKllrECOkQDd4DWhBjHcI5GINmz1IRofPZr6Eb07j1LHLOhTCKYYYn3+dgdrp2vw9CZAACTQTAQrgZppNjoUESKCpCZRT2xfnnD9/Xs6cOaMedmFdQobZUlw1UQe4ra1NHn300Zrz1QIYN0byn1TbgHx8a15kLOzQQgFsZJIydPUlCSXGXPd7tnVIRnt2y7WJpExNTS9wDwfLRCI+J4YLZZhubWmR9taoxJLzFmRrBzav6JF9yBbdUpts0c0ogF999VXp6elZcgFsX1hIzKYzTON9dnZ27hDEDFtdpu0x9zpZGcqNFat57XYxOwnifFmmKYjd0uVxJEACjUyAAriRZ499JwES8A2Bcmr7wloKl2Fkl0XCILiL4r3UBrEBd87HHnus1FMrOh5i4tixYyphF6xnqzZulf9679ICq2pBASyG9N88JK3TV0rvg2HItXS3XDRWy4qNW/NYmnOXtGaYhoUY/UVrCZuSSBsFM0zjmFDQzLlFbxiQgLk4YVfpnS58RrMKYFizkdirnhs+gzqhFt71GkGfsalkdZkeGRlRycqqZanXJZesbtPoh06qpQWxNcs0BXE9ry72jQRIoFwCFMDlkuN5JEACJFADAuXW9sXDNMQvYhZXr16tMsviwbacBndTWKueeOKJck4v6xyIdrhsa8GwbuMmOXR+QiZnE0WuN28B7ho7JV13Pyjr3jgJrqyxZEqCa/bKePdOyQSKu6/iHGxSmJmk3BmfUu7ShTJMI45YzwXcovdvXyNr+quXLboZBTC8EmAlhbW0URo+y3ZBDBdq3bDJg/UOd398Zt2GKJQ7/kKCWIteCuJyyfI8EiCBeidAAVzvM8T+kQAJ+JZAObV9IcI+/fRTuXjxonqARjyh24Q6hUCj5AwE25NPPln1ubC6bOOea9euVVmfh1Mdkg21SDqdKehaLJITwJHJKzJw6xBMtGX3FwIY4gSxz9lAWCa6tsrEsi2SNYMFrxkOBiSdyUj6XpwwSurABRZi2J5hGhsKugYxYkXvX9UnT1XJLbrZBDDWCLwSGk0A2xcOxoF1pmOIUQYJa0Y3ZLm2WojL3cBy+yGgIHZLiseRAAk0OgEK4EafQfafBEigKQno2r7ahXn//v3KVbFY7C4eplEyCC7DiDWEK2WpmWjzwTx48KC67759+6rKGtYvjBdCQLtsw4L9P//zVbmdzsVPIhtzezQsM/GEpBcldc5KIDYqA8MviplJVdRXqwDWzHXppKmOjSK2BFzoVzQUlNlE4fvqDNM5QTyrxDyazjDd0dYmj29bI/t2bpBQsLDQLnVgzSaA8dmAVwISRWGDp1napUuX1GYPrL9YHxDGuiwX1iBKckEQ43OAjZl6E8TYcHP6jmqWueI4SIAEGpsABXBjzx97TwIk0GQE7LV94QaMRFDPP/98UfELkYMMsrAgbdiwQTZt2lRW3dp8OA8fPqyuCxFerWZ1eV61apWqhYoH/I/OD8vf/Ptr0tPbK11d3XO3RwxtJBiQqVhy7v+MdFwGrrwgwcQ4ZGVFXc0ngPUF7aWT8P8dLWEH92x7d5BQKzlnHbZmmO6IBuXxzUOydd2QSvQES2ApScvsd2pWATwwMCDbt2+vaJ7r6WR4bSBhHcqNQeziuwBllnQMMQSxthDDPVkLYohi/Iz/q2YrxUIMMYzPLwVxNWeE1yYBEiiXAAVwueR4HgmQAAl4TAAPmLB4wsKFFx4eYRG9ceOGfO5zn8v7gAsL0enTp+Xq1asqThdJgfr6+jzt2ZEjR1S/Dhw44Ol1cTG7yzMEDUoPoc3Ek/LV770tn5y9oMr34EHf3tqiIVVuKJ5ISf/1VyU6fVVds5oCWPchHu1VGaND3atkOpaU8h2ucxzsGaYHO0OyfaBNOlojyuoHMQwGyCZciiDG2vjkk0+qllzJ80XhcEGIQNQBhms/spo3S7tw4YLg9cgjj+RNVofvhMnJybks0xDEutQRxC+swtplGpsm9SiIq92nZlkLHAcJkEB1CVAAV5cvr04CJEACrgjgoR5iVtfw1IloIIBhwfvMZz6zKCkOHobh8ox6tRC9SAjkVGvUVWdsBx09elQl73nmmWfKOb3gOYixxfhu3769KEs1BOG3j3wsZ4ZvyZUrV6S3F+KvJ++14EI8OHlSQjfeV5mZvRDAs7MzEo/nYoCLic2AaQhKJ93uekCSkcUCvVxgOsN0MhGXdV1BGYxmxDRzVm1sdFjryzq5uTebAMbnBHHpzSaAYf2FFRjlxpAh2qnhuwLhDtpCjJ+1IIb11VpyCSEFpWyaON073++1hVh/h+kyTPguQ3gD7o9+2LNMl3MvnkMCJEAClRCgAK6EHs8lARIggQoJONX2PXnypAwPD8uzzz6rhA8azoEoRMkU/Lx58+YF9Wor7NKi09966y0lstEHr1ohl2d9/Tc/GZajp4eVVfTy5SvK+olXvtY6eVH6bh4W0zAkYIrEEqmKH/bdCGAI72DAlGQqowJ5p9vXymj3LkmHSi815cS1syUku1d3SauRUIIH86EbBLBVENvryzabAIY3AuLSh4aGlKt8s7SzZ8/K5cuXVbkxWPlLbdhEswtiLULhkmwVxBDYtRTEJ06cUCIY4l6XXcK7VQzjZ1qIS511Hk8CJFAOAQrgcqjxHBIgARLwgICb2r6nTp1SD8WwvsK6i4d/iOKbN2+qBFdIdIUH22q2t99+W7lePvfccxXfRrs842EfD7twYV25cuWC614ZGZd/O3JaJXGGlRjj7+nplp6e3kX3D8XvysDw98XMIntuVp0TNA21MZC6l425nE4jCREe2Jct6yxYBzgSCkg8OZ+1V21OGKZMdt4n4907XJVOKrVvG4e6Zf/21RIJGKq+s7b+wUKvG6xs1uzBWCvN5AKtBTBc5VHeq1namTNn1MYWyo0hM3ilDYIYbtI6yzTEsW6hUGiBIC7Vrb7UvuE7BJZ7iHurhVjHCGuPFwriUsnyeBIggXIIUACXQ43nkAAJkEAFBCDOtPi1uzzbLwsrL9wiEX8LkYOkWHiH+yfiZfEgW+327rvvKqH12c9+tqJbFXN51heejiXkG699qGJq0ZLJhFy6dFkJOsQBW5uZjskgkl6ltDU0J4BVPV7TlHDQlEQqXVY1JCcBHAoGJIVrFyCSMUP3SifdL1nT2zkKBUzZe9+QPLhhUFmg0dBfLYYhjCES0SAwsHGCNbNx40aVYbjRrWxYR4cOHVIbJ/fff39Fa7KeTkb5Mnh7oNxYNUIZIEC1GMZaQYIt3axu9dhQw+aalxbiN998U10PFmDd8rlMFxPEOst0Pc0Z+0ICJNCYBCiAG3Pe2GsSIIEGJeDk8mwfFh6KERu4bt06QZkUPCDC7ROZkr18QC2G89ixY6o0ERJxldvwwI14ZVhVIVxg+bWXcclksvLto6flysi8pQpCDuPu7u6S3l5Lcq9sRpZf+4FEZ29ZujQvgDWbgGkK4nQhhEtpWgDny66L66Hper+Fr2tIIrxMpjrvk8nOxaWTSulPvmO72qNyYPsaWbt82YJfY43BRdoqiPMlS/Iiw3SlYyjnfKwhZCZvNgGMzS7E+z/11FM12djCZ8sqiK1u9RDgVrf6Si3SSKSHzbqHH3644JTbBbHeyMrnMm3NMl3OGuI5JEAC/iZAAezv+efoSYAEakjAjcuzvTvI8AwBiAbXVrg8I8NrLRvi9+BG+/nPf77k2+KhFplt4d5ZyOVZX/TI6Svy1idXF9wDtXMvXryk3DWt2a27R96VjvFPbP2BAIZ1PbtocyAcDOSs7gXcovHfY9lWiWdDYhoZCcVGxUxM5S0vA+tv0oWgToXaJJjMWafzlU4qGWaBEzYMdsn+7WukszWS9whYFbGRgphZWILHx8fnkiVZY0MhiKvtCuvFmLUAxiYQ4t+bpeGzfv36dVVuDPNS6wbLulUQoxSYbhDAVkFcqoUaFntYlR966CHXw3ISxPg+wUuLYV12yfUNeCAJkIBvCVAA+3bqOXASIIFaEdC1fRGTZ63j6WTBHRkZUVZTnLN8+XIlfu1W01qMAX1AKSanWsT2vrhxedbnXLo1Jv9+FEm9Fl4FbptwAe/qWiZ9ff3ql20T56T31pt5hl5YAONg8M65RetM0blL3Mx0yNV0t0xlIxKXoBiSlVByWtpS47K1fVraAvPW43xxv/nmIBVslWBqXkDoY1A6aaznAYm1DHo6dXCLfmjTkOzZOO8WrW+gk2Dt3r1bJRLDeoIItmYP1seWmmHa00G4vBhEPCyKcOe+7777XJ5V/4ehjjc2mhDusBSfczshbDToNYJ3a5w5Nkqsceb2xGv2ayFrNzbuHnzwwbInQoeOWN+1CNZWYnsMsdN3bNmd4YkkQAINTYACuKGnj50nARKodwL5avvqOLdCfYelEhZTWE7xgId/I3YuXx3cWowfccewTBWqRZyvD3hg1vHKcFWF23Yhq9bUbC7uF3V/7U0LYJQj6u/vl3DstgxcfVkMlfTK3ooLYH10zi1alBAeTnfJxXSvjGQ7JJ0NSNRISEZMmU4Z0pKekhWRhOwMX5cWIymhoKlqDttFur0X6UBEzExSjGym4PSgdBJqCHtZOgk362qLyP4da2Td8vnEaHYBnI8xLH86qVYpGaZrsf7s94B7OkpzrVmzRjZt2rQUXajKPZHc7tatWyrhXT0KNwhgqyCGQNYNWaWtgtiem+C1115Tv8cmnlfNLohxXau7NL47KYi9os3rkEBzEaAAbq755GhIgATqiIC9tq9+OCvWRbgdQjjCQgfRB7ffc+fOqdg5exKoWg31ww8/FIiofLWIF0lQi8szxotYX7iqFmpwV/7Xw6fk6p3JvIeAITYCkI15sLtdBodfkEBqtsDl3AlgfXLSjMqxxEq5kuqSTmNWlhmzqGak2mwiLTfS7dIZFlkXHJUdoRvqd05xv1kjoDJBQwA7tnulk8a6d0nKo9JJ7dGQTMWSArfop7atkWVtETV3yAKtLcBO/YLlHkJHC+JiGaaXwlUXnxEkVWo2AaxrYntZbsxprsv9PcSnTrym3aaxbnSDtVeXXcL3GMpWYQMLtcqr1SiIq0WW1yWB5iNAAdx8c8oRkQAJLDEB7fIM66U1+ZCTVQduxrAC4bz169cr906IF7hG7t27d0EMbC2HqGsRowxSsazTeACGWIbrttt45cOnLsvbn14rOBwtgDs72mVH8n2Jxm4XGXppAvh8uk/OppdLyghJrzGJAstz10aCoNlURu6GBmV1YEwejl6TUGa+1FD+ThiSDkQlkC4k0POfBcGMRFljFZZOgns3BLoW6cgQ/dDGQRlqzcjZM2dcC2B7L60ZpiGMtdDBeobQgVs1rHsQOrXIMA0LNWpTr127VmW2bpaGjS/wffrppxtuSPjOw8aEthBDFOtM5HowcJvGdxqEcS1cvCmIG24ZscMkUDMCFMA1Q80bkQAJ+IGAtbwRftbuzsXEL0QeEuAgWRFi6WAlgbUEDQIYonLPnj0qDngpmr0Wcb4+WF2eUZ8Vll8n6+DFm2PyH28ujvu1Xh8MkQX7/sxZGQqOS8YMSyA5M2epXdiX0gTwe6nVciHdK13GjLQHUmKIIelMzm0ZD+/JVEomwv2yLJiW+41hGQrMZ6fOxyAVbLOUZCp9piopnQTrdEs4KDPx1KIbZ5OzsjwwI88/9YgSq5U0e4ZpzLs1rh0iGGK4mhmmtQBGZvQNGzZUMpy6Ohex9vD8QAxwozfrOoEnAbLI64bvQmRX1y7T+Lnaghj9QdNl5/TGZCGXaXx36drEjT4X7D8JkMBiAhTAXBUkQAIk4AGBUmr7Wm83OTmpXJ5RkxMuzhC/1pIjKIsC10i4r6L271I0nYkalil7ORSMG0mqkGXYjcuz7v/kbFzF/c7mEWx2ATxx6lXZkDqrrMpoaTMscDUOLrK0liaAjyfXyIVMr/QbkxIxcjHFiA/OZLPKygkBPB3pl4iRku2Ba7IiMF4Qf6GkV+XMVzrYIuNdO0oqndTZGpaJmXkXVOt9JyYmlAB5ZMd98t8e3abcor1qEBJYw9ryV4sM0/isvP3228pLAq9macePH1ffA8gC3UxN123Gph42SLBWYCG2bpzYBXG1PQmcBLEWvxDmOss0BXEzrUqOxe8EKID9vgI4fhIggYoJlFrbFzfEObD4QlziZyTzgTXLbimGWzQsQ7t27RJYVmvZ4EqLkreIH4XIxYM53Bh1s7o8IwkORLqbEk1Ocb/WMYZnbkjo5L9KJByaE8D690g2pURrWifjWVwHuBivU6khOZful4ikZJlpcVs2RLLptMzGE3InvEL6zUnZEbwmvWaupJG9ZcyIGNniSa/Kmbe50kltq5Hdp+AlWiNBtZFgS6A9d7wWwNhA6WhvU5mi924aErhIe91qkWEagvudd95pOgGMettwN9+3b5/X07Kk19NJy6xZu4ttnEBowk0aL3gSYOOrloJYW4kBTVuIdbZpa1ItCuIlXVa8OQlURIACuCJ8PJkESMDvBMqp7Qv3WsT1QtzCoorMqIUyPCMrLCxDO3bsKJpMyqt5mIql5PiVMfnw6oRMxdNKAEdTk9IZvyX//bP75kRoOS7Puo8HP7ok75657tjlQHJKBq98T8Zv31Cu4doCbD8xFWgRI5OSQDahwni167nTDUYy7XIqtUJGMh0yZI5J0JjP2ow5Gk+HJRHulNXmqOwJXlIs7K2kpFdOHSrw+0SkR0Z7d+ctnRQ0DTFMQ5KpwhmntQAeGhqUaLRF3WVZa0Se2r5aNgx2l9krd6chnt3rDNNaAGPDCG7QzdIg6rGp9OSTTzbLkNQ43MRs4zOLdWr1JNBWWohOnVAL35P4HnDKp1ApQF2D2BpHjGtq0UtBXClhnk8CS0uAAnhp+fPuJEACDUqg3Nq+EANweYZVZGBgQAnbYomlbt++Le+++66KqUXW22q26+Mx+daxq3JldFZuTcRlNgkBbEhEEhJOTssPP7ZFfmj3Krl06dICl2eUOXL7QHr+xqh8561PHEsJQdAOXH1JwvE7cvfuqISVBbij4PBh/YQbspGKi5FOuOpPOmvIyfRKuZ7ulKlsi8oErcsgTaZDMpk0ZVVkWrZFR2XAGMubARruyoWzUns7W/lKJ7VFQzIdK55xOp8A1j1bt3yZKpvU1Rb1trOFxLwHGaYxHnwmkAALibCapcGtGxb0xx9/vFmGpMZRjsVeexLoDNOYcy2I4ZJsFcTwPnH7/VMu2GKCGOIdL2zS4aWtxLQQl0ub55FA9QlQAFefMe9AAiTQZATsLs9uEl3hHJTzQX1fHL9lyxaBS6DTgxsSyODBGMdX09o1HU/JVw9fko+uTchsMiMrlkWlMxqUZDorF2/eleuj07J7XZ9s74zL8uyYlOLyrKd/YiYX9xtLLE7UZF8ivTcOSdvUJfXfYIBNAmf36qxksoYkzKgEMzExi9Th1feLZ4PycXpQxjKtMpGNSkKCEjBEQqkZiaQmZGPLjKwPjSkP5HAwIIlUek68V5r0qqyPBUonta2VsZ5dEu3slqlZ53JLxQQw+gBXaLhFP7RpUEKBQFndKvekcjJMI84Y7sIIG6j2plC54yrnPGS2xvfEY489Vs7pdXuOnq9KNizgSYDraAsxRLVu+G6w1iBGmIbT92qlsKyCGF46CGW5//771aam1W2aLtOVkub5JFAdAhTA1eHKq5IACTQpAez0wz1Wx4m5qe0bj8dVJmdYc0sVjrCAoOYpHq6qmfDn6Pm78u/vXZNrYzHZNtQpAYu/7/j4mFy8MSqTKVM2dGTkZx4ekJ07tjtmebYuAWRX/v8OnZLrd6ccV0bH6CnpvnNi7jj3AjgXW60yvJrBXEmi1LQUjp7N3QKW4DvZduUKHVcC2JBIYkw64jdlsCO0IEMtxCLQzEhEgimURSoUees4zIoOCASDMt6+UUa7tksmUNx66ySAdUeQSOup7WtkY5XdogsN3G2GaQgexKU3mwA+evSocrF99NFHK1ob9XYyROuJEydUCSRs+nnRtGu9FsRIHqYbrLBaEOMdYSbVFMTXr19XAhjePEhkaI0hLuQyDSu23jj1ggevQQIkUBoBCuDSePFoEiABnxIot7YvRC+yOCO2b9WqVcqS61QeyIoY4uXIkSPq4bGaNU//30MX5fC5u9LfHpa+dmuW4KzcvHlTZqZn5eK0yLaV3fLFZ7fI5oHC7sj5lsjrJy/J8bPOcb/R6WvSf/01MSzCshwBrB94UVoIL5ROmpaIxLJBJYijRlJaBa7SC3sbDgUkkUwLNi1gnezoaJdAILjgIGShjgaykkmn8rpFV/sjgj4jW3UqnVFjm1i2RSa6tkjWDOW9tVsBrE+utVt0IV7FEiXhHGwmITEcEiXVwupX7XnF5xzi/uGHH672rWp6fWQgR9gHNvEQLlGNhk1J7S4NUYy4Y90ikcgiQexlH1CqDhsySAKoy4yVEkOss0xTEHs5K7wWCRQnQAHMFUICJEACDgTKqe2Lc86ePatq2MINbvv27WVlcYar3+HDh5X4hQiuVvu/Xj4rb10cla2DHRIN5dxgM5m0slrPTM+of08FO2Swu0P+x8OrZM+aLtddOXf9rnznrU8djw8mJmRw+AUxMwvdekdH76pNg46OTsdr5B48kRBqXtlOZKJyw+iRmWxYuXSjRSUprWZCBs0JaTNy5YMioYDEk7lySIUEcMYwJWsEJZDJxRmHg+YCt2jHDnpwgBbp1kvlSidtv1c6aaEbc6kCGNeFpfvBDQOy976hmrtFF0Kk40JhccOmjLXZrX4tLblkX43UDh06pKyVe/fubaRuO/Z1ZGREecBs3bpVhoaGHI/34gBsOGrrMITxzEzuOwwNawOWYR1HDIFcSbt8+bL6rn/ooYdUmad8TQtibR3W8cw6mZb2JNJiGH8zqp35upIx81wSaHQCFMCNPoPsPwmQQNUIlFvbF5ZDWDzw4IX6lrAMWMsHldJhWDIOHjyo3J9hQalW+8vXzsub5+/K2t5W6YyGJB6PCWLb0qm0hCMRScTjMhbolHV9HfJ/PrpadqxwFqPo6/hMXP7ptQ8lFDBV7G8ynT9bsZFJyuDwixJKLK61iwdZPBCCpVODSIJ7pHYxHMu0yNV0l9zNtktSTAnfq/wTTxvKCtxtTsuawKh0BZMCN21kkUbTAhgZZ60W+3z1fiEWYZUtlonZqd9uf28V6fnOSYU65G73LpltXzNXOqkcAayvDbfofdvWyKah6maLdjt+HAePAJQGgws0Plf4t93qp0WOdoWFQK73hs85rNp79uyp966W1HTufpAAACAASURBVD9sViDrPVyEly9fXtK5Xh2Mz7MWxHiPxRC+kGtYQ9YY4lLXCnI74PXII48UzFRvH4eTIP7Sl76kTvn6179eVfdtr/jyOiTQaAQogBttxthfEiCBmhAop7YvOobSRidPnlQiDEmrNm/eXNFOPsT066+/rrLdwoJSrfZfJ2/Kix/dlJlEWpZH00pUGGJIT2+PuuXVm3dkwmyXRzb0yZee3iDtkYVuwfn6BUH5rYOn5MZoLj4vEDCkLRKSydlcuaK5ls1K343XpXX6at7huRXAeMi1uj4awbBcNlfK7UCPtBgZWWbM3nN5NiRphmU8E5ZsJitDgQnZHLqdix2+1/IJ4Hzi19phJMlKqYyw1YkLhtuz2pRZAG8hslSoTYLJaVGlk3p2S6x1UJWXgRuqtQxSqetoLbJFb18j3e21yRZdrH+FXGq11U8LYqvIwUaGVeSUEoZQKqtyj3/jjTdUorcHH3yw3EvU5Xk6Rha1zPv6+uqijzr5mnabxuddN6wVbR3Ge7Es/TgH1l9YgZG8rNyNTrsghhs8roXwmWrGL9fFZLATJLAEBCiAlwA6b0kCJFDfBCCE8IAE92W4tPX39zsmLIHl8eOPP5YrV66oByY87OG8ShsezF599VWV7RalkKrVbk7E5P85eEHeOXtTItmE9LcHZMXAoARDIbk5Oi4fD4/KhqFu+fyu1fJ/7Bp01Y3XPrwoJ87dWHRsSxgJYERm4rls0MvuvC/LRk8WvKaTAMbDI1wcwco0DQmHI5JOp2Qk1SLDslympVV6jTEJBgISMAMSCAbENEzJiCHXjD4ZNCdlTfaGdJrzViG7AEat4WDaOelVtdyiwStomgUt6ICHpF9mJi6GRSCjdNKV0Aa5NpmpSADj+kiM9uDGQXl4id2itQBGPD1igAs1txmm4VkAD4Olbq+99poS6agL3kwtX4xsPY0P3x/WtQJRjM0U3bApoV2mIYjtmyeffvqpDA8Pq/rNlbpT457oD7x98J2PBIgUwPW0WtiXZiFAAdwsM8lxkAAJVEzAWttXux67EZ7IQAqXTLwjCQrEL2L5vGhI7vKDH/xAJdCCC2G1Gh76vvnqcXnnRkomMhHJhlqkLRJUiZZm43GJpKZl76Yh+eJz26Q17CwWzl67K//77cJxv4jQ7WgNS+bOOem+enBB0iv7GMfGRpUVvbNzcXwdNh7AHe+hUFC5kOaaIcPpbrmQXKbidVsy05KxuF+rRFKBoMRCHZKRoKyOTsvK7IgY90onJRJxmZmZVS6NZrhF/b+RzcUHu2leu0U7uT5njICIYS6Kn0Zf44m4XEt1SWb9Pgm2V74p09ESlqe2rZZNK3LeAbVuOrFcKTGlbjNM4/MLwbMUogMbXcgijO+PZmrYFET5N7h2Q0DWe9MbatYYYnwPq28Vw1DrQ3sTYIMUAhhW7v3795eU4LAQB9wf3/ewAuO7n40ESMB7AhTA3jPlFUmABBqQgN3lGS7MeCAtJjxxDqwbKIEBAYYkVRs2bPD04RnXfemll5SlqxoPxhjDpUuXVBZTPNwF+9fLuamQXJ+ISSyZFtMwJJRNSHjqpvzE/h2yYY1zFtfx6Ziq96sTShVaDqH4mAxd+76EjYwkUrAG5y9YBHGOvtkTzMBKg40KjKGlJaqS28D4qZNgXUl3y6U03J+TKtGV2uDIpCWdzkha3S8rk9kWSUtA1pgjMhSaESPSJi2SlGQyJ4Bb2zskGGkRMz1vESpleSNhlcrWXIFbNFyrk6g/XPDGhqSDEQmoskyLm7Zmt3V0Sqxni4x173AsneRmjGv6O2X/jjXS017bhFNeJFUqlmEaFj7tAlurDNNYm/i+gdfIzp073eBvmGPw/XLu3DmV3MtNHH+9DQxzg0027S6Nd/x90IIY6wUCGbkesG4qTV6F73xshHzhC1+Q73znO/WGg/0hgaYgQAHcFNPIQZAACVRCQGd5xoMHHnYgtvB/L7/8ckHhiQcgJHbBzj+svRCnugRGJX2xn4v+vPjiizI4OKgesLxseGhDdlYku0K8Ga6PB1Tc89p4TCZmUxIMGBJOTMrpk++rMRZzOUXfIPa+dfAjuTk2X4YkX5/NdFwGkPQqOal+jSRZEHg4394WC2C4PM+qRDaYq/b2NgmFckmOrFmgb2Q65XK6R5LZgPSatv4YiHDOyrVEi0QyMzKUuiHd2VwCrpQZEsMMipmalVB7j7QH8yfucjsXahOhzGzRuh5zuoiAdopNtrtzZ82gjC/bWrR0ktuxoX+7N+TcoiHUa9GwXhFnj5AAfC68aDrDtLb6Ifu6ztRbiwzT+L6BC/TAwIDKGN9MrZwkUfU8fqwLrA+sFXw3wSVfN4hfbNRBCGtvglIFMTb1kC37J3/yJ+Wf//mf6xkF+0YCDUuAArhhp44dJwESqJRAsdq+xYTn+Pi4cnlG3BiymsI1udTMoaX0/YUXXlD38TI7rHUMEBEYQ6HEQBAcx48fV8fAIl6svfrBBXnv/MISNYuOz2ZUrd+WmcV1gWExVdZOi7nTKoAhFGCNyWV6DuRclM154WUVwKj5ey7VLzcyy6TPnJKIkbPaoEGQTiQDcjfTKisC47IpcEu5D+O6qRQswCmZNVslmE1KWJISDIYUH7hZW+9Xyjzm3KINNT63DaIyUeR4J/GL+xTKaI2Y4fHuHXlLJ7ntn2aJ5GaPb1kl99XALVoLYAhFCMZqNKwDrLtaZZiGAEeyO3wWqxnrXw1WTteE9RdW4EqSRDndYyl/f+zYMfWdhESFEMX4btUJ9RBbDkGsXaZz31f3UtEX6DQ8HFD27ud//uflq1/96lIObe7eX/nKVwRluvSGKTYfsVYPHDggv/mbv9l0Xgt1AZ2dqCoBCuCq4uXFSYAE6pUAhBIsoHhQwQvCRL90n2F5RdZS1HdEwzkXL15UMV9oSMKDGOFqxwt+//vfV9YEL+qD2l2eEUcJUVtsDIi5fPfdd5VlavXq1QWn9NOrd+S775xxnPKu28elc+x0weNgMYVY1MJvfHxMuUe3tbWqB024EyPZDKzW9n5bBTBuMJzukpFMh4xmWqXVSCh36EDAlMlUUGazYek1p2QgMKHqAVvbdMqQxNSohEMhiRthySbjEsjmBDQSbVkFsWEUf6C1DxQiX7lhO7hFO8X9ps2wmNm0Y2xyIQGs+4XSSWM9O2W6be1c6STHSZw7ICutkbDMxHMxkqv7O+VAld2il6KsTrUzTOO7CGWQYPmrZrZ39/Pq3ZGI/0Uc8BNPPOFZbgTvelf5lfDdiM8YkmCh4e8JRLB2mcbP2psAgli710MUQxDbv8NgMUcitF/5lV+RP/uzP6u8gx5cAX8HYZmGF9DKlbkwGHhA4W8hkj5++9vflh/6oR/y4E68BAnUhgAFcG048y4kQAJ1REDXisWDCh5MsCOfTwDCBRq790hGggdglKSAGLS6C9diWNZ+VHI/PGTDdRQCopQxwMXvnXfeUQ/msHLka2NTMfnG6x9KIlncutk6eUH6bh5xNYycW3RW7twdVaI3556Oup1tBbOt2gVwOmvItUyXoB7wtEQkIWF1zYgkpV1i0mPOyKA5LqYl/DhjBiUZj8ns9JRKqoUHPFikZyUomdiMZJKxBSWTIKi1INb1h50GqER+0Fxk7dbnYewoqVSo4lHWMCRrhlzFJjsJYH1Pa+kkp/7r3yMhFspaWZtyi14/IA9vXlEVt2iUGjt16pSyOnmRad3tWK3HeZ1hGt8vsLBBXFSz3nc5Y630HOQXQK6Effv2VdVTptJ+lnv+W2+9pb6bYOHO14q51+P7QluH8fcIm5EQlhDTv/M7vyN/9Ed/VG63PD3v8OHDaiPYntzxr/7qrwQ1i+GJgUzY9VhezFMQvFjTEKAAbpqp5EBIgAScCNgTXeH4QuIXv3vllVeUANq0aZMSvxASiIGFi2It/9Drfjz66KNOQyz4+1Jcnu0XgVsfHvJg8UZtY3tD3O43D34ktxzifkPxuzIw/H1ltXTbVPbeyXFJpjJiBkxpb+8oWrImJ4AXpovCP6clLOPZVkmZYUmlsxKVpHSbM8oqbG1ZlEcyQ5KenVSllbQA1sfg9+lAixipaUknEnPu0tZ7Ym1od2lkmi7W8mWLxmYMBDLqKBdqblyf9bluBbA+PtYyKKO9u1Ut4WIN5axiyVRBkd7eEpZ9W1fJ5pW9bqfb1XG6ruxSCuAFawZrdHpaub/qF0SP/n7RLrDFMkxjjiAyIIBQO7yZGjYrsGnhVZbkemNz5MgRtUmGjVI3De71+D62xpvjvN/6rd9S+RiwriEmf/RHf1T+9E//tOoeRm76XOwY/H2Em/v77+fyRLCRQCMQoABuhFliH0mABComkC/Rld3l2X4TZGVFw8MpXNfgAuyUBKrijua5AJLjwOX38ccfL/nyEGaXL19WNYoxXohYuDGX4rYNVz7Uo4Rlav369Yv68IP3zssHF28V7ZuZisng8PckmJpxPQY8KOZcnjNKEPb39UgyXTgXMi6cTwDrGzq5FOM4LSxhkcsngPW1skZQ0gFkXp5Wuau1VwHih9FvrcFV/V6H+GGcHwoGlMUXlm64SBezpJcifvX6hcUS7pauN24MQ6bb1shYzy6Bi7S9KTdwc95NvdikrurrkKd3rJWeDm+yRWsBjIdtuGbWW9Nx6jp+2BoTWijDNObn6NGjKqQCgqKZGrxOIOyefvppx/jXRhw3XNfhUaNDZUodAzxz8B37+7//+2oTBGJSN3gEPPPMM+r17LPP5t2ALPV+Xh8PzyD8fUE1BPx9YSOBRiBAAdwIs8Q+kgAJlE0AgkiLXyeXZ+tN8ECKBxucg8zIiMmarzFbdnfKOvGNN95QAlzHmLm9SLkuz/br4wEeD+co84TkLNb2yfBt+a93zxbvUjYty6+9ItHZ4iLZehFsOsCqhpZLGpOVrq5ulS06o8oZ5RfChQSwcykhiN82CaZy90wmUWJpsQXYPlBYi/GyC/tcMq1cQq2UJYlVsfhhiHyI9GJWVVifA2mUOyq+EWBnWbIAvncBWLynOjctKp0E6+6UzfW52CKAW/QD6wfkEQ/coq9du6YeuPGZRLmYem9uMkzju+X8+fMqxMD+Gav38Tn1D94zCKOAiGvGhuRlsPJ7laX/G9/4hvzGb/yGcqnGZg8s6Lp98YtflL/+67+uG4xf//rX5ad/+qfVpg0EMP5OsZFAIxCgAG6EWWIfSYAEyiJQqsuzvgliZGG1gICExQY7706ZO8vqoMuTEBuIsTz11FMuzxDlYqczVTtleXa6KEp+wDKBhxyrdWp0alb+6fWTjnG/XSPvSuv0FQmkZgpU+p3vAcYJyysEMJjDaol/ZzJpJYB1g1CEldQuA/MJYIgvHFesFq9dWLoVwLo/sAaLmBJIzy7CqdchBLFOvKYPssYPRyNhga5Hf2E5htu3tWWMnDu1eS8Zl9O86d+X6gKd77oonTSxbIuMd21VG0GliF/r9dqiIdm3bbXcX4FbNOJJEVfaKALYzlNnmIYLLKzEeqMHx+H7BhnfdVxoNbPLu10/lR6H7yF8HyFjcLM1fLa9rt+M0ke/9Eu/JN/85jflx3/8x1XOBngB4T7YRPiJn/iJJcP4J3/yJypGGWsWghc/wysK9YrLtYAv2WB4Y18ToAD29fRz8CTQvATKcXmGpQYP1nAZRkwXXrgOXPeWskF84qHZzQOk1eUZfYZ7Wqkuz/axwg0ZInzDhg1z8Yko5fMvBz+S2+PFXZrbxs9I78jb6pLKWmoEJZCaVQLP3sAf98I72ENoQQRPTEyo/4MosDYIRbwSFqFoF8DKBTmQK61UqGWMkIiRFTMzXyYJQhUPecg8rWsMu1kDENIiGQmk4wUPz2YzqsySLrc0lyEWwtfMJdRCuaWWSGTOLVoJ+ED0nvXXTU/mj9ECuKOjXZxikp2ubIRbZXTZNhnv2ChilG/tWdXbobJF93a2Ot1y0e8RH4nss7C4VaP2dskdqvAEuNvDqg0LMAQwPuu6YQNIi2FkD3btwl5hn7w8HSXU8FkqZQPPy/tX81rVKF+F0ke//uu/Lv/5n/8pX/jCF6rZ/ZKv/ZnPfEZ+8IMfzJ0Hj4V//Md/VPHdbCTQSAQogBtptthXEiABRwK6ti8eTKyJaJxiXiG8kMQD1k48cMK6BNc9/Pu5555zvG81D4D7MeouOrkQ2l2eMQa45lXaYIGFGzbif3WG2pdOnJeTl4q7NIdnR2Tg2stiZBdaMmEthaALWkQiRAAekjF/LS0t0tISVaWP0AoJYD2ucNBULtF42QWwU9xvVgzJIJZXuRXPNy2AEdtXuhXOkFSwRcxMYoGoLjQPWKemZGQmFl8UP4yNgJZIWBLBdolkC4vqYnPslQDObSYgc3VGUqF2FR9cXumkXG/hDv7AuuXy6OaVKu7ZbdMC+MEHH1y0KeL2GvV2HCykqCcL92dYgK0JtfDZQMN3WEdHhxozhD9CMxrB5dReJqje2FfSH529G1ZQr+Jf//zP/1x+7/d+T9WFrldhiZhl1AT+gz/4A0GVAmSrRtZqNhJoFAIUwI0yU+wnCZCAIwE3tX3tF8E5sL4gzgpCBC6+eAjFwyYe3PAg+tnPftbx3tU8ABmYIQ7hil2o2V2ekbAL4smLhvhRPIxhtx8W5dNXRuSFY/OJWvLdA+7Og1e+V9RimUI25UxK4tPjSuCDOSxe9n5PTk6oOFq7Bdh6X51IKpFMqRhhtFDQlFQqUzRatlBCqcoEcK5nWTElHczF7BpFMl8jPlnXPMZ59vjhuBGVViOuEoEZgfkM027rD3slgPNtJuRKJz0gsdahspdaqW7RqCmL2rJ79uxRNVWboUFQwFKKOHtrrW2VBb3CDNNLzeftt99W363lJPFb6r473V8nL8OcYe68aH/8x38sX/7yl9WGCNZ4PTd8T2JesXbxd8ptJux6HhP75g8CFMD+mGeOkgSanoC9tq8qJaOSJxVuEBoQvhDAyLIMi6nVpRJ/1EdGRuT5559fUn6owQuBC/cze8MDMgQB4rHQvHB5tt8D4hQxaMhQO7h6vfwz6v3a4lMXnJNJy8DVlyQSv1OUG9zLJ6emZDYTlIiZlWXtUTHNxZZAWOHhLtzdXbwsD26Ger6wVKbTGSWoCyXLUkIz1CbBZC7plb15IYD1NbNGQNLBaM7122YNhxUUrVB8csoIqbrD6WRCxQ+jMnJalXoScVt/2AsBrLJUpxbHXKu+m0GJRZfLeM9OSUTKT0rl1i0aIQpnz55VMYdeeDgs6Yf73s2x0XbixAkVYoBSSIWa2wzT2CxCCIGT50stxo4M8uhHJWXcatHPcu6BzQkIP5SHQ4iIFw2W1L/4i79Qid4aoSY04oJRwglWa1iE2UigEQhQADfCLLGPJEACBQlol2eIWTwcKhFkmo4PfhCUcHmGe29/f7+qvWh3dUXyFtSv/NznPucopqs5RbAEIIsq+mFtEERIQoI+wm0YMZHVEARw80Mt4qEVK+WD2xm5PbE40ZO1Xz03j0r75PmiSNB3uJ1j/qLRiERb2yUTbBUzNavcga0NAhjHu4n3zLlAZyQSCgrqExcSwOlAVMxMXAxbzWB9Xy8FsL7mfMZo8Ju3UtuTXenjIZyRhdnMJOdwYGxpCNFMSmZiiQU1j4PBwFz8sDXWt1IBrCzP2FTIm3nbyJWDggu5Kp20WsZ6HshbOsnNZwQbArvWLZfHirhFN6MAxucb30cQPCh947Zh4w8hArrkEj4rOqYc32c6fhjv+I5YilZqndyl6GO59wR7eArBawgeMl60X/3VX5Wvfe1rgmRvS1F2r9Qx/N3f/Z383M/9nNRbhupSx8Hj/UWAAthf883RkkBTEbCWN8LPuq5vMasHHg4vXbqkkl2h4YETDy75zkGMEx5CYHldyuQzsAwhEygs0bqfVgE/MDAgO3bs8Mzl2b5IIAaR+ORKLCxTUjxpUcfYx9J9+1iRdZaV2dmYwHUQogpWqnAYGZRzLWMEcjG5lozRpQpglEqCSzFYIT4YP1t1LiyWsLvC/bpQg8V5ampa1fcsPQa4+McsEwircbYZCYknCyfnQhxxMFV4swHjTKXTEosn7pVbyl9/GJ8NiOByk2AVq0tsLR01L9xROmmjjHXtkEywPNHVFgnJk9tWy5ZViy3K+PyiVurevXtVHGwztNu3b6ucA/DgGBoq3528WIbpaDSqNpFqnWEaCfQgvpsxS7C23Ntd1ytZk7/wC7+gMkDjO74R1vfP/MzPyD/8wz8ILMEo38RGAo1AgAK4EWaJfSQBElhAoNzavrBkQtTCrRnCxilJFKyrcC9G7K3XIqiUKbVaoiHqrC7PSLwC1+RqujrCyvS1b/1vOTdhCMR2oRaZuaHq/eaKDi1usF5CVEJQI3kP4n0LJfFJm0HJGmElhKenJyWRgAUYWaDzpI+23CqoskIvFJVI3KTLCrnNplxNAYzuIj45ng1LVrJ5M0bnE5b5mKrY51BAWbvhRl2o/rC6ZyioMlrj3W38cLEkYqlAqwTThbOAW0snZc3y4tFX3ssW3WfJFn3x4kWVMRnxhkgK1Qzt1q1bqvTatm3bBGXLvGr4ztMJtWAlRjiDbth80oK4mhmmkUAP84SkZc3W9MYFvoe9stb+1E/9lHz3u98VzN1SbrzquUIVAmxC2j2h8D3+N3/zNwKLNUKIsKlsjV9vtrnmeJqLAAVwc80nR0MCTU+g3Nq+ePiDiyEsYXhQwYOm08MF4mphbUIZJFhPlqrBMoQ4ZfQDcWHVdnm2j/PW2JT80df+l7S0thV8OA8kJ2Vw+MWC5X8gzODyDGskNhPcxifCvXYCbp2xKUcBDJELQQgxmK/BkhkzWsRMTDlOpe5va2vLAgu144kuDoCbL/qpXYrTwVaRTEoCmVy2XyQHC6qs1Pk3EvLdAteERdhqUdaflXg8puJ3rc1N/DCOgajWLrXW8+HOjXjmYsm99PFwNx/v3i6TnZvKKp2Ese1c2y+P3b9SubZfuHBBvZpJAMPDAxtu8ORAFuhqNXhe1DrDNPIH6Mz61RrXUl1Xb1wg6WCxzcFS+vcjP/IjgsRhSIxWD1m+//7v/15+9md/Vvr6+pQVv7e3VyD8sZl8/fp19bcRFmDULGYjgUYhQAHcKDPFfpIACSjxBGECiyQeyt26PCNhDlwm8TAB4es2xg61RmFpQv1KCLalarAMofQL3AjxAFttl2frOGFNRdKrt0+clPZ2CODF7plGJikDw9+XcGIsD6Ks2nSYns5ZCmF5R8yvkyXXeiEI5+mkIX09XRLIzsfD2m+GbMrxJNyA8wtHZHwOp2eV9dXuFm2/VjUFsD3rc+7eudJJkk1LIJNyJSzzrUeMDbo5adkE0DHAWMNaFMN6Y+Vkjx9WJY9Mc8F19P0Ql6zqOd8T7G4/F5WWToJb9BPbVkk4Pq42ph555BHlRdAMDUICG27IRYCcBLVotcgwjXu8+uqrc3kWajGuWt5Dz9uuXbuUQPSioeoAvByw6emUyNGL+zldA5tNqE2MSgD4ewjxi01MJP6Cd9Qv//Ivq+oJbCTQSAQogBtptthXEvApgXJr+8LdD1ZfWDzgggeX51IemCGc8XryySeXzNUSY4c1AGOA4K+Fy7N1mb1w7KycvnJbzp07K62tbXnjE3tvHJS2qcuLVif6PjMzLfF4Qj3Igb2T1T3fEocAhjsgrEjpUJsY6YQEsgvjd3Wcqr0OsL4eLMlmOomqv+q/rG7R+e6pBTA2HeDe51Ur5lKsaxIj+ZWZnhXTljHabR9gXQYPiGBYcAslwdKZ0+HujfHqfQOIX9QeRj/gLm3PzF2odJTb/uVKJ+2SWOsKt6csOC6UnpXBUEw+9/STS7oxVVbnC5wEsQPvDi+FVKn9q0aGaawxCCds2sFK2mxN16RGAkI3Sfqcxo/vryeeeEJtdOJvTzVDW5z6wt+TQDMToABu5tnl2EigCQjYXZ7dWH0xbLimwUULli7EyCLZVanuZNjthhUYdQ6rkV3ZaXogSmD9hcszGmLovHKzc7o3fn/y0i05cvqKIEvx6U8+VRZoe5xb5+hJ6brz/qLL4cEXwhXvqOsL62O51ozp6SkloiGAMf8QinAb1hmjIWa123M+AYyEU2LLpowO2+NnrYOohgBGP9OZzIKEXAvuGWyVYCpnKUeiroyJZGDTDlHPhWdSu0VPTE7LbCymNnGKfQZ0/LBk0hJLzFvacZ1gMKQ2L4yWTgkp9+zKW6xlUEZ7Hyi5dNLo6F2ZGB+X/7bvQdm3Y71yi270hmR7iKH0Skh5wcOLDNP4/j148KDaOEOCr2ZrXmckx/cXNmrxWcXmLQVws60YjqdeCFAA18tMsB8kQAKLCMAigQcovGuXZycRhWPxIAkXSQgvxNSVKxrhhgarDFwtvdjdL2WKUV4Dya9Qpgluw3ivpSv2yPi0fPONj5QVEWVwrl65IEhktGLFfImW6PRV6b/++qKkV7DWQrTCogjR3NKC+OniyauKsUGtTVgxu7u7FiRvgrDNBiMCi6Cuo7tYAFvK9BS4CcanklJZMjJ7LYBhVQ2Y80Ld3pVCVtX50kmFk005rSvUEFbrqL24AMZ1dMkjbChYE2qhrnLSCEkwm5JgYF4QK1GMwZXbyiidpJI5TU/I8sGV0tneIk9sXS1bV/VW1o9y++/ReUhsd+bMGbXJhY2eemzlZJjG5xZJlFDbGDWOm63peHSv3PHx/YWM0qgpfPTo0WbDxfGQQN0QoACum6lgR0iABDSBcmv7Qihh1xziEQ+RcCespPYlHkqRmAblVryK73KaZYwd94Xwxs+wXENQwhpdK1fsXNzvSbk7OV+C58KF89LeEpW169bKTCwpwcS4Snq1sEYtShzNqkyzEEVwecYmRKVNC2BkqrVvgCCedjZjzGWMRgCsNba1FHddJJFCg+j3WgAXc31OmxHFUbtn5+MFF24RUwLp4jWY852LJFgoPdXbdbCI2QAAIABJREFUvUyyqLJcIEYa5+aPTxbJZA2JpzOSTcyqTali8cPlzDfiit2WThobuyuT4xMytHKlskyjDfW0y4Eda2T5sqWL1S9n3Pocry2JlfTF7bluMkzD8wPfZfDCacY4UbgpY+7gJVTJ3xrr3z542eB6L730ktup4HEkQAIlEqAALhEYDycBEqgugXJq+6JHiKGDWIXb3saNG9XLyVrsNBK4JcKNes+ePVXNzKr7AdGFMSCxCh6m4A4J12sk8IJ1qFau2N9794x8PHxnAZ6LFy8osQFLTkc4K8vOfVfM+PjcMTp+EGOAVRDit1L++uKFBHAESa8s2Y0hJBHhG7hXO7cU8avvpd2iUVt3YmJSzUOlMcCFRCXuiThbiD/rRkKxdYmMyhD5gXTcafnO/V4LYLhVIqYXya3spaJwcNGSR7aaxJlMWpLJVNH6w/nih5067aZ00uzUuNwYuatKrlhjyuGqvWNNvzy+JZctupGaLu3UyLWNC2WYVmsrElEZ5OFJg9q2pYaj1OtcwtsIfyf27dvnSak8/P0Cox/+4R+W//iP/6jXYbNfJNDwBCiAG34KOQASaA4CurbvnTt3VOwoSoG4ca+E4Dp16pQSwHjIgtUXZRq8aIi9hRsyYrIQw1bNZnV5xtiRDVZbT+FmhwetRx99tOrukR9evCkvv3dh0VDxgI5MwatWrlJuz62zVyUUCCghBYsg5gxziJIYEI0VucXa7o5EWrFYXKwWYBVPC4uk7VgVMx6ISjorEsrEVamechoE3szUlIQiEYlEyi+BFTAN5QpeyOqaDrSUYdXNZYw2MwkxMwuTgeUbq1UAa+EBl2/0S8dOF4tPdrORUKj+sDV+uJT6w7nSSdtksvO+BaWT2lvCcmn4uvLyWLNmtQQCi4VuayQoT2xZJVtX93m6DstZR27P8dqV1u19q3WczjCN71BYSLEZhk0yNPyMjT146UDsYWPGy++Lao0p33Xxtwdj3L9/f1kJ/uzXxPcoLMCoBfyNb3yjlkPhvUjAVwQogH013RwsCdQnAWuiK1hAkVnzmWeecbS8WUUjXJQhflGewauGRFrHjx9XYtRt6aRS742xY7wogaJdnteuXbvggRDxzPh9tWORb92L+81XR/fSpYvqwXVH66gsGz15b5hZScbjMjM7I5msSFtbu6f8NUvEr8KtuqtrmcpIjJBTxKrqOrpW5mojRUyVKCsTCIuZTohpyxjtZo5giZmcnJSOtlaJRKMFawsXu5YqJRQwVRKxfC0VbJNgatpNd/IeA3fmdBACOla0bFI+AYwLon+wTqN/EKr55j0n0EurSaw/zzlRnFQbFbq5qT9sHawqndS9U6bb16nM1pjzWyMjyjpvF8A6T4A+f7C7TZ7eubYh3KIR4oBNJmxyLWXJtbIXY4ETx8fH5dixY8ojB4IX8dvIaI//14IYG53Y3MLv8XJbI9zrvpZzPSQpxN8J/L3yQsSjHjRigH/xF39R/vZv/7acLvEcEiABFwQogF1A4iEkQALVI2Cv7YusyxB8Bw4cKBhThQddWBUQW4aG5CqoSejFA4h1pKh3+O6776ryHXC39LpZXZ5hOYXLMx4E7a0Wscion4u439Gp/Bl+L1++JJ3xG7LLOKu6h3mDazKsv7Aq9nQtU+7HRcJLy8anBTCsRrhXUZdiWDXN8D3RBhdjU7SIM7Jp133QAjhn0Y7eu2cuGZvb5uxSXJqwLHRfuFEjRjgnhBeL7UICWF8P/cSoEpYkYGqOzaCqKVzOBoK1r9ksktlBDKfKjh9ORLokNrhXRoPL5c6d20oAr1q9WiaSptyezchMKjcvLUFD+lpM6Ykacwm9dqxdLo/fv1Ki4fp1i9axpI899phKetcsDWL3xIkT6jsa4RO6eZFhuh4Y6TJ7Tz/9tCfdQbgLEqH92q/9mnzlK1/x5Jq8CAmQwGICFMBcFSRAAktCoFBtXzwI4iEAMVX5avYi8YredbfGyVZjEHh4e+utt1TtXQhsLxusi3gwhLizuzzb71OLWOTvvnNGPr26MO7X2o9b50/KhtHD0rOsfS5BFEQwLO7aYgOrbDBoSiIJt9wKMgPbAFgFcEskJIkCFlWclgy0SiA5tWiqskYwJxJdlhWyCmCIYDS4M8OCaReK+dYFXIxTqcUu2jg2bYbFzKaLWm3LWWvzGaORKGteqBcTwDkLcFodHQoGlMDPWYKds2eX00ecU078sN5MiLUMyDljjdyYDUpq2UoZjxsyncpK/J4ADgcMaQ+JdEVN2dgVkKCZW4ct4aA8sXWVbKtTt2jE+GOjCzVg9Xorl289nYeQFohEfIfaS6hZ+1lOhul6GCc8hLARiAz9XjSwwrV+93d/V/7wD//Qi0vyGiRAAnkIUABzWZAACdScQLHavsVq70KQ4gEB7rBIqIISR9YkOF4PBG56KEUB6wXKUnjR3Lg82++D+OYPPvhAWYgxbq/b+xduyCvvXyx4WTMdk8iH/yKh1LR6OIcgRWuDe3AE2YkXil1kU4agyudSW07fZ2dnVBbjHpRBMnOxq/laEomakjNFrbRuywrlE8D6nvb4WXtfdCmhvC7ahilI9ATX7Go1uH6jRFTwXjIwfF7wstcBhtszZs7aT+0WPSNRCSTLd88uZWxO8cPRSFgM5fqeW2dwuT+S2S4jXTtkPB2W9qAh0XvG3VhaZCqRlc6IIQOtpmzqXmj1hVv0gR1rZaCrvrJFw/MFoRDI9F5p0rVS2Ff72JGREZVIcNu2bSV9d7nJMA13aXjMVPNvgBOfd955R2Xpx7x50fD35vnnn5cvf/nL8tu//dteXJLXIAESyEOAApjLggRIoKYE7C7PeKjVL3QkX+1diEYIY1hJEIeKhynE5Hrt8mwHASstaliifIcXJTzcujzb+6GTcSHGuZgVpZyJvDU2Ld88+FFhsZrNyPJrr0r8xicqwzYa5gDWeacHT8RsJpM562IlDdll8YIAFiNXqsjeclbVJMyLrtyUc2WFjDlX6UXXuxcDDMGfzyKnhSKSgNkFeTEXbTcJpSphZT1XZ4xOTo/nFcCF+okEW5FMTMUvW2sje9WvYtfJFz8MIy5izHX88Hg6JO9kNsvt8KD0RU3JtParTQXdkpms3JnNSn+rKVt7g9IWWrhBg7nLZYteVTdu0QjnwEYXrH9elA6rxVy5uQe+u5AoCpuV8HQptxXKMI2/AdjY0Qm1ap1hGh5CWLNwXfeivfzyy/JjP/Zj8pd/+ZfypS99yYtL8hokQAJ5CFAAc1mQAAnUhIDb2r463vWhhx6S/v5+9eAO6yeSp0B0wQqazzW6GoOAa9vBgwdl/fr1qh5vJQ1iGhmlcU08COKB0G3CLp2MC+dY4+gq6Q/OjSVT8k+vfSjj04VL6nTdPi5td06qpDVoeDhvb28To4AQtfdJZQEuUHbHbf/x8JuMx6S1vSOv6EbSKzEDqpQQ1lmxON2pbFgmM8hSnZVuY1qCobAYmYyYmYUM4KaLONNCAlj3XblFW8ZXPO63sqRXbnnZj5tMGhKfmZTu9pa58jOF+gkLOeKIdby0k7W73D65PS8UMGR6Nr4gfviCsUI+MdZL2gjKQGBSjEBQkq0DEosun9sgGYtlJBAwZH1nQFZ3BvLeDm7REMHb1yx9tmgkuUP5M6+yCbvlW+3jIOoh7rF551UtdZ1hGh5BeI2N/f/svXmQJFd9NXpyqb16nemenn2XNFqQ0MYihISMwAbsD2MwJkxgCMLP4JUAO+zwCxsc+I9nwGE74guwn+3AGH88f7YxnwGDJCQhDdo1ktCukTQzmn3rvbr2XF6cX/atzqquJWvr7hnljegYqTvz5r2/zMq65/7O75xZeT7YVlph+qGHHpJ34g033NCTUNL66GMf+xi+/vWv4+Mf/3hP+gw7CSMQRmB5BEIAHD4VYQTCCPQ9AlywUCyJ2V/+1GZ9/QNQdF8KgXAxQ/DLcylCxTqylfSPJPi+7777QFXmffv2dRQnzp01vMyC8L87EexSYlzMfG/btq2jcdQ76XuPvYxXT0037C81fxipE/sxm7dg2gXRVh4dHemovpdAynHcusrNrSZklYuYz2TFP7Q268zsK5WQTZt1r544V6lUFLVo/7GTTgrPW5tx1hlECYZQf+MoY6sxjSvNU4hGPFqyEnwKCoDV2JlN1eCiZNcH4MzGEmRrbYhotYpL0L/zOc4XCkiOjCOiOYhqjtyH2o0CpZxd6zHcLNsddAydHFcvQ8378lh+Ew7b44igjAF4dHxpZhTFxAZYyXEUHQNFB9g+yFrg5uJXpEPfcuU2TIykOxlmT86h+j0VgCn+t5LvuJ4MvkknpHWT3s2NS1oe9aMpD/LVUJjmBik1EOgV34v2rW99C5/61Kfw7//+7/jgBz/Yiy7DPsIIhBGoE4EQAIePRRiBMAJ9jQBps9yd5yKFC26C2mbUZS4CKQ7FbAGBH0EMM5/9qH1tNXHWdt17770CvqkE3W7zexQ3U3lu1S8Xdo899piAcILxXrSnDp3Bfc82rvuN5M9j4NXvopTPeffLiKDk6hgfTIp9TieNp5EWTRGpoLRoZlhzuTyyuVxdAFw2U1KbzMZ400dT2auIDZEZwaSxHo9hH2bcNBbcGCLg9TU40DCo5bFBn8et0YNI6lZFMdq1y+I1G4/HEI8nWk5XQqTrIpRVS4ummjLnrgXw7G15oQ4OqKoBNiNANAWnlIVeoxjdip5dm+3uYCiBTxH/ZNnQqH5SOMbn5qJ40d6CWDSCYWQqG2vqvltaBOcim1GODGD7kIFL1sfr+gX7B8P7d4XQojcjEY0EHmevDuy1nU6vxtVtP1Trp7AhAWI9hftu+693/koqTHODlPRresX3ov393/89Pve5z+GHP/whfvZnf7YXXYZ9hBEII1AnAiEADh+LMAJhBPoSgVqhK16kFfjlMcwYcDHIxgUTFxZUe16NRkDFmizWG9MLuJ3mpzyTys3zg1Kea69Dit8jjzwiNGzSsbttZ2YW8G8/eb5hNtYtzCP90rehlRZkA4KUc2V5NLR+ArTdURnXTsbiASkCxfr+uP4+WYe6kM2J8NbgICnQS+DEEo/avIDLYrEoY2RTNbv0oM1ZGn5s3IizWEeiNIb1BUR1euBqKMHElJNGUithl3Eet0UPyvlUjC5pJrJTZ5FMRAMBYAXseb4fCPdTTTlo7P0AOBmPSl0vQbmjL6litwK//mtVq0UHHUV7x9XN/upRaK6FYws6fmpvx1xkDFv0aSwKPS8CZkd8h084oxhEzrNDig0iGo3IfeS7hM8H30X1Gq2SCIJZI9xvjQH/9SkURcGo2267rb1ArfGjqelA/Ybrr79eNrBWo/VLYZrfcT/+8Y+lVKfd74dGcfirv/orfP7zn5fSGzohhC2MQBiB/kQgBMD9iWvYaxiB13UEWgldNQoO6c8Evzx/3bp1YB1wo4XqSgSY47jrrrsk+0wKX5DWC8pz7XWYiWSt2d69e7F79+4gw2h4TKFk4X/d9yzmc/XrfnPZDNKvfBfJ0rQABQIGAgECelLRFY3RMuJi+mvW1M62MziCHEto8fXzwQpUEsARAFPsRgkE2VqERj3QHFv+RgDMeuNUKl1FIT1ojePh0k7MuQmMOaR7L12L83I0E2cxis3GLG6PvoBR3aPU8t7PZHKIJNIYjDafVaN6Ws6vqMehlYKrKZddHbNuEjk3KlnqGCwM6zkktHI7oa06VgHg0ZEhOG51+p41v5Yekyy6l3MN1vpJi64XT/o5K/XsTDaHx+3dmIxuliz+ei0DU/M2UyxXx6Sblk0RguPr3IPIGEM4au7Cgj4gx3DsVFrms82faDS2DOyOD6dw6wrSonvtJxvsLvb/KIJfguA3velNQhVeC61XCtPMNN9///3y/cDylF60P//zP8eXvvQl0F6JZUBhCyMQRqA/EQgBcH/iGvYaRuB1GQGCPwV+g1KeZdFqWaAIDGtlCXAItHppPdTNzbjzzjtlhz9IjVct5ZnZa9Ljum1KjZrglyC4m/bdRw/i0OmZOl24mJ6eQfL4foyVTshi1Z+xXljIoFQiAF6qASZcso2E1M1SgKqTRjASMQyhDfsba4bLixniWgBM0SuXtjhWUSjPjLvKVBPU+mtb7ytdghftTYijhAG96IlkEXQvPqu85jSGEEUJ1+mv4krzdAVkc+OBQCmaHm6oGM1srzz3dWp7mVWN2Dl42czW6tRTTgpnbOYto8i7HuqOahZSKGJYz2OzPlPJdrYTa8aPXsCDA4NiI+VvzOYLuBRRM10y6u20XtOimfG3pVyiehT+DDUz/VPlKI4kL8ekM4CMG0d0cYOg5EaQ1gpYry/gavMERhY3NNjbfHwTTsX3YN6KoFDIVzZeuHHiZYe9DR9vk4Xq9BDfYPoH95sWTYE8Cs2xBvhiaqQ/kwb9lre8ZdWYPK3iGURhmu/xoaGhqs01AukHHnhAGELdiiSqMf7xH/+xKECzbrrbd32reYd/DyPweo5ACIBfz3c/nHsYgR5GoFPKs58qzLpf+u2y3rUXYK8X0/vRj34kIJYUvmatl5Tn2uv0So36iVdPY/9zR5dNg5kM1l4npl/CjuJLQnmuFeIh0OSCj7GopYYKEDaT0C0KZbWmNdeLo0lPWk1D2XYkk8tkpAKVfgBMCjRFr0jT5pj43Pkz1bUq0D8q7cNBawJDer5uFpVgeNpJAq6Dq+yDuNw9LMNTQDoSMZFMUvVak+v6FaOlxljXZcy1zVmk61JRmY3AjtNqRPvmGE7YIzjvpmHAFVo2RccKbgR5N4JRPYtxPYOtRr3Ni+ZPOuNXLhWQTA0su6+11GdaIHHMtUJYrT5LBPm26wr9uNOm6qhr/aMtIwnTXhK7ymYXUC5bMAY34JAzDm4cFF1P6CqmWRjVsthtnMegXlg+FE1HZmA3ZoevQM7WBQgTAJFBoEA3n30vO+wB4lQijjdfuhlXbe8fLZoZPz7PVIG+mNqF5m/cjsI0N91YmkJhwl7Y5PG+/+7v/i7+6Z/+SRTBV0P34mJ69sK5hBFoFoEQAIfPRxiBMAJdR6ATyjMXGrQ8okWGXx2Zi3XSynbs2CGqz6vd7rnnHgGEpPA1aqxb7kbludUcuUBnTLpRoz49ncG/P/DCsrpfxptenbH8OVxRfkYW+/VqH5sBYDV+R9PhsC7XyrVFp/XPn0CK/FUKZanG7GU2Swp0GlpiGNbCtNCeCZhIefZnqmsB8APlPXjO2gwTtmRR6zUqQzPL+ubIIex2T4C1wwRYSlSJ5xAUEQxLDXJ8UBSjE6Zb1yeXcWAdseGUll1OgKLjqTBX4uYCr9gbcMoZlkz1UA1wI7ibdNIi1rXbPI+UtrzfZs+QY5Uwv+BRyP0bG5bZyJZJA4Gw7pSgtyHc1S0tuh712TaiMga1kcB5qqy/ElUiXTzj0tcZSGvFQPEhnXp+6FLMDV8OV4/IvRa17HxeQDHZDqqp+uGtG0bxs9dfgi1jZAT0tj3xxBNy7Yut7vNC9zdupjDNzxI3D1kWQgBM1ky3deOf/OQnRQGaG6orZffX2yc57C2MwIURgRAAXxj3KRxlGIE1GQHl7ctFAH/YgghdkeLMWl/JOiYSInSlFrPdKi/3OlBU+SQNlhS+2tYvynPtdZidotgKMw2d1Jqx7vdf7nsGmZwfOLlCuaTSdsQp4I3W00g1Eb9lFprjGBkZbukBzLpax4jAKOfbVoxm3W/Zshdp0fT21CoCV7GhdXDyGZRLJRiGjnR6eUazFgAfs0fxk/JenHcGsFGfhaFVc2sLril/26LP4D2xZyvgyXUdzM3NC7Wai1rea0WtlhrSaAxWNIW45sCs0VNiNpybAI0a+4uaekUtetZJ4Ii9HtNuChPaXN2YzThU33axXZ+WeuWgTYmIEdz5ATBruE2bdeCN635d6JL1NuxCxRc4yHU7oUWT8m6RJu67gCs12pFlGwkEwLZtYWioeyDqGDHMDV+B+aG9gLbkF8z+CUjzeQ8Uq/dbMmpg29gQ3n7ldmyaGJeY9kKn4MCBA/Kc33TTTUFCfMEcw41BbrBdLPZOfoVpvjuV8B5vCDfiyJBRP52IN/7Kr/wK7rjjDnkWai3fVvqmc5ORGhjf+973hOp99OhR2UAj2P+lX/olfPaznw1B+krflPB6PYtACIB7FsqwozACr68ItOPt64/MzMwMKPjCBTkpXrQXUsJGPE4pL2/atAlveMMbVj2o+/fvly/92oUpF+Gs2+O/3ao8t5okNwyYid6yZYtYQrXTeJ9Y93v4zBJoYlbj3LlzMnYCsTe6zyNlzzftVgFgblQEXfDbBrNymgCoIM1f98vjCd7Ysrk85rJFD2g6lij6MvNbL9tSC4BtV8NdpStw3BlBwY1iiIJSKInAVNaNiTjWOi2Ly8zTuDGyZAulALASS+I4uPjlvSA4smntRQ9ikr7NOJK6LRZPiA8hErCOlvMjmD5RSgkAtl29qmbVHzPSoOfdOLYZ09htTAYJp/TNGmWqaPsBcLu2TKwTpo+xKG7XWCc1Gwiz3cIOaSByps7VNa/e1p8Vl3eBmYRZZyPBA8C21GT2qlmRFGZH3oBseoenklXVFn3MyyXMLWQFEJsacOmGJHatT0kGkKCH/yaT3Kho3yeMZR989731rW/t1ZTWRD8Xq70Tg8vvMlr28buK3xH8fz6bqrE0Qz0X/DeIC8Av/MIviAAW+wr6nu3Xjf6Hf/gH/Pqv/7p0Tws+fvcoQUZmqMnQIjNpfHy8X0MI+w0j0LcIhAC4b6ENOw4jcPFGoNbblwu+Vl/WBCZUBKUoCo/nFyoB3bJ6UtcFhafaUV7uZ6S5881FvL82j2JdzGzw9xTrIl27k0Vv0HEz3qxF7mRT4MArp/CT549VLsUM+5kzp4XimU6nsM99FQPZpb83GlMnAFj1RbsizbXrUoL9IIj/XU9MitmQ+VwJhluu1GYKT7pOWwLAzCV6x2ScGPaXLxEaMUFkCUx1s862LD7AFJd6W+TVipIwz2E/zJDHYlEkEsmqK4m6c9kSwMIfgmKqN5e0KOJ2VjI3ii4dJIszow/hldIoyk5zAEyxJwLgXQEBsKIUV/kAGya4MRF0U8I/cSpG88e0SCUPphhdm+2ud8/qWR41pmd7FOheA2A1rlJsBLOjVyOf3FQ1VIJ0bhYV6GHtupKhI1U6ptnYM2JgJOHVIBPk+AExN1CCtEcffVT6ffOb3xzk8AvmmGeeeQZTU1N4xzveccGMOehAmQHm/AgE+W5m60Zhmvf/ne98p1gB8qfVd2rQcXZ63De+8Q1xH/jMZz4j39eqsT75ve99r4D/j3zkI/jWt77V6SXC88IIrFoEQgC8aqEPLxxG4MKLgKI8c9Gv6iODUJ65WFQLIdY1kfJM6mCjRtqVskFa7Sg9/PDDsti99dZbq9SqubvfK5XnVnNk3DvZFDg5NY//ePDFitotd+3Pnz8nC23GdytOY2TyqVaXl7+TDkcgNTw8BF1foooGOnkRLpEarNkEsqQ2+5sLessq1eelv7jI5fKYLwExt4CBVBJmNN70kvUAME8g1fmQPS6ZVmZT2VgTvNs4h+361DJqdCMA3MjyiPY7rB0uWi5KloXIYtZbhLJMr3aYoLhe7AjQjzhjmMIgxjFTF9pPOilRhCYFeqPRPFvPuflBpR8Au/FBmOXgtkz1gu0YUTj0ghYgHKxVeyMvneP3T1a/JUDX7TK0BoJqVCSn2FYvM8C1sygkNmBm9BqU4uvkTwOJKDL5+rXXZAtsGY5j77oo8gvzVbRY1oWqLCDZE402RPie4bu0mdZAsEivraMuVnVrRpklPM8//7ywmDZs2FA38EEUprlJwmeE7AGW2hBEUzysn5uq3T4lfF7JVuDYmRUOkt3u9prh+WEEehmBEAD3MpphX2EELuII+O2N+N/8clY/zaZ9/vx5PPvss/Klzowvd5JrFYZrzyfdlwD5xhtvXPWIMjPD7CfHoijPVKsmPXslv/RZF0aqWRA7JgYtXyyL3y8X7QRzzFYwo2mahmTXh51ZjJ36cWCxKgWAa61A2r1BpB57itH5imJ0PVDJZ4xqvwu2KZRl/j/BRDqZgAsXll0/A9kIAKtxUum3DEPmHVn0jq03BwWAeY+5MGWLGLr4Ftda9HAkjlCEl6jeRURglYpwSznZOFGNCtcKDPNfT20aeNUex2lnCKbuYp2eq6IDZ90o5pwkNuhz2GueQ1yr3UCongFrcNmnX0WbIDgxPI4YKO4ULHvb6t6SFs2+2lGM9kTAXBECqx0nr0cBMdbiNrPVWgkArOaeS29DYeJaLLjJllHjc0y16EsmhjA3N4vp6Wn54buPjfd6cHCwkiHmf6ssHzNtLAW54YYbWoX9gvo76bx8f958880X1LiDDJa+9RT54ncBvxNaNb5T+B7lM0GK8+zsrLwbfvjDH+LrX/86rrrqKnlGyHDghrG/NKhV3yv9d85D+TozDhs3blzpIYTXCyPQVQRCANxV+MKTwwhc/BHo1NuXgOWVV17BkSNHJOvB+qGgtg4UfGKGtZ7w1EpH/PHHH5fFChcmnBO9GXfu3Lniu/PtZMV5z35w4BW8fHJaspIUoSkUikIfnpjYgKiTx8TxO5pSkmvjnM/nRBCoWwCs+mUGkQJEcbeAZbY3liWLQFoApaNANGJiYSErCy5FKSWQKtuko1aPtBUADvr81AJg3n8Cttqxsr9WasqaXYRTKlTo0vXUpfNGGqcwjkk3DQsGBgxL/IpzbgSOq2GdvoAJYx4Teuvsr5dNX1LRJvhdKJQxPDiwTLAraDyaHccNDdZm11O9rneeokXz3tX6Pzeq+/X3QyYDs66Dg72rAW40P6lP1nXMpXZiduRK2bxp1caGkrj1yu3YOJqWzSc/6OG7RAlqcSNQCSa99tpr8s672ADwxSruxWeANGVmat/4xjd25PeuFKb/4z/+A9/85jcrG8Xsm5sjZB39zM+K5VaKAAAgAElEQVT8jPxQ/HAtZYRZ203ATpDOz2NQqn+rz0749zACKxWBEACvVKTD64QRuAAjwMUbd6i5YGuH8swFH4WumHEkYCJVWGXRgoShkfBUkHN7eQzn/ZOf/ESov/yCv+aaazpa6PRiTHfffbfEMsgC+bGXT+LBF44L3fjoiZNiKcSF9rp1o9AcCxtO3Iloaa6tYXmKuHkMDQ3CMLx6x24bfXHpl2tpZkXsiHRzZoxszUQ6mUQiZkoGjYDYD4B5bcmm6lqVt26/AHDeSGK2ZELXXAxrOan9bA5+l6LjaotqypanpqzYFKwd9qtLZ5HAVGQMRT2Jsh4X4JU0HKFTrzeyGNMyLVW162XT84UCskUbI+l4S/ZF5/eU1klJ6E4xkHUSx8ksPjcVFAhuVve7WgDYT9GmddLc0D6xT3KNaNNQkfp+2Zb1uGnfFiRjS/LqvPcEDCo7TProkrq4JlRaRZm+GEAFxb34Hl0Lm5mdP9v1z6Qq8qFDh8QjnoC128Z3HN/vvO98DlTs2C91Jqif0Yo91e0Ygp5PcSyKZP38z/88vvvd7wY9LTwujMCaiUAIgNfMrQgHEkZgbUWgE29fzoDZRu4Oc2HPTCkzpu2KeTz44IOyaPILT610dPwqz7w2RVxWc0F67733CgBsVSN4YnIe337wBUxOTWF6ekaUlHdt3yILdtdxsf7MT5DMHm87nAoAc6EXRNip9QWq634tPYaF7ILYHEE3kBocQUx3pJtyuYRMhgA4iVhseQ0w1aMdodW6AiY8QLEkgtV6LPWOcDE7O4f5yHqcjO/COSuBkmsKdTqhlbFJn8UlsWkknXzDWtXaXukNbJsxGOVsVZ2vUpcWQS1mv5FEHjEBwAnDxXjMQiKqw/bC0bA1omhnyoCdnV3mA9xZXJqfJWDfSEC389AbKEbXjlMsobQYXNkgaE3P9jLAbk9AR7PZ1Ipz0ZbJ1SKiEi3WSYN75Vlt1hQt+qrt47JhU9t4z0mFZZmIYpmoY4LWD/fjPvaqz4u1tpnxoagjM/csj+mFZy+fBQqovf/978d3vvMdqa3lZjDf/Xz/fu1rX+vVbemqnx/84Ad43/veJ98DZEhxgztsYQQutAiEAPhCu2PheMMI9DkCnXr7chH/4osvCi2MdZOkR9EeqJP2yCOPyBf+aimH+lWeubDhgvv2229f1d33Zn7EKsa5YhnfuPspHD56XMSjCNhJOydNLR41MTj1LOJnn+zklojiLfvsFQD2Zyodx5YMr2XZcGMDSA8MIeYs+egyU8p7QBYBaaL1GqEFs3XFsmIrdA+AX5yL4CXzUswZw8g6URGhcgTuahjSChg3FnCT+bIA4nbakprycq9gxbpQ6tJ+ujTvIQEXa4drNyFEiV3TpLbW35hVtRamqmyQ2hlrp8fSasnRYzCsarCvrJn8VHLaLEHXEdcdyearjGija68EABaw6quj5lhq6dnNrZOqR09a9C1XbsOm0frif/x8UySLGgmkSbdTP9zpPVqJ87iZye+DIMyVlRhPL6/BjOyxY8cku92J52/tWAh4qZPx0Y9+VCjRa7Gx5pniV3xG//qv/xq/93u/txaHGY4pjEDLCIQAuGWIwgPCCLx+IlBLeQ4qdMUFKSnPBDFUF6YoSDfZUu4qkz5NS4iVbATxtDciAOb4ubNNgQ+C+ttuu21FRa9q592KFs579y93P4EDz78iQJJUZQqzaBQVApDInsDY6f1i5WKR0t460VY1BNLASW0fZB2puUTp7OT+MANYXkxnEtzyueH4CW4jg+uFDm0bi3Ra1/PebQWA1TiMRU9ZKjE3skoKMmYqRf8gsxPnjTHEdRcjWraiFF10TZzDEAaQw17jHN4UORKky2XHBPFJrqVLi+iUpom3rme35IHhRCy6rJ7WNqJCRy7mq32AOxpshyfVgv16FG3LTFQUpclYIJCvrQ32Xz6TIW3Yq5PsV6vN/jarTS7FhjE7cjXyqc0th3PZlnW4ad9WpOLVnyHqHqh3p+okaP1wN/7DLQfc5QEsIeHG1XXXXddlT2vv9IMHD8p3xdve9raefDfQXujSSy/Fpz71qTWT7fVHnXO96aabQOr3Zz/7WfzlX/7l2rsp4YjCCASMQAiAAwYqPCyMwMUeAS60CTT4LxdeQb19CQ6Z+eU5e/bswa5du7oW63jiiSdEtfjd7373ioXdT3n2qzxzbvzCpyBJo+zjSgySC0m2emqqjP139z+BHx14SUDf+PgYBgaWwIFZmsPEiTsryroEGCZpp+XgIFEBYKpzd6NOygygylR6fealrjWVSkNPDIiisCKKUhGYdFq3MI+F+bmmGWA/aGA8/LToTu7PS9YEHi9MIKclsclYqKq9ZXaTNjynnGFsNWZwW/QlpLViJ5eRc2wzAdAn2a5vs+PvWNGl4dgoW2WhRbMOmqLYCgzzX5fUXM2E7pQk+8sf3rvVqiEk2I+aJuxitQVTo7pfTy3aqVLDVnFgpozPjP8Z7zj4dU6sBenMZGtuGVoDSrfqwrNOuhqleHNFYDIV3nzJJrxhxwahRfN5JQAmY4bMmUatWf1wp/7DvYxbvb7uv/9+2aigUNTF1rhZypKfW265pSefK4pGcqPgc5/7HL7yla+sqXCRkcDvHs75E5/4BP7xH/+x6+/5NTXBcDCvuwiEAPh1d8vDCYcRqI5Ap96+BMv0QOQCoNeeuLQbYr8EwCuhfMksL+dCcMGaZT+I5y4/laxZj9yOkFevnzNSCUmL5WLL3/i7ex58HN8/cAiGGcHGjROIRmOVQyiENXHiDkTKmWVDYia2maWQ/4RisYBsNicgqhsATGBTLFticVQqlWXhKPVzZkzqa3V3Sb1YXb9ku5jO5DEU01pSDf01wMx+S30pqdVtZrwftC7Fs/kRpPUihs2lMVEIiUJibGedQSS1Im6MHMFuY7LLW04RKdbOlqG7wSnVOmzkC2WUyiUB5arZkRSSuiX3ip9VioutJgAWyyMAJYp7uY5sdBAUe6C//s0hyPUysdW06H4CYGagCbzV88KaZt5zPcDmhIp9LrVVgLAVbZ6hXj+YwC1Uix5JgRRoCh/RUzZoU/XDijJN8TjV1kr9cL3MdtD5rfXjWLdNmz+W6vTie4rfe/ye+dM//VP82Z/92ZqZPjeHqURNUa4PfOAD+Ld/+7eeAP41M8FwIK/LCIQA+HV528NJhxHwIsCMAmmt586dE4El7tQH8falaAspz6zT5aKNFkfdgKLa+8GFBelWpED3RnCp/h331y0ryjPphP7GXXkqfZL6RQCxWo1iMgQxzESrRlrww48dwB3PnkYknhSf4CrBMdfF2On7kMidajpssRQiSGxylFJnJljt1P+Y18kXqeicEbDGfsRLUtNFDdpw6mdRudAn6ImlhxBLpGHa+YYjrSeCRVBDMFW2WqhILfbKrNw9xUvxYmEUo/oC0oui1wRDAuMW0dGUk4KpObjefA2Xmmd78mi4WFSMtj3F6GaNc+LcKABm6DoKJU9VOueY0IqeUJS/8RlnzFUWmH+edRPIud7mw5CeR0prnYXuZKLVlGINZSpGS9Z7yTu5Ub+1tGgPAGs9/zxKPPUlej7HE8SWqe64NR0LA7sCWSft3TQCd/I17Ni6SexuOm38jDarH1bq0n7/4U6vFeQ8ldnme4nfERdb43cg4+1/J3czR25y/tzP/Ry+9KUv4Q/+4A+66apn5/KZes973iNCXNyQpuJzp+//ng0q7CiMQA8iEALgHgQx7CKMwIUWAb+3L2ttCa52794t2c9mjecxG0pQyAXoZZddhq1bt/Zk99t/XdKsKC7ST+VlP+W5Wd0ylT7p9UihE9oQrVZ79NFHxR6Itchs3CCg2vbDR+bgxgcxPMyxVavMDk09haGZFwINuZ6lkP/EbgEwQUwuzywy632BZDKxSCn37HNY99uoKQBMoRn+WAaziG5dwNxIBZqRiYj1jiOAsVmjj+79+R14rrAecd3CqFkW5V8BwI4HSjmH0+6wWCK9KXIY243pQHEOehCFoWwzvkwx2n9+LVWX47a0KNwyQaUrjAb6QBeLpYqNGc/nZ3fOXIej+mYs6GkUEREAHEcZ6/UsLjXPVCjdrIWedlIowQD5AgNaQeZcR9C44dQa1f0aVlHo3wTBrcA+O1e06JnZub4A4Npxtnoug9xLZo9pm0T7pEbWSRSBO3XiON6ybyv+x61vqqsWHeRa/mNa1Q9TcIubff2sH+bzRwo0hfi6Afbtzn2ljn/yySflnVyvLKWTMdDr/YMf/CC++tWv4tOf/nQnXfT0HN6/D33oQ6JIzTnecccdq8qC6unkws5e9xEIAfDr/hEIA/B6i0Ct0BWzuA888IBYFlGAo1EjAHrmmWcwNTUlWTt64vYrI0qlSdpL9It23IzyXDt/joPjof0QMyir1ZQwGDcFlNr2kVkLs0ghHk8sG1YicxRjZx9oe7iNamdLpSIWFrJCV+4kA1As5JGVel9N+lCMAWYCI03ALydQC4AFgErtbFKoqbrrUZK95i4C3Poq0Kw/JvW7aNXPrioQdNQexQPZzZg1hrHFmIdmGFX+tjk3iik3hW36NG6PvoDooi9w2wFvcUIjxWgBu3Y1tdvRDIlv3HCraN+qBpibDsy8n7TSeMHdjmltGAVEEaeKtaajrEUxqBcxpmfwRuMYZpEU8LvgxlBeBMBJrSTgmPMe1INlb5U9lZoq1ZPN8hJdV8C+EYdh51vW2TJLm8tkpOY53UNGRq01k61H5blqVfcb9H47RqyhdRKBBjf8KDB3yY6toha9ZX1vBb5Wo36Y1HtqF2zatEk2Sy+2xncy50hV5F60//zP/8THP/5xfOMb38DHPvaxXnTZVR9/8zd/g8985jPSxy/+4i82FJ1jvTJ1M8IWRuBCikAIgC+kuxWONYxAlxGo5+1bKpWk/oyZ3Eb1ZxSkIvjlsbRp4GKmn9RkRTumumYv/BVV2IJQnmtDzIUpM9K08WCmeLWaEgbjpgMpoAQRB+d06HV8SCPFaWw4cZfQijXXguEErynl/JSlUKm8RIvmvWfWnJsf7Sh885mjCjGpz6bJet+BCk3b1mPQnSXRq0axtW0Lc3PzSCTiSCSSVYe58GpnTXrIghTn5gBYnezVP0MywpXfiUK2V/9puTruyGzHOWMMJSOJESwggbJYIGXcuPwQKO4zT+NKsznFvBfPjKcYrQtIJIgnECSwXGqa5zFseaCUNbekRbP+WQFgAiyC3AfKe3HCHoHpljDszoOCWrIxBl1AcURzkNZLWKfnMKsNIo4SYrSAcjVkEUNEszGuZbDXPNdU/Kue5RFFzTza8/IsvIB9LbJIcW+cpSdrhRsAw0ODUh/cbasdJ58px4hKnXKvG0W/ZkevQja9U1gFbHy+jx07LgBDvWMu3TyKmy7finQ82ushSH8rUT/MTVPSevmdcckll/RlHqvZKe36uOHUyps96Bj/5V/+Bb/5m7+Jb3/721Jru9rtC1/4QqBaZLLCduzYsdrDDa8fRqCtCIQAuK1whQeHEbgwI9DM25eg8Ec/+pHs0tO+yN8IXuh1SBow6wYJkHlcv1s/aMcEb6zZYt1sO1ZNpBqzJvnaa6+VGtvValxssfaabeOW7Xj4eBb5oj/z6Y1MtwuYOH4HTMvLsAnIM5OymK8nMNVsPgRRBFu0LOoEAHORXchnQSBN0EwRMSUW4xC4uwg0Jj6jBD31ALAaP+nJpEbLvF13sf61mhJeO9cKLXoRRHFsfh/dE7NF/DTyBiwYg5h34yi5pmwOUPhqSMuLAvR15mtt0YG7fX4I9mOGBrtYTRlvpqa8kMstejgP4Kg7jqetrZhzEtikz1bUrUkLJxW37Lg45m5ABGWk3Bw2YRJJw4Vh6PIOcF0Nk24auuZiqz7TtPZ5mZryYoZaiYg1igXBJ7PZplW/1pvPAsfDzZRmatFBY72c+pyqfH6C9tHucX7rJNLUjx8/ISUWfg0CqkXfuHcTrtnpqUX3s/WjfpjsIpbXbNu2TRwCLrbWa4/jv/u7v5Pa3zvvvBPvete7LrZwhfMJI7CmIhAC4DV1O8LBhBHofQRaefvy7/zCpZiV36qCixcCRoIuZiZIeV4pFWTaDpHme+ONN1YtCDuNTjuU59pr0JuRceD8Wcu20o2bEMyIc5ed7Zpr3oifvDqFE5Pzy4fiOhg/dQ/i+XPL/uZZChEg5j0hpzaaiFcVCpibzyCVSiIWi7c8m6rRhVweDlwkk9VZY6Ev6zGYDUSvajtXAJhq462eQVs34cCEXs4Grk0n0GdGsVCq3lCYnstIjep0bDOOO6MouJ5364iexQ59Chv12b6BX9KOZ50EHOiIahZGtazQrAmKSmWnSjFa6mgl81v/vhYLBZRLRSTTA3jS3omD9gQSKDWkMB+x1yHvmNiAaWxzTlXVTIvImm7grL4eW8x57DPP1BXOIpXeonqz72Z62d/GAmbL7rvhPWe1QlkeAF5UD1+sae5U7bsdv9+WD30HBxQS4zg/eCUOnc9LHf/ISLUIH7scHUjg1j7QohsNt1f1w6yPpXYBs4NU1r/YGundZMRwc7QXjb66VH8msO4VrboX4wr7CCNwMUYgBMAX410N5xRGYDEC9SjP9VSemQGmKAppvmy0IKLAEjN4XLyQvlalLtznCNNbmNenJyK9MTttBE6s3z1+/LhkIJnhbpfGfPbsWTz11FNy7kpkv/1zZVaG4JsejMrOJrXlMhx49UzdkIycP4CBuYNNw+VopkfvLOeqvG1bxZhZqtzCAmLxBGLxxgBYLZ45doLKRDK1jC7fKFvZaAztAGCCQI6BVF9NMwIBLpUBJGgT+rPQojWcn88hadjiUczf29BECKqfyTiKTh22xzDnxJFDDLarCeWYdbcTZhZbtcnKBgYVo1lDbdqFmjro6kgqCvTI8BAO2LvxQmkcg00Unw/b65F1YthtnJMstxLU4n3gD2MxpQ0j4eZxiXEGE5G83GOlLl2Pot3uPffPgLXecEjl9xSqawGwOrZtte8aKjkp2Kz5DSLI1erz0s7fGdPjxSRyEzcgNbZt2anxiCE163s3juJtpEUn+kOLbjTmTuuHybZhnSzB78VIkWXpEHUhrr766nZud8Njv/jFL+LLX/4yaIfUqz57MrCwkzACF2EEQgB8Ed7UcEphBNr19qVXI9V1CYAVYCTgIujrBoB2eieYsWXNcTdZV2YfuJBol/JcO2b6PLL+lvRv1kmvVCPoJfglkCSFkIvknx48gvP6emjMwtW01PwhrDv3SODhsaaUtY4ET0EaxV4Yy8F0CnEqMftqZ9X5pNBmMgsy1mQ8img8uWzjpBNlXfY7OzuHeJw06lSL4XoA2LMA0sCsIwGNvgieak+u9X1V3rM5xLEweRKGYfa0Dr3Z4AuuiResTTjnDEiNMQWnDNii0mzBwLixgA2Yw17jrGxeqFpV3bEWFaNzdbP7/hrg55xtOGhvEv9nZpXrtYPWBqkH3qufxUZjOdPAdR2csVKIO3nssI5iFN4x3FyLREwk4jER1aIPM1uzut8gz553jKcWzprxzMxUVQa4tg9mdS2ntdq3P/vLp8URIa5gn4fg4259JD8v/GzFEknYY5dXWSfxPsciZoWdwE0aoUXv2iA13qvRgtYPc9OU72DSn/kOu5haPyye/uiP/kgUoFl2RFeGsIURCCPQvwiEALh/sQ17DiOwKhGQ7Fe5LLYn/FEZX1V7WW9Q+/fvl19zwcJaWdahEfyScroardusK2nLzCBzYcnFFxcTzebfbI4Eoo899hj27duH7du39z0cvH9Unqb1Eu+Hqrs+8NQz+Nb9z2HT1m0CyvwtWpjEhpN3d5S5sgQgthbKUkrMnn1RAjHTQMmnQsxnjs8Oxz+YTsKMUrSpum7R0mOSxWuXgs3nmFT8TgBwLXgiWFStnkgT/2YZScTcPObn5sT6qJdCbM0eoJetcbxmr0cOUYxrczC1JQJxSYvhtJXGRn1OhLfW6VnUZlWXRKSq64P9AHgKQ3i8vAOnnWFsNWeX1WDn3AiO2esQRRk79UmsN5aDZGalT7kjUkO8zzyFmJ1DuWyJ5RLtqaxFcS7W6eqRuIDiqMFnofs6Vt6Pyfk84rqNgVS1IJo/tvy8N6NFe1TyJSXwbjLU3b4UFABmjTvLC8Q6afASzA1fLs9eJr/cm5m06LdfsQ3bxnqrFt3JXBrVD6u++H3CDPBK+Q93Mod2z+H7kN+bvbR4+p3f+R1RgOb332rqTbQbi/D4MAIXYgRCAHwh3rVwzGEEGkSA4INiRQQM/G8CqFbAj8fRq5GLZDZ6AZOy1uq8ft4Eqk4fOHCg7ayrn/JMqx7SyNqlPNfOi8CLAlS0iKJVVD8bQSQFt86dOye1ZazJ5gKYAkX/89v34uDR09ixYztM06tFZTOsHCZO3AGjgWBQkPGKUJaRFF/dRkJZ9ayISHVlBnUuswDWjPOZGRwYgBkxhSbrbxQ1YtrSD0CDjI3HKABMGjvj0rxVZ4D9xxI8ednInAC1ev60nv2NLZsJpNrGoiZSqYEqcayg427nuKJr4KnyNpxwRjChz1XZKjHTR3GuGScpmdldxhT2xucaeif7FaM5Bj8AhmbgcWunqEDPugmM6AWk9CKoA5Z14yL2RZ/fiGZBgyZAPOazeBL6s5sWLEsRrMvMJTo+a6k5zlKpLGCYALMMExHXA3CkSRMM8/lVdOl2YqSO5WfSiMaQGFwPw8o2hdX1aNEcJ+fhLD6knop48NrkTsbc7Bx+trh5RBaOX2HdiCYwNXgZ5gYukbrrem3vplHcvAq06Ebz8dcPE8hRsb7yrjIMKbUhICZ1mJ/l1fye6eY+8nuW9oGbN29uah/YzjU+8YlPiAI02UuttA7a6Tc8NoxAGIHlEQgBcPhUhBG4yCLA3XgCwSDglwuv559/HsyYsq221626FTMzMyKeQruloLVjvaI81z4OpCZSlISZ5H4qmXKhSLpgLpfDxo0bBfwrq6kHnj+GOx55BjMzs9i+fRsikcUaQNeWzG+sMNmTp9ilCrJBb90c9BoEu2RFlJCFOhupsPQGdmxL6JjJFD2CI8vo0QI2zNb0UiYOSfVlhphWPKr1CgBX+tNMROIJ2PmFKuEozt/VI+ItzKZqTWk95WUTvY2lfrQpJ4Vnrc0CSjfpc5VLCIODmwBkdrj6YuZ2HjdGDrf0qCWwI5AvZ+dQKBTFZ5a2WUXXxFPWNpx30ph3EyLuxQRtQitiAHls1mfF+mjSHRCwS8GsmFaG4+pYWLRBGqMNknEOA7pnFcRsOsFm2WdLRMqyVsyIlgA3d/ivaoouTTBMUKzo0kFiSwDMc1ib3cgnubYfZnxJ2+dmEmnEapy0XaJ91krX/frHVw8A+9kJtDybGbkaufT2inWS/3zO54a9m/DGVaRF17tv3MgjE4fvcN5vsmn4nlOfIW5SKjDMf9uxVwvynPTzmH4oXH/4wx8WRwb23U+bwX7GJew7jMCFEoEQAF8odyocZxiBgBHgQpMAuNXOOhf3BFz8suVChIuwtWK9wLHRPoPZ6CC1UL2kPNeGmcCaap/MivfLy5KiX/Qa5sKQoJ/1cur+HTk7g/965CCmpqYwPT2Dbdu2Iir0YmD03CNIzx8K+GQEP6yeUFatFREBMet9CU75/DCbk4xFpFaxFiJSqCliVdNy/aMhIDtlD0ntK0EeAV9KL0kmdFzPCEAh+O82A6yuqTKAZS0CV9fFQ5ittj65VmyJAI8CWL3wnq29G5NOCs9ZmzHnJoTmvDRWL/sr43M1nHBHsd2Yw43GqwFFzDRkSi5KuQxGBlmT7WUSSWM+4wzhpDMMKk4z5iN6Hjujcxhy5uTvR511mHaSyLoxlKisTUVvFMUjeJs+hSF9qV52mZWQkVisL/c/DS4sy66AYT5TlXkaumSGvQwxKf6N6dIeAI5UsQG8rLfWtIaXjAWOs1C2FhkK3PCJrUrdbzUAZvkAs36JymfbH09Fzy5HhzEzejXyqc11P8wj6ThuuZK06KHgH/Y+HkkxRb7XrrrqqoqWRND6YWaK1zIIZMaepTFkBfWKGfS+972vInq4kqKTfXwEwq7DCKzZCIQAeM3emnBgYQQ6iwAXGP6FZW0v/hpT/o2Ai+CKu/Xvfve7WwLnzkbV3llcXJBeRvBLENyo9YPyXHstbhCQIs4sBmPVy8bxc4FIr2HWW1P0iws/1eZzRXzr/mfF73dmZhpTU9PYunWL1AmmZw9idPJAL4ezrC/SgV36sdp5eaYICDlO0zSEpsdkKKl6rM31BIUciHewrlUybK1ErzJODC9YE0LvJf227BL8uEjoZQxreUzo86I0PD87jViMQDvdYs6NKdA8sV6mkvZQtmYiajEjvNT8frP+3/uzib26AVk3iqfLW3DSGcEWfRqG5kpG1S82lnFjyCCNnfpZXG2eCHzpQiGPfKGE5OgEom6pZbbTow3zHtpgTfC0kwbluJiZH9AKGNFyMj7VIhynlF14v+EGilT8ust9qv2DJoPAqx32MsT+7HozunQ9AKz6VVlvYzGL77+eJ3jmyjPK+eWx6BsdOJL9OZBzV7RXbib5LaQI0HW7LJtAqtE6aXb0ahTj9RXy92wcwc1XbMVAwtsoW61GMUOKKtYrReF9eObkPPa/PIkj5+eldGdQL2HvQBnb05B7xJphUqWZHV5r9cN8N1AcsVcCX3z2b7vtNnFgOHbs2Iq6LqzW8xFeN4zAakYgBMCrGf3w2mEE+hCBZgCYiwyqK7PGlsCFgIsLC/6Oi5Xbb7+9q9q8Xk0nCOj0U565QOIiqx8UOlLKqZLNrOzll1/eqynKglepVK9fv15Ex7j4VY1UzX9/4HmcmvZA2ezsDCYnp7Bly2YMufMYP3lP22JSnQ7eMuJwmfGdPifPh2IYsD6ZmTguVknR9bODhTKMKFyr2HCclqvjqfJWnHQGBfiO6DkkUCYhVbKSpIsp8/UAACAASURBVAOP6QvYoU9iNPOKxKe1IFVzAFy37tdIQHcKUh+sO2X5YWsEgPk3ZhMJVEqWZwvUi/actQmv2eskXuNGDi5Bz2LfjNVJrMcYZnGpcaauOnOjMRAAexToQWhGxMt6BvCDDgL0lwuJdZ5V5XPVii5NYEwabW0GuHruVIzmvSxVas5rx8m/JzWPLUMwtprND4D5DuOzxaw/KfmkaCv7p9ox5lJbMDN6DazociEsPpvX79mIa3dPrJpaNJktFPOjlgGBrGqFso2/f/A4nj01j8lsGdmit1GSjpkYjuvYO2LgnZtdZOdnK5u5fO+spfph0rn5/iYraMuWLV0/PgTALEHi83jw4ME1sRHd9aTCDsIIrOEIhAB4Dd+ccGhhBDqJgFpE1p7LLC+BLgEd/WwJ5hTFjFlI7jq/4x3v6AuIbHceBOr33nuv2A6xFra2cZecYlGcK7PE3IVvRfludwzqeC7I7777bhE7IZWvF43iMBw/+26kUr3/uaN44lWvNpttbm4W589PYvuGYeyYvH/FaZukrk4uFAUcxk1NgKii6dVmKjleil6xrjOuO+JhWq+dtgfxvLUR005KFIVr/XWZfZy009hmTGF39mmkokZXANifWVPjcXRTQKa+mK306qAplFVEZm5a5sAa4EatXe/ZZs/PrJPAS9YEzrpDkqpOIwcTjtToziOFQS0ntPArjJNVCtGtnkk/AFb3LGjtrAL6RZ9isv96y6jPZgqmVd9eqdU4q//emC7N4zgPUoab0aXpk2xL7XkRcRNQc5B7zj4cS8CmKTXBvdvIaG+eQLlcQjabE0p3OhmvjLMVe0Kuo+lYGNiJ2ZGrIH7JNW2YtOgrtmH7+MrTovmdQksf+rkPDS1d//994BgePDyNk7MFrEtFMZTw7sdc3sJUtoQtIwncsmcUv/amzWIPRbC51uqHuYnM71O6A1CzodtGAMzvF4o2MrPcr++zbscZnh9G4GKJQAiAL5Y7Gc4jjMBiBGoBMGs0Dx06JD/cRVe2Ov6Accf5yJEjePvb374m1Cc5B4qBEKgzM6qa2h3nwqpXKs+tHhzG76677pJFDrPM3TT2xYwIbY6YwWJ/zP7WtkOnZ/C9xw5WZRbn5+dw/uwZ3KC/hLRbTdXtZkxBziVQ50KUizSNvrjrJmBYBehwUGsnw/6YESXwUB7DAhIBlGu8g58pb8Kr9hiSKGPQV0/qH9NJewjDeg7bsi9hczTbEgBzrNmsZ8fk1ZRGBCRRpItZQH+2r5n3K6nfk5k8Ym4JgwPNadecG+NAYSWlLBwkrvWOoRjWUYxj1opK7S2z4SZcpPUiRrQFXGqcRUxrTi2u7bceAK58pqR2VodhN1dBrncP/T667M9TU2ZdcO8zqsreTdSlS16GXjXeX48yXV9d2oxEUHCjopjO7LqA4sW670ofQvtGlYhXp/ew3fO44Ufxu+HBgYracyDw67uQ3zrJNZaYJOqQ3RtH8PYVpkXzPXf48GHxl1ebSMdn8vh/7jqEl84uYNe6JJLRanXrbMnGa1M5XLYhjf/7Z/dg49CSFV+z+mEymsgE4s9K1A9zE5MCkldeeWVPLIv4fFNngoCamhNhCyMQRqC/EQgBcH/jG/YeRmDFI+AHwKQSc5eaqsqkPxJw1bOR4S49f2666aam2a6VmgwXA3feeSc2bNgg9Dk2LhBJOSP9cSV9iuuNpZM40Irm6aeflnvBBRrp5/V8lln3+7/ue1bEpPyN804cvgtbo5kqqnQnYwl+jivUWcZeNSVExQxvJJaEnc8s0ytqJHpFwCRKvIuc4SfK23DIWof1ehbxBqCO4lAmbGzPv4TtkTmk042zsWQ3kFrOxgwhNxxUi0cjAi78IKkVyBDFWj2C1PC6hpZD/li2ypYGiTtjlC1DFJpn3SRs6DB1HeOYxoiWXZYlD9JnMwCszvdqZx3JljZqFaBvE5Z7MJdUfTZmVWktRTVlWjotuHEBm2mtWGXpFGS8zY9xMTs7V7FTqqcurcCwbHwYRoVSzKx3mR7P5SWRMf+1OKcIn1HxUO89iG80LwLgfD4nmzv0+OY4eS86UaZ2jJj4B9NHuNY6ibXa1+/diDfumpD68n43gl+CYFJ71ffOfzx1Gt/+6WkUSg62jXpq8rXttek80lEDv3zdRvyPN0w0HKbff5jvVf4/m9ix9bl+WNU3c4O23iZmu7Hlu4qewrfccgvuuOOOdk8Pjw8jEEagzQiEALjNgIWHhxFY6xHgFykXhdyhpgUF/3v79u3iVdhIWZLZX2aB14oNEmPMrCvpYKTPkfLMuTAD0G/Kc737y2w0a9iuv/76jm4/6ecEv1zoNrsXrPv73qMv47Vzs8vqSo2TB5A4+bAskv21wh0NKMBJBP4Ekxwzn5t0OoX5eQ98y0J90Uu1rJlwNVOEstjoJ2zajRWfBSQaOoqWhZ+Wt+CQNYYBjcrC9YEXadJprYBtuZewJZKpu0HDsRJAEKxTgItCWRyzyhoSSOSLpUpMuUAuRocxqY3gjL5eapUjsER9eacxiYTmZRgJgHkss1esm6WPcBC/WFKtiQ5rM96tws6xE4T5s9RKAbjVuc3+HgQAe+cv1s7aZehudZbV3z+p6rGIifziJo3KpOctBy9bEzjtDInVEltcK2OTMYtLjLNSc9tt4z1lbTafwyWv1MZ0aY6TQJBgWI+lpZ7WMSIi7tbIO1v5W5ftlaFFl0pFlAoFxJMpGKYJx2htGdYqjnxu5kauxMLArmXWSStFi+amKtk6b3nLWyrWaaQ///CFc1LOQPpzvTa5UAKfpZ+/chyfeMvWVlOVvyv/YQJh0qX5rxKD7Ef98PHjx/HKK68sq28ONNg6B/F7mt93H/jAB8QLOGxhBMII9DcCIQDub3zD3sMIrHgE+EVKsMiFB7NdrCsaHx9vOg4eyzpgArxe7Gb3YtL33HOPAC2CD0V57tVue7vj41g4jhtvvLGtU7ko4+YCac9chPFecJe/Ubv/uaN48tXTiEdNyfTlFsVh4tlTGDp6J7ILC5JJ6YfYl39MXDhSiZv/8hki+CU44qJSAWCCWD/Ao5Kyw3pKqxBInIvnv2YN47niODKL1j+koPpbyTXEHmmbMS01wOkIlgFgbvhwrNwcIcjhM0PQyt/zX6X8y3vBY0ihPWUN4ElnNxa0FLJIwCYY0qhwXMKQUcAN5mvYZMxVAWA1Ls4TYKbU8wpu1DgXZnPboUUvoxTXtRJq6xGUg4MDYK9vr3aWddCFullINU4CfSbzC1ocCyUXj5R3YdL1vIWZDZamAYNaHmPaAt4aPSQZ4W5afQBc3aPa+AA3Pgrc+HAlk06Oc8xgza9Hl0aUzzUa1tP3sr672Zwdq4z5hUV6f3yoRzXU3hU966Q3IJ9aLtS0a2IYb79iGwaT/VGL5nuPQlhkFql31j89chzff/YcTEPDhoH61z0zXxSWCLO/H72xvuVTq2eIn/9m9cNKXbpT/2FF7+Z3JrPN3TYqm1No8WMf+xi+8Y1vdNtdeH4YgTACLSIQAuDwEQkjcJFFgLvurE3iFzwpz/VotrVTJp2LVGnSjUk7XguNIlii0Grbbc2lH2OnCnQikcCb3/zmwN1zI4IxPX/+vIAyUp6bKRi/enpasr+qEQumE1GUFqYxdvQHsApZAXr9BsAU5KEnKUED55xIEPB5hFf6EBMArxsZqgj1qPE6BBi6IRY4FJRSSsrNAkaq7NP2dhy3BmG4Dkb1HEzNoy1TAGvKSWNIy2G7OY2JzItS0+sXpCKgZUy42OUCmxlBAk9iL/6OGVXW/lZbCSXw4/KlOG1ReXpRaMopoQwDC0jKTMf1WdxsHESqNLmYAa5d4KpMaakintVonsyUcwxUi27WasWk/JTiwA9dgwPbBcCqG2ZJaxWj/RsKPI408jhKuDO3F8fsEVgwMKotIKZ52dMiTEy5acmqb9en8PbIywH9i+tPJggA5pm892ykMvMdUmBGupgR26XKZ0wEsEwgNoCoqSGC5ffIT/vuBy2a42R5RC6XR2JobLG+u/f060bWSf2kRb/44ougR/vNN9/sbTgAeOjwDP7xoWM4MVvApeOpZWJPBL4Hz2aFHv1/3bQNN+5YsoXr5nPQ6/rhevTubsZHKzzW/37605/GV7/61W66Cs8NIxBGIEAEQgAcIEjhIWEELqQI8Iueu9O0ZgiqJEm69FNPPSWCUxSeWu1GyjPrfdlIeeZPI/r2Sox1//79ksFlJiNII0WT42cNdq3idr3z57IFqfutVdrVnDI2nbwLCTuDbL4gGQ3Pd3dJGCbIeIIdQxpxQcbM54ZZ30ikmqJIamEiHkUiWS0K5YleJSo0aC7fbSMJ3faEspo1ij4dtDZgmplDOyoAmBWmrB8d1nIYNxZwpXkKC7NT4j88MOCBUX+9b/WmgGeDRLDCTHptTJ9wduG54gbkEMMGba4CxuhJ69gOzruD0F0be9xjuM55Qa4l3qyLYlr+zxQp0WKdZOehu83nydpSyULXCIGx/1pQ6VGKY03rcYPdU+8o3lPGq1MvVb9itF/xW9WqMqP+qLUL55xBbNKml9Up266GE86IUKHfFnlVrK06bQoA0xM6kViueqz69WfTa2nkiglAMKxosvLMmikkDAe08CIw9t/rXtR315szx8nsb7ZQkvsT6XNpLq2T6CFcjlarQg+nYpIN3rGhN4CTc+VGLL9bWNfK96d8bi0HX/jvl/HcqQxMXcOm4bj8y2Y5rihDEwRftWkAX3jvJVIu0Y/Wbf0w6c+kQfvp3d2MkyVIFAv7/d//fXz5y1/upqvw3DACYQQCRCAEwAGCFB4SRuBCigAXiKzbbKfR0uHAgQNijUQa1mo1Zu1eeukloTxz8UngQWum1W4PPPCAgBdmMpo1HkPKHzMf/G/Gs9VGBOt+//f+53F2tsY6xnWx/sx+JLMnvEs6Nmbn5hBP9B4AE/wx68usNReqnhhPtTorhzA7Mw3DjGFgwKOOqtaoTpUAkR7CpqjvNm7zThzH7RHMaUmUHENSuMwYjukZbDVmENEcoV9zTMwA++t9KYql7Ly8K3gAmMJRtTW4RSONu3K7cNRej3V6pq7wVtnVcdoZxmZM4m3lRxB3qym7BOFKXVrFiDXQXqY023Seihbt9w7mc84ssR8Y96Lu1x/tbgGw6suMJ1Esu6IY7UKDY0QFpFPM7Dl7M3TXxTrTqwWvzZZyo0PXXFxjHsdV5smOP9J8Vufm5iXjT4ZCveYHv3JfhK5eP6vqUePLkhnmv5YDlPU4Ik5BKOy16tIKkLVb311vnCrrzwz9fAlSE1vvc9dxsBqd2MQ6ibTomy/fhqFU97RoWr2RAcN3uH8zgeD37x88hmMzeWQKVkUJOleyMRiPYOtIHJ+6eTv2TTRXX+9VXDqpH+b3FJlTb3vb23qiycANaG4UfOELX8DnP//5Xk0t7CeMQBiBBhEIAXD4aIQRuMgi0AkAZv3RI488IkJZO3fuXJWI+FWeSd9mZoa79GsBAD/00EMCDrlAadQ4XmY8uCjiwpyUZ7/3ZaPzfvzMa/jp4TPL/jw0/QyGpp9dApmWJTWpQwNpmJFoz4xmbJsWRx6NmBsOzKbWMgfoT3vGGcCZLODqBgbiEQGnE/o8DJMgKN8U+Nk6hbIilQxxo1iQpkrxJAKDqJUTwKSaB4CZDdKq6n2XMwNcrwbYdqpiRJXjUtnGD4tX4rgzim36VEMq7kl7WOb35vIBjLjzsiGg1IYJkirlrYsUWmaHIxETLoWy9IjY7TRrpEQT9BII19pItVKm7uTD2QsA7PdQZtbb1kxErYwM59HyTjxvbUJSK0mNL4E+M6bM5KlYzTlxlDUDbzBO4trIsU6mIee0AsCMrXddV4SuuAkThI6vBuQ4toDhku0gb+li5cXnUOrJF2uHI6aJeCzSVn137YSViBzHOl/W4GSp1D8AXV++8dRxsFqcSOukzOBezA5fAb91ErP81+2ewHV7NnalFq1U72+99dZlIzl4dgH/5+mzODqdw0LJo55T+XnHuiQ+cM0E9oxVb7L1Kwb1+vXXD/O9Q0YPnyc2viP5/UTaOn/vz253M0ZaH733ve/FV77yFXzuc5/rpqvw3DACYQQCRCAEwAGCFB4SRuBCikAnAJjU2gcffBB79uyRn5VufpVneiFyDE888YQsMN75zneu9HCWXY+bAwQRjcA41ZK5g8961LGxMRG7CqLU/MrJKXz/8VeWXS+xcBzrz/ykSkyKtE2xQkokkE4lpcaRAkvdND+N2KNWM+tTnas9Yo3ihD2CBS2J83kX0AwkIxoGtAKGjBL2macxpDX3kFVjZDaOqNRwWgshESD458iFqFqELtX71s8rEycT7Cog79XTAkUH+EHxKhxz1mGrTqru8qwg17mk607oc3hL+QCG3MwykZt6FFrOkQCd2WEtxk0KAn564jZupGjzHpIFwOboMWhuWSxwetm6BcC16tQeSM+L769ul/F0aQLP2psBV8OovsRkUECYqta0dYpqtmSArzBPdTw9ghN+DhplgAnU1efC8yUO9mw2GlDJ0VC0qfQ1D9tHX+fGSzQaQSIWBevfg5abqOuocdp6DMXcnKhAd0pR7ziYiyd61kn7MD94aZV10kg6hrft24adE53RovlO5HcL/eXrNX6eaXl0YqYgmyZbhuPYPppoO5bdzr/V+UHqhwmK+VPNRmnV89LfaX30y7/8y/jbv/1b/MZv/EbwE8MjwwiEEegoAiEA7ihs4UlhBNZ2BJQfYtBRMvvKOldmf5kFXqnGxSxrn44ePSo1lqxBJoBke/LJJ4U+9+53v3ulhtPwOo8//nhDME7wTqofM8B79+4FAXyQxXCjul+zNIeJE3cuy1oxU0vqJ0WpVO1jra9u0EDV2gYxw0ngVttoQfSyNY6z7hASKCJSmIGpuyIaNOskRTl5sz6DayIn2vJ6tQyqBFswnNa2OOKLm89LlpqtlQgYjy+Wy0LB5X3wU3UJbu8p78MhewxxlDGoLweoOTcKZrx3GFO4qfSw1PY2U3lVisOKRqtAugw2lkbU0BE33WWZPSXSxOM55oLtwoEpNj29bt0CYH+W2tajUiOtPGqpGH1aW4dHC1txyh7EZn22ImSm5kHP6OP2MLbo0yKCNaJ3DkqbAWC/kFivaeTcvBFad2mhQpdWNG9u1kjNcI3PdKP7qMbpaFSmNlDMzndVo92r52XJOmknoOkYSESRyZewc8MQ3n7F9rZp0dzE5LNHmvDF1Pj9So0Hfm9yo7MX/sO0PvrEJz6Bb37zm/joRz+66uHivaP932OPPSY/FOliq3q/rfoowwGEEeg8AiEA7jx24ZlhBNZsBFgD3M4XFY+n6vLWrVtxxRVXrMi8uHggRY5Z3nqK1VxgEFy+613vWlUBLAaDiwF6+XIslUW9D7xzEUTFbfo4Bmms9/zfP3ke52rqfnW7iPGTPxLwa5RzVRRdAmzGigJYS/6nnsVnxDBaqgz7x11rG1RPYIxr/aesbXjNHhVa66BGgSyOSUMsloCtR3DWSsrv90XOSq1uO425V2YSWUNKQFWveUA9j2KhIEI5FMcaGm6cjfKyalQf9kSwONZaIHTIXo/HyztFsGm9nql4/vL6pF+fdwYki3m1eRzb8welr3ZsTniflujSltCwS3pc1H0JhpWYFv1pq5ShY2lE7LyMv9etGwDsB7+kE1Pluxak81nZb12KY8565BwTI1oWSXhAPosYZpwkhvQ89kSncaP+alfTUwCYbIV4fKkG2BMSc4Ryzayq7paWrJi6umL1ycwqE/yzrljRpaV22LJgaJrn4VyhS5uyseT/fFXZckkmPVcRKRsaGoRGULzKjdZJhYlrMROZqJQRcNzX7p7A9W3QorlxyM/CW9/61lWeUe8vz+8EfocR3PPfbv2H//mf/xm//du/je985zt4//vf3/sBt9kjx/Bf//Vfy85qZ13R5iXDw8MIrGgEQgC8ouEOLxZGYGUi0C4A5qKdu71ULGYWtt+NyqDMmnLRqCjPtSCMf+euMynQndLKejUPUvk4ZmajCapY/0WAztrpduym1HjuffoInj5ytnp4roOx0/chkTstvyfApLiSaStRIRuzswTAtPtZXh/HBSpbPZVhdSEu1D3bIFf6YSa5UbaaWdCfljfjnDuELdq0AG0CKbZYagi6U8KCE8W8G8dOcxrXRo53FG5mweitS6oq87WqEehks162jfef/885jgwPLxO34jmsOWVzXIIgDwDbEY+q629UJH7E2o2j9qjYLBmagygssUEquwZG9Bw26bOSqSwseLV/7QBg/7U8gSVPXIlgt+BGRVwpRp0vzaNLExC7sQEBQmyS1XecZSJSHQV38aROATDraSWWi/WPzbKqzJw/XN4lKtpzbhJlx7sfBP6kyI/r83hz5DBShlOpf+5kTgSd8/MZeX4VAPYLiamsajt1v+2PQ9lglaG7SywG3msyNVzbQqHE33sx4LuNNeK836RMU+3YH0sCKL6zqRkQhD3S/njbO8OjrutYiK3H3OjVKMY9Vg7bUDKGm6/Yil0TIy07ffTRR+X5acc+rmWna+SARuA+SP0wvYdr/Ye/9rWv4Q//8A/le3gtlP38xV/8BVjaQ2Vq/uzYsUMy3SEAXiMPYDiMriMQAuCuQxh2EEZg7UWgXQDML7U777xTPIDpBdyv1ozyXHvNF154QdSgWXfLer/VbPTzpbjV7bffLjv9/H/GmIuCSy65pK0M9csnp/Dfdep+hyefwuCsZ7vjb1RR1lwXmpUXwM1YkAbcqBFAlW3Pg3WpuSgUipKp4OKW50ejzWN61h7A884WzFsRbDA8sSMCKQpapWIRWdpbro6TzjC2G9N4S/RIV7eImUVRFS7nBETU+vvOz88JoCBIiJkGSlVzdEGbIa/20xNeKmsRoVj7QbUaIEHws/YWAcFZNw4Lutg1pVDCZmNG6lRZr8r6RYouDQ5WW8Z0OlE+/ywnXSgBWnFeuuE4o7ARiSh1aRMGqbSm5x1cfR87u3InAJjPCTccluppvWxls0Zf5yP2mIiM5eBZdaWQF8GxncakqHmrxueU2VJV/xx0ZksAOF6xA6umPrceZ9BrtTqO9G/afxl2oUIJV+eIsne5jHyxJBsg3JAhi4HgF5E4YqYudkuGYcrnci0BYBVPJciWS23G7Og1VdZJpEXffMU2DKcaW7I9/PDDomp94403tgrlBfd36kJws+JNb3pT07E3qx+mnsO//uu/Sh9HjhwBQScFF2mttNYamUchAF5rdyUcTzcRCAFwN9ELzw0jsEYj0C4A5jS48zw8PCy7vf1orSjPtddkbTAXBRRQ8VN++zG2Vn0+99xzYm/EbPXhw4clI0mhK24YBGmzuTIePzqDZ0/M4qnDZxHVXWxKGxiJe+I5ycxrWH/2wYZdCV3YiGP2/FkkY2ZTAMxOmA01DaoMe9lQ7uTzmWAmamCAFkdmy2HPYABPFTdi2klhszErx9OrlAJNyYS36KVqMynDO40p3BA92rLPIAfkLCCzkEXEKSKVSiIW865F+jfB7dCQR4H25uiBRD8A4jE2DBBnGK7V9JKkPJ92hoT6bGo2NupzSGpLGb1eA2CCSmZVmaVnLS3vj1POwykVKn60HDBBA58xZgspptUsqx8kpp0AYH9MHT0qNdtBxbmkVBbeM2YaEWiaU9fTeMkWyntOgzSyVXhfuCDnj1+dutd1v0HGw2OoNk0WA9W//Rsu3CQSCjk3ZlwHhWIJ5bItYmyKRs7PP+NAgEyLrxWxQWoyMW4kWZaN8iI9u3IoM8LpHZgdfQNs0/NfVrTo6/ZMSBlGbaOwIjfsrr/++qChvGCO63Rufv9hugZ88pOf9N5nui5q8x/5yEfwq7/6qwKKgwgprlTAQgC8UpEOr7NSEQgB8EpFOrxOGIEVjADrrphtaqf9+Mc/FoXhftDVglCea8f6yiuv4NChQ7jppptkYbiaTdGxOQaOhRZHzbKw/rE+fHga/+fp0zifKeLw2XmxV4noECXldQkD1w4tYOupuxrWwaq+CBCmZmahJ4YwnDAlY9mqUeWYwlmsKyXVNp2mxVHrGkMuyAkiHy1uxTF7FON6BjHdQq5Qgu7YFf9VKvuacLDXPIe95vlWw2n6d1XvS3o5BaLiQ2Og56oCCsqKhJs0/kagxkyiAolCOzbi0KWGupn7cOvhLixkRPk3iJ1V694WwVB5qcaXdkIiT+240O3CYu2w50nrB4SJeFREtIyaetIg1+Qx7QJgxp00bGJSl0JiegS6eOl22upThlVvBFLc0KiqiW5wKT8ATiaoGAwvk2zERJGaleKr1Rw9Av7UZsqZDSZQL5RsWJEUzHJ2kRpvyT3nnFTz06VFWKvLZ7idWPBach9gQm+w4aGsk+aGLwfVo9kGk1HJBu+uoUXT2ofvyWuvvbadYVwQx1I0koC1m7nxO5psIrKvvv/978t/q88940aLJbKOPvzhD2Pjxo2rGpcQAK9q+MOL9yECIQDuQ1DDLsMIrHYEOgHA/EJn9oGAs1etHcpz7TWZaX355ZeFDtYrANLJvAi8qILJRSozvqyRDpqleer4LL75yHG8fG4B+UIZcG3oGlCyXeQtF4MRF3usQ/gZ8zn5fbPGhRHp18wKJAeGROiHC+1G62NmfJn55XlDAykYkeA0ctJTCUZetcZw2FqHGTeJ9WYRWm5KhhiLJzDnJpBx46L6e3XkJAb01tZGjeZXW+/LhSWBgF8oKzM7LXPxA2C/zQ6BMMdcNpIwylnZAOoWPPQSAKuYqhgoIOT9/yJAdLjB4GWta8W0JHtMb11m3hZrh4MCpHYAsD9LzXH00pfYowzHJRusVKT9z4RHi6YtVONssB8ADw6kxO6KIN0Ryns3IL2Tt0P9c0jlZ1bYWKw/V9l0PZaS35V9dkrsQdW687Ot6NKqZ9P0UeMDMDe6mQWz1cWyLcCW96hZq2edke2TDgAAIABJREFUtGN8CG+/cokWff/998u7mxuGF1u77777KvoPvZjbb/3Wb4kC9A9/+EOwvvjuu+8GKeT8Lue//diYbmfcIQBuJ1rhsRdCBEIAfCHcpXCMYQTajEAnAJiULi4uG3k2tjkEqWtTKs8ELVwE8Us0aKM10osvvij1YxQMWelGsHX8+HEZAxv/n4qfBGdBGhfxf3Hny3j86KzQH8tFel0uoVwugucXspjQZvD26CvYukgzbtb39PS0ZHJVRpw0Wi60lVCWd66nnJzPe9dj1jcSiYrwELM7tYvv2uuxvra4qERcdnU8b23EGYxgzorCLpckM4RoCjFYWKcvYJc5hS0Bxt5oXqyRq633rQWuBDhTmSJ0K4/R4aV63FpQ6UaSiLpFlMpWjwDwgnwmut2AYeyZTa2ISRmJRY/gaqDXqKbUL6bl2raMibXEbEsAKdJwY6YdALwS9bSNKMOcD+89a2Mb1T8rADyYTkI3oxKDXoL0IJ/toMeQFh0xNbhFr3aeyX4dlgidsbZaPQ8KAKvNHW7eLCmJlyt14IwNNz2UoFY99fagY6s9zou5s0w1vVV/pEPPjlyFhQHPOonZ/Dfu2oDr927EA/v3izL+SggrthpnL//OzyMZU+Pj47jyyit70vWv/dqviQI0P6vqe5LvRW5M031gtYUgQwDck9scdrKGIhAC4DV0M8KhhBHoVQQ8NdL27FSo2MmM4W233db1MM6dOyd0Lo6D3sL0x213scaaW9beXnfddRVv4K4HFrADjpv1WadPnxa6L72JKchFO4+gisAvncngq/cfwYun55HQPDscf4sWJpEtlEWA6XLzDG6JtbaHqQXAqj8RyqJJULkg95CLZ2apCdZrs9XNvINZN2zbbtVYC3oCJ0spkO48U2B2DhhMRCTjy+zvuOH583bSSqUiFhaycqq/3rdeXxTBKrsahtZNiFCWyviqY6mazawifXtJ4SXtWwGMTsbGc7gA7QUA9uyZPGouM5XiTtykPtkDiDHJFtYT8ZLstmsjXyihbFUDJAWO+K+iuwcFwP4NBduISjY6aN1vJzH2KMPmMqVu9kUgxf0iFbfKfaZAWmYBiWRChNzWKvjleFU2vYAYuLkRsb1nnY00f6lhL9uLz5lVqW+vjaWnJN4/urRHfQZKGrPz9MYOVo/tH2c5OoSZ0auRT22RXw8kIogunMbVe7b2DCR28oz14xzeCwJT0pL37dvXk0t86EMfElDNjeOgDKOeXDhgJyEADhio8LALJgIhAL5gblU40DACwSPQCQA+cOCAUGxZc9Rp48KctOXXXntNMpXc+Sd47KQRfDKDzMzxxMREJ110dA5BDy2O+C93+Cl2RTBOUS4Kk9D2KEi7/5VJ/NNDR/Ha+QzSkeozzFIGscJ5UDV30hnAbvM8fj7+XMtueX+Y8RsYGFx2LIVrZnIluFYJqZghVkmNKMBL3sGk23pZaT+dWHVOgEKgRRBExefTCxZKlouxoSQGtGJD+nXLicBFLpcXOykCAQJ10nqbtfn5eQGjjL8WicNytQrFlP7ArFNVFFhmaPijQHJAfaVll+8FAPZnVAkrHIolCcho3RrVlKozJauvawKESZ1l7bB/40uJaamMIjdvGm1ESZaa43NceFZCplhdrUQj2BdbqBrLKl6btFzWYStaNN9thVwWkVgckcRAW+JcKzEX/zWUh7InzpUTsK47xQrNncdyc4S2TmQtBGEaKDaAyhD7tR46pUvzGS3Iq0CDtkjB7zRWtEyaWXcN8tF1IItn75YxfORdb8FIOjj7p9Nrr9R5FLIiY2rLli3iAtCL9p73vEc2XScnJ9veLO7F9Vv1EQLgVhEK/36hRSAEwBfaHQvHG0YgQAQ6AcC1XrcBLlN1CLNMBI6smSWV7+qrr66IJbXbF49nFvnJJ58UALp58+ZOumj7HIJuZp25qOTChjZHBJHM/tKWiQrZpPQFaT95dQp/eedLmFwoitqzarpVQCJ/mpxqUVGedlPYY0zivQEBMEFNbRaaCzJmftkSqTTM1DAMOw+9BfLzewfX0olrQSX7pvougdbISOeU9Eb1vq1i6gfAHDdFr1TmW/ySfRY9HgBmxtXLstXLJLa6Hv/eLQD2i0mxv05VigkQSZ7lPa3XqNwrwMj21JQ9cLRcTMujz3rew7VA2H//VyurKqJgWK4YTeq+soXiZsz07DziySSiiXSX4lxBnoLOjlFqypbU03IjwcuqEujTOol0frIVvOcsA1a8pwaGAqthq1F1S5dW973X9zyb2IRncusQHd6IDeNjeOPuCdywd2NdtejOIrx6ZzFLSxukbdu2Yc+ePV0PhJ/ZW2+9VcAvN4/bZUt1PYAAHYQAOECQwkMuqAiEAPiCul3hYMMIBIsAs0AEwe005XX7zne+s+16I4JVKiVz4d0p5bl2rFwMMCt9+eWXy0Kjn42LyJdeekmALm07CN79dced0LHvee4E/vJHr2IyZ2Ms5ancMruSyJ2qZFmmnJQsfK+KnArko0sfYGb8lC8tF05cjBEAe5nUgcq9czTDE7MRNeTm0UtETRREeXjpuHoLYgJg3uNOa7Lp75vJLMgGA+NMe6ugQlUKAG8YXy+0UdUsIylJbM0uVWyP/ABYHcdsHAEiM5xBmweAG1NTm/XDeYkX7KLgUS8ABgGi0LzrZGaXLIWqvYP5Lsjnc2Jt429+teFUIuZZ9XQB0oPGtPVxjRWjJaNfKmNmbh5mehRpc/UUn5vNQz7rmueTTcCrO0vWWuo8Ur8dnTT3LBYW/aZHhodhLNKiW8ep/hHt0KXVOIs6vZOX6NmdXtt/Hj/jc/MZFIb3wN5+k1gnDSSiuPnyrdizqfMNtF6Mrds++F6gMCK/6/jTbeP7SnklU3Mi6Dux2+u2c34IgNuJVnjshRCBEABfCHcpHGMYgTYj0AkAZoaTAPAd73iHgJMgrZbyzGwtacO9aKT7si75sssuk0xsv5o/c01gR/BbO/9Tp05JTXNQOvbUfA7/3/3P4qETBZyY9wSLhmNAKn8a+qK6ataNisfuRmMOPxM9GKiWlgCYiyNSJRl7LsS44GVmTykn18apvlDW0lGmrgm9VAR2Fn116QEa8WVU1dHMVBGAjI6SBt6exRDrfT1V6tb1vvXudSYzDzg2BoaWKOhUwiallCNxockim5k1gkSVAfb3pTKJVLoN0roBwIr+yuu066PbfGzLFaP9x9NnmMDbbymkaoApnsbnRmWH+d+s/eQ9MUxDxM3ihld/u9qtVhCMgJLjIt07U7CRjmki7rYW2xL1mcAy13SIpLnPZLJC/1YUaH9Gv5v5NaNLk0mSjMfADHWUjPcO6n6bjY3PFjet+C6NJ9PIDF0CZZ20bWwQt1y5/YKlRZPl9MQTT0j2txebs7xPV1xxhXx3ctM3BMDdPPXhuWEEgkUgBMDB4hQeFUbggopAJwCYNa5HjhwRFWhm5lq1XlOea6/HxdNDDz0kAlq7d+9uNZyO/n7+/HkBtq0y1/QxJkWcNc2bNm1qei2KL/3r/ucwOZ/HXNHBU2dKmC44sIp5pN0F8e8tuFHY0DFmZHCpcRbXR461zNLyonNzs3Jt1vcSnHHhxJ15CnW1WjQpurBHx/QagSMzTipLyd8ZsSRcKw9HyQz7Zstr0l5J6nBbpZUr57Vf71svwNkFL/s8vEi/ZoabMxBVal/j7wn69VK2YUxJTWZrpYhdq84b9CGrqvvtk0WPotIatMKqQ3X3Wwo1EsEiSHEdC4ViSTLAfCZN+r+SchwxF+2WlsS0gs6/l8cpxeikXkSpZKFoA9nMPAZScaSTCbHtWUttiVLMut9gWVVu7pS1qIhgqfrwpYy+R2vvRfPTpeEwlqz3jiCCstxrRZHvBQXXb1elVI1pD0UQPD94KQzTxDW7PFo0Y3YhNYoRstzn0ksv7Ul5Du8vN3m5gUzrqLXYwgzwWrwr4Zi6iUAIgLuJXnhuGIE1GgG10GlneK+++ir4Qx9gZbPT6Px+UJ5rr0Ww9cADD2DXrl09ExpR1+CCg3M9dOiQLPoIbJtlrgmUueNPywsKnzRrdz55CC8cO185ZLbg4NCps8hn55Fzo3BcDVHNQlorYq95HleZJ1t6AKvOmHkQ0OIyY8tMalp8gYM2LqMlSypCPLYsPP2ZQlszJRNkuLaID9WCi3YBcHW9L1WpBzqsb3ORz2Ul+8csvVgKiZ9sfTEpyXxpJlzdgGnVPyYIwFCK2n7v4VaxXmZ5ZLbOArbqs9nfHc0UqruXaawGSmqOs/MLQpOvFcGqsjxi/W3R22So1RBQYlqsHV4NOxZhJrgGbC0Cu5RHITMjm3R89v117N3EsRfnsgyBzQLF44IraHOzj/eK4nasD6ZfuNqo4vPEOfYS6Ktx5pwoUJiTe+4XTyMAVmA4qNd0bfzqAWB1jN86aSAZv+Bo0fw+YMkPFaCpBN1t43uS3z90YKAP8Fpo//3f/40vfvGLlaGQ8s33KoUgVfuTP/kTvPe9710Lww3HEEag7QiEALjtkIUnhBFY+xHoBAAz+9tK6biflOfaqDJrxd3w7du398xqgtdgBpPq0lNTUwIISGtulfHmsY8//njLeuTnj53HXU8eqppKLHcGYyfvxTknjbPOIM2KkNKK2G7MIKYFr9Pm4oMUaP7LrC3BZKd2GaQLm4kBlAuZilAWf0eqruEUK+NXKsPKioYZ0WIxWAa4m3rf2meBQG1yerZSf9xKTMpfA0wvVgJDY5F6Xtv3/8/eewBZdpXXwuukm2/HyUmTNKMslCMogpDBPMvGFIbfLmNsXH5UgQEXVZhkmycwz1WEsnEVBmyqbBOMH888bBlEklCOKIfRjKQZTQ6d+8YT/lrf6d339O0bzrmhp0c6u6prQp+w97fPuXev/a1vLY6RtOHgRoA6JioAVjRdFa92/ezlJ4lQ3QXwLxbKqpRLKJdLyOZqKtAUlbJtVyBzo36SQk5V6XoxLRmjqbLDi8W0ejkmXktZCYnomZkVJgX9s4fSxvzmD2EnN2yY0Y9S493rvnJDqcyYGlYkcS4fAGuBjcfFddCcL278BNkanfafsSL4NQOWR0Gvac57UF2anzNq8yPsBojy9yY7pVlJTdA6aaPQojdhJEchtOXdDh8+LKKI3BDtRckPN6folvD2t78d3/ve95bF4L/5zW/iPe95T8u+/NM//RN+//d/f1n0N+5EHIGoEYgBcNSIxcfHETgFItAJAH7llVfEhuHiiy/GihUrFo2SgJTAkSCsFyrP7cJIoPrzn/8cGzdulPqoXjT2ndQ12u8wk8sd/DAgUtUjk/LWTPTk+FQB37nzqQW0WqM6gzX7f9QUfIUdE7MpSpGY50SjIC++C0EfPXId1ISyHKt5ppILe4K6gzMOTlQtAd95o4IRrbHAFueOYJkLdm4upFKsKY9WM6x6LZ6+Duud/frjgZXrYTZRQ1bnLBbBal03y/OEMsyYzIlW8f+iAuBgRpXAW+qTe0RfDf2sCODHguy4okCvGBkWwMsfzgZrv6kwTVGtdv3kM6iyw0GBvaCYVqfZwlZjUzGl2JnpFKQPnJdEfgTJhLXg3WJmk89LL7OlYeO+0PIoHPVZXZse1/RsrmfeNKqDVqyNTh8rnl8S1vji8oHgWIO14pxvRcP2N0Da06UVAM7M+TW3imM5tQLjoxfAzqw6JWjR1ISgaCL1IsK6ArQaPynVpEATTBJUxi2OQByB/kcgBsD9j3F8hzgCSx4BLlYIQqI0JfR0wQUXYPXq1QtODVKe+UVNi6Be1Im16h8X3D/5yU+k5pYU5W4a40FPSma4mWkhoI5irdSuHpnZw3+/+xkcmagtfKn4vPrA7UiUx7vpusyjLx7lScyZmevGhqhR3W/FzEktrZ8RWtwm3DSec9bgWCWBqaqORCKJtG5jUC9KDfMqY2bupFq9r5/Rau/v2yo4QW9ibgAUqh5WDA+Icnar1kgFmsfXA4r6a7DPCZPZYJ9mHgUAS0ZVbIhodWM0Vf/t6mGIcLLvOVuVHwWABwcHYOiG+CNT9dtFZ/1slS30s8O+3VKYzaVWQ+JccOPF0U0B6BQ3UwDYp0AnYdNSyK0s8tbtVbY0TMgV7b2ipwWkR20sbVCsjkbn+nXQVIwu+iUKLVgLre7NDQK+/xWN5QONLbWanU9Gh2IEBBXFm9GlF85TuDKNQnY9JkbOR25kNS7fuQGnL1O1aG4Wv/DCC7jwwgtlM7jbxuvxO+n9738//u7v/q7by8XnxxGIIxAiAjEADhGk+JA4AqdaBDoBwI2EnpgF4Bc96dFc0PZS5bldTDmGH//4xwLGCco7bcxE0NuXtDUumnmtdjXO9fdS9cgU46IoV3370SO78fz+E8ilE5gpViS7Onr4LmRn9nXabYFrhUJRstUEZlR55t+7sSFiZxbV/QaUlO05mx0jYLMz5mbwUGUTjrl5TDkmkm5ZBJKKXgIprYqV+jReZx3AGm18HjCaZjf1vrWQBfs6PTuLYsXByqF8WwGuZgBYXVkABWuIq7MN89KsuaRiNO12RHirzSLXt5OhmrZvy0O7oqgAo4sHpempHvtkpKVmltRhAmBmTWkjxTG6ZgZuWW1edN6DmriSXz+sspMLxbSstvMW7IECa7brwTVq9d4KWGWzmXkV6EYbG2FqvDsf8cIz/ayqziICAelRmw+A+c7kWp5KxWj+KGXpqGrRQn32UqHFuZp1Jgxdmufy8yo4T6HiommoDJ+Oo/kzsW7tWlxz9iaM5JcXLZpevS+++KKwpeo92UONse4gZpNpg/TRj34Un//85zu5RHxOHIE4AhEjEAPgiAGLD48jcCpEoBMAXF/nGqQ8056DtbKs51rKxgww6b5caHTS6FtLyjMzeQTSrNkikI/a6LX7y1/+UmhqtGUKtqf2HsVPfvXi/H8xuzYw8QzShx6Oepv544PiUVwYM5Oq68Y8DbhTH95mold6YNEeFMqC4+CuynbsdUbEZmjIGYfnVJFKJqEbBo46GVQ8E+u1E7iw/AgMtxrZ37dZkII2QjxmvOjAK06Gon/7ALi9cm49oKjvS6VURIniUYOtszzBuC5l3W/YB2y2WMZMFVgzkJKaWm7QsJ+WPduX2lmC4G7FtJqpKVerZEQUkM1mF73LSjFalLHnWAL9EJEKxt2naLt+hrYJg6LdPLE0Q1mZtTuWv6eaMscqGWGttqnV6pEXkO6a0NyqAPVetmZ0ad6Dn18UK+PnbhjWEONJiziKgNE6aWbkbJy3fRMu2bFu2ahFUzyRjCIKQvE57LZRYJH2g3/1V38FCkvFLY5AHIH+RyAGwP2PcXyHOAJLHoFOADAXYffff79YOzAToeyBlory3ChIP/vZz6QvQeXJsMEkpZs1zVyccUwU0wpv3bPwLhQp+cUvfiGej2edddb8L49NzuI7v3x6gTBNavYAVh66E8k5SizBRpSmaufYb4rHMGut+h1VhTl433p1YvaK6sHNxKGYPTyElXi4uBqH3QFs1Mfg2BRFsqVfvpKshv32ADL2BM5292BHerqrel/VX9PQ4DjePNGZdN7y5NHQAlxhAbC6ny+UtbBulv8uFGZRrVSxcsUoqg69hRfPZBCoM4MeFBaKMu/9PNanQJcwPLoKVc2C5jrws/z+gPpZO9taTKtxLamq+6UisiEq3rXAsySAG1KNALCKYaONjajZ0jDzQfE0vt/0zTar0ep+g9fnZy8BYlQwFXxuW9GiyVAQNoBmRhLnChODRseQLl0qlWUTJNj4nLF+WAlq1X8ecwxVIw29WqORK+skd/U5uPrszdixfrTTbvXsPLKiSFu+8sorxYau28bN1be+9a34whe+gA996EPdXi4+P45AHIEQEYgBcIggxYfEETgVI0DQFqUxW3rPPfcI3VNlJNrZA0W5fifH3nHHHQK2rrjiitCnEzg+++yzskDhucxcM4vcTeNCjmCcwlnMIrOVqza+fedTGJ+p1c2alSkRvWLdpQALjQs+HZUq1Z7bi0BxzpitZuNiuF49tRsArMSkVBy4aLfEOqd5e9Zeg19VN8LVdKzAlIBfHwAnJJvDmsBxO4GqnsC5yaO4JHmgmzDLufU1yvQvZmatMDsrVj58PttlkqICYNVp2rPQH1VRwAmAKbzFe5IyzGybUnjmOaaugXbJvB/9VJl1pP3NcmsEwE61gmw+D+gWbD0hY6lXjO6l0nCzGNTEtEiXrtGFlZhWMpGAYZgC1mSLpS6eYQDw/HwaSanFVuMMmy0NO3+MV8lTmd9oG13Be3QKgNU1uEFEzQE+t43E3Ph/BSirrLCj6+44vqt87hSoV2rii9WlfUVxqR1PpAQ0N6KRK+ukoS3n4dpzt5xUWjQpy9xgff3rX98Ro6g+srfddhve+c534qtf/Sre9773dRf4+Ow4AnEEQkUgBsChwhQfFEfg1IsAF4phaKBqZErpmP8m5ZkKl+3sgfodlbvuukuyn1dffXWoWzEzRMozRauozkkA38yCI9QF5w7iou32228Xz0fGhe22h1+Qul/VSC1c88qPYFWnFl2a4FPsZgIKw8GDOE/sOxeNBALMejeyGyE45jHDw0OiGhu2BdWJeQ4XzKqOsNU1nqquxRP2euhwMWhUfH/YSlkojczyUDG5qKdRNnI42zqCq7P7u1bgTYqVjA+MlC8xKdpq7P0EwLynqptlZrw4S+XpCoaGBgWaB+12OGcmNwEofCXZ9Oa+xGHnqV/H0QapUCxJvaKbyM4DQj+D6M57zvL+S1k7u7iW1BGBJ5sMXSuLtOEsEtOKAoBVPFkH7Y/T3xTsVEQqOD8iJGbzedHnN7w6nb9uAbB/Xyqd+x7fhmfP1/pbhoEiEkKXXsqmADDLN7ihoVozujQ3k2wrg7TuisYASz4aNVonTa04HzvOuRiXnr5OKPxL3cgsombGNddc07XQG/tO66P3vve9+Nd//Ve8613vWurhxPeLI/CajEAMgF+T0x4P+rUQgSgA+NixY/OUZ4IvUrvaZdmWIob33nuvZAS40GjXgkrVFKvavn17x5Tn+nvVC3I98dIR/Ozxl2qHeR5GjtyL3MzLLbvJRTMFiIK5Itf1LY6YDePCL5tlvW9jcBsFBKqOKFXjGhhIQnfKIfLRwB57BR6tbsSsl8RaY1IyvmXHA9nPpNGahoEJYxiG5uIc8xDOtQ5KVpSbFgSGUVuwlraeoh1l7J1mgIP9ZY3lZMmGW5zEyBwAVr8nlZNAvVjxs73Lse5X9ZWAtlzyAXBmZC0Sbj0QUp6zFVECV60XIDHq/PPZmS2WUPBMaKXp+dODYlqcW2YWc7msZA7Dt8VWWHzeBJC50bK3ZAPQQqpqUFCqW2BJf+9J2VTqxYYjAblPHS8gwRfVNKVGWXeXlplAASz+UHCwlRq4bKTZDgqOCVRqc16jS/sZ4nq6NK2TymsvxqUXX4ydS0yLZnnQ8ePHpW6307Ka4HNLz90PfOAD+MEPfoC3ve1t4R/p+Mg4AnEEOo5ADIA7Dl18YhyB5R2BMAA4qPLMjCMzfL2wHepVZFiTzMUuFxrNGhfErMmiKidry5j1XblyZa+6MH8dZoCZVd64/Qx8t67ud/DE4xgcf0osWTTHhuEtrH0LdsYHFppY7RDcE/xyDKwlo2dmK6o0KbmsrWNGslmGpP5erFtVdciuZkiKL+xiuOiZuLN8uohgrTKmYVYLIIWVzUik4ekmDjkDWG+M48rESxjRa5RqggvbcRAWW0hNcaCv9aCyNvbWFGiVTefzr+x4uLEQJWOuYsisPJWnB0ZXwQrYxojlke2CatesWUQPlJR7/sDOXZAZssmpGcxUPIwOpIWW36gJcGL9pVOE7tU2LxpRavvR14Wg0q/7bSamxfvzXSe7oxFTolX/FnvrLrS+ajc2ho+1vyUt3bWaMu/F55Uq0L0CwKr/rm7CtFKgRVtGq3QE9NvFotXvSxSQK5UxMJBv+1llJNOwKyWAmxFzZRb8bGxFl1b3LmbXI73tKlx98fkYHch00+XQ55JlxDkLszEb5qJf+cpX8LGPfUzKbK6//vowp8THzEXg4YcfxiWXXCJlUtwwb9Q++9nP4uMf/zg+9alP4S//8i/j2MURkAjEADh+EOIIvEojUL+AqB8md+cff/xxkPqsKM8U4+jWdqiX4XzooYdkoXHjjTc2vCxpdhzD2NiY0DtZ79uLLEqjm/30pz9FJpfH89NJTMzW6n7TM/uw4vDd86qzQnU2M0K3DKorq2sWPEsyq2N2EqVyBQOYxc70FEaS7bNQBGScN85XGI/VYN1vpzRd0qBfcFbicDWLlDOLDEpCrywbGUy6aQybZZymHcWl1l6hzwab1EAb9NVtXwMd7GvVyMCq81MNM3YumP1sui3CTm4AfTNeBMKq3jDMc8p7Ekgz3q5JurAGy/Oz58wAunoCOmwkDW5oNBbJCnOffh2jMuq05ipXyhgZyLbdCPA0c85ztmYRVe+P3Ov+KlBZ8XTZBAlmotW9lJgW33m1CSOLGElyNhbTatXPesVoglp/Y6q1jRFZHAWXYlKL1ZTV4yb6cCGbAsCsq0+newfgVN0v9QgIhi27hITFd3FpnlPlPc3P5VZsIr6TZVdvKM7l06VriuKqpMefc/9dFrq0YaIwsAUbXncDLjtnZ99p0VRt5vjClua0exRofXTrrbeKCGUngo/trv9q//1FF12ERx99VOwO6accbHxmyAajdRXtHClkGbc4AjEAjp+BOAKv4gi0AsBByjPVkamSzEUKbYdYY8kd1eXQ+KXGvt50002LukPgzp14Log3btyIM888s6+0bapAP36oAC9TUyG1yhNYfeDHDTOqFI6SulBalYC2PMDT9lr5mbQNFF2f1pczXWS1MnaaR3GeuV/oxc1asVhAsUgAPLCgrq7R8fV1v2FErxpdh/TQh2ZXYr8zhFktiyKSAr7TuoNBvYhV9AFOkvpsCu2yUYKxXQ10sK9i4PWZAAAgAElEQVSO+BJX5jcUVJ/aAeCgenYqlZSMOgGwn1GqCn174QLaXzxzEd1sgR4EwIrqaKRyqFYq0LwqPN2aX7ifDMpwq3fUV+lmUs3DVNmFW5yS5yZsJpxKyhwf51S1sCAx6mfHvOpzCP9kVVtKtoQ/v43FtBQobkdRrVeMloy368oGR33jZk7VBVzNnBdKsz0N+90R7HNGMO35isADWgmnGSewXh+HobXe2CKwn5yckmx2r2zm+CzSlsmr8j1S3tRJ2cBJeJVQQD/qHNYfz8+qctnfPGo2B3xGpW65Ek5Buyag5ovxqabo0kYiCXf1uTj/6l/DmVvWdzuEpuc/+OCDcn+WCvWiffrTn8YXv/jFhgCuF9d/tV/ja1/7moiHffCDH8SXvvSlBcPlxvUb3/hG3HzzzaDYWNziCKgIxBng+FmII/AqjUAjAMwd9d27dwtdmDvo5557rmR8VSPI4yLs8ssvXxZRYXb30KFDeNOb3jQPUghiuJu7a9cu+T/u+JK23e/2j/9+G546NCtgm411tFR8NqszLW/t6Ca4yN5VHMKvKutx2MnB9GxkdC5Eqc6aQBUmVuvTONs8gNdZzZWUmXXgD7MqraifpOgG1YrDil7VD4T1edPTMwIIZhOjOKSvxvGKhUQygSHDxgZjHKv1qXnQ7ugJQNOb+qESXNTbCQUzvy50eMzENahXbAWA69WzSSdtJACnFtB8N4JZRMPQF9izqDjUA+AaUNdQtvKSpa6nk7cCUP1+RoPXD/roVqePy0ZRKzDSrG+NLKJ6OUahkzsumPUPI8ymAHBQKE6JaSnv4SB11s8UmovEtBY96wHF6EZq0QRxBJZB6nPZM/BgdSuOuAOY8lIoepZUMKRRxYBWlLr5S82XYGnN6+HZV4r29RIAt8qqsj4YnoO05gjI57vdj9Zo86j+PkY6B6dI8Nue/VJ/bk1Azc8QB+ccVgrG+tfhsmtuxrbTNvR8Y/S+++6Ta/YqW/tnf/Zn+Id/+AfxFo4zlNGfRupDcA3A95zq3EHhS6prf/e738X3v/993HLLLdEvHp/xqo1ADIBftVMbD+y1HoH6RUEjynM9XZgUaGb3rrrqqmURPlKa9u/fjxtuuEEWsBwT/48KnLTXIOWZIiv9bkfGZ/C//+U2qWeVBYrnYtXBXyBVPBzq1iXPxP8tnIf9zgDy3gwGjeoCCvOMl8C4m8UmawJvSTwpNXuNGuvqCoXWAFjVeKq632YZ1XYdJ/V3dpb1yRBaObOqzOhwsUHwQZDZrNEPl1Ymyk4oeFywBtq3EvapyrwPF+dmoNY2eF4t+x2kf1M9m7WGJaE853J5WQSFEcHyF9B+ZpjPVX12mNfhBgBtkAgc6SNLsMB+qvpkv26W6s9F1hPNd7fXdjvt5qr+9wszqiUUi1QPb52Na3ePoNUOj63Rojun1M7X0yIBUxSa2wOhZurCwf4TDPlg2J9f1YJiWvw8aZSZDCpG+9ZXGqq2A8aUYnAKpHO677e34mVnBSbcNIb0AjLw31seN+FlMKLPYqtxDJdazcXxFAD2WQvUAOiu+dRninO1yqr6gmCGW0XKcEWPIIpjQJgeBi3EGh1vWQlUHFfsm3rRGtGlq3oK5tpzcPYFl2PN6lUYGRlZ4Kve6X1pF0iQdfHFF3d6iQXn/cmf/IkoQJ84cUL6GLfoEXj/+9+Pv//7v1+gpE2hsvXr10tMaYsYVS8gei/iM06lCMQA+FSarbivcQQiREAsa+YEi5pRnusvxy92ntcrcY8I3W146DPPPIN9+/bh2muvlQXtr371K7ELWrNmjfjxLsUXWqlq41t3PImnntsNKjafdtpmDB1/BAMTz4UaHheWTxSG8YC9DTNaBmutgq+iHABMvNARNy/A97LEXlyYOtKwFpFAj+Mn6OcCfnHzBKip7K+IXkFrWFPZvPM1UCkU7Vxu/l6VShkzM+0BMK9NKOOYWehOqWEtNBfqxL/K8qhdllplvxX9m9RR9oXPBcWoCH4VlTkMAK4ffzN/Wh7HjHc6mYSmzwlFueUF80cxMGa/6zOYjbyDQz00XRykxKQcmXtIhjoMHTXcLecoq4E57WaMnVgJlcsE9FQXXmiv06r/zcS0mteFL1SMTiX8OlXXceY9asfcDO6q7sABZwhr9QkktIW1w8wOH3KHsdEYw7XW8xjQa7oBwb7yuaMHOyn7/OmmcRPI0dPQ2vh7q3soQbCEV4apeW3rn6P0jRtlfDdZUlPfuElHX1+tGo76HOW+6tggXbqsp5DdeD5Wrt8qwJW+8ARF/Gm1kdfsvtwo5mfwBRdc0EnXFp3ze7/3e/iP//gPYWl00p+edOIUv8iTTz4pAphcK5DJxvaFL3wBH/nIR0RgjEJYcYsjEIxADIDj5yGOwKs0AmrR14ryXD/0Bx54QDJ8y0WJ8vnnnxfhih07dgh1m8DmjDPOkCxsu9q+Xk3rDx/chd0HxyQTzYzS2SMeRo/eF+rySpTpIXc7ntW3IWkAg7rvRUrQBNeZr3WddpMowcL51gFcndgDUkOZGQ3WInLxPzvbHAAvsBGSjGoKptN44d1oAEFQSXBAkBFUm2ZWmCJTzL6H9VdmLTSzaqwl9d2Q/abo0Pxz1vN9SltpBwXp35z7mZlp8SHmgpH9CT4PnQDgYDwUvZL35ByKRylFrzQdppUQ0StuQNTXDrtGAqRx18ecSszsa6O60lAPUsiDVEbVdjxfyGpu7nsHgP2OzAtISebbp9BGHaN6Vn3LnvBWQmHtdZqFTIlpKbp0o8y/mlsCRNdKwXKrsDUDaa06LyJFf+wn7A1wPB0r9MZlEEfdvADjC8x9OMs81LBLvQTAfDa5+aUF7KzCPDr+fCaRRnnuM6d7WjTZI8y+NwTA6QGpSV+qpt5nLzOCoc2vg5GugXICWQWIw3iMs88EWMpnvhdj+K3f+i3Q856frWHEDXtxz1fjNchcoxI0y6NOP/10KY969tlnZe2wdevWV+OQ4zF1EYEYAHcRvPjUOALLOQL8MqWIFMWiwiok01KAx1M0Yjk0fpGxXpmN2RFSnhstqPrV10f3HMKdT+6Vyx84cABG4Sgu0Z+fzwK1um9QlOmpxNl4WtsGEy4GA5kgT9NAGq3uOphyk6jAxHnWQQHAbASEBBblqp9dUrWujSjIwVpaHhtV9EotxAn4GoFKXrMTAKxiRCVaqiYb1QIsU4PjeAKHWR9t6oDhOS29gxUAJh2bf+eiVlGz662jugXAqs+8j10tI53JCTOi6BrQAoI9BMAES36NqTlvYRWk0aprMevFTQ01l/14ZhX1ud5CqtcAWPW9lvkmgKU/tAbT1MXrulXjceJPrLPuN1oWsFsAXN+vZpl/f25NZEj9N3PQWdtuF2AZvs/1PcWNeNpeh7RWQV7zN7Xq26SXQgUWzjX24yJrX8Nj1HuXTqeQTHaeAeaGQpEbSU1KCMI8byJ8ZljIoNw1LZrfPxwbyweCzUokUalW5zdOwvSrl8dw7obWn47151yJom3I9x03Q9g45/x+Udnh+o01HsPPxzvuuAOrVq0SFlIv2pvf/GZws5de9q0Us3txr1fzNf75n/8ZzKZ/9KMfFT9lqnTTQYLinnGLI1AfgRgAx89EHIFXaQRoIURBiKDKc7uhkmLM+lqqLi9VhrVZn0j1ZUZaiffQ6mAp6WGHx2fwb3c9PZ+1O7r/RWw8/DOsHmpvVaIytRwbF1F7jQ24t7JNagXX6pOLlJIJgo84eeS0Ii63XsZZ1sLaYmYgJetZKDaswSWYEFrhnHKtTUGhOhuhVnPfqN63kR9xtVoRUawoGeD6+7pG0s8qVwtwmf+m0rDr10628g5WAJjH+dTsLFhH2Kj1CgCX6WVaLiOfH4CbyAtYWyi4RPGdWlY76DvMMYrCrVNZQEFvp4jd7h1t9nvlTUxfYj8DXetXvwCw6gsz3x6MefDFMbJVncaZRB+sWWIVFqbuNzjmXgPg4LXrxbR0uCgiAcv1PaV1KwU9lUHKq+AJdzMeq6wVz+ThgP918HpjbhaeBpxv7pefRk1tlFF8MCyrov46UdWU2z1jnE/dtJBwSh3TohVDIwiA+d56tBOrhs/4t+trp79nqcimsy7BzstuQtHRxUqPP7TdU6wAft8oMKzo0pwvUqDXrl0rzgPdNt7rDW94gwBxsp1iANx5RPnZsGHDBsmiE/h+61vfEgGsd7zjHZ1fND7zVRuBGAC/aqc2HthrPQIEkBSBCKo8t4vJE088IaCZXx5LUV/brD/cCWdflNUFxUZWrFjRrvs9+32pYuNf73gSU4W5zI7nIPXM92HOHsbIyPB8pq/+hlzMMO4E7VzIKKXaimfg/5XOxX53WIRyBrXiPAhmKTDtU/izzpzCW9LPIOfVrGcW3MO1MT45hUxmIQU5SH1mLSq9P2m91L55kk1lTWV9vW+jc5kpYb2in3ntLFtVUydOC104UZcBrGURa97BjCuVcplRYlxJW2xFFewVALZZ81woIjO8CgmPWaLFMWVduBLSWmjN4mcQDSsBLTkAw2UttA8IVWafdFUlVtZ+rpofQQDEa1Y9v666ngLbbwCsekbKPZXiDNZIz7EXCIKDmwTCaLBJoybzwc+8RWn9BMDBfvB9cDygXKnCrZYWiGmRLnzMWI0nta0YwyDW6WPwIX+tcV+E9khUSb/C2oO1RmPKby8AsJFMw66UFukKRIlro2Mp8JYgPaNajEzf5+cE30Oyj1TTU3m4peluu9XT83PZNHZc8AasP+8N0BMZ+YwhGOUPATFLglTj5zkBPdlAFFeifWC3jTHi9xu/b59++umTvvHc7XhO9vms+WXtL9vKlSuldGkpN85P9vjj+4ePQAyAw8cqPjKOwCkVAX6RBxfkYTqvRKeuu+66jrMRYe7T7BjSy1544QXZCSe1lNYGtIZg9pdfZkvV/t8Dz2PPofH52w0ffRDawV8JBZj1Yo2y4wRCYhnkONJ3ZkmDu/kv2CvxUOU0HPXygqMoeEWQWvBYN6qJn+4F1n6caR2WzCFBBKnRwaYytYMDeeiGL4JFQKEop61shOpj167et1GsuwXAQaBOijZFoyiGQ0sp3Vs4Vj9T6snYmE2ybf/37RSoeUwvADDpxBNTMyiUbQzRd7mNn6sfL0+AklIfDlqz6FYSWjKLjGaDtktsVMSmiFS3tGj/GXAX1P0G52+pALB/z4UCUuLRavi0aI6XGz4VZqlDCjXVP4cKAA/wHSCToE/NV1OmsFktW1kT06qi4ni427gYhzAKXQdWaDOwdJ+JUfV0HPfyMOBikzGG66znmvp781mhmBt9jRMJevVGa1bCQtnWxDu7X821MkjqnoDssGrR09NTMtcKABvJDJyyT5Vfbo2f5ytHh3HGJddj6PQrROxONT5vBMIKECu6NM8Jimk1okuHGSfjyUwyv+voL3yymVdh+rycj2HZFHVCGFfaS/3N3/zNcu5u3LeTGIEYAJ/E4Me3jiPQzwgoK5Ao91CiU6Rk1VskRblOJ8cya/rYY4/JQoO77Kz3nZiYAL2A+XcqPy9Fe2T3IfzyKb/uly07+QJGjz0olkC0kmkEgLkoYs0bv3RZy0c6YyMKMUHwE9X1mPGSvmcoa5s1G1mtjHOsgzjDOFLLDDMDZaSh28V58SgFQLPZDHLZDJj3q8wBw3Y2QsHYhan3bRRrLtanppgBTke2bCH4YbZT+kkatONnCdl8oSxat1AIq7ZApg1RsTAr4InZVILL5grYtR53C4CVknKhWMRM2cVoPtURNbGRHY+tmYBhIqO78/XDCcu3bqIfLi2xDruDIH3WgQ4LjogsrdEn5Vmpb7WMenMf3aUFwH4PlcIwhbhoiUWKNsHhrNfOoqf1W04rsFKpLMCqX3RRH/zWLI8a9YgbSIcrKTxob8ExbwgFLymO3my2biGHElYZs7gi8VJTijSP5TvNLCM/b1Wm6pibwx5nJY67edkcy2llbDaOY5N+QtSaVeM7VTEyMPqoplwbuwYk6WFehl1pD7bJ2CCYE5s6XYfG+mK7ca30Unyuh7mHtnI7fv1d/7MpCOU7yhIhbhSTAcPvrFZ06TD35PkUdaSitFIvDnNeP48hK+hzn/scvvOd74gLA+nfrFP+zGc+I5nv5d4YT9oePffccz3J0i/38cb96ywCMQDuLG7xWXEEln0EOgHAVEvkD9UUl8JfVwWRO+wEulxQ8MuLO7hc3JIKTSGvc889d0m+eA+NTeN7dz8zT/dLFo9i1cGfiWALF6ns30KlUFKIactCSrMmWd92dCvSofc6Ixh3MwL1hvQiNhsnkKyzUVGxEfEozRKlXAJQRUEm0PY9df3sWtXMhhIUClvv2+gBZwaMC1sCfB/kh28ElQR4Ys2kMWO1GMy5mgnWH1Ioi5ZLin44QDVqTcPE1HTfATDnkXFlX6erGpxZX0Sue7BF32F6Dvu+w2WPolkeLM+3cSJrYMJajd32Ckx5adkkoUa4qbnIoyS0+Z3mkQVginPPBXRFNg8W1v0GZ+ZkAGB1f6UYndHKIiKW1h1UbVs2QjppfNf4HvZmThb3QOZeTwLcoAnRSdb1P+esxWEnJ2wO0FvXK2PYHcd29xURvePmjWn6Ymn1Gb4gAGZN+2P2Ruxy1mDKS4moFcOU0GwR2lqpT+P11i5ktDnRppNAKSZ13Ujl4JVn4dUxVILRZC3tfLlCegDOEqo+d/JcUY38jLf8T5y1ZUPL0zmuRx55BNu3b5fvJG7Sqvrherq0qh/mhm6zkg1+T5PdROHJ//qv/+qk6z09hxlvMsDuv/9+qXN+/etfj5dfflmy0+wn/385Kyrfd999uPLKK8XKkWJlcYsj0CwCMQCOn404Aq/SCHQCgPlFx13Tyy67TDKd/W5cvJPuTNozF0tU1eSXrmonTpwAxbzOOussAcb9bKz7/Zc7nsB0wc9uUPF1zSv/PW8lw9peLg6GhgaFellPISY1t58WFsyaEjgUxo9JBnYgl6sJ1CSysNzivP9v4zhFq/ftJQBWWUo/S93OmsnDdMlBoVhCUquKvy+BA0FPuTiLbG4Ahknw2Lx1kwGuKSlnYM+MyZz3g26r3s+Ca8KtFDDtZfCCvgnHtFEBiXm9hJTuoaxZmPSY7fewVpvEedZ+AUAUPhP6tKsLUGOWtVk7mQCYfZJ+er5XcsKeqW3czLEXorzX/QbApmWh4tA/OVp9MjP3U24a0HRkDQd5exx2tYJSpdJAKM0HxPy8oLAcrc24ebZb24Rf2RtxxB2QEgkyQ1gIUPIs2RTJayXxFb7BehbJZEI2U1rNe5S4Rj5WNyG05tJUQ1YzgSLHN7BiDdxSY5uoyPfs4wnWOb+OX7vx+rZ3INglU4n1v/XZUH5GKTDMP8OoS/PzhYrSFGqiYNPJbp/4xCdw66234oorrsDtt98uJSdsylN3uQPLt771rbKR8L3vfQ9vf/vbT3Y44/sv4wjEAHgZT07ctTgC3URAMkMhqGrBe5A2RCGOpRCd4uKA5vXM8vJLljRn9WWr+kQ6NJWgudjYsmVLN+FoC5hY9/vi4Qn/ONfB6gO3I1kemz9PAWBf1dSTet9WlkH96CwziCemyxjOp31xGkBAhe7RVsSbU1FeLKzUSb1vo/6Tkjw5yQwwad7t1bB5DVJfKfbExiy11cL2JthPgt7U0EoYngvDq85bQNGbOJtJt7Ta6RQAz3vTSkxtlIv+pkc/AHAwvh40PFLdhL3VPHSnikFvSmpGuWGgUSxK13FcH0FGt3G6eRTbjGMgUC9VHbhGan6TptkzdzIBsMqolzRmqQtCfwd0UYxmvPkO0WM5bOsnAGYtNanMndYnB8dASyH+0FKIQlq0/lG14eo4iQ2ZEbaDRDqH272LsdcdxYBWRH7OL1wda3u6UOOZBb4m+QLWJYrQl4GasmYlRY/AqQO5zIwaVgqDg3m4drTNhLDPQq+OK47sxFt++z3Ip9vXYB87dky+t7gp26osh59BLItRgJjxCNKlWW7E70Cyrfjznve8B//4j//YqyF1dB2uFwjGuXlB5hVp2cF2/vnnizgl7RKpy7FcGr1/v/GNb+Cpp56STPWFF14oG+fds3aWywjjfvQjAjEA7kdU42vGEVgGEegEAFMBml9w/OKLoh4ddbik0dJyiYtZZnxpWN9IdZrH8cuNpvbbtm2LepvQxz/8wkHc9XTNp3P0yL3ITr+04Hxlw0PqL+sQCU6a+dCGvnHEAwlAp6emkEhnYOVGoNsVVDUDCQpmzRXUMjOYMKi066sos95XWZI08/cN2w1ei4sj1r+FqREnbZmNtb+22PM0tz8J1iUzu0Wla7FNYU2lmYFdmEJxZmpeBMukHyu0hlY7UQGwZKZ1VttqMDwbumGKfdFSKQ4XPAuPVjdhvzuKdda0AFrPceC4Lo1H4XguykhgTBvCJv04rkjuk+yaY5H23kQxPDCp3Lzh4pabN0stskOg3qjul/XtzF5yc4ObORXbDSWwpADw4OCAbA70qpH6XDUy0HtcTyv17rqBJL11xR+ZVHgqh/uA2JmzitqvrcJDxnmY1PJYq03MC6UFx0e6tQ0DZ6VO4A3aU70aek+uYyYzqHLDpuI/jwR8iewAMlQGW8bNtvLYftP7cP721tRnNYTDhw9LDTDLcqIIM/LzLUiXplctvwPZCDopgvX5z39eKMdRy0t6FV7WIF9//fXyXctSqPrGGuBPfepT+PSnP42/+Iu/6NVtu77ON7/5TdlAYNkW9Uu+8pWviP1j3OIItIpADIDj5yOOwKs0Ap0A4KWouaUtARcQSv1y48aNTRflrKm66667pOZox44dfZmpAyem8O/3PDtPU8xPPIvh448uutdiH9qc1G0uZaPS9NTkJMqJYewxt2KXuxYVGKKgvMGYwNnmQWzQJ4QeSxXlcqWCySlfjbUXYD0aAPZAr01mfx3NIhSftwGqjxlpoFTC5TPRrJ+lahXjszaGMwZSiZr/b9I0UHGcBTWlYQGwsqrZ64xiRkuDiWp6Lm/QjmOrcQxGeUpAcDvbpW6fAQIb1n4ed3NYb0yAdbP8mVf25QaC7eAlZwXWe4fwOm+XxDStVeUZFLslozkt/GQBYGb/SX3WXFJ1G3kCUzE6A90tI6G5UuetRN2axbRf2WxmK2270qSf3c4whd/SSHDTxilJfblqapNlj7kFj3g7BOAOwbcKYgaLGxbqT8ZyDHls147g5uST3Xeqx1eQjHYqh3KljInJaaR1WzaywrSSZ+KAOyx11FTPHtFnsUrzmRB9a5oO76y34X/c+IbQG0O0QGL2ltnQ0dHRjrtGd4PbbrsNP/rRj8C6VT4HbPSCJohjTfBb3vIWyTQvVfvSl76ED33oQ/jt3/5t/Nu//dui25JaTIrxLbfcgu9///tL1a34PnEE+hKBGAD3JazxReMILI8IsCYpSutnzS3BE4EvFxDMIDLL7NOJmzcuCihkwd1cWkX0uhXLVfH7nS76db/JwiGsOviLRR66pGkyG80/mXkjIDoZ9CpDA548oeFe6xJM63kUXRM2dFFT5mKTyrPnW/txufWiZKkp0MXsWiqTlZrDbhvHzyxGKpVsu7BVtbQus7gU8Wpo0+LJwq9Q8EXESIFvtqnADCYphen8EKxUTmq05xLMApzEamdeEZtq061ptY6n4VH7NOx3hjGFDGZdC56uw/RsoaCOaLM4192FTPl43wHwlJuS2k9SXDfqY/PjIoWWjfWojqfjAEawWT+Bs93doqjrVmsqtIwfWRQ+ILYWLOhPBgBmf2gnQ0DHbHqrRmElihBxTpOGLplvpwktuh8AmNRnvkuGE+3zMvr7pEkNfNLw4FT8ueNnNDfXDqRPx4Pu6aIOP4pJ+awJPsNkO5T1FKa1LHYYR3BT8pnot1+qM4wExianYRloSyvmNFNEjMKAM14KFZjy+cv65wGthHPN/Vih17x4ezmEmdFzcPMt78JILrygH8uEqFlBmi0FEbttpOrecMMNeP/73y/fcz/5yU/wy1/+Up6L973vffjqV7/a7S1Cn//hD38YX/ziFwUEKy/d4MnKkYFjpxBY3OIInMoRiAHwqTx7cd/jCLSJQFQATHBDlcde19wyk0vhECoYr1ixAuedd15btWQOjaDn5z//OTZs2CACWb1sXFz+8IFdODwxg9lSFUZ1Gmte+dEioMa6W1KI3bkFeRgf2l72U12LQPZYycB3Zs/HmD4sSspUhrVgi10OMydUjR3VZ3GZ+zi2VF+aA+s5WKYpwkOkmXbTFABmloKiPc2aUnzm75nha0TTZfz5XHCOuanQTkRMAWDel/cntZS1s6bjZ07YmHHkPDHD1g4AP2OvBX9OeHkMagVkdduv+/VMjHtZ6HCx2j2Oi6uPYjSf7qvAGR+tR+zN2OuOYIh90RYCRldPYNxJCuV7s3EM52XG4ZR9qimfT1Vbyk0m1RhTpT7M2C01BTqMlVD98yOK50YSll2Yo0UvzOzz+F6DedbgVvT0ElkJ+SOmRRQSGVheGbMzMwKA7ewq/Mw5T7Kg6/RxsTtSTAa+d/w5rg0j5ZVwjrsb5xn75ue3n+J7UT8vuO1kJlI4cfSw1OobuVFo1WJDsS7uUT3hbMCLzgocdQeQgIO0VpHPs9k5W6k1+hQutV7CiN6e6h+lr5XkCDZf//u4ZOfGKKeJIvKLL76ISy65pCdOCdzgfdvb3gZmXz/4wQ9KX/g8kPnEMiRmmpeqEXB/7Wtfw8c//nH8r//1vxbdlrRoliPxh367cYsjcCpHIAbAp/LsxX2PI9AmAlz0tgMCwUsQoN5zzz1SA8QvuV401kxRnIILdV6TdOawdYhc0HNHnPVRBM29bA/uOoB7nnlFLplPAIMv3Qa9OB64BbOTZVlwsxF0cUPhZABg5aF7X/k03F3ajIqWwAq9lgFVnZ52Eyi5BtZ6x3GLcR8Gs6kFsSYosWUxHV50KBjzMACYWVlmZJnBq5oZATP1jTti0yQAACAASURBVNfhpgJpvcxWMqbtnol6AKyuaRspv5Y0oNrLzYJytbnVTtXT8dPKmdjnjmKlMYMUbAHTiqbLhflBbwh5bwZnVp/DObmZvgJgjmWfM4IXnFVCg16hT89b3bAvzIyNI4s1ZgE7zKNY6x0TsM/fBem0fNdVbSkVguvffdYWJhLMDve3LrMT8Bt8Rijs5umG2AnxuagG1KJ7CYD5rGrJHNwSM4ydvRPdfCaR5k4V7/LUcXkH7nTOxm5npWRBV2gzYoHFxnme0nKYcUysxhiudR9GyvZp0myMEd8jxQBo9y510+d252q0ZipMSukFPzNZ0sC+sc6d9mZBn+8Tbhb3VbfhgDskXtfBjR9+RB3z8nL8VuM4rjJ3zzMj2vWh3e8Z98qZv4G333gV9Igc6z179oD05csvvzyUDkK7vpBW/Du/8zsCPP/wD/+w3eF9/X0MgPsa3vjiyywCMQBeZhMSdyeOQC8jEBUAc3FJ+tXmzZvFi7ebRpDDXWLumFN8iQCW2d8ojQv4H//4x7ITXq9IGeU69cfuPz6F/3PvXN2v52HFkbuRnWFGxRChmmB2klRnLk590DaDXI5ev+3VQrvpX/Bc0psNg760Hv65eCn2VoeQ14rIGAuzuewfqaPHMYRRbQa/kXkKq5NV6HZJspm1xTJEfbcsgjzRGuNCZW6KVGWzvj1GfVNKyraRFEppfQlfMKPui2mRfti+0I91wlTeVhng4H2VUBbvx3po9pPrWi5ulQJ18Pj9zhDur27FJHJYg3HA8OtUg23aS2LasbDJeQU3ZPf2HQCTkv0s/WTdAYy7fnbd1BxUPFOA0GpjFivNWezQDsGh8rNdgK75yt+kfjdifFM0jUCYGzdBMMyMoQJLjcTnoj0VC48WH13NAprW/Ya/OsfJxlpntXGjADDtyMI8N63uZiWSKNtOQ0/q8L3s7kiWAJCBMrpyFabKHn5R3SnZUG56JLSq2CBRAI3FDgTFrzNfwRnm4aZiWuxNMPvf6/ltNVrXSMNCWZ45loywVCKV8unFZIV4uoWyZ8wzQn5V3YhnnHWy9dCI5sx34hV3WHQNrrR29ywLPLHyItz81luwajBcfXJwzPxOo44FvWb5+dVto/XRH/3RH+Hb3/423vnOd3Z7ua7OjynQXYUvPvkUi0AMgE+xCYu7G0cgSgSiAmBFOaYwFZWZO21c1LFeiGCJdVK0OOp0scAMMD2Jac3Ui1Zg3e8vnsRMyaeZDow9haGxx+cvzQXn1PQ0KlV7LjtJNWJdKKQEwI0AWC/61ewaFHniIp0Zka8XX49DdhYrtUkRuVLNdQh+HckETWoDyOoVvDn5DE43j8HVDAFMZnV2QQaFC1LCzmpAkKfdOBQA5oZGvWUVz1VgzNUNSagRjAYbgRhpz2xRNxKY2SRDIZvNIJlsvPB0NR1chOu0WqKC8lyf6mtK9zgr8Ki9FQXXwKhZagiAKp6Bw04OG5wDuDn7QkuRqXZxC/t7Lvj3uSM46uRRQFJUqS24GDKrGNGmsV4bhw4/pr7NjgnTLrb11VWgkZsNSoFYAWLxEzZ9IS3+2W1tO69TcvQmNd9hI7HwOKHRe1UkNRfjk1NiKdQtADZ1Up8TskF0MpsSwaLVlpHMSo3vwzOjAoJZD0xmQtIABrxpnGUexBbjRMPu0kKMwNNnANSy/72e32axYlaV7IJquSTK83xX+Zlf/7nPemtu6pDmfFdhI3Y7qyT7m9IWbkCp+xxx80ijgkusl5uOPcr8ldKrseHqd+GqszvzlX/22Wdx6NAhUWvuhQgirY/+9E//FD/84Q9FYOpktlgE62RGP773UkcgBsBLHfH4fnEEljACXAwxMxi29YJyTCEtgl8CRop6sJ64m0U1a4AJOi+77LKww2h6HBf9//e+57D36KQck5o9gNHDd4v1DVtQjXggn4Vlsc7UbwqA+SrF3e/8hxmMApQ8tmJk8C/T5+OAPYAhbQYpw2dtEvhyjn21WAPHkceQVsRbk0/hNLPmYyy0Us1YZEXUSEW5ed88jI2NS0a/HgArmjaBuqMnYbo1QSHGnXVtXOyrjHrUzFSU+DuaCUczYLD2cI6STcqwypTu90Zwf3kLJpDFOi1Ie6+NfNZLYMJJSgb4xizrqZurLIeZyyjH2J6GSS8jICFtuMh6RXi62RBU+r66mlgn8Xkh9VzskwKtEW2Y77qiSxMUq8aaWB8Q+5TaKM23POqNj279fT3S6o0MqjNjsMtF5Ac7FyDiM+El80CpRiOOMs5eHkuxOpZaDAwMzH9O6qksJioWjlUteIksspVxrNYnIykiq+w/68OD88v3r0aX5vy2Z1+EGm8yD60yLUwEBYDpF95os4olEnwf/7t4BvbYo1ipTTUFwEfdPFKo4mLrZaFCd9O4aTS78xa888bLJCPdSXv66adx5MgRXHvttV19r6l7/+3f/q3U3PJ77rrrruukSz0751S1QepZAOILvaYiEAPg19R0x4N9rUUgKgBWlGP6ElLpMUrjuRQHoUImKXj0SVyzZk2USzQ8liIhBFyknHXb7n9+P+57dr9cxqxMYs3+Hwv11TbTqEyPo1xklpRqxAS/CaHQij+o7YrQ0NTU9JxNT/8BsAKUUudJyqtTws/KO/FoeS2omjus+5kWofvqOgzdQBkmJt001hkTeHf6ISS0xTRn+vH6NbM1oSVfRTmcSNbY2JhkenK5/ILpUMJX9XW/zEwxc87sFAEVgXMnGyJRALASD7L1pMwnYydzzqy3BpQ8Cz8q7JRs62ptctHimzE/7A0i4xWwo7oL52cnIoPBbp9Vni991zWUtDRMZrVbtJqvbmURLbpd3SzjxawhY8yfIF1aZYYJmlrNG+2juEnTax/d+iFPF0oo2MCG0RwcYUZEr901U1nYJdamRz+3F/MavIbyNQ4CYDX3WnoQTnkWmlPt6rbB+eXnWFADwKfC+9n/TsW0HDODpFezd/LLHGbEz5Z1wM3ao85mPGvzO0LDsDa7oFSD53Az7RV3BGv1SVxh7cFKfaarOJxYfTluetOvYf3ows+uKBd94oknwE3eXoHVv/7rv8ZnP/tZPPjggyKsdTIbN6353U+vd3oUk7kVbBTk4vgffvhhXHTRRSezq/G94wh0HYEYAHcdwvgCcQSWbwSiAmCO5Kc//anYE0X5MuYX55NPPoljx44JwGG9biuV4CgRoxomgcDVV18d5bRFx75ybFLqfrle1pwK1uz/EawqMxYEaLMoiQlsCiMZeqoyvVprUjNbqWB8YlIWdfzpd1OAklkLAlYKNB1yBvB/Zs/COAaRQhlprwDLMISircDvgF7CRdY+XJV4sWkXpWbWyMBw/ZpZ1ZSKcjMLGh5HAEwwRCso1YTWWHUWKT6rTBAz1EoQp1OBnigbEPU+wFyga25V7IRSliEk4gfLp+GFygjG3IwoZ2dQEXBMgSyqQNNeao13AhdXHsGKXOKkAODoGdWFvroyHtuNrJxcyw5TYbpGTVXZQx8w1bKHkmW3krCr9PuNXlse5V0ihZ6fawMjK6AZSSS80gKRrHbXMkwTVVeTZ2E5NAWABwcHFoiTkc4vFFvHhmdlfSDco9jSTzxIl1Zx6ERMy9UspCwNpXIFh9wh7HVHMekkUK1UsDJRxvbEhNQuK8uyYMxJb36guhWHvSGs0SbFg1vzbBG+4uf0mJdF1TOw2TiON1i7ImXA6+e2kN2ANZf9Fq47b3NX005gyPrma665pqvrqJM/+clP4stf/rJYBPbD6i9qJz/xiU/g1ltvlQ3n22+/ff57nLZIH/nIR2Tc3JSOWxyBUz0CMQA+1Wcw7n8cgRYR4OI1aI0SJlikQRHgUeUyTONuMS2OuJCjWjNrhzvNJDS637333isL3m4WHLOlivj9UmyGK6uVh+5AunAQpAlSWIkAjVlmgnbXTEleyKzzBOWicWZ6ChTOSaczYULT8TEKULpcCupBD10Pv5wYxePaTsxoWdiaL5Dk0lbF85DXy9hojOMtyacaZn/rO0RaqQBhCirNZcNIiOT9m4lkjY+PSbZIAWAqLtNeiRRrggql8hqkk/u0cZ+q22njs8yFJ+tYlbBOs2vVA2Aex7HqiRzcSkG8WFn7+YC9DfureUyQbuzpIirFutucVhYf4HO83RgsHZJNnah04E7Hqc6zuOniGj4g8MKXMfhj1cGMsOEUkTQ1YS4QoHBjK+oGRPvsoYVMOinK5EYbv99uY8LzFQBWHqykgNPmS7eLCxSxG91LqM+JHFDuLpPYi3GoazTLzpvpPOxijaJtWgmJsVaZXeRT3l1/VPbft9NyApoAYcS0tEQWlUoJ91e2ioDblJdCyTXBz8usYWPQqIh39QXm3kUAlhneh+wteMUZFjsyahfQCZgiarOOT79fo0/iQnMv1hpTHQ+THtPT2/8HfueGi+WzrZvG7CdLObrdkFV9oPDU17/+ddBfmHZ/J7txbKR3P/DAA1i7dq3UOlP1mv9euXKl2CTSySFucQRO9QjEAPhUn8G4/3EEWkSgEwBMFWhmetp9wXNhzC9tioJwUc3da36BR11gt5tAfvFy0Xv99de3O7Th79lPZn5fOeYvoAZPPIbB8adFGVcJMjUCaD5V2J631+FGAsF+LpNGJpttu9juqLMi2uQDSraghy7H4VOJq3hJW4/dyTMw5uWkTpSgkxYitMi52NobCvwG+yf+q3rCtymZw6ik3pIaXS+SRWEzLoyFsjl3MMWbaFvjZ9VY71uSDRGfTp7riViMAsBhMvCNADC7aug6ikjLGAn62e+XsQZ77SHMuBZcT4OlOVilT2O7cRQ5exyFQnHJAbBYs2g6bBjQuwCVrBvmxkR16jgcp4psjlnGzjch+MzwPVB0af5paEBRSwlwqdGle1hbWvci1QNg9WuCnKShwakUGipi8zgjPQCn2DmQ6vSdbnVeQwCczEGrzCwahwhaJQgwWd/emhLfaV+5GRic31ZiaY6VRcIp4pflLXjZWYExN4sBrShexbSuKptZFJHCSp0CXodwrnlgUbfIuHjc3ojD7iCmPT5FpmxEpVFFVq/iHH0vNhoTnQ5Hzju65g248brrsGV153XjqgOkKvMduOKKK7rqkzr5j//4j0UBmswaij0uh8bP7s997nP41re+Jd/xIyMjePOb34zPfOYzywKkL4cYxX049SMQA+BTfw7jEcQRaBqBTgBwmIwrr0vK1sGDByVbzFohZpf60R566CFMTEzgjW98Y0eXv/fZV/DA8/7CKzOzF6OH7kKxWBDhGQINP7tnNbw2M8Gkz4pSrGtLP5jJzGSyUmdZlRrcjrrV8CQBPh5r3xZ66HLBRd9cZmeEbqoTgA6CAjEUayJoW61PRwa+9Z2geBUzpeZczSx/L97Bjjtfa1kDwHkwS1mpunAs1qgWF9hHESQztr1iA3QLgJn5KTqGUL5JJfVVlC2xZGEGvWRkULK58K6I7Q5bpUIf6KUHwN366NbP63SpimLZxrrhDAuLe7h546GiJeEUJoRSGxTcUzZLBMV8XnvVZmf9enKVAV54XQ1eIgMLDuzKQnVnI5GCU60AEbPpvep3s+sUCrOoVGqq1nwmEzokG9usiaWQSQsnMlWKfe2iEtPixluQTeQZSeRShmRu7/HOwkFnGGv1CfkMoggb6fCmZaKspXDcy2GjPoabEk83FLviZyhZGPvdYRS8BAx4WGUVsE4bk0029Z52MtCZgW1Y8bqbcdOF2zo5fdE59913n3ymXXrppT253u/+7u/iBz/4gWzIkoUUtzgCcQSWJgIxAF6aOMd3iSNwUiLQCQBul3FlFpKUZ/5JShT9fXthB9EsQI8++qjUFr/pTW+KnL3ae3RCVJ+5wLLKY1i570coTE9KhiOKIBMppVUtieljBwQAq/pmAlYR/5nL2HY7ybQ2YsZViV4xV8f6ama9mIkh/ZfAnSB4sAsV3Hb9rM9++yJZzEz7mwBkCKwcHRGadNXMwrJnBfzQ+oSLZD4PBL/dZhuD/eRCfHJyKlQNdn0GWGq4HU+UlOszqr7XrCeexRwj9yDUfDL2zNBRFK3ZJkm7WEb9PYF6ocdKyirLmB1ZA4PZNd2W2uBOBKSC47ESKbEkUrWpzWpL5T0JKEt381zwc4fPQqvn34MOI52FVynCc2zuGEEzEvCqJ9fyqNGzsDCjrcFKZVAthcvuynOtJYVuzOe3303R4TnnVCr3qmU8qW3H8/oWod3Ty1fTdXiSRebnADc/dBxyB5HXSrjEfBmnm0dDd1OpRVO9nYJ2Su087AVsK4fxLb+Od994AdKJxpucYa+ljrv77rvFBaBXtny/+Zu/CW46s7yjV5uFUccUHx9H4LUYgRgAvxZnPR7zayYCiq4YZcCscWKWr1HGlf6HTz31lICcHTt2YMuWLT0FOY36SUsl3pcAOIp68EzRr/ul76/ulDD60g9RnjgiCqgEsazjjbIQ5+Lv2MS0LFCH0wvtYUhb9u1nOk8Hq7pfWviQ0qx7dkMqMWnY7EvjDFiUmW59rJ/9zkptpQ6fkk2AfmJsDLTKIZ2WQJl1po5Nj15SNj3Q+sQXCWtOteWGBK9PsBm2KQq6f/3WNdg1AOwJ7Zn3qxjMUlP5t3Ej3ZwUbv5I1tt1ZbNhKQEw+2rrFD4iqIxW99sqjgtptrqontNXN6G7Il7WSZMaZc+Sd6txq9WW1ovxdaM8HAYAz/eH1lGprHhCO8vA8qhRnIIAmNZMulCfw3+OyOaUyU0T1uBXGnpadzK/rc4h9TnpFmXz487ydjznrEPGpZDcHAjne+0BhmkIqJtwM+JnfIG1D+ebvgp/lKbU20WN35xTsW9XGqBpOLz2Blx71eXYuX40yu1aHnvnnXdK+QeFHnvR+L22Z88eHD58ONL3Wy/uHV8jjsBrOQIxAH4tz3489ld9BDoBwFS5pM/hTTfdNA8Qmd17/vnnRQyDNC3aIYyO9m5R0WoiCLj379+PG264IXSmmSD33+95BgdOTAOeg9ye/wLG9krmlNnbRKK5NUfzvtQ8cFODK6DBXSD6w2tLRqYDQEH1ZS7uuEhkLS5BBamepEbWU4mnpiYFxPcbAKs4MLPDjDTBI9e1U5OTsEwNqfyILNSrpYJkqP3Y5lrS+I67Wbxsjwp1m2JTKc0W0a7NxommPqCqHwoAM/vCmu1WLQiACdRY99vORojXU16ztE0y4MJ1qpiYmpZx9ZPlwHtLXbJhouLqAmR62RrVmfpCWazdpTCcE4kWzYyuY2WglcNlKjkWfoYo32FSmFVTysPKm7bdppQPgJ3QJRdGKifzqBkW3NLyEb9S41eU7vyKNUh6VdhOZxsS4muraSjBt0zrlWJ0/XPITa+MXkVlbg7vrW7D0/ZaZLQqsl5BPpvoTR50mJrQB2BqEDGr85OHO3q0lTgfGTK0uSYQ1p0qdK8xVXxq6EwMnXUtfv3SHR3dr9lJFInkdx+ZT902fk5RZIqbmi+99FKkDdlu7x2fH0fgtR6BGAC/1p+AePyv6gh0AoBpZ3TgwAHceOONQhOmKiQpz6S+UqSD4JcgZKkaRbYIvKlMGfa+9zyzDw/uOiiLbn3Pz5Abf0521/N51qQuzN5GGUfQAkishMyMUA+DVkKSrQAWiUc1u48SkiIdldfTytNCL+fcKWXqICggVY6/W2rBFKmZ1UzJoldcD8Ojq1GZHcfMbFFi20opmQkt+n3uclaJT/GslxQAnNBs5OcUly9LvIxhvXk9YycAmJn5omvNqVOHz6h6miFK0c7sBColUqBz0HpYx9roWYhueRT+yW3lA0wBNNZzplFG1bZD1bQTVDpC0w2fqVzYW2aHacXj+w4vrB025unSjSihUQCwZ7CelBsZPuCm/y8BlFbtb91s+JmBL2znuOK/Wi13R9Hm545lGai6QFVLidCbUmWP0qdmx/K94Jx4Ts0repe9Gg/bp4n681ptUjZyCOLp0cwMsOMBB70RrHTHcIn7NFZp43Pz63sPR2H1sF+KFs2NRlLdKX5WD/grySGcOO3X8P9ddz5y6d7V1fI5pQXQ6tWrxe2g20YAfOGFF8r3Gr93223+dHu/+Pw4AnEEahGIAXD8NMQReBVHQGVdogyR4lb79u3DddddJzWdpCBzkUq68+mnnx55wRLl3o2OZeaZu+PcKQ/jLfzykQn8x/3PSe1s8aWHsG7i0bma1OwCn81O+kVqOOmG+fzA/OnKSsjPkNYAAQENa9dasxk9EZLyqX0ZeMUJ8SRW9b4+4F/IE56enhLwsNQAmAMmTfvQ2AyqMJBFScSI0kkL6WyuJeX5JXsEv6pulFrAjFaWekATLkqwhB5JEa/1+gSuTb7QNBMcFQBTtIwKs7rp2zN10iiKNVUoCeU9l0lJbXAUemrYe5JaXtLTfVP2bQWAVR/FxsowkfTK8tw2a2LH47g9pdr6ysMEw74Vj3pnatlhHyzx30oMrr3ongYzmYFdl6UmgNKSWfGp7XWmPex8B48TAKxZGMmlevZsKZBYdMgoSYZiP4TpO8Em/cf5eaVayTNxe+VsHHCHhE0wrFEPYA4AW0mcQF4+Fzdqx/EG77E5q6Xa86W8pRUtPqxVGt8ZNm4eyIaV4QN+IvBD62/CVRe/DudtXhVmWKGP4fcgfelpD9QLz15+luzcuRObNm0Se6EYAIeeivjAOAJdRyAGwF2HML5AHIHlG4FOALACnKeddppkXrkwOffcc2XX+2S03bt3gz9XXXXVvPdss35MF8tS93t8bBJTB57Htsn7kJV638VAspOxBC2A6s9ndlSshAiE5zBrTTyqMaAgZVoEXowkKrOTKBV4bmvrIG5KcCFGa4qlbBT7Yo3z0emyALWKnkImoSOfToqatmVwLMy0LQTsLIv+WeUMvOSMIoMKBuuyvPz9IXcIg3oJF1r7sLOJSA6f5aAKd7Oxc1FZmKOPUyAopVVlA0RRbKPETIlgpQZGYFkJJLyy1BS3AohRrs9j5Vkx03CrZaHV96OFAcDqvgQSzJyjWlxU006wQt/dfmdQazY8i31pPY+bEBSBa606r6cG4JaaWx6RqcFaVqdS7CmYjzp/YwUbCaeAXGBTLeo1mh2vQCJBKinvQXX3qPfgBl1WKzcs8djrjOAR+zQcd/OoeIa8J/ysqBppOWelPoMrrd0iksXW3lva3/BoJwqlMt72nKAbWSoTw+dgYNul+M0rdvYcUJINRcEq2v1RA6PbxjjwWhTU+vnPf97t5eLz4wjEEYgQgRgARwhWfGgcgVMtAp0A4F27duHFF1+UoebzeRH7aFdz2c+4MPtLUH755Ze3rHtl7dn37n4aT+3ei6njh3HGzH0YThsCXHrVCMAIUFstvgkQ2IKqrKzx9WvjahniecVnGJidnYZTLi2q923U75MBgLnQpPDVdMVDYXJMMjp8JpKp9JxNlC+UJXWIpIY7NSB31Mnhrsp2HHYHxAqlkQ0trZymvDS2GcdxQ/L5htOlAHAyWVPhrj9QKVFTQKqayCPpliTjxNiziY+qqBGHo19Wq1TgLgjzgABaCWWldNYhurC7ED1Tfed1yw46zlKHebajAGD/en6Nb0r37YRURrYdqAzTl6jHLPSlrc8O+/PIGC7InllpsS5rp3ItACphoewlAG5cRRCfijqORscTsJVnJ2VDi7Zm/WjzINFxhRYN0S6IphhNmryleZLZbRaiA84QnnHWSolDweHnnYu85WFUn8V55n6M6M0F6JqphzdiADSKkcp4TxkjOL7xTfida87BcK73ZTp8j5ip5ebwtm3d2yqR1bJixQrcfPPN+OEPf9iP6Y+vGUcgjkCTCMQAOH404gi8iiPAHWZmscI2AjyqQDMDQ4sj+vu224UPe+1OjyMdm7TsSy65pKXw1i8e24Pb7n0cxdkZnFF8FKuSlZ73fXJyQsBBu+yTgECqCjsVUXP2IQV831zbmVM/pn2SjYnZiigtN6r3bRQzUiY5p6RALxVljpnq8ZkCCgWKQzly3yAF29UMuEYSRtXPfgc9kvc5w7i3shUzbhKrjcYZOdvThUK52RjDW1NPdQSA+cySHisQPDWAXGJOipZgznECFNuaABNBfdCep/7GBCYU+FIAmL/3hbLSMJ0ykiYtk9rR3Js/+YxTEUmfutnHpgDw0BBBVnjpbdZYaknWuZegmSnYXdX99maArIFnFpjewkFfWn5OEQgbVgIpbsxUw4M8AigjkRCfaPpZd17bHGWMGrREClMnjkpGlMrC/Ww+S0OXDK6/kRNWMZrPe0JKE6otqPHsO/eDjnl5HC9qogy/IQep62+06dV8rDX1cG5e0ftcNTXHZCXxJ9hocTa29dfxujN34MJta/oSSm4+0pd+69at2Lx5c9f34Hu5Zs0avPOd78S3v/3trq8XXyCOQByB8BGIAXD4WMVHxhE45SIQFgDzOALN5557TsbIf5OWxd3pk92oAE0laIqFUCimUXti9yv4x/+6VzIpO73dWIfjfQGHUS2IqOrscLEZsBIyuBA1dUzPFnGi4CDhFOe8bcPRtJcaAHPRPDYxKVkdLoK5mOWitFENMutIFdXSp39reLmcx92VbRhzM1hvcANhcSt7pihDbzVP4M3JZxoe0yoDXKmUpXaai/xUdgAJZv2l7peZ34WAL0i/5POianpr2WE/Q6zR+3keAGcWMQlU3WHSK8qGRrAuMsw7w74KEKmEV1IOc91Gx3QKgNW1NCsFzUzAK033rE6107EQhCjASCDMumFfXZoCXh48MwWdns6mOUd99+cyTON76WgJVD0NhgDh/jXbyiLlFjE+MSFZ1X4DYDUSjpH3q5KRYsxtbnjNa75tM4usVoqkbl8ozIqCfS+U6hcyAPw5ZuMmXNBOa3z15chsPA/vuPos+RzoR+MGMX3pqYWxcePGrm9Bf3tmkt/73vfi61//etfXiy8QRyCOQPgIxAA4fKziI+MInHIRCAOAuXAkwKQPIWmt69evYvIJtAAAIABJREFUxwsvvCDZX+5On+xGD2AKcTXrz/N7XsZX//M+ycRtS01hU5EAqj8LoLAWREXPwkFnEBWYsGBjjTmDpGnArM6C4liT0zMoVAHDLUW22GFGslwuY3h4KPTCvvM5dDE9NYMZ10LetKWvBODMzAwPN69BplWK5tkw3Cpcw8J/F3Zirz2ClfpUQ5GrY25ORLHOtg7hIuuVht0l2Bkfn0AymZB++M1DsVgUv2QueocGBuHM0V/9hfJiAFx/cWYRlRpxfUaRNa/8Hd8LZugbNaWOndEqcOg3G5IWbVCgqVJcEtpttwBYT2bhUkyKgmJWEm5xuvNHqsszgwC4/lKsS6eQHMHX4uywT5euzxzWX0MxGIpIwHNcGG74THLYoZFBkJ6zEmJGm/cMCuuFvU6nx6kxUtTNpZLynIBUvWK01IN7FaGSRxF/C3obd9rHZuc5Djc8/E0PNceT1kocHL0Ct1y2Hds3rRPg3Q/m0okTJ+S7iMJV/J7strG8h64KH/jAB/DlL3+528vF58cRiCMQIQIxAI4QrPjQOAKnYgQIlpo1ghn6/nLBQpGrc845BxR64i43ha968SXfbcyOHj3asD/MDDz9zLP4t18+iamKh81DBjaeuAes/+xXa2dBRNXhx+0N2O8MidUPqb2G5iKLimQ/L8gcw8zUJCrlMpK6i8GBHJKWFUlUSQFgLvKiWohEiQtBbnF2FrNuAgMpDZk5IbGwNchBm6gnKmvxvLMWx50UVunTSMLPJBMrsvZ32ktjnTGBNyR2N60V5AKczyaBKG2J+O+gV/LoyBBKWkYUb4M+wFE2Q8TXWLKJviJxMDvsi2g1qDedCyrBAseURhXlNlRRK5FA2fb6WvcbnOtuALCRGYBTWEhdJ3jn3HmV/lK3Gz2vVEFvlDElTTepUU3ap7i3msswdeBkalDwjEBYc+2ezRWZAwThrl2WcfgAWGsr8Bfl3Q17rIzR0FGpOpCNHD0xrxhNqr+nmfI51Y76XH8/5W3ciwxwq7FwjsuujucGr8a6oQy2jfibVPxcZJkK/XopFsgShl6UizBjS7uis846qyebw9x4vvLKK/Hnf/7nuPXWW8NOW3xcHIE4Aj2IQAyAexDE+BJxBJZzBJoB4IMHD+Lpp58WsRIqWrKmiYsE7nKzzolf8rRnONmtUX+UN/G9zx3AwVlg/Yoc1h/8qdQq9rMR/DXLftqehnsr27DPHcaYm5XMb0JzUPUMyQTTHmSlfQTnO08jkcxgMJuC6fn2PI1EspqNg/TCUqkM1nOyDrIfrVwuoVIqouwZyGUzSFm1ejvW2TLDFlaF2tV0FPUsHiysxRFvAONuRjYp6M9ahomk5mCFNoOzrENNFaAVoFEAmBlZzoXySh4ayIuNEK2o1LFhM8Ct4sc4+NllX9RHNVWLKDWnRnAOSGtOI6k70JzqAjEwda5JFWk9Bb3Pdb/BcXUKgLVEBh49cxsoHxHs0w+Y9HPd6cxmqpNnt1HGlLR7K5GEXW7tI90409+6Dpx1+8yAlrXFfrOd9J8lEUmvNP9skFVCijYFB09WU7Roitf5ZQwGoOmRqc+q/1G8mrsd87E1r0dy9el45+vPQrlUBL3a+UO6snpnuWnGzysFiPnedtLIkqIeBTeHqZHRbXvggQfwxje+EZ/97GfxsY99rNvLxefHEYgjECECMQCOEKz40DgCp2IEKJgUpK9xUcBaX9b8UlGXFKwgmOHCgUqXpHnR+/dkt/r+cHHz2GOPYd/xGeye1jE6NIg1B3+KRHms711tlf3cZa/CI9WNOObmsaqO6lt0dBxxchh0p3F+8jAuTB/GfH2wU4buOQtEsloNhGCGGwDMcPSa5icWQoUC7GoZDgzkBoeR0BZm1FUN8sjIcCSqeQkJPONuwKFyGiU9CVqJWnDEFul08xg2GeMt509lgCV75jqi7Ex7K4JhT0/AZZZuLvvfaQa4vgOqBpj3YF1wrd60kVetT6/lJhLFo0jFpu1Tda42ldeWLFQiC6880/dntWsArBuyweLarUX0dCp/J3JwSrPQWtSS9mrADSnDyRy0ykwbz+1aD/w6cD/L36gOXNUPK4aFogyXbcBuQhcOM75GVkLUFWAWNpc7eQDYfzZ98TqWktC+yND8LDrVtKM2fk7yeyaMWGDUawePn8lvwdjqK/BbV56BdSML48fNMX538PuCm6j8XFON9db8zuMP/x6WSXPgwAFxJGA5TtgNwFbjo/XRb/zGbwj9mTTouMURiCOwdBGIAfDSxTq+UxyBkxKBIABmvSTBIxdd/AIn+CUIDjYuXu655x4R56DYx8luwf5w554LkJLt4fmZJKxkGqOH70F25uUl6WYzASrSQX9SORMv2qPIaWXk9Rrt3LFtuI6DGSQxaw5jm3EMb0o+C0PzxVyYIRVVYXtWQDCthPhnNaB+GhxcsViQrOTg4AAMY6ESajdB4ILVz9zYsEwTicFVSLiLF7/dinAV9QxOuFmhleYNG3l3OqRKrIexsRpIzuWySCSS0teSq8Nwg0CNli3haoBbxYwgieJajWqAm3nVmqz1nqNK62YCSKRF6Iw0UjOdhV3kQrxmh9XNnIU9t5MMsJnOw45Q62taFqpaEqjwOe7f+Oopw2LX5Jaavi9hYqTqwDnfdoC+TmDke0j7qsOGboC0YT5vpAwrxkGoe+gJYQbwXsGEug+ADaH1L4emsU5a16C5DlgjTEaD7lYjUcBb1Wn3aowU5zq48ddw7tb1uPbc09pelpuGCgyTSaKo8pxXCvqpDHEq1dw+iZvG9KS/6KKLegLuaX307ne/G9/4xjfwB3/wB23HEB8QRyCOQO8iEAPg3sUyvlIcgWUZAQWAWb/0xBNPSMaDNg7bt29vuPNNkHznnXcKJfqMM8446WNizetdd90ldVz8ezqdwUvlDMYLNvLjz2D4xK+WrI+sbSuXF1sQzbgJ/Lh8Jl5xh7FJH/Ntjqi0alfhkTqradCtNPY7gxjVZ3C2eRg5vYyV+ozYhLA5uimZTLWopmhWpW6xzON80aeiZC7aCfqEDQxB7/T0jGRtcpk0tOwoLKdxfWezGIS9lzpOCWXRa7beI3nxtWpiV/ydGjszVgUvCXNRX30AzAxWN7V/rQBwsI++Uq2fUeSf8765OpVqLbBmNp3NA3a5bUY1ahzDHB8VAHfq9yvKvIkkSo4Bw+6PunUQANOfNmXqkaze2sUrqBIe9JDmeco/OpPmpqGGisaaU61t6YVsxRhJJLCYFk9rNW5kLRcATMBLQTd+fjmeJ4rvYv1lZmDYpVBZ/r4Le2kajqy9DtbIJrz7mnOQsKKVgii/cGaGCYrZX9W42aXAcL2YFkWr+ENLvl5Q1r/zne/gfe97H7773e/iHe94R7tHM/59HIE4Aj2MQAyAexjM+FJxBJZjBAiAd+3ahT179ghgOu+885raCbH/PJ7ULNo8nH322Sd9SFygPPjgg9IPCnWNaQN46cgk3PF9GNn/875mm+oH30yAatpN4vbymdjvDgkA5upRqJWuJ/RG3Uqh4Jp42RmFDlcyxFQ9Tmo21uqTuDTxMkZ1H3BSTImKxyZ9OueshJiJUa1UKqJQ6B0A5nwT1BK0DQ3k4CXy0N1yUx3tXopwERg4ZhaGU0TK1ESMpz53GBS7Ygz4DBMAMxNXoY1LtR5oEfiSvunOUf9riuBRwbDvLTyDTCYt2eawTWWH+Qxwsc2MaNVIwTRMZNMJGJ4TmnYZ9p6tjosCgGkjxGx6sOY5ah/IYuB1+Nz2ui6/VjM7AD2RhlcthKY+Rx2HvI8tPKTTZM/oOpDMQydduIlidCsrIdJ0mWXmBt/Jbuxnwi0I+GXdM9+XBDcY5ryulfUX39dWYoP9FvaaGtyJ8RUX4W2Xno7Nq4fmw3ZwsoTnDs9gsmjLtKwZSOLMNXkMpFozZfieqtph/ql0M+rFtFgDzCzw5ZdfLqyQbhutjz784Q/jP//zP/GWt7yl28vF58cRiCMQIQIxAI4QrPjQOAKnWgS4eGM97/HjxwU0sHap3Rc3z/nJT36CdevWCVg+me3IkSOStWaf2P+Vp+3Efz70AszKNNYd/DESsCMpKHc7lpoA1UIFZqo931Y+G3udEazApPj+chGZoJ+sYWLKTeGAM4gCFWXhIa/5ALMCQ7Ito9osbko+g1WGXxsqwNDIQHMrMDxSkinC5InFDql8BDTMQHQq5uLHgVlVijwVZaE7NDgAmEkBE7rnK+k2ar0EwOr6FDFibWXSLcLQtHk6q8rUKLErgnWOmWNnRt2pluo2QHzwy7H5dcDNZ5w1j+0UojsFwMG7cgxV1jzPjvvK0qS9GylYmgfL8Obo0lyg98e6i32p+bIOtrwPAQ7ja1ei1302ijQz9CUtBTik0LauJQ77bpIyTGCSHlkjProUblqq1ig7TLYHN6pAQbrUAJKGJ3OrGssbkqBF1kLqs/r9cgHAVICmdVnS0BZ9plINWzac5ujhvmJ0cwp4P+uaq4lBHFz/ZuzctAo3XbBVwsiNlp8+fxx7jhdwbLqM2bIjZRVDGQsrsglcvmUIr9vAZ7998zfcZufp0hyL2gwiVZ2fRRSN5GZsd5+/kNrfT37yk7jjjjtwzTXXtO9cfEQcgTgCPYtADIB7Fsr4QnEElmcEaHPEL3XSmcOIJvHY22+/XVQuL7zwwpMyKPaBXsQvvviiLDK4Q58fXoHHj3uolEtYvf92JCoT0rcoCsrdDqaVANWj1Q14rLQWs66JlRhDSnxjNdiaiRerI5hGCjo8DGsFDCnas6dhzMvC1FxsMsZwS/Ixnz491yiUReEcwynA8DxQkXaa1kSz3QFgtcgjoOQzkc/nYJmWUDrbZeyabQJ0G1ue7+iWWK9wU6BULmNKxHQodpUWwSvW7jEDPDI6KnXghhsE6kHwOyc4JUCYV+4MDCsAzPvX18qHHa+ZysIuqbpfMgN8mjSz3Y6RnNss8ebptXzew4ryhO1DDQDXsmWNzqWqs1PqrUCXsBjmqOrMkOoL5izsCGrHCSCxMlg9mEal2nyjJvqVo59BMTYlpAXX8SnDngbXzCBtOGBddMJKwNK9pkB9OQBgf1MmiYxuo1x1mgaCGxpBr2ulGG06C9W3+1XXzI2yw+vfBHNgFd597TlIJyz5bvvvZ47hiQNTePlEEaNZC0NpSzYLj89WULJd7FyVxfU7VuDsddGFxoJiWnROCPpL///svQmwHFd5Nvz0Ntvdr3S1W5Jt2bI2r9jGi4xtbGwTkg/4SQiQ/BQpkpBUipCkshEKSFHUl1QqIVWkslYRPrLxhSSEn4DBeMHyvi+SbEu2JGuxtruvs/Xy1/P2PXd65vbMdM+MriTTp+picW/36dNvb+c5z/s+jxLTorq0LMqRdo7R/vf//t/gz7PPPit1xedy46LAf/3Xf0k2Fn+oJcLvxxe+8AV88YtfPJeHnowtiUBoBBIAnNwYSQTe5hFQaZhxTvP+++8XkQ/WOi1140f1pZdeEuVOjoFCXQ/9+GHsGfWQ6h7A8pOPIDN3XFKFzfKsrPQTM7IOrNHkrRPnoepvawWoOAk7NePgUecynNaXwzMs9GpFmDow5qRx2u2BCx3dKGKZMSvpz6pRq+mk24cVxrSwwBcYPrAPNlczxZ6E9cGOXcLszCyyXRSB8n0v4zRO2lnv67OqTL2kF7GJOaQjifqcSRVqdR5ztobZmWlktDLSmRxS80JtBMAcKwW6gpNupXJe8e2tx6aq9OjGIk0qVbpdAKyblqSSwgkHarwWJceD7Rooz9EOx49ARXzJV5Zut0UBwF6qW9LJZbxnoDEtmtY6BaQl5b1Vv+7xqWmkLAvd2cwZlNpqJQAeHKYKuzbmCkXYribWXL3IS5q0qh+uXoT0MDExKc9xs8ycVkYUdR/b6kLamfOzE0Q8rn6rqEWrEgOWMWTE1NuYTwH3Qb0p75ZOtonByzE5sB13X30RLl27TLo+Op7Hf790EntPzOCylV3oSlc/LycmCxidLePq9X346DvWwOJ92GKjby996ckA813UqpiWOvwf/dEf4atf/aq4MtB14VxuBLxXXXXVoiEmAPhcvmrJ2Bq+y7ygP0oSqyQCSQTedhHgJD64ah3lBB966CFh3VjrtJSNzAEZa6b5rlu3Dlu2bBGG8i/+z3/hxCxwWWYU/WMvLwyJoJCTasVainppIIW202MPE6BibJXv5Yw1gN3mdkwhiykvB8cDptw0CrDE8oeiV/QGrm0Tbha65uF66xCuTx2uO2wyhmRGi1NjGOzvlfTqOHiFiyEcK1/7ilVNmWYdIanwYZxZAFwRu9J0Ddn+leiyIIrRTHWdmBiHm+rBYE5NcivMbnPwW3s+zdlhPju+8FprDLCe6YJbiCYGpVlp34JmdkoYYgVECDh8VWkqEbfGDjcDwI6RQlqjkNeZZVQXFqpcjS7ZIeJlzZ/YkekCcrqD3DlQM1tvtAT79Hr25iYwW/ZQtD1Y84rq+rwwmvKQZr3s2QTATNHm+1PV+ja/Av4WPEdmq1T0CSiUlZFndWpsuON1zcXMcpxccwcuWj2I911bcSe4/7URPPzGqNSNrB/MLho+3wsvH5/GRcty+OkdK3HJitZrrVmOw4XZ2267TY7TTExL+Q7XimmpQX7mM5/B1772NdBeiSVH53KjhgjZai6K8+d73/sePv/5zycM8Ll80ZKxNYxAwgAnN0gSgbd5BFoBwLt27RIW6uabb16y6Bw7dgyvvPKKHG/r1q0CgNn2vzWKv/r3+7DMG8U2d3+o6JUoCrs2DK8s+zBVrxyioNzuydTW34YBSqq9vl5ehhPlbqnxPWQvx6iXQ69WQBfVVUMaa4SptHqtdRg3pg42HCYZ8rG5Mnq7suhOGTIRVbV5jXYsFguSOk1ARWaGk27W9ZU1C5pbhhYRSZ8pG6ag2JWflu2nFIpfspVDVivi1NiUpD339fZWpTX7as88+3bqaBezw6VSUeqkaY2iUqCjCmmZuV7YcxV12Sj3Hs/BSHdJaq9XmpPU/1prHoqqKZulqOxwIwDM+y6VzqBcqE5jjTLeVrcREGjoyDsGWHdcm0Jbr18ylcWxE2Aq7Lmimhw2VtbTso4+Y+mS6u56LgqugVK5DBRnFwmM8X4nAxylRKXVmIftx9gzlhndjfQOCeujNi2a/PHwVBFZy0VPdjEgbWX8nm5K3a/RNSCqz93ZSubLt54/gccPjmFtfwZ9WSu0+8NjeaQMDfdsW4FrNzQuA2g0Pi7O0uLplltuCd0sKKZFoMx3NVutmBavNX9HBWgqQZMxP9Oeya3EvdE+f/Inf4I//MM/TABwpwOb9LdkEUgA8JKFOjlQEoGzEwFfxTQes/P444/L5HsphDm4ik7gSwBMoME0KzUZmJgp4F8e3o0jr72IS6aexEBPfeVNpShMASoqLYcpKLd7BRSIZM0s40o1ZgIietJaVkqAN2tTPYrGzNc67ipuwgv2OnietlD7WzuOYbdb6l5vTh3AFdZbTQGwKBN3dcPsGoBhc3Lnq7ay7q22ERyStaWyKSddHLvyDzZMEyVXg+H6CwdR2pmwYaoVu6Iibi3QNNM5nBgeR04voqu7p6q2t33wW33mjBkXO/jjX9/u0Po+dVzbo7SZJqntkpKf6hJ14lj0fGAIFB3y0qwdLkD3aKvkLdgsibr4/GKF2A6Z5oJXraaFp3c2ZIDT3dBKswt9RrkHOrUN6/d5KkVQYZuK0RX/7NpjMPshrdsYHRk9p2yDFj1v0OAaqYV6WoJ9pt2q8gwyrq5TgltUCxyVdzOvZ8V32GrLwivKNaLlUcopyLpRs9TnRv0F1aLZDzN5rHQG6Z5BYZdbTXdXxxxbfi2m+y7B7ZdvxPYNQ1VD+c8XCIDHsbI3jYFcOAA+ODKHXMrAPduG8I71rQNg1urynRBlYbiemBaFHT/72c/ihhtukO8yBbCYBdCuoFaU693JbRIA3MloJn2djQgkAPhsRD05ZhKBJYxAKwD4qaeektTP22+//YyOlGCKtUWcMDFdjPW+qq6VKa//95G9GB4dR3r3vyHtzKCvr/nkhYyGpPXN1wd3UiSLIJJxIejg5IWMDcER/0s2lQCUbHSQ0Tru9IlC9GmnB0P6NCytWrW24JkihEU7pJ/NPI9uvbFaLgEQWYiurhzS6QxczRBVYdYHp63KRJsXjsCSYJlj5ZiDQM730GUaajzmr9MA2K+zrRa7qmVyyXqWkML06EkR61m+fJkwpPQojcrIRr+RqQI7J+yNf30Jxn1gGawYotjRAXcFXnNWY9TtlvrJbq2ES61hbEudQipmXMPGx7TnkpYRMK17lfuGzzTvA/4Eyxs4XgWg1CIH+60HgCmwlvEKS6qkXHuewZrSspGF7pQWqZCTpQZVh70yRsfGz0h9afT7o/GWtBLKIg/bdqtqlBXY53tNfHWNHFCexcykb4PExSmy/bynVaN4mGL7O80O89rznUHtBLLUnWhS5+15GBkbRzqdEs/2ZorRzY6bz63G6dW3Yd2yHnzghs2LnvdHD4zhwX2jmC3Z2DS0OL2Z7+SX3prCllXd+MAVq3DBQOusNL+LfKcSvMZtSkyLFoMUjTp69OhCF9dddx3uvvtu3HXXXeC/o2Z2xB1DJ7dPAHAno5n0dTYikADgsxH15JhJBJYwAq0AYK50U+DjzjvvPGMjZYoYxa4INC666CJccsklVZObB148iJcPncLQiYdQOvGagA/WUkVtSlGYAI+JsVRQjpIq3Kh/lQLNbZSAFAEY+ydIo+ULJ5XBRobr3uI2HHKWYcrLoFsrIgOfcSUAnUUag9osdljHcVOT9GfuowAw0+jImKumzjftFYT9LpRKInbFCRvTd7m9AoucjOexeKxRYlvxIe6RyXk7jSnGMzN+jSyBZpjXLmPrpXuA4rSkCvIcmB7tELyZgFPMd0wMid7BHA9BufJmXQywPZRdHfeXt4iv84yXQdEzhQGmLVeXUcZyTOGe1G706vUZzahxkxpgKw0ulBhklWvOls+FSpUWm6UAO6zqhhVYDj4/rp5GxnD9tNxzoCmrnaLjyQKW7uQXQD/BWg5FeX7PBdXkeuHi4hdrfbkYFmbPVCsgZXvA2FQB3WkgN/8s83klEFZq4aoywWeH/Tpw3pvtLPy4LHugD7nBGl4f/LIMY9jrAS3dKD63Sp9EOkSvoNmtwvHPzUzDSqWRrno/pWKlu/M4XOw6vu4e6OlufORd29DfVXnfqXGMzpbw788dx54TM1jTl8aKnopfN8HvoVH/fXz1BX34uatXtxU3ZkYRnBKkttvocvCbv/mbeOaZZ+Q9zkVgNj6jd9xxB/7xH//xnE7zTwBwu3dAsv/ZjkACgM/2FUiOn0TgDEdAfEhjTnLJyp48eVJWpNuZaIWdGifob775Jvbt2ycMG72G6akYbPuOjeD7z76B/pEX0DvxiqSIEcgPDAzEjpbUB3u2pPmaBsFqxWc2Tmd+ve+0pGwS/Po1iH7NadoyMOvVV1EmePlRcQuOO72yXRG+iFNGs9GjFXChMYp3pfbDDPiH1p1k22VMTU0jl8sik1nMZtBPt1wowJ4bh+16AnzJFKtGcEwFbdj+wkDc1hkf4orYFVNEu7sJpsPVjo1MD5zClMR9ampSGDLeN7wGppWCmesT9Vkn5j1ee95KzCy4YFAvNrtKl2C3vQYTbg7dWgFZlKQ2vailMe2mBUCs1cfwgdTzMOavabvPEdk11peXXEjae73mOPaCNU+t+B0n2sI2miZMKwO31LnFg7j3Ub3tVU1pGcxsSIu6cE7zwS9bKwCYKepvuQP+MwoHa4xJWYjqZGMmBt8HWdNrqkZPgEzAny9ykWoaVrYb6a7eUBV2Lsb4CxzVYob12P4o58Tn3/J8L/Jpx8Juey1G3C4Rw3OgI6XZ6EIRFxjj2GycXLiHo/RNZXO+n7KZDHp7uuS6BeUFahWjG/U5vPImzHVvwE1b1uGaTavrbvrkoXE89eYE3hielcW/vqwp2Tjjc2V0p01sGsrhp7avlDrhdtqjjz4qgnidsCzid/Cmm26SjCJ+C2kt9MMf/lB+hoeHQdGpdt8Z7Zxrs30TANwsQsnfz/UIJAD4XL9CyfiSCLQZgVYA8O7du0WZkivRnUzH4iSOfbMOigDyyiuvXLTKPT6Tx78+vAfm2AEsP/WYnD0niWRFBgYGW4qGXx+cg+4UoXuOpP2VayZm9Tv260H9el+/rFOlH3MfqqfmXVNqNRsJSXESfshZjv32Sox7WZl8MiX6MvMULtDHFyxwmp0gY8gFAV/FuRYAE1gWMJfPSyr2sp4McikzwHx7MFJplMuOxKGV1i4Arid2FTYWM5VBqVyC5vpjJRBgGjrvBTWpJoA2rDQyPf0w3VJLdbfsj8wvx+YvGFRYpNpxzXopfDN/LU66vRjQZyssGdOkPRfMYB3xeuRvd1qv4CJjeNGptVqzrFSUC15KhJUa1czyoGS0ySSStQ/WeJJR7TYcsAacz/e5NtFWNaV5V4emG/BgQC/7mQJxADDvkb3OGuy110r2hQ1dnjsuWFxgjOGd1iGpve9EI2ud0XhvVqc+N+qbEm8Tk5MiQsbFiXq+uqoPdT0VIA6vBW/MDjNF27Rn5R04WdLxZPkinHJ7MellkdVKUsfOjAYbBpbr09igj+FK80iVP3mjc+KiC9/XPB/+cOGG93vZDpZ+0N88C90ty09Ym+3eiJGVN2JFfxd+7qYt4HPeqD1/dBIvHpvC6EwJMyVHxks/YDLCOzcNYF1/66nP6rgPP/ww6P0bZgcU9x7itVPfP2ZCBZ9BvmOD2T1x+w7b/gMf+ABeffXVWF194xvfqMt2JwA4ViiTjc/BCCQA+By8KMmQkgh0MgKtAGCKUh05ckTsHhqBgTjjZC0qVTS54r1q1Sps3759EbgmKP3mI3sxefoYVh67bwGkkXktlcoYHCQD3Apv6Y/Una8P5gTQoICQoQVG/L4xAAAgAElEQVRsPBafjRIy8etBdWFcOX6VfiypjpJMaMQSkooTt9ptyfBNThIAZ6TGrjI5Zu3qjMTJV1HuBgxL0kmzKMBzyZz6YL0ZcGo0vqAQGIW/4rQoYleqP03XpX4Q5cKC4nMFODLt12fGgj7XVItNZXJSK0qGWNfJyjVuBNQUCeMElOJbzcRoXi6vxa7yJZK+vkxXFke036rUCJMFJpO2zTyOu1J7GmphtQKGRUVZ18W7OaxmtvaMec8yTlx0mnNNWOUZFMsV8SVfSMtPr+10rWmz+Nf7Oxet9FQWaa+MQtn2sxY8D9NjpyJZ7BD8Pm1fKOzmuJeDR2YTNhxmgHgmevSCpPnek9rTNggmqLScWWF1w1Kf652jAou93V0wLGvhPuEz20wUjH1yMUylSy+uBfevZ3ABk0JiultCel45/kV7HfbZq4QZX6lPwQzoE8x5FobdXqzWJ/AO8zBWG36KbrPG9xNLL4LWYWrhhiA46DMttdBmDoZNoazKghx/x9Rnzcrgwzu3YnlvffHD4HiKtosDw7OYyNsCgFf3ZbB+INORBR5+C2gPuHz5cslaarexP5b9sPzniSeeaLe7pvsTbBNox2k831tvvTV0lwQAx4lksu25GIEEAJ+LVyUZUxKBDkagFQDMlKxDhw5h586dAgrabUynJvPLsVx66aXYuHFj6KTkRy8cxCsHjmDVsXur0gEJnglCmQLdCbZKhFk0UwSgmHJpu+4iFdSgv6+q9+Xvguwr2Y1jzgBOljPUnZZ05o3G6CKhq3bjF9yfY2C9GBkCAnG26rGm5JoF48RzNVIZvzYxP93WcJQQGMGUEiyL0mEUsatKPx70TC+cPG2EfHuiRtedaZcKDBMYw0wJE2vBr+VVwlC1iye0dCoUfHVsJWbW7FyeLF+IJ0sXSs1vr05wTitqXRYYVGPK+4yXxqXGaXwg8+L8rxfbLIUdK879LQJvnoailoHhzNXNQFAAuGdwCBnDXzwI1poGSySCPrVnkx0mqMxpBRFoIltJYMm01uHJArKWhp5cfZaecT3h9OHe0nYMuz0CdpnS6y82AGVPx6jXjS6tiO3GcSk/aLWxlpo2YhlLa5r6XHsM/5nwwWIum5Xns6JTMM+SOmXJLmnWGtWC8zoSYFNJnX7kBIdzjoGHSptxxB2sW+877uZQhi73MT3Ko7TgOdUunjI9mfesUsRW/dGOianRTO1nKcGpVbeikFuNay9ZjRsu8+3wznbjO5YMMMt1tm3b1vZweL3Wrl2L66+/Hvfff3/b/S11BwkAXuqIJ8frdAQSANzpiCb9JRE4xyLAD63yI4w6NNYfUaTjxhtvlJSvVhsn2eyHYJpgiSrPVHsOa68eHcYPnn0dK44/gEz+dNUmnMATeA0M9C8o8rY6puB+rJclf8vUWQJhNTEL8/cleKqwr1nYXUP4UX4zTtqU5/HZGyo8syb0KusYrjCPRU5rjnMuBHsTExUAXG+swT458XdT3XDtskyyLbeEckBtNs7xWwHAUcSugmNg3a8dEfzWjr1iGVRGybNglwowyPtRTGpeQIhsJ1PaGTsq7RL81rMQqu3/+fJ6PFq+GCXPxIA+B7D2syadnGnSTCPdYp7E+9K7Q8Lrg2EF7uvFPwo7rISVCo4GR1d16NV2WPL8lMsYWL5K6qXDLG9UrWmQUee4gjZLURj1OPdSvW2Zvp/yisL4KsZQ2ZqdHhn1BZZ6h+YtdsJT+QnuyP5yoUKuU00reQZG3G6s0SfxocxzLbHA7JuLaRmdTGz01Gc1lDCwuNhXV4djZhuea1gca2vBi3oGabeAdMqUe3bcWIanvUsx5nZhrTEReim4UMDaaS7q3WntjZQGrcoJuDhXb4GMtlC8Q2vZcsZyuvdiTCy7GoM9Wfz8zq2SQn0uND4XjzzyCNasWYPLLrus7SERUA8ODuKnf/qn8Z3vfKft/pa6gwQAL3XEk+N1OgIJAO50RJP+kgicYxFoBQBTpOq1116T1elWhKcYAoJuimmNjY2Jry/rpurVNY1N5/FvD+9G14mn0DO5mI2hjQuZOipkkq3rZJOJGNPwnCJSuis1tNMzswspscFJnGJf7dxyfNe7AWNOFnnPQkpjwqsroIgkU58+h6uto7gxFY01iXM+XFRgHSTZFaar+um7rEuuz8ga6RzG8y722KuFGXM0C0PmHLbrR6QOOU7jdSVrVU+1ubqv6GJX/n4eNCsLp1SQelq2OIxo2Hl48FBwTJTmJmUBINiYEp7LdcWqc6dn838XrsQptwdDxqwwakG4SWA74nWjRytip/U6Lm/i66xUnblf0GYp7FwaxUKpKBc8v2bWCNgwEQDP2AYuWJZrmPKvjqmyRlStqfo9n72KTy1Fy1ovR6h3z7n6vECc7oWotnuYnpoUtfB0JiuqwkyNNuzZRSP518J1OOoMYpk+I89nWDvt9qBXK+COFGu1R+I8BrKtSn3WWVoRyACI2lEFLGarFNAratEVASlPM+EYKRj2XOyol/U0vMI04DoivMX7bBgDeN7Ygmm9RxYBeG1r7y9afZEhpkhfVADMe0aViTTKEFlIi3Yq2Tdlqxcn1t0NGCY+dOMWrB6k0OC50ViXSxXoCy64QFKX2218hxJMf/SjH8W//Mu/tNvdku+fAOAlD3lywA5HIAHAHQ5o0l0SgXMtAq0AYHoU7t27V9Quh4aGYp8SARrBLycNnDBs2UIRk3DgKnW/u/Ygf2wPlp1+MvRYBHnsi0D6TNUoutAwmbfh5ieRsUxku7oW1ZASGExNTuCB1E4c9FaDSrX92tyCSipBTB4pzLhpmXj/P5kXsdKIBzCbBZtCOOPjE1LPS0VkxpX1vkHf1yqQZ1p4orAOTxQ3ipUOQTob6/1oc7IldQp3mHsjp20rAMw060b14XHErvzxEqoa0EwDbslX6G0X/AbjwHrl6TKQH2d2QTVD6lvMqFTp5hYz3y1ejjecIWGYe7W5BYBFwDDlZaXOlDWVP595VpS+47X22GHFINI/WNWRTuRdmPY0unv64g1l/rooSx6VOq06CdrydGphivWvOb0Umk7Me4rp/wRWA309Aub5uzCv2X8q3IC3nH6s0CfrqquTAaYI1rutV3GJWZ110ixQyu+7HR/dZmAxTEAqrq8uFwm4QqZ7rIv1gToX8sZKBh5zNuO4twyrvGFw2YSvaF0zoBu6vFeYxk97JC4ORE0TL5dL4qMdpZ6eMWbKPRlh1u+eWPMelDLLcPnGFbh1x4Zml2BJ/05QTx/gDRs24OKLL2772BSCJJD+5V/+Zfz93/992/0tdQcJAF7qiCfH63QEEgDc6Ygm/SUROAcjwLTVOO348eN4+eWXRaWSglVxGsEzRbQIKlgrxTqnRu2+Fw7g9X2vYuVbP4I2z/rVbp/P58EfpmN3UpVaHSdYQ2uks8j1DiDjsTZUq1IvJfh8c9LF94xbMYYeLNdnQi1CJtysqKleZR3F7enW6wvD4qZYaP6NAITMbz3wwXTnZ8oX4qH8hZj2MsJS0+KE7EvZM0DNV9rBbM2M4mesF0VZuFmLAoDjiF35x/NBn5Hpgp2fmU8d7yy7yIn53OwsPMOCletFl+HAtiu1w0ERoUrab7go1KSbwb32lThVzomAkKZRYsmT2tKsVpZFkTvSr2GDMdYsnE3+rsBwPHZYqSgTVJSNHEozE7ALs+iL4aNdb2B+vXXFmkdtV2GHq4WX4gSAjGrazUvacxgbHgTATLFVdkKqblaEsujJ7RTw7eJVOOgMiYVSV4jSs+sBJ91+DOlT+Kn0bqzSWW8erSmWOq3Tszd+6rM6SjMArLarTYvm73muXMYxHb8OvV7zRabmpI6a9dTB9kT5IrxhD0msB7wJqWNX6urMaRkxlmFAm5OSjk3WaKTg8P3ABcuoAFh1WlhxBSaXXSGq+h9913YpSTmXGrUfnn32WRGtooZFu+3gwYPyff3MZz6Dr3zlK+12tyT7U0n6xIkTcizOEfit5/d93Tq/Tnv16tX49re/vSRjSQ6SRKDdCCQAuN0IJvsnETgPIhAXAJ8+fRrPP/88duzY0RTAqtMngKDNwrFjx0TUhSnPzeqH9x4ZxgNP78aqYz9o6G1KGxfWbPb20i/W6mjE69b7GmnfFkd3pFaNk3IyFQ+N9uNpY4d4vg6G1BZycEXPwLSbxQZzFL+QfaZj41U1gwSYBBxkxBuxpOVUL/5h/EowbZfAl+As2AiCCYwJ2D7c9RLW54pw8zM1/Gj18Bkv2pwEraCCW8QTu+KePsjzRa+oNKuJgjLBehPnk8hxZfYAF1AYK9b7ZjMp2HpGVHQNWieRK533y1ZiWqrzWmDHPlyrS1SUnyxtEGsr1vuyHpTp0GuMCVxjHsEqIzqginwi87FqnirNHjXJEvDMNOYmxzFZ0rCiL9PQqiv6OPwt/XprBYbLC7XFlXprX4k4CjssqcxOQUSSqi1zKqOqBcDqLwRLIupFVDtvebanOIQnShvF/miFNg29xmObzCb9uJneS79mJZAVJQa08LGc/AKjGmWfsG3isKVKQKrWV9e3E3JE3bm22UYOpjMnMQ0D6mTAnylvxAm3TxbFerS8KO8XXEPilvXmsMobxdXuK0jrXqT0d9b7813N5yzqYmUpPYgTa98jYnI/c92l2LCilUyFVq9CtP2Y1cRvIllbZjW127jAfPPNN+Nzn/scvvSlL7Xb3ZLsT+B/+PDhusciO87yqaQlETgfIpAA4PPhKiVjTCLQZgS4Kt9s0hw8xOjoKJ555hls3boV69evb3p0ggtaHHGVXNlENFMIHpmawzcfegmDR+9DutC4/q5d79nwE6C/b7UFTu2YxT/YyMHwSsgaHmhv88yIhSfMq2BrFvr1fGjXZAIn3BwuMMbx8dxTTeMXZQNeQ9ocKYaGytTd3T11dzWy3Xhpqgf3FrcJS9mn+bXCtY3p2gQH11hHcFf6VSDVJaJRUocb0hQAVlZQwU2UQBZ/F7VGWGpfzQxKto0D9nIccZZJqjYbbYYuNEawVp+IBVAqY/KEjSoWfRsrxkuBMcbCMk3kkYZm0x+6kqocBHa1olBeugf9KSJ2Q/piDTgXGAiA+7R83XsiyjWOt03zVGmCI7Kf9DjOF4roGVgG18jMK6xXp4HHO3b41lwE8+uG+VNhGxl7JUAWBoqYputpOrKG21BJWQHgdDpVZQHG0SjWW6VF52Hh/ytdg5N2N4owRJwuLTZIujwPZZhYrk3jltR+bArxaq4XD+Wjm7Yqonmtxq4VtlTSopnBUSViR8XoHHS3CN3172OmStNaiAtJjeyZqAnwsr0WM15GUp65h6XZ6NWKGNBmcAUOwLJn5ZoGxdOCWRLBBQ6+A/g9iAqAaV12fO3dsFO9uGzdMrznqotaDecZ3Y/fRNoIUQCLtbvtNlof3XXXXWAq8e///u+3212yfxKBJAIxI5AA4JgBSzZPInA+RiAuAOZq95NPPonNmzfjwgsvbHjKIyMjMjHgxJe1UZs2bWpau0kW45sP74F34EF0Tx1sGtJWlIcbdVrr70tg1Ki2mMxE2epG2pnDSyMaHjCuxxR6REAqHFTScdTAZvMU3p95uen5Nd6gIiTFST7rfaempoWN6ekJB8C0PLLLJewqXITHyhcLoxuWBsrjUg234FnYZA7jo9lnZSiSWpnthVOcFeGcYONEmMevBsBxxa4qKb2OZmBOy+HR/HqMul3CPLFOmaA8hxL69Dw2GKPiRRqHEeY1ptAMGUqVKh7GljONVjcM5L2UCEdpaoUhcNIK2BUcT3x3WXvNxntG1Q5TTfpMiEJFv3mqbZZYo0qOk9kLFJFjHJYPDqLsOKIWTcDZLH02+rEXb1lR4/YZYrUAx2sQVJam+jbBW8YriM1Ro4U6liDQA5u158wyCWsEiLxPCITH3BweKG/FqNcj/ses2WeqelYriS3SO6zD2GG+Ffk0xUfXKSNt+v2321oBwDxmmICUPLfzPue6k4en834uIApQ54LTMXdALKMIgDMoY40+IXXsRoA553NQYfwrC0Z+loTP9nMbLljy3RRFr2Fs+TWY7tuMXNrEL9y6AxmqVJ+DjVlRe/bskUXhuGVBYadD66MPfvCD+OpXv4rf+I3fOAfPOBlSEoG3dwQSAPz2vr7J2SURkAjEBcAEDo8++qgA2nqKl5yoso6JNkec0F5++eVYsWJFpIj/4Lk3cHT3oxgceS7S9vGUhxt3yRrG6ekZmagRvPgWOI3qTT0YqawASk4wT47P4D79JhzXlguj1K1X11dTCGnU7Ua/Poc706/hMvNUpHMM20iBOAIITiYJfmlFMz4+LvY9PT2LLaoI1oX9sYt4pHQxHi1tAqfq9QAw07WLniUiQB/J1lwP3QAVpGlJpM1rz3ICTKY/l8siQyVezxNmulRSY6ywrOEnHqxrBbx0N+6bXo+3nAEUYEo6dhZlUJRMRHiQxQp9WoDKNtOvP2vW1DVmWizBEsfaDJwyjbbk6bDpD20vZvY9TYNmpoFyXs5VpUoHgR1BAMW2+N+otkrNzqWVv9P3mfZeBlzM5f30b9azE5RYhiHp/ASbtNdh3b1KA2/lWFH3UYsIjFuw3tpNdSGn28imfVDeqIkI3VRjAKz2V/7eeUfHAWcF3nBXCctpeSWx/dlsnJQMg6iNYyNbabhlAdiMX7utlXTh4DEXBKRqanu5WMeFnAwKcOxqlfJ2x8z9eepMmx6zU3Itu+xJLHdHq9Sp+dyRqW9knVXIrsKp1beJSNc911yMS9YMdmJ4Z6QP1r6yxIdlQa0IQ9YOitZHv/iLv4ivfe1r+MQnPnFGxpx0mkQgiUD9CCQAOLk7kgj8BESgNo2z2Slzwvzwww+L2EeY5yH72717N7gqTgDJel+KnkRpew6fxq7HHseK4w9Ca1htWuktivBSlGNH8cyt7YepxnlbWwAJBJ/7zU141tiBcRG7coRRIrNE5nLOS4myLNOfP5h5UdSWW2lBYS6mZjO+CqhzDATEi2usPamldWl5AuB1ewjfKVy+UOcbhvOZ1k3/4uutN3FbHcEuw0rD0Uh7+UwiQQgZOE5yWQ/MsdaOMfycq9N2tXQPDsxl8HT5QmF/yTrV1mrSU3fc68IGfRR3p5urVas6aanfznGMvjBSlCZq0KaOvGNSDheGXUkDp5gQWcpa/+Sgf261kJZih1ORmLAo44u6DZnKrG5jcmpG2DgydLx/gqmqAvjnwRFrRZnmb9T4GUc9XtzteG34LBYcSKq9pXlSv+urcftMIhenahem4gBgjimsbpaxkfroJuJRtefUydRn1XfcdOF6ceY9y8QFsv1+LXXRz0wx0yh7WuiCTtxrprY/5vRjj7MWU65vAcdGbYEebQ5bvDcxWDop9diqEaQH09/VNXWNFI6vey/4XF20qh/vu7Z9a6FWzynKfm+99Rb27dsnwlX07223/eu//is+9alP4Vvf+hY+9KEPtdtdsn8SgSQCMSOQAOCYAUs2TyJwPkYgLgAm4HzwwQdF7INKzsFGwMN6X9ZWUvWRf48qdjI8OYv/eOApLDv8fZmkRW2N6k6j9RGs923smRvsj6JXBS1dJdDF9HBN1/BGdjueKl8k6cPivcqJpuYI+F2pT+O96T3o1hcL00QZbzOgzjFwYtnbWy0WY2R74OQrtktko7+WvxEnnF4B6F3a/MRYBIwgIkBM/WUt8/+bfRLL6oh6qTGbmW7M5fOYGR8W8EsRH9YFEgxnswSajZl0P7vYB8GumUPKK+DBwibss1dK2mWvvrjumNsed/sxqM/iptQBrDfGwfM67vZhzksvCE/RboiMGi1YlC8yQVQrjdedQy0iJX7Enm4Ig16sYdpq+672z2Xar7+FMHXzNksEA520d6odA9OJs14BE1PTkvnBTAE/y0Gxq5VFCAJE/hDUs4ZZgLAzJ/eKv1jSWSXu4Fh5PIKgNEoolpgmXRJ16eAiQjDFnP+OC4AX7tuQulnaLUmNbIh41OKYZgVEqkWDVu6psH0UAG5kYxb1WCI8ZlkoOvDTtAM1yr46thfrnRt23CPOoIhmDXs9oiLPdx1vcb5D6LW8XJvCFe4+LC8dlxIJxfoHAbGf/m5ibNXNKA1egrRl4mO3bkd3JhX1VM/KdkeOHMEbb7wh1oAUH2y3/cM//AN+53d+B9///vdxzz33tNtdsn8SgSQCMSOQAOCYAUs2TyJwPkYgLgDmhOW+++4TgHvFFVcsnDLTwFgHxb+zPpiqj1En82Sb/u2hF5F67TtIFcdjhbE27TbOznHrfVXfkubIVMLSTNXhBABrmkyCWGO4116DY94ycbHt9vK4zDyJi42RUHuk5uOOBtSDY1gYbyoLp8wa1uqj0Obku8UdIuDF+j7fBomWPTRq0tCjFXCddRi3pN9oPjxu4XkYmS7Ac4owPFfAVTPBMwV6F8CvYSFjaMIC/qC4FQecIREkoi9xWKNaLcd9nXlIxIt222vFb9cWtWi/pnODexzbSnuQ0R0Ru4pSf9jshAki8o4Bw0rBKfkKudEbWU6//rX2+fNTpX2Ws1GKaPRj+VvaFLhyCpidmUapzNpnqypzYHF/Phgmg8i6Zqb1Mn2awNQoV4umRX3Oo46ZjKrlzImVEZlL1VjnW7FZqiwiqNphxrJRDXC944fXzS4Wj6rdn5ZHfKYoDEeQ2YnUZ3WMYpHp6dHrZZvF1reR8lWfuVhTXc7Oc6VidFl+4jZqBfywtA1HnUGktTIGtdkF/QMeZ8zrkgyYVd5p7Cw/jWV9ldIStTBE/QBe24nUKrzZfZU8o7duvwDXbdmIgYEBuV/P1Xbo0CHw57rrrpN3XrvtL//yL/H5z38eu3btws6dO9vtLtk/iUASgZgRSABwzIAlmycROB8jQAAZZFaanQNBIwEwa52uvvpqAbz79+8XiwOCnVbSwO599nUMP/sddM3Ut1GoNy7HsUX8xmcaw8VvwvZdXO/LNOLGdYZ+Px6MdDfs4uwiDmxyclLqXvtrPFUJHCg+pZdnW5oks09fsZj+w3pDEZnJyQkZZV9fvz9c3RALEc8On9gyFfrB0mZMuxmU4PtrEv4SOFL9+Z3WoYgCU6z39cdoGgYy/Sth0i+5QSq7qpFdEDfSdFjpDOyiX2d7f/EyvO6sQK+Wr1unfNLtBbV8yRCTDZ7wsnA8gnkHtsdqVw1d7ixWa2P4X7lXkDHar8+Uu4AMqZUGNYPzZb9mlp6qYUJZzZ4p3z+3LPXDfB5VC9osERi3yroSqLmOg9LspIAfv/Y512xYC38XVWyDysYcmwdbT4HuxmFCWe2ywwRiZFSjCDQFbZaU+Jjcv/NMIhn1OIsdkpJr6FWeuEo8yhdBqy5ZIFPM33ea/eU5MD09jmBUo4upYsprQ9VnMvsOa72r1KL9e9r3Bi4IAx61HXCWS8bLuJuTUoXacgqVqdHnTeEKey929DGbY3EGAdnoA8tvx2zJES/ua9b473MucLCkY9myZfLTXJsh6sg7sx3ZX7LA73znO2M9V/WO/uUvfxl/+qd/KtZKLCFKWhKBJAJLG4EEAC9tvJOjJRE4KxGIC4AFmNx/v7CcFLd68cUXRXiJoI/gN5OJXlfJvpgm++j9/42R3Q/WsBLRwkHwTuDJ40ad1FenEWfmgXO0lE4znRGgEsb4TU1NyvnUAmB1JqwxTFsWnEJjP93gmXOBIahYXJ2yujhGQRAuDF62C3ahsaAPgeIbzgocd/oEMNLDeIt5cpE3cL0rEhS74jYUuOkh02qlQKEhw6bPbnDvoNiVD0g5yfVS3TDKsyLExLbHXoMXyheAtb4rtalFE2syTye9PgxocyKKNeZ2oUcviEK0ADWCPs/EtN4jAJkevDemmiuLR7nzmE6c04oCllJkSV1PlIRFYdeei9JF6DYVhWTFDqv4ENj5zLDPDkdZrPEPwXR2e3pU1Im5SBT3GVUDrdjs+AwiWWUqgTcSyorDDvsCXUDKoIdwPIEmVd/NuESpM210gYJ1s2o7ilw5emreJgpQdb9nAvzymAoA09+8nUwAxVJrnl21qFBrDVX1dGqGXy/MBZ0IWgxPlzfiZXudlBxQmT2sTbhZlFzgMvsN3NI3HLrN6dW3Ip9bI57qH3nXNvEXHhsbA22G+I1RC7VcaGWtLcEw/3u22WEuANPj/qabbpLFpXbbZz/7WfzVX/2VLCzXE5ps9xjJ/kkEkgjUj0ACgJO7I4nAT0AEWgHADz30kLAs3JeMH/2AKYgVZ1K+MLH0PEzu/iFO738WB06MYa4YLwWPk12m/XLi0VxsK1oacb3LTjapBLNuvRwFoDhJY8pew5bqgg4Hbh0/XbWvmtTzHAlaCF6aAYogCDdyvXDmps7oXcyxKbErTkRVGqq6FpzM2noaZdebZwyrwa9iDMOEpAh8f1TcIsxuCmUBurReUTXKI64PbG16K3tkND306QVh4XkduB3vSTKWk15W2KmPZJ4RdridJuAXRRGKUo3nIWCo7MAWMSW0XVfJPvxaSdbA1vrnBm2W6tvDzLkWylMEHB6yua4IKenNI5OyDNi2u7BQQYaRyuLavM9svR6ascMEXaZbgE7LnoBYUvMR+XHifcjnJJNJC4AOSzEP2iw1ApYL15NAPJA0wBRwBymY7qzkS3Q69Vmda6GQFy9yMp+tvFcX3iHzjLo8h667aJGRjDBTzYP3stqXivH8MZss6DxVvlAAcEYEr8I9wqfcDPKuhsucg3hX72L1++neSzA2dK0c+uatF+Dqi1dVXXa+Z7i4RzDMn9nZyqJekB2mmnmzd2SU+ynONlSAZgnQLbfcElnzolH/n/70p/H1r38dx48fl1KjpCURSCKwtBFIAPDSxjs5WhKBsxIB5d8Y9eAEFwTAFNHhxGz79u1Ys2ZN1N3rbleaPImJPQ/g8IFXcWx4amFy3axj1gWOjzcHwMF632ZpxGHH5ETXTXVDK1bX/Qa3nZ6ekol3UwAscESDSSyffnMAACAASURBVGGqwoyIKdU2ijbNzPiTPILJqMzCAghfuQ5aaTYCf9MswvX/7gP06QWxK46x3mIEU1rn3BTglqTOkKBiAfyKMrGLUnnx4sdhZxDPlekD3I05zxLw6nqapGv2aXlhhg+7y3Da7cFyfUaYeab7ErMY9H1l+rcHEeehjdL70i/jAsNPE2+luWR4YUuqs2Kqg/1UWFJ3vq7SbqmuMmxslRpYXxSq2mapwg4rADBrA8XpcaQMHZlcV0cm52pctenCKn1WZ32w3M+VhY56z1MwDbZdJeVqAFydhVItQBaWYk516fAU81qAyJRoMtXQDKRMDU6xdba/0f1HtX0uLrYDgLlQQ/CqUp+D9dS1x+biDRcdwuqYfXVsXdK9wxq1Dp6z14voH23JwtoptxcpZw473P14R2/1opxt9eD4urvh6RZW9nfhZ2/aIuJwjRoZcsUO87+KHeYiXJAdbq5B0MpboHofal/Q9eC2227rCPj+5Cc/iX//938XRf16fu7tjzrpIYlAEoF6EUgAcHJvJBH4CYhAHADMbffu3Ssr05xk33DDDSF2O+0FLX/8NZx6+X4cOHQYE7PhbELwCAQBTI/jRKeeAEnr9b6VIxkZgtXwyZ3aigwUWadYVhi6CT2dE6bWB4Ss9yX7Q4saTUSboippc2+C8KKjYeWy/li13XGvGifnioVRYleVxQhaM9WKwXhSe0iV7LyXhkk7Frji72qaKTjl+tf6pNOL15xVYofEOmUm/2ZRwjpjHJcap/B/i9dK+vYKjMNzmcQNGKZRNRmlWFa3VhAF7guN0binK9sT5HmahbTuoBxgf8M681lSR1SppT7YYV1la7ZX4YP104QVy1lts2TC1XQZY84E0tmuWLWwcYJjmVQ5Z7q5f25UxXb0dIA19IHwQp13SOci0OUWBajXWklFHUsjAFzdhy9ApkSXgunSQZulWtZVAcSizhrlufm63/lFDqcE3asA66hjbrSdAsB9fb0RtQmqe+NCDdOeec9Fqafm3pW06FqRLL9vYfo9B4ZTrWBPEb0flbbimDuAIX16UekEF66YrbHKPY2dzrNY3RdYoKB/+pp3o5gZEib653duxbLe6PXpHJdSAFfsMEtGVCOAVLXD/Hc7bHq96/XSSy/JN+jWW2/txKXHRz7yEdx7772+noIszCQtiUASgaWMQAKAlzLaybGSCJylCEQFwBRhosURQZ6quXr3u999RkbtOTZmDj6Dg88/gMPHh5tMilknNi5jClstb6feV52ckfHraJtVCXPiRWZ8cJAp0M22rg6dkcrCdoGZ8dMCanyLmvgTtqmZGTB7tL+3fTXS8IvrgZNzKtTWAvR6ixG1Yle0N6EHaVlLS41hys03FQcjiJryMpj10iKsResjssH8/T8XrsdRp1/ErpiGyVT1YBqk6wGn3D5hpz6QeQFDen0Wv9ENTUaNdlHNLI9UH8KS6rrvqSvAMAPTblyP3eoDVWE5S1KjXjYyyHhFgMJiCzZLZkcYqtoxBtO/VbawsIZatV9yreK3gBdeTV2HJQBSE1a9Wap0WIwUAKblVhx/Z1+AzFfkridApkCIY/mp7wTpfLoVW6qEsnSHauCdWeTI5ykoVxKthbgpvbwGrpGWFHxmJJDZrVZ9bnyXSRaDBpT5QlrU5hWja0A/szT2Oysx7PaIBRKfEzY+r/Q/57O3ofwmtnlvVFm0TQ5sw8Sg7yZw3aVr8M7Na1t9BBb2I3AMssPquvI6BtnhqFk1zQZEsSq++5kC3Yn2Mz/zMyKARVB9JgB7J8aY9JFE4O0cgQQAv52vbnJuSQTmI6Amzo0CMjw8jJdfflkmiZs2bZI0V04w3vOe95zROJJxHdnzEPa9+DiGJ+oDh/HxMVkp7+npDYynvXrfBRBjWjLRbVbfyO0VAGYKdNxJKyfw3N/RfeXa3mwqdh8cw3TRRXl2Ih4LHfEqBsWuOMZaRmUxAA4Xu+LhhA3O9EBzSyg4gGk3Z/vDhslj7ppdg+fdS1DQMliuz6JWH4r1h7RFusgYwQfTLywS04py+gS/Ga8QG0ywb7KkfM7ESki3QEElCoN1ujEWvIdmXUtUs1MpC4Ui/ZgrQEaB4bhCWlHGSgaPKcPBelLbyEpaerinroey7qtnU0iMIl1hLcqzRDX46ekZqZNvFdj4AmQVe6pKijmgmRmkUqaw1LlMyhc9q8kCELVtIw2zzLTo9tTGueDIxTTfV1YD1c6Z6s80Y1NzpZ59vT4Ks9bbTJjaLlloEf9fvXVWnVkMygKr9rrUgn5mOjxvbwBLFqa9jIyTLauVJfNiozGKSwp7hZFmWjdbKT2IE2vvlHTywZ4sPnLLVrl/OtmURoFih7mAqxozV5SQFuPcKth89tlnha2lCFa7jffcnXfeKarSb731Vstjanccyf5JBH6SI5AA4J/kq5+c+09MBBoBYH6MDxw4ANo8EGDS95f2R1R+PnnyJO66666WQFrc4JYmTuDQU9/DG/v2olBanGrIlXIBjfMTq3brfRfGJzPIDFCOBlaYEsyJEFWg40ymgix1LpdFrqsLntUt9cE6ojNKrFHOj58U5qgVFrrRdQmKXTHdnHXJi4GJz8YTeHFyqVgnXo9aVo+CR1m9DM+lSzJQ1FLQXLcOUAofma+QPY0pJ437rJ0Y1frFz7hbKyIFW/5NBopp08u1Gbw79RouNkfi3n4gm2k4ZZgGqnxp43RUy5Ly/MVSy/GZsnabikXBNdCV0jHQ27MARH0hLV9VOshy8pmpsMMi6dTuMGR/pgtTcImK6KrZVg66U4TuVoTDVI2qr6TMmmZuHa92eKF/qUdvDwDXnnwwboyr5ZVg6kyFZ9xM5Kh4r5G1rt6TatFk/Gnn1GpTADjVN4THypdg2O0WYGlLCYArDGuvVsCN1oGqulvfmomLSV7k1OdGY+RClcnFiXK4cJyAfj0Ng5kN856/h5zlIjrH1q/lsdEYEW9glmfwOeBCJWN4Yt1dKKf65Xes+101cKayVipnyEWFIDvMZ4KNz0KQHY6jlP7UU09Jij9tkNpt7OfGG2+UEpjXX399Sb6v7Y452T+JwNstAgkAfrtd0eR8kgiERKAeAObEgKwv2V8yffQjVDZDu3fvltXpO+64Y0lrlKaPvYK9u76D4yeOV6X0kZEmGOMqfifqfVWYjGwvnHx0FeW5uVlRbo0OgIMsNet9u2BZqYWrZKVSwqToJZ/NadQIqNJaGRMTk8IctcJC1+u/VuyKaab1wBInlwRVfj22D2Zqwa+rGz7T4/hCTgocFm1VU1kNlMLGpRSyuT9Zv3FrBe4rbcWUlxXbJHoB65qHLMpiV3S99SausI7FfgewlpYMVdZwI6c+NzoIWVKmRqv0UtZVUhRMb6Kg3PDaS/bANMquhlyuC725VF2gzngpMMz/hgtpMVW6PSZuoZ60XLEzEqGsee9c1lJrbhkGD+OhRlBsceZAvfNXizDqfmiHAa57/9Mblx7ejg27XEaxVIkbAWI27QNe3vfBRaF2Fjm4mDZXdvFs9gaprWX6P/2uU5ot2QyzXgaW5mClNonbU6+JdRlBJZlZ3k9+6jPj2B4TrWJCFWm2ejXarDkmGG6kGE1RJ8aH35PxZVdjqv8y6fPKi1bilm3rYz+b7e7A2JARVuwwx6caF/gUO9zsff7444/Ld/C6665rd0hyvbjQzG8ZS46iZD+0fdCkgyQCSQSqIpAA4OSGSCLwExABfnAJmIKNkwJ+fMlCUOF527ZtVSI6r7zyiqRoUfQjzkp5J8LJ+uC3du/C3ifvW1BJpj0GZ9Gc/JMF8kFRPH/f2rEZVGjONxa9qt2H8eLKPScvZBQaNY6RgJlsbSNVak6w9VQaBVuDWUeFlYBS6mrnhanIQg8MkFlpD8Rw/GFiV43OywfA5gIDHDaBc0zWp5YWTaZVCm3R8XwfUicvasu1jfdrRYCrsmhA4PuqvUo8jVl3aMLFBmMUW80TbdT9ZpFl3Wed9NxW72kRyXJ8ltQHhjkYDhWU4wEWgtjZWd7zQLp3GbpMV9LLw9R8w8YaTPmtFtJS7HCq6b3cKAYEYmxBBWKm+ZNVT5Vn5sWkmtlSKUAcxg4zXpow27wnOg2AOU6mb/O6BD1/g+ww/62APOaBMJ8Bw/CVpXm/E+zHWeTgubxmr8Ke1HaMeV2ids7UZ9WUsjlB8BbjBG5JvQ7FqHMbehl3+p4Vj2bLkOc2yO4Hr38j0M/3NJXZzaFNOLX6dlkZ68ulxfOXsT3bjc9SkB1W30W+y7mgqAAx77Fge+SRR2Rx+Jprrmn7FPhdYJkRfwisk5ZEIInA0kcgAcBLH/PkiEkEljwCtQCYCs+0deDv6e1Lj99aELNv3z4cOnQIO3fujOC9e2ZOyc5PYfcj38XhvU9L2i2ZbMUmUoG4HfsLzcrAs4uUro01eArXUByKyq3+5De8+emqMzJp99lSphM3Bquc0Jb0DFzbhuFWFizE7iedhVv00y1bTcNePNL6Ylf1g0JFbt9iKJ1OCZtdq2IaRUhKpdAy3dOj7VDAh5T+qIyxzyR1N4xzrIsXsjHHSusWHisqoIxzTAJVXlclqrVYQblxb1yc4KILx5fuG5J6y6iKv2E9V9sF+VZVbGSsK6nS1Sxn1PMNegczHZp1smYmh2LZnU/ZjdpTOBgmWKE4Gxe+Uinfh7ld9sxX/TaFUQ2C39qRKladytKe64hHssByXl+LFku0qErBsbrm1cCbAX5gZnYG9zlX4y1znaQ7M62/ttH/+oTXh3X6OO7OvY4+lwuBaOseiHIVai2wwvahrzfc6ncVAbBmpTF12c/Bsfx05/e/czPWDwW1G6KM4Mxvo+rpg+ywYtMJdoPs8KOPPiqLnldeeWXbA+MxuOjMNOj77ruv7f6SDpIIJBGIH4EEAMePWbJHEoHzLgIKAHPyS2B7+PBhEZHhx7yeny3rglmfxI+0qrs9Wyc+evwQ/uefv4rSxKmGTGrU8RGEcMXfLVez4lH299WR8xKTevYVKlWT8SZ7zprfqLWXZGAs+ukiDd0u+p63KXp95hcYmUoaNkVdWmNVmoldLY5FhZlT9i1hqbVatlcUYqMwU1J7aPiCSqIo7AGF6THJVuD1YYp1nDrrKNcvuA3rOAl80iLO1BywxO0/uD3TSwmYFEvq+65qDYEh4+xbZenI9i6DpdtIG6yl7dRYfbsglS5daxekAHGce0y8XVNdcAszYnlDgE3VZ9pEwXWqFnaix9OTLAq1EFDvvmhFWVoxqsqTth7ruQgQu/SitjFXKFVZkfG+NVJpGJlupLxSw4rriZk5fM97J04Yq7BOG5d0/rB20u1Dv56X+va1+pgwrP5iYPQItrplrQXW4n6UYrT/rmKpyull1yK98VrZdOsFy3HHlRe2evgl3Y/PAbUmCIjJEnPxiY3PH+PN+2779u0LZUKtDo7fB9Yiv//978e3v/3tVrtJ9ksikESgjQgkALiN4CW7JhE4XyJAoMLaJ+VlSNBL8NtISfXNN9/Ea6+9huuvv74uSF6K8ycAYKo2mYXx4weRmTsGt4lXb6Nxcc5ozVsetTJ+jocT8d5eevf6KqjBFkwnZo1Zq2q1pqFJXeqslkO3N1MFKOOkYYedY7CGur7YVXDP6npNVatc61FLNtfQPGQsA7philBWFPBEcEgbl4mpaeQdAxlLQ28u0za71/A+0HSpp8wYbl3xn1buj2b7kL0Vy6R58MLUWR8Y+kI9fvMwO+srBBNQdXX3QTNNmF7ZF2SqVWRqdtCIf/ftgspisRRmF8Tr6S/61C9WZ40oVYBThq8CXmsnRcApQlledBDPZ46LAQQiFfDbmpBWMBTBdOJG7G+j8Ekmg+OIErcvQFZh1Vkza6QzyOmOxK2WrZ6cmcP/zAPgtdq4PDth7YTbhwG9gDusPVhjTIrieDOP6oiXPNJmC+Jugfu2dkc+S0yNPjZpY3jVzVixYiW60hY+dut2ZFLnn8+tEllU7DCBvWpMj1bsML+lzUphamPFb/G6devwC7/wC/inf/qnSNcg2SiJQBKBzkYgAcCdjWfSWxKBczICrPd97LHHZEV7w4YN2Lx5c1Nm7ejRo9i7d6/UPFEV+mw0rsJTjZpAgGCSqb+33vouPP3j7+P0K49Gsi2qHXcrdb/BPhQApsiL8kpWoGVuTjF2FLsiQG5v4scJdM7ykLc1ODAW6oOjpmGHXbM4YlfqvILKvfVYNqYP0+rIKM8hX6ww62SrfCaRqdLhKsTKHooMmGmlkMl2gX6sut05z9XaWFCYKu0WRN23UyJCUZ+RUCshps7SJsr1lY5V6jzve8aCGQCtgrSo4wpu59sF+arS/FGgW0TTJd3X/6ll6H017aJfR2o7/pgDIlk8hrLXiVIPzcUevrfqZwT4QLjZNawFny4FuuBC8+bH2AarviAGxrTogM0S40eLobKWkjWDrO4upEvzfChq9qB3FY4aF4i3dY+22Cas5Bk4jT6s08bw0+mX0Ge5HcwAiHdnhN23wR4IgJ9wtiLTO4ihoRV47zUXY9OawXgHOQe35veHKdAEuwS/BMVBdpgCWgoQM3W6WVo+3RUuvfRS/Oqv/ir+9m//9hw848qQuAj+ne98Bz/4wQ9AYUwuRPNcmRn2W7/1W1IilbQkAudjBBIAfD5etWTMSQRiRoAAgwCYtb6rV6+OtPeJEyeEMSZTvGrVqkj7dGojTiIJwF999VWZYO/YsQOnT58Ga5fpn8jJ4/GTJ/H0j/4T9ql90CL6cXqpbuhlX0yo1aYYXjJRqgbZ81jvOytAgSCP4Lfd1F0POgzLglcuyoSKjDBFnzhpL81MNk3DDju/uGJXFbsapfRcn/kjm0YlZgE7AfBENjFchdivM/UFnmZlG04ec9mMpM7SL5bCX66eEb/TTjbWp1qsTzVa90/txHgIDsl8q9pjxwPGZ8vQyjPIptOSOu/7vdJDt5Opz/FHT3Xkis1Shb0N2iwh0yfXitePac/qOatVxVZH9+11UnVUhSssOBeS+Lw1Axa192v9s/RFq0yn4KdpAx1h1Xk/MQM86HXM1Fl/IcHGnGNA82yYni0114zPm9pqvGRdjhGvByu0KaS0Smzpu3va80sKtpnHsTN9aD6ubbzA4l/6RXvwXpTzqslEOLniZrw24kjd/vU7LsVPvWNTB4529rtg9sETTzyBCy64AJdccom8q7gwE2SH1TuOJS9BdjhsEZSWg1dffTV++7d/G3/+539+9k+wwQjIVNMNgs8fLaCYuk2BTGqI8Hn8i7/4C3zmM585p88hGVwSgbAIJAA4uS+SCPyERECtWEc9XQLO559/XsDn2rVro+7W9nYE6/zA8qNLQERrJrKt/OAeO3YMt99++wLwJCv13Isv4uCT/wNz7nTDY7tGGhZ9Y53oqZdhHZZKRQG7CgD77OW0MD3R0ombh0jqJq0sUm6hSpgpZeoy6ZwoeijOTGKgpysiy9ya2FWQWWsEPgh+026+CvQEz7KeCrGqrSOrSFGzIKMe9Jl1jZSwhsKQttl8xd8i0vPMZJvdtb27Si9lLSnvI97TqVwP0l09kkqsO/O+tItshNo+dMsdcMHHrx1m2q9vc1XS0qAbM69hLpMGlZJr75kw72AOgtfEAxXQ/eur6tPZN/sL96NuNnwfJIaxw2UzB2tedI1jqmf70+wI9f4eVP+u3sYTVnzONeEVZ8QizIGGH+vX4oi+RsodLDgidGZ6DvJaCl1aGau0Cbw79SoGU/aSpj43Ov9a1num92KcHrwGR44cxdBgP37zQ7ehK7O4RKTVmJ7N/bhARx/gjRs34qKLLlo0FH4DVO0wQTGzhNgYoyA7rO5jZjXdcsst+PznP48//uM/Ppun1vTYtEH8+Mc/jp/92Z+tcoP4u7/7O3zqU5+SxWhaKW7durVpX8kGSQTOpQgkAPhcuhrJWJIInMEIMI2rWZpg8PD8kD/zzDPyYSNzvBQtWO+7fPly8UpUoIhsMMW7wmyZJmcL2PXwA5jbvyuUTXI1AynLgl1qH0AxjkxR5WSGAE5ZMpGt8+2impj5Rghk2exCt1YI9aQlYHLKJYxPzSI7sFImy40Y8HbEroLMbb1hE5xqrg1Lr7bBqbu9S/BUlkliUHSJseS1DtaZisLuvEiWACURUqLqbLBeNkJA5zchiOZPWnM7Dnqij2LxloxHfs5nuTPZnCykUJ3YNugf7CCjnzvAZ/HoPZRsoMx031JBMhRsUtlgqrS5kCqt6iSDwme1ffH6ek4Zc5NjslBFlXHfl7z9Z0qxw7ZBxXFf3IgLSkG21gct8r/tXE7ZV6l/B+u9g52yZvb0TAm7nfU4rK/FCProAMykbHmeaYe0TJ/FBn0UN1gHsTJd6ohHddsnVtMBWW831YM3V70HZRc4evQY7r5mE+666epOH+qs9cea3WeffVbAL0Fwo8Z3JhljJaRFYKzec//8z/8s7z5aDn7lK1/BF7/4Rfze7/3eWTuvdg981113iYo1z+MLX/hCu90l+ycRWNIIJAB4ScOdHCyJwNmLgF/HV/GYbDYS1vow7Yu1SmGr3s32j/t3ThQodkWAyeMx1SzIIO3fvx8HDx5saMv06uETeH7XD5A6/dKCyA4ZIJM1pYXOpNGS9ZqenhGgxvRejpEWR6xx7UQj6GHKYyOlX4LHYp5CXL1wrQxc3YJl+xZJwdYZsasGgke0ddIMZA038uRcicuodPF0OrNQa1qpM/WtZVSdaco0qxSUyTizzpT1m3EaARZ9luN46Mbpv5VtmVFAwSt1H3VlM3LtywbrfufE7mbW831q43jMtjKWVvdhXA077yt6B1SleY1VUwscvKYExmReg6rY3I7vp6npGRRhoTttoCvTmWdKjcGdv18llrQ/ojp1E0Gx5mnXjaPGmPAJqmWZ+V66f2Y9DmhrMan3I4OSpEbnYaHokTn1MOhN4mb3eWyyxmBQBM1cXHPd6jXr2H6ahpNr7oDVvwbTcwXMjp7Az996xZJ8Mzp2Dk06Ut8mfpOYBh2ncSGHAloExJ/4xCekjpaNzwO/c7/0S7+Ee+65RxZ7273X4oyrE9sSvP/Zn/0ZfuVXfgVkhJOWROB8ikACgM+nq5WMNYlAGxGIC4DJbFL44+KLLxYweqZaWL1vWM1xVFumQsnGrhf24uSL9yM3fVgsWdqt+w2eO+NIUTE2Cjyx3jeuCmi9WLqaCepEeQ7TSutHXNXy9nR3o6srK2mRtp6WCb057x98psSugqOikFQWxUiWRwrg8L7ipNBPF69m9/h7LjBwYSGYqq6YRIJDLuFIWqumwzEIvGYj8XXKl5bpqaxTPhda0OaIKfXqPnJTXch4ZMgJ0Hy7G6YIE2jqTgG6F30h60yfp4orL4Kp6ws2TzxuUBCq9v2jFjd4TR2pGfdLCXjOZH1TmaycL/2htXaK9gMBqK/63EhIS9x+pZdW2WHuLaJgjrtQa3zK7cH/zF2GYQxgyJhFWuc11WRRx/U0THhZlD0da73TuMd9bGHxsnYh4Uxf32b9T/Vvwfiyq/z4uDY2pmawY8slTZnSZv2eS38fGRmRNN/LLrtM/HvbacxkovDVf/zHf4iQFhd82fjNu/vuu/Frv/ZruO6669o5xJLt+6EPfQj/+Z//eV6kci9ZUJIDnTcRSADweXOpkoEmEWgvAnEBMCfnDz/8sExk+OE/E42MD+t9WdsbrPcNO1ZcW6ajw5PY9cTTSB9/CtrMqY4Mn+Ml+CU4I/glA6uRVepAI8DxjBTSKDdNz60V4lIiQ0zndIwcyoVp5KcnZVRBsa76w1SCRc3FrlQfBBMpJy/YIIotD0WUyJwTFGWzGWQy9Eau3xjriuhSxVrGNAxk0impMSVLzBRsKvqSLa3XqE5rOIWzLiRVGV+1zVHQ11a8iT0/rZ11yvQNDrKUvt1MuuH5duB2jNSFP9YS6N5DprrW8qi2E3+Bw1eVDtoskSUlxGfqNEsLlLgc928slBVpmLJRdMuj6meh0RHiMnYUvvJZcgePljfhheJquNCx3KyUZvD68l1AMHnC68cKYwa3GXux2hteiF2lNCGoyE2bpc68i6JGtZTux4k1d8mzyHb1hkGURg5j06ZNS1Y2E3Ws7WxHPQxqUDB1eeXKle10JfsyFfrXf/3X8c1vfhMs9bn33nvlh99CAsoPfvCDbR/jTHfABWnGg98ipofTLSJpSQTOpwgkAPh8ulrJWJMItBGBuACYK9MPPvig+BVu3769jSOH79qo3jdsjyNHjsgE4R3veIdMGqI0+nM+ve8tvPLC4+gZfqEt0BBkVHlsgrhslgxmZxqFebq1YlMQwaMF65CDPsOsaZyemZVURNfMYqDLgmX4k9P6LT74jQsoOV4KybDVApwo0QtjEk0qDQsLP+83nOkRns6YZ8BVvwIoqKINhxhLhLrOZqsWeDJF/EsBKY7T0yxJdyb4LdqOsI5MF+a/g2LnPvDXYXZAGKyVePhjNaG7ZQF1BOpxGuPAdxIn0AoM85ry6hgLNksVUEfQz9UWLmTEbSwRILMqomKUaY51H0S1WeKootUO08f323M78FppGXoxiy6zck9Sr4AMP6/thJsV4b4brAO4wjy2cNpqIYHq0mTOVfMVuf1U6XYt2JrFmBkYJ9behXJ6QDZdNdCFO7auEts6ls3wu/F2aXREIHN7+eWXR/72NDp3pgv/7u/+rlgLsY5WNX7j+G3z697P3cbn9bbbbpMMsQ9/+MMC5JOWROB8i0ACgM+3K5aMN4lAixHgRyuOAjIZOApc0DaJ9UmdbMF63wsvvFAmTM3YFKpCs36K9hErVqyINZyRqTk88Px+zB58Gj3jryzUB0ftJGgfRLEr+v1S8KpTExWyU1mv0JT5VeMNA8ALwKpURjplIcNJlJkWmxl68/rpm9WtIooWnfmlNRG7yhiIBNa50MFsAr/GtbsjE/MFAFAuw3Md2FLbrsEwDWiZXqQNDSndtTb+DAAAIABJREFUBxW0u6F6NEEHU8XPZuMzpVLAwwSeFEtJ4a/amlGpJdUoNlUNNLkYwaUAqkUvZVNj5ZgMjerk8QAwx8rninYyvDf4XFFJnaAOYrHjX1PairG+ninTBHhMu9dpI+REF0JT3sQ8ZpjwVfS4RWOHo6RKf7d0OfaXhtCNOXSbfuwIKpnvrkTtxr1uGJqLG803cIV5NHSYFcsx36YqKFwXFCFr9n6NHgN/y/FlV2Kq31f+ZQbKR27ZBq80J9Z5nUgVjjueM7k9M5SoQUFLQNoAtdtoHUThKALIm266qd3uGu7/gQ98QMB7nPaNb3yjYRo22eu/+Zu/kRpmCmV2IiZxxpdsm0SgExFIAHAnopj0kUTgPIhAXADMU/rhD3+IoaEhAZ2daFHrfcOOdfLkSWEXCMajehkH++Gxd795Go+/9Cpyp56T+uDm/sH0e8yLYjGZI9/fV8PExGTHALCtp4TlYZ1jVHZS1SGzhpYiUmFiV77IkJ9W6rNnEOEov1U8WqMoPQfjSMCVRlHSchuTqfXTfDtxL6k+aMnjOQ5K5RIKRSqdQ5R0XTMnTFi3aaMrm16k9tvJMUTpS9llMV08m1WK4ZU9CexMmwsFvoJwPXEmsdihR3JN8AlIl0ooK5hOHCX1OSw+hUIe+TyfK118Y/X5NFpuy4UC17GFGS4Uiwv3GZ89EdGyUtDoOew2F0KLnvoc5SrWbhOVHV68+vRY+WI8X1wDMr4rzAKoxUUAXBF20/CW24chbRp3ZPZhtZWPlMESTDMPLnhW/JpNyZpopxUzQyJ8pVbVrr90Da7fvBbDw8OySEnngKX2jm/nfJrtS/cBpvwyzbevr6/Z5k3//qUvfUnEo9T3rOkObWxA0M5FiTjtoYceEreFsPblL38Zn/vc5yQVnACe6e5JSyJwPkYgAcDn41VLxpxEoIUItAKA77//fvngX3vttS0csXqXYL0vAQBBNf19ozZOrp577jlJx24nvW4mX8KPd7+Jwwf3Y2DkOaQLI6FDILCi369SK/bBry6Tc6p6MvWY6bztNE5+yXKlDTeykBSPpwAwGWiyPEo8iHFlanYwFZMAhTWHorhrZKG5ZUlbVSA4ClulztG2umCVZ6X+uVHKK0E1mU7ec0zJDKb5thOvZvsS9M8VCiKilbc1sdTxDEv8ZZXoEv/L67iUjawm7yXGJSwF3DUsaK6fohtFpEushGjhUyPmxbRkhwrZHRSOqo0Ta3I5Tv6EMdVR4krWl+wvQVmw/jlsX8vUUSiUUCzRc7hayd6w0tAy3cjpNoyQa6pS9dkvWUpZHDljKfDx2OHTbg++O7sZI/oglulzyBqA5vmeynx+x9wcHOjYYIzif6VeADO3WXPNlH4qmUdpKs1c1VwHF7tUqjSfhzjssKebOL7uvbCtbhnC8t4sPrxzq8Sfi5QsU+E7Om6WTpTzOVvbHDp0CPyhOBXv13bbH/zBH+Cv//qv8cYbb4jI5PnSKN5FkS7OCX784x8LI560JALnawQSAHy+Xrlk3EkEYkaAbEBQeCbK7vzIEejdcMMNUTavu02w3nfZsmXC4gaFbqJ0rnyJt2zZgg0bNkTZpeE2B06M46GXDsIZ3o/+0Rer2BXF1jEl009V7arUaXoemMLN3xPYtdo4z6XKbZce39+TgGpqalpiqFREG4ldcfLPH9rrUGFW1ITtOegCi6PVLSqrG4LMRhZNfuxmZKGA9w5TW6Meo9VYBvfjeWqm5YNDuwQR3yrBB+Oenyas2DBaWbXLhjUbc63NEVnpYCNolTR1pxhbpIvgUFJg5313Vb+dEo6qPTfeLa6R9i2omjDV4XFhVsCsLFBw4Yb3bBTwFfQOZrYDAR37UO8zBwY8M4Muw17wkfZrv/UF6yjGqjZ9vNm1a/3vizMsFsXS8/DgzDocMjZgXOtDCjYyGu9PHbNeSp7N5fo0brX24QJjvGp3qp8zz6GS0RFtpIwX3x3lcnU5DNPMeV+qNPNGvY0OXY+ZXh+0kZH/0I2XYdWA/x48fvw4XnvttY7VykY7qzO/FYEq63P5HeQiY7vt05/+NL7+9a/LgkEnRLXaHU+U/Vnn+7GPfUwyn1gadaZTt6OMKdkmiUA7EUgAcDvRS/ZNInAeRaAVAPzII4/IBPXmm29u+UwJFpnqRcYnar1v2MHIuj755JPYvHmz9NOJRiD32CtH8fKBY+gZ24ueiVfhlPILbB0Z1kzGF99RjYCD50Tw2Q4bYJu0usnHFg/iODiRnZqaWpiEkp1uLnrjiSAWhcEkfVmngnI0NWElIpQ20DCVOMh0EvgyPftsNDJ/WY0ezZU6ZV63vGPALuXhlvILtZIqrZZ1poxhFEAW9ZyCab5Bm6Pg/mTVzfKsL84k6b/xRLqUSBbv5Vpysx3hqLBzVGPl36Iw1cE+glkBXHjwsyeiLb6ofsg4B72DK/WvvrJ0yaNftIYUSvDSvcIME9Rl01akevWo1zX+dovZYb6Pp2ZmscfaijeN9ch7loyft0FWK6FHK+Ja8yA2GqN1Dqf59dBOSWqi4zZmuBAI++xwRWWd97/Klqh9Hua61mJ41bsWDnXVRSuxc9v6hf/f6VrZuOd0prbft28fqEHB72DchduwMdH7lzZIXAzqlI7EmTp39vv9738f73//++Xd+N3vfhfvec97zuThkr6TCCxJBBIAvCRhTg6SRODsR6AVAPz444/LBOld76pMeuKcydGjRyUljimnTItrpXZXHY/2Q4899pjUHHW67ujk+Azuf/EgDh14HeljT2CwfFLALSeCi5uHsTECYEtqgltpTEU23QIMpjXGBDw+kJiWyStBU29vX9OU3qDYlQZNBKEUi0uQRNDANOGwphhKxZ7WA2gVppNKz/Vi10q04u3DhQXTngUZP6ZpC2M9nwLOnjzG3MjBLczAKfvp0mSrVetMqjRrx5nmW2qY5tvJ+lSf5ddD2Xmy90yvjSMcVRt128gs3CNxhaSqxb+YFdCeyi2vadkJAfyOD+im3TRQmoXJcyZTbVQYzuYLRfHut/hbU/3axuwsLcGAdO8QFQBwwBnCjJeFrnkY0qawQR9Bd8oXF2u0KKL8sHUn35Y/NBfVFBhm5otqvpCWCS3djVMb3ie19Wx9uTQ+euu2KpV5sqRkSztVKxs/tmdmD37DyNbecsstERYam4+Bysk/+tGPRBzw7N+PjcfLb+6dd94p98a3vvUtAcJJSyLwdohAAoDfDlcxOYckAhEioHxVI2y6sMlTTz0l6azvfve74+wmgEL5+7ZS7xt2MAKKXbt2CftLFriTjYsDu/fswaO7D+DNCRsX9JkYmngZ6WI4+zI+PiYpg3FqmNV4nXlLlozhpyTHaUGxK+5Hdprp2fVbfbGrWjZN2CTXnq8PrvRoWzmY5bmG6bmcyPlCYbosHDDF+Gw0pfgsZCpTi+cXF3yfWVSx7Z5uwNEzApbr+dNWhIOi28o0sjkKxiRYSxuXTW0UW4LDMNCkgL+kL3sx7zsqf3usUXUEUAZj2+w6q3uW74Qo/s/N+lN/rwf4/bg68FwXc64JozSLYrFQpY4cXOToJOMfZewEEnynkrnP9A4ho/vsK++bsPKCBYafKf1hUu7zB5W0d2Z0lOmHHS+LoHbc6luh0qX590PdV2E2t1YWL2j/9pFbL8eGlb4Fkmr0aj948KBoRrTybowSv7OxDT2A6QVM659O3C/ve9/7RJhqbGys6eLl2Tjf4DEHBgZE84LfXS4AhDUy45/85CfP9lCT4ycRiBWBBADHCleycRKB8zcCrQBgik6x9jZOyhOBEFOe+dFstd43LMpMoaY65fr160VltFMtWJ9MxeuNmzbj4b1HcfjUBLqmD6F/7EUYdrXoDFOgCY56e3tjDYPqxJ5uIas7scFv0IeYwLdQKDZRoq4Gv2FiV8HJNafMCiTpTkHYJMVQ1lP69cFeRSiMzO9SC0ypC+ADH0/AT7065TDm0PfTNUSBmS0oHFRrK0PgROafix9hE+Eg08lUSap0h6X5BmtpCeQozNRJbaZgzWztDeozhrSGmoucgKwWFthXsxrw4PGk/nqaTKcnwCnoWR3rwWmwMdloZlGoTIpqyyMDRVFBz8AtTMMp+0JaQXXkoFXQmV64UX7YYvvU2y+ZGBQ/q8SVIlhyFy66H4KLOI2E60QoS++kP7SHsfRavJndgXx+Ttjr9QMZXLmuBwRHfMfzh9eX4Jcg+Prrr29bILBT90cn+iFY5Tu/njJynGPwWeCCMuulmSF1tt6XUcccBfB//OMfl5rmpCUROJ8ikADg8+lqJWNNItBGBFoBwASyTP266667Iq1819b7XnLJJR37wBMAUpV67dq12LFjRxuRqOxKkP7CCy9IfTI9DTle9cF/9egwdu05IpO+3vFXpD5Yn2fOJibG5byYfhynEVCm3XxswBP0IVZeuo2VqKtrDpspPROEMZ1aiQRRQKhsZmGV52AZTNN2F03Iq8GeVSUUFicmndpWKf42Y1Nrz1UdnyBPc12xEgo2lRpaLlNQK5gqzdRQCgel5F4Iin81YzqDtbStKilHiVstyx/ch3XdXDRQwL9efyoDQEDavKJ4lGP79eB+mm93d5fE6Uw1tZAzi4zUU7PVLizwfLn4ROCv3oUEw/xRjdexXv1ru2MPeh4LO5ruEn9qtvqs+mKbJQJ+PqdBjjcMpPB5IJCOK5RVe54sKTh+wXsldnIPGMCdW4YwPTkhoFCVVzDThwsIvOadUktuN+ad2v/555+Xhb6dO3e23SXj9c53vlN0HFhbHAVgtn3QpIMkAkkEFkUgAcDJTZFE4CckAvzwKsXgqKdMT0eKf9xxxx1Na5U6We8bNj5OWqk+SX/JTtgv8Lz27t0rhyKgDqtPLpRsPLznMF45MgyjPIv+0RfQNXNY2G1OXOJ4QqraVDI5jSyEqs99sQ8xGSsK2IyP17NiqgW/0YWGlMcsaxJBiyZNg07P0BLTKistCPaoCtoJZdSo92TYdgpQxmFTyWQS2NfWYIufrlNcWOwIHi9MgVgAzLw9Fv/djOm0TaZd+8CnVQ/duLHidS2HiGSxn8Ue0YHrbKShi9eur/hL1BXFRijIdKoFm7hjjrs9a5TTKAmYJEAku8pzrm21wmC+kJZf/1prs9SZenBICjb9xJXnsZvqqVKdj7awUHmuyfD7qu6VBRl1ntWLXfNCWW5pQQ07Vlw1DadW34ZCdtXCbj917SZcvMpPfeZ7gCCYWUIjIyOykKiehyA7fLbfD7HOOWTjZ555Rr6dnVA+5v3G7w1Zc2ZYJQC43auT7J9EoLUIJAC4tbgleyUROO8i0AoAZh0vhU2Y+kWgE9YITF999VVJ5+JE56qrroqdGhw1mD/84Q/BNGV6CLfaON79+/dLqh7PieNtBmSPDk/igZcOYXymgFR+GN7++5CzJ9DX1x9pGI7uAwnlyRtlJ1VLSpEmMitkjVS6XD0lasXGBP0+oxwruA0n1/RWdfLTC+muFFGCa8NwfZCgBHyY4ptKUSX77DXWLpPFJMzn4kI5wNI2G1VtCrjaXtKEzWxDP12VKs1Jf9BeLKiiW+ux6mpUmfYk7ZVjDWPWm4251b83EslinwL8A0DJ1XRZBPE9o5n6TIGtxYCrdjy1TOeZTivm8Xm9gpZH2ZQpqs+NwLoIg4kndrWCcrR6cN+/u14j6/2W249Tbi+KpTLS5Uls0Icx2J0VuybdK0u6fpy4Lj6W599DzuJFnOC2vB89zN/PTiFW/fdU/2UYX1Z5125aPYD3vmNT6GnzeWCtLP3a+T6lSr16D3FRSKVK9/f3dywrqNVnIe5+1MJQzG3cfWu3Zz/0/qWOxaOPPtpud8n+SQSSCLQYgQQAtxi4ZLckAudbBFoBwASKrOti6pdvW1LdzlS9b73YMgWakyuKrLTSCN6Y1k3GghMxgt+odYlkbZ/e/xaeff043nzzMPryh7ElPbyoPrh2XKwv5WQ5Y7iRAAT3D4pd+bWkFR9imfAvsmKqL3YVN06Kqc5YFFOix6wPejhdn7F1FKfHYWqeiF2dbQVTqfulhrXrtMWmholkyXXQTXj06LWrGXAVU97/FACTms5cVsBI/RrTFJDK+h664kUcJxMg7lWsv3095tu/xvMe0U4BLlWf5887KlMdFEMLLth0bvThPSmPav5VZQFQ7dwkaKd4VJ1WEQYjMFwM7pvVg/sMMRc1fJE1tpNOLx4rX4xxN4c51wAFxi3NRp9pY7v5FranR2C680wpF5taUIIPng73Z6q7r3TeuJacwm+uwYWd2ab13+VUH46vvZsS2nK4TMrEx961HV2ZMGV8f0RKLZmuAYwdRZ74ruWPyj7igsjg4OACII76/j3T91Cj/umGwGvd6ncn2DcXYJltxG8qF3STlkQgicDZiUACgM9O3JOjJhE4KxFQKWpRD37gwAG8/vrruPHGGxexusH62Y0bN+LSSy894yv7FMEiy8waqriNtWms5aKa9Lp160RIqxUBkpGpOfz9f92PUxOzuHD9OqkP7p14NZRZIcnjmhlY8xPeKOmjQbErnivrSRezTUErpu6F+lxOOpvV+zaKm6qjVemunFATMJFN8219itAME9n+IaTdcNukuNel1e2DQlKdqqOta69j+Cx3pZ6ysc1RpcaUoks+w1jS6U1ckol0VzYDCqLF9cFtNVa1+9VjvtV2JbMLdNwlAPYBpb/oUtsU0znhZWEX81hWOok+o4Senu4qUNipcYf1E7SS4t9pf6Vq2f9/9t4DyJKrPP9+u/umyZu0q02SVmFXcQUooBxQQEIYELJIQrbl73PZpjD2H9uUjSkMf5ftwoAD2EBhl4tkCn0m2QQjJBYhIckIBXZXK61WYfPO5jDxxu6vfqfnzPS90/d29w0zuzvnVE1tmA7nvKdv3/Oc93mfR/27xjs47BraSigKGAap0tVCWo6qcT7sLJQHyxfJIbdX8p4jGbcgjuVJ0fa9xOfb4/La1Ha5LL1ddSMe9Tle9KYy/FpIK3zOuBpK9NT0pivV4n76TsRj7/JbpZhdMHnzm1+zSs5fuahhZ+qpJWsPaA2Gjx07NnkdNtJ0dhhRwWbeyfEi1PxRjz76qNqEbIV5pO/OBhnjffvb3y7f+ta3mu+UOdNEwESgpQgYANxS+MzJJgInVgSSAmBowps3b1aqntR06abrfcl84e+7bNmyGQnEz372MwUgAORJGrQ8lDxZtJ577rlKSbqV2qsnn3xS1m/dJ6XeUxU4dEojE/XBO6q65S/O8aSlHjEefRSxFRoLQ7K/9RrZFWLBcVo1thXwS6aa88mmBkEEi9fxsVFF87VsR92P2MUVUUoyT0mO1XW/9BnadlI/5Xr3akQVZj6tckHGR46pTC8ZcB2PetcjfnkygYVhKRVLyueVpHojqnSSOLRyLJlvYHiQNg44sr2youi6dkZS6bS4Bf+ZDLYXy4vlsdLZcsDtlQqpVM+TlFWRc1IH5Q2ZF2XA7vwGieukxXL9vtIaZapV+UGdOmg9riTPdFBIC8EvNgl+7Fwlu6xTVSa93xuSFLW6KUc8zxIEuka8rCy2h+Rt2V/Jkkw+1jsh6fxOz/BXawIEr1dWwFxURjr47ji6YK0cm3/h5KGnLx6Qt75+dWRXNmzYoLK92AU1anx2OE5niLUQGZ+nYHa40fsvsjNtPODhhx9WjKF2aE+wccz3z7333itf+cpX2thLcykTAROBJBEwADhJtMyxJgIneASgoYVlcuoNa9euXaqu65JLLlG1tzNZ7xvWJ2qm6H9cNU6O3bp1q6r5BSyygGH3vdWm7aGuuvYG+enGbfLynsP+Anx8v8w7+LRkC4el7HRLqjIWk5obLnbVqJ/aiolsG+v/VgA999HZ3yCI8JWehxW1N5vNyLz+filXqK2c6lk99eRWY9zo/CDltZ1ZtOA9a+11+B3xGB4dlbybkp60SG93WHa+uufuBIXUdisKZLiViuQLs2vHUxtb5R1cqUhF2NjITGa69bOg5thzxa74Ctm/Kq2QHxfPl3EvLWXPFkdc8VStqSUZKcsCe0zek3tS5tnhGcZ2PBtBBgDXiyOAFlUHrfvVSBgsvO+e7Cj0yI9Ka+WANyCLvMMqg06DTQFF2rJtOeL2qPhcktkh12S3tm3TprZP9TP84WC47HSJ5fk1/oXcQhlcerPa7FKbNClbUZ/7u6Nr/VHUp/YXCnTcxjt6eHhYiWgBivm7btDoeV8vWrRIaSC0+o6L26fgcfQP5hHff+1wH8D+iE3Y3//935fPfe5zzXTJnGMiYCLQhggYANyGIJpLmAicKBFICoAHBwdV5hTgSAaYBU67/X2TxO6JJ55QNNw4foxkewHvjIEsHfQ1xFja0YjDvn37Ju2hXhk8Ij/dsFWGx4sqE9Y9/KrMO7xBurx8pChTUOwqlSLDOiV2Va+vnMM80MiS6FrEZim1QYVqLczkU7F9D1eo2FoEjQU9mcPa2kqyo6q+dcIqqh1xDruG7/frqp8knrTN9CcIJACIxAMQTCyy3b3iOtkqNd+we+iNBTVXKT8LqVuUHY/vOZyaEaq0AmrZPqmMD6nuhYl0McdDZVv+ffRKGfZy4nhlySjlZcvPwnuWjElG0uLKOan98s7cU82EPdY506jPCQTQGtVBB2/uC2WVJ4XAGnXsV6Xl8kj+TCl6jsy3RxVY81zPtzwTMu2uFKyMjFndsia1X96aW9/xeQ3L8FePYcpmiSR+yemTfadeK+X0lL/5dReeJq9ZtUSNJ4qezMYgpRJxNyjD4sl3lKZKkyHWAnO84zRVmiwx/56JxvcIzCPcB9rhP89m7KWXXip/8id/Ip/85CdnYgjmHiYCJgIhETAA2DwWJgJzKAJJAfD+/ftV3SyqlWSDAZ8zVe8bNi1Qj8kQ3HTTTQ1nDXEi+k02YvHixbJ27dq2CjZB9WMn/5ZbblEKzbRCqSyPv7BL1m/dqzKyKSnLvGPPS9fB5+uCwiixq+mDnBK5YYzMByCK1iylVtvyBKnE2saG69bzcIXWzb2D1GO/ljLnqyd34HMVzPolseVpuSteRYaHR5QgWK3NketklAKx9nQN3isI0qLAerUdT1HciTS7P6/aczjdsdpanVUn4wdoA7KFKWr/tLhaHi+dLSXXkpzkxVFCTlOz7XqWDEtO+qy83Nf1uJxij7Qc/toLkJEOxrsZFkBUHbS+py+U1SNOZTxUKIvjsCV7YnixPCnnCRn/BY4vcqV+h0BWpaxiSl3wiPTI6d4euc19TFIpRLT8nyhw2UoQtb1ZYw0CTw4tvEyG+s9Sm168w06d3yNvv2L15PxqEFwPDLfTLojx8n7hHa4BMRtQuiGGqAFxVBlCK7HjXQjzCP95lJtbbWyekiH/2Mc+Jn/5l3/Z6uXM+SYCJgJNRsAA4CYDZ04zETgRI1Drcxk1BhYeLGpY8Mx0vW9Y3zT1+NZbb63bdajBLDJYuADczz777LZT58gssyEAEK/NROw9MiIP/epVwUOYjHDd+uByWYF5nWENF7sKDrNa6VljDl95uCjYJVUL86SEDCLiPPUW12HZ1CklX0tlzh18gOs0DSIKZDUDtGhqMz1JKdDQzhYElPV8Xtt5P6415WkrMn9gAE7rpOhY8F6AMnErikZKC2Z+mwHrvh2PT5UuB7LGZIQ1aGqXxVDQnom+Q31WY1fKwtXt38eulO2VBZKWkmRsmaT6Bo8a9fws8C3Z5ycFn9o1L57yp/Zr1WlxqM+N7h2dJfXPrre5o8sEtrqL5YnUJXLU6pMl1lCgpnYqgsfcLhXP86ztclXl2ap5ZS71vHZCXZ0MfSNV7PHupXJg2VTtLse/4+pzZV5PdgIQTxdB472i3y382U67oLA5Y9NP1w3zp37fwYIJZofbGT/ehzCPVq5cKeecc07LjzGCWnfccYfK/pIFNs1EwERgdiJgAPDsxN3c1URgViKQBACzsIP+DNWXBcXll1/eMX/fuMGopR7Xngco3bRpk1qUUa8Fba0TTfsjI/YSZuNB7J5+eVB+8eLuySwa9cHzDz4tmcJhlbmNK3Y1sfyeAF0+CK4ndlWPUhu2uKZmU9d7+krPZUVfBPBxPOA3blaqHogABIKMp9STm58N7ferARriY51u2uYIAAs1nbgoCriNJ+70+2tbHfxllfvqhMdsrTJx0n7XCi5pQWbmR4MmssTNUuCDGdUg9RlwyXi1gBvvj3/NXyv7ZJHkrJKkLVfV/dIYr27UBlMDe2PmRbkq82rS4TY8PvgccGDbFMCx/SpX17eHdYTNHTYM8J7WDA7mJ9vVI//lXS073fnSJUXpswsKNGt7pZLnyEGvV06xhuWN2U2y0jmqMsfE1P9BvdmPYbNsjjiBDlPFhsUwuPJNUklNlYhcsWaFvH7NcnVJxkffAJz677X3os9PPfWUemcgmtjpRj8oA9HZYd5dOnY6O0ztMIyNVmqHyTrDPIL5dOaZZ7Y8LKyP7r77bvn85z8vv/d7v9fy9cwFTARMBJqLgAHAzcXNnGUicEJGIC4ABqDhl0s2lcaXP8Ids93CqMd6gfbiiy/K9u3bVX0m9b5YanSqoYyNQvZ1113XsK742GhefrJ+q2zfP2H74blS3vWs9Ox7WrJSVKAqOltRLVwTdzHnU2pLKjPMvAcX12SGJTdP2fJowEM2GgCgbHpqfIfjxtEXU3IV3TPYyNzabkEQgmqmBTPVYbWpzVwz6hxt+1RvM6BRHWnJ8Rfd2Orgp9xesO4psKSB0xQFXiYptcxv0Ju20Vi1mraPHkSBe+39rM9jrGPj4zI8Mirfc26QrfYKSYkrWcu3eKKxoaIwsOfJiOQUQL4js1HWpndHhTr278vpbkmVpjyZ4/oTx71BVJY0eJ2Cl1IMDqeSn6TFv1A+VR4rnqWAbko86bbyaiMg76VlXDIyYI3Jmc4huTWzSexpNQJ8Xsn6+5/XWjZHu7P+QVXsg6deLWO9p0/iIaRSAAAgAElEQVQOb1F/t7zrugvEsaf8jYNjV6UPATCs6fsAYN6/aEbo7HDcTbS4c1TvODK1GgzzvaU/F/RHZ4fRsUjKmoCCzbhgE51++lSMmu0v1kf33XeffPWrX5X3vve9zV7GnGciYCLQYgQMAG4xgOZ0E4ETKQIIigQXVmF9D/r74pdLVpUv/vPOO2/Wh0p2FwumN7zhDZMWQWQsyVSz+GGBw+IrLCvbzs7jjYxH8jXXXDNhQ9T46i/sPCAPb9gq23bulpGRUenO2LImd1jmDb8cIRo1JVKj8Emg1jLpeKY8TIsy5mYk6/k2NblMWmV/WcSyWETwqpVWL0OqKKSpLr8+OMRTtt49g5lqMIPjTAdorfS39lxflGw0ls1RWB1pEKRZqaxUxBa73F4qeLDPPlUa0FSsoUprSm2m7qI/SNPmmvUAJZnw/Pi4pNOOvJReIz8qr5UxLyO9kq8CcuDfkqQk76VkgTUqv9/9SBVIbmWeKnZGbK80+ex0ciOEjL0CdZXptF/GEBSIy/afIrm0qGw/j/XG8nJ5unyGjAF6PUoBRDJSkV67IMvtI8oiKmNFbwRFCaQBiNk8a+WdQIa/OO9M2b1gKmOrqM/Xni9L5mGvFt3oJxumvIPZEOC7ArpwsEXVDkffJdkRfCZ0dhh1aZ5fGmAcOyMNiOOIIuqSGnzu+T5stWF99P73v1++853vyNve9rZWL2fONxEwEWgyAgYANxk4c5qJwIkYgSgArCnEut4XCtm6devUFz9+v7PdXnjhBZXlRUQEoAY9DbErsnX0EZXOmcg4AH4BwfgRx8k0qzqyJ38pT768T46U0rJkyWKVoXNKwzL/0K+ke6TaP1j7+hLvYOa2HfGH9uyVC1IqFsSrlFStsm6oUFMzzOI6aaaktm9hNkIck8RrVYEN5aXsZ/3anfGr7bNfzzmiNomoK+zpgRIaLeelKeB5SU/SvTmL/0dISlHBPVcc17cR6lSrR6kNUqU1aGJDgp9JmnYdFWVdE87Gg1IoT2Xk30avkMFKv5TFkayCvFChRYqSUv/qtgpyRXanXJ96Pkb0oqMR3ATh6GBso89u7gi1ueE4UqxUqmq+YVX46ui+VzefFWjglXS3OGqjw5Nj0isvFRfIPrdfXLGUKNYqa68ss49NlC8k61O1QFppMrvJVaYo8MmFtKA8D668Xbq6e6WIlkC5Iq87a6lce8FpsTvI5gvaDBr8QhOmv5oqXWu7p/UkgvXDsW/WxIHKx3wiOwwYBhjrPvEdosEwwDjsncc5MI9gQLXD7/4LX/iCfOhDH5If//jHSkTRNBMBE4HZiYABwLMTd3NXE4FZiUA9AMxiBVrvjh07VBbwta99rVBHxf/zRb106VK5+OKLZ6XPwZtiIfHqq6+qzCugl6wDfSQ7TdahlWxIksHhLQzlmlo3ss6NGgsuQDqZagS5Mv2LZN2GbXJkxM9K0LLj+ybqg6Gch4ldRYOwOP13UaS1HGXrUikVZWgEuxaRbDan6hl9qrR/JQ2aWrHiaaS0G8drNVjv2WodbVR8AL1Bm6OkmXDAZCadkUqpoCjgYcrEjIfYa9AZ1afWfh9Oldagycr1SZddUfMcVACfuieZcL8mXNtz6c/XYbdbvlW4VA663VL00DtHM9qTtFRUdvPC1G65PfOcSGqqXraVsWibrsnPS9tp5fV753sHW1Is+2JzMDiIA+C3tnzBsx0pOb2SLh2bBP5sgtRSyluJBedOZf0RSJvawEokpGVZsn/pDZLvXup/3i1LlszvlTuvWCMIzMVpPBuAXz43AF9+qhTBQ6jS+rqA0CAYjmOzFKdPUccQL7K6iGgBbsle6/cd73INiPXnHw0MmEcXXHCBLFmyJOrykb//1Kc+Jf/3//5fefzxx+XKK6+MPN4cYCJgItCZCBgA3Jm4mquaCByXEQgDwMF6X/wVoRCT/dIN0Y5TTjlF1dXOdtOZ19NOO02BdbIf9JdFy0w27o0Q1mWXXdbw3lgloRhNw4pJi3KxIP7Fi7uUUNakjZDnSc/wyzJwaL3KJDUSu2p2rNrqhoxIsZAXscjqofSsF7z16kux4pmybEm60QAIoIUBgXqgsGKnxfbKivLK4hxEoa2Bmh1/vfOClNZam6O499JgHQp4xrElX0eki2wmmbekVPC4/ah3XBA0jbkpybj+Bgxz39OVFbGdScXvIA28Xk04NOdNlRWyobxcDle6xRZXVjpH5LWpHXKmc7Aq09mKGFrZyUmqMrVZlHIs9ZlJwKJvNXTqfK9SliFU28WSvr5wdXTdV18oKy3p8tiMUPanhLSqa/0bfWaHB1bLkVMunYyNJZa8/apzZcWieNoJfG8AfikXYGNv1apVDeMcV0hLA+GZYPLo51zXDh87dmwyO4wOAt8rHEPZDe9vGFGtto9//OPy6U9/WmWVEWo0zUTARGB2ImAA8OzE3dzVRGBWIsAiOJgx4Auf7CSLGWq38DmsXXg89NBDKhsM2JvtRvaXLDAN4AYoj1PH1e5+QxUH2F5yySVqc6C2sWiCIk1/qUemn8Swth0cGpOf/OpV2XMYf0sW9Z5IpSgDh5+T/qGXIuqDk42K2lSnOKoWrCzmATx4/DYSS9JWPM1YLIX1Lii8E/x9LSjk3x4AuOJThqM8dJNFovrooM1RT49PaU3agjRtsLqD+q/tA/agT3Lwugh7QUfX9O6k92z2eO7JM1aesFhyK+XJjQmtPsw7AsASlwaedhxxvWpP6LD++WJoxdgZcHfC8kiLp81EDXhYv3k/wjhhc2PhvHlCaXBtdXCtPRPXSeV6VH19O1TQ4873VK1/fSEtNzdf9q68TTx7yuLswtMXy00XNwaxug/U1AJ+iQnWQIgkJm1hQlq11/DZCb4F30wAYmKnbZYAxbwbdOP9DRMKUNyKxgT0Z2jQbOa2Q1U6adzN8SYCJgJ+BAwANk+CicAcikAQAAPiyGLSqO+tV9/08MMPqy/82aZrkbXEZ5LFF/Val156aQwF5c5MLplddvDJPtdaLbGI2rhxo7KPYtEEnRxaeb0G6F2/dZ88umm7FJUY1YQNSnlE5h16VrpHdrY8CEU3Lo3L6MiIsmWxUyg9x6tv1TdPYrHUqMM+pTTcRkiDQtK9qfKoukwYlbjlgExcQNscscCul9WLuhexReHamkBEwTrlSZ/kBpZNPhXcUmrCnW61Gws6s85CnywiGx1aPZe+YK2ka8KjAAgxpO47zDs4OC692YEwmO25DYesGQv6oE4+C/U6MmWFZUtfX58CYmG2X7X2TEHq88xS36dGEmafRQb71flXizWwTG0ednV1S393Vt5740WSVVZajRvvYcAvf7JhChun1RY3O6wBcdSz2Gp/OJ/3MtRugCqgONjYfNVUaTQgkvQHASyEsPbv3x+6edqOvrfjGny/ffGLX1QK2DCe2BDgewydjfe85z3KwqmZzcJ29M1cw0SgHREwALgdUTTXMBE4QSIAAGaxG1bvW28Ijz76qNqBp+52tppW4tQ78mHAcyb7BrjFkxhaXHDjgEUhGXUEYQDGUNwaiUmxyNKCMUNjefnZczvk1b1Hq+ijfn3wM5Ip+JZUSRtZtHKlImNDR1WdZq6rS9X8ttKiLJYATVEKtfVEssgSWp6vkpvxiqqethN01ymbowlxpzqWL43ipGqqbUfsSsnvb8oJ9Qeu55McvDZgz3LLqka4Uy2Yqa7tr18DPayy1krYSdloJa8vbUR3D47LtR1x7dzkRkftmGvrfjup+lwv3lMCYOG+2L7tV0WKzpRQm7pWiJ2UEspCBb2Sn/QG7tQ8h1/XL28YzJ0lO5zT1YaH6qplyc0XrZSLz1mpKL6NGDXEA0DEpgC6C+1QRQ7ra9zs8EwIacHiwfKOjUyYAABBALGOH+85Soc0IA6WD4WN7bd+67fk29/+tsqeJ9UZmMnn5Z//+Z/lD/7gDxQzDIo7TKcDBw7IY489puYfIUr0QaLGO5N9NvcyEUgSAQOAk0TLHGsicIJHgAXML3/5SyUCElbvGzY8xDr4sucLbzYa9VdkqlnsIHTFYqQWeM50v1gIkAUhc64XgbViV/hGNqqVDYJfnXVjjADghzduk5F8QDHY86R3+BUZOLQhcaZwrGJLYeiQEuUZ6O8Ty4nO8iSNZ9BiqVKZyupFKdTqrKH2yYWea01Y3SjZr2yvVEr52JTZOP1OYnMUdb0goFS+rpbVsE6ZDGa5PN0nWd+nkyCpNkMZBOvBGmhqH/Willg1U1+qpi7tbwZEbV4w52wkBOt81XPgltSGzQSe7Hgtbe1c6w2SWgGwac9EKiNpy4+Tbo0y1QhlVezsjFPf6Vsxt0D2Lr9V1f6T7WeMi3Iiaxb4tk805h8gzA9MG53dpHSCdx4gkCzg8uXLoz4ebfk978bgT62qtHo+JijSnaBKU8rCdxAMKA1Y6QP+wLp2mA1P3cgIazAMY6D2O+Duu++Wn/70pyr2rSrttyXAdS4C8KfV0rTZ/L355ptVCdBnP/tZZelkmonAiRgBA4BPxFkzfTYRaDICfJkDJuvV+4ZdFtoxVLCbbrqpybs2d1qtMjV1tFp4JQg8m7t6a2ex8GEjgYUgFMB6Ylf17qKzvpr6p+vc9GIJQPj45p2ycet+KoMnLwMoGDjynPQdfTFWFmmoZEtl5JBahC2cPyAVrz1q0o2iV0+htpHFUtqxxbUsKXlkU33gr6nEijLrdItTSeYfHNZHbIJ4lsvlZDZHYdeqzVDGrVOu55McvEe7QRLUcqjv/NCComIAN2ICXTuqBrr+RgdUaUTSMlV00Dhj1eOupHJiufSxVFX/HXwWWvvUxj07qH6dUloDjTayoLBT44tysgZnFdeNAfzT4lkpcSqd84iufaYGV9wm5cyUFgEe4PfeeJFkHGtSFTmojKyzm4A67Od4Vnj3Ugs7G02/LzUg1puItX3RmeEk1OR640Htf/fu3YoBVS/bCTNJg2Gyw5o5wWcCMEwpDH9fvHix3HHHHapEhuPb0b/ZmIevfe1rcu+998qdd96pstmmmQiciBEwAPhEnDXTZxOBJiMAONm7d28iNUt2/fmyvvXWW5u8a/LTWFD86le/UosyrCmgn7H44N9PPvmkot8B4merkUFnY2D16tVqsRMldlW1EA14ZLKQ03VtYWMZPDwi6zZsFcSygi2Ff/DBZ6VrdFedEHhydNwVNz8kKceR+QP9SrhnppvvS1tW9jFRFktkU3usgsoapmzfP7d2AU9m0PdaTd5atTkK3tF1JjKUE1mzuOA3eA0Ak6J6uvUnhvtAYU81OWZ9Pw3S9L91f7W4Uz1bn0ZR9utL/XlljnXDL1jXDWubIO4HKGw0Vn1+MdOvaNFa+Gomqc9x1K+DMandBFGWYqmUFMp+LX+cBvDn4E4LZR1ZdIkMz1tT1aVbX3uWnLeyWtmYGJDRBAjzg1CibmRAAb9khwHFSdXg48QjyTEzkR1mw5jvTBhQcTK29CmYHWZziWzpRz/6UWWlxIYKgJrvjDjXSxKPmTr2/vvvl3e9613yzne+U77xjW/M1G3NfUwE2hoBA4DbGk5zMROB4zsCLG6CypZxegsQZQHwxje+cUYWPCy+qKOFrg3lGbCrd8pZjD3xxBMKeM6mgiYLHKjh0AWhBsYRuyLWxB8gpjMXjcCvnhtqMp95ZVB+sWWPqjcMtuzYXllw6BlJF45O/jeg89hoXiqlksqi9vX2KvXaOOAjzvPQ/DH1LZa8bL/0OGU/i5hylO+qpkXX3q8Z0ah22BzpfighKSs1WatLXwE71ConbZMiWeXKdFnhwMV0dhQF5aStXqY6KO5UbYWV9A7+cx1FlcZPGpGlQoOxapo2nsoVh3rZMUnbVtt9dMNGqEWPeFbiqF/XbipwTc1aaCT0Vi+6SRWyk8xSvvtU2b/0RkXR1+2MxfPkrVdUA+Laa/IupuaXmECH9tkT/mYHMdJUabKctZ7ISfrXjmODQlr6/VpLlw4qSsfNvpKtpeTlxhtvbOr7j8/ZunXr5F/+5V9ULPleo6Ed8aY3vUn9QCmGLn0iNDZ/WQvAgELMi0ywaSYCJ2IEDAA+EWfN9NlEoMkINAOAWQCwY82XdKcXOdQXoT7JYgbgW6swyoIMEQ7qa7HfmK2mKdDcP47YFcfV0vbigN/g+I6O5mXd+m2y8+BURkb9PlAfLMVRGR4ZlaLnSG/GUoI2zWQnZyKuWpBtvGyJlPOT9Z5duYxYNt60OXGlfk1tXNEobXPEmAB6rSqX1tbSAthLgLoWWhyRLC6vQFKlIPaESFjULfHfDapLsxECo35kdFSVE5CBIiZxwUDU/fTvG1nxML+AJ7eGjo8oFkraWgCNa6WyOclX7I7Xy/rgd1hR41G8j7JW89W0U5PiZ6qvIf7ESiRL1bDG2xzRNeB2JR+pkB13LvAkHlx5h/Ke1o1+vfeGi6SvGwXy8KYt8vicXnzxxUoEiXcY/6+zwwBiGsASgAwg5jjidzxkh+m7BsZhtcNxbJbWr1+vNDNuuOGGuCGvexx1vwBp4kesXnjhBXUs76Rf+7Vfk29961st36PdF6Bs6q//+q9VHPl+ZuOXeUcFGlDf7ndHu/tvrmciUC8CBgCbZ8NEYA5FoBkAzJc09V8sABrZ+bQSRvoFJYwvWxYDUJ4R6aptLCAeeeQRWbVqlbLgmI2G2BW0cDJeLPpe//rXNyV21ewC8YWdB+WRTTskX6xWCy6MHhNv6+PSX9gr83OiFvLHK/jV86ZUlPF6LRd8inSlLOOFqSwn9O2urqw6JmzzJQowFAp5GRsbV/MD0Gt1A6dWRTloedSOZ9EXyapII7yksqOoCZcb10SH+dJSa310aFixQIhFVH1rO8ZUzz6L2NlOWlLplDhOStw0Y5qyggraCDWT9Y/bd/rHgh6wxPstjjJv7SaIAjHOdNq+BofqdwiCxewUc8eY2+ERfXDJlTLWV+3ve8NFZ8jFq5bU7Q3vOFTuiQ2K+2R4wxrZTA2GKU/RYn7EUGeHKWGZbapvXJslLaIVBHWwkWD5XHvttTFnr/5hfM/x3QUVmu+xrVu3yv/8z//ID3/4QxWvL33pSy3fo90X+PnPfz5t7B/4wAfkr/7qrxQN3jQTgRM1AgYAn6gzZ/ptItBkBMj8JGlbtmxR4JQFAJTfdjeyRdRIQbOGBgb4rZeBoe8oaJIZRoBqpltQ7IpFVVQ/gmJXwXrfZsGvHu94oaRA8OZdB9V/DQ1Rs3dALMuWFQu6ZdnYZukd390xC6F2xT0IJHR2klrRctn3pPXrhj1xwMmWLU7K96WttVhSgCGVlVRpqla6HTZHwXFW7LTYXlmsCapzWMavHXFRwlGO76fbqPmeyem69cHTPHRTthw6ckxRWNlk4rPc6nOYdLzaPkt7DpOOdixLxqyc9DqVCSGttFjEwLanUZ/b7afLZxJWCX8C2uJs8NVughCDOBshce2hgjFlfsk0N1v3PtZ7mhw8tdq+btmCPvn1q8+rO/faco65qrcRGTbvbCAAgjUghvpLA0wCoDUgjhPjpM9V0uPj2izx+WCzk8/MVVddlfQ2047nvkuWLFFZYIBvJxsCVTrDHPc+UJovv/zy0MOZX/yAv/Od78jHP/5xNQ5skM4444y4lzfHmQgcVxEwAPi4mg7TGROBzkcgKQB+5ZVXVGaWBUC7d3yDvrl8oeKb2yhLx0LkoYceUhYcHDtTjcUgMdBiV9gwUQOFBRKqqGEtTOm53XSx7fuPyv+37inZOXhAxW3p0lN9pVLLkoHifune+8uq+uCZilec+9QCiXSKLFm18BXXCdJpUTEuT6RHtcUStaWAY5oSjfJsyQ8dVOC5XVlOn/KanlSoDvN5jTPmJMcQD6izUbXbfnZUqkSUyunuqs0ASj+pW/cpvhnp7m7/RlaSseljK2T8y5ZY5XEpl0qTme+ubFrE9pWla7OHMKcrTo9ST9aq1s3de8r3mA03GBNRTalpiyeWO7U5Qb1vEq9qwDLPeZKacajs4rniJKgBZwNkcOXt4nLuRHNsW95z/YWyoK8rdKgAWDK/AD/AL9nbZpoWEwMMUz8LbVpTkGEdaKo02gkzvQlTO56o7DAAmD5edtllk3ZLzcSEc/juhSL+67/+6/Kf//mfzV4m1nlk7qFvJ2lsLseheqP8fNddd8mb3/xm+d73vpfkFuZYE4HjJgIGAB83U2E6YiIwMxGA/hhWD1Xv7vjubt68We0Mh9GSm+01iy0EtujP2Wefrep6oxZDLFbYdabuli/4mWgAMOqgqX/SYlcszB988EGliEp9XG1rRuwq6VjYkSdzPrh3n+wZs2TInvKc7OvKyvB4QS2ae4dekYHDGxP7ByftT5LjAW22W1A1n7RGvqnB66qavkpZgaUgBVxbLAF4yfyOuynJZRzp78pEPlNx+t1p6nO9PgBc044jRcTPIvizOjvqiS22V5yMretWZHxsVGWU41J848SkHccENxYQbxO3IoVCUfLqHeXfgU2jKT9pPKx9ISefCp4TpzQ28T/xewTwHh4eUe/BoO9x1BXU/QI0bY6vR31udK0k9lBVz3+qWyy3GMsXe//SGyTfs6yqG1edu1IuW139f/oAwCqAiXgDfinvaFdjMwrdBJ0d1p7J2iZIZ4dbrc9vR3+D2WFYSTCg2AiAtqxbszZLfOeRMf3N3/zN45LuHDd+fG7YDCfLDz28nj1U3OuZ40wEZiMCBgDPRtTNPU0EZjECSQHwrl27FNC65JJL1O51O9rOnTuVHzELCbKpZH/jtgceeEBlEOhPp1swQx0Uu2IBQD/oN4vFYAtac+gFfBSwTzoOFh5sHkDfpF/QwY+MFuQn67fK0FhBxgrV9cFWpej7Bx/b0lLWLGk/w44HuEBZtl2/j83a3KQdS8byBQWYoEwHRZjJGuYQ4sn1S6qSb2nMiAdRb6tbM4Cn1bgpkSxLQjPkVc/eRH0wtaNQtdkwyI+NSLHsqrKCOFnOVvua5PzajQWgLZlvPl9jeW2xVJwUkeJzlE5rz2E/869owgn8dIOK4ElE0ZqlPjeKR9wsf/AayhcbMbTyeF2hrJGBs+XwKdVU1lP6e+Rd112gFOFrG1lawC+fG/zW2ejrVGNug0JavMN000JavN9noj690Rgpd9m0aZPaNGKTE5AXtnGs64b1n42uyXcp7+r3ve99SkDqRG7YEEKJZpMgyff3iTxm0/eTKwIGAJ9c82lGYyIQGYGkAHhwcFAtjsi4ArZaaYBDssl8cVJzx2Irqf0DFGgWaFDSOtkQgkEApV6Gmkw0tW0aiGvrDQ2A6VtSpec444HKCviFTocVFD8aYNOHjdsOyGMv7FB+urUtVRyS+YfwD94d51YdOSZY98tSHN/Yco3nb9wb25YlqZQtY+MFpeILCCYWeqHK351MVpxcr3RZ5cTZYEV59dxJAA0I5Z5RtOS4/U96XJRIlgZpKCoXKraUhg4o8Njd03PcZWl8m6NqT+dgLW1QQRkgrz2HoXHrpjP/ZA69TI+iJzfy0yXziOAV8+iLoqVjTUHFzqgNG66vW1Lqc70baSssMvRxRbK4Vj2hrHK6V1GfoezrxjP7zmsvkMXzplPf9+/fr5T3YU/wLkv6Po4VwAYHsZmns8P8yVzT2KzRVGmYRzMppMV3Hpu+gN9LL71UfVdpqnStmn/t0BplhzWT6kMf+pB84hOfaDV0s3Y+pUCwtnhWyGrP5NzM2qDNjU+6CBgAfNJNqRmQiUDjCLAI1GqdcWLFAgkgSK0rNa/NNoAkwI0vTBY0AOpmqFPUKbEgueKKK5rtSuR5QbErMtRhwD8IxHW9r/5T+022O/PLXEDH5j5Q8qBgh7Xh8aI8vHGbvLr3SOjvc2N7Zf7BpyVdrLFUioxMawfU1qbGEQ+KuiPAKD82psBDV3ePostCcdUiWnpBXUYwKpWV7hRiS5lI+w6u5zrZKkAVl6od1edWfl+PPhvcWOCzNjY6Kl4qK129fZKzWrNpaqW/YeeG1dKGZdYBbmRJg57QQVXpYOZfU6XJ+mccT5waqyhth5VUETzsOWBM7bC/CsYGQM3chtXBN4q/qnu3sIrKq9r/vctvlmKumqlzydlL5ZrzT5t2GbJ3AD0+M4BfNgVmszG3iHBpqjTlDDTmFhqypkpH2VS1MoYg+CUm9e4VZPpocKzvy/s56Dmss8PUEyOAhYjURz/60Va62fFzP/vZz8rdd9897bvvxRdfVBTuX/ziF/L+979fOM40E4ETMQIGAJ+Is2b6bCLQQgSSAmDteYsvL7SnZho0N0A0lGKUk88999xIAFLvPthHsON89dVXN9OVhufUil01ogNqII4NUjArEIcKl7Tj9AsrKm0TxeZBnBq9VwaPKCA8kp+yFpq8t6oPflnmHd6ovGU73WrrfuuJXiXpR9DmqK+vV7pzWVXrGsykTQEmKLVlKdpZ5TWbs73J2tIw4bVaymsmZSsq8fHSAGCqXtH1JJiphhkAcKC/ue4eZTGkPJO9cpVv7WyOo9afOIoJADhmTqczBTw1p7zTgu81aPaS7ZMupyzZVEr9jpjw2SRrlSRjFUp9TjlSaNH7uV78k3oH6+sQ0+H+s2Vo4dqqS8/ryck9N1ykSg2CTQM9NiHJcnZC4b/VZ4z6Ug2GAcaa2UFfNRjmPdgucUE2BNhgJPtMTOIC7aCQlmYC1dKlefbwsH/LW94in/70p+WDH/xgq+Hp6PnUKlOqBP2bbK/+DgLEM97rrrtOfvCDH8z6pklHg2AuflJHwADgk3p6zeBMBKZHICkApl7riSeekNWrVyu6bdKmFxV8aVL/tHLlyqSXqDoeX0L9BdzShWpODhO7amTZ8bOf/UwBKA2AWSB0gvKsaeO7d+9Wi1RqjuN4lerhkT17fPNO2bh1v3ghJMuZqA/2637tSfEeMnsoF0HPbbaNj49JPl9QMQf82rajLoV9jqqXDaFVM0fKggdA5DrilAtii6syb9BGzgkAACAASURBVL7Qkm+x5FL3G6Dn6rrJVvrb7Dgbncc4oQHnPaxyCmqDCUop4Li7h5hMgR5fPZlxtVYT3eo4wgBl3Mw6jAGo/cF672B/fKq0D4b5PFewx7LSkq6Mq1j09HSrOY7byk5O1ZAHm099lkRCgnHvp49T1H6ssBKA7GJ2vuxdcavvqz3RLLHkrqvPleULq/1aeZegwQDQI8t5PILf2pgxn7CHqFcGFJPRV5/3VEoxitCnoCSl2Tp3RA4Bv2wItBqTejZLX/ziF+XDH/6w/PEf/7H83d/9XduAe9LnK87x//Ef/6H8iZ966ilV58u7RTO33v3ud8u99957XPc/zhjNMXM7AgYAz+35N6OfgxFQC8OJOqs4w6dmDtCJSvM555wT5xR1DGADC6WXX35ZLSpasdUI3hQwTpYrjl1D3M7WE7tqdP6jjz6qxqip2J0Avyzkqc9j4cfiDuunZpVSBw+PyLoNW+Xg0JSgU3B8fn3wM9I1uidu2GIfN92T1gcyzTQlkISqcVHbHOFnW53d4rpR9bIcU6y4Ml6xxRsfEm/C2gbQlEmnJZf21Ye5Nlm5ZvvbzBiTnAOgzLrjwobA6FheZTfnDfRPWgrVXkupJzuoGSdXT07Sr7BjVS1tQKGaY5KKigFAsfOJmg9ACFlfBYatNNsckvHYMGGzI6PmttZPOtjn2k0b/bt2U58bxbR+5rv6LM92ZO/yN0opW63cfNHpi+UNF6+qOhghJvxhg/Wtrc7rTJ/POwBWkc4Os0mrG+rEOjvM3+OUoeg6aJ6JdmfDdXYYHY3bb79dqSZ/5CMfUTToOH2b6dia+5kIzJUIGAA8V2bajNNEYCICSQEw4JBsJ/RnaNBxWjCbyiIkaday0T2efPJJtfi56aab4nQl8pgosatpAMLzFPD93//9X+WtCk2O7MPixYuVOFe7FjUs3qmZZsFE7fWaNWta3nGvuK488/Je+cWW3cLfw1pubFDmH3ymbfXBtXW/cbN9YX3DKmdkZFRl9/D/xc+2UbxVvSyZtFJjsO06aSlVbHHzQzJeEbGKU5sEuWxarDqetJEPV4cPIEOJLc/o6IgCel3ZjPT39SlgH9WSqidHXS/q98ryyEpNqn9zfCuiYmxK8AzXEyTj88NGGRsC1LZiBzVacaRSHBerPFUSMGWxlK76fLWSqY6KRdLfR2W+jyx6rQzPq3439+Yy8t4b1wrn6ob4IDWcMEjIciZhkiTt80weTzZYg2FKdng/0Nh41WCYDcSwUodOgl8dA7LtgF++L775zW+qvzfyu5/J2Jl7mQjM1QgYADxXZ96Me85GICkAZmH9k5/8RIEwhLCiGgvPZ599dtKih6xlkpq7qOtTg8Qi59Zbb406NPL3ccSughcJKj2TdeB8Fl4stPWCCzDMTyvKpdS7kTEg9lDPqZtuF7Cmn0dH87Ju/TbZebCOCFab6oNraz1bUc4FwODdSkYll8tKV1d35PzqA4L1so1OKqZ7xXFLymJG02jJNOt6Pi20BPj2F7DTLWVid6rFA1F69lxPxoaPTW4IDPT3K1o5ythRoF/fnjnCZLiRenKLXVWnd8JGqJ6CMptGgCLmqNZOB9J90cpKJT8ilWJexY7/K0lKbMeRnpQldteA5KzqunmeXajXbj3+dTuC1OAa9cTP8l2LZf+ym/zdhEB7y+WrZdWp8yf/Bz93NATYsAP8Nirv6PBQOnp53g/aZgm6NM8Cjfentlni/UwctPdxJ0XA2HAA8PKddf/998udd97Z1nd5R4NpLm4icBJHwADgk3hyzdBMBMIiAP1Z75DHiRALCix/UBxGEKNRg6oL+AU8QJcOWvTEuVecY8iKUpP0xje+semFRBKxK90nrfCsKW1a7Ir/Z2efxRbZBL3gAvSTdSAzTBYiLnUZUK09ktk8aJf3clhsX9h5UB7ZtEPyxWrfYH0s4lj4B/ceeymxl65LLaJlTdb9sjwnGxtWmxs170Hv1u7uLslmAW3JmgZLSrwopPQ4qKIMWGPsWceTUpm6Ui20VOtJS92w/9PODYo4IwPE5Y8dUOUM1D1S3xq0aPL9dMOEo8KvrsbsFifnK04f4h4TBn6TUp8b3Uv5JANiKxXFEOD9g19wT09v3XmB4py3u+XF/IBsKp4qR7wutdHR5eXlbHeHnC9bpTfj14Zjl+RnYaMz63Fj0uxxQe9gsvhYHlXS1erNa5YvlNsuOXvyFtjWUI5CrS/gt9k62Wb7PJvnwWDS2WG+n7QDAtlhNkl4T0N7hqnU7kb5D+CX+uKvf/3rSlV5pt8T7R6TuZ6JwMkSAQOAT5aZNOMwEYgZgaQAmMs+8MADCoihilyvQa+jtowsGUAZ4NeJRk0sIPHmm29uikaWVOyKMdSC30b1vgBgwDA/0KtpLHqw8dDZ4TDqoa6Z3rp1q1qgQhufCU/O8UJJgeDNuw7Wna5U8diEf3D8+uBKyqfn6tas5ZH2buU6vb3YHMUXMQob0BRYmgIzrpUSy/LEmqgD5jyyvGNuWolhWYGsX5jFEscDuHRtabtUaetNSMHOSv7IfrWY51kimxdGLQf0q5rVmB6z0JQrCIBRH9ymTKeKrUx5Kfufhw74KXueFPJjMp4vSjqTnhB2qp+hRzjs+4W1studL6NeRkqeo/ZF0lKRLsnLQu+o3FR+QnpkXNIOHXZmbH6j3pt6M2fPgstltK+6xjeXSctv3HiRdGXT6r0F+OWHTDjgtxnruaj+nCi/57sPEEwdNKBYNz6vbFZqunQ7suNk3G+77TZBcOzLX/6y3HPPPQb8nigPiunnnIiAAcBzYprNIE0EpiLQDACGAg0Yu/zyy6eFkkU4wBfLBGhlnQZumzZtUvd6wxvekHgx14zYFYtIYqYzB0nErqBGs9AiMxzMPhBLXTfs1ye6wrjIFJCJwOZoprM0Ow4ck3Ubtsmx0WrV2+CE58b2yPyDz0bWB9dm/JrN9mlLn6TerXE+775IlqsorbVUbUAy9aWK8mqnhExbqjwe+uz7qsO+xZJuZJXIHAKisSFqZyt4jowfOyzUQ/N54zmJEulK6jHbaMxJx1K7EaI2F9osKsZnFLE+Nrdy2awM9PdFimT9oHChvFg+VUa8rPRYeclaFUE1ueDZMuplpcsqyQr7kNxpPSHlclGKIfOrhbSSxqQdx4/3rJChlW8Qx7FkrDDF4LjtdWfJmhWLFPglAwkQ430D+I3LQmlH/47Xa0BFhkXEexxXAr4T2KyENq1LHXgn834GEDej68D3EwwlNoX/7d/+Te677z4Dfo/XB8L0a85GwADgOTv1ZuBzNQLNAOCHH35YLbSvvPLKqrBBIYPyTM0qO+hkfjudYdi8ebNa1F1//fWJRFyaFbvSHr8MPAn4rX2+iDuLL8AwoBjgRCOuLLyIJYuudtdMJ3nOofs+uWWPPPPKYP1ax8n64A1iV6b7C9daxzSb7dOWPrU2R0nGE3UsdZVWtk8q40OTh9bzpMXHmFavVlZbLGkbnqm64Xiqw1F95fcoHw+Pjimass6GQ3smxvXEoILXDYL+OPfzx2wp66RmWij1OWVLqY1UYjYCqA3XVHDt3dqo7vuQ2y3fyF8mB91eWWCNSspyxRNo1K6y60If7rDXI/PtMbmre6Msk4PqM6rnlj/1/LI5ExTSmgmKKxs2gyvfJG7KLwVA8Gq8VJbTFvXLW16/RvVty5YtCoAB4NiUNOBX1PtXg182BIK0Z+aU32u6tH4/E7dgdjgqjrCTyPxCOf/85z8vv/u7v2vAbzMvD3OOiUCHI2AAcIcDbC5vInC8RQBAp7/c4/YNyx8Wdtdcc83kKdS9PvPMM8p3FIXodqgUx+kPQi4sLugLO/VxWitiV8SLseufOPeLOoZrAsihx5H11YtpFldkHXT2oZ3iYVF9Cv4eq6SfrN8qe4+M1D1N1Qcf3ih9Qy/BEVfHIcxEs4NU4oTZvmqbI1/BN8zmKMl46h2rs5OZFBlfT/1EUbX9+uCi2N5Uxnf69T0pq9rhorJr0uwB37fXzwxri6W442CD5Mh4RbJeQcVEq8gmVdVWHrMJRLLoHzZWlluuUnCO6jfg2XYLYgXqrZvdDKl3L+I6MjIslQpU8Jzkcl1Vh9ar+/7f4ip5rHSWoj0P2GT28aW2RLwpWvyw16Xqil+T2iG3ZF+Y1gWyzRoQB23lfCq87yndKSr8gaXXCRngYOvKpOVd150vfV1ZpfRMFhLRJ8CvURwWxcBhs5Y5qQW/tZPLO0gLaQGIcR3QTQtp8Z6uFVhDm4KaXzYfPvOZz8j73/9+A36jXhTm9yYCsxQBA4BnKfDmtiYCsxWBZgDw448/rjKU2nuXL/qNGzeqhf0FF1ygFKJnqgF+AcFko8luNGrtFLtq9/hYWFHPzOIZsTAaVDy92GKhhpK0rhueaUo0sduwbb88/sLOhnTSYH1wLZU4KdU1SGUFRCDa06mMWi1YB/8o8FushGlkVU2/qpV1uqbVB9cF2hXAMFTpUpUAHcBEZw8bbXYUCnk5mvckJ4DfvklV9aTxDfbPF8nypFwJUQQLGYgn1Acz5nykIJqyPLJTYleqxdWiNheSfMb43EB75h0UJYyWsm2Fb7UA27riGnmqdLrY4kqPVRTPcsTyqq2y8l5a8pKWc9MH5G2ZpxvqffvvVGjwzHGQCo+X9JTncJLx1Tt2pP9MObz4imm/vvGiM+SiMxarchQ21tAcoJTCgN9q8IuORdT3Rm1w2eTV2WH+1BsebDRgh3fHHXeo8qB3vetdSsDwU5/6lHzwgx/s2LurHc+RuYaJwFyPgAHAc/0JMOOfcxFoBgBr713qbqkrA4RCdSa7wEJrJhv0Z2jQLDgAiPVap8WuWhmz9uME/Kxdu7ZqHLomDTAMtVxnh8k8aDAMMJypNjxelIc3bpNX9x5peMvs6KAsODTlH5zUNiaYzQPsayprp8ZZC9Z1dlID7nIsL13qgzOSKk/5Bkf1F8ouQInMcLkMldY/gw0PPzOckVSKTLov4MTzMFIoS9oW6e/tmcwqQt8Gqbdiy1PPRqjRGACLZHcbjbnT1GcACBtFfDZQv85kfHp6VFMU8Iorj+TPkv8trRKqfvvsYiigH/OyUhZb1qZ2yRu7tigPYwTRopraVCj789tuqnQ53atUnz07XdWN5Qv75O1XnqvA1+DgoHqfAH5ni0ESFaOZ/D3vUJhKfL6aAb+1feU9xTXZwPz0pz8t//Ef/6EO4V3OfL/5zW+Wf/zHf5SzzjprJodp7mUiYCKQMAIGACcMmDncROBEj4CuN00yDrx3+cIHgFHDSu0U4DdMzTjJdZs5FmofglFYV0BDC2szLXYVdxwsnqDHacEwFqmNwCwLKi2iFcw8cI4W0WIuOpUlDY7r5cHD8rON22UkP73ud/I4z5W+Yy8panTWLseu9URZmTpOZUMzoWocN6bNHBcG0Gqzqb7tTWUSoDa6T1R9cL1zfbBU32KJ56VYLouV7pL5PZkqKngr2d/a/iQVyeJ818mIK7akauqDtYp08B7tpD4HLbH4HCTVHIACPigL5D9HL5LDbo8stEbFtmrtjSw56PZIv5WXW7PPy3mpvWo4UOYpEHbcBp+BmuDWo0rr7D+bHvZE6UDDZ9myZN+ym6TQVa2u79i2vPu6C2TP9leUPRzvRDbVDPgVVWYC+KUBftlEbHd74okn5KMf/ajaFGbTUpc7nHvuuSozjO/v1Vdf3e7bmuuZCJgItBgBA4BbDKA53UTgRItAMwD4qaeemrSNOPXUU2dVqIl6XqjDAPAlS5ZMC/9si13Vex5YCNNvgCxZcwTDogRVgtfSFh7aYglKOg0AgOUUgJjMT6fqDrlXoVRRlOiN2/aL14Ao3J/xxNn9dFV9cL24BG2OmgE0ST9/tZlfFcM6dcoAQwAGQDhOa9VLN8xiqWDnpMcuq3nmeVGZ4oR11XH6ruIwkSF13Xi0aA0KLddVHsKoR1teteWRvm6xFC+GjfpKZhXaM1lz6i+TfH6C12V43yxeJtsqC6Tk2tJnjUt6AgSXPVuGvZyiTJ9qH5P35n4x+Tt9jXh14NNHotk3mg6vj5jK/uM5jGr4dPumofnnydGFr5120SvPXSHZcV9pnncA4LeT74C4z9JsHzcT4BcdjLe+9a0CQ+rP//zP5c/+7M8Ex4Qf/OAH8sMf/lBl49/znvdMZolnOybm/iYCJgJTETAA2DwNJgJzLAJJATCAjQwwizdoXWefffaMZBzrTQuiUYiZsNBbtmxZ1WHHm9iV7hwZadRHWbzT5/POO6+lRaoWaQEMs/AdG/MpuGR9giJazQKEqI/E4OERWbdhqyCWVdty6ZQUVObUU3ZJ8w4+I11jg6GX7KTNUdgNoe9Sn4qK8hT4iKYSAzixRYqjtOzXynaLU2nOSzdYB+2meyTjFarqhlE3VjTpdKrtFkvEhAwp9cFsdiRpgEKQaaqGJsy12qH6TI3tyMioevcERcCS9DF47AFroXx/bLUc9npl1M2oemAaWW0skAasMXlzdqMsc46F3iJpHXjtRaay/75QWiNV6VJ2ngwuf6PyIg62Rf3dct5AWQ4dPKg2Ay+88MKW3ivNxvJ4Ow8BK76zaJ0q0+FdTnYXfQzqfT/5yU9WxZ7vS975vINR9jfNRMBE4PiKgAHAx9d8mN6YCMxIBAAeUY0FGbWq1NvS+DciWLmcb70xWw3Qx+IG8a2VK1dO9g1hrFdffVXZCsWp9WI82uKIv7db6VnHh0zE+vXrlYjYOeecoxSz201ZHh0dVUBY+1lyb+5Bpllnh9s9bwDCZ17eK7/YsluBQ3VPsVQWsRAQAuL/c6N7ZL6qD56yGwraHAFoZoKyqUScavx842ZTiSdq0XGBoWc7UrEb18rWfob8Omjf0ifT1S1dXd1ie/5mwqS9UqU8KeZEHbAWWUJ9OCxz2OznFO9msvxxRbLK6R4VWz/GPvhvF/WZzw7POHPQ18ez0pq3sp+prshQJStPllbJq5VTpCgpxWlISVnOcA7LZeltcopdXwVdx9WfZ+rAo+uDG81FWPaf451URgaX3yLOvOUTyuFTV7lwgSfu+LDAyuF9aDK/otSboT3zWeJ7oBMaFWw43nXXXfLII48oped/+qd/MrFv9kVjzjMRmKUIGAA8S4E3tzURmM0IRAFgFg8IquzatUuJEUGt5e/XXnttw5rVmRgTdhZQzqixOuOMM1R2DEVqMsPa8zIK7NWC31b8fRuNmZo86pVpZAEAo51uzK2mSZO915mlvr6+STBca9/RSp+OjuZl3fptsvPgMWXBMjxeZ3NF1Qdvkf7Dz0l++IjaEEDsqZM2R8FxhdX9ZlOOylYnaT4wFCWmFKdRH0xWuLZWtvbcoKoxlj7p3vnilKv9d30V5XLbLZZq+7K7MiC/KJ0pWyuLpCK2LLBH5NLUdrkwtUd55k7ru5Ot8kd27bSiQ3dbRWmV+qxZAoD9oAJ2nNjXO4Y5Cfo5j3tpOeT1KRAzICPSI8l9j1UduGVNm7Nm+hmkSu9Inyn7cr5KPJsc3d09qk5+aZcrK3pcxSg5//zz276p1ky/Z/scKMmarUTmt5FIYrN9ZePune98p6I64/H7uc99zoDfZoNpzjMRmMUIGAA8i8E3tzYRmK0IAD40MKrtAwtOqFsoXS5cuFCpiZIJjms91OkxscOP8Mjq1atl6dKlarcfRdi4tcmMG7DBn/x0Avxy3a1bt06qZRPDpNYb7YgjmwOAYLLDiGnxbxoLaC2ihTBMOzLSL+w8KI88t13yNdnf4Djwaz04uEOJZC319kpvd3db7h0VqzBPWup7UVDWKsxR16j9fRKRLM4tp7oU9dp2q62B1O/K5YnaVl/V2O6eP01luV5/eZZbsVgKjotYPFA8Xx4trQ4NBzWxv5l7QvrtKYDoWraI5UwbF9RnrIRoQbCZJM7YzwA4+IyygdOODGc51SOp8ui0bmg/5aSZ/toLUWOOn3ASoax6MSl0nSKDp94k+UJeRkfHVKkDz0pv1pHrzponvT3dsmrVKvVZTioGlmQeToRjg+CX9y3fXe1ufDdS0/ujH/1I7rvvPvnXf/3XGWGutHsc5nomAiYCAkup2a9/Ez4TAROBEzUC9QAwiwgAJQtPqLpr1qxRi8641kMzEQ8ooj//+c9l+fLlKtPJWKhLpj45CsgFKc+dAr86e44ACot2FmNRGemZiJu279DZYeaYRo2atldi0dgKFXm8UJJHNu2QzbsOThsS/qh79w6qeseBgX45tS+lbJNyY77CbqeaZ9nCT7DuF4mhlGNPUombvbcvkmVJsRwvG6y9dO1KXmzPPycoAkY23M71hgJGMs/ax7Zef5NaLNVe59Hi2fKj4oUNw7HUPia/1/WwpCxfKCsss04dMfRnXTPtg/9S1RxExVxT5HkeVVxsO+qUyN/XZn71CWF1yjwfanwxM/21N/eFsgqKwt5Mw0d5cOWbBOsj3dhA4r1y6bKcnDq/R737dGODLWiTFvUubKZPx+s5bICS+WUzqFPgl1jfe++98v3vf1/uuece+fKXv9zSu/J4jaXpl4nAXImAAcBzZabNOE0EAhEIA8AsrKASAwwRUwFg6gb9+bnnnpNLLrlELbJms5EFofaKxqIYMSyyv42azvZqAKzPbfcikbhS70vdL2JU0J59VdfjqxEPFo1aRItNBR0TQDBUbfrfbFZpx4Fjsm7DNjk26oNsMieDg3vVAnXhwgUyMEDW2Y9J1+huVR+cKg53JEiAr9r6zLh1v3E7xPXKritx1ZOVl24qK5WRozI2NiXs5KQz08A6ffCpz8mAVJTFEhsf/OjPQNFz5BOjb5S8ZCKHfU/P03K+tTMU/HKyzqYGL+SLRsUTB+MzzjPjU+T7Ije2IjuMhoFlCaDSrlRn4KPqlJNm+qvHbFfVRMfppz7m8OLLZaT/7MlTNPhd2mPJm6+8QOkJ8L6B4cHnOGiTxoYb72k+w51Whk8ypk4cGwS/KOvXs8Zr5d5sUv32b/+2fPvb35Z3vOMdStX5eHyvtzJGc66JwFyLgAHAc23GzXhNBCayTtqvkIVyUECK2qlav0TAMcCO3fUosNnJANNXRLm2b9+udt8vv/zySGrxTIldIdKDOjWZq9NOO01RtNsNsDsVW/qsRbSgvuvGc6BFtKgFT9JK5Yo8uWWPPLrR9yeFa8S1ent7pl/Gq/j+wUc2TgMoSe5Ze2wY3ZXMXrNZvUZ9UerJjh27ppgM/HC+LGKnZEFPWj3PYSJd9BeRsVa5WvVElqgrRUjreetMZQ0Up53l7Jf/t/8ZsdyycN1gQyisUUa8sWiUp6i+fn14aqI+fLolUJw+Tn8Wpm+EcEwYWK89l/rjVAI7rNrzqYf27IwSB4vTxnuWyYGlNwTAb0VlfmEx/ObNr5Xz1pwz7d1Sj+HBc8WmFsCQH0QCT5bGxh0WfdDC+W7qBPjl2tT6fuMb35C3ve1tcv/99ze9MXiyxN2Mw0TgZIiAAcAnwyyaMZgIJIwAO9osmLQ3LeCnv79fqWaG0XX5PdRoMsMrVqxIeLf2HB4Uu+KKAHEWPY1aGPhtB5Wy9p5kX/D4JcOJONdsxagdkQZ8UC9MVok/9UYJNFRNseRZiQPuYQ48+avn5Lm9Y5LpWyiIOzUEkZW8qg/uG3pZWeq00lDmhXZrBfyKyfbh69sJAKz7Gkcka3x8TPL5gjiOrTKcXqZHKuJIplyjOmyJAl7t7u+UyFJRoKbTHrcvlsed18UK+XxrVP5P/yOScguScRwpqpp630IpSH1udDElGiWWOBWfJcBnlU0k3k1kpvGEjvOMxelwGE2b85JaNCWxwwrrlz/mxjXRrpOVPSvfJG6qSx3LO2XPHkoHivLr11wg1156UWRcdCz1ZxhGim58dvXnuJ1ieHHmoZ3HBMEvmd9OMJOI/fve9z752te+JnfccYd885vfPC7KWdoZR3MtE4G5GgEDgOfqzJtxz+kI6LpDQC0LCcSkALf16j+18jL+tdQGz3QjQxkUuyKjGAWAZ0LsijgA8shKEzvo2J0QX5npeOv7sQBk7nV2mOeGRhZJi2hhM1K7qUDsX3nlFSUEhuAWGxUv7x+RJzbvkmIM1eV04aiiRTdbH1xPmKkZKnEzsQcEapp1NY4Pz3C6dkYsryQVp0sBQmuiPngm+qstlh4pniM/ldfGGu4ie1T+T/eDk8f6tdB+zWyc+Q3ehKy3VynJ+NARtSEH7R4hsHZZOinLI3S43WoKeRT1uV4g9Nwq9fAm92gaCaIdPPUaGes9Td2+XCbzu0fVzV967unyG7dfFWt+ag+qpUprMTz9OdZU6Vbq/5vqWJMnsVFC5pf3Ee/cTqjrs0n0h3/4h/Lv//7vcuutt8p3v/td9S4zzUTARODkiIABwCfHPJpRmAgkigAAEkDJAgKhK+yEGmVbgsrLZ57pW3LMVCN7QV+DYlcPPvigAprUJIe1mRC7Ajhs2bJFKWRrkEdG5WRtjJe50HXDbErQoKqygNY1h4BhLLSgbJJtAvxq2uXweFEe3rhNXt07RbNuFK+u0V0y/9CzieuDwzJ+SbN97ZhHgCH02VIZCjPgd0RlXIMZTlWbaqXFdn1BIwS7UBLOeQUpV5LV/bbS552VefKFcWi3oLrGtOPLU1vlrbn1VbeD+sx5SWqh1XhVPfqIjHsp6clY0hvBEkg6xkoqF2pN1OrmAtR0ohQlTFavv1oQLbjhMdq3Sg4tuVKdAkgl88s7evnSxfKBO6+Trqyvqt1K492oP8d8lvXnmM8t9cL6c3w8CPeFjXOmwO+f/umfyhe+8AW58cYb5Xvf+96s2/+1MufmXBMBE4HpETAA2DwVJgJzLAIsOB999FElghSXOqaVl1FaRnxlptqePXuU+BYtKHaFByMKy9QAB9tMiV2xOKVfLCCpkyWOzQpGzVQs23mfIMWS7DDq4TQ2UQDELNrJDFNPHpZVennwsDy8cbuM5qdUbOv21mr4hAAAIABJREFUL2F9cLgqsd83rUrczljEuVbaseTI0SEphWQ4w/qr4GcqJxC4awW84tyvmWPIVH9+/HrZ7c6PPP23ve/J8vSoAvLMr02HLUuJgEGDBhzGyQQDxni3wDQAcGW7e8QFsJbGIiB4ZBfVAXWpzzEUtePdwa8hJlPrNpkN9mui/dKAwZW3i+dk1CYJmV/+XLRoobzzDZfImuXtt/XhngDKIFU66BuuwXDckoe4MWv2uCD4RWBwyZIlzV6q7nk8k3/xF38hn/nMZ5Tv/Q9/+ENVi26aiYCJwMkVAQOAT675NKMxEYgVARadqKzG/WJHsOfhhx9W9Gdo0J1utcJc1CYHfXR/+tOfqgXzlVf62RLaTIldEQt8ktlAgDp+/vnnt8WipdMx7eT1iQmbFdhlAWZ0Y+GsRbRqazpRNX78hZ2ycdt+8WJwSe1yXuYd2SC9Q6/UrQ/WVGKrpn643arPSWLpg7xhcSuu8m1NZVBZ9jOs9QBaMDtJBlPc9vjKRvV7sNIv/zp+rRSETGN4Jvhq2SBXlp6evBSZw56urBLzCirjRtVCExc+Q/wJgyKYcXTttKCUreuDo/od9vuwGnCOa5b63KgPrYpk0al9S2+UQvepavOIzC+bbKecskguPud0ecvrw32Zm4lLo3O4t1aVDvqGs7mnwXCrVmnN9hllcGjPMIE6BX75Dvn4xz8un/zkJ+WKK66QBx54QLFYTDMRMBE4+SJgAPDJN6dmRCYCkREApOg6sMiDJ1Sjyboi7kStcCdbUOwK0EsWsZaOhw0Smaerr766LvjthNgVmU7AL5sHZMNXrVoVKUjTyVgdL9cGyKCATVygyKMYra1Z9HPG/+m6YeZVU+73HB6Wdeu3yaHheAq56cIRRYuurQ/2bW7SYleqs8qzCX75nCnw63rS3d0l2WxOCS+Bz4uerWp9db2vnst6fr+AZadSEKtJX9m4z8reSr98t/Aa2ekuqDqlxyrKjenNckX6VQWOmddSqShupawo3j64tFRWOGixFGYjVB2X7rrKxFDBuRfjTtKA7ohJhZ3XKvW5UT/SKUdtxCUVLRuet0aOLLpE1fqS+SWjvHjxKbJw/jx5741rpa8r2poqSXziHMvGBKUvOjtM9pXGexV2hwbEM1EXq8Ev7xfAbyecCJi3v/3bv5W//uu/lksvvVR+/OMfq3GaZiJgInByRsAA4JNzXs2oTAQaRiApAGYxxIKAjCd03061WrErFjthFNrHHntMZRqvu+46teDk75r+zAKtXeqxwXFC9cUnmXbBBRd0ZBHWqbh28rpkjLDI4hlhcyS4ONXWLFpEiwUsjYwSdcNkh6k7JAP29MuDyjYJy584rWt0p8w/+KykSr5yclg2lRpcwKbboqJ0nP7UHgNAhGnBc0n2u5oib4mT6xY3P1qV+47KTur6YOx02mMOVH9kuysDsrWySCpiy3wnL+c5eyQt1TXJ0J1pxVJJZS75CTIAtMVSLpsRwCH2SNgmUfMbHpfw/jC3tlsQu0bIql7vZ4L6XO/ekwJopUoMXoNIKTMggytuU5RxMr/Ej89FX1+v3Lj2DFl7Rvtpvs08z4BQrQ6PVZqmSmt1eD7PwY2tZu4Rdg7fCWR+YZnwfuE7qN2NsXzqU5+Sj33sY+r77aGHHuqIpVK7+22uZyJgItB8BAwAbj525kwTgRM2AkkBMAOFDsYip57wVKvBCIpdUWdMJrEekH3iiSfUguj6669XwEtb9XQC/LI4wncYr2RADAukWp/kVsd+op6/e/dueeGFF9QmBWJXjTImxJEMuhbRCmaUtIiWk+uVn2/eLbsO+jXFkU3VB2+RnqFXJFOcfs5sCF/RZzKjIyOjim7b09OrMqLBpgEaFkcco4WU4njSch3XSYsnKXEqvhBZpxv2PWHZ1LDsum+xVFTZzCDLhGckl03L+HhBbUgAnGrj0mgcZPh9lexxqaW4V8XWyUlqwlop+P9H3S7Z5K6Ql0qLpOClJGeV5Bxnv1yY2i39drIMc5x4BwXQ6h3Phsa+5bfIsNWnMr/EbvHiJcore/nCfrnrqnM7spkXp/+NjmGjA3V4nR3W6vDMZ5AqHaTEN3PPmQK/1Pt++MMfVhub69at64iqdDPjN+eYCJgIdC4CBgB3LrbmyiYCx20EtA9okg7WE55Kco16x9YTu6p3/JNPPqnA1A033NBR8EucAHj0jywedOyZoPy1I6advAZg9tVXX1U/0NOJS9x6ct0vMko6M6x9StnwYHNhyM3KC/vGpBxTWEjVBx/eIL3DU/XBnaS6NootWW7GxljI4jlOqurwcghAA0iSS1XWOglasxThBLeQcrpHUiWf/hpscajl2mLJzw4XVTaecaZStthOStLpjJAljlKdDt4XWyNqvVPl6ZR5ACU/tut7G+v2YnmJ/KR0noy6acl7aXHxHxZPgeAeqyC3ZzfJGc6hJGGJfSxxqqeMfWzBRbK/Z7VSTOddg6gT75mU48g9N1wo83oa+2bH7kQHD2SOg1RpWA80nn9NlQYUJ31vAn6ffvpppVINMF22bFnbR0HfUXr+kz/5E6VtwXdcJzLMbe+4uaCJgIlAyxEwALjlEJoLmAiceBFoBgAjgoWdTVB4qtWRR4ldhV2fc1gYQb1lYYQoC5mHdtOeWbRD7YXuxz1QoW41o9FqvI6H84ObAihxA361zVGz/UPYhmySrhvmHtBlXzlakUMFW/nCZjJZlS1t1FR98MFnpLe4Xyk+zzTzGVYCC3aYCGwI1NL3EXYik1kL0FBR5hwoxXHUk2tjkJQiHHeewsA65yrqs4U2V7wdCuaXjD+nAXrJDOvaZwjvqVRaMhm/dtiyfD/hqEZWmk4EhbLCqM9Qub9beK0cdrvFFk+6rKI44kpZHBn30gqQL7BH5e7c04K/cSca8aK2O7jBUcgtlO0Lr5PBvfsUndgHv/gfi1x9/mly6dntp/p2Ymy11+T511RpssSaKg2w19lhNrkava/5HEF75lqIDC5fvrztXadfePx+4AMfUM4GZH7RuDDNRMBEYG5EwADguTHPZpQmAlURaAYAY53EouWaa65pSzTjiF3V3kgrPZN5fOWVV9SvtX8lC0iotO2wI2KxjtgVmbyVK1fK6tWr57zSM7FmztgUYGFLrKnRbvemAPR8NjfIDrOQ3nNkVDbsGZWiaymAwEKarHPdBTRZ5Pxu6d771GR9cFse2IiL8KyQ/QX0An7DRNgqqS5xytNpy8FsKrRtgGVSyyZVH6yuP9aQIhw3FvWyqZwfJ/ur71OdEe+b3BRgnhV11i1LvlCarJfledIiWmH1/7X9L6e61IYCmeEwy6j/LqyV58vLlE1Rn5Wv2kRhg2TI65KUVZHXpXbKLdkX4oanqeO0MnbJs2TbKW+QHQdHFUCkbh6RNNrieT3yzmsuUP7RJ3rjfRGkSrMRQmN+eX/wozcw9ViD4JesbCdAKTH/6le/Ku973/vkjDPOUA4Hp5122okebtN/EwETgQQRMAA4QbDMoSYCJ0sEmgHA1N2ymIV23GqLK3YVvE+tzVEwa6hFWTSFFhEZfmrVo+P0m2sB8licr1mzxiyMJoLGwhSlZyiOZGTOPffcjm8K8JxCjyZL9uhz22TznqMKyCjrnZ5u6e7uUcAhCDb7urIyPF4Q8SrSf/RF6T+ySWy3FGfqmzzGU9lNal4Bb4DfMHBeT5gpDExqISWVMYyXZJ3sOxZC9cBgkgHGAetR1wtmxKGD27YTegrjrZRKMpYv+KB4ojmOPUGTTjfcaHGttFSctMoG296UiNqIm5EvF66WA5UemW+NimNND2bZs+WY1yWL7WH5f7oek4yVjIYeFYOw3x9b9DpZfySrphbwq+nBPMfvvu4CWdTvZ4JPphbUAGBjC+V4mn5nkx1GRGvTpk1q47GT4Pf++++X3/md31HgGks99CZMMxEwEZhbETAAeG7NtxmtiYCKAIsRvRsfNyTU3bJouemmm+KeEnocABMgxf2jxK70BbTQVT2xK67FooqsIdlDfZz2oQUMkzmMatT6Pv/88wpQkd1kUWaaqHnXNkdnn322ypq0m3IeFWee2W17DsgPfvG8bN97SAoFP5tEPwAQAOKF8wekpPDLFNAJqw+Oulfc39MnNgTIdJHVqvU61teBros9k1WDZlWWr4FKdcqxFUVXi2TF7RfH+RRhSWwhxDn1wPpR6ZHNpSUy7GZV1vR0+7Cc7hwKBZZscgGAAbG9vX2xNktUvWylIoWiXzPMpoKm0IZZLE3GN5UTp5xX3sGMW9cHH5B58vWx18kxt0vRnOu1g26v+v1v5P5X5tmdFRY7llooz7qrJeVYcvrKFeLZUzXil52zXK46b27QcHk2tIgWWWL9zmaOUIbHYg6qdDvt7HiWvv3tb8t9992nNh6gPcPuMc1EwERg7kXAAOC5N+dmxCYCTQFgXXd76623Nh3BpGJX2tooidIzYCRIodVKtIATgDCgFmAcBHDc5+WXX5Zt27aprDGKxtS3miYqlhs2bFD2LNRcz7ZIDHO1fus+eWTjNjk6NCSjo2OSz6MMLILyrpPJKCBKdpi6Ut38+uCnJTe+vy3T6nmusvMhLtRA43Mc1pQ/sZUKzUJnUraqdY5qPjB0m7Jz8inCpWl1x/XuicK05VKjO7WJUPQcWVdcI1vdxTLmplT9LPW0Wask86xxuSXzvCx1plS4g3RwMr9x63rpE59L4lLwdzIm/IbrWyxZXQOScfNVw0EkCwr3UCUtXx29VA57PbLQGgmtIYdRcNjrlYX2qNyXe0x67Wof6ai5SfL7gmvJL+3XKDVrPke5XFZ6chkplsrS25WV91x/obDpMdda0OcXFoV+Z/N3KNK6djiJanhtDHlvfO9735N7771XXZPML1lm00wETATmZgQMAJ6b825GPccj0EwGmJrYvXv3CgA46a58s2JXQdozC2P9E3f6AM5kF7TasM56A3JZVPl+m30q68sxAGPAb6uiTnH7d7wfpzPi1GJi/6Q8e4+TNjxelJ9u2CZb9x2RSsUVq1KSA0eOKvqkFmcCAAOEyQ5ns9QNi3SN7JT5h6b8g5sZjutWFPjl+eJZaqRwW5f6nHakOAHy4vSBbDHgKMk5+rrKQijVHVkfDOR1ayyPKp4l3y+sla3uIhlyc5KWsqIJV8RSisr8fYE1KnfmfiWn2MNqQ4LPWSM6eJzx6npZgL9utRZLJSstKa+s4gI4ov5f1w2jAj5cycjXRl8neyoDkrOKkrOq1aG5LkJYJS8lq5yD8q7cLyOF1uL0PewYqN2b5CwZ6lqhwG/wHePYtrz9yjWybGF/s5c/Yc/jWUHwijICSk7QXIBxorPDqP3TePdDkdZguB7bIiwQfI/86Ec/kne/+93qGmR+YfiYZiJgIjB3I2AA8NydezPyOR4B6nmTtOeee0527dqlKNBJduJbEbtiwcvihcVPUtBdOzZt1wHQ5QeKpl5Y8TvodoC8dohoJYnr8Xgs8di6dasSGmvW5mimxvXSnsPy5JbdcnDIt8Wh78wtC2rAGBlaGsBIi2h1ZTMyMER98POJ64O5Hgt07kPWt9FmCdnXMGEmMtVkHjW9N0msyAZXXDexSBb3aGQhxO/DwPqL5cXyQPECOep2yzxrTNKBGlmSxEe9bklZrqx29snN5cdUDS/WRvgft4MmD5BFGbtW0bviQQ33pFIYU/fUseQ9kctmxEmlFAh/snSG/Ly0WtG2+6xxSQPdYZ57IkVJyYiXkwFrTG7KbpaLUnuSTEXsYwF5u8v9srP/dbJ06TLJZjNV565dtURuvOiM2Nc7WQ4kLjCLKCOAinz66adPGxrfUxoMB8tb2HTSYBi7pXrfDzwX2Bu94x3vUMyQhx56SCnXm2YiYCIwtyNgAPDcnn8z+jkcARYfSRbg+OFu375diWDFFZdqh9gVC5t2LKSDU8249+3bpzK/GiBpkIQyaTsodyfqo1Vrc0RGPO58z9aYC6WyPP7CLtm4bb94gTpbQE6hkFdAeGzMF6qikU0FvPZlHVmWf0n6R7f5iCiilcslGRnxlXtZTDfaLEGMyvIqk3Y/wUunU46UEnr+Bs/XIllkg6N7PX1QYfXBlYk62tqjv5N/jWypLBFHKtJjTacHkyE+5PXKAhmSXyutk1MyhYl6+/apGCtquw1dfEqgqhqse4o2y/wChrFYAiArt6ZUVn5sXyk7vFNk1MuJJe6kDRJjRRn6nNR+eVNmo3RCeJn37NHxirw0/zpZsuL0ac9Mf3dW7rnhIqWsPZdaEPyiBYGuQFTjXY2GhAbE1BHr97amSvNncFPqZz/7mdx1113q/x544AG5/PLLo25jfm8iYCIwByJgAPAcmGQzRBOBsAgkBcBbtmwR7IewQULpNqo1I3YFsGCRU0/sKuqecX/PAmrjxo3qPvhMkv3VmeFjx46pywC6ofzquuG5QIsGRFDvS6blRPQ+3nN4WNat3yaHhv1scG0DIAGEyQ7n8z4DApA0zynI6aWXZMAdqptJmvKytaS3t0d519ZrYVRifSwZTV3fGvd5rXecrhcN0oSTXNP3Dy769b6ARbdaAZn//rf8dTJY6VMqymR6w9rhSpfkpCC32M/K2h7/89OJprPfBSsrqUp13a++H8eMF4oKCCOkVS5XVM3y0/b58op9uuStnKoPxgu4xyrIhak98vr01lAhr1bHUCwWZGR0TLbPe730nQ67ZPoz89Yr1sgZi+e1eqsT6nzmhswvTApE9RC8Stq0AJ32HNbvbUSuuPbNN9+sanw/+MEPKiYAFOh2etgn7W+j4+nvgw8+KAhN8rN79251eNQG9Ze+9CX53Oc+pzZy2Yy74oor5CMf+YhcddVV7eyeuZaJwEkZAQOAT8ppNYMyEYiOQFIADB32pZdeUosI6qgaNS12BYik1grFzUatGbGr6BFOP4L77Ny5U1588UVF44byDH0u2DTlDkBM/bBehACStb1So5rPZvp1PJxDNoU6bxalM2Vz1IlxQw9++uVBeXLLHkUVrtcARj4YHlOUaeZ5oLhXTiu+LL0ONN6petJ6XrZ1r53qnlQiDh4DYKVPMZLNiUJTjyYc5yLUB5dSfZIuD0/zD+bz+4XRa2Wf2ycLrJFQkMiG1VHplW6rKHfknpc1qX1xbtv8MbYjaYSSioVp2W+fWu5VxVdbvgG6Rkoie6zFUpC0ZFMiZzjHpD9dbruXNYPTz8zh7lUiq28JLRs5d8UieePrzmo+FifgmUHwe9ZZZ7XNgkg7AfzN3/yNfOMb36gqcXnb294mv/u7v6vYS8fjRib9+6//+q9ps9kIAP/RH/2R/NM//ZPSH0CXg/c3VG/O+eY3vylc0zQTAROB+hEwANg8HSYCczQCLESC1hNRYYD+DA0aClk9MaTZEruK6ju/Z6wAX+qYoa9C7a2n3KuvR4y0vRJ/6nghnKVFtOr5vsbp0/FyDDV42ByxiGJRSkam3bTzmR7rkZFxWbdhm+w6OKVOXK8PzOvY2LgCxGMjw7Jw7FVZkn9F0pZffw7Ig4rPvEfVolecnPKjrW0QggHAzVgaxYldGE04znnldLekSmPi2Y6gnuyUp2yAUGP++shr5OXKKZIKoUATFyjQR+0BWWSPyDtzT8miBnZDcfoTdYyuqw7LfiOc1Si+vJ9gOWiLJcByyc4Juta5lK0ytGT2W332Nfgtpftk5Lx3SCqbmzasrmxafuPGtZLLTNkgRY39RP8979NnnnlGELbCe5d3TScanvV/8Ad/oDzEmXPEG2m89wGLKEHfeeednbh1U9f8xCc+oVgpl112mfqBDs4zVA8AU8d8yy23KJYOY4VCTuPvgHy+19BwYNPWNBMBE4HwCBgAbJ4ME4E5GoGkABjgiBDWJZdcEuqPe7yIXYVNJ2OF8gy1F/C+du3aREJeXJPFPudDn+aHa9LYgdeZYTLjrS6eZ/pxJMu9fv16NT7o4MuWLZvpLnT0fpt2HJCfb9oh+dJ0BeCwG7PoZCMgP3RI+vY/LfPyu5QXr/ahbQSSXMtRVGK7hkrMfTIJVZ+bDUoSkayKnRHbo2Z2qpLYrw+2pMsqqZrb58unyoOF8+WY1zVJg+ZwlLAR8hq1e1Xt7DnOfnl77tlmux3rvDCRLsZbqlRUDW1SarkPhktCDXneS0naLSh7J9gh+idqw6O249Scs5liOY6MrLlT3J4loWO7/ZKzZfXyhbHGfTIcNFPgl3fZHXfcoTLA3/3udxVQ3Lx5s7JA+v73vy+PP/64/OEf/qH8/d///XEbVjQXGgHgN73pTfI///M/8g//8A9CJjjYGNtnPvMZ+dSnPiV//Md/fNyO0XTMRGC2I2AA8GzPgLm/icAsRSApAB4cHFRAicxpLaX5eBW7IrT0jewmO+ztovaSMSS7oOuGtaI2dVgaDDdSJp2lKZ92W+Z006ZNSiGZTQEyCidjGyuU5NFNO2TzroOxhoeNEnPLMzNg5+X04hZJj+6tEkzTAAlArH1u6wlJdYr6XG8wWiSrESBsVKdMNrnkdIlUSuJWyvLdwmtlR2WBDHtZyUpJUl5JZX6LdlbRohdYY/KW7HpZ5nSu/hcFa19UbLrsVzpliyVWlUhWrIkOHMRnOl92ZbwsYhdgDfgiXnw2mOMgJb7etdk44X3jOLaUV14hI6eEqw2feep8+bXLVyft4gl7PBsNZH6p04VdQua3ExuFvMtuv/12VcbxrW99SwHh2vuwiQlderb9zBtNZiMAzPPFdwvfOZTzrFixoupSjz76qFx33XVy/fXXy8MPP3zCPjOm4yYCnY6AAcCdjrC5vonAcRqBpACYrCdiHRdeeGHVl24rYle69rcTSs+EHZBKXStjhSaGzUa7F16MAUqfBsP40NIQXoEmrRWltT/p8fA4BG2OqInDFgR678netu8/pmjRQ2PhAkqMH0/hffv2yvh4Xom9LV58inpmuke2S9/+Z0TGjyi1YRb1ujHX0jUgPXZJbLtGzdcSSdm2NCtU1cqcNBLJKqd6JFUeDb082VRlPST4B3dJsVyWB/PnyY7KPBlzIUM7KuvbZZWl18rLLdkX5DTnSCtdjTyXzLRTCbduAwCXyq7KAjdrERXsQNlKqzF7+ZFpFktT2WGoy1NK1/n8uHpm+JynF54mB067TcSyp40rm07Je2+8SHpz1VZIkQE4QQ8Igl+ovYhetfsdTGjI8gJ+YbTcf//9iuLcifvMxDQ0AsB8n/G+5nuF75zaxqYd7y1AMrEwzUTARCA8AgYAmyfDRGCORoCFSdACKCoMfJmiUImypvZrPF7FrhgL2U3UMWkIcZGZnYlGPS2bBdgskYmgAfDJrmpF6SQ+yu3uM5kuFosojbJQYjF1vNsctTMG2A/9YstuefaVvUowqQr4lCvquSFDNDDQLwsXLlIq0ZPNrUj/sc0ycGSTyo7qWtLxsiUOVGLxVPaPbCFzDDBup+pzs3GoFckqO7n6KsohVG3PcmS8YsvOYyV51V4ppUy/ZG1PVjpH5Bxnn6TrqEM329/a8xqB9dr4tmoRVfU8pLrEcsviFccmLZa0DgD30WAYQTUycsprun+e7DvtdilnwoUC33DxKrno9Jl5F7Ur/s1eh+8Y2DdsRPKdwSZkJ0Ap4oyAXwDh17/+dbn77rs7cp9m45D0vEYA+L//+7/lrW99q3pvk1UPa4BfYs7G7FzY2EwaX3O8iQARMADYPAcmAnM0AkkBMPQ1RDZWr16taGwsOrBFIoP4ute9LlIZeiaVnumX7huU7f7+/lmZZehqgGEWZmTK1UvXstTuvAbDMwk+T3Sbo3ZO4oFjo/KT9dtk39ERdVmyuoBfYkSdOAIyVeA3cHOnPCbzDq+XnuFt4irYa4tbHFfAGbaBFq9BlMlO6XrS6oxhO8cS51q+SJYl+Qp+R+F1yvxe1ffWbAwA9PE/pma4t39Acla8euo4/Yo6plHmN0z1WV+vVYsofR3PEqk43UrYTPkLVypVFkv6OJ4VPsvDp14h44suCB3WikX9ctdV50UN+aT4PXECoAHETjvtNPW90Qnwi9jTbbfdJmzGfuUrX5H3vOc9HbnPTE5KIwAMwL/nnnvk6quvlp///Ofhz9mKFWqDk5+TTdNhJufB3OvkjoABwCf3/JrRmQjUjUBSAExmky9caGzQfAF1iD7FySACCMic8MPfWQjpn3ZOEYsusr6ofrLzDfidSYDZaCyAIw2GyabrTBIx1IrSqJR2qpGhIhtDVppFEZn8pAI/9fp2bLwk2w6NSdn1ZOlATpYNTFe97dS4Wrkuz+KvXt0nP13/suzctVvNyaJFp0h/fzw6eCZ/SPoPPyfdY75vp25aXKlSLgkZZ5ovopWazA53AgzEiYWT6xWvOCrUOde2NIJSE/3Vvwv6H/OZIssJLdryymJXfCG4TjUsmjw7Vfc+Yf2t7YsWyWrVegrvYKXwXR6bJD6jGl4oFNXc8iwNpxfKK72XSSabke7uHunp6ZZsNqc2UlKOI/fccKHM6zkxPhutzCnvYd41bPr9/+ydB5iU1fX/z8xsX5aO0pEiIIhGBQUFBEVURBPU2KJGTdSfGv8ajRpTLFFjrLHFqNFoYk00xt6woGAFDV2K9LIgnd1l++z/+dzhLi/LzOy0d9qe8zz7LMrM+977ve8M93u/53yPm+R35cqVhvzy+/HHH5fzzz8/48kvuCsBjufp0/cqApEhoAQ4Mpz0VYpA1iHAJsVZx9jcBDF4wVSDtD9ULoywSC1urrbVkl/72616Xzbq1EehVEMoIxlbc3N26+/BHTMW0qRpr2RT0SHA1kQLspEokuRsc0T7EX4Sce1NFTXy76/XyvQVW6S8ut6ohwW5XhnYuZWc8oOu5ne6B+vw+fRvZM7aMmkobGdIS7RRVLZC2m6eKTm1u2pqbWpuKMXQSYYTdRDR3LhtKrEXMp7j3c01OViqtm3nw/hIl3d+1psqo83dO5a/ty2Pgr03mtTyWFuVcjRDAAAgAElEQVREBbuv35srfk+O1JRtMmnPpLmDjd+XJ0s7jJXtNQ2mt7T9TJMSDxkee2AfGXvwgGa/L2PBKZ3ew7z5HuaQr0ePHjJgwICEfNc0nSPqJmnP9Kd/5JFH5KKLLnLlPqnAVlOgU4G63rOlIaAEuKWtuM5XEdiJQLQEGKI2Y8YM825quSIhUZBe7uO22ZWT4LlptOLGw4PqyGbRmmjZ9kpsgiwZDqTjOotRIx+JW22Ovi+rltveXixLN1bIpopayfGKQKyq6/zSuiBHurQtkF8c2VuG9krfXpROF2wyGTbs8MvHc1dIRVVN5ADbV1IfvPVbabN1vuR5/caUqWmw1qwvKcW1jrZM0TgNRz+wwDvo8evxB+qUbZCizX+Zw6mGwG8b1tG4uf7H9A+u9+ZLTl3A/C1REazlkb12qFTt5u5NX+N6f4P5iSdQfstrUPR90rYoz3w2N+49QnaU9DaXBcbA4UGFIcNFPr8c0aeNUYFJr7fGeOmSnRIPFs73Oskv7sQDBw6M+Xsr3JjI8IH8Llq0SB588EG57LLLXLlPonCJ9jpqghUtYvp6RSB6BJQAR4+ZvkMRyAoEoiHA1uyKDTxkbPjw4c1i4Ex5ZmPtlvKLgjd79mxDtNlwNW0L0exA0+gF4ORsrwQJIVDdbZo0ZlqRKoaW4PH6Aw88MKFtjm55a5F8vmyzlFfVS9c2+VKQG3A/rq33y/dlNVLr98u+nYrlnlMHS+uC3DRCOTAU0iYXLlzYWMOOikfQE/bT+atk7ooN0uAgi5FOILe+Utptni0F25cGmFCIYK0DZDjwY8kna2Xb7hh36QREIJU4V7z1wYl9YV6O6ZNsh0vtOs8exBxcInneUEDpg5xTVxn3iE3LIzyog/RT5uLWpTqWG8VrkoXLLtkmKPjFxSVSn1soVQV7yebORwQdDtj9aFhfaagOmOORFmzXGm8C6xQPzrEecsWCQ6Lfw/cvbfL4PqbdHCUWbsyHg0LaG1Hqcs8998gvf/lLV+6TaHyiuV6kbZBWr15tsHaGtkGKBml9bUtGQAlwS159nXuLRsCqUeFAYKPmNLti49exY0c55JBDwm7snTW/vNAt8ksfREgMG3UIHupKtgQYomyTJs3GmT8TzJU1QB3mdzCSxHuXL18u3333nSF4iW5zhOp7w+sLZcmGCunTsUhQEp1h7r+5UjoU58nPjugpJ+y/d9osC2MDF/Ah5RxsCgsL9xjf2k1l8sHsZbK5LDpCR3ub8qoaoT643cavJb8qkt7DkOG6RnXY1ucG6oZzhf7SrHOshCKcmmrJJKoqCv62sjJT15qTA/mNPg2fXsji94vPH4OKvnMVQvVTjpf8OhcZkyxyKjiwiSxIbYb81po14dlhPRhraY8J4vcFr+09tH83GTFwV69WDjsgiXymyaqxZSiQHkuGM6GHuBMz/i2B/DIfN8kvuEF+58yZI7fffrtcd911MX8mIlvz1LwqHAFmRBMmTJC3335b/vznP8uVV1652yCvuOIKeeCBB+Tuu++Wq6++OjUT0LsqAhmAgBLgDFgkHaIi4AYCzRFg6xjsNLv69NNPjbnUoYceGnRITuLL9S3xjXXjHmre3If0N1Q8yAskxk0DKTfwj/aa1niM9aDOmQBXFGGrDkOUwJ1DAdQBt9ocvTyzVJ76fKVU1vilW9vgG/8tO2qlvKZOjhrQUX57XP9op+vK68Hm22+/NY6xmI9hkgZmoYKesjMWl8r0xWtNf9nmojg/Tyqqdyd+RWXLd9YHR54ibE20IEvOVmUBMhxwlfYE6TEbbHzh6mh3d1EO1K7662rF6/NJUXF8iiSkm769noaACVikEY6se2k+HMSlOtJrB3tdXm7A+Cu8SRaHURXmgALsW7XCrC5QkvB9lzFSVdw16BDalxTKWUfuLz7vnv2AeQPPIxkfkGF+UN4JDjvs55pDrlS2TWsOWyf5xVxv0KBBrpBScJo4caIx17rpppvkhhtucOU+zc03GX/fHAF+//335ZhjjjHPCJ0ZKEki+PPYsWPNv4m4Y5OtpaEIKALBEVACrE+GItBCEQhHgNmI0cICx+AuXbrI/vvvb5RHTLBQFEeMGLEHasGcniNJnYwWfsgBCgBqA//Ao/yGIzHRXj8TXk96qrO9kk2pBA826ShVqOEHHHCAK5vn56evkWe+Wm1SOfcqyQ8KWXl1nWysqJGRfTvIrScNTDmsEElS5XluIBVg05yBmx30lvJK0zJpzabtIecBySG9ti6IokgvWeqD+eHP0YTfX9/Yg9ZpWgdJsupwqM+Z3+sTTwP9DoOTUOuizDryzFiCV9KqlUAMq2ujI69N5xXMOTnc3Gmz5KWfcgg2Gk/qc7j7QqxzvF6paeKAHXjPLvLL4UPgoC1Afsvb9JPNnYIfBnrEIz8eOUi6tI/MCM6ugSXDzkMuPtdWHS4qit6kLZrnLZrX8m8InynGzL8TgwcPdoWU0s/2pJNOkunTp8tvfvMbufXWW125TzRzT+Rr33zzTbnlllsaL/nVV1+Z79bDDjus8f/9/ve/N+q3DZTf+++/X3geIMNkZ02ePNm876WXXpIf/ehHiRyiXksRyDoElABn3ZLqhBSByBDgH0r+0Wwa1Khxys7fNTW7og0S7xs1atRub0uW2RXEj7GRDsyGC7XBDZIdGYLp8SpICxtQjGFIEbSBUm9NtGy6ZqJG/Nbc9fK3T1fI1h210qt98A35hvIak1567KC95OpxfRN165iuA0Y406IixfPczFu5QabNW2nqZZuGTX0ON0DTP3jTLCkuXx62PjjUNRoaAiZapOFaszReC5G36rDPt6tuOGwqca5PamoDBnV8niDXHCQFXLADBA+naLhoMFIfzUL4fbnSIDniqw+dTk61tN+Xb1TjYAEhZ7xuBgQbpd+aZAVIablJT2+KTV1uKyntcbyprQ4WB/buLGOG9Ip5uJhocVjDZ5vPtW2bxmfZkmGyGBKdXRPpgBkPB5FkpLhJfnk2IXOom6T03nnnnVn3nf/UU0+ZFk7h4sknn5Tzzjtvt5fwvoceeshktfB84s0BUT788MMjXUZ9nSLQYhFQAtxil14n3tIRCEaASQ1lUwOppI0QrY6cwSaEjdmYMWMa/7c1u7IbNLfqfVFEIDEQ8379+pl+xKna/KXbs4N6h2LPAcHee+9t1o+Ns1UMUQksGcZ4J17cNlfUyFUvzZNF35ebFOjivN3Nmur8flm2sVK6tSuQXx7VR0b0SV1tNpiADRj16tXLHOrEM/8d1bXyydwVsnDNrsOGSMiv85nJq9q4sz541zWifab4/NbV7SLDThMtkyJd2FYKPZDJPd3Dbeozn9mysnKTZk1mRzB1MV7TKOe86KOLohqM5CY79TkU3na+mKGBza6DAUePbo9H1nUbJzUFnYJepnVRvvxkzBBj1pWIYH1wc7fqsD24hPSQzQAhJh020oyGeMfkJL/8G0GGUDyfqVDjoezjlFNOkU8++UR+8YtfGMWzpR94xrt2+n5FQBEIIKAEWJ8ERaCFIuAkwE3Nrg4++GBTI9k0SM0iLfroo49ubG1kCTCvdYv8YgQ1d+5cMxw2W5A8jQACKPYcDLBJxnnVuoKyLvwdCg0bZw4uCIiOrRmOx2znb9NWyDvzv5fSbVXSvjhP2hTkGBMlk/pcXiOtCnJkSLfWcttJA/cwyUrW2jnbY/Xv398Q4ETF8u+3ykezV8iO6hqjkkZSI7zbvRsapKh8hbTbNFNQhuMLyDCp0rRXqpWaBp/4GuqEklnIsP2xJMX0AK6pM8ovzw01h8GMwJxjMiZZXk/Q9k7Rjh2y6/VXi3eny3Odr0By6gOO58HCrdTnUPfj+7ByR4U5YMjJ3fNgYHu7wbK1w4Ehx/uj4QOl1157fn9Gi1Ow1zM2UoItGbbmeHz32hZLfL75nLsRfK/wXcx3Mt/DfB+7QUopwznttNPkww8/lIsvvlgefvhhV+7jBkZ6TUVAEUh/BJQAp/8a6QgVAVcQsAQ4mNlVqP6UX3/9tUnLGz9+vEnJs3W/bKztTyIH63QzRu3AtCgYMU/kPTPpWqQ9sxllA0pNK2pQuE2z7TWMskJQR+psrxSNglRT55f7PlwqX6/cKpsraqWsus40DSrM9Ur7ojzZp0ORXDu+r3RuHdwky22cSXfmYIDnm9pE0jQTHZgnTf9urXy9uFT84V2UQt56V33w/JBtf6IZt188UtfgEX91wLF4dxOtHCkqLBB/g8co4nyGIb7R9KOFjJISHet87VxMfXBOoXhpm+TxijdEbXQqyG95eZk5UIBEtm3dSurq6ZMcaGlVk99O1nUfL+IJru7u16OTjD+oTzRLFtdr+SzbVGm3Wywli/xyWHfmmWfKu+++a1KD//a3vyVN3Y5rMfTNioAikDEIKAHOmKXSgSoCiUcAkhDM7CrUnSAUkC4UYAKCCvF1QwFgs0WvR3rZ4mYM+W1OpUo8Qul5RXBfsWKFaVHFwQCKPTW/kYQ127FkGEWfYA2d7ZUicZ4l1fmzJVvkw0UbZfnGHYYAty/OlTH7djTuzyUFieljG8m8nK9BHcOch8AkLdTBQLTXDfX6Ddsq5P1Zy+T7rRUxXzJQHzxTistXxFQfbG/cNJXYmt2hDvvrA71+bXdi1pjPVDQHH+ZZ8XiENkLBTaOig6A2p5V4pD5o/2DuQwa3bQsV3ZWjfzWfDT4PAVU8XwoLA/XtxiTL55XqepF13Y6V2vzg7rpF+blyztgDpKBJSUD0I4ntHaj/zrrhRLZYAhsO2/j+p5yCEhk3vvd5Ts855xx544035OyzzxbqXKN9PmNDT9+lCCgCLQkBJcAtabV1roqAAwFS56ZOnRrU7CoUUNQHr1mzRkaPHm3UQ7dSntkE0VcSgg55YbMVrN9tS1xQNqK0OaIHcrg+tpFiQ6qhJcPgTXCoQXq0rRuOJJ0SwytUsjz6q0JcUhTUsXNwwvNCe6xkZQxA0mYtWy+fL1gttfWxmzXFUx+MoupDUQ0V/nrZun37Hi1/+BxzkAIhjuZzRio187amUdEuubNFU70vkLLrrA9OhvGVHTMGY7YeOlRKeGXXYbK19UDTNilYTBi6r+zbNXX17s4x2RIImypNLTwRS4slvnPmzZtnDiPJGCHbxA3yC4G/4IIL5OWXXzbpz88++2xUz2O0z5++XhFQBFouAkqAW+7a68xbOAJskL788kuhd2NTs6tg0Nj+siiP8aTONgc7qZk4PUPMevbsKdRuppJQNTfeZP49yhSHEGxqIaiom5EotZGOkYMHWzOM86w1VoJEWjKcTm1Yms5r+fLlRhWHwKCKp6I3dNmOavloznJZtj5wmBBTmPrg5dJu06yI64P93hzxSEPINGqv+GXzVshvg8moyM3NMe7GAVdp6pgDujCftUCv4TzzOW/us9doGgUptNJyBJM2423wmx9nmPrg+hop8PmFNPtkhJP8FhYWSEFB4R63rSrcS77verQhfsUFeVJWubtbdd8u7WXisEA/1nQL6/TN9wYKcTQtlngvB0ocLLlJflGrL7roIvnXv/5lXJ/53dLa26Xbc6PjUQSyGQElwNm8ujo3RaAZBKwxUriXsQGytb68HgWYjZRNnSU9DZUWQxScSKNRkJreF6dTlF+I3oABA6RHjx66hjsRgKRwMIABTjytfCIFlA0pm2UIMb9tLSnkyZJh/twcQYr0fvG8judz0aJFsnLlSkPuUH6jqWuN596h3rtozSb5eC4mWbUxXz5QHzx/Z//g8KoyDsu+EEZSpD5v35nqDj45OXu27mG9IcO1tTVSv7OXMeSW11p1ONxakyKM7k8mQCQRrkWTx0t9cJF4aitC9gSO5B6RvIaDPWp+mXOoemi/N9e0PKrP3dXTNz83YPpWWVMr/PmcsUMMMc6E4HvcKsN854ZqscRcLPnlO54DNzeUX75bLr30UnnmmWdMr9v//Oc/rpl4ZcL66BgVAUXAfQSUALuPsd5BEUhbBNjw2s1PsEE6yS+vc5pdodDiBApBsooCmyNIMGSYDVM06iTEmn6GEGpSnt2u20zbRQkyMKcq3rt3b+nbt29Siadtw2LVYduDFpJpyXDbtm2TOiYLE88l6ZnUJjIGasWjee7cfA5opfPp/FUyd8UGaYhGHm0yKF9thbTdPFOKy1YEHW64FkIcnFRV7hB/g5jDgUgOqFjvABmubWylxY15r1WHQxEh0pbr6sKbZNXlFklObWjna5v6jErs9+ZJTtwu2cFXOdAGqqxZM7BNex0mFa2D97JuVZgvIwZ0lUE993LzUXLt2qw12R5WHXa2WGK9MdnCXZpDJTfIL2twxRVXyN///ndjrvjKK6+o14Nrq60XVgQUAYuAEmB9FhSBFoxAOAJsVV/r9hzO7Ir6MltHihMpwevZOEGGSZ0Llc7Gfb777jshfRVCxUaLjbpGAAGnm/HAgQOle/fuKYWG54ExsWFmzW1tIetrHaVZdzc2y00nbh3M2cBzbw5O0tEwZ+2mMvlg9jLZXBamPjeCVc2r2iDtN34jeVW7+gfXe/PE21AjniDpxyh9NVWVUt8gxiQtFmysiZYlxHaYXIuDBta96XWtaVRN7Z6qdbjxcu1grs/B6oMjgCvsS5zkl7T+UHXulcXdZUOX0SGv1aNjGzn58IHxDict3s93MYeZfLZXr17dePjhVosl1uCaa66RRx55RI466ih5/fXXg/aiTgtwdBCKgCKQVQgoAc6q5dTJKALRIRCKADclv9GYXdk6UsgR6XW2tpCaVUuGbXoq6gPOoryWOlPUO6372rWGtv8xhwmQO0heOoV1zbWHHyjVBMqRdZSONy0+1HydKeH0PuZwIBmkO1b86RM8Y3GpTF+8Nvqewc6bNjRIcflyabtplnjrK6XBkyNe/55p1hxMVFdVisfjlVYlrcTrDd62J5r5sN6WCPPbfrbBPUCGrYlWwAStqUlWg8cjDd5cU+MbLEi5Jq04lKlWoH9wTciWSZHOxe+vN4ZXELBw5Je08tIeE8SfE7yVV47PJ2eP2V/aFKem1Vek843mdazpggULDAG2tf+UQHDo5fQE4LuIH+rsYymDAPvf/OY38uCDD8qoUaPkrbfe0oPPaBZKX6sIKAJxIaAEOC749M2KQGYjgILm7BPKbNjk8P9sanQ05LcpGmySrVKISmevycYKlRDiBGnChGvQoEExKVSZvQKhR4/ZGHWtHAigirdu3Trtp8pa2jRpZ1o8a02qdLhMgGgmR/o97btIz0xFSng0Y2362i3llfLBrOWyZtP2eC4jHn+ttNq2UNpumbeH8RX4VFVVSl5OjhQWQ369cd0r+JsbjEJIr2HnQRpkCDJsf1CDjapbWy+1ucWSUxu6VVQkrs8NNE3KKRJf/Y6Y6oP5bqPmF/fq4uIiycsLuE8HC5RfFOBQMWpwTzm4b+L7S7uwWBFd0ukwz2eWA0mr7odqsUTdNIddfLY55IzkWePfgZtuuknuueceGTFihLzzzjsZ8f0WEYj6IkVAEcgIBJQAZ8Qy6SAVAXcQcBLgpvW+3DEe8tt0xE5TJUixJcMQPMyuUIdT4drrDrKxXzXRbY5iH0l870SBtIcfpMU7MwEsGY6lrzM1m5iBkd6LURpO4ZkWpq3Myg2mPriqti6u4VMf3G7TTCmif7CIORQAG3rRFhShzrlBfvccMsQSVR6i5DxUs0Q4r1VbyfVXS10IkyzU4tooXJ8bvD6p9+ZHVR/MuHh+wJ/vmnDZJuWt+8jmvYaHXJvO7VrJaSMHxaR+xrXgLr3ZaSQHkeXQLVTKfDwtlrjPH//4R/MzdOhQmTx5sqnd11AEFAFFIJkIKAFOJtp6L0UgzRCwBDic2VWih4xCSCsfNlFsfOhHzDiIdHQYTvT8w13PmRLuRpujZM7Fea9QmQCo2pYMR1L3DZGeOXOmIVj7779/RO27UjXnSO6LQ/Qnc1fIwjW7anojeV+w1+RVfi85yz8RX/l6yc/NlaJWxVTix3q5uN4XqBuuMeown22/eIX053yfR4oL80W8gR7iNppLfQ77mTH9gz0hHbDte6Mhv3W5xSb1mXTtYMHYzxw9WDq2LooLp3R5M9//tA8j64TvZFqIRVov7myxxIEXLvWE7SXO+pPxQ4kCr73rrrvk5ptvNuoy5DdTzQ6nT59u5jJt2jRz0MeBCmUq9DE+77zzsuZgJF2eUR2HIpBoBJQAJxpRvZ4ikEEIsDkxG1S/3/ywQQlndhXP1Lg2Gyw2WqhCbIDYbHFfaoVtHal1GKY2zzoMQ5RiqTOLZ7zJfi/qGeSO1GHUcAheJOmEyR5nvPeDiFBTaNuw2MOP5tbbHpzwHNCOhdribInl32+Vj2Yvl+07du8tG+n8+GytX/+9VJSXS1fvJhmYUyoNNaFTjSO9biJex9h2+H3SULld6uqoG6bOl/pgn3h8gVTpooK8uHv+1ucUCm2jgtVD19fXmZpfxtKqVbHpcRwyPB5Z3/VoqS4M7ep82IBuMnxAas3oErE2XMNpQsj3McpvJE7hoe7ftMUS7s44O++zzz6mzIVa38GDB8uHH35ovt8zMWjTdPrpp5uDOA4L+vXrZ77Ppk6dav49Peuss+TZZ5/NxKnpmBWBFoOAEuAWs9Q6UUVgTwQCNXw1jeQ3kSnPzrtBcjFWodURJ+VssoKlv1qHYcgOBlC2JYdttwMxRE3INjLsbHPERpENVbbNMdjnz6ZS2sMPu9448jrbK61du9a0yMqkeuhov29q6+rli4VrZObSdeKHJUYY1LKuX79OduyoNOSub68eUrGjQlpvmS+tty3Yoz44wssm7GXOFk2QrUC/4YA67DMCdYPUN3gaew3n5ubErFwH6oMLxVtfJd6GQD9i7keWSYD8tmq2Rdb2tgNla8eDQ86/Q0mRnHnkYPG5UledMNgjupCT/PK9CpmLh/w2vSkEEWfnF154QT7++ONGdZh64R/+8Idy0kknydFHH51Rzs88T5ju8Z0FyYXs2uA7auTIkeZAF4I/duzYiNZBX6QIKALJR0AJcPIx1zsqAmmBAErjkUceKccee6zZjLjVQgZFd/bs2WZTgLHKAQcc0Owm1GyLd7bksL2Gne12IEeQYRSLTFdJbZsjcCJNkHrolhh2vS0ZxsiJYH0hypBfNui088nm+H5rhWmZxO/mor7eb/of89lo3bpEunfpIjX19Y311r7a8p31wSubu5Qrf08fX09DfUizKpRZvyHEtVJdi5O1R0iHDtQN55nfsRwENXh8Up+TL1K53RhecZ4QCfmtzW8rpd2OFQnhmO0Rj5w2apBQ/5vpwedtyZIlsmzZMnOomGjya/HhPk888YRceeWVsu+++8oxxxwjH330kcyfP9+8hIPQc88917RCyoSgawH/VuI/wKFu06Cn8QMPPCB33HGHXHvttZkwJR2jItAiEVAC3CKXXSetCIh89dVXcsIJJ5h0VKJv376GCE+aNMmkJyeCWGLIQ1ovCif9a9k0xHJd227HkmGuS7BBtr1nSYmN5dqpfBZsWi9j4GAg3docpQobW1fIJtnWFDIW6hJZZw5AqB1k/bMxUHVnLlsnXyxYI7X1e/bSZc6oa6WlpVJdXSNt27YxuBTk5gQ11cqv/F7abaJ/8OakwkX/Xl996LTu/FyfVO/sFeyRBqmtqZHK6mqpq9s1ZxRJDj9Y62g+36Rbb62oNvXH7YtpzxT+WWnweGVd92OlNr9dSIwO6tNZRu/fK6kYunUzyO/SpUuN+zLk143PEp/jp59+Wi699FLj1g7xtaZ19H5HHX7ttdcMoYQ0ZkJQwtO/f/9mCfDjjz8uP/vZzzJhSjpGRaBFIqAEuEUuu05aEQggQMopqWnUNFGnBcEk2KRAhn/0ox/JsGHDIjZEceKKYdGsWbOMusOGgWvGouY0XSs2VRBqS4ZJbyRs71mUYchApCYuqXoWVq5cKQsXLjSbew4cUGE0Agig+KK0sMaYgXFwYuvEUcwJniVneyXSprMtynZUy0dzlsuy9YE52yANc+3aUvPZat++nbRt205aF+VJWWXw/rrmfTgfly2Ttptnia8uoK67Gc7U52D3yfV5pTaIIzSkuKqmdmd7JVyld7lk85mm1zDqcLjPN7jwvYCa3KpViXgKWhkl2lu/Z79kO7atHQ6U7e0Gh4SkdVGB6flL7XKmR7LIL6nPF154oclqmTJliiHBwcJ6T2QCrhw88X0EhqFSoG1qOd9PGoqAIpCeCCgBTs910VEpAklHgE01jpaQ4f/+97+mXpeg3olaLQgxPRsjqRFDmZo3b55RbDjdd1PZtL1nUVOtWsh9UQghw/yOZMzJAtzZbgTjJ9SXWNoBJWu8yb4PzyEHJxBelF6eH6fyZ012WG9eY9srkQ5vswHANZti0ZpN8vHcFYJrNLWzfL7AqWPHDubgBOW3um5X6nO4udM/uM2W+VLiYn1wc8ovpb8+nzdkSySf1yP81NQFjPkgtPbHrjfPBKolB0jOz/cu8usxac/27xo8IvU++gdXiWdnfbDFqbqwk6zvOo5TlZDQnTxiP+nRKf17cTf33KP6Qt4oJTjkkENcU35ffvllOf/8841TO/WwHIJmS3z66acyceJE4TCO729Su/k+wgQLo6+nnnrK+FxoKAKKQPoioAQ4fddGR6YIpAwBNtdffvllIxlevny5GQuEkn/4UYZHjRq1x+aJ03HIy6ZNmwRFjk1AMms2qRu1NaRWKWSjzEk8Y4cguZHqF+lCOdscQdhQflM5nkjHnazXQW7p8UuvVlLmqYkOlzUA2bGO0vy2/WezsZ1WdW2dTP56kXw4fa5JEd5rr06Bz5bHIwVGNY2un7Bb9cG0O2qgzVEYtdWZ+hzu2crL8Umdcai3pmCQ4UDNMEZa9v/zjNgUaeqh+e+Sklbi82GotXuQ6lzvK2jsH8xYS3scL3W5oWvLB/fcS8b9ILh6mazPRiLuQ70vqcduk5YiP0gAACAASURBVF9Sm8855xxz+Aj53W+//RIx/LS6Br4WlAtxoGCDw5jLL79cfv/732tGT1qtlg5GEdgTASXA+lQoAopAWARIR/3666/lpZdeMsowNVAEacbUEEOGcbuEvPzkJz8xpioPPvigUYtTmZbKeCwZJh3bptnZtFnURTYsyYqmbY5oBZLuadrJwob7UNf9zTffCIcY1KOTLhlNyjzk16ZJ05LEttNCXbeO0pnsIM6BDocDG7ZXyUZPa6mVALkrKcyXssrY2ifxflMfvPFryavekpDlrssplJwwKdY5YZTfYAMw5NbnlRpHXbB9HSZaKOKstT384O9QffPzrYnWrn7Dzuv7fXni9/hke/shUt66X8i5FxfkyTljh0i+cafO3OAQk+9uDodQft347uM79u233zbOyBzwffDBByaDI9vi+eefN+r28OHD5c477zRtnXCqv/vuu+Wxxx4zqvBnn32W0n//sg1znY8ikGgElAAnGlG9niKQxQhAhjn5hgyT4kbbB7MJLykxhJhNFs7S1H5hrpIuAfmEFEGIUadtGiX1pZYc0WrJrYDcQV743atXL5MyFw25c2tc6XJdUtfBh3VCLUL9jSec7bRYcw5DCDb9Nk2ag5BoTJXiGU+87+WZJbOC59b0z27XTmYsLpU5y7+XimrqWiNvmxR0LKY+eOnO+uCqmIfbXN2veERyvKFTn8PdGBLMLOuC1A3z3FAKQQaz1+vbgwwHUqUx0dq9freyuKts6DIm7HwnDusvfbuENsaKGawkvpH+64sWLXKd/L7//vumPy6t7vhzNqYBc4gA4eXfDVygOVBwxoknnihvvPGGPPzww3LJJZckcZX1VoqAIhANAkqAo0FLX6sIKAKNCLAZp86Xf+hxvESFYWOAudNxxx1nlGFaXlCPmU5kz6bNQoxIm4UsEaiDtr1SImtyaTcFuWvpbY5CfXRQbXEK53lCLWINEhlcF4JtD0AgSoQ1TWvXoZMsKffJtKVbZENZjeTleGX/LiVy9MBO0q2te4cikc6R55RDJ7IFIBQoazY2l1XKh7OXy5pN2yO9XNjXxVMfbFoeSUPYvsORpj6HGyTXQA22rZJraqqlomKHeL3U/JYYnBoa/ObzZtVhez3+zpJhySuW0h4TxJ9TGPJ2/bq0lxOG7ZsQbFN1EUt+IaVDhw51RfllbphcnXLKKcJB4nvvvWfME7MxbrnlFrnhhhuMwzP/7jUNXK9p63TGGWcISrGGIqAIpCcCSoDTc110VIpARiBAuhsn/tTd3XzzzYbEkCY9Y8YMM37I7/jx442BFqQYpTidyDBpk5Bg3IadNaSM0yrDTU/4o1kYZ5sjN8hdNGNJx9eC+5w5cwxpQdlEkXc7nKZpqzdul1dWeGV9pUcq/V6pb/Aac6bWBTnStjBXzhjWTU4c0tntIYW8PmmVtIKCtIXqgWwOolZukE/nrwraAimWwefUlkvbTf+TovJVEb+9PqdAfHWh1WNSn+v9mFpFfMmQLwyYZHmlrGKHyapAyafmt6nCywXAh5ZIlgzb7I9VbQ6Ruvb9pKioWAoLC/b4XsJY7OyxQ4QU6EwN6zQP+SXt2a2SFMwTTz75ZPM5fuedd0z5S7bGxRdfbNKcr7rqKrnnnnv2mCZtnfj37thjjzVYaCgCikB6IqAEOD3XRUelCKQ9Ao8++qjp74giRTo0qc92w0kdMP+Pn88//9z8f5SBo48+2mwOJkyYYN6XbmQYNRJShlqIERjB5tEqw5DhSMdsN5+QF5Q7bXO0+yO9atUqk0JIWnIocufmhwDX5Ov/O0/mrNkmWyvrpMjXIPleEfyWDBkWj/RsXySXHdlbjuzf0c2hBL22xYfPDfjwHIaLiqoa+WTeSsExOlERaX1wc6nPzbk+xzJe0tprqioF0y2U30jT2flcb8zpLN/lDWxsscR7OawrLi4yv/nvY37QRwb17BTL0NLiPfb5YT4ov26R3y+++MJk+3Cw8NZbbxlzxGyOG2+8Uf7whz/I6NGjTQvBpoEB1q233ioQ5UceeSSbodC5KQIZjYAS4IxePh28IpA6BDjpvu6664Tf1LQGCzZFq1evbiTDKAWkHEMKMc5i44SRFvXDkRLLZMyYMWKcZckwNYaENVTCUZoa52BjZs7UiZF6yOYT8pttbXniWQPwoQ0LhySpbAP1wcIN8pcpy6V0e5X0bFcoOV5Po7twbV2dbK8WqfGL7NsuR248prt06bx3syQ0Hlzse8GHWnrcemPBh57BU+Ysl+07YjfG2m0epj54ibTdPDuowlvvzROvv9akP4eKRKQ+O69NxglmaRDVNq1bS35ejlTX1kcEf11ukZR2nyB+b55xkiYjgBRqWydOHfE+e7eTU0cONvXibnoDRDTgGF7Edy7+DDw/KL9uzQFzRLoCcKiA8/NRRx0Vw2gz6y0Y9YEp0bTOl8OAcePGmWdq8uTJ5s8aioAikJ4IKAFOz3XRUSkCGYEAdXaRtvFhY79u3TqTIo0yTM0YKcjUYqIaQIYxEEFtTScyzLghw9ZR2m6UUVSsMmzVbOZDXTTEGcWXtF433FYz4uEIMkgOFlB96THNAQKHA6nC54bXF8jUJZulKNcr7Yp2T3NlzWtqa2T55mrpkN8gp+xTJ/uU7MoGYN3dSOfnvhBfCDDZBii/sSh3tXX18sXCNTJz6TrxJyLnWEQC9cHzpGTbwsY6Xyiv35cvvvrQZDuRqc88Upb8kqruVH4DJlkNUlcfJsfa45H1XcZKddGeae2QOIgwqvKh3fKlMDfgHs3nGCLMDyp8On03BfsesOSXwzqUX7fIL6ZsHF5yEPHqq6+aUpeWEtdcc41xfCbwvaD3L+UKZDvxHXfRRRcJGVIaioAikL4IKAFO37XRkSkCWYsAG33SjNk4QYZplwGZRtE5/PDDDRk+6aSTpGvXrmm14bSGShBcCDGbPwISh4qN2RKn/xCk/fffX9scOZ5gDgeo92XdweqAAw4whx+piouenSVz126Xbm0LJT8neKuctduqpDjPJz89pKMMKqk2Y7fZABAL6yjNAUikKbih5suzhWrH4QCki8OBSA+XQl3z+60V8sHsZcLvRIWzPjiS1GcIcG0Q5+ZYxlNVVSmVlVXmc0XNr8ez+7qh3tI7uKa2PqgeXdZ2gGzpGFDvQsWR+/eSwT06GLd4a5RnWyyhqNo1T8eWWjw71Iy7TX455Dv++ONNv+7//Oc/hgin+8FALM9buPdwkEuKMyo4RocciHHgeeGFF8qZZ56Z6Nvp9RQBRSDBCCgBTjCgejlFQBGIDgGrsJJKDRnGQRSVlQ3VYYcdZmqG+enZs2dabbIYd3l5uVF7UbYtGYYIkSLND0QvXmIUHZrp+WoON3B6ppdt586djWqSalwue2G2zFy9XTqX5Eth3u7tcSyKq7ZUGjOsK47qI0cN6GjqHNns2mwAu+YQVWd7pWj7O6MaQSp4jmjPxEY62muEWnm/v0FmLlsnXyxYI7X1kaUJR/IU5e9YL+02fRO2f3AiU5/BGvU3FPl1jhmTLFyha+sCDu9EbV4bKe1+nEiTVkjO93VpVyI/Hrnfbt8zrA3eABx+8NO0pRbrzpolar0iwT7Ya1AgeYYgv6ToJtLJ3nk/Mjggv2TF/Otf/zKHlS2N/Ma6Rvo+RUARSB8ElACnz1roSBSBFo+AJRhvvvmmIcO4aOLySrCpQxVmw9W3b9+02XRBiCB3KIMdO3Y0G2SUEYJNsSVG/F2qN8mpeMAgLbSB4rCAQ4z+/funxdo9OGWZvDFnvdT7G6Rz6/w9oEG1XL65Uvp2LJK7Th68R0skewBiyTDzs2vOWpMFwO/mVG7URdoc4ULOe3ALd+NwgJpgaoOpEU5YNDRIq7Il0mbTbPHV7+4AncjU58rKHVJVVb2T/EbuJI8aTN9gDM3WdztGago6hJw6rtJnHrm/dCgJ3RYpVEstPtccdtlU6XiV+2jXx5JfshJIe3aL/OJtAPnlmafFz6mnnpoWn+Vo8dLXKwKKgCKgBFifAUVAEUhLBCzBgASTZofDqCWWpM9ChidNmiQDBgxI2SYMRQjyQjjbHEHaLTGCIBOQGkuM2Cg3R4zSclGiHBTp4JjGQIIxSuvVq1fK1qrp0BesK5eb3lwoKzbtkA6t8qRNQU7j2CC/gfTnHBm9bwf5zXHN94INtuYoY6iDtqVW03pn6k45PEFN69Kli6kldIP8OueOS/THc1fIjupao2hvrGyQ73fUC1nKxXke6VHikzwfvs2RR6A+eK6UbF0onoaA6kpNbiJSn8GVQ6WcHJ+p+Y1WbfR6PFLV+WBZVzwg7IRGDOwuh/bvFvmkRUy5g1WGyW4gGB8p8aw5n3O3yKgdaGlpqcydO9fU+rpJfjGto5UdZPuf//ynnHXWWVGvRVTg6osVAUVAEXARASXALoKrl1YEFIHEIMBGnRRI0qMhw2+88YZJpyX2228/kyINGU4GgbAzsm1GUHtIWWXTGywgf5BhUqWdm2QUI7tJTpURVGJWJ/hVIP4ov5A81oV67nSLpz5fKW/O/d6QXaIw12cU4R219dK2MEf26VAkN54wQLq2KYhq6BA2iBHrTvqs7T1riRHrjmoIPtSN9+jRI6kHOdW1dfLiF0vk+W/Wy5Yqv9T6xRhI+TweKfCJ9G+fI4M77joQiHTyObVl0nbTTGlXvTZiV+Zw195FfnOMKVi05JdrVxd0MOpvUUGB1NXXS03dnmngHVsXyRmjB5vewrEGGSDONSd1mmDc9nOeaOM0Uuapq8coDfLrlts8Ld3oa8t33hNPPCHnnXdeTGsRK7b6PkVAEVAEEo2AEuBEI6rXUwQUAVcRgExAMDDOIk0aIy0Ma4h+/fo1kuEDDzzQFTXN6dSLuoNTb6QbT0uMIMOoflyLTX27du0aVcJYXH9dBTyGi5POizLO/FDrUcLSMXBIfnXWOnlr3nrZVF4r9AZGMWyVnyMDO7eSnx/Ra4/U52jnQf0zeFhDJUuMUHr5c7du3WTgwIGuPKuhxrp0Y4Vc/+q3smpzpZRX10mOB+VSxJbMFuV4ZN/2OXJI59xop2tMqHxla6XNxvD1wc1dGHUVUkmmRKzkt8Hrk9Lux0tdXmtzOz5rrQrypKyqRmSnOzbrfdqoQbJ321bNDSnivyet3WmixTNAJNI4je8QyC+HZ26SX4y1UH6XLl1qTJ9wOI7lICJi8PSFioAioAgkAQElwEkAWW+hCCgC7iHAJpmWSpDhV155xSitBOm2KMPUDA8bNiwhBMNpVhRvmyM2xShGjJfNcjCV0O30STdWhZRMzHhQOHEyDqWMu3HvWK9ZVVsvM1ZslQ3lNZKX45XBXUqM+pvogBiBz6JFi0wLMBu2vzRKYTLcha95eb58sWyz1NY3SMdWeVJTWyc7quukoSGgBlfVi7TK9chRvfKlU1EUqmiDSFFBrkmvhmCGqg8Oj2tDoB1RTY3k5uZIcXFsyi/32NxpqJS36b/H7fJzc4zavaOmVg7u20VGDe6Z6KVuvB7fGWR+WHWYjBACYm9rhskGiaYkwpJfsk8gv7RnciNQmKn55Xl98MEH5bLLLlPy6wbQek1FQBFIOgJKgJMOud5QEVAE3EIAUvnpp5/KSy+9ZMgw6gXRvXt3UzMMIR4xYkRMZlRcm96XKLeJbnNEmjAqIRtbfluVkF65ttdwpCqzW9hGct0VK1aYzTIqNso4yp3GLgQwyqImmkwAVF/W1BIjp7uwrRkmMyDRNcFLNlTIlS/OldVbq4y6neMN1PuS+l1eVSP0EN5R2yD8b1Tg4V1375Ecbj1RV7mGM4LVB4e+BuQX5TfQX7xVK4hddPXI9tqVRV1kQ9exYR+/Lu1byaThAyU3J7gLeKKfXadxGutuPQ1YY2rFLSEOlwVCJgHZFW6TX76LaG9Ea6577rlHfvnLXyr5TfQDoddTBBSBlCGgBDhl0OuNFQFFwE0EIJVffvmlIcP0bIScEbThmThxolGGR44cGVGvVeqPqddkc+52vaZNn7RkmHkQtpaQ9kooPumUhuhMC2dskF/SPTV2IUCtL+SX9aRHNM+hDesubI3TrPO5VQkhxKiEiXARf33OOrn/w6VSUVMve5fs6X5dVVsn23bUGBK8V5FXJvaLbB2t4zJp5cGC+uB2G/8nhRWrQzwWtBWrMP3A4yW/fl+elPaYIPU5oVV8j3jk5MMHSveOgfToVATfK9ZEy5ZEMA6yAGzdsFPddZJfXPHdOmDiEA7yi7nW7bffLtddd11afd+kYq30noqAIpBdCCgBzq711NkoAopAEARQVGfMmNFIhr/77jvzKlyZ2ehBhseMGWPq6ZoGm05UEFIyaeFDanWygnHbWkI2yraWEOUQIswmOdHGOtHOjTHOnz/fpPaycSftOdltYKIdc7JfD7nhACWSmmhew0GLJcNOldBpnBYrxq/MKhVaQFXV+qVTq+DqLinh67dXS/sCj5y0b2QEuCh/Z+pzM+Dm71gn7Td9I7nVu9ox2TnzfOfl5e5M6Y1N+eX2G/c+QnaUhP+c7t9rLzn6wN7JfhRC3s9ZK85n3qbIQ4BRhllv2hBZ5dct8suzygEh7uQ333yz/P73v1fymzZPiQ5EEVAEEoWAEuBEIanXUQQUgYxAAMJGKrNVhiG3BLWqkGHSpI8++miTxvviiy/K1VdfLTfeeKOceOKJhnSmKhg3m1NLjCDkBEqrJcPJqB91zt/Zw5bDBAyvEqFSpgpjN+5rW2Wh2OMWTqprNGFVQtad9SecxmmQo2jU9unLt8iNbyyU9WXV0r1tQVBys7Wy1tQHH9y9RA7u2CD0EA4XJYX5UlYZ/jW7vb/BL622L5E2m+eIt65SKirKpba2zhxAxVvPuqNVL9nY+Yiw4yVV++yxQ4Ra4HQMPle4h1t12H7WGSuHXhin8RwlOj2eLAVKRaZPny6/+c1v5NZbb1Xym44PiI5JEVAE4kZACXDcEOoFFAFFIFMRQHnCsInWSpho2Z6+1N4edthhxmmaDfnTTz8txxxzTNpMk3FjrGPJsDXWgbTb+lEIfaI3yE4A2JSjEtHuiBZHtKNy835pA34UA8FEiDRSUplRxjmgiCecrXacxmmhUmaD3Yta3wufnSXzS8skP8dr2j050+np3btue7XsVZIv143vJ4f3aSdfLlwjM5euk2Dpzbm+QOso/87+v9HMr6G2WvxLp0rr7YulIA/Dq/jMnEh5Lu1xvPh9e6Z2O8d14qH9pU/ndtEMNWWvhQRzYMcacUBgP+scNHHoxAEIv2PNCLAToz6dTJjPP//cHPrdeeed+nlO2arrjRUBRcBtBJQAu42wXl8RUAQyAgFIJQZOKMMPPfSQQF4wISLVEKdVlGF6YaY65bgpmM76UeqGUQwJNsSWDCdaLWITTj0rqbr77LOPaT+VTjXJ6fDArV692qTOQ1rcqNe0xmm2vZIzZdaue6hn9YOFG+TPHyw1ac4+r0dK8n3i9XpMWnRZdZ20LcyVA7u3ljsmDZI8X8AF+vutFfLB7GXmtzOK8/Okonp346tI8CejobR0nSF0HYq80q9hhRTtCJjWxRQej3zfZYxUFXUJ+/b+3TrI8Yf0i+kWyX4ThxwcMkF2eYZYT5seDzHm8ImwGQGQYdY+mowA3s81TznlFJk6dapcfvnlct999yn5TfZi6/0UAUUgqQgoAU4q3HozRUARSGcEUNguuOACefbZZ006L4rIe++9J1988YUZNhvLcePGGTJMexBU1nQifk6XWcgwG1sikWZKTifjZNdEp/Oz4xybdcOOtk90rPODTJIyazMCnH1ni9t2lMI27aVrp3bSpnBXze9rs9fJ3z9bKWVVdcYQC++qvBzIcI7s16VEfnf8vtKuaPcaYb+/QWYuWydfLFgjtfX10qowX8qjSX3eOcF6VOZ1pVJVVW1UcWqb6UNcsGOdtGtSHxwpJmVt+suWTkPDvrwgL1fOGTtEqFdO97Dkl6wKyC9ZKU3D9hWHDDszAiDK1kSruR7KHJiddtpp8uGHH8rFF18sDz/8sJLfdH84dHyKgCIQNwJKgOOGUC+gCCgC2YAAKcUnn3yyfPTRR6YO7rnnnjMpmZDKlStXGidp0qRpswThQNkbO3asIckTJkzYuYmP3bjHDQytWgQZtmZKNnWSDTKpk9H0HwUjzJxQGwcPHixduoRX29yYUzpfk2dlyZIlsmzZMvPspMIN26bHfzxvlby7YLMs3eYXf4MYhXfgXoVy4pDOctSQnobkrNteJe/M+15mrykTUp/3bp0v4wZ2kmG92hplOFRQE/zxnBWyYsM2qff7o1oSyC+GaZA3DpDIToD8NsbO+uC2m2eLtz6yuuK6vBIp7X68NHjD1/SOP6iv7NejY1TjTcWLOczgcxaO/DYdFxkBkGBbN2zd4zmEse2VmpZFsAZnnnmmvPvuu+bg77HHHsvoGn7mfscdd8jrr79uvrOZOxkqeDrcddddqVhKvacioAikKQJKgNN0YXRYioAikFwEVq1aJcOHD5dJkybJ/fffH3QjCLlg8w4Z5mfKlCmGDEIiR48ebcgwDqqQy3RShkESpQeFEDJsUyfZYEfqLOw0czrwwAPN+zQcvK2hQRYuXCg8R6h11PwGcxVPBmb/+V+pPPvVatm8o8YovDy3/OR7G6QkV2RkF5FJQzqa5zTaQxDn+Beu2SSfzF0hO6prI5oWn5UA+a2Rdu3aSrt2Tciv4yqe+hpps2WulGxbJJ5w9cUej6zrNl5qCsI/j/vs1VZ+OHxARONM5Yuc5JcDlFjqxq1hniXDtm54+fLlMnnyZGPoRwbLZZddJm+88YacffbZ8tRTT2U0+f36669NiQqHABzO0WoMUy8c6ilHsAcCqVxbvbcioAikDwJKgNNnLXQkioAikGIEIIioJZGQVwgFG8xXXnnFKMOkEJJ6Cqk84ogjDBlGSUYljeR6yZw6G2LGDhl2Ogujxtn6USd5W7t2rdlIJsrMKZlzTca9nK2gqBvH7TkaZT2RY5yxYqv88Z3FsmZrpRTl+aRtUa7k+rxSV++XzRW1Ul5dKx0KRE7sUScD2jSY59WuO89+tKS9urZOps1fJfNWbJAGCd4DmPnV1QXIL2UG7du3M/X1kUROzXZpt4n+wcHrg7e131+2tT8g7KXoUXz2mCFSUhTeHCuS8bj5Gtsui3uQ9hwL+W06Pr6nyP7g8/6Xv/xFHn30UfMS1pnnloOaV199NaOzOZjboEGDhP7Zzz//vPnedcZXX30lhx56qJtLp9dWBBSBDENACXCGLZgOVxFQBNIPATaZKDek3uEojcpCeiHEF1WZmmF+evTokXZkGEJia0eZA3MhICiQYeaBckT9M4pUvE696bd68Y0IEjFnzhyDYTq0grrpzYUyZdFG85wF6/O7sbzGuDaP6N1GLhvaxowb4tV03SHDpJBGGms2bZcPZi2XLeUBEzZnoL4FyG+tdOjQ3qQ+RxsFO0ql3cZvJLcmYPxE1BS0N+qveAJGXaFizJB95MDeqWthFslcKS/AWI7gcxYLRpHcB1dyevvifs/BFutuD+3s9xSmdpkUl156qfz1r381BJ8/aygCioAi0BwCSoCbQ0j/XhFQBBSBKBBgQ0mK8ZtvvmnIMPV1KBMEbtKoE6jDffr0STsyjIKNmgIpIpUQckewQe7Vq5fpPxoNKYoCtox8KSm9uPRycEAvZtIuU9kKauuOWvn5MzNl+aZK6dm+QHJ2Ojg7wa3zN8jKTTukV4cieeTMA6RTSb7JXLDpshs3bmxcd2umxEEIBx/NZTJQDzx98VqZsbi0sTYY8gvRos9vx44d4lM1TX3wd9J28xzxNNTJuu7HSW1e+NZSXduXyKlH7Nfs2FP5ACaL/PK8QhCfeeYZU6qBGvz+++8bBfidd94x31O0UuN5LioqSiUkEd+b0g4+e3xX8Qzr91PE0OkLFYEWjYAS4Ba9/Dp5RUARcBMB68r89ttvGzLMb2tGRR0tZJiaY9yUmyMXbo6z6bWdqibtlNg4WzIMKWLDaUlRMseVTveCNGJUxGEHBwP0QU71Gq7cXClXvDhHVm+pkt4dQxOY5Zt2SNc2BXLPKYOlb6fde++y1hx+cAgCobC1kxAimx5PjXO4uW4uq5QPZy+T5eu2SGmpJb8dpU2bPZ2MY1lT6oPzqjdLdVHnsG/3eb1y1pH7S/uSyJXsWMYTz3t4flB++a4gHTnS1PBo78nn9//9v/8nTz75pKmVxcPASRYhkvQ9X7p0qXldpgStm/BfGDlypGnjxHcsGTiUefC9isM1fco1FAFFQBFwIqAEWJ8HRUARUASSgAAbXDaZKMLUDGM+g/JDUL9G+iFkGCKVShURwjN79mxDgiA8qJpEMFKEKmjJcHPtVpIAcdJuQdo4pIXDDJTxfffdN+Xkl8lvrqiRi56dJRDcnh2KJCeIkzPpz/z9Ph2K5C9nDJHOrQtC4mbNlGyKPPMmUAltmx0IW7DnFQfylydPk5krt0jb9h2ldeuSpK2PvdHhA3vIsP7pS34s+bW1uNRiuxFc/1e/+pVRfHFEfu211zJG4W0OD+b0f//3f8bBn8Mb1GxnQPKfeOIJ43atoQgoAoqARUAJsD4LioAioAgkGQHIMLW1pB9Chtm0kXZIQKYsGaYXcTLJMAQHVRP31FCqZqies2w0LRluTiFMMtwJvR3KEo6zpItSK0mblVQrv84J/ubVb+XTJZslx+eRDsW79/G1JLmmzi+H9W4vd06KXLW2qf3WPI3DHCJYj2nILxjxjPfuN0AWb66RRWs2JXQdmrtYp9bFcsbowab9UzoGnzEwSgb5vf766+Whhx4ySimlGRxWZUv86U9/EubHc0iLt3vvvVd+/OMfm88nc7777ruFLBaMsDCn01AEFAFFAASUAOtzoAgoAopAihGAeNJ/GDKMqzSKGwG5ggxTM0z9sJtkGEKDqsnGkfrkSGqU2byjYluF/hZS3gAAIABJREFUEMJDYJhl02Ux80knghjPUkPswAgSPHDgQGNqlm7x2ZLNctfk72TNtippXZAjbQpzTU9flN9tlbWyrbJOurUtkCuP6iNj+sfWExcybHtMs/Y2rZ/nk/VG2USNGzJkiHTuHEhTXrZ+q0yZs1zoIex2eD0eOX3UYNmr7e7p3W7fN9LrO8kvpMytlmJ8Pm+66Sa55557ZMSIEabOl8OpbIo//vGP8tvf/tZMiR7A11577W7TIwX6xRdflLPOOkueffbZbJq6zkURUATiQEAJcBzg6VsVAUVAEUg0AtSWTps2TV566SVDhjEQIrp3797oJo2zNGpHogICA7GDiMdK7KxCaHsN296jtFuxZDhUumyi5uHmdSxGrA8p6+laV8g6/PPL1fLfmaUmJbqipt6YYdXX+6Uwz2dU4YlD9pafHd4zYQcTtsc0Ts+WDLMWEDvcpFl/0qZr6urliwWrZday9eLf6Tbuxpod0q+LjBzU041Lx31N8EH55YDATfLLcwA55GfYsGHy3nvvueYsHTcocVzggQcekCuuuMJcwbaxc16OmuAJEyaYjBb6AWsoAoqAIgACSoD1OVAEFAFFIE0RoB73iy++aCTDK1asMCNFVTvxxBONMoz5Szw9Z0m9njVrVqNiRxpzvGF7j1oybF2wSUW0hAhy5KaiHe8cnO9H5SY1HNJCWjqELp0D/D/5brO8MWedLN24Q3B+RgXu06FITth/bxnTv0PCyK/FwaqaPLMo45Bi6sZteyV62tqDkPKaBnl/1jLZsK0i4TC2LS6Qn4wZEtQBO+E3i/KClvyCEeSXtlluBJjfddddcvPNN5v7UGrhlsrsxvijuSblI3wPYtJGVkLT+Pbbb82BFd89toY9muvraxUBRSA7EVACnJ3rqrNSBBSBLEOAdMbp06cbMoyD65IlS8wMIZQnnHCC2QQeeeSRguIaaUBQ6WFLijIbZTdMeGy67Pr1641CU15eboYHaYcAQLjZnCdS0Y50/pG8DhLHAQHzcFOxi2Qs0b6GMZdur5ayqjpplZ8jXdvkJ5z4MianmRPu5pbYQfRoq8S685sDBIIa1I6dOsn6So/MWrlZ6uoD7bbiDY945JQjBkq3DumX5stzP2PGDOOqDUZ8bt0I1vz+++83acEY2OHsnO4HNvHgsHLlSmNEx3cYhy5kGjjj008/NYeEZJ9Yn4V47qfvVQQUgexAQAlwdqyjzkIRUARaEAKQYfrP0loJMozKQbDJgwxTN3zUUUeZzWCo+lvSAXkfhJn2K8mqDUQNtmQY1ZBACbZkmN/xKNqJfAwgbjhiQ87BiPpWjd0R2LJli1HHmzsggPxCQGx7JVLJCb8vV5ZtE9la4zG14544PKuG9NpLjjqwd9otEeSXtGfm7Db5/etf/yrXXHONcZP/8MMPG2uw0w6UBA6IgykOqXDYHz9+/G5XtjXCfB9yGKChCCgCigAIKAHW50ARUAQUgQxGAOIxd+5cQ4Yx0ULRJSC0xx9/vFGGx40bZ3p+QoYhz7///e+lb9++pk/mwQcfnLKWKLZ2FFJkW0JBhlGiUYZRyUhdTEVQez1//nxzfzCi/7HG7ghAaDmIIaLpYWvN03CUZu2pF1+zrVoWbqgSX16B0F7LPq+RYt6qIE/OHnuA5OcmrjY+0nuHex1puSi/kF830+f5Hnj88cflyiuvNE7ymOpR99oS4rnnnpOf/OQnxnQNEtylSxczbZ5N2j7xnP773/827tAaioAioAgoAdZnQBFQBEza2O233y4vvPCCkE4G+TjuuOPklltuaTEbqGx5DNgEL1y4sJEMY2xFQCiOPfZYUzf8+uuvG6J8+OGHm17ETVMGU4UFDtLWTRpVkblA2FG1LRlO1lhXrVolCxYsMIok5Bf8NHZHgNRwCAYHFvGo48568dVr18mMpRtk1dYq8Xi85mAG7IuKCputFz/p0P7Su3O7tFqmZJLfp59+Wi655BLj3g757dkzPU3A3Fqg8847T/7xj3+YLA2+2/h37bPPPjOtuC688EJ57LHH3Lq1XlcRUAQyEAFVgDNw0XTIikCiEEB5GTt2rDFa4tR81KhRsnz5ctMzEfWN/8+GSiPzEIBYUCcM2eXnyy+/NGomStSAAQPk8ssvl0mTJgnmROnWpgizGqsOOo2U2NzaXsOQ00QHmPH8f/fdd4Z8QX5RIjV2R4C1ITUc8gtGPEOJCkjjnMXL5b1vlsjGnSZZPJ+sA2S4uLhoj3rxAd06yHGH9EvUEBJyHdsLmWcZZTIR5nLBBsYz+/zzz8tFF11kSC/kt3fv9EsDTwioYS5iFfBHH33UlHbwzKC4X3zxxfLTn/7U7dvr9RUBRSDDEFACnGELpsNVBBKJwO9+9zu57bbbTI9I2mRgTkPce++9cvXVVxtTpSlTpiTylnqtFCBAejHp0Bxo0L4HpRUzHup/OQAhTZraYdT/dCPDEHankRLps4TTVRiyGm+wgV68eLHgtM3nAGKXLMU53rEn8/22Lpo6bTByq3YcY6zP5i+XqbOXyvaycqmqqhTbOamwMJAmzU+rokI5d+wQKcxPTap8MOypcyftGfKLEZXthZzodeKZpfTh/PPPNweYkF/SnzUUAUVAEVAEwiOgBFifEEWghSLA5gx3UOvgShqjMzBrQeVhI3fIIYe0UJQyf9rr1q0z5Jd0VVTfP//5z8L/wzyLn48//ti480JoOPCADE+cONFkAKQbGWaclgyjQlpXYepzbYsde4gTzcpBJFCN1qxZY4g1n4VU1R5HM+5kvxbzMmrM3aiLrqipk/e/3SiTF2yQ0m1Vkuvzyv5dS2Rkn7YmG2DVhm0CsURZ3bEDMtxgpj9qYGcZOnAfs/7pkKrOGDG8IrsG5ddN8vvaa6/JueeeawzkMLzC+EpDEVAEFAFFoHkElAA3j5G+QhHISgRQC3DGxAyJlM+mQQ3wDTfcIDfeeKPcdNNNWYlBtk8KkkA9HMovSv/111+/G6nl71H06KWJksQzgeKK6/ERRxxhyPBJJ51kNvHpSIatq7BVtFlPSBBkiJRTyHBz40ZRxkQMcocCjqNsurZkSuXzWlpaanAia4ADsVgOGkKNf+22Krnx9QWyfHOladlUU+c3btBFeT4pKciR4wbtJUf2LpbPv10t1bV14vc3GDLcJs8vgzv4TDaDXXvbZxplurm1TzSe1J1yYAj5Rfm1ZkyJvg+f27feessYP1EWgLsxZFtDEVAEFAFFIDIElABHhpO+ShHIOgTuu+8++eUvf2mcMXHIbBpvvvmmUQKpE6WGVCMzEUCNQsknTTJcsKmGUKIqQYbff/99YyADiRg+fLghw7RX6t69e9KJRXPIQ2IxzoLEohaS3UBQN2rJcDBChIIMNqjKvA4SQV2rxu4I4Ig9b948kxIO+U2k0lpdVy+X/2uuLFhXJhU19dK6IFcKc73ibxApr66THTX1sldJvpw7vLv8cMhe8vHclbJ47SbJy/EZ1+fi/Jyga0+NuCXDkES319VJfgcPHmxKDdwIPqeTJ0+WM844w6wD5JdDGw1FQBFQBBSByBFQAhw5VvpKRSCrELjqqqtMOiwkmJrfpkFfRTZW1PlBojRaDgJsskmNxyUaMkxrETb4xLBhw4wqDCHGbCfZKltzq8DYqXm2vYYh8QTkzaZJ4ywN+SUtHOKMUjdo0CDXSVJzY0/Hv7f9oiGUkN9E1Fs75/net9/L3ZOXyKaKGuncukB83t0bAUOCt1fVSd+ORfLkuQcZVXjZ+q1SU1snA7p33A0y+9xaN3H7zJKybckwKn+iFX4UX5Rf7ucm+WWyZGmceuqp5nAH34ahQ4em42OjY1IEFAFFIK0RUAKc1sujg1ME3EMA19C//e1v8tvf/lZuvfXWPW5EWjSGKvwsWrTIvYHoldMaAdum5u233zZkmN/l5eVmzByQQIbJEuA5SUcyvH379kYy7CREjJ90bxTtgQMHpt3Y0+GhsO2gIFuQXzccsX/9ynz5ZDGKrteov02D5690e7W0L8qV647tJ0cP6BQRNLyP59SSYfvMQn47dOhgDkOonY231ttJfjlEcbP37rRp08xnjXp9DqXIzNBQBBQBRUARiB4BJcDRY6bvUASyAgElwFmxjEmdBKQCEvnOO++YtHgUYpRiAuWLFGk26JjxpCMZhgRhdIWqaU2UIBOog9QMu6EOJnWBEngz3LA5+ELxhfy60XaK4V7w9EyZs2a7tC/Ok/yc4OnnqMN5Pq9cPKqXnDG0W0yzpGbYttYiQ4DgGWXNIcM8A9G6fkN+yY7h2jzzHKa4FZ9//rn5bNn6X1rWaSgCioAioAjEhoAS4Nhw03cpAhmPgKZAZ/wSpnQCbMRJL6YeETJM7TA1xET//v0byXA61dVC3iEs/O7Vq5dR0lAIy8rKzLhRB1EFIcP8TnSqbEoXLIqbL1u2zBjjUWMK+Y2WGEZxK7nk+dny9cqtRv0lvTlYfF9WLcV5PvnFmN4y6Qddorl80Nfy3FoyzDNrD0OoFbZp8s2p3VyDtGfILxkEPXr0iHtcoS7AM4sfA2ZfHDrRukxDEVAEFAFFIHYElADHjp2+UxHIaATUBCujly/tBo/xFPWJpEnjKg2xJKgTRhmmZhgy5bYZUShgUH+/+eYbQ9qbqnWQGJsqaxVtxgkJTlSqbNotWJABQQSXLl1qfnB5Zr1wfXYzHpu2Qp6fvkYqa+ulU6u8PTIH6AdMCnS3tgXy0OlDZJ8O8fd8ds7H2Wd606ZNja21mL+ztZYzo4FnCFJKSya3yS9eDBMmTDDO0nyuxo8f7+Zy6LUVAUVAEWgRCCgBbhHLrJNUBPZEQNsg6VPhFgKQiqlTp8pLL71kNu24CBOoZJBhfg477LCkKayQ2v/9739GQaM9TbjerBANS4YxyCIgP7ZulFRZt0mhW+sS7rqQ3yVLlgjqL32VMb9LxjxXb6mUS16YLaXbqqUw1ydtCnPESw8karTr/bKxvEYK83wysm97+eMP3e1zizGaba2FQsxzTFg3cQgxf7bkd8CAAdKzZ0/Xlou2U5BfMhQ4WDrhhBPSrrTAtcnrhRUBRUARcBEBJcAugquXVgTSGQEUOzZ0lhw0baVx4IEHmhYxpPmhBGkoArEgAOmkfhEy/Morr8jKlSvNZXBexkALMkzPYdKR3QgIDW7PELwDDjjA1HpGGnxGLBm2qbKQYVykrTroZnpwpOOM93Vgs3jxYqHul3ZRkN94zaGiGdNrs9fJo1NXGCfo6jq/qQX2NzRIbX2DtC7IkV7tC+WOSYOkS5uCaC4b12tprUWtsF1/6ybO+oMXZleov25lNHz77beG/HII869//ctkUKRbXX1cAOubFQFFQBFIIQJKgFMIvt5aEUg1Ar/73e/ktttuk8MPP9y01LD9PWmLdPXVV8uRRx4pU6ZMSfUw9f5ZggAK2/Tp0xvJMIojASmlxpFNPs9cosgXKh6HOBCHgw46yBDXWAM1kOvRXgkyDEEioqkbjfXebr4PMrdw4ULB8blNmzaG/Lp1GBFuHlMWbZTnpq8RFOGaer94xCOFeV4Z1qudXDSyl+zdOt9NGMJeG4xIj0aRtaowbwAnZ5p8omrGOYw47rjjzPP2/PPPm7ZHSn5Ttvx6Y0VAEchCBJQAZ+Gi6pQUgUgRIN1zzJgx8uWXXxpFDmdRVCD+G1LyxRdfSJ8+fSK9nL5OEYgYAQgkacmkdv73v/+VBQsWmPfiyovyBRk+6qijTBpuLJv/devWGcICSYHUoWwmKlC1N27caMgwvy0Z5h5WGbaHSYm6pxvXgdihNOKMzeEAWSCpIL92bqi+c9eWSem2Ksn1eWVwl5KUEl87LjIBSHumjpx2X3w3WmWYNlsESrAzTT7WQxzqr48//nhTNvD000/LmWeeGdPz78bzotdUBBQBRSBbEFACnC0rqfNQBGJEAEfc22+/XZ577jmjAkFAUB9uueUWV9t6xDhcfVsWIgARmzNnjiHDOEpDXAkIpSXD48aNM614IiHDtDmC2JGeDPnF0MitQNVGHbRkGHJMWBMlHKUhw5GM260xBrsumM+fP98QLT7zkN9EKZjJnIfb90LxhfxSh9uvXz9j6uYMZ804KdPg6kyThyxH2kKKw0e+e/kefuKJJ+S8885Lu+fGbbz1+oqAIqAIJAMBJcDJQFnvoQgoAoqAIhARAhAI1GCrDOPcbAnlsccea2qG+R2KVKKgkVqNWRG16821s4loUBG+CCUYMow66DRRopcuyjBkGIOpVJNhxjlv3jxBJUe1pN5fye+ei+wkv3379m02Gwal2NleKZrMAFR4nmtMyB555BGhT3uqn5MIH/uwL+PzgOs6uIAh7bU0FAFFQBFINQJKgFO9Anp/RUARSAgCtLKhjvn111+XadOmmVRuNvWoNqeccorQ99hNJTAhk9CL7IaAdSa2ZJjUfAJSe8wxxxgyjGJG7SqvZY2pn7z++utl6NChEStvbsBuTZRQhiHEkCMCNdCSYcadbJLDuFDYGRfqJMZgbhk5uYFrsq4J+eXwhRRnykAgb9EEmQD2MIQ0eZsZwMEN68/fDRs2zGDPQQTPMc/ugw8+KJdddlnSn4to5hbNa1Gx//nPf5rPpxLgaJDT1yoCioCbCCgBdhNdvbYioAgkDYHHH39cLrzwQnM/FAfa3bB5/eyzz0z6Io6tH3/8sdl8amQeAmygOdSgXpg06U8//dRsqklzpo6dP3MAQmsafmNOlC7B2HBbt2SYtFmCsduaYcy03CaikF9MwVDjuO+QIUNcv2e6rEE044CsQn5ZM1KeOUSLJ8Dd2V4JlReSyxqMHj3amJBRAoD54JVXXpk15PeDDz4QShdQsx977DElwPE8RPpeRUARSCgCSoATCqdeTBFQBFKFwD/+8Q9DdtlAQoBtlJaWmv6ZGC5hKEOts0ZmIwChpHYVMkx7pU8++cQQYEtWUIZZcxTOZCuszSHLODmYsSZKZC4QmCZZMkxNbqLJMLXKkF/USPogDx48OOH3aG7umfD3TckvqmUinyHWHwX+oYceksmTJ5tDEQJl+LTTTpOTTz7ZkMZI64bTFVO8JThg4ZCH9mf9+/dXApyui6XjUgRaIAJKgFvgouuUFYGWhgB9aGn1xGYM8oGzsEbmI0Bv1rPOOssowmy2qbGlbRckhvT3kSNHGjdp+g3zd4kkMolADzKEs7Alw/yZwIkZ8g4hpkY33vpcyO+sWbNM2i1u75DfdMMiEXjGew2eGw7KMLPaZ599jPLrFk709+WQhrWHHKIQowITlGpg/vbb3/7WpKhnYvz617+WO++802Td9OjRwxxOaQp0Jq6kjlkRyE4ElABn57rqrBQBRcCBACqbbUuDcggJ0MhsBCoqKmTSpElGRTvjjDNMnSHEEZL32muvGRMtUjAhyZCYESNGGDKMOtytWzfXiE08qDInCBGqIGn7BOTX2Ws22jZFkF9IHYSLeZMd4Rapi2fuqX6vk/z26tXLtDtyCydSq3kO6YkNycVxn3thEEVWAz8c2uE+jYt5pgWZBhjQnXvuucbNevny5UqAM20RdbyKQJYjoAQ4yxdYp6cIKAJiUg5RCEkzhVigBGtkNgJ/+9vfTG0hPw8//PAeKinqKkreG2+8YRTid999V0jLJA499FCjCkOIUfrcIjrxIMxYLRmGMBHR9pp1kjpUOOqj03Gu8eCUiPc6Dwl69uxpFFm3cOL7h4MbCO6vfvUrueOOO4KmolO6Qaq6W+NIBG7BrkG98/Dhw42bNW7uZDBkIgHm8AFDRfwEaBfGISpZJEceeaRce+21GavMu7Xuel1FINMQUAKcaSum41UEFIGoEcAcC5OsE0880aiDGpmPAASXDSpr2hxJ4LUQj7feesuQ4bffftukHhMHHXSQIcOQEjdTXuNBHNMsjKtQhlFyCeZMrbCtG26a1o+LMcov5NltUhfP3FL93mSSXxR+HOmnTp0ql19+udx3331ZV4d9//33Gx+GJ5980vQxJjKNAHNwxGEpwWfssMMOMxlEfJ5oscZn7dlnn5VTTz011Y+v3l8RUARiREAJcIzA6dsUAUUgMxCA9EycONGkx5JySM9TjZaLAGQYdRUSDBl+8803DUkkcA4nNRVlOF1ThW2vWcgwdaPMh2jXrl0jGSZtGgUL0u92LWsmP0mQ35kzZxoc3VbIeeZ+/OMfy0cffSQXX3yxyVpItNFZqtdi5cqVpr6c9Gdq8W1kIgHGM4L0dP7tsDX4qNs33HCD3HbbbaafNz3H08ltPtXrr/dXBDIJASXAmbRaOlZFQBGICgFS8NjIoJqhtlxxxRVRvV9fnN0IQB6pEaaOmJphFGXIEEG6MGQYZRhinI5kBZUXV2fIMLXPbNAJxsqf3SZ1mfx0OI3BunfvbtqkNZdJEOt8UfBxoCed9oILLjAtgeI1Not1LG6+j2wM5ojhGnhmKgEOhxHfGRyO0brqqaeekp/+9KduQqrXVgQUAZcQUALsErB6WUVAEUgtAmvWrJEjjjjC9I696qqr5J577kntgPTuaY8AZBiFDjL86quvmrRjok+fPo3KMKZE6UiGSdtct26dLFq0SCB3NlCqqF0kVdoawaX9Qrg8QCf5ddsYDMX+7LPPNpkG55xzjkkNzkbyy5JxgEA/66ZZNhwAfPnll6a1E+nExAsvvGBqnJMVp59+uvz73/+Wa665xrhTO4PPjDUbI80ZA7RwQbuqF198Uf74xz/K9ddfn6wp6H0UAUUggQgoAU4gmHopRUARSA8EUPFGjRplzEvOP/9840TqlrqTHjPWUSQaAdRV+gvTZxgyjCkRgaqKMswPm/l0ITOQDNKeMeuh3QytdDDRgsRDjgkIsCXD/H1L/EygjKNQopy7TX55hqiDpQ8uBOyZZ54xpRjZGtE8T5hkkZ6frCALiJZSdAF4//33ZezYsebWrBFZQjNmzBCM9X7+8583O6ShQ4eaz9rf//538++LhiKgCGQeAkqAM2/NdMSKgCIQBgHMjY4++mj56quv5OSTTzan/ulCUnThMhMBCORnn31mlGFa1KxatcpMpGvXrsZACzLMJjpV5AbyywaeOlNStzG9sgHh40DI9hpmw08UFhY2kuHWrVu3CDLsJL+s3aBBg1ybN88M5nt8/5BGj+LZUvuPp0sNMNkd48aNM59bWjVRN0+dL0oudf98tpuLadOmmcNV1pJ5aUu95hDTv1cE0hMBJcDpuS46KkVAEYgBAVJYJ0yYIB9++KEce+yxxvG5pW46Y4BP3xIBAqTPcrhiyTBGOAQpxhjmsJEePXp0o4tsBJeM6yWQXsgvJJjaROpZQwX1iyhhlgzzeSFITbVu0qSwRqPkxTX4JL4Z8gvpQRGHtGDW5NY8eUYuueQS4xTMM0EWQUtuvZYuBJjHjRZGd911l5DG/Itf/ELGjBljDoJ4NpoztNq+fbug/i5evFiuu+46+dOf/pTEJ1hvpQgoAolEQAlwItHUaykCikDKEGDTicsqp/ic0L/zzjtSVFSUsvHojbMfAUgVNYMQHJ47jHEIWqeccMIJhgyTaskhjBtki3RnUjEhv6iZpPRGGpBh3K9tr2GuQTBWS4ZRyNKx3jnSOdrXsU5z5swxc3Wb/HIvWhxhkMQhHM8FantLjnQiwNRkU7qA+zeZDzil4wjPWoUL/n3h80xfcfqI08pKD1db8lOtc890BJQAZ/oK6vgVAUXAIGD7T/JnUg7Z3ASLu+++u9mTfoVUEYgWAQglJMuS4blz55pLtGnTxmQlsHkmNR+1NRFkmJ6yKL9s6HGpjicV0/ZJtmQYYk3QC7VTp06GEHfo0CEjybCT/GK6BFaJwD/Y88G9rr76auPyzFqTgaKHcOnXBxi11xp1odTTkqq5uOiii0yNMCUGpEE3pxY3dz39e0VAEUgtAkqAU4u/3l0RUAQShMBNN90kN998c7NXS7b5SrMD0hdkHQIQym+//bYxTRqVmMB46rjjjjM1wyhOkKNYyBh17ii/1PNC6BLtpsv1LRnmzwT1zWz6IcP8zoS6eggpBxG0iSLN1c12VtwLR+CHHnrIpMDj+sx6a6QfArbul5GNGDHCqLnhnudf//rXcscddxgDvE8//dT81lAEFIHMRkAJcGavn45eEVAEFAFFII0RgAx/9913jWSY+mEC8nvMMccYMgwpjtSIipRNyC8mS7jaQkjdDNRgS4apgSRIi7ZkGIU4VeZf4eYNIZ03b55pDQVGQ4YMcU3B5l433nij3HvvvYZQUX4RKgPFzbXSazePAOotdb88txyI4AjNwekNN9wQ9M20TKLel2cIV3gUYA1FQBHIfASUAGf+GuoMFAFFQBFQBDIAAcgw9ZDUhb788svGWZr/h0ESKbOQYdKlqb0Npgxv3brV1BxTj0gKJ5v4ZAZ1wpYMMxaCcZIejcLKeEibTnWAKcpvMsgv97rtttvk9ttvl2HDhsl7771neuFqpB8CHODwueEzSN3vQQcdZA5GMIaDGNsexXbkpDyT+sx64iD9gx/8IP0mpSNSBBSBmBBQAhwTbPomRUARUARSg8CmTZuM2y9utvR7RV3UyDwEIE70JIUI84O6hJIIgUShomYYIy2UVkjmBx98YFJs+SHFFtKZysBB2rpJQyCYD+OEvFsynArnY8aB8kvfZgg5KrlbRl7cC4XwD3/4gyFHqImpXpdUPhPpfu9zzz1Xnn76aeP+/OCDD5rhUqfNwVO/fv2MMRa9sglq+endTKYGhxoo+xqKgCKQPQgoAc6etdSZKAKKQAtA4LzzzpN//vOfhnAoAc6OBWctqVN95ZVXTKr0lClTTIozdYk4mh9yyCHGqAeCTD/Z8ePHp9XEMeLiQAZCzAEN8yFQziDDpI9i/uV2cN/58+ebgwUODlD73CS/9913n/zud78zqbS0Xku2Iu82ntl0/RdffNG0PsItHfM4pzO3Nbj62c9+Jo8//rh5jqnz5blGIT744IPbdwLgAAAfmklEQVSDQsEhFT8aioAikHkIKAHOvDXTESsCikALRQAVcNy4cSYtD6dZJcDZ9yBA4iCRr776qlGGUZ9IeUZNRYUiRfqkk04yLY9iMdByGzGIuyXDGzduNKSdoCbWkmE3nJGTTX45kKCnLNkYkN9EG5G5vU4t6fpr1qwxRBbn9C+//HKPVGb+P+nQ9PflM8efe/fu3SxE1H1jvqihCCgCmYeAEuDMWzMdsSKgCLRABCorK80mDiKEUti/f38lwFn+HLz77rtGYULFpPfoF198YXr+EtQrkrrJT69evdKSDEPcIcEoapBi/pvAHdmS4UQ4JVvXbYgOKcgov265VHMvVMIrr7xS9t13X1MbGk3/5Sx/ZHV6ioAioAhkBAJKgDNimXSQioAi0NIRoBUH9YYff/yxSc9DoVAFOHufCtronHzyyaYmERWYNGhMfN566y2jUmHig3JFkKKJKgxZppYxHZVhlGCUbUuGaeFEMD9SpPkpKSmJeuwQ0gULFsjq1auTQn4pP7j00kulT58+hvz27Nkzex9CnZkioAgoAlmKgBLgLF1YnZYioAhkDwKzZ882BAgTlyeeeMK4mCoBzp71bToTiOI+++xj6mYnT568R8ompI/2RJBgyDBk2bYoIksAVRgyPHDgwKgJZTJQhQxjnGVNtKi1JKjLtGS4TZs2zY7dSX7bt29vcHJT+X3++edN+QGkF/IbSZpsMvDUeygCioAioAhEh4AS4Ojw0lcrAoqAIpBUBCALw4cPl2XLlhmlixRPJcBJXYKU3AxSS2ozBkvhAhJIWjREGQOtN954QzZv3mzeAgGGDE+aNEkGDx7smiFUPAAxfloqWTJsU7xJ9bdkOFhbKN63cOFCWbVqlSSD/ILt+eefL126dDHkl/RnDUVAEVAEFIHMREAJcGaum45aEVAEWggC999/v6k3fPLJJwUHaEIJcAtZ/BimSXsiTJkgbBhpUYNLkC5vlWFMftxyR45hyI1vgdSiZNtew9S9E3l5ecZhmbphS4YXLVokK1euNP/NfNxUfmmVc84555gxQH45WNBQBBQBRUARyFwElABn7trpyBUBRSDLEWCDj3JH+jOtcWwoAc7yhU/Q9KizpWYcMoxx2rp168yVSeG1ZHjYsGGukcd4pgEZLi8vN+2hIMS23jknJ8cYwfHfpEnz2XCT/FJz/ZOf/MS0dOJgoTlFPp4563sVAUVAEVAEkoOAEuDk4Kx3UQQUAUUgagROPPFEY4A0a9as3VQnJcBRQ9ni30B7os8++0xeeukl+e9//2tMowgcjHnOqBmmzRIEMx0DwgsZJuXZ1gxDfOn3izLM70QSYQg4aeWnn366ca2mBRk1xhqKgCKgCCgCmY+AEuDMX0OdgSKgCGQpArj5ojzR1sUZ1EnSzxKTJNrhEC+88IL2Is3S5yDR06Id0VdffdVIhqkvJyCSEydONGR41KhRkpubm+hbx3w9COl3331n0v9xi6Y+mPTubdu2mWuS0k19vCXD8Y4dtffHP/6xMebiEGro0KExj13fqAgoAoqAIpBeCCgBTq/10NEoAoqAItCIQDTtbCAxOAdrKALRIIDJ2jfffNNIhqmtJSCTJ5xwgiHDY8eONWQ4mucxmjE091rI75IlS4wRHGnPtH2ySjWHQdZAC2dpgnFijAUZpm6XGuJoYurUqaYFFfegFzMmdBqKgCKgCCgC2YOAEuDsWUudiSKgCLQQBDQFuoUsdJKnCRmeM2dOIxmeN2+eGQGkEzJM3fC4ceNMDW4yyTDkd+nSpdK6dWtDfkOpu6RGWzKMEzbEmXFilGUdpRl7uCBNHNdsgjZTI0eOTPIqJO52tMpCvX799ddl2rRpsmLFCpMmTq/oU045Ra666iqT3q2hCCgCikBLQ0AJcEtbcZ2vIqAIZDwCSoAzfgnTfgKQx/nz5xsDLWqGZ86cacZM+vFxxx1nyPD48eOlqKjIVTIM8YUAc18MryJNbcYAbMOGDYYQ01cZck9QUmDJMOnNzpgxY4aph6ZemnZSKN+ZHI8//rhceOGFZgr77befMfDCZRuSX1ZWZnwFMEkDDw1FQBFQBFoSAkqAW9Jq61wVAUUgKxBQApwVy5gxk4AML168WF5++WXzM336dDN2yC8kGDIMKYakJlIZJuWZut9oyW9TYCG01AtDhvlNDTTxj3/8wzhin3HGGYYgo3KTUk37KOaV6cH8ILu0UYMA2ygtLTVz/d///idnnnmmPPfcc5k+VR2/IqAIKAJRIaAEOCq49MWKgCKgCKQeASXAqV+DljoCyDDPnyXDn3/+uUk1xpDt6KOPNmR4woQJRmmNhwxb8kuKLspvtHW8odYH8osiTIsx1F4UUYKaZ2qIH3jgAbnkkkvSsk9yIp851u3www836exgkCh8EzlGvZYioAgoAm4hoATYLWT1uoqAIqAIKAKKQBYjAPFds2aNIcOkSX/yySdGSSVNmfRhDLRQGiGX0ZBhCDaKc6LJb9OlQO39+9//bpTgBQsWmNRnYt999zU1svxAvqMZe6YsN/XBxcXFZrhr166VLl26ZMrQdZyKgCKgCMSNgBLguCHUCygCioAioAgoAi0bAcgwfXohwhDiKVOmGEKJkzItlSDDKK7Um4YjlBg14UTtNvlltbjP8ccfb9Kin376aeMYbck8acLE2Wefbf4u22Lu3LkyZMgQc1hBPXBz5mDZNn+djyKgCLRsBJQAt+z119krAoqAIpByBDAruuOOO4xbLampmBPR0omU2rvuuivl49MBRIcAZBhSSS0thPKDDz4QHJrp1UvaLWT4pJNOkq5du+5Ghv/617+aXta0L6LvrptpuZhrUbcM0X3mmWdMHbAl5qjYpAhjAEaf7dNPPz06ADLg1ZhjYZLFocRrr72WASPWISoCioAikDgElAAnDku9kiKgCCgCikCUCHz99ddy7LHHmrrMwYMHNzrV4kC8evXqxrTUKC+rL08TBCDD1NbiqgyhpC0PqceQTcglNcP8YMR06623mtRpXuemIonKDPldtWqVSYH+6U9/mpVpzqEegbfeeksmTpxo1HkMzQ488MA0eVp0GIqAIqAIJAcBJcDJwVnvoggoAoqAItAEAZTfQYMGCfWIzz//vFEFnfHVV1/JoYceqrhlCQKQYQyXIGCQ3HfeeUcqKipMLSq/qUN98sknTe9dt+puOVSB/GKy9eijj5o2QW7dKx2XjVpnVHgOJe677z654oor0nGYOiZFQBFQBFxFQAmwq/DqxRUBRUARUARCIXDppZcKaa9/+ctfhD9rtBwEIMOQ3quvvloee+wx4yKN6rtt2zY54IADzGHIpEmTZMCAAQkjqKQ7U/OLwdaDDz4ol112WcKunQkrh2HZEUccISjgV111ldxzzz2ZMGwdoyKgCCgCCUdACXDCIdULKgKKgCKgCDSHQGVlpan1pN4SJZi6X42WhcATTzwhP//5z6Vv375GDZ43b55RhkmXRqEk6F9LijRkmGwB6ohjCQy6aM+EAnrvvfea3rgtSfndvHmzMSOjtOD8888XsG9J84/lmdH3KAKKQPYioAQ4e9dWZ6YIKAKKQNoiMHXqVBk9erRJd+XPb7/9tkyePNnUh/bv319OO+00Y5KkkZ0IPPXUU3LBBRdI7969jWN0jx49GidaXV1tjLMw0MJIC0Mtol+/foYMY6L1gx/8IGIyzAEL7Zgg2H/605/k2muvbVHkr7y83BjKUVJw8skny7///W/x+XzZ+WDprBQBRUARiAABJcARgPT/27vzUKuqNg7AKwLLMYcy1KgoLWiwUSxLS0kLK7Q/GlQoG2yAQksKKi3TVCzLvEl8WqBRWqQNStJkg+ScqVlkNFk2/GHRSFQ2+PFu8OKQ1+nu4z7nPAsEh3PWftezjnB/Z6+9lpcQIECAQP0KxPOX119/ffYD+T///JMFnc1b3BGOu1T9+vWr3wvrrRACs2bNSnfccUeaN29eOvTQQ7dbU+wePX/+/OzO8AsvvJAdtRTtsMMOqw3DnTp12m4YjjvJEX7fe++9NHLkyDRs2LCqCr/xZULc+X7jjTeyzeZix+c8d9cuxIdLEQQIENiBgADsI0KAAAECJReIO3G33357thNt3I2KZakXX3xxtiHWpEmT0vjx47MzSuOuVdzt0ypPIMLtroSxOFd44cKFKcJzhOHY0CraIYcckj0zHHeHTz/99Nq7m/E8cfz98uXL05133plGjRpVVeE3vliK/1NxNnMsf45l5o0aNaq8D5IRESBAYBcFBOBdBPNyAgQIENhzgTFjxmShJFqcARzLUjdvsQR65syZqX///mn69Ol7fkE9VJRAhLulS5fWhuHY1TlanCMcR/zE3c7Y5GnJkiXp1ltvzZY+7+7zw+UKN3HixOxZ52jxDHWzZs3+cyjxZdOBBx5YrsNUNwECBHZZQADeZTJvIECAAIE9Faipqak9gmX9+vXpoIMO2qLLeCY4lm62a9eu9k7fnl7T+ytTIDZSi/Ok485w3O2MXZ43tdjpOT5r1RZ+Y/wjRoxI99xzzw4nPb48OPzww3f4Oi8gQIBApQgIwJUyk8ZBgACBMhKIZ35jM6NYkhnH4Wzd1qxZk+36G8ugY6msRmBnBCIMr169Oltev27duvT+++9XZfjdGSuvIUCAQLUKCMDVOvPGTYAAgb0oEOEkNjKKo1jiSKQ4A3bzFs96xg7RLVq0SHGEi0ZgVwXirGFH/eyqmtcTIECg8gUE4MqfYyMkQIBAIQVic6vYnfeVV15JvXr12qLGTc8I9+jRIzsSRyNAgAABAgQI1IeAAFwfivogQIAAgV0WmDFjRhowYEA6/vjjsxDcpk2brI9Vq1Zl55bGnd84szR2stUIECBAgAABAvUhIADXh6I+CBAgQGC3BAYOHJgef/zx1Lx589SlS5dsOfSiRYtSnF86aNCgNGXKlN3q15sIECBAgAABAv8lIAD7XBAgQIDAXhOI5zQfe+yxNHny5BQbX8Uzmx07dkzXXXdduuKKK/ZaXS5MgAABAgQIVKaAAFyZ82pUBAgQIECAAAECBAgQILCVgADsI0GAAAECBAgQIECAAAECVSEgAFfFNBskAQIECBAgQIAAAQIECAjAPgMECBAgQCAHgXfeeSfdf//9acGCBem7775LjRs3zna8vuqqq1Js/uWM2hzQdUmAAAECBHYgIAD7iBAgQIAAgXoWePbZZ9Oll16a/vnnn3TyySen9u3bZyH47bffTn///Xfq379/mj59ej1fVXcECBAgQIDAjgQE4B0J+XcCBAiUocD333+fZs+enZYuXZqWLVuWPvjggyyMTZ06Nbv7qOUnEAG3Xbt2af369VnIjbC7qcVO12eeeWZ2xvEbb7yRunfvnl8heiZAgAABAgS2ERCAfSgIECBQgQIvvPBCuuiii7YZmQCc/2THlw2x1Pnoo49OH3300TYXHDx4cKqpqUnjxo1Lt912W/4FuQIBAgQIECBQKyAA+zAQIECgAgUWL16cnnzyyXTqqaemTp06ZYHr0UcfdQe4BHP9ySefpKOOOmqHATjOP7766qtLUJFLECBAgAABApsEBGCfBQIECFSBwPXXX58mT54sAJdgrmOpedz9/eyzz7a7BHrjxo3p008/TS1btixBRS5BgAABAgQICMA+AwQIECgjgdhQ6Zlnnkm33npruu+++7ao/OOPP842Woq2cuXK1KFDh21GJgCXdrIXLlyYLrjggvTTTz9lcxNzEs8ExyZYxxxzTJo2bVo66aSTSluUqxEgQIAAAQLJHWAfAgIECJSBwI8//pg6duyYvv322zRv3rzazZP++uuv1KVLl7R8+fJsifM111zzn6MRgEs/yatXr86ew/78889rL96gQYN00003peHDh6cDDjig9EW54l4R+P3339PYsWPT008/ndatW5fd+T/vvPPSqFGjsg3TNAIECBAonYAAXDprVyJAgMAeCbz55pvpnHPOSW3btk0Rrlq0aJHuvPPONGbMmNS3b9/0/PPPb7d/AXiP6Hf5zU899VS68sor02mnnZbdsT/22GOzLy/Gjx+fpkyZkt0VXrRoUdpvv/12uW9vKC+BP/74I/vCasmSJalNmzapa9eu6Ysvvsh2Zz/ooIOyvz/iiCPKa1CqJUCAQBkLCMBlPHlKJ0Cg+gRi1+D7778/XXLJJenGG29MZ599djr44IOzQHzggQcKwAX4SMQmWBF4W7dune0C3aRJky2quvDCC9OLL76YHnnkkXTDDTcUoGIl5CkwbNiwNHr06HT66aenV199tfbz8OCDD6ahQ4ems846K7311lt5lqBvAgQIENhMQAD2cSBAgEAZCWzYsCF17tw5rVq1KjVr1iz9+uuv6aWXXkrnnntunaNwB7h0kxzLWu+6665sh+fY6Xnr9sQTT6TLL788XXbZZSnuFGuVKxD/X+OLkJ9//jmtWLFim+e+TzjhhOzLq3iE4ZRTTqlcCCMjQIBAgQQE4AJNhlIIECCwMwLxA3P84Bwt7iDGncQdNQF4R0L19+/XXXddtsz5lltuSQ888MA2Hc+ZMyf16dMn+9Li5Zdfrr8L66lwAvHYQo8ePdKRRx6Z7fq9ddv0Zcndd9+dRowYUbj6FUSAAIFKFBCAK3FWjYkAgYoW2PTcbwwyllXGzsL77rtvnWMWgEv3kYgwM3LkyNStW7c0f/78bS4cG2Dde++9KYLy//73v9IV5kolF3jooYfSzTffnC6++OJsF/et29y5c7PdwmOztOeee67k9bkgAQIEqlFAAK7GWTdmAgTKVmDBggXZc7+xec5xxx2X7Qh9zz33ZEtu62oCcOmmPJa6blrOuvVzvrHhUWxk9ttvv6XXXnst+71WuQKxCmDChAlZCI5nfrdu7733XjrxxBOzTdHefffdyoUwMgIECBRIQAAu0GQohQABAnUJ/PLLL9nS59hBNp77jXNkjz/++BRHJEUwjmeDt9cE4NJ+tuK85tjxOVpsiBVn/8Yu0IsXL07//vtvuvbaa9PkyZNLW5SrlVwg5jmOJ4tVG3HXf+sWy6LjjOj4Fed5awQIECCQv4AAnL+xKxAgQKBeBGLjpNhAKXZ/fvjhh7M+Nz1P2r59+2xjrMaNG//ntQTgepmCXeokjqWKJc5xZy82QWratGl2t2/QoEGpX79+u9SXF5engABcnvOmagIEKltAAK7s+TU6AgQqRGDmzJnZ0UdxJzF2jG3YsGHtyDb9kL31rsNxBu2mtnbt2rR+/frsvNFYPh0tll3uzAZaFUJoGARKLmAJdMnJXZAAAQI7FBCAd0jkBQQIENi7At9880221DmeG126dGl2F3HzFn8fy6Hj/NnYSCc21Im2zz771Fm480f37ry6euUL2ASr8ufYCAkQKD8BAbj85kzFBAgQIECAQBkIOAapDCZJiQQIVJ2AAFx1U27ABAgQIEBg5wTi+eXYrXrZsmXZr1iNEG3jxo11djBt2rRsef2HH36YGjRokGI5/rBhw1KXLl127sIV8qoNGzak1q1bZ8+Ar1y5cpvVG7GpXZzrHY81bNo5vEKGbhgECBAorIAAXNipURgBAgQIENi7An379k2zZ8/epoi6AvCQIUPSxIkTs+fUe/Xqlf7444/0+uuvZ6F51qxZKfqsphbBf/To0Vn4f/XVV2s3qotjkYYOHZo8ilBNnwZjJUCgCAICcBFmQQ0ECBAgQKCAAuPGjcuePe/UqVP26/DDD09//vnndu8Ax7nUPXv2TK1atcqOfIrjfaLF7+P86kaNGqXYkK158+YFHG0+JcUXADH2eH6/TZs2qWvXrunLL7/M/hwb0sXZ0LE5nUaAAAECpREQgEvj7CoECBAgQKDsBfbff/86A3Dv3r2zM6onTJiQ4k7w5m3w4MGppqYmOx857nxWU/v999/T2LFj04wZM9JXX32VWrZsmc4777w0atSodMghh1QThbESIEBgrwsIwHt9ChRAgAABAgTKQ6CuABwhr0WLFllAjpC3dbB7++23U7du3Sz5LY+pViUBAgQqVkAArtipNTACBAgQIFC/AnUF4FWrVmXHccWy3jhzeusWS6mbNGmSheQffvihfgvTGwECBAgQ2EkBAXgnobyMAAECBAhUu0BdAXjOnDmpT58+WQhesWLFf1JF+P3pp5/SL7/8kpo2bVrtnMZPgAABAntBQADeC+guSYAAAQIEylGgrgAcz7cOGDAgnXHGGWnBggX/ObxYFh1HKcWvtm3bliOBmgkQIECgzAUE4DKfQOUTIECAAIFSCQjApZJ2HQIECBDIS0AAzktWvwQIECBAoMIELIGusAk1HAIECFShgABchZNuyAQIECBAYHcEbIK1O2reQ4AAAQJFEhCAizQbaiFAgAABAgUW2NljkL7++uvUrl27LUbiGKQCT6zSCBAgUEUCAnAVTbahEiBAgACBPRGoKwBHv717904vvfRSmjBhQhoyZMgWlxo8eHCqqalJ48ePT0OHDt2TMryXAAECBAjstoAAvNt03kiAAAECBKpLYEcBeN68ealnz56pVatWafHixalDhw4ZUPy+e/fuqWHDhmnt2rWpefPm1QVntAQIECBQGAEBuDBToRACBAgQIFAsgblz56ZRo0bVFrVs2bK0cePG1Llz59q/Gz58eDr//PNr/xx3fidOnJgaNWqUheENGzak1157LXvfrFmzUt++fYs1SNUQIECAQFUJCMBVNd0GS4AAAQIEdl5g2rRp6corr6zzDVOnTk0DBw7c4jXxvkmTJqU1a9akBg0apNNOOy1FUO7SpcvOX9wrCRAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAgJwDqi6JECAAAECBAgQIECAAIHiCQjAxZsTFREgQIAAAQIECBAgQIBADgICcA6ouiRAgAABAgQIECBAgACB4gkIwMWbExURIECAAAECBAgQIECAQA4CAnAOqLokQIAAAQIECBAgQIAAgeIJCMDFmxMVESBAgAABAgQIECBAgEAOAv8HBVaBQBXk6uQAAAAASUVORK5CYII=\" width=\"640\">"
+      ],
+      "text/plain": [
+       "<IPython.core.display.HTML object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
    "source": [
     "# %matplotlib inline\n",
     "%matplotlib notebook\n",
@@ -45,19 +1019,20 @@
     "ax = plt.axes(projection='3d')\n",
     "ax.scatter(X[:,1], X[:,2], y)\n",
     "s = ax.plot_surface(x1,x2,z, facecolor='blue', label='True params', alpha=.5)\n",
-    "s._facecolors2d = s._facecolors3d\n",
-    "s._edgecolors2d = s._edgecolors3d\n",
+    "#s._facecolors2d = s._facecolors3d\n",
+    "#s._edgecolors2d = s._edgecolors3d\n",
     "\n",
     "# Plot of plane generated by true parameters\n",
     "z = theta_hat[0] + theta_hat[1]*x1 + theta_hat[2]*x2\n",
     "s = ax.plot_surface(x1,x2,z, facecolor='red', label='Estimated params', alpha=.5)\n",
-    "s._facecolors2d = s._facecolors3d\n",
-    "s._edgecolors2d = s._edgecolors3d\n",
+    "#s._facecolors2d = s._facecolors3d\n",
+    "#s._edgecolors2d = s._edgecolors3d\n",
     "ax.set_xlabel(\"x1\")\n",
     "ax.set_ylabel(\"x2\")\n",
     "ax.set_zlabel(\"y\")\n",
-    "ax.legend()\n",
-    "plt.show()"
+    "#ax.legend()\n",
+    "\n",
+    "plt.show()\n"
    ]
   },
   {
@@ -69,9 +1044,30 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 5,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of repetitions is  10\n",
+      "Repetition  10  of  10  repetitions done.\n"
+     ]
+    },
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtcAAAHgCAYAAABuGUHVAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAACEkElEQVR4nO3dd3xUVfrH8c9JISEQOgICUlWqBAwqihrXhrq2tWJZO+q6Krsrrq6svbv6s3exl7WgoqLCusTuKmAUsFAsCCg91FASzu+PZy4zCSmTZCYzSb7v1+u+Zs6dmTvP3EySZ86c8xznvUdERERERGovJdEBiIiIiIg0FEquRURERERiRMm1iIiIiEiMKLkWEREREYkRJdciIiIiIjGi5FpEREREJEbS4nVg51xX4CmgA+CBh733d5W5z8nA3wEHrAHO995/Fbrtp9C+EqDYe59b1XO2a9fOd+/ePYavIjrr1q2jWbNmdf68jYHObfzo3MaPzm386NzGj85t/Ojcxk+izu20adOWee/bl3db3JJroBj4m/d+unMuG5jmnJvsvf8m4j4/Avt671c65w4BHgZ2j7h9P+/9smifsHv37kydOjUmwVdHfn4+eXl5df68jYHObfzo3MaPzm386NzGj85t/Ojcxk+izq1z7ueKbotbcu29/xX4NXR9jXPuW6Az8E3EfT6JeMhnQJd4xSMiIiIiEm91MubaOdcdGAz8r5K7nQW8HdH2wCTn3DTn3Kg4hiciIiIiEhMu3sufO+eaA+8DN3jvx1dwn/2A+4Hh3vvloX2dvfcLnXPbAZOBC733H5Tz2FHAKIAOHTrs+sILL8TplVRs7dq1NG/evM6ftzHQuY0fndv40bmNH53b+NG5jR+d2/hJ1Lndb7/9plU0HzCuybVzLh14E3jXe39HBffZBXgVOMR7P7uC+1wNrPXe/6uy58vNzfUac92w6NzGj85t/Ojcxo/Obfzo3MZPfT23mzdvZsGCBWzYsCHRoVRow4YNZGZmxu34mZmZdOnShfT09FL7nXMVJtfxrBbigMeAbytJrHcAxgOnRibWzrlmQEporHYz4CDg2njFKiIiIiKlLViwgOzsbLp3746ldclnzZo1ZGdnx+XY3nuWL1/OggUL6NGjR9SPi2e1kL2AU4EZzrmC0L5/ADsAeO8fBK4E2gL3h35oQcm9DsCroX1pwHPe+3fiGKuIiIiIRNiwYUNSJ9bx5pyjbdu2LF26tFqPi2e1kI+w+tWV3eds4Oxy9v8ADIpTaCIiIiIShWon1sHwl/z8WIeSEDX5YKEVGkVEREQkKRUWFnL//fdX+3GHHnoohYWFld7npZdeon///qSkpMR0nRQl1yIiIiKSlCpKrouLiyt93MSJE2nVqlWl9xkwYADjx49nn332qU2I24jnmGsRERERkRq77LLLmDdvHjk5OaSnp5OZmUnr1q357rvvmD17NiNHjuTXX39lw4YNXHzxxYwaZUujBKt2r127lkMOOYThw4fzySef0LlzZ15//XWaNm1K37594xKzeq5FREREpGp5efDEE3Z982ZrP/OMtdevt/aSJdZetcra40NLnCxbZu033rD2b79F9ZQ333wzvXr1oqCggNtuu43p06dz1113MXu2FZm77777mDZtGlOnTuXuu+9m+fLl2xxjzpw5XHDBBcyaNYtWrVrxyiuv1ODFR0891yIiIiJSL+y2226lyuI9+OCDTJw4EYBffvmFOXPm0LZt21KP6dGjBzk5OQDsuuuu/PTTT3GNUcm1iIiIiFQtsgJIenrpdlaWtYNqIS1blr69XbvS7Y4daxRCs2bNIsLJJz8/n08//ZSsrCzy8vLKXfAmIyNj6/XU1FSKiopq9NzR0rAQEREREUlK2dnZrFmzptzbVq1aRatWrcjKyuK7777js88+q+PoyqfkWkRERESSUtu2bdlrr70YMGAAY8aMKXXbiBEjKC4upm/fvlx22WXsscce1Tr2q6++SpcuXfj000857LDDOPjgg2MSs4aFiIiIiEjSeu6558rdn5GRwfjx48td/jwYV92uXTtmzpy5df8ll1yy9frRRx/N0UcfHdtgUXItIiIiIrHSQFZmrA0NCxERERERiRH1XNdSXh4UFuZQUJDoSEREREQk0dRzLSIiIiISI0quRURERERiRMm1iIiIiMREXl54HZnGSsm1iIiIiCSlwsJC7r///mo/7tBDD6WwsLDS+4wZM4Y+ffqwyy67cPTRR1d5/2gpuRYRERGRpFRRcl1cXFzp4yZOnEirVq0qvc+BBx7IzJkz+frrr9lpp5246aabahPqVkquRURERCQpXXbZZcybN4+cnByGDh3K3nvvzRFHHEG/fv0AGDlyJLvuuiv9+/fn4Ycf3vq47t27s2zZMn766Sf69u3LOeecQ//+/TnooIMoKioC4KCDDiItzQrn7bHHHixYsCAmMSu5FhEREZEq5eXBE0/Y9c2brf3MM9Zev97aS5ZYe9Uqa48fb+1ly6z9xhvW/u236J7z5ptvplevXhQUFHDbbbcxffp07rrrLmbPng3Afffdx7Rp05g6dSp33303y5cv3+YYc+bM4YILLmDWrFm0atWKV155ZZv7jBs3jkMOOSS6oKqgOtciIiIiUi/stttu9OjRY2v7wQcfZOLEiQD88ssvzJkzh7Zt25Z6TI8ePcjJyQFg11133bo0euCGG24gLS2Nk08+OSYxKrkWERERkSpFrmyenl66nZVl7aBSSMuWpW9v1650u2PHmsXQrFmziHjyyc/P59NPPyUrK4u8vDw2bNiwzWMyMjK2Xk9NTd06LATgiSee4M033+S9997DOVezoMpQci0iIiIiSSk7O5s1a9aUe9uqVato1aoVWVlZfPfdd3z22WfVOvY777zDrbfeyvvvv09WVlYswgWUXIuIiIhIkmrbti177bUXAwYMoGnTpnTo0GHrbSNGjODee++lb9++7Lzzzuyxxx7VOvaf//xnNm7cyIEHHgjYpMYHH3yw1jEruRYRERGRpPXcc8+Vuz8jI4Px48eTnZ29zW3BuOp27doxc+bMrfsvueSSrdfnzp0b20BDlFyLiIiISExEjqturFSKT0REREQkRpRci4iIiIjEiJJrEREREZEYUXItIiIiIhIjSq5FREREJDby8sIryTRSSq5FREREJCkVFhZy//331+ixd955J+vXr9/avuKKK+jatSvNmzePVXjlUnItIiIiIkkplsn14Ycfzueffx6r0CqkOtciIiIikpQuu+wy5s2bR05ODgceeCDbbbcdL774Ihs3buToo4/mkksuYd26dRx//PEsWLCAkpIS/vnPf7J48WIWLVrEfvvtR7t27ZgyZUq1V3CsKSXXIiIiIlK50aOhoKDq+wX3iWbcdU4O3HlnpXe5+eabmTlzJgUFBUyaNImXX36Zzz//HO89RxxxBB9//DHr1q1j++2356233gJg1apVtGzZkjvuuIMpU6bQrl27qmOJIQ0LEREREZGkN2nSJCZNmsTgwYMZMmQI3333HfPmzWPgwIFMnjyZv//973z44Ye0bNkyoXGq51pEREREKldFD/NWQY91HNZB995z+eWXc+65527dt2bNGrKzs5k+fToTJ05k7Nix7L///lx55ZUxf/5oqedaRERERJJSdnY2a9asAeDggw9m3LhxrF27FoCFCxeydOlSFi1aRFZWFqeccgpjxoxh+vTp2zy2Lim5FhEREZGk1LZtW/baay8GDBjA5MmTOemkkxg2bBgDBw7k2GOPZc2aNcyYMYPddtuNnJwcrrnmGsaOHQvAqFGjGDFiBPvttx8Al156KV26dGH9+vV06dKFq6++Oi4xa1iIiIiIiCSt5557rlT74osv3np9zZo1DBo0iIMPPnibx1144YVceOGFW9u33nort956a/wCDVFyLSIiIiKxEYex1vVN3IaFOOe6OuemOOe+cc7Ncs5dXM59nHPubufcXOfc1865IRG3neacmxPaTotXnCIiIiIisRLPnuti4G/e++nOuWxgmnNusvf+m4j7HALsGNp2Bx4AdnfOtQGuAnIBH3rsBO/9yjjGKyIiIiJSK3Hrufbe/+q9nx66vgb4Fuhc5m5HAk958xnQyjnXCTgYmOy9XxFKqCcDI+IVq4iIiIhILNRJtRDnXHdgMPC/Mjd1Bn6JaC8I7atov4iIiIhI0or7hEbnXHPgFWC09351HI4/ChgF0KFDB/LreCB9YWEOJSUldf68jcXatWt1buNE5zZ+dG7jR+c2fnRu46e+ntuWLVtWu070oYc2BWDixKJ4hLSNkpKSuNey3rBhQ7V+fnFNrp1z6Vhi/az3fnw5d1kIdI1odwntWwjkldmfX95zeO8fBh4GyM3N9XnRrGUfQ61aQWFhIXX9vI1Ffn6+zm2c6NzGj85t/Ojcxo/ObfzU13P77bffkp2dXa3HpKbaZXUfV5HCwkKee+45/vSnP5V7e7BCY3nuvPNORo0aRVZWFuvXr+e4445j3rx5pKamcvjhh3PzzTdHFUNmZiaDBw+OOuZ4VgtxwGPAt977Oyq42wTgj6GqIXsAq7z3vwLvAgc551o751oDB4X2iYiIiEgjUVhYyP3331+jx955552sX79+a/uSSy7hu+++48svv+Tjjz/m7bffjlWYpcSz53ov4FRghnOuILTvH8AOAN77B4GJwKHAXGA9cEbothXOueuAL0KPu9Z7vyKOsYqIiIhIkrnsssuYN28eOTk5HHjggWy33Xa8+OKLbNy4kaOPPppLLrmEdevWcfzxx7NgwQJKSkr45z//yeLFi1m0aBH77bcf7dq1Y8qUKVtXamzSpAlDhgxhwYIFcYk5bsm19/4jwFVxHw9cUMFt44BxcQhNRERERKph9GgoKKj6fsF9ohkFk5MDd95Z+X1uvvlmZs6cSUFBAZMmTeLll1/m888/x3vPEUccwccff8y6devYfvvteeuttwBYtWoVLVu25I477mDKlCm0a9eu1DELCwt54403Sq30GEt1Ui1ERERERKQ2Jk2axKRJkxg8eDBDhgzhu+++Y968eQwcOJDJkyfz97//nQ8//JCWLVtWeIzi4mJGjhzJRRddRM+ePeMSp5Y/FxEREZFKVdXDHAh6rONRHMV7z+WXX8655567dV8woXH69OlMnDiRsWPHsv/++3PllVeWe4xRo0ax4447Mnr06NgHGKKeaxERERFJStnZ2VtL7R188MGMGzeOtWvXArBw4UKWLl3KokWLyMrK4pRTTmHMmDFMnz59m8cCjB07llWrVnFntJ8Uakg91yIiIiKSlNq2bctee+3FgAEDOOSQQzjppJMYNmwYAM2bN+fBBx9kzpw5jBkzhpSUFNLT03nggQcA66UeMWIE22+/PU8//TQ33HADffr0YciQIQD8+c9/5uyzz455zEquRURERCRpPffcc6XakRMR16xZw6BBgzj44IO3edyFF17IhRdeuLVtdTTiT8m1iIiIiMREPVyIMuY05lpEREREJEbUc11bBQU0Ly4GchMdiYiIiIgkmHquRURERKRcdTVOOVnV5PUruRYRERGRbWRmZrJ8+fJGm2B771m+fDmZmZnVepyGhYiIiIjINrp06cKCBQtYunRpokOp0IYNG6qd/FZHZmYmXbp0qdZjlFyLiIiIyDbS09Pp0aNHosOoVH5+PoMHD050GKVoWIiIiIiISIwouRYRERERiREl1yIiIiIiMaLkWkREREQkRpRci4iIiIjEiJJrEREREZEYUXItIiIiIhIjSq5FRERERGJEybWIiIiISIwouRYRERERiREl1yIiIiIiMaLkWkREREQkRpRci4iIiIjEiJJrEREREZEYUXItIiIiIhIjSq5FRERERGJEybWIiIiISIwouRYRERERiZG0RAdQ380t6kzJFp/oMEREREQkCSi5rqVNPo3VJVmJDkNEREREkoCGhdRSdup6NvoMlixJdCQiIiIikmhKrmspO3U9AFOnJjgQEREREUk4Jde11Dy1CPBKrkVEREREyXVtpbktNHUblFyLiIiIiJLrWGiesl7JtYiIiIgouY6F5qnr+PVXWLQo0ZGIiIiISCIpuY6B5inrAPjiiwQHIiIiIiIJFbfk2jk3zjm3xDk3s4LbxzjnCkLbTOdciXOuTei2n5xzM0K3Jf2Ai2YpRaSkqGKIiIiISGMXz57rJ4ARFd3ovb/Ne5/jvc8BLgfe996viLjLfqHbc+MYY0ykui3076/kWkRERKSxi1ty7b3/AFhR5R3NSOD5eMVSF3JzLbn2WgldREREpNFK+Jhr51wW1sP9SsRuD0xyzk1zzo1KTGTVM3QoLFsGP/+c6EhEREREJFHSEh0AcDjwcZkhIcO99wudc9sBk51z34V6wrcRSr5HAXTo0IH8/Py4BxypuLg53nucmwbsypNPzmLffZfWaQwN2dq1a+v8Z9pY6NzGj85t/Ojcxo/Obfzo3MZPMp7bZEiuT6TMkBDv/cLQ5RLn3KvAbkC5ybX3/mHgYYDc3Fyfl5cX12DLSksroLi4mDPO2JWLLoKiov7UcQgNWn5+PnX9M20sdG7jR+c2fnRu40fnNn50buMnGc9tQoeFOOdaAvsCr0fsa+acyw6uAwcB5VYcSSYZGTBwoCY1ioiIiDRmceu5ds49D+QB7ZxzC4CrgHQA7/2DobsdDUzy3q+LeGgH4FXnXBDfc977d+IVZywNHQovvGCTGi18EREREWlM4pZce+9HRnGfJ7CSfZH7fgAGxSeq+MrNhYcegrlzYccdEx2NiIiIiNS1hFcLaUhyQxW5NTREREREpHFSch1D/fvb2Gsl1yIiIiKNk5LrGEpPh5wcJdciIiIijZWS6xgbOhSmT4eSkkRHIiIiIiJ1Tcl1jOXmwtq18P33iY5EREREROqakusY06RGERERkcZLyXWM9ekDWVlKrkVEREQaIyXXMZaaCkOGKLkWERERaYyUXMfB0KHw5ZeweXOiIxERERGRuqTkOg5yc2HDBvjmm0RHIiIiIiJ1Scl1HGhSo4iIiEjjpOQ6Dnr3hhYtlFyLiIiINDZKruMgJcV6r5Vci4iIiDQuSq7jJDcXvvoKNm5MdCQiIiIiUleUXMdJbq5VC5kxI9GRiIiIiEhdUXIdJ5rUKCIiItL4KLmOk+7doW1bJdciIiIijUlaVXdwzqUAg4DtgSJgpvd+SbwDq09SffE2+5zTpEYRERGRxqbC5No51wv4O3AAMAdYCmQCOznn1gMPAU9677fURaDJ6up1Y8CXAP/d5rbcXLj5Zli/HrKy6j42EREREalblQ0LuR54BujlvT/Ye3+K9/5Y7/0uwBFAS+DUuggymS1O6cjwkg9g6dJtbsvNhZISqxoiIiIiIg1fhcm1936k9/4D770v57Yl3vs7vfdPxje85PdOkyNJowSefXab2zSpUURERKRxiWbM9R/K2b0KmKGx1/Bjam++SelHv3Hj4OKLbbB1SOfO0LGjkmsRERGRxiKaaiFnAY8CJ4e2R7Cx2B875xr9sBCAiWmHW0Hr6dNL7Q8mNX7xRYICExEREZE6FU1ynQb09d4f470/BugHeGB3LMlu9P6TdjBkZsLjj29zW24ufPcdrFmTgMBEREREpE5Fk1x39d4vjmgvCe1bAWyOT1j1y1qXDUcfDc89Bxs2lLotNxe8hy+/TFBwIiIiIlJnokmu851zbzrnTnPOnQa8HtrXDCiMa3T1yRlnwMqV8PrrpXZrUqOIiIhI4xFNcn0B8ASQE9qeAi7w3q/z3u8Xt8jqm9/9DnbYYZuhIR06QNeuSq5FREREGoMqq4WESvG9HNqkIqmpcNppcP318MsvllGHaFKjiIiISONQZc+1c+4Pzrk5zrlVzrnVzrk1zrnVdRFcvXP66TbA+qmnSu3OzYW5c23UiIiIiIg0XNEMC7kVOMJ739J738J7n+29bxHvwOqlnj0hLw+eeMKS7JBg3HWZSn0iIiIi0sBEk1wv9t5/G/dIGoozzrBu6o8+2rpr113tUuOuRURERBq2aJLrqc65fzvnRoaGiPyhglUbBeCYYyA7G8aN27qrbVvr1K4wuc7Ls01ERERE6rVokusWwHrgIODw0Pb7eAZVrzVrBiecAC+9BGvXbt2tSY0iIiIiDV+VybX3/oxytjPrIrh664wzYN06S7BDcnPh559h6dIExiUiIiIicVVhcu2cuzR0eY9z7u6yW92FWA8NGwY771xqaEgwqXHatATFJCIiIiJxV1nPdTCJcSowrZxNKuKc9V5/9BHMmQNoUqOIiIhIY1DhIjLe+zdCl0/WXTgNyKmnwj/+YWX5briBFi2sM1vJtYiIiEjDFc0iMjs55x52zk1yzv032OoiuHpt++1hxAh48kkoKQE0qVFERESkoaty+XPgJeBB4FGgJL7hNDBnnAHHHQeTJ8OIEeTmwrPPwqJFlnsH8gruBCA/IUGKiIiISKxEk1wXe+8fiHskDdHhh1uR68cf35pcg01qjEyuRURERKRhiKbO9RvOuT855zo559oEW1UPcs6Nc84tcc7NrOD2POfcKudcQWi7MuK2Ec65751zc51zl1Xj9SSXjAw4+WR47TVYsYLBgyElReOuRURERBqqaJLr04AxwCeEK4VEkx4+AYyo4j4feu9zQtu1AM65VOA+4BCgHzDSOdcviudLTmecAZs2wXPP0awZ9OuncdciIiIiDVU0i8j0KGfrGcXjPgBW1CCm3YC53vsfvPebgBeAI2twnOSQkwODB9vQEGxS49Sp4H1iwxIRERGR2KtsEZnfhS7/UN4Wo+cf5pz7yjn3tnOuf2hfZ+CXiPssCO2rv844A6ZPh6++IjfXVmn85ZeqHyYiIiIi9UtlExr3Bf4LHF7ObR4YX8vnng50896vdc4dCrwG7FjdgzjnRgGjADp06EB+fn4tw6qe4uLmeO8rfd60HXZgz/R0Fl13He53lwO78sQTM9lnn2VbjwHUeez1wdq1a3Ve4kTnNn50buNH5zZ+dG7jR+c2fpLx3Dofx/EJzrnuwJve+wFR3PcnIBdLsK/23h8c2n85gPf+pqqOkZub66fW8WzBvFYFFBcX89Ha3MrvePzxMGUKG+YtJLttE8aMgRtvDB8DIL8wJ66x1kf5+fnk5eUlOowGSec2fnRu40fnNn50buNH5zZ+EnVunXPTvPflJn/RlOLDOXcY0B/IDPYFExBrEVRHYLH33jvndsOGqCwHCoEdnXM9gIXAicBJtXmupHDGGfDSS2T+500GDvyDJjWKiIiINEBVJtfOuQeBLGA/bCGZY4HPo3jc80Ae0M45twC4CkgH8N4/GDrO+c65YqAIONFbN3qxc+7PwLtAKjDOez+r+i8tyRx0kBW3HjeO3Nw/8NJLNqnRuUQHJiIiIiKxEk3P9Z7e+12cc197769xzt0OvF3Vg7z3I6u4/V7g3gpumwhMjCK2+iM1FU47DW65hdybC3mksBU//AC9eiU6MBERERGJlWjqXG8IXa53zm0PbAY6xS+kBuz002HLFoYueA3QYjIiIiIiDU20KzS2Am7DKnz8BDwXx5garp12gr32ov87t5OR4ZVci4iIiDQwlSbXzrkU4D3vfaH3/hWgG9DHe39lZY+TSpx5Jk1mz2RQr7Wa1CgiIiLSwFSaXHvvt2BLkQftjd77VXGPqh7JzxnNm73Pjv4Bxx0HWVnkMpVp02DLlvjFJiIiIiJ1K5phIe85545xTnUtYiI7G447jtwfXmTtWpg9O9EBiYiIiEisRJNcnwu8BGx0zq12zq1xzq2Oc1wN25lnMnTDh4AmNYqIiIg0JFWW4vPeZ9dFII3K3nvTp+dmsn4q4osvmiY6GhERERGJkSp7rp1z70WzT6rBOdLOOJXBW6Yx9aMNVd9fREREROqFCpNr51ymc64NtsJia+dcm9DWHehcZxE2VKedRi7T+PLrFLxPdDAiIiIiEguV9VyfC0wD+oQug+11KlhZUaqha1eGDiyiqLgJ60uaJDoaEREREYmBCpNr7/1d3vsewCXe+57e+x6hbVBo6XKppdzTBwLQZvPiBEciIiIiIrFQ5Zhr7/09dRFIY7TjefuTzWqaFK9LdCgiIiIiEgPRlOKTOEnJymTX7X/jty0daO7XJDocEREREaklJdcJlvu7FnzNLuyzcXKiQxERERGRWqqwzrVzbkhlD/TeT499OI3P0N93YNMzjp6bv0t0KCIiIiJSS5UtInN76DITyAW+AhywCzAVGBbf0BqH3KG2qvyKLa1g5kwYMCCxAYmIiIhIjVVWLWQ/7/1+wK/AEO99rvd+V2AwsLCuAmzoevSAdDbzP3aHRx9NdDgiIiIiUgvRjLne2Xs/I2h472cCfeMXUuPiHDRPK+ID9oWnn4YNWrFRREREpL6KJrn+2jn3qHMuL7Q9Anwd78Aak+apRfxAD4pWrIfx4xMdjoiIiIjUUDTJ9RnALODi0PZNaJ/ESMvUtWwhlde2OxcefjjR4YiIiIhIDVU2oREA7/0G4P9Cm8RBm7Q1ZKUUcaP7Bye835GU2bNhp50SHZaIiIiIVFOVPdfOub2cc5Odc7Odcz8EW10E11g4BztkLGHm4u2YkHKUJjaKiIiI1FPRDAt5DLgDGA4MjdgkhrZLX0nPnnBDi1vwjz8BmzYlOiQRERERqaZokutV3vu3vfdLvPfLgy3ukTUyzsFll8HUwh2ZvCwHJkxIdEgiIiIiUk3RJNdTnHO3OeeGOeeGBFvcI2uE/vhH6NzZc33GdfDII4kOR0RERESqqcoJjcDuocvciH0e+F3sw2ncMjLg0ksdF1+8Ox9OKmLvH3+0VWZEREREpF6osuc6WKmxzKbEOk7OPhvaty3hBq6Axx5LdDgiIiIiUg3RDAvBOXeYc+5S59yVwRbvwBqrrCz46yWpvMvBfPHQdCguTnRINZKXZ5uIiIhIYxJNKb4HgROACwEHHAd0i3Ncjdqf/gStmm3ixmXnwMSJiQ5HRERERKIUTc/1nt77PwIrvffXAMMArXASRy1awEWjU3mNo5l5+7uJDkdEREREohRNcl0UulzvnNse2Ax0il9I9Ux+PgV33hnzw170l1SapW/kpg/2ggULYn58EREREYm9aJLrN51zrYDbgOnAT8BzcYxJgLZt4fzTiniBE5j7r9cSHY6IiIiIRCGaaiHXee8LvfevYGOt+3jvNaGxDvztulakp5Rw82PtoKSkyvtrEqGIiIhIYkVVLSTgvd/ovV8Vr2CktI4d4ewDf+aptX9g/rMfJjocEREREalCtZJrqXuX3rMDHsdt16xPdCgiIiIiUgUl10luhx0z+OOA6Tz6w34snrk07s+noSUiIiIiNRdNnev3otkn8XPZbe3YRBPu+PO8RIciIiIiIpWoMLl2zmU659oA7ZxzrZ1zbUJbd6BznUUo7DiiF8e3n8L9HwxgxXJf8R0LCmwTERERkYSorOf6XGAa0Cd0GWyvA/fGPzSJ9I/RRaz1zbn7rz8lOhQRERERqUCFybX3/i7vfQ/gEu99T+99j9A2yHtfZXLtnBvnnFvinJtZwe0nO+e+ds7NcM594pwbFHHbT6H9Bc65qTV6ZQ3MwL8cwJFpb3H3C+1ZsybR0YiIiIhIeaKZ0Pibcy4bwDk31jk33jk3JIrHPQGMqOT2H4F9vfcDgeuAh8vcvp/3Psd7nxvFczV8TZtyxR++ZeWm5jzwr3WJjkZEREREyhFNcv1P7/0a59xw4ADgMeCBqh7kvf8AWFHJ7Z9471eGmp8BXaKIpVEbOvZgDuJdbr8Dioqqvr+IiIiI1K1okutgacDDgIe9928BTWIcx1nA2xFtD0xyzk1zzo2K8XPVXwMHckXfV1mythmPPlLJxEYRERERSYi0KO6z0Dn3EHAgcItzLoMY1sd2zu2HJdfDI3YP994vdM5tB0x2zn0X6gkv7/GjgFEAHTp0ID8/P1ahRW3t2rW1et7i4uYAUR2j42HZDP/2Q264Koc+faeTnh5OsqtznIoUFuaEjlFQ42PE8ji1PbdSMZ3b+NG5jR+d2/jRuY0fndv4ScZz67yvvAfUOZeFjZ2e4b2f45zrBAz03k+q8uBWtu9N7/2ACm7fBXgVOMR7P7uC+1wNrPXe/6uq58vNzfVTp9b9/Mf8/HzyarHySl6rAjtOKCGt1Nq1vNv+FEZseI1HHoGzz67hcSqKJc8ua/s+jdVxantupWI6t/Gjcxs/Orfxo3MbPzq38ZOoc+ucm1bRvMAqe6C99+uBJYR7louBOTEIagdgPHBqZGLtnGsWMYGyGXAQUG7FkUapeXMOOrUDu7rp3HxjCcXFiQ5IRERERALRrNB4FfB34PLQrnTgmSge9zzwKbCzc26Bc+4s59x5zrnzQne5EmgL3F+m5F4H4CPn3FfA58Bb3vt3qvWqGjh37iiu8Ncx78dUXnwx0dGIiIiISCCaMddHA4OB6QDe+0VBz3JlvPcjq7j9bODscvb/AAza9hGy1a67cmTOfPp/N5cbb+zFiSc6UmI2Cl5EREREaiqalGyTt4HZHrYO1ZAESxl1Nv/YcCWzZjlefz3R0YiIiIgIRJdcvxiqFtLKOXcO8B/g0fiG1cjk5NhWHSedxPFN36RXiyXccANUMS9VREREROpAlcNCvPf/cs4dCKwGdgau9N5PjntkjUiNKmq0bEnaCcdw+fPXcPa0+5hUZe0WEREREYm3aCY03uK9n+y9H+O9v8R7P9k5d0tdBCdVOOccTt34CF3brOX66xMdjIiIiIhEMyzkwHL2HRLrQKQGhg2jSb8dGdP8QT76CAqLNRxeREREJJEqTK6dc+c752ZgpfS+jth+BL6uuxClQs7BqFGcPf+fbNdmM/M3dkh0RCIiIiKNWmU9188BhwMTQpfBtqv3/pQ6iE2iceqpNM3w/G3nt1hZ3ILlm7M1uVFEREQkQSpLrku89z9570d673+O2FYEd3DONa+DGKUybdrAMcdw/qw/k+E2MXN9L3r2hL/+FT78EEpKEh2giIiISONRWXL9unPudufcPpG1rZ1zPUOrLb4LjIh/iFKlc84he/VCLsm4m52azqd/f7jvPthnH+jUCc45ByZOhI0bEx2oiIiISMNWYXLtvd8feA84F5jlnFvlnFuOLX3eETjNe/9y3YQpldp3X9hxR47f/BydmqzgzTdh2TL4979h//3t8rDDoH17OPFEeOEFWL06zjEVFNgmIiIi0ohUWufaez8RmFhHsUhNOQdnn80uf/87O5T8AOSQnQ3HH2/bxo3w3//Cq6/C669bst2kiSXeRx8NRx4J221X/afdsgWWLIGFC2HBgtKXs9Z1p1vm4pi/VBEREZFkVuUiMlJPnH46m/9+BWdtuA+2HAUp4S8lMjLgkENse+AB+PRTS7RffRVGjYJzz4W99rJEu6gImjaFTZtg0aJtk+bIy0WLoLi4dBhpabD99lBY0pxV65oxezbstFPdngoRERGRRFFy3VBstx2PZl7I+Rv+D669Fq6+uty7pabC8OG2/etf8PXX4UT7b3+z+6S5YjIytn1rZGVBly627buvXXbuXPpyu+0sr98tew4F63pz4IHw0UfQtWscX7uIiIhIklBy3YD8u8mp9CiZy4hrroF+/WxMSCWcg0GDbLv6avjhBziw/0KKtmRw3hXttkmeW7a0x0QjK3UjA5v9wNzCnTnwQKtc0r597V+jiIiISDKrMrl2zvUCFnjvNzrn8oBdgKe894XxDU2qzTlubzqWEbutgNNOg549ITc36of37AldM5YCcOWV7WodTnZqEW++AQcdBCNGwJQp0KJFrQ8rIiIikrSiWf78FaDEOdcbeBjoii0wI0los2sC48dDhw42U3HhwoTGs/fe8PLLNvzkiCNsTHe08vJg9OiceIUmIiIiEnPRJNdbvPfFwNHAPd77MUCn+IYltbLddvDGG1Zv78gjYf36hIZz2GHw1FPwwQc2UmXz5oSGIyIiIhI30STXm51zI4HTgDdD+9LjF5LExMCB8NxzMH06nHEGiV4TfeRIW9jmzTctnC1bEhqOiIiISFxEk1yfAQwDbvDe/+ic6wE8Hd+wJCYOPxxuuQVefNEqiCTY+efDDTfAs8/CxRcnPN8XERERiblKJzQ651KBK7z3Jwf7vPc/ArfEOzCJkUsugVmzrBxIv35w3HEJDefyy2HFCrj9dmjdOilyfhEREZGYqWqFxhLnXDfnXBPv/aa6CkpiyDl46CGYO9cqiPToUa0KIvEI57bboLAQrrvOEuy//CVh4YiIiIjEVDR1rn8APnbOTQDWBTu993fELSqJrYwMqyCy2242wfHzz61wdYIE+X5hIfz1r5Zgn356wsIRERERiZloxlzPwyYypgDZEZvUJ9ttBxMmWAWRo45KeAWR1FQbe33ggXDWWbZCpIiIiEh9V2XPtff+GgDnXPNQe228g5I42WUXqyBy5JFw5pnw/PPRL7kYB0GH+oEHwoknwsSJsP/+CQtHREREpNaq7Ll2zg1wzn0JzAJmOeemOef6xz80iYvDD4ebb4Z//9sGPSdY8+bw1luw006W8//vf4mOSERERKTmohkW8jDwV+99N+99N+BvwCPxDUviaswY+OMf4aqr4KWXEh0NbdrApEm2qOShh8LMmYmOSERERKRmokmum3nvpwQN730+0CxuEUn8OQcPPwx77WUVRKZNS3REdOoEkyfbUJGDDoIff0x0RCIiIiLVF01y/YNz7p/Oue6hbSxWQUTqs2DA83bbwRFHwKJFiY6Inj2tB3vDBjjgANi4MdERiYiIiFRPNMn1mUB7YDzwCtAutE/qu6CCyKpVVkGkqCjRETFgALz9NixeDDNmQHFx4iZcioiIiFRXpcl1aIXG8d77i7z3Q7z3u3rvR3vvV9ZRfBJvQQWRqVOtgkgSrEm+++7w2mtWLXDevOZ8+22iIxIRERGJTqXJtfe+BNjinGtZR/FIIhxxhFUQeeEFTt2YHHNVDzjAerE3b3bk5sLjjydF3i8iIiJSqWhWaFwLzHDOTab0Co0XxS0qqZH8nNHBteo/eMwYmDWLs556gBUp7WBhe+jY0VZ7SZA2bWDnndfQoUNLzjwT/vMfePBByNYSRiIiIpKkokmux4c2achCFURmPPsVY4qugy7XWWLdsSN06WJb586lL7t0ge23h8zMuIWVnu6ZPBluuskqB37+ObzwAuy6a9yeUkRERKTGKk2uQ2OuT/fe71dH8UgiZWRwSbMHGFw8lZvvaAILFsDChXb5zTdWK2/16m0f165d6YS7c2cO2JTK++kH1C6eggKaFxeTmprL2LGw775w0kkwbBjceitcfHHVC0zm5dllfn7tQhERERGJRqXJtfe+xDm3xTnX0nu/qq6CksTZ6JryWfrecG5O+XdYvdoS7iDpjkzAFyywruWlSxkL7Lv5P1A8CdKi+YKkanvvDQUFNu/yL3+B996DJ56Atm1jcngRERGRWtOY64akLrpnW7SwrW/fiu+zcSP3thrLnzf8C847Dx55pOou5ii1bWuVRO65x4aJDxpkxU722ScmhxcRERGplWjqXI8H/gl8AEyL2ETKl5HByxkn82TGOfDYYzB2bEwP7xxcdBF8+ik0bQr77QfXXgslJTF9GhEREZFqq7Ln2nv/pHOuKbCD9/77OohJGojHM87ntNNT4MYboX17GD06pscfMgSmT4fzz7fJjlOmwLPP2hxLERERkUSosufaOXc4UAC8E2rnOOcmxDkuaQicg/vug2OOsUHSzz4b86fIzoann7Y62J9/bsNE3n475k8jIiIiEpVohoVcDewGFAJ47wuAnnGLSBIqP2d0RL3sGEhNtaR6v/3g9NPjkvk6Z4eeNs16rQ89FC65BDZtivlTiYiIiFQqmuR6czmVQrZEc3Dn3Djn3BLn3MwKbnfOubudc3Odc18754ZE3Haac25OaDstmueTJJWRYbMQBw6EY4+Fzz6Ly9P06WOH/tOf4PbbYfhwKCqKy1OJiIiIlCua5HqWc+4kINU5t6Nz7h7gkyiP/wQwopLbDwF2DG2jgAcAnHNtgKuA3bFe86ucc62jfE5JRi1aWK/19tvDYYdZ3ew4aNrURqK8/DLMnm292UuWaOl0ERERqRvRJNcXAv2BjcBzwCpgdDQH995/AKyo5C5HAk958xnQyjnXCTgYmOy9X+G9XwlMpvIkXeqDDh1g0iRo0gQOPhjmz4/bUx1zjNXEzsqCb7+1XuwpU+L2dCIiIiJAFMm193699/4K7/3Q0DbWe78hRs/fGfglor0gtK+i/VJPVDh2u0cPePddWLPGEuxly+IWQ/fukJMDO+4IP/8Mv/udbR9/XP1j5eWFV3sUERERqUhsls5LIOfcKGxICR06dCA/Aetcr127NiHPGw85hYUAFNTy9VR1nJbXXssuY8awbp99+Or22ylp2nSb+7zWfTQlJSXk599T4zhWr84hKwvuvfdrJkzoxHPPdWP48CYMHbqCM8/8kT591kR1nMLCHADy8wtqHEuyaUjv22Sjcxs/Orfxo3MbPzq38ZOM59b5OA9Gdc51B9703g8o57aHgHzv/fOh9vdAXrB5788t734Vyc3N9VOnTo1p/NHIz88nr6F0awavo7Zv1GiOM2EC/OEPcMABdr1Jk22OUVhYSKuCgpiFsW6djcm+9VZYvhyOOMIWoBk0qHrHaQga1Ps2yejcxo/Obfzo3MaPzm38JOrcOuemee9zy7stmjHX8TQB+GOoasgewCrv/a/Au8BBzrnWoYmMB4X2SUNyxBHw8MM2TOT002FLVEVoaqVZM7j0UvjxR7juOnj/fRs6ctxxcZtjKSIiIo1INIvI7OScey8op+ec28U5F9V61s6554FPgZ2dcwucc2c5585zzp0XustE4AdgLvAI8CcA7/0K4Drgi9B2bWifNDRnngk33wzPP28LzdRRWY/sbFuV/ccf7fKdd2DAADjlFJgzp05CEBERkQYomp7rR4DLgc0A3vuvgROjObj3fqT3vpP3Pt1738V7/5j3/kHv/YOh2733/gLvfS/v/UDv/dSIx47z3vcObY9X/6VJvXHppfDXv8Ldd8NNN9XpU7dubT3YP/4IY8bA+PHQt6/l/D/9VKehiIiISAMQTXKd5b3/vMy+4ngEI42Uc3DbbdZtfMUV8MgjsTt2QYFtVWjXDm65xZLsCy+E556DnXaC88+HBQtiE4oqjoiIiDR80STXy5xzvQAP4Jw7Fvg1rlFJ45OSAuPGwSGHwHnnWRdyAnToAP/3fzBvHpx9Njz2GPTuDXPnwtq1UFKSkLBERESknogmub4AeAjo45xbiC0gc16ljxCpifR0eOkl2H13GDkSQuX8EqFzZ7j/flvl8ZRTYOFCW+2xTRvL/2+4AT74QMuri4iISGmV1rl2zqUCf/LeH+CcawakeO+jKwwsUhPNmsGbb8Lee8PMmaT27Fmrw4UXssmv0eO7d4dHH4VZsyzXz8uDjz6ySZBgnwdyc20FyOHDYa+9oG3bWoVcJ/LyrHZ3LaocioiISDkqTa699yXOueGh6+vqJiRJqGQo5NymjZXn692b5vPmwX//a0srJlBGhg0ZeeABa69YAZ98Yon2Rx/BXXfZsHGAfv3Cyfbee0O3bjasPFaSpeZ2ssQhIiKSTKJZofFL59wE4CVga4LtvU/MoFhpHLp0gZwctnz1FakHH2yTHE8/PdFRbdWmDfz+97YBbNgAX3wRTrb//W8r4Q02xGT4cBta0qIFFBdDWr1fG1VERETKE82/+ExgORDZdegBJdcSX5mZrOndm1adOsEZZ9gsw2uvjW03cIxkZlov9d57W3vLFpg5M5xsf/hhuOpIy5YwdCjssUd469gxcbEnmnrARUSkIakyufben1EXgYiUKzUV3noL/vQnuP56+OEHqyqSkZHoyCqVkgK77GLbn/5k+/bYA1avhgMPhM8+gzvugM2b7bZu3cKJ9rBhtmpkkr9EERERKUeVybVzLhM4C+iP9WID4L0/M45xiYSlp9sYi5494R//gF9+gVdfrR8zByNkZtp2113W3rABvvzSEu3PPoNPP7XhJABNmsCQIaV7t3fYIfpOe+/t+KtW2UTMVavCW2GhncKiogzuuQeaNrW4yl6Wt69pU4st2b48iFXvt3rRRUSktqIZFvI08B1wMHAtcDLwbTyDEtmGc3D55ZZgn3aade9OnGhFqOupzEx7GcOGhfctWhROtj/7DB56CO68027r2NGS7J9/tuT5L38pnTCXTaCDXvGKNeWii2oee3GxfbEwdKjFVnbr1Cl8PSurZs8jIiJS30STXPf23h/nnDvSe/+kc+454MN4ByZSrhNOsMmORx5pmebrr1v9uwZi++3hD3+wDSxBnjGjdMIdLMv+6KPQqpWN4W7ZErbbzlaVDNotW5a+PfL6yJGwalUhU6a0YsMGq9cdeRnNvueeswS7fftwHfDFi228eVnZ2RUn3suX2wTP6dMtCc/Ksh7y4DIlmmr8IiIiSSKa5Dro/yp0zg0AfgO2i19I0iDE83v1vfayLPPQQ2H//eHJJy3pjqNEDRNIT7fhIUOGhMdu7723JZzvv1/z46alWa9zu3Y1P8bnn9vlxInhfSUlsGwZ/PZb6e3XX8PXv/4aJk2yHvZIu+5a/vMEw1GCxDsy+Q62776zc3LeeXbpXPgy8npVt/38MzRvDitXQuvWNT83IiLSeEWTXD/snGsN/BOYADQHroxrVCJV6d3bBikffTSceKJNdLzssuQbDBwHqamJjqBiqalWD7xDBxg0qPL7FhVZT/cf/mA94NdfD+vXl96KiipvFxaGL7dssaH43tv1yMvy9pV3H+8ttjZtwvXK99rLtp4969/bS2PI46ehndtYLCzV0M6JSE1FUy3k0dDV94HaLZcnEktt28LkyXDmmTbRcd48W+UlPT3RkZVL/3BKa9rUVsBs0cLaRxxR82PF6p/63nvDmjVw3HHw8cel65V36BBOtPfaCwYPtsmd5cWi1S9LU9JVPp2X8um8bEvnpH6JplpIub3U3vtrYx+OSDVlZMAzz1i34vXXw/z58NJLNrBYpJpSU21s+hVXWHvLFpg1yxLtYBsfqvDftCnstls42R42LDmGkmzebBNj58+HJUtsuMvnn4e/UcjMrPoYkZLpn7p6V7fV0F6PSEMQzbCQyGXPM4Hfo2ohkkycg+uuswR71Cj7Lv+tt6x2Xaw0sP9g+fmQn18A5CU2kCSXkgIDB9p23nm279dfSyfbt95qw1oA+veHFSsAMhg3zhL11q1LX7ZoUfUkzYrebt7D0qVWSnH+/PIvf/1120mlu+8evp6dHU60O3SwibAVtbOza3DSRCRpxeJfWQP7dxgX0QwLuT2y7Zz7F/Bu3CISqakzzrCE+phjLJt4881ER1RajP4iJcsftGSJI5aieU2dOsGxx9oGsG4dfPGFJdoffWSTK0tKmnLWWeU/3jn7YqV1620T7+By4UK735VXlk6eFyywai2RMjOha1d76x90UPh6167WA19SYgubLl5sPdmLF4e377+HDz6wii3BePOyx/beJsAOGlTxxNCqLr/+2i5PPtkm0Va0tW1b/lCburJpk01mXbFi263s/unT7TUdfDA0a1bx1rx5xbdt3mzflnhfd+P5i4ttjsLKleHXtHKlfduxbl0T7rknPNm37OTfqvYvXmyXr7++bdWfstfTounakwaroSfoNXl7ZwFdYh2ISEzsvz988olVEtlnH+jRo3YlMSTuYvHHNZF/oJs1s38UwT+LffeFFSsKefPNVqxcGU5kIhOasvu+/TbcLioKH/uGGyyZ32EHqxhz1FHhxDm4bNeu4sTsxhvt8ve/r/w1FBdblZfIxDtIxJ96yhL0nj2jmxgaeVlSEr70Hv73P3uespViIrVoEU60yybfixbBhg1NePRRO2bkVly87b7y7jNnjsVy3HHbJtBr11Ycl3P24adNG9vS0uw4hYX2YWjduvC2fn3l57us9PTSiXh5lxXdtmyZJbr//nc4US67Re5fs6aySLJqXPs+0lFHRfeaIxPuyMuZM+1D1o032p/wHj1sfkaHDtF/CEmmHtraDGcqKbEP1ytX2vv33XftdyTYWra094JKliaXaMZczwCCPo1UoD22mIxIcurXz0r1HXGEdSn26lW3XUPSqDlnvZHdutlWXRs2wO9+Z0nphx/WzfzctLRw3fGygpKLr75a8+OXTVI2bbKEb9myyrclS+Cbb+z6uq0DFLM455zonjf4WQRbWpolvs7ZWPo2bewDyqBB4aS57BYk1C1blk5gKku8tmyxD0mRCXd52003WfJ00kmW2K9bV/py+XIrDxm5f+PG8l/riSeGr2dmhuNu3do+iA0aFP62JPK2YDvrLFi9ehX5+S1LVc8pr6JORftPOskuH3qodHWfoqLS1yu6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\n",
+      "text/plain": [
+       "<Figure size 864x576 with 1 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
    "source": [
     "# %%%%%%%%%%%%%%%%%%%%%%% Parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
     "w_t = np.array([[1], [2], [0.5]]) # True weights\n",
@@ -181,9 +1177,30 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 6,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of weight decays is  100\n",
+      "Weight decay #  100  of  100  decays done.\n"
+     ]
+    },
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "<Figure size 720x576 with 1 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
    "source": [
     "# %%%%%%%%%%%%%%%%%%%%%%% Parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n",
     "import numpy as np\n",
@@ -280,9 +1297,32 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 7,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<matplotlib.legend.Legend at 0x1d54722b430>"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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KEfjoyw5qgJWEmLRokVUqVKFhg0b4pwLOpyU5L3nr7/+YtGiRTRq1CjocCTNKEdjpxyVRFKOxk45KomkHI1dWXNUU4hDLjcXGjaErCy7zs0NOqLI/Pvvv9SsWVMJHQPnHDVr1tSZvZBTjmYu5WhqUI5mLuWoJJJyNHZlzVGNwIZYbi4MGAB5efb5ggX2OUBOTnBxRUoJHTv9DsNNOSr6HYabclT0O5RE0t9X7MryO9QIbIgNGrTpRbdAXp7dLiLBU46KhJtyVDLG1Knw2WdBRyGSFCpgQ2zhwuhul8T57LPPOOywwwB44403uO2224q979KlS3n44YejfozBgwczdOjQMscoyaccDQ/lqBRFORoeytEEu+oq6N8fvA86EklRqZSjKmBDrH796G6X6K1fvz7q7+nTpw9XX311sV8va1JL6lGOJp5yVGKhHE085WhIzJoFv/8OP/8cdCQSMumYoypgQ2zIEKhUafPbKlWy26V08+fPp1mzZuTk5NC8eXOOOeYY8vLyaNiwIVdddRXt2rXjpZde4oMPPqBjx460a9eOY489lpUrVwLw3nvv0axZM9q1a8err7668bjPPPMM559/PgB//PEHffv2ZY899mCPPfZg3LhxXH311fz444+0bduWK664AoA777yTDh060KZNG2688caNxxoyZAhNmjShc+fOzJkzJ4m/HYkH5WhslKOSaMrR2ChHU0Reni3wBvjqq2BjkaTK1BxVE6cQK2gwMWiQTXeqX99edFOh8cRmLr4YpkyJ7zHbtoV77y31bnPmzOHJJ5+kU6dOnH766RvPFtWsWZNvvvmGJUuWcNRRR/HRRx9RuXJlbr/9du6++26uvPJKzjrrLD755BN22203+vXrV+TxL7zwQg444ABGjx7N+vXrWblyJbfddhvTp09nSv7P/MEHH/D9998zceJEvPf06dOHMWPGULlyZV544QWmTJnCunXraNeuHXvttVecfkGSDMrREihHJQSUoyVQjqaP77/f9PHEiXDsscHFkqmUo0nNURWwIZeTk4IvtCFSr149OnXqBMCJJ57I/fffD7AxSSdMmMDMmTM33mfNmjV07NiR2bNn06hRI3bfffeN3zts2LCtjv/JJ5/w3HPPAZCdnU3VqlX5559/NrvPBx98wAcffMCee+4JwMqVK/n+++9ZsWIFffv2pVL+8ECfPn3i/eNLEihHY6MclURTjsZGOZoCZs+262rVNAKbgTIxR1XASuJFcPYoUbZszV3weeXKlQHbQPnggw9m5MiRm91vShzPonnvueaaaxg4cOBmt98b4O9FZDPKUeWohJtyVDlakjlzwDkbec3NhXXroJze4ieVcjSpOao1sJLWFi5cyPjx4wEYMWIEnTt33uzr++67L19++SU//PADAKtWrWLu3Lk0a9aM+fPn8+OPPwJslfQFunfvziOPPALYIvlly5ZRpUoVVqxYsfE+hxxyCE899dTG9Qa//PILf/75J126dOG1115j9erVrFixgjfffDO+P7xIClCOioSbcjQFzJ4NDRpA1662HnbGjKAjkiTKxBxVAStprWnTpjz00EM0b96cf/75h3POOWezr9eqVYtnnnmG/v3706ZNm41TKipUqMCwYcPo3bs37dq1o3bt2kUe/7777uPTTz+ldevW7LXXXsycOZOaNWvSqVMnWrVqxRVXXEGPHj044YQT6NixI61bt+aYY45hxYoVtGvXjn79+rHHHntw6KGH0qFDh2T8SkRCRTkqEm7K0RQwZw40awb77GOfaxpxRsnEHHU+xfaLat++vZ80aVLQYUgpZs2aRfPmzQONYf78+Rx22GFMnz490DhiVdTv0jk32XvfPqCQSqQcTQ3K0fhRjkoiKEfjJ61zdMMGqFIFzjoL7rkHata0qcSPPZbYIEU5GkfR5qhGYEVEREREUtEvv9i04WbNbB1sgwbw669BRyWSUCpgJW01bNgw5c9IiaQz5ahIuClHU0DBvppNm9r1jjvCH38EF48kVabmaMIKWOdcPefcp865mc65Gc65i4q4T1fn3DLn3JT8yw2JikdENqccFQk35aiIlGruXLtWASsZJJE9ttcBl3nvv3HOVQEmO+c+9N7P3OJ+Y733hyUwDhEpmnJUJNyUoyJSsr//tutatey6oID13qYUi6ShhI3Aeu9/895/k//xCmAWUCdRjyci0VGOioSbclRESrVqFZQvbxewAva//2D58mDjEkmgpKyBdc41BPYEiurr3dE5951z7l3nXMtivn+Ac26Sc27S4sWLExmqSEZSjoqEm3JUJHU55+Y756blT/OPbwvwVaugcuVNn++4o11rGrGksYQXsM657YBXgIu991ueDvoGaOC93wN4AHitqGN474d579t779vXKpgiIUmXmwsNG0JWll3n5gYdUXTmz59Pq1atgg5jK127diXILS2Uo+lDOZoYylGJF+VoYgSdoxHq5r1vG/ete/LyoFKlTZ+rgM1omZKjCS1gnXPlsRfdXO/9q1t+3Xu/3Hu/Mv/jd4DyzrkdEhmTlE1uLgwYAAsW2LKKBQvs81R78Y23devWBR1CTJSj6UM5WjTlqISFcrRoqZ6jgdMIrCRYGHM0kV2IHfAkMMt7f3cx99kp/3445/bOj+evmB54yRJYuzamQ8jWBg2yk3yF5eXZ7fGQiLPSd999N61ataJVq1bce++9gCVhTk4OzZs355hjjiEv/4e6+uqradGiBW3atOHyyy8HYPHixRx99NF06NCBDh068OWXXwIwePBgTjrpJDp16sRJJ53Evvvuy4wZMzY+bsFZplWrVnH66aez9957s+eee/L6668DsHr1ao4//niaN29O3759Wb16dew/bBkElqOSEMpR5ahyNNyUo+mXo1HwwAfOucnOuQFF3aHM0/xVwKYM5Wgcee8TcgE6Ywk7FZiSf+kFnA2cnX+f84EZwHfABGC/0o671157+RL16uV9ixbef/xxyfeTqDjnvZ0z3vziXNH3nzlzZsTHfv557ytV2vy4lSrZ7WU1adIk36pVK79y5Uq/YsUK36JFC//NN994wH/xxRfee+9PO+00f+edd/olS5b4Jk2a+A0bNnjvvf/nn3+8997379/fjx071nvv/YIFC3yzZs28997feOONvl27dj4vL8977/3dd9/tb7jhBu+997/++qtv0qSJ9977a665xg8fPnzjMXfffXe/cuVKf9ddd/nTTjvNe+/9d99957Ozs/3XX39d7M9S1O8SmORTNUclIZSjylHlaLgpR9MvRyO9AHXyr2vn52qXku4fVY527+59p06bPl+3zvusLO+vuy7yY0iZKEeDy9FEdiH+wnvvvPdtvM35b+u9f8d7/6j3/tH8+zzovW/pvd/De7+v935cjA8KAwfC6tXQvTv06wc//xyXnyfT1a8f3e3RSMRZ6S+++IK+fftSuXJltttuO4466ijGjh1LvXr16NSpEwAnnngiX3zxBVWrVqVChQqcccYZvPrqq1TKX0vy0Ucfcf7559O2bVv69OnD8uXLWblyJQB9+vShYsWKABx33HG8/PLLAIwaNYpjjjkGgA8++IDbbruNtm3b0rVrV/79918WLlzImDFjOPHEEwFo06YNbdq0KfsPGoNAclQSRjmqHFWOhptyNP1yNFLe+1/yr/8ERgN7x+3gq1ZtvgY2Oxt22EEjsCGjHI1vjialC3HSOAd9+sCMGTB4MLzxBjRrBrfeai3FkyjVGzVsaciQzf8/gn0+ZEjsx164MLrbY+G22BPNOUe5cuWYOHEixxxzDG+99RY9e/YEYMOGDUyYMIEpU6YwZcoUfvnlF7bbbjsAKhearlOnTh1q1qzJ1KlTefHFF+nXrx9gsxteeeWVjd+/cOFCmjdvHv8fSspEORo55agEQTkaOeVoeDnnKufv44xzrjLQA5getwfYcgoxbNoLVkJDORpf6VXAFqhYEW68EWbNgh494NproVUreOedpDx8OjZqyMmBYcOgQQM7T9CggX2ekxP7sRNxVnr//ffntddeIy8vj1WrVjF69Gj2339/Fi5cyPjx4wEYMWIEnTt3ZuXKlSxbtoxevXpxzz338N133wHQo0cPHnjggY3HnDJlSrGP169fP+644w6WLVu28SzTIYccwgMPPFAwfYhvv/0WgC5dujBixAgApk+fztSpU8v+g0qZBJqja9faxvO//mqXxYvjcoJNOaocTSd6HY2OcjTUOboj8IVz7jtgIvC29/69uB09L08FbApQjsY5R4ubWxzWS5nW7rz3nvdNmtiE88MP9/7HH6M/RhQaNCh6nUuDBgl92FAJel2A997fddddvmXLlr5ly5b+nnvu8fPmzfNNmzb1OTk5vlmzZv6oo47yq1at8r/++qvv0KGDb926tW/VqpV/5plnvPfeL1682B933HG+devWvnnz5n7gwIHee1sXcOedd272WL///rvPzs72gwcP3nhbXl6eHzBggG/VqpVv0aKF792798bb+/Xr55s1a+b79u3r995771Cv3Yn2kgrr6xKeoxs2eD9vnvcjR3p/zTXe9+njfevW3lerVvQDg/eVK9v/qR49vL/oIu+ffNL7qVNtPVMCKEeVo2EWdY6uWuX95Mnejxrl/b33en/DDd5feqn3l1zi/eWXez94sPf33+/9Sy95P3Gi9/nrv8JMOaocjchOO3l/1lmb35aT433DhpEfQ8pEORpcjjr7eupo3769L9M+QmvWwL33wi232AjIlVfC1VdvPZ8nDrKy7E9zS87Bhg1xf7hQmjVrVlRTCHJzbR3AwoV2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csst7nMemy9uHxVgLHtii6p3D/r3ssX9PyOxTZwi+d30BJ7N/3gHbDpBzYBieRc4Nf/j5ti6AJfA309Dil/c3pvNF7dPTOTfTkC/b+VogDkapvyMIh7laHJ/38pR5Wi08SQtR5WfycnPKOJRjipHt4wnITmasD+uZFyw7lVz85NlUP5tN2NnfcDOKLwE/ABMBHYNMJaPgD+AKfmXN4KKZYv7Jiypo/jdOGyqx0xgGnB8gLG0AL7MT/gpQI8ExjIS+A1Yi52dOwM4Gzi70O/lofxYpyX6eQro960cDThHw5SfEcajHE3u71s5qhyNNp6k5KjyM7n5GWE8ylHlaOE4EpajLv8AIiIiIiIiIqGWymtgRUREREREJIOogBUREREREZGUoAJWREREREREUoIKWBEREREREUkJKmBFREREREQkJaiAFRERERERkZSgAlY2cs5Vc86dW8zXGjrnVjvnppRyjFzn3N/OuWMSEqRIBlOOioSbclQk3JSj6UEFrBRWDSgyqfP96L1vW9IBvPc5wBtxjElENqmGclQkzKqhHBUJs2ooR1OeClgp7DagsXNuinPuzpLu6Jyr7Jx72zn3nXNuunOuX5JiFMlkylGRcFOOioSbcjQNlAs6AAmVq4FWpZ15ytcT+NV73xvAOVc1kYGJCKAcFQk75ahIuClH04BGYKWspgEHO+dud87t771fFnRAIrIZ5ahIuClHRcJNORpSKmClTLz3c4F2WHL/n3PuhoBDEpFClKMi4aYcFQk35Wh4aQqxFLYCqBLJHZ1zuwB/e++fd84tBc5MZGAiAihHRcJOOSoSbsrRNKACVjby3v/lnPvSOTcdeNd7f0UJd28N3Omc2wCsBc5JSpAiGUw5KhJuylGRcFOOpgcVsLIZ7/0JEd7vfeD9BIcjIltQjoqEm3JUJNyUo6lPa2AlUuuBqpFs7gwcAPybjKBEZCPlqEi4KUdFwk05miKc9z7oGERERERERERKpRFYERERERERSQkqYEVERERERCQlqIAVERERERGRlKACVkRERERERFLC/wPmLS1klawgcgAAAABJRU5ErkJggg==\n",
+      "text/plain": [
+       "<Figure size 1152x288 with 4 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
    "source": [
     "#create the dataset\n",
     "t = np.linspace(0,1,11)\n",
@@ -349,7 +1389,10 @@
     "plt.xlabel('t [s]')\n",
     "plt.ylabel('h [m]')\n",
     "plt.title('Degree = 10')\n",
-    "plt.legend()"
+    "plt.legend()\n",
+    "\n",
+    "def add_fun(a,b):\n",
+    "    return a+b"
    ]
   }
  ],
@@ -369,7 +1412,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.6"
+   "version": "3.8.6"
   }
  },
  "nbformat": 4,
diff --git a/examples/02471/instructor/02471/week02/__pycache__/week2.cpython-38.pyc b/examples/02471/instructor/02471/week02/__pycache__/week2.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8eed558995bb1bfbcdf3fd6e2f98ffeb6297f6ea
Binary files /dev/null and b/examples/02471/instructor/02471/week02/__pycache__/week2.cpython-38.pyc differ
diff --git a/examples/02471/instructor/02471/week02/week2.ipynb b/examples/02471/instructor/02471/week02/week2.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..6bc411a7d674f4520b58bb03f2d9f1abeb2f7476
--- /dev/null
+++ b/examples/02471/instructor/02471/week02/week2.ipynb
@@ -0,0 +1,69 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Exercise 2.2.1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "hello world\n",
+      "6\n"
+     ]
+    }
+   ],
+   "source": [
+    "var = \"hello world 2\"\n",
+    "def myfun(a,b):\n",
+    "    return a*b\n",
+    "\n",
+    "output = myfun(2,3) + 10\n",
+    "print(var)\n",
+    "print(output)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "z = 234 \n",
+    "def mymul(d):\n",
+    "    return myfun(d,2)+1"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/autolab_example/autolab_example.py b/examples/autolab_example/autolab_example.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cdf58ac889a8c71b98bcbeb4788d299391ecf7f
--- /dev/null
+++ b/examples/autolab_example/autolab_example.py
@@ -0,0 +1,18 @@
+import os
+from autolab.autolab import deploy_assignment
+
+if __name__ == "__main__":
+    wdir = os.getcwd()
+    args = [('example_simplest', 'cs101', 'report1_grade.py', 'report1_grade.py'),
+            ('example_framework', 'cs102', 'report2_grade.py', 'report2_grade.py'),
+            ('example_docker', 'cs103', 'report3_complete_grade.py', 'report3_grade.py'),
+            ]
+
+    for bdir, name, instructor, student in args:
+        instructor_base = f"{wdir}/../{bdir}/instructor"
+        student_base = f"{wdir}/../{bdir}/students"
+
+        output_tar = deploy_assignment(name, INSTRUCTOR_BASE=instructor_base, INSTRUCTOR_GRADE_FILE=f"{instructor_base}/{name}/{instructor}",
+                                       STUDENT_BASE=student_base, STUDENT_GRADE_FILE=f"{student_base}/{name}/{student}",
+                                       output_tar=None)
+        print(output_tar)
diff --git a/examples/autolab_example/cs101.tar b/examples/autolab_example/cs101.tar
new file mode 100644
index 0000000000000000000000000000000000000000..12bb8cb3a744022b918d3dac9ad98625a30b5279
Binary files /dev/null and b/examples/autolab_example/cs101.tar differ
diff --git a/examples/autolab_example/cs102.tar b/examples/autolab_example/cs102.tar
new file mode 100644
index 0000000000000000000000000000000000000000..38eaca29d10e6011f09737ae02e85fb059ef7f90
Binary files /dev/null and b/examples/autolab_example/cs102.tar differ
diff --git a/examples/autolab_example/cs103.tar b/examples/autolab_example/cs103.tar
new file mode 100644
index 0000000000000000000000000000000000000000..a8461f339c31d46dd69e93adfe9a287a7cc4e2a6
Binary files /dev/null and b/examples/autolab_example/cs103.tar differ
diff --git a/autolab/lab_template/hello/Makefile b/examples/autolab_example/tmp/cs101/Makefile
similarity index 62%
rename from autolab/lab_template/hello/Makefile
rename to examples/autolab_example/tmp/cs101/Makefile
index b49f04804ab2cdfd1215204910e4afb7e8ae472e..c0b709ce773ab1bb73048dad0bf97fe0207af42f 100644
--- a/autolab/lab_template/hello/Makefile
+++ b/examples/autolab_example/tmp/cs101/Makefile
@@ -3,22 +3,30 @@
 #
 
 # Get the name of the lab directory
-LAB = $(notdir $(PWD))
+# LAB = $(notdir $(PWD)) # Fail on windows for some reason...
 
 all: handout handout-tarfile
 
 handout: 
 	# Rebuild the handout directory that students download
-	(rm -rf $(LAB)-handout; mkdir $(LAB)-handout)
-	cp -p src/Makefile-handout $(LAB)-handout/Makefile
-	cp -p src/README-handout $(LAB)-handout/README
-	cp -p src/hello.c-handout $(LAB)-handout/hello.c 
-	cp -p src/driver.sh $(LAB)-handout
+	(rm -rf cs101-handout; mkdir cs101-handout)
+	cp -p src/Makefile-handout cs101-handout/Makefile
+	cp -p src/README-handout cs101-handout/README
+	cp -p src/driver_python.py cs101-handout
+
+	cp -p src/student_sources.zip cs101-handout
+
+	cp -p src/Report1_handin.token cs101-handout
+
+	cp -p src/docker_helpers.py cs101-handout
+
+	cp -p src/report1_grade.py cs101-handout
+
 
 handout-tarfile: handout
 	# Build *-handout.tar and autograde.tar
-	tar cvf $(LAB)-handout.tar $(LAB)-handout
-	cp -p $(LAB)-handout.tar autograde.tar
+	tar cvf cs101-handout.tar cs101-handout
+	cp -p cs101-handout.tar autograde.tar
 
 clean:
 	# Clean the entire lab directory tree.  Note that you can run
@@ -27,7 +35,7 @@ clean:
 	rm -f *~ *.tar
 	(cd src; make clean)
 	(cd test-autograder; make clean)
-	rm -rf $(LAB)-handout
+	rm -rf cs101-handout
 	rm -f autograde.tar
 #
 # CAREFULL!!! This will delete all student records in the logfile and
@@ -41,4 +49,3 @@ cleanallfiles:
 	make clean
 	rm -f log.txt
 	rm -rf handin/*
-
diff --git a/examples/autolab_example/tmp/cs101/autograde-Makefile b/examples/autolab_example/tmp/cs101/autograde-Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..78b2c548c19d14aec7ea0edc74cc0bf021aa6aae
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/autograde-Makefile
@@ -0,0 +1,7 @@
+all:
+	tar xf autograde.tar
+	cp Report1_handin.token cs101-handout
+	(cd cs101-handout; python3 driver_python.py)
+
+clean:
+	rm -rf *~ hello3-handout
\ No newline at end of file
diff --git a/autolab/lab_template/hello/hello.rb b/examples/autolab_example/tmp/cs101/cs101.rb
similarity index 72%
rename from autolab/lab_template/hello/hello.rb
rename to examples/autolab_example/tmp/cs101/cs101.rb
index 02f1da0742ee74bebccb7bca5c462626724d7251..4c4cd2abc1be9dee7470d9eec227332d9840fcf1 100644
--- a/autolab/lab_template/hello/hello.rb
+++ b/examples/autolab_example/tmp/cs101/cs101.rb
@@ -1,11 +1,11 @@
 require "AssessmentBase.rb"
 
-module Hello
+module Cs101
   include AssessmentBase
 
   def assessmentInitialize(course)
-    super("hello",course)
+    super("cs101",course)
     @problems = []
   end
 
-end
+end
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs101/cs101.yml b/examples/autolab_example/tmp/cs101/cs101.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7631a7fbbb0ccf4d294b447404186abdc40fa70e
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/cs101.yml
@@ -0,0 +1,38 @@
+---
+
+general:
+  name: cs101
+  description: ''
+  display_name: CS 101 Report 1
+  handin_filename: Report1_handin.token
+  handin_directory: handin
+  max_grace_days: 0
+  handout: cs101-handout.tar
+  writeup: writeup/cs101.html
+  max_submissions: -1
+  disable_handins: false
+  max_size: 2
+  has_svn: false
+  category_name: Lab
+problems:
+
+  - name: Total
+    description: ''
+    max_score: 10
+    optional: false
+
+  - name: Week1
+    description: ''
+    max_score: 10
+    optional: true
+
+autograder:
+  autograde_timeout: 180
+  autograde_image: tango_python_tue
+  release_score: true
+
+# problems:
+# - name: Correctness
+#  description: ''
+#  max_score: 100.0
+#  optional: false
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs101/src/Makefile b/examples/autolab_example/tmp/cs101/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..6d094e3a3869dfe9ee9e51a06150c6999c402286
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/src/Makefile
@@ -0,0 +1,7 @@
+# Makefile for the Hello Lab
+all:
+	echo "Makefile called... it is empty so far. "
+	#gcc hello3.c -o hello3
+
+clean:
+	rm -rf *~ hello3
diff --git a/examples/autolab_example/tmp/cs101/src/Makefile-handout b/examples/autolab_example/tmp/cs101/src/Makefile-handout
new file mode 100644
index 0000000000000000000000000000000000000000..6d094e3a3869dfe9ee9e51a06150c6999c402286
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/src/Makefile-handout
@@ -0,0 +1,7 @@
+# Makefile for the Hello Lab
+all:
+	echo "Makefile called... it is empty so far. "
+	#gcc hello3.c -o hello3
+
+clean:
+	rm -rf *~ hello3
diff --git a/autolab/lab_template/hello/src/README b/examples/autolab_example/tmp/cs101/src/README
similarity index 99%
rename from autolab/lab_template/hello/src/README
rename to examples/autolab_example/tmp/cs101/src/README
index 9a62e294a88fac6a4fec7cef188fefca31614ad8..8eea4bef3abb4665581173c4843b6155b3dc59d2 100644
--- a/autolab/lab_template/hello/src/README
+++ b/examples/autolab_example/tmp/cs101/src/README
@@ -13,4 +13,3 @@ hello.c                 Solution hello.c file
 Makefile-handout        Makefile and ...
 README-handout          ... README handed out to students
 hello.c-handout         Blank hello.c file handed out to students
-
diff --git a/examples/autolab_example/tmp/cs101/src/README-handout b/examples/autolab_example/tmp/cs101/src/README-handout
new file mode 100644
index 0000000000000000000000000000000000000000..8eea4bef3abb4665581173c4843b6155b3dc59d2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/src/README-handout
@@ -0,0 +1,15 @@
+This directory contains all of the code files for the Hello Lab,
+including the files that are handed out to students.
+
+Files:
+
+# Autograder and solution files
+Makefile                Makefile and ...
+README                  ... README for this directory
+driver.sh*              Autograder
+hello.c                 Solution hello.c file
+
+# Files that are handed out to students
+Makefile-handout        Makefile and ...
+README-handout          ... README handed out to students
+hello.c-handout         Blank hello.c file handed out to students
diff --git a/examples/autolab_example/tmp/cs101/src/Report1_handin.token b/examples/autolab_example/tmp/cs101/src/Report1_handin.token
new file mode 100644
index 0000000000000000000000000000000000000000..5ccd4e5495ad2bbfe4c3cc09832916b356f0d31b
Binary files /dev/null and b/examples/autolab_example/tmp/cs101/src/Report1_handin.token differ
diff --git a/examples/autolab_example/tmp/cs101/src/docker_helpers.py b/examples/autolab_example/tmp/cs101/src/docker_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..cde3e6e03ba0c0104e0f61ec7aca43991d15c59b
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/src/docker_helpers.py
@@ -0,0 +1,161 @@
+# from cs202courseware.ug2report1 import Report1
+# import thtools
+import pickle
+import os
+import glob
+# from unitgrade_private2.deployment import remove_hidden_methods
+# from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet
+# from unitgrade_private2.hidden_create_files import setup_grade_file_report
+import shutil
+import time
+import zipfile
+import io
+
+def student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination):
+    """
+    :param Dockerfile_location:
+    :param host_tmp_dir:
+    :param student_token_file:
+    :param ReportClass:
+    :param instructor_grade_script:
+    :return:
+    """
+    # assert os.path.exists(Dockerfile_location)
+    start = time.time()
+
+    with open(student_token_file, 'rb') as f:
+        results = pickle.load(f)
+    sources = results['sources'][0]
+
+    with io.BytesIO(sources['zipfile']) as zb:
+        with zipfile.ZipFile(zb) as zip:
+            zip.extractall(host_tmp_dir)
+    # Done extracting the zip file! Now time to move the (good) report test class into the location.
+    import inspect
+    # if ReportClass is not None:
+    #     gscript = inspect.getfile(ReportClass)[:-3] + "_grade.py"
+    # else:
+    gscript = instructor_grade_script
+    print(f"{sources['report_relative_location']=}")
+    print(f"{sources['name']=}")
+    # student_grade_script = host_tmp_dir + "/" + sources['name'] + "/" + sources['report_relative_location']
+    # instructor_grade_script = os.path.dirname(student_grade_script) + "/" + os.path.basename(gscript)
+    print("Now in docker_helpers.py")
+    print(f'{gscript=}')
+    print(f'{instructor_grade_script=}')
+    gscript_destination = host_tmp_dir + "/" + grade_file_relative_destination
+    print(f'{gscript_destination=}')
+
+    shutil.copy(gscript, gscript_destination)
+
+    # Now everything appears very close to being set up and ready to roll!.
+    # import thtools
+
+    # os.path.split()
+    d = os.path.normpath(grade_file_relative_destination).split(os.sep)
+    d = d[:-1] + [os.path.basename(instructor_grade_script)[:-3]]
+    # print(f'{d=}')
+    pycom = ".".join(d)
+
+
+    """
+    docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker python3 -m cs202courseware.ug2report1_grade
+    """
+    # dockname = os.path.basename(os.path.dirname(Dockerfile_location))
+
+    # tmp_grade_file = sources['name'] + "/" + sources['report_relative_location']
+    # print(f'{tmp_grade_file=}')
+    # pycom = ".".join((sources['name'],) + os.path.split(sources['report_relative_location'])[1:-1] + (os.path.basename(gscript),))
+    pycom = "python3 -m " + pycom # pycom[:-3]
+    print(f"{pycom=}")
+    # tmp_path = os.path.abspath(host_tmp_dir).replace("\\", "/")
+    # dcom = f"docker run -v {tmp_path}:/app {dockname} {pycom}"
+    # cdcom = f"cd {os.path.dirname(Dockerfile_location)}"
+    # fcom = f"{cdcom}  && {dcom}"
+    # print("> Running docker command")
+    # print(fcom)
+
+    # thtools.execute_command(fcom.split())
+    # get token file:
+
+    token_location = host_tmp_dir + "/" + os.path.dirname( grade_file_relative_destination ) + "/*.token"
+
+
+    # host_tmp_dir + "/" + os.path.dirname(tmp_grade_file) + "/"
+    # tokens = glob.glob(host_tmp_dir + "/" + os.path.dirname(tmp_grade_file) + "/*.token")
+    # token_location = host_tmp_dir + "/" + os.path.dirname(tmp_grade_file)
+
+    # for t in tokens:
+    #     print("Source image produced token", t)
+    elapsed = time.time() - start
+    # print("Elapsed time is", elapsed)
+    return pycom, token_location
+    pass
+
+def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file, ReportClass=None, instructor_grade_script=None):
+    """
+    This thingy works:
+
+    To build the image, run:
+    docker build --tag python-docker .
+
+    To run the app run:
+
+    docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker > output.log
+
+    """
+    # A bunch of tests. This is going to be great!
+    assert os.path.exists(Dockerfile_location)
+    start = time.time()
+
+    with open(student_token_file, 'rb') as f:
+        results = pickle.load(f)
+    sources = results['sources'][0]
+
+    if os.path.exists(host_tmp_dir):
+        shutil.rmtree(host_tmp_dir)
+
+    with io.BytesIO(sources['zipfile']) as zb:
+        with zipfile.ZipFile(zb) as zip:
+            zip.extractall(host_tmp_dir)
+    # Done extracting the zip file! Now time to move the (good) report test class into the location.
+    import inspect
+    if ReportClass is not None:
+        gscript = inspect.getfile(ReportClass)[:-3] + "_grade.py"
+    else:
+        gscript = instructor_grade_script
+
+    student_grade_script = host_tmp_dir + "/" + sources['name'] + "/" + sources['report_relative_location']
+    instructor_grade_script = os.path.dirname(student_grade_script) + "/"+os.path.basename(gscript)
+    shutil.copy(gscript, instructor_grade_script)
+
+    # Now everything appears very close to being set up and ready to roll!.
+    import thtools
+
+    """
+    docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker python3 -m cs202courseware.ug2report1_grade
+    """
+    dockname = os.path.basename( os.path.dirname(Dockerfile_location) )
+
+    tmp_grade_file =  sources['name'] + "/" + sources['report_relative_location']
+
+    pycom = ".".join( (sources['name'], ) + os.path.split(sources['report_relative_location'])[1:-1] + (os.path.basename(gscript),) )
+    pycom = "python3 -m " + pycom[:-3]
+
+    tmp_path = os.path.abspath(host_tmp_dir).replace("\\", "/")
+    dcom = f"docker run -v {tmp_path}:/app {dockname} {pycom}"
+    cdcom = f"cd {os.path.dirname(Dockerfile_location)}"
+    fcom = f"{cdcom}  && {dcom}"
+    print("> Running docker command")
+    print(fcom)
+    init = time.time() - start
+    thtools.execute_command(fcom.split())
+    # get token file:
+
+    host_tmp_dir +"/" + os.path.dirname(tmp_grade_file) + "/"
+    tokens = glob.glob(host_tmp_dir +"/" + os.path.dirname(tmp_grade_file) + "/*.token" )
+    for t in tokens:
+        print("Source image produced token", t)
+    elapsed = time.time() - start
+    print("Elapsed time is", elapsed, f"({init=} seconds)")
+    return tokens[0]
diff --git a/examples/autolab_example/tmp/cs101/src/driver.sh b/examples/autolab_example/tmp/cs101/src/driver.sh
new file mode 100644
index 0000000000000000000000000000000000000000..05a006e95e416fa5d5088f1d61479f73901588c2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/src/driver.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+# driver.sh - The simplest autograder we could think of. It checks
+#   that students can write a C program that compiles, and then
+#   executes with an exit status of zero.
+#   Usage: ./driver.sh
+
+# Compile the code
+# echo "Compiling hello3.c"
+# python3 -c "print('Hello world from python 2')"
+# python3 --version
+python3 driver_python.py
+
+#(make clean; make)
+#status=$?
+#if [ ${status} -ne 0 ]; then
+#    echo "Failure: Unable to compile hello3.c (return status = ${status})"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#    exit
+#fi
+#
+# Run the code
+#echo "Running ./hello3"
+#./hello3
+#status=$?
+#if [ ${status} -eq 0 ]; then
+#    echo "Success: ./hello3 runs with an exit status of 0"
+#    echo "{\"scores\": {\"Correctness\": 100}}"
+#else
+#    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#fi
+
+exit
diff --git a/examples/autolab_example/tmp/cs101/src/driver.sh-handout b/examples/autolab_example/tmp/cs101/src/driver.sh-handout
new file mode 100644
index 0000000000000000000000000000000000000000..05a006e95e416fa5d5088f1d61479f73901588c2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/src/driver.sh-handout
@@ -0,0 +1,33 @@
+#!/bin/bash
+# driver.sh - The simplest autograder we could think of. It checks
+#   that students can write a C program that compiles, and then
+#   executes with an exit status of zero.
+#   Usage: ./driver.sh
+
+# Compile the code
+# echo "Compiling hello3.c"
+# python3 -c "print('Hello world from python 2')"
+# python3 --version
+python3 driver_python.py
+
+#(make clean; make)
+#status=$?
+#if [ ${status} -ne 0 ]; then
+#    echo "Failure: Unable to compile hello3.c (return status = ${status})"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#    exit
+#fi
+#
+# Run the code
+#echo "Running ./hello3"
+#./hello3
+#status=$?
+#if [ ${status} -eq 0 ]; then
+#    echo "Success: ./hello3 runs with an exit status of 0"
+#    echo "{\"scores\": {\"Correctness\": 100}}"
+#else
+#    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#fi
+
+exit
diff --git a/examples/autolab_example/tmp/cs101/src/driver_python.py b/examples/autolab_example/tmp/cs101/src/driver_python.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b3e081e764bac25f8d6acecad887fec8f971270
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/src/driver_python.py
@@ -0,0 +1,83 @@
+import os
+import glob
+import sys
+import pickle
+# import io
+import subprocess
+import docker_helpers
+import time
+
+verbose = False
+tag = "[driver_python.py]"
+
+if not verbose:
+    print("="*10)
+    print(tag, "Starting unitgrade evaluation...")
+
+sys.stderr = sys.stdout
+wdir = os.getcwd()
+
+def pfiles():
+    print("> Files in dir:")
+    for f in glob.glob(wdir + "/*"):
+        print(f)
+    print("---")
+
+student_token_file = 'Report1_handin.token'
+instructor_grade_script = 'report1_grade.py'
+grade_file_relative_destination = "cs101\report1_grade.py"
+with open(student_token_file, 'rb') as f:
+    results = pickle.load(f)
+sources = results['sources'][0]
+host_tmp_dir = wdir + "/tmp"
+
+if not verbose:
+    pfiles()
+    print(f"{host_tmp_dir=}")
+    print(f"{student_token_file=}")
+    print(f"{instructor_grade_script=}")
+
+command, token = docker_helpers.student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination)
+command = f"cd tmp && {command} --noprogress --autolab"
+
+def rcom(cm):
+    # print(f"running... ", cm)
+    # start = time.time()
+    rs = subprocess.run(cm, capture_output=True, text=True, shell=True)
+    print(rs.stdout)
+
+    if len(rs.stderr) > 0:
+        print(tag, "There were errors in executing the file:")
+        print(rs.stderr)
+    # print(rs)
+    # print("result of running command was", rs.stdout, "err", rs.stderr, "time", time.time() - start)
+
+start = time.time()
+rcom(command)
+# pfiles()
+# for f in glob.glob(host_tmp_dir + "/cs101/*"):
+#     print("cs101/", f)
+# print("---")
+ls = glob.glob(token)
+# print(ls)
+f = ls[0]
+with open(f, 'rb') as f:
+    results = pickle.load(f)
+# print("results")
+# print(results.keys())
+if verbose:
+    print(f"{token=}")
+    print(results['total'])
+# if os.path.exists(host_tmp_dir):
+#     shutil.rmtree(host_tmp_dir)
+# with io.BytesIO(sources['zipfile']) as zb:
+#     with zipfile.ZipFile(zb) as zip:
+#         zip.extractall(host_tmp_dir
+# print("="*10)
+# print('{"scores": {"Correctness": 100,  "Problem 1": 4}}')
+
+sc = [('Total', results['total'][0])] + [(q['title'], q['obtained']) for k, q in results['details'].items()]
+ss = ", ".join([f'"{t}": {s}' for t, s in sc])
+scores = '{"scores": {' + ss + '}}'
+print('{"_presentation": "semantic"}')
+print(scores)
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs101/src/driver_python.py-handout b/examples/autolab_example/tmp/cs101/src/driver_python.py-handout
new file mode 100644
index 0000000000000000000000000000000000000000..9b3e081e764bac25f8d6acecad887fec8f971270
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/src/driver_python.py-handout
@@ -0,0 +1,83 @@
+import os
+import glob
+import sys
+import pickle
+# import io
+import subprocess
+import docker_helpers
+import time
+
+verbose = False
+tag = "[driver_python.py]"
+
+if not verbose:
+    print("="*10)
+    print(tag, "Starting unitgrade evaluation...")
+
+sys.stderr = sys.stdout
+wdir = os.getcwd()
+
+def pfiles():
+    print("> Files in dir:")
+    for f in glob.glob(wdir + "/*"):
+        print(f)
+    print("---")
+
+student_token_file = 'Report1_handin.token'
+instructor_grade_script = 'report1_grade.py'
+grade_file_relative_destination = "cs101\report1_grade.py"
+with open(student_token_file, 'rb') as f:
+    results = pickle.load(f)
+sources = results['sources'][0]
+host_tmp_dir = wdir + "/tmp"
+
+if not verbose:
+    pfiles()
+    print(f"{host_tmp_dir=}")
+    print(f"{student_token_file=}")
+    print(f"{instructor_grade_script=}")
+
+command, token = docker_helpers.student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination)
+command = f"cd tmp && {command} --noprogress --autolab"
+
+def rcom(cm):
+    # print(f"running... ", cm)
+    # start = time.time()
+    rs = subprocess.run(cm, capture_output=True, text=True, shell=True)
+    print(rs.stdout)
+
+    if len(rs.stderr) > 0:
+        print(tag, "There were errors in executing the file:")
+        print(rs.stderr)
+    # print(rs)
+    # print("result of running command was", rs.stdout, "err", rs.stderr, "time", time.time() - start)
+
+start = time.time()
+rcom(command)
+# pfiles()
+# for f in glob.glob(host_tmp_dir + "/cs101/*"):
+#     print("cs101/", f)
+# print("---")
+ls = glob.glob(token)
+# print(ls)
+f = ls[0]
+with open(f, 'rb') as f:
+    results = pickle.load(f)
+# print("results")
+# print(results.keys())
+if verbose:
+    print(f"{token=}")
+    print(results['total'])
+# if os.path.exists(host_tmp_dir):
+#     shutil.rmtree(host_tmp_dir)
+# with io.BytesIO(sources['zipfile']) as zb:
+#     with zipfile.ZipFile(zb) as zip:
+#         zip.extractall(host_tmp_dir
+# print("="*10)
+# print('{"scores": {"Correctness": 100,  "Problem 1": 4}}')
+
+sc = [('Total', results['total'][0])] + [(q['title'], q['obtained']) for k, q in results['details'].items()]
+ss = ", ".join([f'"{t}": {s}' for t, s in sc])
+scores = '{"scores": {' + ss + '}}'
+print('{"_presentation": "semantic"}')
+print(scores)
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs101/src/report1_grade.py b/examples/autolab_example/tmp/cs101/src/report1_grade.py
new file mode 100644
index 0000000000000000000000000000000000000000..8972ab5fd7d427147f65315d2b2b87f6dee0f6fb
--- /dev/null
+++ b/examples/autolab_example/tmp/cs101/src/report1_grade.py
@@ -0,0 +1,462 @@
+
+import numpy as np
+from tabulate import tabulate
+from datetime import datetime
+import pyfiglet
+import unittest
+
+import inspect
+import os
+import argparse
+import sys
+import time
+import threading # don't import Thread bc. of minify issue.
+import tqdm # don't do from tqdm import tqdm because of minify-issue
+
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: 
+To run all tests in a report: 
+
+> python assignment1_dp.py
+
+To run only question 2 or question 2.1
+
+> python assignment1_dp.py -q 2
+> python assignment1_dp.py -q 2.1
+
+Note this scripts does not grade your report. To grade your report, use:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)')
+parser.add_argument('--showexpected',  action="store_true",  help='Show the expected/desired result')
+parser.add_argument('--showcomputed',  action="store_true",  help='Show the answer your code computes')
+parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
+parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
+
+def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
+    args = parser.parse_args()
+    if question is None and args.q is not None:
+        question = args.q
+        if "." in question:
+            question, qitem = [int(v) for v in question.split(".")]
+        else:
+            question = int(question)
+
+    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:
+        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")
+
+    if unmute is None:
+        unmute = args.unmute
+    if passall is None:
+        passall = args.passall
+
+    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,
+                                          show_tol_err=show_tol_err)
+
+
+    # try:  # For registering stats.
+    #     import unitgrade_private
+    #     import irlc.lectures
+    #     import xlwings
+    #     from openpyxl import Workbook
+    #     import pandas as pd
+    #     from collections import defaultdict
+    #     dd = defaultdict(lambda: [])
+    #     error_computed = []
+    #     for k1, (q, _) in enumerate(report.questions):
+    #         for k2, item in enumerate(q.items):
+    #             dd['question_index'].append(k1)
+    #             dd['item_index'].append(k2)
+    #             dd['question'].append(q.name)
+    #             dd['item'].append(item.name)
+    #             dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol)
+    #             error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed)
+    #
+    #     qstats = report.wdir + "/" + report.name + ".xlsx"
+    #
+    #     if os.path.isfile(qstats):
+    #         d_read = pd.read_excel(qstats).to_dict()
+    #     else:
+    #         d_read = dict()
+    #
+    #     for k in range(1000):
+    #         key = 'run_'+str(k)
+    #         if key in d_read:
+    #             dd[key] = list(d_read['run_0'].values())
+    #         else:
+    #             dd[key] = error_computed
+    #             break
+    #
+    #     workbook = Workbook()
+    #     worksheet = workbook.active
+    #     for col, key in enumerate(dd.keys()):
+    #         worksheet.cell(row=1, column=col+1).value = key
+    #         for row, item in enumerate(dd[key]):
+    #             worksheet.cell(row=row+2, column=col+1).value = item
+    #
+    #     workbook.save(qstats)
+    #     workbook.close()
+    #
+    # except ModuleNotFoundError as e:
+    #     s = 234
+    #     pass
+
+    if question is None:
+        print("Provisional evaluation")
+        tabulate(table_data)
+        table = table_data
+        print(tabulate(table))
+        print(" ")
+
+    fr = inspect.getouterframes(inspect.currentframe())[1].filename
+    gfile = os.path.basename(fr)[:-3] + "_grade.py"
+    if os.path.exists(gfile):
+        print("Note your results have not yet been registered. \nTo register your results, please run the file:")
+        print(">>>", gfile)
+        print("In the same manner as you ran this file.")
+
+
+    return results
+
+
+def upack(q):
+    # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()])
+    h =[(i['w'], i['possible'], i['obtained']) for i in q.values()]
+    h = np.asarray(h)
+    return h[:,0], h[:,1], h[:,2],
+
+class UnitgradeTextRunner(unittest.TextTestRunner):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
+
+def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
+                    show_progress_bar=True,
+                    show_tol_err=False,
+                    big_header=True):
+
+    now = datetime.now()
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
+    print(b + " v" + __version__)
+    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
+    print("Started: " + dt_string)
+    s = report.title
+    if hasattr(report, "version") and report.version is not None:
+        s += " version " + report.version
+    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")
+    # print(f"Loaded answers from: ", report.computed_answers_file, "\n")
+    table_data = []
+    nL = 80
+    t_start = time.time()
+    score = {}
+    loader = SequentialTestLoader()
+
+    for n, (q, w) in enumerate(report.questions):
+        # q = q()
+        # q_hidden = False
+        # q_hidden = issubclass(q.__class__, Hidden)
+        if question is not None and n+1 != question:
+            continue
+        suite = loader.loadTestsFromTestCase(q)
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
+        q_title_print = "Question %i: %s"%(n+1, qtitle)
+        print(q_title_print, end="")
+        q.possible = 0
+        q.obtained = 0
+        q_ = {} # Gather score in this class.
+        # unittest.Te
+        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
+        UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
+        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
+        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
+        z = 234
+        # for j, item in enumerate(q.items):
+        #     if qitem is not None and question is not None and j+1 != qitem:
+        #         continue
+        #
+        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.
+        #         # if not item.question.has_called_init_:
+        #         start = time.time()
+        #
+        #         cc = None
+        #         if show_progress_bar:
+        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )
+        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)
+        #         from unitgrade import Capturing # DON'T REMOVE THIS LINE
+        #         with eval('Capturing')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.
+        #             try:
+        #                 for q2 in q_with_outstanding_init:
+        #                     q2.init()
+        #                     q2.has_called_init_ = True
+        #
+        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.
+        #             except Exception as e:
+        #                 if not passall:
+        #                     if not silent:
+        #                         print(" ")
+        #                         print("="*30)
+        #                         print(f"When initializing question {q.title} the initialization code threw an error")
+        #                         print(e)
+        #                         print("The remaining parts of this question will likely fail.")
+        #                         print("="*30)
+        #
+        #         if show_progress_bar:
+        #             cc.terminate()
+        #             sys.stdout.flush()
+        #             print(q_title_print, end="")
+        #
+        #         q_time =np.round(  time.time()-start, 2)
+        #
+        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")
+        #         print("=" * nL)
+        #         q_with_outstanding_init = None
+        #
+        #     # item.question = q # Set the parent question instance for later reference.
+        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)
+        #
+        #     if show_progress_bar:
+        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)
+        #     else:
+        #         print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="")
+        #     hidden = issubclass(item.__class__, Hidden)
+        #     # if not hidden:
+        #     #     print(ss, end="")
+        #     # sys.stdout.flush()
+        #     start = time.time()
+        #
+        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
+        #     q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
+        #     tsecs = np.round(time.time()-start, 2)
+        #     if show_progress_bar:
+        #         cc.terminate()
+        #         sys.stdout.flush()
+        #         print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="")
+        #
+        #     if not hidden:
+        #         ss = "PASS" if current == possible else "*** FAILED"
+        #         if tsecs >= 0.1:
+        #             ss += " ("+ str(tsecs) + " seconds)"
+        #         print(ss)
+
+        # ws, possible, obtained = upack(q_)
+
+        possible = res.testsRun
+        obtained = len(res.successes)
+
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
+
+        # possible = int(ws @ possible)
+        # obtained = int(ws @ obtained)
+        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
+
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
+        score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
+        q.obtained = obtained
+        q.possible = possible
+
+        s1 = f"*** Question q{n+1}"
+        s2 = f" {q.obtained}/{w}"
+        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )
+        print(" ")
+        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])
+
+    ws, possible, obtained = upack(score)
+    possible = int( msum(possible) )
+    obtained = int( msum(obtained) ) # Cast to python int
+    report.possible = possible
+    report.obtained = obtained
+    now = datetime.now()
+    dt_string = now.strftime("%H:%M:%S")
+
+    dt = int(time.time()-t_start)
+    minutes = dt//60
+    seconds = dt - minutes*60
+    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")
+
+    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")
+
+    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])
+    results = {'total': (obtained, possible), 'details': score}
+    return results, table_data
+
+
+
+
+from tabulate import tabulate
+from datetime import datetime
+import inspect
+import json
+import os
+import bz2
+import pickle
+import os
+
+def bzwrite(json_str, token): # to get around obfuscation issues
+    with getattr(bz2, 'open')(token, "wt") as f:
+        f.write(json_str)
+
+def gather_imports(imp):
+    resources = {}
+    m = imp
+    # for m in pack_imports:
+    # print(f"*** {m.__name__}")
+    f = m.__file__
+    # dn = os.path.dirname(f)
+    # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__)
+    # top_package = str(__import__(m.__name__.split('.')[0]).__path__)
+    if m.__class__.__name__ == 'module' and False:
+        top_package = os.path.dirname(m.__file__)
+        module_import = True
+    else:
+        top_package = __import__(m.__name__.split('.')[0]).__path__._path[0]
+        module_import = False
+
+    # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__)
+    # top_package = os.path.dirname(top_package)
+    import zipfile
+    # import strea
+    # zipfile.ZipFile
+    import io
+    # file_like_object = io.BytesIO(my_zip_data)
+    zip_buffer = io.BytesIO()
+    with zipfile.ZipFile(zip_buffer, 'w') as zip:
+        # zip.write()
+        for root, dirs, files in os.walk(top_package):
+            for file in files:
+                if file.endswith(".py"):
+                    fpath = os.path.join(root, file)
+                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))
+                    zip.write(fpath, v)
+
+    resources['zipfile'] = zip_buffer.getvalue()
+    resources['top_package'] = top_package
+    resources['module_import'] = module_import
+    return resources, top_package
+
+    if f.endswith("__init__.py"):
+        for root, dirs, files in os.walk(os.path.dirname(f)):
+            for file in files:
+                if file.endswith(".py"):
+                    # print(file)
+                    # print()
+                    v = os.path.relpath(os.path.join(root, file), top_package)
+                    with open(os.path.join(root, file), 'r') as ff:
+                        resources[v] = ff.read()
+    else:
+        v = os.path.relpath(f, top_package)
+        with open(f, 'r') as ff:
+            resources[v] = ff.read()
+    return resources
+
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
+
+def gather_upload_to_campusnet(report, output_dir=None):
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
+    print(" ")
+    print("="*n)
+    print("Final evaluation")
+    print(tabulate(table_data))
+    # also load the source code of missing files...
+
+    sources = {}
+
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
+
+    if output_dir is None:
+        output_dir = os.getcwd()
+
+    payload_out_base = report.__class__.__name__ + "_handin"
+
+    obtain, possible = results['total']
+    vstring = "_v"+report.version if report.version is not None else ""
+
+    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)
+    token = os.path.join(output_dir, token)
+    with open(token, 'wb') as f:
+        pickle.dump(results, f)
+
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
+
+def source_instantiate(name, report1_source, payload):
+    eval("exec")(report1_source, globals())
+    pl = pickle.loads(bytes.fromhex(payload))
+    report = eval(name)(payload=pl, strict=True)
+    # report.set_payload(pl)
+    return report
+
+
+
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    @classmethod\n    def question_title(cls):\n        return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    # def _callSetUp(self):\n    #     # Always run before method is called.\n    #     print("asdf")\n    #     pass\n    # @classmethod\n    # def setUpClass(cls):\n    #     # self._cache_put((self.cache_id(), \'title\'), value)\n    #     cls.reset()\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        self._ensure_cache_exists() # Make sure cache is there.\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    # def unique_cache_id(self):\n    #     k0 = self.cache_id()\n    #     # key = ()\n    #     i = 0\n    #     for i in itertools.count():\n    #         # key = k0 + (i,)\n    #         if i not in self._cache_get( (k0, \'assert\') ):\n    #             break\n    #     return i\n    #     return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n    #\n    # def _cache2_contains(self, key):\n    #     print("Is this needed?")\n    #     self._ensure_cache_exists()\n    #     return key in self.__class__._cache2\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nfrom cs101.homework1 import reverse_list, add\nimport unittest\n\nclass Week1(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(add(2,2), 4)\n        self.assertEqual(add(-100, 5), -95)\n\n    def test_reverse(self):\n        self.assertEqual(reverse_list([1,2,3]), [3,2,1])\n        # print("Bad output\\n\\n")\n\n\nimport cs101\nclass Report1(Report):\n    title = "CS 101 Report 1"\n    questions = [(Week1, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs101]'
+report1_payload = '8004953f000000000000007d948c055765656b31947d948c2c6e6f20636163686520736565205f73657475705f616e737765727320696e20756e69746772616465322e7079948873732e'
+name="Report1"
+
+report = source_instantiate(name, report1_source, report1_payload)
+output_dir = os.path.dirname(__file__)
+gather_upload_to_campusnet(report, output_dir)
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs101/src/student_sources.zip b/examples/autolab_example/tmp/cs101/src/student_sources.zip
new file mode 100644
index 0000000000000000000000000000000000000000..c02da952b6b745265b505c90991ba35382d9b5ff
Binary files /dev/null and b/examples/autolab_example/tmp/cs101/src/student_sources.zip differ
diff --git a/examples/autolab_example/tmp/cs102/Makefile b/examples/autolab_example/tmp/cs102/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..8cef2a8073c6d0899af2d35f9d3e5546ef3c4e16
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/Makefile
@@ -0,0 +1,51 @@
+#
+# Makefile to manage the example Hello Lab
+#
+
+# Get the name of the lab directory
+# LAB = $(notdir $(PWD)) # Fail on windows for some reason...
+
+all: handout handout-tarfile
+
+handout: 
+	# Rebuild the handout directory that students download
+	(rm -rf cs102-handout; mkdir cs102-handout)
+	cp -p src/Makefile-handout cs102-handout/Makefile
+	cp -p src/README-handout cs102-handout/README
+	cp -p src/driver_python.py cs102-handout
+
+	cp -p src/student_sources.zip cs102-handout
+
+	cp -p src/Report2_handin.token cs102-handout
+
+	cp -p src/docker_helpers.py cs102-handout
+
+	cp -p src/report2_grade.py cs102-handout
+
+
+handout-tarfile: handout
+	# Build *-handout.tar and autograde.tar
+	tar cvf cs102-handout.tar cs102-handout
+	cp -p cs102-handout.tar autograde.tar
+
+clean:
+	# Clean the entire lab directory tree.  Note that you can run
+	# "make clean; make" at any time while the lab is live with no
+	# adverse effects.
+	rm -f *~ *.tar
+	(cd src; make clean)
+	(cd test-autograder; make clean)
+	rm -rf cs102-handout
+	rm -f autograde.tar
+#
+# CAREFULL!!! This will delete all student records in the logfile and
+# in the handin directory. Don't run this once the lab has started.
+# Use it to clean the directory when you are starting a new version
+# of the lab from scratch, or when you are debugging the lab prior
+# to releasing it to the students.
+#
+cleanallfiles:
+	# Reset the lab from scratch.
+	make clean
+	rm -f log.txt
+	rm -rf handin/*
diff --git a/examples/autolab_example/tmp/cs102/autograde-Makefile b/examples/autolab_example/tmp/cs102/autograde-Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..6271b0eec055d104c95772f298fce53c4638f050
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/autograde-Makefile
@@ -0,0 +1,7 @@
+all:
+	tar xf autograde.tar
+	cp Report2_handin.token cs102-handout
+	(cd cs102-handout; python3 driver_python.py)
+
+clean:
+	rm -rf *~ hello3-handout
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs102/cs102.rb b/examples/autolab_example/tmp/cs102/cs102.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3a1dd46f68cb737ffa08dce42724d9bb5eab82cb
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/cs102.rb
@@ -0,0 +1,11 @@
+require "AssessmentBase.rb"
+
+module Cs102
+  include AssessmentBase
+
+  def assessmentInitialize(course)
+    super("cs102",course)
+    @problems = []
+  end
+
+end
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs102/cs102.yml b/examples/autolab_example/tmp/cs102/cs102.yml
new file mode 100644
index 0000000000000000000000000000000000000000..31c609b876cd004e926fdfed808ab3108b854643
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/cs102.yml
@@ -0,0 +1,43 @@
+---
+
+general:
+  name: cs102
+  description: ''
+  display_name: CS 101 Report 2
+  handin_filename: Report2_handin.token
+  handin_directory: handin
+  max_grace_days: 0
+  handout: cs102-handout.tar
+  writeup: writeup/cs102.html
+  max_submissions: -1
+  disable_handins: false
+  max_size: 2
+  has_svn: false
+  category_name: Lab
+problems:
+
+  - name: Total
+    description: ''
+    max_score: 18
+    optional: false
+
+  - name: The first question for week 1.
+    description: ''
+    max_score: 10
+    optional: true
+
+  - name: Second problem
+    description: ''
+    max_score: 8
+    optional: true
+
+autograder:
+  autograde_timeout: 180
+  autograde_image: tango_python_tue
+  release_score: true
+
+# problems:
+# - name: Correctness
+#  description: ''
+#  max_score: 100.0
+#  optional: false
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs102/src/Makefile b/examples/autolab_example/tmp/cs102/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..6d094e3a3869dfe9ee9e51a06150c6999c402286
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/Makefile
@@ -0,0 +1,7 @@
+# Makefile for the Hello Lab
+all:
+	echo "Makefile called... it is empty so far. "
+	#gcc hello3.c -o hello3
+
+clean:
+	rm -rf *~ hello3
diff --git a/examples/autolab_example/tmp/cs102/src/Makefile-handout b/examples/autolab_example/tmp/cs102/src/Makefile-handout
new file mode 100644
index 0000000000000000000000000000000000000000..6d094e3a3869dfe9ee9e51a06150c6999c402286
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/Makefile-handout
@@ -0,0 +1,7 @@
+# Makefile for the Hello Lab
+all:
+	echo "Makefile called... it is empty so far. "
+	#gcc hello3.c -o hello3
+
+clean:
+	rm -rf *~ hello3
diff --git a/examples/autolab_example/tmp/cs102/src/README b/examples/autolab_example/tmp/cs102/src/README
new file mode 100644
index 0000000000000000000000000000000000000000..8eea4bef3abb4665581173c4843b6155b3dc59d2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/README
@@ -0,0 +1,15 @@
+This directory contains all of the code files for the Hello Lab,
+including the files that are handed out to students.
+
+Files:
+
+# Autograder and solution files
+Makefile                Makefile and ...
+README                  ... README for this directory
+driver.sh*              Autograder
+hello.c                 Solution hello.c file
+
+# Files that are handed out to students
+Makefile-handout        Makefile and ...
+README-handout          ... README handed out to students
+hello.c-handout         Blank hello.c file handed out to students
diff --git a/examples/autolab_example/tmp/cs102/src/README-handout b/examples/autolab_example/tmp/cs102/src/README-handout
new file mode 100644
index 0000000000000000000000000000000000000000..8eea4bef3abb4665581173c4843b6155b3dc59d2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/README-handout
@@ -0,0 +1,15 @@
+This directory contains all of the code files for the Hello Lab,
+including the files that are handed out to students.
+
+Files:
+
+# Autograder and solution files
+Makefile                Makefile and ...
+README                  ... README for this directory
+driver.sh*              Autograder
+hello.c                 Solution hello.c file
+
+# Files that are handed out to students
+Makefile-handout        Makefile and ...
+README-handout          ... README handed out to students
+hello.c-handout         Blank hello.c file handed out to students
diff --git a/examples/autolab_example/tmp/cs102/src/Report2_handin.token b/examples/autolab_example/tmp/cs102/src/Report2_handin.token
new file mode 100644
index 0000000000000000000000000000000000000000..63734376c0eae1c4df3121a0c656e90452e80cba
Binary files /dev/null and b/examples/autolab_example/tmp/cs102/src/Report2_handin.token differ
diff --git a/examples/autolab_example/tmp/cs102/src/docker_helpers.py b/examples/autolab_example/tmp/cs102/src/docker_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..cde3e6e03ba0c0104e0f61ec7aca43991d15c59b
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/docker_helpers.py
@@ -0,0 +1,161 @@
+# from cs202courseware.ug2report1 import Report1
+# import thtools
+import pickle
+import os
+import glob
+# from unitgrade_private2.deployment import remove_hidden_methods
+# from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet
+# from unitgrade_private2.hidden_create_files import setup_grade_file_report
+import shutil
+import time
+import zipfile
+import io
+
+def student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination):
+    """
+    :param Dockerfile_location:
+    :param host_tmp_dir:
+    :param student_token_file:
+    :param ReportClass:
+    :param instructor_grade_script:
+    :return:
+    """
+    # assert os.path.exists(Dockerfile_location)
+    start = time.time()
+
+    with open(student_token_file, 'rb') as f:
+        results = pickle.load(f)
+    sources = results['sources'][0]
+
+    with io.BytesIO(sources['zipfile']) as zb:
+        with zipfile.ZipFile(zb) as zip:
+            zip.extractall(host_tmp_dir)
+    # Done extracting the zip file! Now time to move the (good) report test class into the location.
+    import inspect
+    # if ReportClass is not None:
+    #     gscript = inspect.getfile(ReportClass)[:-3] + "_grade.py"
+    # else:
+    gscript = instructor_grade_script
+    print(f"{sources['report_relative_location']=}")
+    print(f"{sources['name']=}")
+    # student_grade_script = host_tmp_dir + "/" + sources['name'] + "/" + sources['report_relative_location']
+    # instructor_grade_script = os.path.dirname(student_grade_script) + "/" + os.path.basename(gscript)
+    print("Now in docker_helpers.py")
+    print(f'{gscript=}')
+    print(f'{instructor_grade_script=}')
+    gscript_destination = host_tmp_dir + "/" + grade_file_relative_destination
+    print(f'{gscript_destination=}')
+
+    shutil.copy(gscript, gscript_destination)
+
+    # Now everything appears very close to being set up and ready to roll!.
+    # import thtools
+
+    # os.path.split()
+    d = os.path.normpath(grade_file_relative_destination).split(os.sep)
+    d = d[:-1] + [os.path.basename(instructor_grade_script)[:-3]]
+    # print(f'{d=}')
+    pycom = ".".join(d)
+
+
+    """
+    docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker python3 -m cs202courseware.ug2report1_grade
+    """
+    # dockname = os.path.basename(os.path.dirname(Dockerfile_location))
+
+    # tmp_grade_file = sources['name'] + "/" + sources['report_relative_location']
+    # print(f'{tmp_grade_file=}')
+    # pycom = ".".join((sources['name'],) + os.path.split(sources['report_relative_location'])[1:-1] + (os.path.basename(gscript),))
+    pycom = "python3 -m " + pycom # pycom[:-3]
+    print(f"{pycom=}")
+    # tmp_path = os.path.abspath(host_tmp_dir).replace("\\", "/")
+    # dcom = f"docker run -v {tmp_path}:/app {dockname} {pycom}"
+    # cdcom = f"cd {os.path.dirname(Dockerfile_location)}"
+    # fcom = f"{cdcom}  && {dcom}"
+    # print("> Running docker command")
+    # print(fcom)
+
+    # thtools.execute_command(fcom.split())
+    # get token file:
+
+    token_location = host_tmp_dir + "/" + os.path.dirname( grade_file_relative_destination ) + "/*.token"
+
+
+    # host_tmp_dir + "/" + os.path.dirname(tmp_grade_file) + "/"
+    # tokens = glob.glob(host_tmp_dir + "/" + os.path.dirname(tmp_grade_file) + "/*.token")
+    # token_location = host_tmp_dir + "/" + os.path.dirname(tmp_grade_file)
+
+    # for t in tokens:
+    #     print("Source image produced token", t)
+    elapsed = time.time() - start
+    # print("Elapsed time is", elapsed)
+    return pycom, token_location
+    pass
+
+def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file, ReportClass=None, instructor_grade_script=None):
+    """
+    This thingy works:
+
+    To build the image, run:
+    docker build --tag python-docker .
+
+    To run the app run:
+
+    docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker > output.log
+
+    """
+    # A bunch of tests. This is going to be great!
+    assert os.path.exists(Dockerfile_location)
+    start = time.time()
+
+    with open(student_token_file, 'rb') as f:
+        results = pickle.load(f)
+    sources = results['sources'][0]
+
+    if os.path.exists(host_tmp_dir):
+        shutil.rmtree(host_tmp_dir)
+
+    with io.BytesIO(sources['zipfile']) as zb:
+        with zipfile.ZipFile(zb) as zip:
+            zip.extractall(host_tmp_dir)
+    # Done extracting the zip file! Now time to move the (good) report test class into the location.
+    import inspect
+    if ReportClass is not None:
+        gscript = inspect.getfile(ReportClass)[:-3] + "_grade.py"
+    else:
+        gscript = instructor_grade_script
+
+    student_grade_script = host_tmp_dir + "/" + sources['name'] + "/" + sources['report_relative_location']
+    instructor_grade_script = os.path.dirname(student_grade_script) + "/"+os.path.basename(gscript)
+    shutil.copy(gscript, instructor_grade_script)
+
+    # Now everything appears very close to being set up and ready to roll!.
+    import thtools
+
+    """
+    docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker python3 -m cs202courseware.ug2report1_grade
+    """
+    dockname = os.path.basename( os.path.dirname(Dockerfile_location) )
+
+    tmp_grade_file =  sources['name'] + "/" + sources['report_relative_location']
+
+    pycom = ".".join( (sources['name'], ) + os.path.split(sources['report_relative_location'])[1:-1] + (os.path.basename(gscript),) )
+    pycom = "python3 -m " + pycom[:-3]
+
+    tmp_path = os.path.abspath(host_tmp_dir).replace("\\", "/")
+    dcom = f"docker run -v {tmp_path}:/app {dockname} {pycom}"
+    cdcom = f"cd {os.path.dirname(Dockerfile_location)}"
+    fcom = f"{cdcom}  && {dcom}"
+    print("> Running docker command")
+    print(fcom)
+    init = time.time() - start
+    thtools.execute_command(fcom.split())
+    # get token file:
+
+    host_tmp_dir +"/" + os.path.dirname(tmp_grade_file) + "/"
+    tokens = glob.glob(host_tmp_dir +"/" + os.path.dirname(tmp_grade_file) + "/*.token" )
+    for t in tokens:
+        print("Source image produced token", t)
+    elapsed = time.time() - start
+    print("Elapsed time is", elapsed, f"({init=} seconds)")
+    return tokens[0]
diff --git a/examples/autolab_example/tmp/cs102/src/driver.sh b/examples/autolab_example/tmp/cs102/src/driver.sh
new file mode 100644
index 0000000000000000000000000000000000000000..05a006e95e416fa5d5088f1d61479f73901588c2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/driver.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+# driver.sh - The simplest autograder we could think of. It checks
+#   that students can write a C program that compiles, and then
+#   executes with an exit status of zero.
+#   Usage: ./driver.sh
+
+# Compile the code
+# echo "Compiling hello3.c"
+# python3 -c "print('Hello world from python 2')"
+# python3 --version
+python3 driver_python.py
+
+#(make clean; make)
+#status=$?
+#if [ ${status} -ne 0 ]; then
+#    echo "Failure: Unable to compile hello3.c (return status = ${status})"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#    exit
+#fi
+#
+# Run the code
+#echo "Running ./hello3"
+#./hello3
+#status=$?
+#if [ ${status} -eq 0 ]; then
+#    echo "Success: ./hello3 runs with an exit status of 0"
+#    echo "{\"scores\": {\"Correctness\": 100}}"
+#else
+#    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#fi
+
+exit
diff --git a/examples/autolab_example/tmp/cs102/src/driver.sh-handout b/examples/autolab_example/tmp/cs102/src/driver.sh-handout
new file mode 100644
index 0000000000000000000000000000000000000000..05a006e95e416fa5d5088f1d61479f73901588c2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/driver.sh-handout
@@ -0,0 +1,33 @@
+#!/bin/bash
+# driver.sh - The simplest autograder we could think of. It checks
+#   that students can write a C program that compiles, and then
+#   executes with an exit status of zero.
+#   Usage: ./driver.sh
+
+# Compile the code
+# echo "Compiling hello3.c"
+# python3 -c "print('Hello world from python 2')"
+# python3 --version
+python3 driver_python.py
+
+#(make clean; make)
+#status=$?
+#if [ ${status} -ne 0 ]; then
+#    echo "Failure: Unable to compile hello3.c (return status = ${status})"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#    exit
+#fi
+#
+# Run the code
+#echo "Running ./hello3"
+#./hello3
+#status=$?
+#if [ ${status} -eq 0 ]; then
+#    echo "Success: ./hello3 runs with an exit status of 0"
+#    echo "{\"scores\": {\"Correctness\": 100}}"
+#else
+#    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#fi
+
+exit
diff --git a/examples/autolab_example/tmp/cs102/src/driver_python.py b/examples/autolab_example/tmp/cs102/src/driver_python.py
new file mode 100644
index 0000000000000000000000000000000000000000..092842afd627dc96a2f2f3ce12ee158b4c47b9ff
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/driver_python.py
@@ -0,0 +1,83 @@
+import os
+import glob
+import sys
+import pickle
+# import io
+import subprocess
+import docker_helpers
+import time
+
+verbose = False
+tag = "[driver_python.py]"
+
+if not verbose:
+    print("="*10)
+    print(tag, "Starting unitgrade evaluation...")
+
+sys.stderr = sys.stdout
+wdir = os.getcwd()
+
+def pfiles():
+    print("> Files in dir:")
+    for f in glob.glob(wdir + "/*"):
+        print(f)
+    print("---")
+
+student_token_file = 'Report2_handin.token'
+instructor_grade_script = 'report2_grade.py'
+grade_file_relative_destination = "cs102\report2_grade.py"
+with open(student_token_file, 'rb') as f:
+    results = pickle.load(f)
+sources = results['sources'][0]
+host_tmp_dir = wdir + "/tmp"
+
+if not verbose:
+    pfiles()
+    print(f"{host_tmp_dir=}")
+    print(f"{student_token_file=}")
+    print(f"{instructor_grade_script=}")
+
+command, token = docker_helpers.student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination)
+command = f"cd tmp && {command} --noprogress --autolab"
+
+def rcom(cm):
+    # print(f"running... ", cm)
+    # start = time.time()
+    rs = subprocess.run(cm, capture_output=True, text=True, shell=True)
+    print(rs.stdout)
+
+    if len(rs.stderr) > 0:
+        print(tag, "There were errors in executing the file:")
+        print(rs.stderr)
+    # print(rs)
+    # print("result of running command was", rs.stdout, "err", rs.stderr, "time", time.time() - start)
+
+start = time.time()
+rcom(command)
+# pfiles()
+# for f in glob.glob(host_tmp_dir + "/cs101/*"):
+#     print("cs101/", f)
+# print("---")
+ls = glob.glob(token)
+# print(ls)
+f = ls[0]
+with open(f, 'rb') as f:
+    results = pickle.load(f)
+# print("results")
+# print(results.keys())
+if verbose:
+    print(f"{token=}")
+    print(results['total'])
+# if os.path.exists(host_tmp_dir):
+#     shutil.rmtree(host_tmp_dir)
+# with io.BytesIO(sources['zipfile']) as zb:
+#     with zipfile.ZipFile(zb) as zip:
+#         zip.extractall(host_tmp_dir
+# print("="*10)
+# print('{"scores": {"Correctness": 100,  "Problem 1": 4}}')
+
+sc = [('Total', results['total'][0])] + [(q['title'], q['obtained']) for k, q in results['details'].items()]
+ss = ", ".join([f'"{t}": {s}' for t, s in sc])
+scores = '{"scores": {' + ss + '}}'
+print('{"_presentation": "semantic"}')
+print(scores)
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs102/src/driver_python.py-handout b/examples/autolab_example/tmp/cs102/src/driver_python.py-handout
new file mode 100644
index 0000000000000000000000000000000000000000..092842afd627dc96a2f2f3ce12ee158b4c47b9ff
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/driver_python.py-handout
@@ -0,0 +1,83 @@
+import os
+import glob
+import sys
+import pickle
+# import io
+import subprocess
+import docker_helpers
+import time
+
+verbose = False
+tag = "[driver_python.py]"
+
+if not verbose:
+    print("="*10)
+    print(tag, "Starting unitgrade evaluation...")
+
+sys.stderr = sys.stdout
+wdir = os.getcwd()
+
+def pfiles():
+    print("> Files in dir:")
+    for f in glob.glob(wdir + "/*"):
+        print(f)
+    print("---")
+
+student_token_file = 'Report2_handin.token'
+instructor_grade_script = 'report2_grade.py'
+grade_file_relative_destination = "cs102\report2_grade.py"
+with open(student_token_file, 'rb') as f:
+    results = pickle.load(f)
+sources = results['sources'][0]
+host_tmp_dir = wdir + "/tmp"
+
+if not verbose:
+    pfiles()
+    print(f"{host_tmp_dir=}")
+    print(f"{student_token_file=}")
+    print(f"{instructor_grade_script=}")
+
+command, token = docker_helpers.student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination)
+command = f"cd tmp && {command} --noprogress --autolab"
+
+def rcom(cm):
+    # print(f"running... ", cm)
+    # start = time.time()
+    rs = subprocess.run(cm, capture_output=True, text=True, shell=True)
+    print(rs.stdout)
+
+    if len(rs.stderr) > 0:
+        print(tag, "There were errors in executing the file:")
+        print(rs.stderr)
+    # print(rs)
+    # print("result of running command was", rs.stdout, "err", rs.stderr, "time", time.time() - start)
+
+start = time.time()
+rcom(command)
+# pfiles()
+# for f in glob.glob(host_tmp_dir + "/cs101/*"):
+#     print("cs101/", f)
+# print("---")
+ls = glob.glob(token)
+# print(ls)
+f = ls[0]
+with open(f, 'rb') as f:
+    results = pickle.load(f)
+# print("results")
+# print(results.keys())
+if verbose:
+    print(f"{token=}")
+    print(results['total'])
+# if os.path.exists(host_tmp_dir):
+#     shutil.rmtree(host_tmp_dir)
+# with io.BytesIO(sources['zipfile']) as zb:
+#     with zipfile.ZipFile(zb) as zip:
+#         zip.extractall(host_tmp_dir
+# print("="*10)
+# print('{"scores": {"Correctness": 100,  "Problem 1": 4}}')
+
+sc = [('Total', results['total'][0])] + [(q['title'], q['obtained']) for k, q in results['details'].items()]
+ss = ", ".join([f'"{t}": {s}' for t, s in sc])
+scores = '{"scores": {' + ss + '}}'
+print('{"_presentation": "semantic"}')
+print(scores)
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs102/src/report2_grade.py b/examples/autolab_example/tmp/cs102/src/report2_grade.py
new file mode 100644
index 0000000000000000000000000000000000000000..503237e5942ba9c82dca8454230add50e726429f
--- /dev/null
+++ b/examples/autolab_example/tmp/cs102/src/report2_grade.py
@@ -0,0 +1,462 @@
+
+import numpy as np
+from tabulate import tabulate
+from datetime import datetime
+import pyfiglet
+import unittest
+
+import inspect
+import os
+import argparse
+import sys
+import time
+import threading # don't import Thread bc. of minify issue.
+import tqdm # don't do from tqdm import tqdm because of minify-issue
+
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: 
+To run all tests in a report: 
+
+> python assignment1_dp.py
+
+To run only question 2 or question 2.1
+
+> python assignment1_dp.py -q 2
+> python assignment1_dp.py -q 2.1
+
+Note this scripts does not grade your report. To grade your report, use:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)')
+parser.add_argument('--showexpected',  action="store_true",  help='Show the expected/desired result')
+parser.add_argument('--showcomputed',  action="store_true",  help='Show the answer your code computes')
+parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
+parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
+
+def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
+    args = parser.parse_args()
+    if question is None and args.q is not None:
+        question = args.q
+        if "." in question:
+            question, qitem = [int(v) for v in question.split(".")]
+        else:
+            question = int(question)
+
+    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:
+        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")
+
+    if unmute is None:
+        unmute = args.unmute
+    if passall is None:
+        passall = args.passall
+
+    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,
+                                          show_tol_err=show_tol_err)
+
+
+    # try:  # For registering stats.
+    #     import unitgrade_private
+    #     import irlc.lectures
+    #     import xlwings
+    #     from openpyxl import Workbook
+    #     import pandas as pd
+    #     from collections import defaultdict
+    #     dd = defaultdict(lambda: [])
+    #     error_computed = []
+    #     for k1, (q, _) in enumerate(report.questions):
+    #         for k2, item in enumerate(q.items):
+    #             dd['question_index'].append(k1)
+    #             dd['item_index'].append(k2)
+    #             dd['question'].append(q.name)
+    #             dd['item'].append(item.name)
+    #             dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol)
+    #             error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed)
+    #
+    #     qstats = report.wdir + "/" + report.name + ".xlsx"
+    #
+    #     if os.path.isfile(qstats):
+    #         d_read = pd.read_excel(qstats).to_dict()
+    #     else:
+    #         d_read = dict()
+    #
+    #     for k in range(1000):
+    #         key = 'run_'+str(k)
+    #         if key in d_read:
+    #             dd[key] = list(d_read['run_0'].values())
+    #         else:
+    #             dd[key] = error_computed
+    #             break
+    #
+    #     workbook = Workbook()
+    #     worksheet = workbook.active
+    #     for col, key in enumerate(dd.keys()):
+    #         worksheet.cell(row=1, column=col+1).value = key
+    #         for row, item in enumerate(dd[key]):
+    #             worksheet.cell(row=row+2, column=col+1).value = item
+    #
+    #     workbook.save(qstats)
+    #     workbook.close()
+    #
+    # except ModuleNotFoundError as e:
+    #     s = 234
+    #     pass
+
+    if question is None:
+        print("Provisional evaluation")
+        tabulate(table_data)
+        table = table_data
+        print(tabulate(table))
+        print(" ")
+
+    fr = inspect.getouterframes(inspect.currentframe())[1].filename
+    gfile = os.path.basename(fr)[:-3] + "_grade.py"
+    if os.path.exists(gfile):
+        print("Note your results have not yet been registered. \nTo register your results, please run the file:")
+        print(">>>", gfile)
+        print("In the same manner as you ran this file.")
+
+
+    return results
+
+
+def upack(q):
+    # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()])
+    h =[(i['w'], i['possible'], i['obtained']) for i in q.values()]
+    h = np.asarray(h)
+    return h[:,0], h[:,1], h[:,2],
+
+class UnitgradeTextRunner(unittest.TextTestRunner):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
+
+def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
+                    show_progress_bar=True,
+                    show_tol_err=False,
+                    big_header=True):
+
+    now = datetime.now()
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
+    print(b + " v" + __version__)
+    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
+    print("Started: " + dt_string)
+    s = report.title
+    if hasattr(report, "version") and report.version is not None:
+        s += " version " + report.version
+    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")
+    # print(f"Loaded answers from: ", report.computed_answers_file, "\n")
+    table_data = []
+    nL = 80
+    t_start = time.time()
+    score = {}
+    loader = SequentialTestLoader()
+
+    for n, (q, w) in enumerate(report.questions):
+        # q = q()
+        # q_hidden = False
+        # q_hidden = issubclass(q.__class__, Hidden)
+        if question is not None and n+1 != question:
+            continue
+        suite = loader.loadTestsFromTestCase(q)
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
+        q_title_print = "Question %i: %s"%(n+1, qtitle)
+        print(q_title_print, end="")
+        q.possible = 0
+        q.obtained = 0
+        q_ = {} # Gather score in this class.
+        # unittest.Te
+        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
+        UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
+        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
+        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
+        z = 234
+        # for j, item in enumerate(q.items):
+        #     if qitem is not None and question is not None and j+1 != qitem:
+        #         continue
+        #
+        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.
+        #         # if not item.question.has_called_init_:
+        #         start = time.time()
+        #
+        #         cc = None
+        #         if show_progress_bar:
+        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )
+        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)
+        #         from unitgrade import Capturing # DON'T REMOVE THIS LINE
+        #         with eval('Capturing')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.
+        #             try:
+        #                 for q2 in q_with_outstanding_init:
+        #                     q2.init()
+        #                     q2.has_called_init_ = True
+        #
+        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.
+        #             except Exception as e:
+        #                 if not passall:
+        #                     if not silent:
+        #                         print(" ")
+        #                         print("="*30)
+        #                         print(f"When initializing question {q.title} the initialization code threw an error")
+        #                         print(e)
+        #                         print("The remaining parts of this question will likely fail.")
+        #                         print("="*30)
+        #
+        #         if show_progress_bar:
+        #             cc.terminate()
+        #             sys.stdout.flush()
+        #             print(q_title_print, end="")
+        #
+        #         q_time =np.round(  time.time()-start, 2)
+        #
+        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")
+        #         print("=" * nL)
+        #         q_with_outstanding_init = None
+        #
+        #     # item.question = q # Set the parent question instance for later reference.
+        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)
+        #
+        #     if show_progress_bar:
+        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)
+        #     else:
+        #         print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="")
+        #     hidden = issubclass(item.__class__, Hidden)
+        #     # if not hidden:
+        #     #     print(ss, end="")
+        #     # sys.stdout.flush()
+        #     start = time.time()
+        #
+        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
+        #     q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
+        #     tsecs = np.round(time.time()-start, 2)
+        #     if show_progress_bar:
+        #         cc.terminate()
+        #         sys.stdout.flush()
+        #         print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="")
+        #
+        #     if not hidden:
+        #         ss = "PASS" if current == possible else "*** FAILED"
+        #         if tsecs >= 0.1:
+        #             ss += " ("+ str(tsecs) + " seconds)"
+        #         print(ss)
+
+        # ws, possible, obtained = upack(q_)
+
+        possible = res.testsRun
+        obtained = len(res.successes)
+
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
+
+        # possible = int(ws @ possible)
+        # obtained = int(ws @ obtained)
+        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
+
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
+        score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
+        q.obtained = obtained
+        q.possible = possible
+
+        s1 = f"*** Question q{n+1}"
+        s2 = f" {q.obtained}/{w}"
+        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )
+        print(" ")
+        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])
+
+    ws, possible, obtained = upack(score)
+    possible = int( msum(possible) )
+    obtained = int( msum(obtained) ) # Cast to python int
+    report.possible = possible
+    report.obtained = obtained
+    now = datetime.now()
+    dt_string = now.strftime("%H:%M:%S")
+
+    dt = int(time.time()-t_start)
+    minutes = dt//60
+    seconds = dt - minutes*60
+    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")
+
+    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")
+
+    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])
+    results = {'total': (obtained, possible), 'details': score}
+    return results, table_data
+
+
+
+
+from tabulate import tabulate
+from datetime import datetime
+import inspect
+import json
+import os
+import bz2
+import pickle
+import os
+
+def bzwrite(json_str, token): # to get around obfuscation issues
+    with getattr(bz2, 'open')(token, "wt") as f:
+        f.write(json_str)
+
+def gather_imports(imp):
+    resources = {}
+    m = imp
+    # for m in pack_imports:
+    # print(f"*** {m.__name__}")
+    f = m.__file__
+    # dn = os.path.dirname(f)
+    # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__)
+    # top_package = str(__import__(m.__name__.split('.')[0]).__path__)
+    if m.__class__.__name__ == 'module' and False:
+        top_package = os.path.dirname(m.__file__)
+        module_import = True
+    else:
+        top_package = __import__(m.__name__.split('.')[0]).__path__._path[0]
+        module_import = False
+
+    # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__)
+    # top_package = os.path.dirname(top_package)
+    import zipfile
+    # import strea
+    # zipfile.ZipFile
+    import io
+    # file_like_object = io.BytesIO(my_zip_data)
+    zip_buffer = io.BytesIO()
+    with zipfile.ZipFile(zip_buffer, 'w') as zip:
+        # zip.write()
+        for root, dirs, files in os.walk(top_package):
+            for file in files:
+                if file.endswith(".py"):
+                    fpath = os.path.join(root, file)
+                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))
+                    zip.write(fpath, v)
+
+    resources['zipfile'] = zip_buffer.getvalue()
+    resources['top_package'] = top_package
+    resources['module_import'] = module_import
+    return resources, top_package
+
+    if f.endswith("__init__.py"):
+        for root, dirs, files in os.walk(os.path.dirname(f)):
+            for file in files:
+                if file.endswith(".py"):
+                    # print(file)
+                    # print()
+                    v = os.path.relpath(os.path.join(root, file), top_package)
+                    with open(os.path.join(root, file), 'r') as ff:
+                        resources[v] = ff.read()
+    else:
+        v = os.path.relpath(f, top_package)
+        with open(f, 'r') as ff:
+            resources[v] = ff.read()
+    return resources
+
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
+
+def gather_upload_to_campusnet(report, output_dir=None):
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
+    print(" ")
+    print("="*n)
+    print("Final evaluation")
+    print(tabulate(table_data))
+    # also load the source code of missing files...
+
+    sources = {}
+
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
+
+    if output_dir is None:
+        output_dir = os.getcwd()
+
+    payload_out_base = report.__class__.__name__ + "_handin"
+
+    obtain, possible = results['total']
+    vstring = "_v"+report.version if report.version is not None else ""
+
+    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)
+    token = os.path.join(output_dir, token)
+    with open(token, 'wb') as f:
+        pickle.dump(results, f)
+
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
+
+def source_instantiate(name, report1_source, payload):
+    eval("exec")(report1_source, globals())
+    pl = pickle.loads(bytes.fromhex(payload))
+    report = eval(name)(payload=pl, strict=True)
+    # report.set_payload(pl)
+    return report
+
+
+
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    @classmethod\n    def question_title(cls):\n        return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    # def _callSetUp(self):\n    #     # Always run before method is called.\n    #     print("asdf")\n    #     pass\n    # @classmethod\n    # def setUpClass(cls):\n    #     # self._cache_put((self.cache_id(), \'title\'), value)\n    #     cls.reset()\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    # def unique_cache_id(self):\n    #     k0 = self.cache_id()\n    #     # key = ()\n    #     i = 0\n    #     for i in itertools.count():\n    #         # key = k0 + (i,)\n    #         if i not in self._cache_get( (k0, \'assert\') ):\n    #             break\n    #     return i\n    #     return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n    #\n    # def _cache2_contains(self, key):\n    #     print("Is this needed?")\n    #     self._ensure_cache_exists()\n    #     return key in self.__class__._cache2\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nimport random\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n\n    def test_add(self):\n        """ Docstring for this method """\n        from cs102.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    def test_reverse(self):\n        """ Reverse a list """  # Add a title to the test.\n        from cs102.homework1 import reverse_list\n        self.assertEqualC(reverse_list([1,2,3]))\n\n\nclass Question2(UTestCase):\n    """ Second problem """\n    @cache\n    def my_reversal(self, ls):\n        # The \'@cache\' decorator ensures the function is not run on the *students* computer\n        # Instead the code is run on the teachers computer and the result is passed on with the\n        # other pre-computed results -- i.e. this function will run regardless of how the student happens to have\n        # implemented reverse_list.\n        from cs102.homework1 import reverse_list\n        return reverse_list(ls)\n\n    def test_reverse_tricky(self):\n        ls = [2,4,8]\n        self.title = f"Reversing a small list containing {ls=}"\n        ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.\n        ls3 = self.my_reversal( tuple([1,2,3]) )  # Also works; the cache respects input arguments.\n        self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.\n\n\nimport cs102\nclass Report2(Report):\n    title = "CS 101 Report 2"\n    questions = [(Week1, 10), (Question2, 8) ]  # Include a single question for 10 credits.\n    pack_imports = [cs102]'
+report1_payload = '8004959a010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c057469746c659486948c19446f63737472696e6720666f722074686973206d6574686f64946803680486948c066173736572749486947d94284b004b044b014aa1ffffff756803680486948c0474696d6594869447000000000000000068038c0c746573745f72657665727365948694680686948c0e526576657273652061206c69737494680368108694680a86947d944b005d94284b034b024b016573680368108694680e86944700000000000000008c0474696d6594470000000000000000758c095175657374696f6e32947d94288c095175657374696f6e32948c13746573745f726576657273655f747269636b799486948c066173736572749486947d944b005d94284b024b044b086573681d681e86948c057469746c659486948c2e526576657273696e67206120736d616c6c206c69737420636f6e7461696e696e67206c733d5b322c20342c20385d94681d681e86948c0474696d65948694470000000000000000681a473f5066000000000075752e'
+name="Report2"
+
+report = source_instantiate(name, report1_source, report1_payload)
+output_dir = os.path.dirname(__file__)
+gather_upload_to_campusnet(report, output_dir)
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs102/src/student_sources.zip b/examples/autolab_example/tmp/cs102/src/student_sources.zip
new file mode 100644
index 0000000000000000000000000000000000000000..7029f9b9b2c81e7cc6eb1081f402c8cf11df5635
Binary files /dev/null and b/examples/autolab_example/tmp/cs102/src/student_sources.zip differ
diff --git a/examples/autolab_example/tmp/cs103/Makefile b/examples/autolab_example/tmp/cs103/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..c96bcd1245ba5f79bf5621999f05f589f36265bd
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/Makefile
@@ -0,0 +1,51 @@
+#
+# Makefile to manage the example Hello Lab
+#
+
+# Get the name of the lab directory
+# LAB = $(notdir $(PWD)) # Fail on windows for some reason...
+
+all: handout handout-tarfile
+
+handout: 
+	# Rebuild the handout directory that students download
+	(rm -rf cs103-handout; mkdir cs103-handout)
+	cp -p src/Makefile-handout cs103-handout/Makefile
+	cp -p src/README-handout cs103-handout/README
+	cp -p src/driver_python.py cs103-handout
+
+	cp -p src/student_sources.zip cs103-handout
+
+	cp -p src/Report3_handin.token cs103-handout
+
+	cp -p src/docker_helpers.py cs103-handout
+
+	cp -p src/report3_complete_grade.py cs103-handout
+
+
+handout-tarfile: handout
+	# Build *-handout.tar and autograde.tar
+	tar cvf cs103-handout.tar cs103-handout
+	cp -p cs103-handout.tar autograde.tar
+
+clean:
+	# Clean the entire lab directory tree.  Note that you can run
+	# "make clean; make" at any time while the lab is live with no
+	# adverse effects.
+	rm -f *~ *.tar
+	(cd src; make clean)
+	(cd test-autograder; make clean)
+	rm -rf cs103-handout
+	rm -f autograde.tar
+#
+# CAREFULL!!! This will delete all student records in the logfile and
+# in the handin directory. Don't run this once the lab has started.
+# Use it to clean the directory when you are starting a new version
+# of the lab from scratch, or when you are debugging the lab prior
+# to releasing it to the students.
+#
+cleanallfiles:
+	# Reset the lab from scratch.
+	make clean
+	rm -f log.txt
+	rm -rf handin/*
diff --git a/examples/autolab_example/tmp/cs103/autograde-Makefile b/examples/autolab_example/tmp/cs103/autograde-Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..340be2e27f2ec200100793fe232185e86790b311
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/autograde-Makefile
@@ -0,0 +1,7 @@
+all:
+	tar xf autograde.tar
+	cp Report3_handin.token cs103-handout
+	(cd cs103-handout; python3 driver_python.py)
+
+clean:
+	rm -rf *~ hello3-handout
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs103/cs103.rb b/examples/autolab_example/tmp/cs103/cs103.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1ae1e3384bd0cc243de26537740a7b35c7bbd840
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/cs103.rb
@@ -0,0 +1,11 @@
+require "AssessmentBase.rb"
+
+module Cs103
+  include AssessmentBase
+
+  def assessmentInitialize(course)
+    super("cs103",course)
+    @problems = []
+  end
+
+end
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs103/cs103.yml b/examples/autolab_example/tmp/cs103/cs103.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c6ff65759ec6d66a13c5683edb125eabc55215e3
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/cs103.yml
@@ -0,0 +1,38 @@
+---
+
+general:
+  name: cs103
+  description: ''
+  display_name: CS 101 Report 3
+  handin_filename: Report3_handin.token
+  handin_directory: handin
+  max_grace_days: 0
+  handout: cs103-handout.tar
+  writeup: writeup/cs103.html
+  max_submissions: -1
+  disable_handins: false
+  max_size: 2
+  has_svn: false
+  category_name: Lab
+problems:
+
+  - name: Total
+    description: ''
+    max_score: 20
+    optional: false
+
+  - name: The first question for week 1.
+    description: ''
+    max_score: 20
+    optional: true
+
+autograder:
+  autograde_timeout: 180
+  autograde_image: tango_python_tue
+  release_score: true
+
+# problems:
+# - name: Correctness
+#  description: ''
+#  max_score: 100.0
+#  optional: false
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs103/src/Makefile b/examples/autolab_example/tmp/cs103/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..6d094e3a3869dfe9ee9e51a06150c6999c402286
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/Makefile
@@ -0,0 +1,7 @@
+# Makefile for the Hello Lab
+all:
+	echo "Makefile called... it is empty so far. "
+	#gcc hello3.c -o hello3
+
+clean:
+	rm -rf *~ hello3
diff --git a/examples/autolab_example/tmp/cs103/src/Makefile-handout b/examples/autolab_example/tmp/cs103/src/Makefile-handout
new file mode 100644
index 0000000000000000000000000000000000000000..6d094e3a3869dfe9ee9e51a06150c6999c402286
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/Makefile-handout
@@ -0,0 +1,7 @@
+# Makefile for the Hello Lab
+all:
+	echo "Makefile called... it is empty so far. "
+	#gcc hello3.c -o hello3
+
+clean:
+	rm -rf *~ hello3
diff --git a/examples/autolab_example/tmp/cs103/src/README b/examples/autolab_example/tmp/cs103/src/README
new file mode 100644
index 0000000000000000000000000000000000000000..8eea4bef3abb4665581173c4843b6155b3dc59d2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/README
@@ -0,0 +1,15 @@
+This directory contains all of the code files for the Hello Lab,
+including the files that are handed out to students.
+
+Files:
+
+# Autograder and solution files
+Makefile                Makefile and ...
+README                  ... README for this directory
+driver.sh*              Autograder
+hello.c                 Solution hello.c file
+
+# Files that are handed out to students
+Makefile-handout        Makefile and ...
+README-handout          ... README handed out to students
+hello.c-handout         Blank hello.c file handed out to students
diff --git a/examples/autolab_example/tmp/cs103/src/README-handout b/examples/autolab_example/tmp/cs103/src/README-handout
new file mode 100644
index 0000000000000000000000000000000000000000..8eea4bef3abb4665581173c4843b6155b3dc59d2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/README-handout
@@ -0,0 +1,15 @@
+This directory contains all of the code files for the Hello Lab,
+including the files that are handed out to students.
+
+Files:
+
+# Autograder and solution files
+Makefile                Makefile and ...
+README                  ... README for this directory
+driver.sh*              Autograder
+hello.c                 Solution hello.c file
+
+# Files that are handed out to students
+Makefile-handout        Makefile and ...
+README-handout          ... README handed out to students
+hello.c-handout         Blank hello.c file handed out to students
diff --git a/examples/autolab_example/tmp/cs103/src/Report3_handin.token b/examples/autolab_example/tmp/cs103/src/Report3_handin.token
new file mode 100644
index 0000000000000000000000000000000000000000..f623c0e13b0e234299024b217e8b5aabf126eefc
Binary files /dev/null and b/examples/autolab_example/tmp/cs103/src/Report3_handin.token differ
diff --git a/examples/autolab_example/tmp/cs103/src/docker_helpers.py b/examples/autolab_example/tmp/cs103/src/docker_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..cde3e6e03ba0c0104e0f61ec7aca43991d15c59b
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/docker_helpers.py
@@ -0,0 +1,161 @@
+# from cs202courseware.ug2report1 import Report1
+# import thtools
+import pickle
+import os
+import glob
+# from unitgrade_private2.deployment import remove_hidden_methods
+# from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet
+# from unitgrade_private2.hidden_create_files import setup_grade_file_report
+import shutil
+import time
+import zipfile
+import io
+
+def student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination):
+    """
+    :param Dockerfile_location:
+    :param host_tmp_dir:
+    :param student_token_file:
+    :param ReportClass:
+    :param instructor_grade_script:
+    :return:
+    """
+    # assert os.path.exists(Dockerfile_location)
+    start = time.time()
+
+    with open(student_token_file, 'rb') as f:
+        results = pickle.load(f)
+    sources = results['sources'][0]
+
+    with io.BytesIO(sources['zipfile']) as zb:
+        with zipfile.ZipFile(zb) as zip:
+            zip.extractall(host_tmp_dir)
+    # Done extracting the zip file! Now time to move the (good) report test class into the location.
+    import inspect
+    # if ReportClass is not None:
+    #     gscript = inspect.getfile(ReportClass)[:-3] + "_grade.py"
+    # else:
+    gscript = instructor_grade_script
+    print(f"{sources['report_relative_location']=}")
+    print(f"{sources['name']=}")
+    # student_grade_script = host_tmp_dir + "/" + sources['name'] + "/" + sources['report_relative_location']
+    # instructor_grade_script = os.path.dirname(student_grade_script) + "/" + os.path.basename(gscript)
+    print("Now in docker_helpers.py")
+    print(f'{gscript=}')
+    print(f'{instructor_grade_script=}')
+    gscript_destination = host_tmp_dir + "/" + grade_file_relative_destination
+    print(f'{gscript_destination=}')
+
+    shutil.copy(gscript, gscript_destination)
+
+    # Now everything appears very close to being set up and ready to roll!.
+    # import thtools
+
+    # os.path.split()
+    d = os.path.normpath(grade_file_relative_destination).split(os.sep)
+    d = d[:-1] + [os.path.basename(instructor_grade_script)[:-3]]
+    # print(f'{d=}')
+    pycom = ".".join(d)
+
+
+    """
+    docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker python3 -m cs202courseware.ug2report1_grade
+    """
+    # dockname = os.path.basename(os.path.dirname(Dockerfile_location))
+
+    # tmp_grade_file = sources['name'] + "/" + sources['report_relative_location']
+    # print(f'{tmp_grade_file=}')
+    # pycom = ".".join((sources['name'],) + os.path.split(sources['report_relative_location'])[1:-1] + (os.path.basename(gscript),))
+    pycom = "python3 -m " + pycom # pycom[:-3]
+    print(f"{pycom=}")
+    # tmp_path = os.path.abspath(host_tmp_dir).replace("\\", "/")
+    # dcom = f"docker run -v {tmp_path}:/app {dockname} {pycom}"
+    # cdcom = f"cd {os.path.dirname(Dockerfile_location)}"
+    # fcom = f"{cdcom}  && {dcom}"
+    # print("> Running docker command")
+    # print(fcom)
+
+    # thtools.execute_command(fcom.split())
+    # get token file:
+
+    token_location = host_tmp_dir + "/" + os.path.dirname( grade_file_relative_destination ) + "/*.token"
+
+
+    # host_tmp_dir + "/" + os.path.dirname(tmp_grade_file) + "/"
+    # tokens = glob.glob(host_tmp_dir + "/" + os.path.dirname(tmp_grade_file) + "/*.token")
+    # token_location = host_tmp_dir + "/" + os.path.dirname(tmp_grade_file)
+
+    # for t in tokens:
+    #     print("Source image produced token", t)
+    elapsed = time.time() - start
+    # print("Elapsed time is", elapsed)
+    return pycom, token_location
+    pass
+
+def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file, ReportClass=None, instructor_grade_script=None):
+    """
+    This thingy works:
+
+    To build the image, run:
+    docker build --tag python-docker .
+
+    To run the app run:
+
+    docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker > output.log
+
+    """
+    # A bunch of tests. This is going to be great!
+    assert os.path.exists(Dockerfile_location)
+    start = time.time()
+
+    with open(student_token_file, 'rb') as f:
+        results = pickle.load(f)
+    sources = results['sources'][0]
+
+    if os.path.exists(host_tmp_dir):
+        shutil.rmtree(host_tmp_dir)
+
+    with io.BytesIO(sources['zipfile']) as zb:
+        with zipfile.ZipFile(zb) as zip:
+            zip.extractall(host_tmp_dir)
+    # Done extracting the zip file! Now time to move the (good) report test class into the location.
+    import inspect
+    if ReportClass is not None:
+        gscript = inspect.getfile(ReportClass)[:-3] + "_grade.py"
+    else:
+        gscript = instructor_grade_script
+
+    student_grade_script = host_tmp_dir + "/" + sources['name'] + "/" + sources['report_relative_location']
+    instructor_grade_script = os.path.dirname(student_grade_script) + "/"+os.path.basename(gscript)
+    shutil.copy(gscript, instructor_grade_script)
+
+    # Now everything appears very close to being set up and ready to roll!.
+    import thtools
+
+    """
+    docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker python3 -m cs202courseware.ug2report1_grade
+    """
+    dockname = os.path.basename( os.path.dirname(Dockerfile_location) )
+
+    tmp_grade_file =  sources['name'] + "/" + sources['report_relative_location']
+
+    pycom = ".".join( (sources['name'], ) + os.path.split(sources['report_relative_location'])[1:-1] + (os.path.basename(gscript),) )
+    pycom = "python3 -m " + pycom[:-3]
+
+    tmp_path = os.path.abspath(host_tmp_dir).replace("\\", "/")
+    dcom = f"docker run -v {tmp_path}:/app {dockname} {pycom}"
+    cdcom = f"cd {os.path.dirname(Dockerfile_location)}"
+    fcom = f"{cdcom}  && {dcom}"
+    print("> Running docker command")
+    print(fcom)
+    init = time.time() - start
+    thtools.execute_command(fcom.split())
+    # get token file:
+
+    host_tmp_dir +"/" + os.path.dirname(tmp_grade_file) + "/"
+    tokens = glob.glob(host_tmp_dir +"/" + os.path.dirname(tmp_grade_file) + "/*.token" )
+    for t in tokens:
+        print("Source image produced token", t)
+    elapsed = time.time() - start
+    print("Elapsed time is", elapsed, f"({init=} seconds)")
+    return tokens[0]
diff --git a/examples/autolab_example/tmp/cs103/src/driver.sh b/examples/autolab_example/tmp/cs103/src/driver.sh
new file mode 100644
index 0000000000000000000000000000000000000000..05a006e95e416fa5d5088f1d61479f73901588c2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/driver.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+# driver.sh - The simplest autograder we could think of. It checks
+#   that students can write a C program that compiles, and then
+#   executes with an exit status of zero.
+#   Usage: ./driver.sh
+
+# Compile the code
+# echo "Compiling hello3.c"
+# python3 -c "print('Hello world from python 2')"
+# python3 --version
+python3 driver_python.py
+
+#(make clean; make)
+#status=$?
+#if [ ${status} -ne 0 ]; then
+#    echo "Failure: Unable to compile hello3.c (return status = ${status})"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#    exit
+#fi
+#
+# Run the code
+#echo "Running ./hello3"
+#./hello3
+#status=$?
+#if [ ${status} -eq 0 ]; then
+#    echo "Success: ./hello3 runs with an exit status of 0"
+#    echo "{\"scores\": {\"Correctness\": 100}}"
+#else
+#    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#fi
+
+exit
diff --git a/examples/autolab_example/tmp/cs103/src/driver.sh-handout b/examples/autolab_example/tmp/cs103/src/driver.sh-handout
new file mode 100644
index 0000000000000000000000000000000000000000..05a006e95e416fa5d5088f1d61479f73901588c2
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/driver.sh-handout
@@ -0,0 +1,33 @@
+#!/bin/bash
+# driver.sh - The simplest autograder we could think of. It checks
+#   that students can write a C program that compiles, and then
+#   executes with an exit status of zero.
+#   Usage: ./driver.sh
+
+# Compile the code
+# echo "Compiling hello3.c"
+# python3 -c "print('Hello world from python 2')"
+# python3 --version
+python3 driver_python.py
+
+#(make clean; make)
+#status=$?
+#if [ ${status} -ne 0 ]; then
+#    echo "Failure: Unable to compile hello3.c (return status = ${status})"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#    exit
+#fi
+#
+# Run the code
+#echo "Running ./hello3"
+#./hello3
+#status=$?
+#if [ ${status} -eq 0 ]; then
+#    echo "Success: ./hello3 runs with an exit status of 0"
+#    echo "{\"scores\": {\"Correctness\": 100}}"
+#else
+#    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#fi
+
+exit
diff --git a/examples/autolab_example/tmp/cs103/src/driver_python.py b/examples/autolab_example/tmp/cs103/src/driver_python.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed9bd8bd0b965021e1261cb9ff9f20c02b503594
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/driver_python.py
@@ -0,0 +1,83 @@
+import os
+import glob
+import sys
+import pickle
+# import io
+import subprocess
+import docker_helpers
+import time
+
+verbose = False
+tag = "[driver_python.py]"
+
+if not verbose:
+    print("="*10)
+    print(tag, "Starting unitgrade evaluation...")
+
+sys.stderr = sys.stdout
+wdir = os.getcwd()
+
+def pfiles():
+    print("> Files in dir:")
+    for f in glob.glob(wdir + "/*"):
+        print(f)
+    print("---")
+
+student_token_file = 'Report3_handin.token'
+instructor_grade_script = 'report3_complete_grade.py'
+grade_file_relative_destination = "cs103\report3_complete_grade.py"
+with open(student_token_file, 'rb') as f:
+    results = pickle.load(f)
+sources = results['sources'][0]
+host_tmp_dir = wdir + "/tmp"
+
+if not verbose:
+    pfiles()
+    print(f"{host_tmp_dir=}")
+    print(f"{student_token_file=}")
+    print(f"{instructor_grade_script=}")
+
+command, token = docker_helpers.student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination)
+command = f"cd tmp && {command} --noprogress --autolab"
+
+def rcom(cm):
+    # print(f"running... ", cm)
+    # start = time.time()
+    rs = subprocess.run(cm, capture_output=True, text=True, shell=True)
+    print(rs.stdout)
+
+    if len(rs.stderr) > 0:
+        print(tag, "There were errors in executing the file:")
+        print(rs.stderr)
+    # print(rs)
+    # print("result of running command was", rs.stdout, "err", rs.stderr, "time", time.time() - start)
+
+start = time.time()
+rcom(command)
+# pfiles()
+# for f in glob.glob(host_tmp_dir + "/cs101/*"):
+#     print("cs101/", f)
+# print("---")
+ls = glob.glob(token)
+# print(ls)
+f = ls[0]
+with open(f, 'rb') as f:
+    results = pickle.load(f)
+# print("results")
+# print(results.keys())
+if verbose:
+    print(f"{token=}")
+    print(results['total'])
+# if os.path.exists(host_tmp_dir):
+#     shutil.rmtree(host_tmp_dir)
+# with io.BytesIO(sources['zipfile']) as zb:
+#     with zipfile.ZipFile(zb) as zip:
+#         zip.extractall(host_tmp_dir
+# print("="*10)
+# print('{"scores": {"Correctness": 100,  "Problem 1": 4}}')
+
+sc = [('Total', results['total'][0])] + [(q['title'], q['obtained']) for k, q in results['details'].items()]
+ss = ", ".join([f'"{t}": {s}' for t, s in sc])
+scores = '{"scores": {' + ss + '}}'
+print('{"_presentation": "semantic"}')
+print(scores)
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs103/src/driver_python.py-handout b/examples/autolab_example/tmp/cs103/src/driver_python.py-handout
new file mode 100644
index 0000000000000000000000000000000000000000..ed9bd8bd0b965021e1261cb9ff9f20c02b503594
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/driver_python.py-handout
@@ -0,0 +1,83 @@
+import os
+import glob
+import sys
+import pickle
+# import io
+import subprocess
+import docker_helpers
+import time
+
+verbose = False
+tag = "[driver_python.py]"
+
+if not verbose:
+    print("="*10)
+    print(tag, "Starting unitgrade evaluation...")
+
+sys.stderr = sys.stdout
+wdir = os.getcwd()
+
+def pfiles():
+    print("> Files in dir:")
+    for f in glob.glob(wdir + "/*"):
+        print(f)
+    print("---")
+
+student_token_file = 'Report3_handin.token'
+instructor_grade_script = 'report3_complete_grade.py'
+grade_file_relative_destination = "cs103\report3_complete_grade.py"
+with open(student_token_file, 'rb') as f:
+    results = pickle.load(f)
+sources = results['sources'][0]
+host_tmp_dir = wdir + "/tmp"
+
+if not verbose:
+    pfiles()
+    print(f"{host_tmp_dir=}")
+    print(f"{student_token_file=}")
+    print(f"{instructor_grade_script=}")
+
+command, token = docker_helpers.student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination)
+command = f"cd tmp && {command} --noprogress --autolab"
+
+def rcom(cm):
+    # print(f"running... ", cm)
+    # start = time.time()
+    rs = subprocess.run(cm, capture_output=True, text=True, shell=True)
+    print(rs.stdout)
+
+    if len(rs.stderr) > 0:
+        print(tag, "There were errors in executing the file:")
+        print(rs.stderr)
+    # print(rs)
+    # print("result of running command was", rs.stdout, "err", rs.stderr, "time", time.time() - start)
+
+start = time.time()
+rcom(command)
+# pfiles()
+# for f in glob.glob(host_tmp_dir + "/cs101/*"):
+#     print("cs101/", f)
+# print("---")
+ls = glob.glob(token)
+# print(ls)
+f = ls[0]
+with open(f, 'rb') as f:
+    results = pickle.load(f)
+# print("results")
+# print(results.keys())
+if verbose:
+    print(f"{token=}")
+    print(results['total'])
+# if os.path.exists(host_tmp_dir):
+#     shutil.rmtree(host_tmp_dir)
+# with io.BytesIO(sources['zipfile']) as zb:
+#     with zipfile.ZipFile(zb) as zip:
+#         zip.extractall(host_tmp_dir
+# print("="*10)
+# print('{"scores": {"Correctness": 100,  "Problem 1": 4}}')
+
+sc = [('Total', results['total'][0])] + [(q['title'], q['obtained']) for k, q in results['details'].items()]
+ss = ", ".join([f'"{t}": {s}' for t, s in sc])
+scores = '{"scores": {' + ss + '}}'
+print('{"_presentation": "semantic"}')
+print(scores)
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs103/src/report3_complete_grade.py b/examples/autolab_example/tmp/cs103/src/report3_complete_grade.py
new file mode 100644
index 0000000000000000000000000000000000000000..50afad98747a22ab071cf3c531e327b0b5a5ccec
--- /dev/null
+++ b/examples/autolab_example/tmp/cs103/src/report3_complete_grade.py
@@ -0,0 +1,462 @@
+
+import numpy as np
+from tabulate import tabulate
+from datetime import datetime
+import pyfiglet
+import unittest
+
+import inspect
+import os
+import argparse
+import sys
+import time
+import threading # don't import Thread bc. of minify issue.
+import tqdm # don't do from tqdm import tqdm because of minify-issue
+
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: 
+To run all tests in a report: 
+
+> python assignment1_dp.py
+
+To run only question 2 or question 2.1
+
+> python assignment1_dp.py -q 2
+> python assignment1_dp.py -q 2.1
+
+Note this scripts does not grade your report. To grade your report, use:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)')
+parser.add_argument('--showexpected',  action="store_true",  help='Show the expected/desired result')
+parser.add_argument('--showcomputed',  action="store_true",  help='Show the answer your code computes')
+parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
+parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
+
+def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
+    args = parser.parse_args()
+    if question is None and args.q is not None:
+        question = args.q
+        if "." in question:
+            question, qitem = [int(v) for v in question.split(".")]
+        else:
+            question = int(question)
+
+    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:
+        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")
+
+    if unmute is None:
+        unmute = args.unmute
+    if passall is None:
+        passall = args.passall
+
+    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,
+                                          show_tol_err=show_tol_err)
+
+
+    # try:  # For registering stats.
+    #     import unitgrade_private
+    #     import irlc.lectures
+    #     import xlwings
+    #     from openpyxl import Workbook
+    #     import pandas as pd
+    #     from collections import defaultdict
+    #     dd = defaultdict(lambda: [])
+    #     error_computed = []
+    #     for k1, (q, _) in enumerate(report.questions):
+    #         for k2, item in enumerate(q.items):
+    #             dd['question_index'].append(k1)
+    #             dd['item_index'].append(k2)
+    #             dd['question'].append(q.name)
+    #             dd['item'].append(item.name)
+    #             dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol)
+    #             error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed)
+    #
+    #     qstats = report.wdir + "/" + report.name + ".xlsx"
+    #
+    #     if os.path.isfile(qstats):
+    #         d_read = pd.read_excel(qstats).to_dict()
+    #     else:
+    #         d_read = dict()
+    #
+    #     for k in range(1000):
+    #         key = 'run_'+str(k)
+    #         if key in d_read:
+    #             dd[key] = list(d_read['run_0'].values())
+    #         else:
+    #             dd[key] = error_computed
+    #             break
+    #
+    #     workbook = Workbook()
+    #     worksheet = workbook.active
+    #     for col, key in enumerate(dd.keys()):
+    #         worksheet.cell(row=1, column=col+1).value = key
+    #         for row, item in enumerate(dd[key]):
+    #             worksheet.cell(row=row+2, column=col+1).value = item
+    #
+    #     workbook.save(qstats)
+    #     workbook.close()
+    #
+    # except ModuleNotFoundError as e:
+    #     s = 234
+    #     pass
+
+    if question is None:
+        print("Provisional evaluation")
+        tabulate(table_data)
+        table = table_data
+        print(tabulate(table))
+        print(" ")
+
+    fr = inspect.getouterframes(inspect.currentframe())[1].filename
+    gfile = os.path.basename(fr)[:-3] + "_grade.py"
+    if os.path.exists(gfile):
+        print("Note your results have not yet been registered. \nTo register your results, please run the file:")
+        print(">>>", gfile)
+        print("In the same manner as you ran this file.")
+
+
+    return results
+
+
+def upack(q):
+    # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()])
+    h =[(i['w'], i['possible'], i['obtained']) for i in q.values()]
+    h = np.asarray(h)
+    return h[:,0], h[:,1], h[:,2],
+
+class UnitgradeTextRunner(unittest.TextTestRunner):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
+
+def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
+                    show_progress_bar=True,
+                    show_tol_err=False,
+                    big_header=True):
+
+    now = datetime.now()
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
+    print(b + " v" + __version__)
+    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
+    print("Started: " + dt_string)
+    s = report.title
+    if hasattr(report, "version") and report.version is not None:
+        s += " version " + report.version
+    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")
+    # print(f"Loaded answers from: ", report.computed_answers_file, "\n")
+    table_data = []
+    nL = 80
+    t_start = time.time()
+    score = {}
+    loader = SequentialTestLoader()
+
+    for n, (q, w) in enumerate(report.questions):
+        # q = q()
+        # q_hidden = False
+        # q_hidden = issubclass(q.__class__, Hidden)
+        if question is not None and n+1 != question:
+            continue
+        suite = loader.loadTestsFromTestCase(q)
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
+        q_title_print = "Question %i: %s"%(n+1, qtitle)
+        print(q_title_print, end="")
+        q.possible = 0
+        q.obtained = 0
+        q_ = {} # Gather score in this class.
+        # unittest.Te
+        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
+        UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
+        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
+        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
+        z = 234
+        # for j, item in enumerate(q.items):
+        #     if qitem is not None and question is not None and j+1 != qitem:
+        #         continue
+        #
+        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.
+        #         # if not item.question.has_called_init_:
+        #         start = time.time()
+        #
+        #         cc = None
+        #         if show_progress_bar:
+        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )
+        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)
+        #         from unitgrade import Capturing # DON'T REMOVE THIS LINE
+        #         with eval('Capturing')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.
+        #             try:
+        #                 for q2 in q_with_outstanding_init:
+        #                     q2.init()
+        #                     q2.has_called_init_ = True
+        #
+        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.
+        #             except Exception as e:
+        #                 if not passall:
+        #                     if not silent:
+        #                         print(" ")
+        #                         print("="*30)
+        #                         print(f"When initializing question {q.title} the initialization code threw an error")
+        #                         print(e)
+        #                         print("The remaining parts of this question will likely fail.")
+        #                         print("="*30)
+        #
+        #         if show_progress_bar:
+        #             cc.terminate()
+        #             sys.stdout.flush()
+        #             print(q_title_print, end="")
+        #
+        #         q_time =np.round(  time.time()-start, 2)
+        #
+        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")
+        #         print("=" * nL)
+        #         q_with_outstanding_init = None
+        #
+        #     # item.question = q # Set the parent question instance for later reference.
+        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)
+        #
+        #     if show_progress_bar:
+        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)
+        #     else:
+        #         print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="")
+        #     hidden = issubclass(item.__class__, Hidden)
+        #     # if not hidden:
+        #     #     print(ss, end="")
+        #     # sys.stdout.flush()
+        #     start = time.time()
+        #
+        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
+        #     q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
+        #     tsecs = np.round(time.time()-start, 2)
+        #     if show_progress_bar:
+        #         cc.terminate()
+        #         sys.stdout.flush()
+        #         print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="")
+        #
+        #     if not hidden:
+        #         ss = "PASS" if current == possible else "*** FAILED"
+        #         if tsecs >= 0.1:
+        #             ss += " ("+ str(tsecs) + " seconds)"
+        #         print(ss)
+
+        # ws, possible, obtained = upack(q_)
+
+        possible = res.testsRun
+        obtained = len(res.successes)
+
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
+
+        # possible = int(ws @ possible)
+        # obtained = int(ws @ obtained)
+        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
+
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
+        score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
+        q.obtained = obtained
+        q.possible = possible
+
+        s1 = f"*** Question q{n+1}"
+        s2 = f" {q.obtained}/{w}"
+        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )
+        print(" ")
+        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])
+
+    ws, possible, obtained = upack(score)
+    possible = int( msum(possible) )
+    obtained = int( msum(obtained) ) # Cast to python int
+    report.possible = possible
+    report.obtained = obtained
+    now = datetime.now()
+    dt_string = now.strftime("%H:%M:%S")
+
+    dt = int(time.time()-t_start)
+    minutes = dt//60
+    seconds = dt - minutes*60
+    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")
+
+    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")
+
+    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])
+    results = {'total': (obtained, possible), 'details': score}
+    return results, table_data
+
+
+
+
+from tabulate import tabulate
+from datetime import datetime
+import inspect
+import json
+import os
+import bz2
+import pickle
+import os
+
+def bzwrite(json_str, token): # to get around obfuscation issues
+    with getattr(bz2, 'open')(token, "wt") as f:
+        f.write(json_str)
+
+def gather_imports(imp):
+    resources = {}
+    m = imp
+    # for m in pack_imports:
+    # print(f"*** {m.__name__}")
+    f = m.__file__
+    # dn = os.path.dirname(f)
+    # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__)
+    # top_package = str(__import__(m.__name__.split('.')[0]).__path__)
+    if m.__class__.__name__ == 'module' and False:
+        top_package = os.path.dirname(m.__file__)
+        module_import = True
+    else:
+        top_package = __import__(m.__name__.split('.')[0]).__path__._path[0]
+        module_import = False
+
+    # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__)
+    # top_package = os.path.dirname(top_package)
+    import zipfile
+    # import strea
+    # zipfile.ZipFile
+    import io
+    # file_like_object = io.BytesIO(my_zip_data)
+    zip_buffer = io.BytesIO()
+    with zipfile.ZipFile(zip_buffer, 'w') as zip:
+        # zip.write()
+        for root, dirs, files in os.walk(top_package):
+            for file in files:
+                if file.endswith(".py"):
+                    fpath = os.path.join(root, file)
+                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))
+                    zip.write(fpath, v)
+
+    resources['zipfile'] = zip_buffer.getvalue()
+    resources['top_package'] = top_package
+    resources['module_import'] = module_import
+    return resources, top_package
+
+    if f.endswith("__init__.py"):
+        for root, dirs, files in os.walk(os.path.dirname(f)):
+            for file in files:
+                if file.endswith(".py"):
+                    # print(file)
+                    # print()
+                    v = os.path.relpath(os.path.join(root, file), top_package)
+                    with open(os.path.join(root, file), 'r') as ff:
+                        resources[v] = ff.read()
+    else:
+        v = os.path.relpath(f, top_package)
+        with open(f, 'r') as ff:
+            resources[v] = ff.read()
+    return resources
+
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
+
+def gather_upload_to_campusnet(report, output_dir=None):
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
+    print(" ")
+    print("="*n)
+    print("Final evaluation")
+    print(tabulate(table_data))
+    # also load the source code of missing files...
+
+    sources = {}
+
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
+
+    if output_dir is None:
+        output_dir = os.getcwd()
+
+    payload_out_base = report.__class__.__name__ + "_handin"
+
+    obtain, possible = results['total']
+    vstring = "_v"+report.version if report.version is not None else ""
+
+    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)
+    token = os.path.join(output_dir, token)
+    with open(token, 'wb') as f:
+        pickle.dump(results, f)
+
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
+
+def source_instantiate(name, report1_source, payload):
+    eval("exec")(report1_source, globals())
+    pl = pickle.loads(bytes.fromhex(payload))
+    report = eval(name)(payload=pl, strict=True)
+    # report.set_payload(pl)
+    return report
+
+
+
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    @classmethod\n    def question_title(cls):\n        return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    # def _callSetUp(self):\n    #     # Always run before method is called.\n    #     print("asdf")\n    #     pass\n    # @classmethod\n    # def setUpClass(cls):\n    #     # self._cache_put((self.cache_id(), \'title\'), value)\n    #     cls.reset()\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        self._ensure_cache_exists() # Make sure cache is there.\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    # def unique_cache_id(self):\n    #     k0 = self.cache_id()\n    #     # key = ()\n    #     i = 0\n    #     for i in itertools.count():\n    #         # key = k0 + (i,)\n    #         if i not in self._cache_get( (k0, \'assert\') ):\n    #             break\n    #     return i\n    #     return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n    #\n    # def _cache2_contains(self, key):\n    #     print("Is this needed?")\n    #     self._ensure_cache_exists()\n    #     return key in self.__class__._cache2\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    @hide\n    def test_add_hidden(self):\n        # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n        # See the output in the student directory for more information.\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
+report1_payload = '80049567000000000000007d948c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d659486944700000000000000008c0474696d6594473f847ee00000000075732e'
+name="Report3"
+
+report = source_instantiate(name, report1_source, report1_payload)
+output_dir = os.path.dirname(__file__)
+gather_upload_to_campusnet(report, output_dir)
\ No newline at end of file
diff --git a/examples/autolab_example/tmp/cs103/src/student_sources.zip b/examples/autolab_example/tmp/cs103/src/student_sources.zip
new file mode 100644
index 0000000000000000000000000000000000000000..da9e734a992fa202e11d57c29514921fe2617005
Binary files /dev/null and b/examples/autolab_example/tmp/cs103/src/student_sources.zip differ
diff --git a/examples/example_docker/instructor/cs103/Report3_handin_20_of_20.token b/examples/example_docker/instructor/cs103/Report3_handin_20_of_20.token
index 46f1cf9630a010ab69e3899228cc3cc93a35366e..7f832c63864e424f9206fcd3c14ff26f81d12d3f 100644
Binary files a/examples/example_docker/instructor/cs103/Report3_handin_20_of_20.token and b/examples/example_docker/instructor/cs103/Report3_handin_20_of_20.token differ
diff --git a/examples/example_docker/instructor/cs103/__pycache__/homework1.cpython-38.pyc b/examples/example_docker/instructor/cs103/__pycache__/homework1.cpython-38.pyc
index 301c79fcc3244747ff167131505a756d0f54b54e..4beaff64aef655b294b4513aa4842f7eada9c024 100644
Binary files a/examples/example_docker/instructor/cs103/__pycache__/homework1.cpython-38.pyc and b/examples/example_docker/instructor/cs103/__pycache__/homework1.cpython-38.pyc differ
diff --git a/examples/example_docker/instructor/cs103/__pycache__/report3.cpython-38.pyc b/examples/example_docker/instructor/cs103/__pycache__/report3.cpython-38.pyc
index e5e928b3cf8c95f9ee7e0283457b713591a406e4..da19a02cc1c508d3eaafcf35b0b34e9d58501d7e 100644
Binary files a/examples/example_docker/instructor/cs103/__pycache__/report3.cpython-38.pyc and b/examples/example_docker/instructor/cs103/__pycache__/report3.cpython-38.pyc differ
diff --git a/examples/example_docker/instructor/cs103/__pycache__/report3_complete.cpython-38.pyc b/examples/example_docker/instructor/cs103/__pycache__/report3_complete.cpython-38.pyc
index c2a10cbaa71b99e5be06729a818f59fbbb70813a..921af5c7208e7aa4d953fa1e4551029aeb05c182 100644
Binary files a/examples/example_docker/instructor/cs103/__pycache__/report3_complete.cpython-38.pyc and b/examples/example_docker/instructor/cs103/__pycache__/report3_complete.cpython-38.pyc differ
diff --git a/examples/example_docker/students/cs103/__pycache__/report3_grade.cpython-39.pyc b/examples/example_docker/instructor/cs103/__pycache__/report3_complete_grade.cpython-38.pyc
similarity index 72%
rename from examples/example_docker/students/cs103/__pycache__/report3_grade.cpython-39.pyc
rename to examples/example_docker/instructor/cs103/__pycache__/report3_complete_grade.cpython-38.pyc
index 17b373431080e522689c3074908153560ca3d7db..29600ad002bee7bcffb89098df90876e25fd7158 100644
Binary files a/examples/example_docker/students/cs103/__pycache__/report3_grade.cpython-39.pyc and b/examples/example_docker/instructor/cs103/__pycache__/report3_complete_grade.cpython-38.pyc differ
diff --git a/examples/example_docker/instructor/cs103/deploy.py b/examples/example_docker/instructor/cs103/deploy.py
index 8bd55716702ae2ac16e1455e26162608943917f7..9379f590784446c87e4008fd2d59bdc59660c26a 100644
--- a/examples/example_docker/instructor/cs103/deploy.py
+++ b/examples/example_docker/instructor/cs103/deploy.py
@@ -12,7 +12,7 @@ from snipper.snip_dir import snip_dir
 
 def deploy_student_files():
     setup_grade_file_report(Report3, minify=False, obfuscate=False, execute=False)
-    Report3.reset()
+    # Report3.reset()
 
     fout, ReportWithoutHidden = remove_hidden_methods(Report3, outfile="report3.py")
     setup_grade_file_report(ReportWithoutHidden, minify=False, obfuscate=False, execute=False)
@@ -31,8 +31,10 @@ def run_student_code_on_docker(Dockerfile, student_token_file):
 
 if __name__ == "__main__":
     # Step 1: Deploy the students files and return the directory they were written to
-    # student_directory = deploy_student_files()
-    student_directory = "../../students/cs103"
+    student_directory = deploy_student_files()
+    # import sys
+    # sys.exit()
+    # student_directory = "../../students/cs103"
     # Step 2: Simulate that the student run their report script and generate a .token file.
     os.system("cd ../../students && python -m cs103.report3_grade")
     student_token_file = glob.glob(student_directory + "/*.token")[0]
diff --git a/examples/example_docker/instructor/cs103/report3.py b/examples/example_docker/instructor/cs103/report3.py
index c9a23ec4c93481016205b12cf9ec7546e83f376d..6dfbe04f7107436eb376dbd03206f30f44472287 100644
--- a/examples/example_docker/instructor/cs103/report3.py
+++ b/examples/example_docker/instructor/cs103/report3.py
@@ -9,10 +9,15 @@ class Week1(UTestCase):
         self.assertEqualC(add(-100, 5))
 
 
+class AutomaticPass(UTestCase):
+    def test_student_passed(self):
+        self.assertEqual(2,2)
+
+
 import cs103
 class Report3(Report):
     title = "CS 101 Report 3"
-    questions = [(Week1, 20)]  # Include a single question for 10 credits.
+    questions = [(Week1, 20), (AutomaticPass, 10)]  # Include a single question for 10 credits.
     pack_imports = [cs103]
 
 if __name__ == "__main__":
diff --git a/examples/example_docker/instructor/cs103/report3_complete.py b/examples/example_docker/instructor/cs103/report3_complete.py
index 37c50b9ed9ee5ee87e58df267a272893284bcf0c..4e72f820656948ed33cb485d3c940c6e4f1fd85a 100644
--- a/examples/example_docker/instructor/cs103/report3_complete.py
+++ b/examples/example_docker/instructor/cs103/report3_complete.py
@@ -15,10 +15,18 @@ class Week1(UTestCase):
         from cs103.homework1 import add
         self.assertEqualC(add(2,2))
 
+class AutomaticPass(UTestCase):
+    def test_student_passed(self):
+        self.assertEqual(2,2)
+
+    @hide
+    def test_hidden_fail(self):
+        self.assertEqual(2,3)
+
 import cs103
 class Report3(Report):
     title = "CS 101 Report 3"
-    questions = [(Week1, 20)]  # Include a single question for 10 credits.
+    questions = [(Week1, 20), (AutomaticPass, 10)]  # Include a single question for 10 credits.
     pack_imports = [cs103]
 
 if __name__ == "__main__":
diff --git a/examples/example_docker/instructor/cs103/report3_complete_grade.py b/examples/example_docker/instructor/cs103/report3_complete_grade.py
index 9dfbd03283e1bae5cb87d6d189cd8abfb87e97da..b053e48dd7d2feeb7bc45cebec0c175ec41590a6 100644
--- a/examples/example_docker/instructor/cs103/report3_complete_grade.py
+++ b/examples/example_docker/instructor/cs103/report3_complete_grade.py
@@ -4,6 +4,7 @@ from tabulate import tabulate
 from datetime import datetime
 import pyfiglet
 import unittest
+# from unitgrade2.unitgrade2 import MySuite
 
 import inspect
 import os
@@ -40,8 +41,6 @@ parser.add_argument('--showcomputed',  action="store_true",  help='Show the answ
 parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
 parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
 
-
-
 def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
     args = parser.parse_args()
     if question is None and args.q is not None:
@@ -63,53 +62,6 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass
                                           show_tol_err=show_tol_err)
 
 
-    # try:  # For registering stats.
-    #     import unitgrade_private
-    #     import irlc.lectures
-    #     import xlwings
-    #     from openpyxl import Workbook
-    #     import pandas as pd
-    #     from collections import defaultdict
-    #     dd = defaultdict(lambda: [])
-    #     error_computed = []
-    #     for k1, (q, _) in enumerate(report.questions):
-    #         for k2, item in enumerate(q.items):
-    #             dd['question_index'].append(k1)
-    #             dd['item_index'].append(k2)
-    #             dd['question'].append(q.name)
-    #             dd['item'].append(item.name)
-    #             dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol)
-    #             error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed)
-    #
-    #     qstats = report.wdir + "/" + report.name + ".xlsx"
-    #
-    #     if os.path.isfile(qstats):
-    #         d_read = pd.read_excel(qstats).to_dict()
-    #     else:
-    #         d_read = dict()
-    #
-    #     for k in range(1000):
-    #         key = 'run_'+str(k)
-    #         if key in d_read:
-    #             dd[key] = list(d_read['run_0'].values())
-    #         else:
-    #             dd[key] = error_computed
-    #             break
-    #
-    #     workbook = Workbook()
-    #     worksheet = workbook.active
-    #     for col, key in enumerate(dd.keys()):
-    #         worksheet.cell(row=1, column=col+1).value = key
-    #         for row, item in enumerate(dd[key]):
-    #             worksheet.cell(row=row+2, column=col+1).value = item
-    #
-    #     workbook.save(qstats)
-    #     workbook.close()
-    #
-    # except ModuleNotFoundError as e:
-    #     s = 234
-    #     pass
-
     if question is None:
         print("Provisional evaluation")
         tabulate(table_data)
@@ -138,13 +90,29 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
 
 def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                     show_progress_bar=True,
-                    show_tol_err=False):
+                    show_tol_err=False,
+                    big_header=True):
+
     now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
     print(b + " v" + __version__)
     dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
     print("Started: " + dt_string)
@@ -157,29 +125,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     nL = 80
     t_start = time.time()
     score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
     loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
-        q_hidden = False
+        # q_hidden = False
         # q_hidden = issubclass(q.__class__, Hidden)
         if question is not None and n+1 != question:
             continue
         suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
         q_title_print = "Question %i: %s"%(n+1, qtitle)
         print(q_title_print, end="")
         q.possible = 0
@@ -188,88 +143,21 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # unittest.Te
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
-        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
-        z = 234
-        # for j, item in enumerate(q.items):
-        #     if qitem is not None and question is not None and j+1 != qitem:
-        #         continue
-        #
-        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.
-        #         # if not item.question.has_called_init_:
-        #         start = time.time()
-        #
-        #         cc = None
-        #         if show_progress_bar:
-        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )
-        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)
-        #         from unitgrade import Capturing # DON'T REMOVE THIS LINE
-        #         with eval('Capturing')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.
-        #             try:
-        #                 for q2 in q_with_outstanding_init:
-        #                     q2.init()
-        #                     q2.has_called_init_ = True
-        #
-        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.
-        #             except Exception as e:
-        #                 if not passall:
-        #                     if not silent:
-        #                         print(" ")
-        #                         print("="*30)
-        #                         print(f"When initializing question {q.title} the initialization code threw an error")
-        #                         print(e)
-        #                         print("The remaining parts of this question will likely fail.")
-        #                         print("="*30)
-        #
-        #         if show_progress_bar:
-        #             cc.terminate()
-        #             sys.stdout.flush()
-        #             print(q_title_print, end="")
-        #
-        #         q_time =np.round(  time.time()-start, 2)
-        #
-        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")
-        #         print("=" * nL)
-        #         q_with_outstanding_init = None
-        #
-        #     # item.question = q # Set the parent question instance for later reference.
-        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)
-        #
-        #     if show_progress_bar:
-        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)
-        #     else:
-        #         print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="")
-        #     hidden = issubclass(item.__class__, Hidden)
-        #     # if not hidden:
-        #     #     print(ss, end="")
-        #     # sys.stdout.flush()
-        #     start = time.time()
-        #
-        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
-        #     q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
-        #     tsecs = np.round(time.time()-start, 2)
-        #     if show_progress_bar:
-        #         cc.terminate()
-        #         sys.stdout.flush()
-        #         print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="")
-        #
-        #     if not hidden:
-        #         ss = "PASS" if current == possible else "*** FAILED"
-        #         if tsecs >= 0.1:
-        #             ss += " ("+ str(tsecs) + " seconds)"
-        #         print(ss)
-
-        # ws, possible, obtained = upack(q_)
 
         possible = res.testsRun
-        obtained = possible - len(res.errors)
+        obtained = len(res.successes)
 
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
 
         # possible = int(ws @ possible)
         # obtained = int(ws @ obtained)
         # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
 
-        obtained = w * int(obtained * 1.0 / possible )
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
         score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
         q.obtained = obtained
         q.possible = possible
@@ -368,38 +256,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -414,10 +319,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
@@ -428,8 +336,8 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    @hide\n    def test_add_hidden(self):\n        # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n        # See the output in the student directory for more information.\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
-report1_payload = '80049570000000000000007d948c055765656b31947d94288c055765656b31948c08746573745f616464944b0087944b04680368044b0187944aa1ffffff6803680486948c057469746c6594869468046803680486948c0474696d659486944700000000000000008c0474696d6594473f8756a00000000075732e'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n        self._stdout = stdout\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout if self._stdout == None else self._stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\nclass Capturing2(Capturing):\n    def __exit__(self, *args):\n        lines = self._stringio.getvalue().splitlines()\n        txt = "\\n".join(lines)\n        numbers = extract_numbers(txt)\n        self.extend(lines)\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n        self.output = txt\n        self.numbers = numbers\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n#     raise Exception("no suite")\n#     pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        # item_title = item_title.split("\\n")[0]\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        if item_title == None:\n            # For unittest framework where getDescription may return None.\n            item_title = self.getDescription(test)\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 1\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    def capture(self):\n        return Capturing2(stdout=self._stdout)\n\n    @classmethod\n    def question_title(cls):\n        """ Return the question title """\n        return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _callSetUp(self):\n        self._stdout = sys.stdout\n        import io\n        sys.stdout = io.StringIO()\n        super().setUp()\n        # print("Setting up...")\n\n    def _callTearDown(self):\n        sys.stdout = self._stdout\n        super().tearDown()\n        # print("asdfsfd")\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        self._ensure_cache_exists() # Make sure cache is there.\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n# from unitgrade2.unitgrade2 import MySuite\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    @hide\n    def test_add_hidden(self):\n        # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n        # See the output in the student directory for more information.\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n\nclass AutomaticPass(UTestCase):\n    def test_student_passed(self):\n        self.assertEqual(2,2)\n\n    @hide\n    def test_hidden_fail(self):\n        self.assertEqual(2,3)\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20), (AutomaticPass, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
+report1_payload = '80049586000000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d659486944700000000000000008c0474696d6594473f60628000000000758c0d4175746f6d6174696350617373947d94680c473f689d000000000073752e'
 name="Report3"
 
 report = source_instantiate(name, report1_source, report1_payload)
diff --git a/examples/example_docker/instructor/cs103/report3_grade.py b/examples/example_docker/instructor/cs103/report3_grade.py
index af156c0c666acd29d42881551487bd9a863447a3..06bc99f052747054bba09354cd0d4c8fa5e230c4 100644
--- a/examples/example_docker/instructor/cs103/report3_grade.py
+++ b/examples/example_docker/instructor/cs103/report3_grade.py
@@ -4,6 +4,7 @@ from tabulate import tabulate
 from datetime import datetime
 import pyfiglet
 import unittest
+# from unitgrade2.unitgrade2 import MySuite
 
 import inspect
 import os
@@ -40,8 +41,6 @@ parser.add_argument('--showcomputed',  action="store_true",  help='Show the answ
 parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
 parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
 
-
-
 def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
     args = parser.parse_args()
     if question is None and args.q is not None:
@@ -63,53 +62,6 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass
                                           show_tol_err=show_tol_err)
 
 
-    # try:  # For registering stats.
-    #     import unitgrade_private
-    #     import irlc.lectures
-    #     import xlwings
-    #     from openpyxl import Workbook
-    #     import pandas as pd
-    #     from collections import defaultdict
-    #     dd = defaultdict(lambda: [])
-    #     error_computed = []
-    #     for k1, (q, _) in enumerate(report.questions):
-    #         for k2, item in enumerate(q.items):
-    #             dd['question_index'].append(k1)
-    #             dd['item_index'].append(k2)
-    #             dd['question'].append(q.name)
-    #             dd['item'].append(item.name)
-    #             dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol)
-    #             error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed)
-    #
-    #     qstats = report.wdir + "/" + report.name + ".xlsx"
-    #
-    #     if os.path.isfile(qstats):
-    #         d_read = pd.read_excel(qstats).to_dict()
-    #     else:
-    #         d_read = dict()
-    #
-    #     for k in range(1000):
-    #         key = 'run_'+str(k)
-    #         if key in d_read:
-    #             dd[key] = list(d_read['run_0'].values())
-    #         else:
-    #             dd[key] = error_computed
-    #             break
-    #
-    #     workbook = Workbook()
-    #     worksheet = workbook.active
-    #     for col, key in enumerate(dd.keys()):
-    #         worksheet.cell(row=1, column=col+1).value = key
-    #         for row, item in enumerate(dd[key]):
-    #             worksheet.cell(row=row+2, column=col+1).value = item
-    #
-    #     workbook.save(qstats)
-    #     workbook.close()
-    #
-    # except ModuleNotFoundError as e:
-    #     s = 234
-    #     pass
-
     if question is None:
         print("Provisional evaluation")
         tabulate(table_data)
@@ -138,13 +90,29 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
 
 def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                     show_progress_bar=True,
-                    show_tol_err=False):
+                    show_tol_err=False,
+                    big_header=True):
+
     now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
     print(b + " v" + __version__)
     dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
     print("Started: " + dt_string)
@@ -157,29 +125,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     nL = 80
     t_start = time.time()
     score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
     loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
-        q_hidden = False
+        # q_hidden = False
         # q_hidden = issubclass(q.__class__, Hidden)
         if question is not None and n+1 != question:
             continue
         suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
         q_title_print = "Question %i: %s"%(n+1, qtitle)
         print(q_title_print, end="")
         q.possible = 0
@@ -188,88 +143,21 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # unittest.Te
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
-        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
-        z = 234
-        # for j, item in enumerate(q.items):
-        #     if qitem is not None and question is not None and j+1 != qitem:
-        #         continue
-        #
-        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.
-        #         # if not item.question.has_called_init_:
-        #         start = time.time()
-        #
-        #         cc = None
-        #         if show_progress_bar:
-        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )
-        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)
-        #         from unitgrade import Capturing # DON'T REMOVE THIS LINE
-        #         with eval('Capturing')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.
-        #             try:
-        #                 for q2 in q_with_outstanding_init:
-        #                     q2.init()
-        #                     q2.has_called_init_ = True
-        #
-        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.
-        #             except Exception as e:
-        #                 if not passall:
-        #                     if not silent:
-        #                         print(" ")
-        #                         print("="*30)
-        #                         print(f"When initializing question {q.title} the initialization code threw an error")
-        #                         print(e)
-        #                         print("The remaining parts of this question will likely fail.")
-        #                         print("="*30)
-        #
-        #         if show_progress_bar:
-        #             cc.terminate()
-        #             sys.stdout.flush()
-        #             print(q_title_print, end="")
-        #
-        #         q_time =np.round(  time.time()-start, 2)
-        #
-        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")
-        #         print("=" * nL)
-        #         q_with_outstanding_init = None
-        #
-        #     # item.question = q # Set the parent question instance for later reference.
-        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)
-        #
-        #     if show_progress_bar:
-        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)
-        #     else:
-        #         print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="")
-        #     hidden = issubclass(item.__class__, Hidden)
-        #     # if not hidden:
-        #     #     print(ss, end="")
-        #     # sys.stdout.flush()
-        #     start = time.time()
-        #
-        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
-        #     q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
-        #     tsecs = np.round(time.time()-start, 2)
-        #     if show_progress_bar:
-        #         cc.terminate()
-        #         sys.stdout.flush()
-        #         print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="")
-        #
-        #     if not hidden:
-        #         ss = "PASS" if current == possible else "*** FAILED"
-        #         if tsecs >= 0.1:
-        #             ss += " ("+ str(tsecs) + " seconds)"
-        #         print(ss)
-
-        # ws, possible, obtained = upack(q_)
 
         possible = res.testsRun
-        obtained = possible - len(res.errors)
+        obtained = len(res.successes)
 
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
 
         # possible = int(ws @ possible)
         # obtained = int(ws @ obtained)
         # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
 
-        obtained = w * int(obtained * 1.0 / possible )
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
         score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
         q.obtained = obtained
         q.possible = possible
@@ -368,38 +256,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -414,10 +319,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
@@ -428,8 +336,8 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
-report1_payload = '800495a9000000000000007d948c055765656b31947d94288c055765656b31948c08746573745f616464944b0087944b04680368044b0187944aa1ffffff6803680486948c057469746c6594869468046803680486948c0474696d65948694473f7198800000000068038c0f746573745f6164645f68696464656e944b0087944b046803680d869468088694680d6803680d8694680b8694473f032000000000008c0474696d6594473f4186000000000075732e'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n        self._stdout = stdout\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout if self._stdout == None else self._stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\nclass Capturing2(Capturing):\n    def __exit__(self, *args):\n        lines = self._stringio.getvalue().splitlines()\n        txt = "\\n".join(lines)\n        numbers = extract_numbers(txt)\n        self.extend(lines)\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n        self.output = txt\n        self.numbers = numbers\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n#     raise Exception("no suite")\n#     pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        # item_title = item_title.split("\\n")[0]\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        if item_title == None:\n            # For unittest framework where getDescription may return None.\n            item_title = self.getDescription(test)\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 1\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    def capture(self):\n        return Capturing2(stdout=self._stdout)\n\n    @classmethod\n    def question_title(cls):\n        """ Return the question title """\n        return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _callSetUp(self):\n        self._stdout = sys.stdout\n        import io\n        sys.stdout = io.StringIO()\n        super().setUp()\n        # print("Setting up...")\n\n    def _callTearDown(self):\n        sys.stdout = self._stdout\n        super().tearDown()\n        # print("asdfsfd")\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        self._ensure_cache_exists() # Make sure cache is there.\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n# from unitgrade2.unitgrade2 import MySuite\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n    def test_student_passed(self):\n        self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20), (AutomaticPass, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
+report1_payload = '80049525010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d65948694473f506a000000000068038c0f746573745f6164645f68696464656e948694680686947d944b004b04736803680c8694680a86944700000000000000008c0474696d6594473f926de000000000758c0d4175746f6d6174696350617373947d94288c0d4175746f6d6174696350617373948c10746573745f68696464656e5f6661696c9486948c066173736572749486947d9468158c13746573745f73747564656e745f706173736564948694681886947d946815681b86948c0474696d659486944700000000000000006812473f9894100000000075752e'
 name="Report3"
 
 report = source_instantiate(name, report1_source, report1_payload)
diff --git a/examples/example_docker/instructor/cs103/unitgrade/AutomaticPass.pkl b/examples/example_docker/instructor/cs103/unitgrade/AutomaticPass.pkl
new file mode 100644
index 0000000000000000000000000000000000000000..2a722e2b9c8264b76eca73fec2c7dd84eb0e02d3
Binary files /dev/null and b/examples/example_docker/instructor/cs103/unitgrade/AutomaticPass.pkl differ
diff --git a/examples/example_docker/instructor/cs103/unitgrade/Week1.pkl b/examples/example_docker/instructor/cs103/unitgrade/Week1.pkl
index fc298168d395c432420ad99533ade24705a6e589..fe27b785553c86fe6975853b9990eed439d2d5bc 100644
Binary files a/examples/example_docker/instructor/cs103/unitgrade/Week1.pkl and b/examples/example_docker/instructor/cs103/unitgrade/Week1.pkl differ
diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/Report3_handin_5_of_30.token b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/Report3_handin_5_of_30.token
new file mode 100644
index 0000000000000000000000000000000000000000..675c59014e1063147604cc9ab25520eb3d1bbb5e
Binary files /dev/null and b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/Report3_handin_5_of_30.token differ
diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/homework1.cpython-38.pyc b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/homework1.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6403436716a672ff6c51a7026fc0edf847aa3213
Binary files /dev/null and b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/homework1.cpython-38.pyc differ
diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/report3_complete_grade.cpython-38.pyc b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/report3_complete_grade.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8bcaebe92988bf2a57647836e6aabc65f30fdadc
Binary files /dev/null and b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/report3_complete_grade.cpython-38.pyc differ
diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/deploy.py b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/deploy.py
deleted file mode 100644
index 24299492a33d4fd290394b344abd9fc96ef2d149..0000000000000000000000000000000000000000
--- a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/deploy.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import inspect
-from cs103.report3_complete import Report3
-from unitgrade_private2.hidden_create_files import setup_grade_file_report
-from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet
-from unitgrade_private2.deployment import remove_hidden_methods
-from unitgrade_private2.docker_helpers import docker_run_token_file
-import shutil
-import os
-import glob
-import pickle
-from snipper.snip_dir import snip_dir
-
-def deploy_student_files():
-    setup_grade_file_report(Report3, minify=False, obfuscate=False, execute=False)
-    Report3.reset()
-
-    fout, ReportWithoutHidden = remove_hidden_methods(Report3, outfile="report3.py")
-    setup_grade_file_report(ReportWithoutHidden, minify=False, obfuscate=False, execute=False)
-    sdir = "../../students/cs103"
-    snip_dir(source_dir="../cs103", dest_dir=sdir, clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py', 'report3_complete*.py'])
-    return sdir
-
-def run_student_code_on_docker(Dockerfile, student_token_file):
-    token = docker_run_token_file(Dockerfile_location=Dockerfile,
-                          host_tmp_dir=os.path.dirname(Dockerfile) + "/tmp",
-                          student_token_file=student_token_file,
-                          instructor_grade_script="report3_complete_grade.py")
-    with open(token, 'rb') as f:
-        results = pickle.load(f)
-    return results
-
-if __name__ == "__main__":
-    # Step 1: Deploy the students files and return the directory they were written to
-    student_directory = deploy_student_files()
-
-    # Step 2: Simulate that the student run their report script and generate a .token file.
-    os.system("cd ../../students && python -m cs103.report3_grade")
-    student_token_file = glob.glob(student_directory + "/*.token")[0]
-
-
-    # Step 3: Compile the Docker image (obviously you will only do this once; add your packages to requirements.txt).
-    Dockerfile = os.path.dirname(__file__) + "/../unitgrade-docker/Dockerfile"
-    os.system("cd ../unitgrade-docker && docker build --tag unitgrade-docker .")
-
-    # Step 4: Test the students .token file and get the results-token-file. Compare the contents with the students_token_file:
-    checked_token = run_student_code_on_docker(Dockerfile, student_token_file)
-
-    # Let's quickly compare the students score to what we got (the dictionary contains all relevant information including code).
-    with open(student_token_file, 'rb') as f:
-        results = pickle.load(f)
-    print("Student's score was:", results['total'])
-    print("My independent evaluation of the students score was", checked_token['total'])
diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/homework1.py b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/homework1.py
index 286b79fbac40c2d02b5874c0a73fc387835ce2b3..3543f1ba46b63eec3a2c2e007ee998660c7136c6 100644
--- a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/homework1.py
+++ b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/homework1.py
@@ -1,14 +1,19 @@
-def reverse_list(mylist): #!f
+"""
+Example student code. This file is automatically generated from the files in the instructor-directory
+"""
+def reverse_list(mylist): 
     """
     Given a list 'mylist' returns a list consisting of the same elements in reverse order. E.g.
     reverse_list([1,2,3]) should return [3,2,1] (as a list).
     """
-    return list(reversed(mylist))
+    # TODO: 1 lines missing.
+    raise NotImplementedError("Implement function body")
 
-def add(a,b): #!f
+def add(a,b): 
     """ Given two numbers `a` and `b` this function should simply return their sum:
     > add(a,b) = a+b """
-    return a+b
+    # TODO: 1 lines missing.
+    raise NotImplementedError("Implement function body")
 
 if __name__ == "__main__":
     # Problem 1: Write a function which add two numbers
diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3.py b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3.py
index c9a23ec4c93481016205b12cf9ec7546e83f376d..c97b5a4117c254a17a5fed6787a485f4e69e0ebf 100644
--- a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3.py
+++ b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3.py
@@ -1,3 +1,6 @@
+"""
+Example student code. This file is automatically generated from the files in the instructor-directory
+"""
 from unitgrade2.unitgrade2 import UTestCase, Report, hide
 from unitgrade2.unitgrade_helpers2 import evaluate_report_student
 
@@ -9,11 +12,16 @@ class Week1(UTestCase):
         self.assertEqualC(add(-100, 5))
 
 
+class AutomaticPass(UTestCase):
+    def test_student_passed(self):
+        self.assertEqual(2,2)
+
+
 import cs103
 class Report3(Report):
     title = "CS 101 Report 3"
-    questions = [(Week1, 20)]  # Include a single question for 10 credits.
+    questions = [(Week1, 20), (AutomaticPass, 10)]  # Include a single question for 10 credits.
     pack_imports = [cs103]
 
 if __name__ == "__main__":
-    evaluate_report_student(Report3())
\ No newline at end of file
+    evaluate_report_student(Report3())
diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete.py b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete.py
deleted file mode 100644
index 37c50b9ed9ee5ee87e58df267a272893284bcf0c..0000000000000000000000000000000000000000
--- a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from unitgrade2.unitgrade2 import UTestCase, Report, hide
-from unitgrade2.unitgrade_helpers2 import evaluate_report_student
-
-class Week1(UTestCase):
-    """ The first question for week 1. """
-    def test_add(self):
-        from cs103.homework1 import add
-        self.assertEqualC(add(2,2))
-        self.assertEqualC(add(-100, 5))
-
-    @hide
-    def test_add_hidden(self):
-        # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.
-        # See the output in the student directory for more information.
-        from cs103.homework1 import add
-        self.assertEqualC(add(2,2))
-
-import cs103
-class Report3(Report):
-    title = "CS 101 Report 3"
-    questions = [(Week1, 20)]  # Include a single question for 10 credits.
-    pack_imports = [cs103]
-
-if __name__ == "__main__":
-    evaluate_report_student(Report3())
diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete_grade.py b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete_grade.py
index 9dfbd03283e1bae5cb87d6d189cd8abfb87e97da..b053e48dd7d2feeb7bc45cebec0c175ec41590a6 100644
--- a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete_grade.py
+++ b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete_grade.py
@@ -4,6 +4,7 @@ from tabulate import tabulate
 from datetime import datetime
 import pyfiglet
 import unittest
+# from unitgrade2.unitgrade2 import MySuite
 
 import inspect
 import os
@@ -40,8 +41,6 @@ parser.add_argument('--showcomputed',  action="store_true",  help='Show the answ
 parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
 parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
 
-
-
 def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
     args = parser.parse_args()
     if question is None and args.q is not None:
@@ -63,53 +62,6 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass
                                           show_tol_err=show_tol_err)
 
 
-    # try:  # For registering stats.
-    #     import unitgrade_private
-    #     import irlc.lectures
-    #     import xlwings
-    #     from openpyxl import Workbook
-    #     import pandas as pd
-    #     from collections import defaultdict
-    #     dd = defaultdict(lambda: [])
-    #     error_computed = []
-    #     for k1, (q, _) in enumerate(report.questions):
-    #         for k2, item in enumerate(q.items):
-    #             dd['question_index'].append(k1)
-    #             dd['item_index'].append(k2)
-    #             dd['question'].append(q.name)
-    #             dd['item'].append(item.name)
-    #             dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol)
-    #             error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed)
-    #
-    #     qstats = report.wdir + "/" + report.name + ".xlsx"
-    #
-    #     if os.path.isfile(qstats):
-    #         d_read = pd.read_excel(qstats).to_dict()
-    #     else:
-    #         d_read = dict()
-    #
-    #     for k in range(1000):
-    #         key = 'run_'+str(k)
-    #         if key in d_read:
-    #             dd[key] = list(d_read['run_0'].values())
-    #         else:
-    #             dd[key] = error_computed
-    #             break
-    #
-    #     workbook = Workbook()
-    #     worksheet = workbook.active
-    #     for col, key in enumerate(dd.keys()):
-    #         worksheet.cell(row=1, column=col+1).value = key
-    #         for row, item in enumerate(dd[key]):
-    #             worksheet.cell(row=row+2, column=col+1).value = item
-    #
-    #     workbook.save(qstats)
-    #     workbook.close()
-    #
-    # except ModuleNotFoundError as e:
-    #     s = 234
-    #     pass
-
     if question is None:
         print("Provisional evaluation")
         tabulate(table_data)
@@ -138,13 +90,29 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
 
 def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                     show_progress_bar=True,
-                    show_tol_err=False):
+                    show_tol_err=False,
+                    big_header=True):
+
     now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
     print(b + " v" + __version__)
     dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
     print("Started: " + dt_string)
@@ -157,29 +125,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     nL = 80
     t_start = time.time()
     score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
     loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
-        q_hidden = False
+        # q_hidden = False
         # q_hidden = issubclass(q.__class__, Hidden)
         if question is not None and n+1 != question:
             continue
         suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
         q_title_print = "Question %i: %s"%(n+1, qtitle)
         print(q_title_print, end="")
         q.possible = 0
@@ -188,88 +143,21 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # unittest.Te
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
-        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
-        z = 234
-        # for j, item in enumerate(q.items):
-        #     if qitem is not None and question is not None and j+1 != qitem:
-        #         continue
-        #
-        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.
-        #         # if not item.question.has_called_init_:
-        #         start = time.time()
-        #
-        #         cc = None
-        #         if show_progress_bar:
-        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )
-        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)
-        #         from unitgrade import Capturing # DON'T REMOVE THIS LINE
-        #         with eval('Capturing')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.
-        #             try:
-        #                 for q2 in q_with_outstanding_init:
-        #                     q2.init()
-        #                     q2.has_called_init_ = True
-        #
-        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.
-        #             except Exception as e:
-        #                 if not passall:
-        #                     if not silent:
-        #                         print(" ")
-        #                         print("="*30)
-        #                         print(f"When initializing question {q.title} the initialization code threw an error")
-        #                         print(e)
-        #                         print("The remaining parts of this question will likely fail.")
-        #                         print("="*30)
-        #
-        #         if show_progress_bar:
-        #             cc.terminate()
-        #             sys.stdout.flush()
-        #             print(q_title_print, end="")
-        #
-        #         q_time =np.round(  time.time()-start, 2)
-        #
-        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")
-        #         print("=" * nL)
-        #         q_with_outstanding_init = None
-        #
-        #     # item.question = q # Set the parent question instance for later reference.
-        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)
-        #
-        #     if show_progress_bar:
-        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)
-        #     else:
-        #         print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="")
-        #     hidden = issubclass(item.__class__, Hidden)
-        #     # if not hidden:
-        #     #     print(ss, end="")
-        #     # sys.stdout.flush()
-        #     start = time.time()
-        #
-        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
-        #     q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
-        #     tsecs = np.round(time.time()-start, 2)
-        #     if show_progress_bar:
-        #         cc.terminate()
-        #         sys.stdout.flush()
-        #         print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="")
-        #
-        #     if not hidden:
-        #         ss = "PASS" if current == possible else "*** FAILED"
-        #         if tsecs >= 0.1:
-        #             ss += " ("+ str(tsecs) + " seconds)"
-        #         print(ss)
-
-        # ws, possible, obtained = upack(q_)
 
         possible = res.testsRun
-        obtained = possible - len(res.errors)
+        obtained = len(res.successes)
 
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
 
         # possible = int(ws @ possible)
         # obtained = int(ws @ obtained)
         # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
 
-        obtained = w * int(obtained * 1.0 / possible )
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
         score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
         q.obtained = obtained
         q.possible = possible
@@ -368,38 +256,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -414,10 +319,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
@@ -428,8 +336,8 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    @hide\n    def test_add_hidden(self):\n        # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n        # See the output in the student directory for more information.\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
-report1_payload = '80049570000000000000007d948c055765656b31947d94288c055765656b31948c08746573745f616464944b0087944b04680368044b0187944aa1ffffff6803680486948c057469746c6594869468046803680486948c0474696d659486944700000000000000008c0474696d6594473f8756a00000000075732e'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n        self._stdout = stdout\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout if self._stdout == None else self._stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\nclass Capturing2(Capturing):\n    def __exit__(self, *args):\n        lines = self._stringio.getvalue().splitlines()\n        txt = "\\n".join(lines)\n        numbers = extract_numbers(txt)\n        self.extend(lines)\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n        self.output = txt\n        self.numbers = numbers\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n#     raise Exception("no suite")\n#     pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        # item_title = item_title.split("\\n")[0]\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        if item_title == None:\n            # For unittest framework where getDescription may return None.\n            item_title = self.getDescription(test)\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 1\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    def capture(self):\n        return Capturing2(stdout=self._stdout)\n\n    @classmethod\n    def question_title(cls):\n        """ Return the question title """\n        return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _callSetUp(self):\n        self._stdout = sys.stdout\n        import io\n        sys.stdout = io.StringIO()\n        super().setUp()\n        # print("Setting up...")\n\n    def _callTearDown(self):\n        sys.stdout = self._stdout\n        super().tearDown()\n        # print("asdfsfd")\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        self._ensure_cache_exists() # Make sure cache is there.\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n# from unitgrade2.unitgrade2 import MySuite\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    @hide\n    def test_add_hidden(self):\n        # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n        # See the output in the student directory for more information.\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n\nclass AutomaticPass(UTestCase):\n    def test_student_passed(self):\n        self.assertEqual(2,2)\n\n    @hide\n    def test_hidden_fail(self):\n        self.assertEqual(2,3)\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20), (AutomaticPass, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
+report1_payload = '80049586000000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d659486944700000000000000008c0474696d6594473f60628000000000758c0d4175746f6d6174696350617373947d94680c473f689d000000000073752e'
 name="Report3"
 
 report = source_instantiate(name, report1_source, report1_payload)
diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_grade.py b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_grade.py
index af156c0c666acd29d42881551487bd9a863447a3..ecff9f7ce0634562a106d021cda68177c65c12fb 100644
--- a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_grade.py
+++ b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_grade.py
@@ -1,9 +1,12 @@
-
+"""
+Example student code. This file is automatically generated from the files in the instructor-directory
+"""
 import numpy as np
 from tabulate import tabulate
 from datetime import datetime
 import pyfiglet
 import unittest
+# from unitgrade2.unitgrade2 import MySuite
 
 import inspect
 import os
@@ -40,8 +43,6 @@ parser.add_argument('--showcomputed',  action="store_true",  help='Show the answ
 parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
 parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
 
-
-
 def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
     args = parser.parse_args()
     if question is None and args.q is not None:
@@ -63,53 +64,6 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass
                                           show_tol_err=show_tol_err)
 
 
-    # try:  # For registering stats.
-    #     import unitgrade_private
-    #     import irlc.lectures
-    #     import xlwings
-    #     from openpyxl import Workbook
-    #     import pandas as pd
-    #     from collections import defaultdict
-    #     dd = defaultdict(lambda: [])
-    #     error_computed = []
-    #     for k1, (q, _) in enumerate(report.questions):
-    #         for k2, item in enumerate(q.items):
-    #             dd['question_index'].append(k1)
-    #             dd['item_index'].append(k2)
-    #             dd['question'].append(q.name)
-    #             dd['item'].append(item.name)
-    #             dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol)
-    #             error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed)
-    #
-    #     qstats = report.wdir + "/" + report.name + ".xlsx"
-    #
-    #     if os.path.isfile(qstats):
-    #         d_read = pd.read_excel(qstats).to_dict()
-    #     else:
-    #         d_read = dict()
-    #
-    #     for k in range(1000):
-    #         key = 'run_'+str(k)
-    #         if key in d_read:
-    #             dd[key] = list(d_read['run_0'].values())
-    #         else:
-    #             dd[key] = error_computed
-    #             break
-    #
-    #     workbook = Workbook()
-    #     worksheet = workbook.active
-    #     for col, key in enumerate(dd.keys()):
-    #         worksheet.cell(row=1, column=col+1).value = key
-    #         for row, item in enumerate(dd[key]):
-    #             worksheet.cell(row=row+2, column=col+1).value = item
-    #
-    #     workbook.save(qstats)
-    #     workbook.close()
-    #
-    # except ModuleNotFoundError as e:
-    #     s = 234
-    #     pass
-
     if question is None:
         print("Provisional evaluation")
         tabulate(table_data)
@@ -138,13 +92,29 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
 
 def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                     show_progress_bar=True,
-                    show_tol_err=False):
+                    show_tol_err=False,
+                    big_header=True):
+
     now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
     print(b + " v" + __version__)
     dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
     print("Started: " + dt_string)
@@ -157,29 +127,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     nL = 80
     t_start = time.time()
     score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
     loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
-        q_hidden = False
+        # q_hidden = False
         # q_hidden = issubclass(q.__class__, Hidden)
         if question is not None and n+1 != question:
             continue
         suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
         q_title_print = "Question %i: %s"%(n+1, qtitle)
         print(q_title_print, end="")
         q.possible = 0
@@ -188,88 +145,21 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # unittest.Te
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
-        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
-        z = 234
-        # for j, item in enumerate(q.items):
-        #     if qitem is not None and question is not None and j+1 != qitem:
-        #         continue
-        #
-        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.
-        #         # if not item.question.has_called_init_:
-        #         start = time.time()
-        #
-        #         cc = None
-        #         if show_progress_bar:
-        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )
-        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)
-        #         from unitgrade import Capturing # DON'T REMOVE THIS LINE
-        #         with eval('Capturing')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.
-        #             try:
-        #                 for q2 in q_with_outstanding_init:
-        #                     q2.init()
-        #                     q2.has_called_init_ = True
-        #
-        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.
-        #             except Exception as e:
-        #                 if not passall:
-        #                     if not silent:
-        #                         print(" ")
-        #                         print("="*30)
-        #                         print(f"When initializing question {q.title} the initialization code threw an error")
-        #                         print(e)
-        #                         print("The remaining parts of this question will likely fail.")
-        #                         print("="*30)
-        #
-        #         if show_progress_bar:
-        #             cc.terminate()
-        #             sys.stdout.flush()
-        #             print(q_title_print, end="")
-        #
-        #         q_time =np.round(  time.time()-start, 2)
-        #
-        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")
-        #         print("=" * nL)
-        #         q_with_outstanding_init = None
-        #
-        #     # item.question = q # Set the parent question instance for later reference.
-        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)
-        #
-        #     if show_progress_bar:
-        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)
-        #     else:
-        #         print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="")
-        #     hidden = issubclass(item.__class__, Hidden)
-        #     # if not hidden:
-        #     #     print(ss, end="")
-        #     # sys.stdout.flush()
-        #     start = time.time()
-        #
-        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
-        #     q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
-        #     tsecs = np.round(time.time()-start, 2)
-        #     if show_progress_bar:
-        #         cc.terminate()
-        #         sys.stdout.flush()
-        #         print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="")
-        #
-        #     if not hidden:
-        #         ss = "PASS" if current == possible else "*** FAILED"
-        #         if tsecs >= 0.1:
-        #             ss += " ("+ str(tsecs) + " seconds)"
-        #         print(ss)
-
-        # ws, possible, obtained = upack(q_)
 
         possible = res.testsRun
-        obtained = possible - len(res.errors)
+        obtained = len(res.successes)
 
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
 
         # possible = int(ws @ possible)
         # obtained = int(ws @ obtained)
         # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
 
-        obtained = w * int(obtained * 1.0 / possible )
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
         score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
         q.obtained = obtained
         q.possible = possible
@@ -368,38 +258,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -414,10 +321,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
@@ -428,10 +338,10 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
-report1_payload = '800495a9000000000000007d948c055765656b31947d94288c055765656b31948c08746573745f616464944b0087944b04680368044b0187944aa1ffffff6803680486948c057469746c6594869468046803680486948c0474696d65948694473f7198800000000068038c0f746573745f6164645f68696464656e944b0087944b046803680d869468088694680d6803680d8694680b8694473f032000000000008c0474696d6594473f4186000000000075732e'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n        self._stdout = stdout\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout if self._stdout == None else self._stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\nclass Capturing2(Capturing):\n    def __exit__(self, *args):\n        lines = self._stringio.getvalue().splitlines()\n        txt = "\\n".join(lines)\n        numbers = extract_numbers(txt)\n        self.extend(lines)\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n        self.output = txt\n        self.numbers = numbers\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n#     raise Exception("no suite")\n#     pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        # item_title = item_title.split("\\n")[0]\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        if item_title == None:\n            # For unittest framework where getDescription may return None.\n            item_title = self.getDescription(test)\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 1\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    def capture(self):\n        return Capturing2(stdout=self._stdout)\n\n    @classmethod\n    def question_title(cls):\n        """ Return the question title """\n        return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _callSetUp(self):\n        self._stdout = sys.stdout\n        import io\n        sys.stdout = io.StringIO()\n        super().setUp()\n        # print("Setting up...")\n\n    def _callTearDown(self):\n        sys.stdout = self._stdout\n        super().tearDown()\n        # print("asdfsfd")\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        self._ensure_cache_exists() # Make sure cache is there.\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n# from unitgrade2.unitgrade2 import MySuite\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n    def test_student_passed(self):\n        self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20), (AutomaticPass, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
+report1_payload = '80049525010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d65948694473f506a000000000068038c0f746573745f6164645f68696464656e948694680686947d944b004b04736803680c8694680a86944700000000000000008c0474696d6594473f926de000000000758c0d4175746f6d6174696350617373947d94288c0d4175746f6d6174696350617373948c10746573745f68696464656e5f6661696c9486948c066173736572749486947d9468158c13746573745f73747564656e745f706173736564948694681886947d946815681b86948c0474696d659486944700000000000000006812473f9894100000000075752e'
 name="Report3"
 
 report = source_instantiate(name, report1_source, report1_payload)
 output_dir = os.path.dirname(__file__)
-gather_upload_to_campusnet(report, output_dir)
\ No newline at end of file
+gather_upload_to_campusnet(report, output_dir)
diff --git a/examples/example_docker/students/cs103/Report3_handin_10_of_30.token b/examples/example_docker/students/cs103/Report3_handin_10_of_30.token
new file mode 100644
index 0000000000000000000000000000000000000000..7231343c1882f4366cfe40348680c8d947889798
Binary files /dev/null and b/examples/example_docker/students/cs103/Report3_handin_10_of_30.token differ
diff --git a/examples/example_docker/students/cs103/Report3_handin_20_of_20.token b/examples/example_docker/students/cs103/Report3_handin_20_of_20.token
deleted file mode 100644
index e6e91d99f0946a63ec47434ea1be99bbe2d634b3..0000000000000000000000000000000000000000
Binary files a/examples/example_docker/students/cs103/Report3_handin_20_of_20.token and /dev/null differ
diff --git a/examples/example_docker/students/cs103/__pycache__/homework1.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/homework1.cpython-38.pyc
index 301c79fcc3244747ff167131505a756d0f54b54e..5c552a81fc42feaf0654da24d93270b73dc806af 100644
Binary files a/examples/example_docker/students/cs103/__pycache__/homework1.cpython-38.pyc and b/examples/example_docker/students/cs103/__pycache__/homework1.cpython-38.pyc differ
diff --git a/examples/example_docker/students/cs103/__pycache__/report3.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/report3.cpython-38.pyc
index e5e928b3cf8c95f9ee7e0283457b713591a406e4..da19a02cc1c508d3eaafcf35b0b34e9d58501d7e 100644
Binary files a/examples/example_docker/students/cs103/__pycache__/report3.cpython-38.pyc and b/examples/example_docker/students/cs103/__pycache__/report3.cpython-38.pyc differ
diff --git a/examples/example_docker/students/cs103/__pycache__/report3_complete.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/report3_complete.cpython-38.pyc
index c2a10cbaa71b99e5be06729a818f59fbbb70813a..921af5c7208e7aa4d953fa1e4551029aeb05c182 100644
Binary files a/examples/example_docker/students/cs103/__pycache__/report3_complete.cpython-38.pyc and b/examples/example_docker/students/cs103/__pycache__/report3_complete.cpython-38.pyc differ
diff --git a/examples/example_docker/students/cs103/__pycache__/report3_complete_grade.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/report3_complete_grade.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..29600ad002bee7bcffb89098df90876e25fd7158
Binary files /dev/null and b/examples/example_docker/students/cs103/__pycache__/report3_complete_grade.cpython-38.pyc differ
diff --git a/examples/example_docker/students/cs103/__pycache__/report3_grade.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/report3_grade.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7433fca776754f5b6910141e9adc509cbf13a6f5
Binary files /dev/null and b/examples/example_docker/students/cs103/__pycache__/report3_grade.cpython-38.pyc differ
diff --git a/examples/example_docker/students/cs103/deploy.py b/examples/example_docker/students/cs103/deploy.py
deleted file mode 100644
index 24299492a33d4fd290394b344abd9fc96ef2d149..0000000000000000000000000000000000000000
--- a/examples/example_docker/students/cs103/deploy.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import inspect
-from cs103.report3_complete import Report3
-from unitgrade_private2.hidden_create_files import setup_grade_file_report
-from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet
-from unitgrade_private2.deployment import remove_hidden_methods
-from unitgrade_private2.docker_helpers import docker_run_token_file
-import shutil
-import os
-import glob
-import pickle
-from snipper.snip_dir import snip_dir
-
-def deploy_student_files():
-    setup_grade_file_report(Report3, minify=False, obfuscate=False, execute=False)
-    Report3.reset()
-
-    fout, ReportWithoutHidden = remove_hidden_methods(Report3, outfile="report3.py")
-    setup_grade_file_report(ReportWithoutHidden, minify=False, obfuscate=False, execute=False)
-    sdir = "../../students/cs103"
-    snip_dir(source_dir="../cs103", dest_dir=sdir, clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py', 'report3_complete*.py'])
-    return sdir
-
-def run_student_code_on_docker(Dockerfile, student_token_file):
-    token = docker_run_token_file(Dockerfile_location=Dockerfile,
-                          host_tmp_dir=os.path.dirname(Dockerfile) + "/tmp",
-                          student_token_file=student_token_file,
-                          instructor_grade_script="report3_complete_grade.py")
-    with open(token, 'rb') as f:
-        results = pickle.load(f)
-    return results
-
-if __name__ == "__main__":
-    # Step 1: Deploy the students files and return the directory they were written to
-    student_directory = deploy_student_files()
-
-    # Step 2: Simulate that the student run their report script and generate a .token file.
-    os.system("cd ../../students && python -m cs103.report3_grade")
-    student_token_file = glob.glob(student_directory + "/*.token")[0]
-
-
-    # Step 3: Compile the Docker image (obviously you will only do this once; add your packages to requirements.txt).
-    Dockerfile = os.path.dirname(__file__) + "/../unitgrade-docker/Dockerfile"
-    os.system("cd ../unitgrade-docker && docker build --tag unitgrade-docker .")
-
-    # Step 4: Test the students .token file and get the results-token-file. Compare the contents with the students_token_file:
-    checked_token = run_student_code_on_docker(Dockerfile, student_token_file)
-
-    # Let's quickly compare the students score to what we got (the dictionary contains all relevant information including code).
-    with open(student_token_file, 'rb') as f:
-        results = pickle.load(f)
-    print("Student's score was:", results['total'])
-    print("My independent evaluation of the students score was", checked_token['total'])
diff --git a/examples/example_docker/students/cs103/homework1.py b/examples/example_docker/students/cs103/homework1.py
index 286b79fbac40c2d02b5874c0a73fc387835ce2b3..3543f1ba46b63eec3a2c2e007ee998660c7136c6 100644
--- a/examples/example_docker/students/cs103/homework1.py
+++ b/examples/example_docker/students/cs103/homework1.py
@@ -1,14 +1,19 @@
-def reverse_list(mylist): #!f
+"""
+Example student code. This file is automatically generated from the files in the instructor-directory
+"""
+def reverse_list(mylist): 
     """
     Given a list 'mylist' returns a list consisting of the same elements in reverse order. E.g.
     reverse_list([1,2,3]) should return [3,2,1] (as a list).
     """
-    return list(reversed(mylist))
+    # TODO: 1 lines missing.
+    raise NotImplementedError("Implement function body")
 
-def add(a,b): #!f
+def add(a,b): 
     """ Given two numbers `a` and `b` this function should simply return their sum:
     > add(a,b) = a+b """
-    return a+b
+    # TODO: 1 lines missing.
+    raise NotImplementedError("Implement function body")
 
 if __name__ == "__main__":
     # Problem 1: Write a function which add two numbers
diff --git a/examples/example_docker/students/cs103/report3.py b/examples/example_docker/students/cs103/report3.py
index c9a23ec4c93481016205b12cf9ec7546e83f376d..c97b5a4117c254a17a5fed6787a485f4e69e0ebf 100644
--- a/examples/example_docker/students/cs103/report3.py
+++ b/examples/example_docker/students/cs103/report3.py
@@ -1,3 +1,6 @@
+"""
+Example student code. This file is automatically generated from the files in the instructor-directory
+"""
 from unitgrade2.unitgrade2 import UTestCase, Report, hide
 from unitgrade2.unitgrade_helpers2 import evaluate_report_student
 
@@ -9,11 +12,16 @@ class Week1(UTestCase):
         self.assertEqualC(add(-100, 5))
 
 
+class AutomaticPass(UTestCase):
+    def test_student_passed(self):
+        self.assertEqual(2,2)
+
+
 import cs103
 class Report3(Report):
     title = "CS 101 Report 3"
-    questions = [(Week1, 20)]  # Include a single question for 10 credits.
+    questions = [(Week1, 20), (AutomaticPass, 10)]  # Include a single question for 10 credits.
     pack_imports = [cs103]
 
 if __name__ == "__main__":
-    evaluate_report_student(Report3())
\ No newline at end of file
+    evaluate_report_student(Report3())
diff --git a/examples/example_docker/students/cs103/report3_complete.py b/examples/example_docker/students/cs103/report3_complete.py
deleted file mode 100644
index 37c50b9ed9ee5ee87e58df267a272893284bcf0c..0000000000000000000000000000000000000000
--- a/examples/example_docker/students/cs103/report3_complete.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from unitgrade2.unitgrade2 import UTestCase, Report, hide
-from unitgrade2.unitgrade_helpers2 import evaluate_report_student
-
-class Week1(UTestCase):
-    """ The first question for week 1. """
-    def test_add(self):
-        from cs103.homework1 import add
-        self.assertEqualC(add(2,2))
-        self.assertEqualC(add(-100, 5))
-
-    @hide
-    def test_add_hidden(self):
-        # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.
-        # See the output in the student directory for more information.
-        from cs103.homework1 import add
-        self.assertEqualC(add(2,2))
-
-import cs103
-class Report3(Report):
-    title = "CS 101 Report 3"
-    questions = [(Week1, 20)]  # Include a single question for 10 credits.
-    pack_imports = [cs103]
-
-if __name__ == "__main__":
-    evaluate_report_student(Report3())
diff --git a/examples/example_docker/students/cs103/report3_complete_grade.py b/examples/example_docker/students/cs103/report3_complete_grade.py
deleted file mode 100644
index 9dfbd03283e1bae5cb87d6d189cd8abfb87e97da..0000000000000000000000000000000000000000
--- a/examples/example_docker/students/cs103/report3_complete_grade.py
+++ /dev/null
@@ -1,437 +0,0 @@
-
-import numpy as np
-from tabulate import tabulate
-from datetime import datetime
-import pyfiglet
-import unittest
-
-import inspect
-import os
-import argparse
-import sys
-import time
-import threading # don't import Thread bc. of minify issue.
-import tqdm # don't do from tqdm import tqdm because of minify-issue
-
-parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: 
-To run all tests in a report: 
-
-> python assignment1_dp.py
-
-To run only question 2 or question 2.1
-
-> python assignment1_dp.py -q 2
-> python assignment1_dp.py -q 2.1
-
-Note this scripts does not grade your report. To grade your report, use:
-
-> python report1_grade.py
-
-Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
-For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
-
-> python -m course_package.report1
-
-see https://docs.python.org/3.9/using/cmdline.html
-""", formatter_class=argparse.RawTextHelpFormatter)
-parser.add_argument('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)')
-parser.add_argument('--showexpected',  action="store_true",  help='Show the expected/desired result')
-parser.add_argument('--showcomputed',  action="store_true",  help='Show the answer your code computes')
-parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
-parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
-
-
-
-def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
-    args = parser.parse_args()
-    if question is None and args.q is not None:
-        question = args.q
-        if "." in question:
-            question, qitem = [int(v) for v in question.split(".")]
-        else:
-            question = int(question)
-
-    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:
-        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")
-
-    if unmute is None:
-        unmute = args.unmute
-    if passall is None:
-        passall = args.passall
-
-    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,
-                                          show_tol_err=show_tol_err)
-
-
-    # try:  # For registering stats.
-    #     import unitgrade_private
-    #     import irlc.lectures
-    #     import xlwings
-    #     from openpyxl import Workbook
-    #     import pandas as pd
-    #     from collections import defaultdict
-    #     dd = defaultdict(lambda: [])
-    #     error_computed = []
-    #     for k1, (q, _) in enumerate(report.questions):
-    #         for k2, item in enumerate(q.items):
-    #             dd['question_index'].append(k1)
-    #             dd['item_index'].append(k2)
-    #             dd['question'].append(q.name)
-    #             dd['item'].append(item.name)
-    #             dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol)
-    #             error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed)
-    #
-    #     qstats = report.wdir + "/" + report.name + ".xlsx"
-    #
-    #     if os.path.isfile(qstats):
-    #         d_read = pd.read_excel(qstats).to_dict()
-    #     else:
-    #         d_read = dict()
-    #
-    #     for k in range(1000):
-    #         key = 'run_'+str(k)
-    #         if key in d_read:
-    #             dd[key] = list(d_read['run_0'].values())
-    #         else:
-    #             dd[key] = error_computed
-    #             break
-    #
-    #     workbook = Workbook()
-    #     worksheet = workbook.active
-    #     for col, key in enumerate(dd.keys()):
-    #         worksheet.cell(row=1, column=col+1).value = key
-    #         for row, item in enumerate(dd[key]):
-    #             worksheet.cell(row=row+2, column=col+1).value = item
-    #
-    #     workbook.save(qstats)
-    #     workbook.close()
-    #
-    # except ModuleNotFoundError as e:
-    #     s = 234
-    #     pass
-
-    if question is None:
-        print("Provisional evaluation")
-        tabulate(table_data)
-        table = table_data
-        print(tabulate(table))
-        print(" ")
-
-    fr = inspect.getouterframes(inspect.currentframe())[1].filename
-    gfile = os.path.basename(fr)[:-3] + "_grade.py"
-    if os.path.exists(gfile):
-        print("Note your results have not yet been registered. \nTo register your results, please run the file:")
-        print(">>>", gfile)
-        print("In the same manner as you ran this file.")
-
-
-    return results
-
-
-def upack(q):
-    # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()])
-    h =[(i['w'], i['possible'], i['obtained']) for i in q.values()]
-    h = np.asarray(h)
-    return h[:,0], h[:,1], h[:,2],
-
-class UnitgradeTextRunner(unittest.TextTestRunner):
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-
-def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
-                    show_progress_bar=True,
-                    show_tol_err=False):
-    now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
-    print(b + " v" + __version__)
-    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
-    print("Started: " + dt_string)
-    s = report.title
-    if hasattr(report, "version") and report.version is not None:
-        s += " version " + report.version
-    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")
-    # print(f"Loaded answers from: ", report.computed_answers_file, "\n")
-    table_data = []
-    nL = 80
-    t_start = time.time()
-    score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
-    loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
-
-    for n, (q, w) in enumerate(report.questions):
-        # q = q()
-        q_hidden = False
-        # q_hidden = issubclass(q.__class__, Hidden)
-        if question is not None and n+1 != question:
-            continue
-        suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
-        q_title_print = "Question %i: %s"%(n+1, qtitle)
-        print(q_title_print, end="")
-        q.possible = 0
-        q.obtained = 0
-        q_ = {} # Gather score in this class.
-        # unittest.Te
-        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
-        UTextResult.q_title_print = q_title_print # Hacky
-        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
-        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
-        z = 234
-        # for j, item in enumerate(q.items):
-        #     if qitem is not None and question is not None and j+1 != qitem:
-        #         continue
-        #
-        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.
-        #         # if not item.question.has_called_init_:
-        #         start = time.time()
-        #
-        #         cc = None
-        #         if show_progress_bar:
-        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )
-        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)
-        #         from unitgrade import Capturing # DON'T REMOVE THIS LINE
-        #         with eval('Capturing')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.
-        #             try:
-        #                 for q2 in q_with_outstanding_init:
-        #                     q2.init()
-        #                     q2.has_called_init_ = True
-        #
-        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.
-        #             except Exception as e:
-        #                 if not passall:
-        #                     if not silent:
-        #                         print(" ")
-        #                         print("="*30)
-        #                         print(f"When initializing question {q.title} the initialization code threw an error")
-        #                         print(e)
-        #                         print("The remaining parts of this question will likely fail.")
-        #                         print("="*30)
-        #
-        #         if show_progress_bar:
-        #             cc.terminate()
-        #             sys.stdout.flush()
-        #             print(q_title_print, end="")
-        #
-        #         q_time =np.round(  time.time()-start, 2)
-        #
-        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")
-        #         print("=" * nL)
-        #         q_with_outstanding_init = None
-        #
-        #     # item.question = q # Set the parent question instance for later reference.
-        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)
-        #
-        #     if show_progress_bar:
-        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)
-        #     else:
-        #         print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="")
-        #     hidden = issubclass(item.__class__, Hidden)
-        #     # if not hidden:
-        #     #     print(ss, end="")
-        #     # sys.stdout.flush()
-        #     start = time.time()
-        #
-        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
-        #     q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
-        #     tsecs = np.round(time.time()-start, 2)
-        #     if show_progress_bar:
-        #         cc.terminate()
-        #         sys.stdout.flush()
-        #         print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="")
-        #
-        #     if not hidden:
-        #         ss = "PASS" if current == possible else "*** FAILED"
-        #         if tsecs >= 0.1:
-        #             ss += " ("+ str(tsecs) + " seconds)"
-        #         print(ss)
-
-        # ws, possible, obtained = upack(q_)
-
-        possible = res.testsRun
-        obtained = possible - len(res.errors)
-
-
-        # possible = int(ws @ possible)
-        # obtained = int(ws @ obtained)
-        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
-
-        obtained = w * int(obtained * 1.0 / possible )
-        score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
-        q.obtained = obtained
-        q.possible = possible
-
-        s1 = f"*** Question q{n+1}"
-        s2 = f" {q.obtained}/{w}"
-        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )
-        print(" ")
-        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])
-
-    ws, possible, obtained = upack(score)
-    possible = int( msum(possible) )
-    obtained = int( msum(obtained) ) # Cast to python int
-    report.possible = possible
-    report.obtained = obtained
-    now = datetime.now()
-    dt_string = now.strftime("%H:%M:%S")
-
-    dt = int(time.time()-t_start)
-    minutes = dt//60
-    seconds = dt - minutes*60
-    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")
-
-    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")
-
-    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])
-    results = {'total': (obtained, possible), 'details': score}
-    return results, table_data
-
-
-
-
-from tabulate import tabulate
-from datetime import datetime
-import inspect
-import json
-import os
-import bz2
-import pickle
-import os
-
-def bzwrite(json_str, token): # to get around obfuscation issues
-    with getattr(bz2, 'open')(token, "wt") as f:
-        f.write(json_str)
-
-def gather_imports(imp):
-    resources = {}
-    m = imp
-    # for m in pack_imports:
-    # print(f"*** {m.__name__}")
-    f = m.__file__
-    # dn = os.path.dirname(f)
-    # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__)
-    # top_package = str(__import__(m.__name__.split('.')[0]).__path__)
-    if m.__class__.__name__ == 'module' and False:
-        top_package = os.path.dirname(m.__file__)
-        module_import = True
-    else:
-        top_package = __import__(m.__name__.split('.')[0]).__path__._path[0]
-        module_import = False
-
-    # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__)
-    # top_package = os.path.dirname(top_package)
-    import zipfile
-    # import strea
-    # zipfile.ZipFile
-    import io
-    # file_like_object = io.BytesIO(my_zip_data)
-    zip_buffer = io.BytesIO()
-    with zipfile.ZipFile(zip_buffer, 'w') as zip:
-        # zip.write()
-        for root, dirs, files in os.walk(top_package):
-            for file in files:
-                if file.endswith(".py"):
-                    fpath = os.path.join(root, file)
-                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))
-                    zip.write(fpath, v)
-
-    resources['zipfile'] = zip_buffer.getvalue()
-    resources['top_package'] = top_package
-    resources['module_import'] = module_import
-    return resources, top_package
-
-    if f.endswith("__init__.py"):
-        for root, dirs, files in os.walk(os.path.dirname(f)):
-            for file in files:
-                if file.endswith(".py"):
-                    # print(file)
-                    # print()
-                    v = os.path.relpath(os.path.join(root, file), top_package)
-                    with open(os.path.join(root, file), 'r') as ff:
-                        resources[v] = ff.read()
-    else:
-        v = os.path.relpath(f, top_package)
-        with open(f, 'r') as ff:
-            resources[v] = ff.read()
-    return resources
-
-
-def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
-    print(" ")
-    print("="*n)
-    print("Final evaluation")
-    print(tabulate(table_data))
-    # also load the source code of missing files...
-
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
-    sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
-
-    # json_str = json.dumps(results, indent=4)
-
-    if output_dir is None:
-        output_dir = os.getcwd()
-
-    payload_out_base = report.__class__.__name__ + "_handin"
-
-    obtain, possible = results['total']
-    vstring = "_v"+report.version if report.version is not None else ""
-
-    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)
-    token = os.path.join(output_dir, token)
-    with open(token, 'wb') as f:
-        pickle.dump(results, f)
-
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
-
-def source_instantiate(name, report1_source, payload):
-    eval("exec")(report1_source, globals())
-    pl = pickle.loads(bytes.fromhex(payload))
-    report = eval(name)(payload=pl, strict=True)
-    # report.set_payload(pl)
-    return report
-
-
-
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    @hide\n    def test_add_hidden(self):\n        # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n        # See the output in the student directory for more information.\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
-report1_payload = '80049570000000000000007d948c055765656b31947d94288c055765656b31948c08746573745f616464944b0087944b04680368044b0187944aa1ffffff6803680486948c057469746c6594869468046803680486948c0474696d659486944700000000000000008c0474696d6594473f8756a00000000075732e'
-name="Report3"
-
-report = source_instantiate(name, report1_source, report1_payload)
-output_dir = os.path.dirname(__file__)
-gather_upload_to_campusnet(report, output_dir)
\ No newline at end of file
diff --git a/examples/example_docker/students/cs103/report3_grade.py b/examples/example_docker/students/cs103/report3_grade.py
index af156c0c666acd29d42881551487bd9a863447a3..ecff9f7ce0634562a106d021cda68177c65c12fb 100644
--- a/examples/example_docker/students/cs103/report3_grade.py
+++ b/examples/example_docker/students/cs103/report3_grade.py
@@ -1,9 +1,12 @@
-
+"""
+Example student code. This file is automatically generated from the files in the instructor-directory
+"""
 import numpy as np
 from tabulate import tabulate
 from datetime import datetime
 import pyfiglet
 import unittest
+# from unitgrade2.unitgrade2 import MySuite
 
 import inspect
 import os
@@ -40,8 +43,6 @@ parser.add_argument('--showcomputed',  action="store_true",  help='Show the answ
 parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
 parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
 
-
-
 def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
     args = parser.parse_args()
     if question is None and args.q is not None:
@@ -63,53 +64,6 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass
                                           show_tol_err=show_tol_err)
 
 
-    # try:  # For registering stats.
-    #     import unitgrade_private
-    #     import irlc.lectures
-    #     import xlwings
-    #     from openpyxl import Workbook
-    #     import pandas as pd
-    #     from collections import defaultdict
-    #     dd = defaultdict(lambda: [])
-    #     error_computed = []
-    #     for k1, (q, _) in enumerate(report.questions):
-    #         for k2, item in enumerate(q.items):
-    #             dd['question_index'].append(k1)
-    #             dd['item_index'].append(k2)
-    #             dd['question'].append(q.name)
-    #             dd['item'].append(item.name)
-    #             dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol)
-    #             error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed)
-    #
-    #     qstats = report.wdir + "/" + report.name + ".xlsx"
-    #
-    #     if os.path.isfile(qstats):
-    #         d_read = pd.read_excel(qstats).to_dict()
-    #     else:
-    #         d_read = dict()
-    #
-    #     for k in range(1000):
-    #         key = 'run_'+str(k)
-    #         if key in d_read:
-    #             dd[key] = list(d_read['run_0'].values())
-    #         else:
-    #             dd[key] = error_computed
-    #             break
-    #
-    #     workbook = Workbook()
-    #     worksheet = workbook.active
-    #     for col, key in enumerate(dd.keys()):
-    #         worksheet.cell(row=1, column=col+1).value = key
-    #         for row, item in enumerate(dd[key]):
-    #             worksheet.cell(row=row+2, column=col+1).value = item
-    #
-    #     workbook.save(qstats)
-    #     workbook.close()
-    #
-    # except ModuleNotFoundError as e:
-    #     s = 234
-    #     pass
-
     if question is None:
         print("Provisional evaluation")
         tabulate(table_data)
@@ -138,13 +92,29 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
 
 def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                     show_progress_bar=True,
-                    show_tol_err=False):
+                    show_tol_err=False,
+                    big_header=True):
+
     now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
     print(b + " v" + __version__)
     dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
     print("Started: " + dt_string)
@@ -157,29 +127,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     nL = 80
     t_start = time.time()
     score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
     loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
-        q_hidden = False
+        # q_hidden = False
         # q_hidden = issubclass(q.__class__, Hidden)
         if question is not None and n+1 != question:
             continue
         suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
         q_title_print = "Question %i: %s"%(n+1, qtitle)
         print(q_title_print, end="")
         q.possible = 0
@@ -188,88 +145,21 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # unittest.Te
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
-        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
-        z = 234
-        # for j, item in enumerate(q.items):
-        #     if qitem is not None and question is not None and j+1 != qitem:
-        #         continue
-        #
-        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.
-        #         # if not item.question.has_called_init_:
-        #         start = time.time()
-        #
-        #         cc = None
-        #         if show_progress_bar:
-        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )
-        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)
-        #         from unitgrade import Capturing # DON'T REMOVE THIS LINE
-        #         with eval('Capturing')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.
-        #             try:
-        #                 for q2 in q_with_outstanding_init:
-        #                     q2.init()
-        #                     q2.has_called_init_ = True
-        #
-        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.
-        #             except Exception as e:
-        #                 if not passall:
-        #                     if not silent:
-        #                         print(" ")
-        #                         print("="*30)
-        #                         print(f"When initializing question {q.title} the initialization code threw an error")
-        #                         print(e)
-        #                         print("The remaining parts of this question will likely fail.")
-        #                         print("="*30)
-        #
-        #         if show_progress_bar:
-        #             cc.terminate()
-        #             sys.stdout.flush()
-        #             print(q_title_print, end="")
-        #
-        #         q_time =np.round(  time.time()-start, 2)
-        #
-        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")
-        #         print("=" * nL)
-        #         q_with_outstanding_init = None
-        #
-        #     # item.question = q # Set the parent question instance for later reference.
-        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)
-        #
-        #     if show_progress_bar:
-        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)
-        #     else:
-        #         print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="")
-        #     hidden = issubclass(item.__class__, Hidden)
-        #     # if not hidden:
-        #     #     print(ss, end="")
-        #     # sys.stdout.flush()
-        #     start = time.time()
-        #
-        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
-        #     q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
-        #     tsecs = np.round(time.time()-start, 2)
-        #     if show_progress_bar:
-        #         cc.terminate()
-        #         sys.stdout.flush()
-        #         print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="")
-        #
-        #     if not hidden:
-        #         ss = "PASS" if current == possible else "*** FAILED"
-        #         if tsecs >= 0.1:
-        #             ss += " ("+ str(tsecs) + " seconds)"
-        #         print(ss)
-
-        # ws, possible, obtained = upack(q_)
 
         possible = res.testsRun
-        obtained = possible - len(res.errors)
+        obtained = len(res.successes)
 
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
 
         # possible = int(ws @ possible)
         # obtained = int(ws @ obtained)
         # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
 
-        obtained = w * int(obtained * 1.0 / possible )
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
         score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
         q.obtained = obtained
         q.possible = possible
@@ -368,38 +258,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -414,10 +321,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
@@ -428,10 +338,10 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
-report1_payload = '800495a9000000000000007d948c055765656b31947d94288c055765656b31948c08746573745f616464944b0087944b04680368044b0187944aa1ffffff6803680486948c057469746c6594869468046803680486948c0474696d65948694473f7198800000000068038c0f746573745f6164645f68696464656e944b0087944b046803680d869468088694680d6803680d8694680b8694473f032000000000008c0474696d6594473f4186000000000075732e'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n        self._stdout = stdout\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout if self._stdout == None else self._stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\nclass Capturing2(Capturing):\n    def __exit__(self, *args):\n        lines = self._stringio.getvalue().splitlines()\n        txt = "\\n".join(lines)\n        numbers = extract_numbers(txt)\n        self.extend(lines)\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n        self.output = txt\n        self.numbers = numbers\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n#     raise Exception("no suite")\n#     pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        # item_title = item_title.split("\\n")[0]\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        if item_title == None:\n            # For unittest framework where getDescription may return None.\n            item_title = self.getDescription(test)\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 1\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    def capture(self):\n        return Capturing2(stdout=self._stdout)\n\n    @classmethod\n    def question_title(cls):\n        """ Return the question title """\n        return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _callSetUp(self):\n        self._stdout = sys.stdout\n        import io\n        sys.stdout = io.StringIO()\n        super().setUp()\n        # print("Setting up...")\n\n    def _callTearDown(self):\n        sys.stdout = self._stdout\n        super().tearDown()\n        # print("asdfsfd")\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        self._ensure_cache_exists() # Make sure cache is there.\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n# from unitgrade2.unitgrade2 import MySuite\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs103.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n    def test_student_passed(self):\n        self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n    title = "CS 101 Report 3"\n    questions = [(Week1, 20), (AutomaticPass, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs103]'
+report1_payload = '80049525010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d65948694473f506a000000000068038c0f746573745f6164645f68696464656e948694680686947d944b004b04736803680c8694680a86944700000000000000008c0474696d6594473f926de000000000758c0d4175746f6d6174696350617373947d94288c0d4175746f6d6174696350617373948c10746573745f68696464656e5f6661696c9486948c066173736572749486947d9468158c13746573745f73747564656e745f706173736564948694681886947d946815681b86948c0474696d659486944700000000000000006812473f9894100000000075752e'
 name="Report3"
 
 report = source_instantiate(name, report1_source, report1_payload)
 output_dir = os.path.dirname(__file__)
-gather_upload_to_campusnet(report, output_dir)
\ No newline at end of file
+gather_upload_to_campusnet(report, output_dir)
diff --git a/examples/example_docker/students/cs103/unitgrade/AutomaticPass.pkl b/examples/example_docker/students/cs103/unitgrade/AutomaticPass.pkl
new file mode 100644
index 0000000000000000000000000000000000000000..2a722e2b9c8264b76eca73fec2c7dd84eb0e02d3
Binary files /dev/null and b/examples/example_docker/students/cs103/unitgrade/AutomaticPass.pkl differ
diff --git a/examples/example_docker/students/cs103/unitgrade/Week1.pkl b/examples/example_docker/students/cs103/unitgrade/Week1.pkl
index fc298168d395c432420ad99533ade24705a6e589..fe27b785553c86fe6975853b9990eed439d2d5bc 100644
Binary files a/examples/example_docker/students/cs103/unitgrade/Week1.pkl and b/examples/example_docker/students/cs103/unitgrade/Week1.pkl differ
diff --git a/examples/example_framework/instructor/cs102/Report2_handin_10_of_18.token b/examples/example_framework/instructor/cs102/Report2_handin_10_of_18.token
new file mode 100644
index 0000000000000000000000000000000000000000..3dc602acc93517ba2a3211a3f926b2dfe0cf8c90
Binary files /dev/null and b/examples/example_framework/instructor/cs102/Report2_handin_10_of_18.token differ
diff --git a/examples/example_framework/instructor/cs102/Report2_handin_18_of_18.token b/examples/example_framework/instructor/cs102/Report2_handin_18_of_18.token
index 9d33ee3e0b160c03ba7f8ddbc9334c0353162777..01a473c0bc4e8c0b536bb6c2b01d901ca33c5689 100644
Binary files a/examples/example_framework/instructor/cs102/Report2_handin_18_of_18.token and b/examples/example_framework/instructor/cs102/Report2_handin_18_of_18.token differ
diff --git a/examples/example_framework/instructor/cs102/Report2_handin_28_of_28.token b/examples/example_framework/instructor/cs102/Report2_handin_28_of_28.token
new file mode 100644
index 0000000000000000000000000000000000000000..4fe9c89fea3b77998c9201c910a729cc1eb16d89
Binary files /dev/null and b/examples/example_framework/instructor/cs102/Report2_handin_28_of_28.token differ
diff --git a/examples/example_framework/instructor/cs102/Report2_handin_5_of_18.token b/examples/example_framework/instructor/cs102/Report2_handin_5_of_18.token
new file mode 100644
index 0000000000000000000000000000000000000000..e22d430bac7ac0c21c0931fd87cd6871fa95b8c9
Binary files /dev/null and b/examples/example_framework/instructor/cs102/Report2_handin_5_of_18.token differ
diff --git a/examples/example_framework/instructor/cs102/Report2_handin_5_of_28.token b/examples/example_framework/instructor/cs102/Report2_handin_5_of_28.token
new file mode 100644
index 0000000000000000000000000000000000000000..cb4ed5d6abfec70f738f205440eba8f73c909f7d
Binary files /dev/null and b/examples/example_framework/instructor/cs102/Report2_handin_5_of_28.token differ
diff --git a/examples/example_framework/instructor/cs102/__pycache__/homework1.cpython-38.pyc b/examples/example_framework/instructor/cs102/__pycache__/homework1.cpython-38.pyc
index 648a38c39c1ecfb3574ee2b7c6cc02f36bc389f5..aca3c8b22c11ae4d9d824d7ba252b4194f5db12f 100644
Binary files a/examples/example_framework/instructor/cs102/__pycache__/homework1.cpython-38.pyc and b/examples/example_framework/instructor/cs102/__pycache__/homework1.cpython-38.pyc differ
diff --git a/examples/example_framework/instructor/cs102/__pycache__/report2.cpython-38.pyc b/examples/example_framework/instructor/cs102/__pycache__/report2.cpython-38.pyc
index 3fa4e7ab13c858ce6ae10653e9f82f0f04d6c6eb..e88ea2f06cb46d8605dea67d9293e601ee149b83 100644
Binary files a/examples/example_framework/instructor/cs102/__pycache__/report2.cpython-38.pyc and b/examples/example_framework/instructor/cs102/__pycache__/report2.cpython-38.pyc differ
diff --git a/examples/example_framework/instructor/cs102/__pycache__/report2_grade.cpython-38.pyc b/examples/example_framework/instructor/cs102/__pycache__/report2_grade.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5fb59bc9a735c474aff24e25cd7c318cc0869f8d
Binary files /dev/null and b/examples/example_framework/instructor/cs102/__pycache__/report2_grade.cpython-38.pyc differ
diff --git a/examples/example_framework/instructor/cs102/report2.py b/examples/example_framework/instructor/cs102/report2.py
index 1b00e8413534498cb492b01ec4bfc63c7e431f17..381cdb16108cfb10f1705d57df8aab14bfddc97a 100644
--- a/examples/example_framework/instructor/cs102/report2.py
+++ b/examples/example_framework/instructor/cs102/report2.py
@@ -1,18 +1,21 @@
 from unitgrade2.unitgrade2 import Report
 from unitgrade2.unitgrade_helpers2 import evaluate_report_student
 from unitgrade2.unitgrade2 import UTestCase, cache, hide
-import random
 
 class Week1(UTestCase):
     """ The first question for week 1. """
     def test_add(self):
+        """ Docstring for this method """
         from cs102.homework1 import add
         self.assertEqualC(add(2,2))
+        with self.capture() as out:
+            print("hello world 42")
+        self.assertEqual(out.numbers[0], 42)
         self.assertEqualC(add(-100, 5))
 
     def test_reverse(self):
+        """ Reverse a list """  # Add a title to the test.
         from cs102.homework1 import reverse_list
-        """ Reverse a list """ # Add a title to the test.
         self.assertEqualC(reverse_list([1,2,3]))
 
 
@@ -28,7 +31,8 @@ class Question2(UTestCase):
         return reverse_list(ls)
 
     def test_reverse_tricky(self):
-        ls = ("butterfly", 4, 1)
+        ls = [2,4,8]
+        self.title = f"Reversing a small list containing {ls=}" # Titles can be set like this at any point in the function body.
         ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.
         ls3 = self.my_reversal( tuple([1,2,3]) )  # Also works; the cache respects input arguments.
         self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.
@@ -37,7 +41,7 @@ class Question2(UTestCase):
 import cs102
 class Report2(Report):
     title = "CS 101 Report 2"
-    questions = [(Week1, 10), (Question2, 8)]  # Include a single question for 10 credits.
+    questions = [(Week1, 10), (Question2, 8) ]
     pack_imports = [cs102]
 
 if __name__ == "__main__":
diff --git a/examples/example_framework/instructor/cs102/report2_grade.py b/examples/example_framework/instructor/cs102/report2_grade.py
index e5b3f7c2e42c7ded98caad5240adc22ec1b31927..503237e5942ba9c82dca8454230add50e726429f 100644
--- a/examples/example_framework/instructor/cs102/report2_grade.py
+++ b/examples/example_framework/instructor/cs102/report2_grade.py
@@ -40,8 +40,6 @@ parser.add_argument('--showcomputed',  action="store_true",  help='Show the answ
 parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
 parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
 
-
-
 def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
     args = parser.parse_args()
     if question is None and args.q is not None:
@@ -138,13 +136,29 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
 
 def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                     show_progress_bar=True,
-                    show_tol_err=False):
+                    show_tol_err=False,
+                    big_header=True):
+
     now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
     print(b + " v" + __version__)
     dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
     print("Started: " + dt_string)
@@ -157,29 +171,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     nL = 80
     t_start = time.time()
     score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
     loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
-        q_hidden = False
+        # q_hidden = False
         # q_hidden = issubclass(q.__class__, Hidden)
         if question is not None and n+1 != question:
             continue
         suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
         q_title_print = "Question %i: %s"%(n+1, qtitle)
         print(q_title_print, end="")
         q.possible = 0
@@ -188,6 +189,9 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # unittest.Te
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
         # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
         z = 234
@@ -262,14 +266,15 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # ws, possible, obtained = upack(q_)
 
         possible = res.testsRun
-        obtained = possible - len(res.errors)
+        obtained = len(res.successes)
 
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
 
         # possible = int(ws @ possible)
         # obtained = int(ws @ obtained)
         # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
 
-        obtained = w * int(obtained * 1.0 / possible )
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
         score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
         q.obtained = obtained
         q.possible = possible
@@ -368,38 +373,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -414,10 +436,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
@@ -428,8 +453,8 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nimport random\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs102.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    def test_reverse(self):\n        from cs102.homework1 import reverse_list\n\n        """ Reverse a list """ # Add a title to the test.\n        self.assertEqualC(reverse_list([1,2,3]))\n        random.seed(42)\n        self.assertEqualC(reverse_list([1, 2, 3]))\n\n    def test_reverse_nicetitle(self):\n        l = [1,2,3]\n        from cs102.homework1 import reverse_list\n        # self.title(f"Reverse the list {l}")\n        self.assertEqualC(reverse_list([1,2,3]))\n\n\nclass Question2(UTestCase):\n    """ Second problem """\n    @cache\n    def my_reversal(self, ls):\n        # The \'@cache\' decorator ensures the function is not run on the *students* computer\n        # Instead the code is run on the teachers computer and the result is passed on with the\n        # other pre-computed results -- i.e. this function will run regardless of how the student happens to have\n        # implemented reverse_list.\n        from cs102.homework1 import reverse_list\n        return reverse_list(ls)\n\n    def test_reverse_tricky(self):\n        ls = ("butterfly", 4, 1)\n        ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.\n        ls3 = self.my_reversal( tuple([1,2,3]) )  # Also works; the cache respects input arguments.\n        self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.\n\n\nimport cs102\nclass Report2(Report):\n    title = "CS 101 Report 2"\n    questions = [(Week1, 10), (Question2, 8)]  # Include a single question for 10 credits.\n    pack_imports = [cs102]'
-report1_payload = '80049570020000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f616464944b0087944b04680368044b0187944aa1ffffff6803680486948c057469746c6594869468046803680486948c0474696d6594869447000000000000000068038c0c746573745f72657665727365944b0087945d94284b034b024b01656803680d4b0187945d94284b034b024b01656803680d869468088694680d6803680d8694680b869447000000000000000068038c16746573745f726576657273655f6e6963657469746c65944b0087945d94284b034b024b0165680368168694680886946816680368168694680b8694470000000000000000680368044b0287944b04680368044b0387944aa1ffffff6803680d4b0287945d94284b034b024b01656803680d4b0387945d94284b034b024b0165680368164b0187945d94284b034b024b01658c0474696d6594473f505b0000000000758c095175657374696f6e32947d9428288c095175657374696f6e32948c13746573745f726576657273655f747269636b79948c056361636865948c0966756e63746f6f6c73948c0a5f4861736865645365719493942981948c09627574746572666c79944b044b018794614e7d948c096861736876616c7565948a08f4847d050274ba8e7386946274945d94284b014b04682f652868286829682a682d2981944b014b04682f8794614e7d9468328a080bf6b043c808a2777386946274945d9428682f4b044b0165682868294b0087945d9428682f4b044b01656828682986948c057469746c6594869468296828682986948c0474696d65948694470000000000000000682868294b018794683b6825473f506b000000000075752e'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    @classmethod\n    def question_title(cls):\n        return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    # def _callSetUp(self):\n    #     # Always run before method is called.\n    #     print("asdf")\n    #     pass\n    # @classmethod\n    # def setUpClass(cls):\n    #     # self._cache_put((self.cache_id(), \'title\'), value)\n    #     cls.reset()\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    # def unique_cache_id(self):\n    #     k0 = self.cache_id()\n    #     # key = ()\n    #     i = 0\n    #     for i in itertools.count():\n    #         # key = k0 + (i,)\n    #         if i not in self._cache_get( (k0, \'assert\') ):\n    #             break\n    #     return i\n    #     return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n    #\n    # def _cache2_contains(self, key):\n    #     print("Is this needed?")\n    #     self._ensure_cache_exists()\n    #     return key in self.__class__._cache2\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nimport random\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n\n    def test_add(self):\n        """ Docstring for this method """\n        from cs102.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    def test_reverse(self):\n        """ Reverse a list """  # Add a title to the test.\n        from cs102.homework1 import reverse_list\n        self.assertEqualC(reverse_list([1,2,3]))\n\n\nclass Question2(UTestCase):\n    """ Second problem """\n    @cache\n    def my_reversal(self, ls):\n        # The \'@cache\' decorator ensures the function is not run on the *students* computer\n        # Instead the code is run on the teachers computer and the result is passed on with the\n        # other pre-computed results -- i.e. this function will run regardless of how the student happens to have\n        # implemented reverse_list.\n        from cs102.homework1 import reverse_list\n        return reverse_list(ls)\n\n    def test_reverse_tricky(self):\n        ls = [2,4,8]\n        self.title = f"Reversing a small list containing {ls=}"\n        ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.\n        ls3 = self.my_reversal( tuple([1,2,3]) )  # Also works; the cache respects input arguments.\n        self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.\n\n\nimport cs102\nclass Report2(Report):\n    title = "CS 101 Report 2"\n    questions = [(Week1, 10), (Question2, 8) ]  # Include a single question for 10 credits.\n    pack_imports = [cs102]'
+report1_payload = '8004959a010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c057469746c659486948c19446f63737472696e6720666f722074686973206d6574686f64946803680486948c066173736572749486947d94284b004b044b014aa1ffffff756803680486948c0474696d6594869447000000000000000068038c0c746573745f72657665727365948694680686948c0e526576657273652061206c69737494680368108694680a86947d944b005d94284b034b024b016573680368108694680e86944700000000000000008c0474696d6594470000000000000000758c095175657374696f6e32947d94288c095175657374696f6e32948c13746573745f726576657273655f747269636b799486948c066173736572749486947d944b005d94284b024b044b086573681d681e86948c057469746c659486948c2e526576657273696e67206120736d616c6c206c69737420636f6e7461696e696e67206c733d5b322c20342c20385d94681d681e86948c0474696d65948694470000000000000000681a473f5066000000000075752e'
 name="Report2"
 
 report = source_instantiate(name, report1_source, report1_payload)
diff --git a/examples/example_framework/instructor/cs102/unitgrade/Question2.pkl b/examples/example_framework/instructor/cs102/unitgrade/Question2.pkl
index 6e7cf7b627288b573660ca68878a98497c155518..634a7fbbe4bad27f24b2d894ef3c0b37c4f5dd94 100644
Binary files a/examples/example_framework/instructor/cs102/unitgrade/Question2.pkl and b/examples/example_framework/instructor/cs102/unitgrade/Question2.pkl differ
diff --git a/examples/example_framework/instructor/cs102/unitgrade/Question3.pkl b/examples/example_framework/instructor/cs102/unitgrade/Question3.pkl
new file mode 100644
index 0000000000000000000000000000000000000000..c20bf1a1963e7d4466efa8cccb2c56052d7afc7f
Binary files /dev/null and b/examples/example_framework/instructor/cs102/unitgrade/Question3.pkl differ
diff --git a/examples/example_framework/instructor/cs102/unitgrade/Week1.pkl b/examples/example_framework/instructor/cs102/unitgrade/Week1.pkl
index 2e3ed1cd3668611cf65c9bce6ae9a8e96adf719b..7912698f036128bb2a8b616c2a66c58ac9e774e5 100644
Binary files a/examples/example_framework/instructor/cs102/unitgrade/Week1.pkl and b/examples/example_framework/instructor/cs102/unitgrade/Week1.pkl differ
diff --git a/examples/example_framework/students/cs102/Report2_handin_0_of_18.token b/examples/example_framework/students/cs102/Report2_handin_0_of_18.token
new file mode 100644
index 0000000000000000000000000000000000000000..63734376c0eae1c4df3121a0c656e90452e80cba
Binary files /dev/null and b/examples/example_framework/students/cs102/Report2_handin_0_of_18.token differ
diff --git a/examples/example_framework/students/cs102/__pycache__/homework1.cpython-38.pyc b/examples/example_framework/students/cs102/__pycache__/homework1.cpython-38.pyc
index 648a38c39c1ecfb3574ee2b7c6cc02f36bc389f5..a099ef9f65bf987d85152d20d4dad941ae1d27bc 100644
Binary files a/examples/example_framework/students/cs102/__pycache__/homework1.cpython-38.pyc and b/examples/example_framework/students/cs102/__pycache__/homework1.cpython-38.pyc differ
diff --git a/examples/example_framework/students/cs102/__pycache__/homework1.cpython-39.pyc b/examples/example_framework/students/cs102/__pycache__/homework1.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..59aec17144bee92c76e761ed5a8bcfd0b24ba30c
Binary files /dev/null and b/examples/example_framework/students/cs102/__pycache__/homework1.cpython-39.pyc differ
diff --git a/examples/example_framework/students/cs102/__pycache__/report2.cpython-38.pyc b/examples/example_framework/students/cs102/__pycache__/report2.cpython-38.pyc
index 3fa4e7ab13c858ce6ae10653e9f82f0f04d6c6eb..d06f59685aba1b0e7100778b3122129ce8573306 100644
Binary files a/examples/example_framework/students/cs102/__pycache__/report2.cpython-38.pyc and b/examples/example_framework/students/cs102/__pycache__/report2.cpython-38.pyc differ
diff --git a/examples/example_framework/students/cs102/__pycache__/report2_grade.cpython-38.pyc b/examples/example_framework/students/cs102/__pycache__/report2_grade.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cb391caf0c1bf2175df62b4c8789fee08d1961e3
Binary files /dev/null and b/examples/example_framework/students/cs102/__pycache__/report2_grade.cpython-38.pyc differ
diff --git a/examples/example_framework/students/cs102/homework1.py b/examples/example_framework/students/cs102/homework1.py
index 586edb01305cb363f42e853ea3fd63a08bc1c389..3543f1ba46b63eec3a2c2e007ee998660c7136c6 100644
--- a/examples/example_framework/students/cs102/homework1.py
+++ b/examples/example_framework/students/cs102/homework1.py
@@ -2,14 +2,20 @@
 Example student code. This file is automatically generated from the files in the instructor-directory
 """
 def reverse_list(mylist): 
-    # TODO: 5 lines missing.
+    """
+    Given a list 'mylist' returns a list consisting of the same elements in reverse order. E.g.
+    reverse_list([1,2,3]) should return [3,2,1] (as a list).
+    """
+    # TODO: 1 lines missing.
     raise NotImplementedError("Implement function body")
 
 def add(a,b): 
+    """ Given two numbers `a` and `b` this function should simply return their sum:
+    > add(a,b) = a+b """
     # TODO: 1 lines missing.
     raise NotImplementedError("Implement function body")
 
 if __name__ == "__main__":
-    # Problem 1: Write a function which add two numbers and reverse a list.
+    # Problem 1: Write a function which add two numbers
     print(f"Your result of 2 + 2 = {add(2,2)}")
     print(f"Reversing a small list", reverse_list([2,3,5,7]))
diff --git a/examples/example_framework/students/cs102/report2.py b/examples/example_framework/students/cs102/report2.py
index 9955f642d9dc2ac171e51b6c3692e7104e9e1019..d84e9c436feeb3f9a7c62cf6c637df8e8561b522 100644
--- a/examples/example_framework/students/cs102/report2.py
+++ b/examples/example_framework/students/cs102/report2.py
@@ -8,23 +8,16 @@ import random
 
 class Week1(UTestCase):
     """ The first question for week 1. """
+
     def test_add(self):
+        """ Docstring for this method """
         from cs102.homework1 import add
         self.assertEqualC(add(2,2))
         self.assertEqualC(add(-100, 5))
 
     def test_reverse(self):
+        """ Reverse a list """  # Add a title to the test.
         from cs102.homework1 import reverse_list
-
-        """ Reverse a list """ # Add a title to the test.
-        self.assertEqualC(reverse_list([1,2,3]))
-        random.seed(42)
-        self.assertEqualC(reverse_list([1, 2, 3]))
-
-    def test_reverse_nicetitle(self):
-        l = [1,2,3]
-        from cs102.homework1 import reverse_list
-        # self.title(f"Reverse the list {l}")
         self.assertEqualC(reverse_list([1,2,3]))
 
 
@@ -40,7 +33,8 @@ class Question2(UTestCase):
         return reverse_list(ls)
 
     def test_reverse_tricky(self):
-        ls = ("butterfly", 4, 1)
+        ls = [2,4,8]
+        self.title = f"Reversing a small list containing {ls=}"
         ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.
         ls3 = self.my_reversal( tuple([1,2,3]) )  # Also works; the cache respects input arguments.
         self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.
@@ -49,7 +43,7 @@ class Question2(UTestCase):
 import cs102
 class Report2(Report):
     title = "CS 101 Report 2"
-    questions = [(Week1, 10), (Question2, 8)]  # Include a single question for 10 credits.
+    questions = [(Week1, 10), (Question2, 8) ]  # Include a single question for 10 credits.
     pack_imports = [cs102]
 
 if __name__ == "__main__":
diff --git a/examples/example_framework/students/cs102/report2_grade.py b/examples/example_framework/students/cs102/report2_grade.py
index 7bd91ff42c8a43d75efebbac00336d94a05ba36a..1f9a88549ff25664292eea15d23b7d7dd77c2b86 100644
--- a/examples/example_framework/students/cs102/report2_grade.py
+++ b/examples/example_framework/students/cs102/report2_grade.py
@@ -42,8 +42,6 @@ parser.add_argument('--showcomputed',  action="store_true",  help='Show the answ
 parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
 parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
 
-
-
 def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
     args = parser.parse_args()
     if question is None and args.q is not None:
@@ -140,13 +138,29 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
+        test_names.sort(key=testcase_methods.index)
+        return test_names
 
 def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                     show_progress_bar=True,
-                    show_tol_err=False):
+                    show_tol_err=False,
+                    big_header=True):
+
     now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    if big_header:
+        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+        b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    else:
+        b = "Unitgrade"
     print(b + " v" + __version__)
     dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
     print("Started: " + dt_string)
@@ -159,29 +173,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     nL = 80
     t_start = time.time()
     score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
     loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
-        q_hidden = False
+        # q_hidden = False
         # q_hidden = issubclass(q.__class__, Hidden)
         if question is not None and n+1 != question:
             continue
         suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
         q_title_print = "Question %i: %s"%(n+1, qtitle)
         print(q_title_print, end="")
         q.possible = 0
@@ -190,6 +191,9 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # unittest.Te
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
+
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
         # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
         z = 234
@@ -264,14 +268,15 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # ws, possible, obtained = upack(q_)
 
         possible = res.testsRun
-        obtained = possible - len(res.errors)
+        obtained = len(res.successes)
 
+        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun
 
         # possible = int(ws @ possible)
         # obtained = int(ws @ obtained)
         # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0
 
-        obtained = w * int(obtained * 1.0 / possible )
+        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0
         score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle}
         q.obtained = obtained
         q.possible = possible
@@ -370,38 +375,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -416,10 +438,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
@@ -430,8 +455,8 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nimport random\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n    def test_add(self):\n        from cs102.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    def test_reverse(self):\n        from cs102.homework1 import reverse_list\n\n        """ Reverse a list """ # Add a title to the test.\n        self.assertEqualC(reverse_list([1,2,3]))\n        random.seed(42)\n        self.assertEqualC(reverse_list([1, 2, 3]))\n\n    def test_reverse_nicetitle(self):\n        l = [1,2,3]\n        from cs102.homework1 import reverse_list\n        # self.title(f"Reverse the list {l}")\n        self.assertEqualC(reverse_list([1,2,3]))\n\n\nclass Question2(UTestCase):\n    """ Second problem """\n    @cache\n    def my_reversal(self, ls):\n        # The \'@cache\' decorator ensures the function is not run on the *students* computer\n        # Instead the code is run on the teachers computer and the result is passed on with the\n        # other pre-computed results -- i.e. this function will run regardless of how the student happens to have\n        # implemented reverse_list.\n        from cs102.homework1 import reverse_list\n        return reverse_list(ls)\n\n    def test_reverse_tricky(self):\n        ls = ("butterfly", 4, 1)\n        ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.\n        ls3 = self.my_reversal( tuple([1,2,3]) )  # Also works; the cache respects input arguments.\n        self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.\n\n\nimport cs102\nclass Report2(Report):\n    title = "CS 101 Report 2"\n    questions = [(Week1, 10), (Question2, 8)]  # Include a single question for 10 credits.\n    pack_imports = [cs102]'
-report1_payload = '80049570020000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f616464944b0087944b04680368044b0187944aa1ffffff6803680486948c057469746c6594869468046803680486948c0474696d6594869447000000000000000068038c0c746573745f72657665727365944b0087945d94284b034b024b01656803680d4b0187945d94284b034b024b01656803680d869468088694680d6803680d8694680b869447000000000000000068038c16746573745f726576657273655f6e6963657469746c65944b0087945d94284b034b024b0165680368168694680886946816680368168694680b8694470000000000000000680368044b0287944b04680368044b0387944aa1ffffff6803680d4b0287945d94284b034b024b01656803680d4b0387945d94284b034b024b0165680368164b0187945d94284b034b024b01658c0474696d6594473f505b0000000000758c095175657374696f6e32947d9428288c095175657374696f6e32948c13746573745f726576657273655f747269636b79948c056361636865948c0966756e63746f6f6c73948c0a5f4861736865645365719493942981948c09627574746572666c79944b044b018794614e7d948c096861736876616c7565948a08f4847d050274ba8e7386946274945d94284b014b04682f652868286829682a682d2981944b014b04682f8794614e7d9468328a080bf6b043c808a2777386946274945d9428682f4b044b0165682868294b0087945d9428682f4b044b01656828682986948c057469746c6594869468296828682986948c0474696d65948694470000000000000000682868294b018794683b6825473f506b000000000075752e'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    @classmethod\n    def question_title(cls):\n        return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    # def _callSetUp(self):\n    #     # Always run before method is called.\n    #     print("asdf")\n    #     pass\n    # @classmethod\n    # def setUpClass(cls):\n    #     # self._cache_put((self.cache_id(), \'title\'), value)\n    #     cls.reset()\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    # def unique_cache_id(self):\n    #     k0 = self.cache_id()\n    #     # key = ()\n    #     i = 0\n    #     for i in itertools.count():\n    #         # key = k0 + (i,)\n    #         if i not in self._cache_get( (k0, \'assert\') ):\n    #             break\n    #     return i\n    #     return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n    #\n    # def _cache2_contains(self, key):\n    #     print("Is this needed?")\n    #     self._ensure_cache_exists()\n    #     return key in self.__class__._cache2\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nimport random\n\nclass Week1(UTestCase):\n    """ The first question for week 1. """\n\n    def test_add(self):\n        """ Docstring for this method """\n        from cs102.homework1 import add\n        self.assertEqualC(add(2,2))\n        self.assertEqualC(add(-100, 5))\n\n    def test_reverse(self):\n        """ Reverse a list """  # Add a title to the test.\n        from cs102.homework1 import reverse_list\n        self.assertEqualC(reverse_list([1,2,3]))\n\n\nclass Question2(UTestCase):\n    """ Second problem """\n    @cache\n    def my_reversal(self, ls):\n        # The \'@cache\' decorator ensures the function is not run on the *students* computer\n        # Instead the code is run on the teachers computer and the result is passed on with the\n        # other pre-computed results -- i.e. this function will run regardless of how the student happens to have\n        # implemented reverse_list.\n        from cs102.homework1 import reverse_list\n        return reverse_list(ls)\n\n    def test_reverse_tricky(self):\n        ls = [2,4,8]\n        self.title = f"Reversing a small list containing {ls=}"\n        ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.\n        ls3 = self.my_reversal( tuple([1,2,3]) )  # Also works; the cache respects input arguments.\n        self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.\n\n\nimport cs102\nclass Report2(Report):\n    title = "CS 101 Report 2"\n    questions = [(Week1, 10), (Question2, 8) ]  # Include a single question for 10 credits.\n    pack_imports = [cs102]'
+report1_payload = '8004959a010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c057469746c659486948c19446f63737472696e6720666f722074686973206d6574686f64946803680486948c066173736572749486947d94284b004b044b014aa1ffffff756803680486948c0474696d6594869447000000000000000068038c0c746573745f72657665727365948694680686948c0e526576657273652061206c69737494680368108694680a86947d944b005d94284b034b024b016573680368108694680e86944700000000000000008c0474696d6594470000000000000000758c095175657374696f6e32947d94288c095175657374696f6e32948c13746573745f726576657273655f747269636b799486948c066173736572749486947d944b005d94284b024b044b086573681d681e86948c057469746c659486948c2e526576657273696e67206120736d616c6c206c69737420636f6e7461696e696e67206c733d5b322c20342c20385d94681d681e86948c0474696d65948694470000000000000000681a473f5066000000000075752e'
 name="Report2"
 
 report = source_instantiate(name, report1_source, report1_payload)
diff --git a/examples/example_framework/students/cs102/unitgrade/Question2.pkl b/examples/example_framework/students/cs102/unitgrade/Question2.pkl
index 6e7cf7b627288b573660ca68878a98497c155518..634a7fbbe4bad27f24b2d894ef3c0b37c4f5dd94 100644
Binary files a/examples/example_framework/students/cs102/unitgrade/Question2.pkl and b/examples/example_framework/students/cs102/unitgrade/Question2.pkl differ
diff --git a/examples/example_framework/students/cs102/unitgrade/Question3.pkl b/examples/example_framework/students/cs102/unitgrade/Question3.pkl
new file mode 100644
index 0000000000000000000000000000000000000000..c20bf1a1963e7d4466efa8cccb2c56052d7afc7f
Binary files /dev/null and b/examples/example_framework/students/cs102/unitgrade/Question3.pkl differ
diff --git a/examples/example_framework/students/cs102/unitgrade/Week1.pkl b/examples/example_framework/students/cs102/unitgrade/Week1.pkl
index 2e3ed1cd3668611cf65c9bce6ae9a8e96adf719b..7912698f036128bb2a8b616c2a66c58ac9e774e5 100644
Binary files a/examples/example_framework/students/cs102/unitgrade/Week1.pkl and b/examples/example_framework/students/cs102/unitgrade/Week1.pkl differ
diff --git a/examples/example_simplest/instructor/cs101/Report1_handin_10_of_10.token b/examples/example_simplest/instructor/cs101/Report1_handin_10_of_10.token
index 0a688a52f5136ace5fb186affec815bdeb67c268..5d34d0fd91b71eecb9ff050591857b09f382421b 100644
Binary files a/examples/example_simplest/instructor/cs101/Report1_handin_10_of_10.token and b/examples/example_simplest/instructor/cs101/Report1_handin_10_of_10.token differ
diff --git a/examples/example_simplest/instructor/cs101/__pycache__/report1.cpython-38.pyc b/examples/example_simplest/instructor/cs101/__pycache__/report1.cpython-38.pyc
index d71c0905a85dfe88c5fe08fe3973d3de7ff67421..83e9e30d8000e629914b0160acf822078d8a3b78 100644
Binary files a/examples/example_simplest/instructor/cs101/__pycache__/report1.cpython-38.pyc and b/examples/example_simplest/instructor/cs101/__pycache__/report1.cpython-38.pyc differ
diff --git a/examples/example_simplest/instructor/cs101/__pycache__/report1_grade.cpython-38.pyc b/examples/example_simplest/instructor/cs101/__pycache__/report1_grade.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3b0930bdcddcda845eef8dfac07d6ab732a2473f
Binary files /dev/null and b/examples/example_simplest/instructor/cs101/__pycache__/report1_grade.cpython-38.pyc differ
diff --git a/examples/example_simplest/instructor/cs101/report1.py b/examples/example_simplest/instructor/cs101/report1.py
index ea4f3b2c3381a0c8b4ec450570cf5f42fde7a070..e00853f3f03381b3f1879db5c217fee7c3ff0279 100644
--- a/examples/example_simplest/instructor/cs101/report1.py
+++ b/examples/example_simplest/instructor/cs101/report1.py
@@ -11,6 +11,7 @@ class Week1(unittest.TestCase):
     def test_reverse(self):
         self.assertEqual(reverse_list([1,2,3]), [3,2,1])
 
+
 import cs101
 class Report1(Report):
     title = "CS 101 Report 1"
diff --git a/examples/example_simplest/instructor/cs101/report1_grade.py b/examples/example_simplest/instructor/cs101/report1_grade.py
index 1a8a049ab1900d22c089909ffc8b259f8cd75d73..8972ab5fd7d427147f65315d2b2b87f6dee0f6fb 100644
--- a/examples/example_simplest/instructor/cs101/report1_grade.py
+++ b/examples/example_simplest/instructor/cs101/report1_grade.py
@@ -139,7 +139,12 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
 class SequentialTestLoader(unittest.TestLoader):
     def getTestCaseNames(self, testCaseClass):
         test_names = super().getTestCaseNames(testCaseClass)
-        testcase_methods = list(testCaseClass.__dict__.keys())
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
         test_names.sort(key=testcase_methods.index)
         return test_names
 
@@ -170,15 +175,12 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
-        q_hidden = False
+        # q_hidden = False
         # q_hidden = issubclass(q.__class__, Hidden)
         if question is not None and n+1 != question:
             continue
         suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
         q_title_print = "Question %i: %s"%(n+1, qtitle)
         print(q_title_print, end="")
         q.possible = 0
@@ -188,6 +190,7 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
         UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
 
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
         # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
@@ -450,7 +453,7 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        a ="234"\n        b = "234"\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        return root_dir, relative_path\n\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        # test.countTestCases()\n\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        testcase_methods = list(testCaseClass.__dict__.keys())\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nfrom cs101.homework1 import reverse_list, add\nimport unittest\n\nclass Week1(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(add(2,2), 4)\n        self.assertEqual(add(-100, 5), -95)\n\n    def test_reverse(self):\n        self.assertEqual(reverse_list([1,2,3]), [3,2,1])\n\nimport cs101\nclass Report1(Report):\n    title = "CS 101 Report 1"\n    questions = [(Week1, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs101]'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    @classmethod\n    def question_title(cls):\n        return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    # def _callSetUp(self):\n    #     # Always run before method is called.\n    #     print("asdf")\n    #     pass\n    # @classmethod\n    # def setUpClass(cls):\n    #     # self._cache_put((self.cache_id(), \'title\'), value)\n    #     cls.reset()\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        self._ensure_cache_exists() # Make sure cache is there.\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    # def unique_cache_id(self):\n    #     k0 = self.cache_id()\n    #     # key = ()\n    #     i = 0\n    #     for i in itertools.count():\n    #         # key = k0 + (i,)\n    #         if i not in self._cache_get( (k0, \'assert\') ):\n    #             break\n    #     return i\n    #     return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n    #\n    # def _cache2_contains(self, key):\n    #     print("Is this needed?")\n    #     self._ensure_cache_exists()\n    #     return key in self.__class__._cache2\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nfrom cs101.homework1 import reverse_list, add\nimport unittest\n\nclass Week1(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(add(2,2), 4)\n        self.assertEqual(add(-100, 5), -95)\n\n    def test_reverse(self):\n        self.assertEqual(reverse_list([1,2,3]), [3,2,1])\n        # print("Bad output\\n\\n")\n\n\nimport cs101\nclass Report1(Report):\n    title = "CS 101 Report 1"\n    questions = [(Week1, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs101]'
 report1_payload = '8004953f000000000000007d948c055765656b31947d948c2c6e6f20636163686520736565205f73657475705f616e737765727320696e20756e69746772616465322e7079948873732e'
 name="Report1"
 
diff --git a/examples/example_simplest/students/cs101/Report1_handin_0_of_10.token b/examples/example_simplest/students/cs101/Report1_handin_0_of_10.token
index f5d1790d62c080547dc0c0940d993d6e89efd745..5ccd4e5495ad2bbfe4c3cc09832916b356f0d31b 100644
Binary files a/examples/example_simplest/students/cs101/Report1_handin_0_of_10.token and b/examples/example_simplest/students/cs101/Report1_handin_0_of_10.token differ
diff --git a/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-38.pyc b/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-38.pyc
index 7dbbdac7ae33a6d3d1764a740a5f47ff3e00b252..1149b3ace2379ce11502c3e4633382e7f8d4950a 100644
Binary files a/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-38.pyc and b/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-38.pyc differ
diff --git a/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-39.pyc b/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-39.pyc
index f4a5b8432432aa2fb0239a4f02152ac367b45b4c..797cf46e4a10aa111b1af224256cb5ec15734be5 100644
Binary files a/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-39.pyc and b/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-39.pyc differ
diff --git a/examples/example_simplest/students/cs101/__pycache__/report1.cpython-38.pyc b/examples/example_simplest/students/cs101/__pycache__/report1.cpython-38.pyc
index d71c0905a85dfe88c5fe08fe3973d3de7ff67421..d7c0fcd96cc7cec28f9155fb61bec8c3bb6e00df 100644
Binary files a/examples/example_simplest/students/cs101/__pycache__/report1.cpython-38.pyc and b/examples/example_simplest/students/cs101/__pycache__/report1.cpython-38.pyc differ
diff --git a/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-38.pyc b/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0aeda2d66a25eaae02e31a9c629a9a2887fb7dbd
Binary files /dev/null and b/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-38.pyc differ
diff --git a/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-39.pyc b/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-39.pyc
index 5a8613dee80bdb366573f9b28d6ec044938c16aa..320b3cf336a65641c1c544822f0a7b397f2c8859 100644
Binary files a/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-39.pyc and b/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-39.pyc differ
diff --git a/examples/example_simplest/students/cs101/report1.py b/examples/example_simplest/students/cs101/report1.py
index a50ddcc74e292918fb3a9c7d86c48fbeb012b32b..8e5dfca2a42c1fcae00f20cd81d3a64221b85331 100644
--- a/examples/example_simplest/students/cs101/report1.py
+++ b/examples/example_simplest/students/cs101/report1.py
@@ -13,6 +13,8 @@ class Week1(unittest.TestCase):
 
     def test_reverse(self):
         self.assertEqual(reverse_list([1,2,3]), [3,2,1])
+        # print("Bad output\n\n")
+
 
 import cs101
 class Report1(Report):
diff --git a/examples/example_simplest/students/cs101/report1_grade.py b/examples/example_simplest/students/cs101/report1_grade.py
index 841e8a37a04e5bbfe929af1a743729f271f67cf7..efb1981670ec07e36b2088237d84a7406a9a507f 100644
--- a/examples/example_simplest/students/cs101/report1_grade.py
+++ b/examples/example_simplest/students/cs101/report1_grade.py
@@ -141,7 +141,12 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
 class SequentialTestLoader(unittest.TestLoader):
     def getTestCaseNames(self, testCaseClass):
         test_names = super().getTestCaseNames(testCaseClass)
-        testcase_methods = list(testCaseClass.__dict__.keys())
+        # testcase_methods = list(testCaseClass.__dict__.keys())
+        ls = []
+        for C in testCaseClass.mro():
+            if issubclass(C, unittest.TestCase):
+                ls = list(C.__dict__.keys()) + ls
+        testcase_methods = ls
         test_names.sort(key=testcase_methods.index)
         return test_names
 
@@ -172,15 +177,12 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
-        q_hidden = False
+        # q_hidden = False
         # q_hidden = issubclass(q.__class__, Hidden)
         if question is not None and n+1 != question:
             continue
         suite = loader.loadTestsFromTestCase(q)
-        # print(suite)
-        qtitle = q.__name__
-        # qtitle = q.title if hasattr(q, "title") else q.id()
-        # q.title = qtitle
+        qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__
         q_title_print = "Question %i: %s"%(n+1, qtitle)
         print(q_title_print, end="")
         q.possible = 0
@@ -190,6 +192,7 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
         UTextResult.show_progress_bar = show_progress_bar # Hacky.
+        UTextResult.number = n
 
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
         # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
@@ -452,7 +455,7 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        a ="234"\n        b = "234"\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        return root_dir, relative_path\n\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        # test.countTestCases()\n\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        testcase_methods = list(testCaseClass.__dict__.keys())\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nfrom cs101.homework1 import reverse_list, add\nimport unittest\n\nclass Week1(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(add(2,2), 4)\n        self.assertEqual(add(-100, 5), -95)\n\n    def test_reverse(self):\n        self.assertEqual(reverse_list([1,2,3]), [3,2,1])\n\nimport cs101\nclass Report1(Report):\n    title = "CS 101 Report 1"\n    questions = [(Week1, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs101]'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    @classmethod\n    def mfile(clc):\n        return inspect.getfile(clc)\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n        return root_dir, relative_path, modules\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        for (q,_) in self.questions:\n            q.nL = self.nL # Set maximum line length.\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    number = -1 # HAcky way to set question number.\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        j =self.testsRun\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = UTextResult.number\n\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        item_title = test.shortDescription() # Better for printing (get from cache).\n        # test.countTestCases()\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            # print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) )\n        # key = (self.cache_id(), \'@cache\')\n        # if self._cache_contains[key]\n\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache. Ensures method always produce same result.\n    _cache2 = None  # User-written cache.\n\n    @classmethod\n    def question_title(cls):\n        return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n    @classmethod\n    def reset(cls):\n        print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def shortDescriptionStandard(self):\n        sd = super().shortDescription()\n        if sd == None:\n            sd = self._testMethodName\n        return sd\n\n    def shortDescription(self):\n        # self._testMethodDoc.strip().splitlines()[0].strip()\n        sd = self.shortDescriptionStandard()\n        title = self._cache_get(  (self.cache_id(), \'title\'), sd )\n        return title if title != None else sd\n\n    @property\n    def title(self):\n        return self.shortDescription()\n\n    @title.setter\n    def title(self, value):\n        self._cache_put((self.cache_id(), \'title\'), value)\n\n    # def _callSetUp(self):\n    #     # Always run before method is called.\n    #     print("asdf")\n    #     pass\n    # @classmethod\n    # def setUpClass(cls):\n    #     # self._cache_put((self.cache_id(), \'title\'), value)\n    #     cls.reset()\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        self._ensure_cache_exists() # Make sure cache is there.\n        if self._testMethodDoc != None:\n            # Ensure the cache is eventually updated with the right docstring.\n            self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n        # Fix temp cache here (for using the @cache decorator)\n        self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n        res = testMethod()\n        elapsed = time.time() - t\n        # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    # def unique_cache_id(self):\n    #     k0 = self.cache_id()\n    #     # key = ()\n    #     i = 0\n    #     for i in itertools.count():\n    #         # key = k0 + (i,)\n    #         if i not in self._cache_get( (k0, \'assert\') ):\n    #             break\n    #     return i\n    #     return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self._assert_cache_index = 0\n        # self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n    #\n    # def _cache2_contains(self, key):\n    #     print("Is this needed?")\n    #     self._ensure_cache_exists()\n    #     return key in self.__class__._cache2\n\n    def wrap_assert(self, assert_fun, first, *args, **kwargs):\n        key = (self.cache_id(), \'assert\')\n        if not self._cache_contains(key):\n            print("Warning, framework missing", key)\n        cache = self._cache_get(key, {})\n        id = self._assert_cache_index\n        if not id in cache:\n            print("Warning, framework missing cache index", key, "id =", id)\n        _expected = cache.get(id, first)\n        assert_fun(first, _expected, *args, **kwargs)\n        cache[id] = first\n        self._cache_put(key, cache)\n        self._assert_cache_index += 1\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        self.wrap_assert(self.assertEqual, first, msg)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        # print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)We can be somewhat "hygienic", but newDecorator still isn\'t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it\'s not a big issue\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        # testcase_methods = list(testCaseClass.__dict__.keys())\n        ls = []\n        for C in testCaseClass.mro():\n            if issubclass(C, unittest.TestCase):\n                ls = list(C.__dict__.keys()) + ls\n        testcase_methods = ls\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        # q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n        UTextResult.number = n\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nfrom cs101.homework1 import reverse_list, add\nimport unittest\n\nclass Week1(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(add(2,2), 4)\n        self.assertEqual(add(-100, 5), -95)\n\n    def test_reverse(self):\n        self.assertEqual(reverse_list([1,2,3]), [3,2,1])\n        # print("Bad output\\n\\n")\n\n\nimport cs101\nclass Report1(Report):\n    title = "CS 101 Report 1"\n    questions = [(Week1, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs101]'
 report1_payload = '8004953f000000000000007d948c055765656b31947d948c2c6e6f20636163686520736565205f73657475705f616e737765727320696e20756e69746772616465322e7079948873732e'
 name="Report1"
 
diff --git a/unitgrade_private2/__pycache__/docker_helpers.cpython-38.pyc b/unitgrade_private2/__pycache__/docker_helpers.cpython-38.pyc
index 61e1a3ce0d3a883def761a67fc6197ab8888857b..64a1893f40852651e6f4f6145d3b8ddb4b5e6dcb 100644
Binary files a/unitgrade_private2/__pycache__/docker_helpers.cpython-38.pyc and b/unitgrade_private2/__pycache__/docker_helpers.cpython-38.pyc differ
diff --git a/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-38.pyc b/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-38.pyc
index 90a3c83fa1f1577c7c30c70bc20fb41d24589e87..07472b0de4bf0b0f74be03f08a47140647e9cb27 100644
Binary files a/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-38.pyc and b/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-38.pyc differ
diff --git a/unitgrade_private2/docker_helpers.py b/unitgrade_private2/docker_helpers.py
index a16d66f485187b19c947ac3e033f5b47ca7507e0..cde3e6e03ba0c0104e0f61ec7aca43991d15c59b 100644
--- a/unitgrade_private2/docker_helpers.py
+++ b/unitgrade_private2/docker_helpers.py
@@ -148,7 +148,7 @@ def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file,
     fcom = f"{cdcom}  && {dcom}"
     print("> Running docker command")
     print(fcom)
-
+    init = time.time() - start
     thtools.execute_command(fcom.split())
     # get token file:
 
@@ -157,5 +157,5 @@ def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file,
     for t in tokens:
         print("Source image produced token", t)
     elapsed = time.time() - start
-    print("Elapsed time is", elapsed)
+    print("Elapsed time is", elapsed, f"({init=} seconds)")
     return tokens[0]