Skip to content
Snippets Groups Projects
Commit b6ead38d authored by tuhe's avatar tuhe
Browse files

Update to version 1.3: Added progress bars

parent 3113b998
No related branches found
No related tags found
No related merge requests found
from unitgrade.version import __version__ from unitgrade.version import __version__
import os import os
# DONT't import stuff here since install script requires __version__ # DONT't import stuff here since install script requires __version__
def cache_write(object, file_name, verbose=True): def cache_write(object, file_name, verbose=True):
...@@ -28,4 +29,4 @@ def cache_read(file_name): ...@@ -28,4 +29,4 @@ def cache_read(file_name):
else: else:
return None return None
from unitgrade.unitgrade import Hidden, myround, mfloor, msum from unitgrade.unitgrade import Hidden, myround, mfloor, msum, Capturing, ActiveProgress
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
...@@ -11,6 +11,9 @@ from io import StringIO ...@@ -11,6 +11,9 @@ from io import StringIO
import collections import collections
import inspect import inspect
import re import re
import threading
import tqdm
import time
myround = lambda x: np.round(x) # required. myround = lambda x: np.round(x) # required.
msum = lambda x: sum(x) msum = lambda x: sum(x)
...@@ -71,8 +74,11 @@ class QItem(unittest.TestCase): ...@@ -71,8 +74,11 @@ class QItem(unittest.TestCase):
title = None title = None
testfun = None testfun = None
tol = 0 tol = 0
estimated_time = 0.42
_precomputed_payload = None
_computed_answer = None # Internal helper to later get results. _computed_answer = None # Internal helper to later get results.
# _precomputed_payload = None
def __init__(self, working_directory=None, correct_answer_payload=None, question=None, *args, **kwargs): def __init__(self, working_directory=None, correct_answer_payload=None, question=None, *args, **kwargs):
if self.tol > 0 and self.testfun is None: if self.tol > 0 and self.testfun is None:
self.testfun = self.assertL2Relative self.testfun = self.assertL2Relative
...@@ -82,6 +88,8 @@ class QItem(unittest.TestCase): ...@@ -82,6 +88,8 @@ class QItem(unittest.TestCase):
self.name = self.__class__.__name__ self.name = self.__class__.__name__
self._correct_answer_payload = correct_answer_payload self._correct_answer_payload = correct_answer_payload
self.question = None self.question = None
# self.a = "not set"
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.title is None: if self.title is None:
self.title = self.name self.title = self.name
...@@ -105,7 +113,14 @@ class QItem(unittest.TestCase): ...@@ -105,7 +113,14 @@ class QItem(unittest.TestCase):
print(f"Element-wise differences {diff.tolist()}") print(f"Element-wise differences {diff.tolist()}")
self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}") self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")
def precomputed_resources(self): # def set_precomputed_payload(self, payload):
# self.a = "blaaah"
# self._precomputed_payload = payload
def precomputed_payload(self):
return self._precomputed_payload
def precompute_payload(self):
# Pre-compute resources to include in tests (useful for getting around rng). # Pre-compute resources to include in tests (useful for getting around rng).
pass pass
...@@ -128,8 +143,8 @@ class QItem(unittest.TestCase): ...@@ -128,8 +143,8 @@ class QItem(unittest.TestCase):
correct = self._correct_answer_payload correct = self._correct_answer_payload
try: try:
if unmute: if unmute: # Required to not mix together print stuff.
print("\n") print("")
computed = self.compute_answer(unmute=unmute) computed = self.compute_answer(unmute=unmute)
except Exception as e: except Exception as e:
if not passall: if not passall:
...@@ -190,8 +205,8 @@ class QPrintItem(QItem): ...@@ -190,8 +205,8 @@ class QPrintItem(QItem):
def process_output(self, res, txt, numbers): def process_output(self, res, txt, numbers):
return (res, txt) return (res, txt)
def compute_local(self): # def compute_local(self): # Dunno
pass # pass
def compute_answer(self, unmute=False): def compute_answer(self, unmute=False):
with Capturing(unmute=unmute) as output: with Capturing(unmute=unmute) as output:
...@@ -215,6 +230,7 @@ class QuestionGroup(metaclass=OrderedClassMembers): ...@@ -215,6 +230,7 @@ class QuestionGroup(metaclass=OrderedClassMembers):
items = None items = None
partially_scored = False partially_scored = False
t_init = 0 # Time spend on initialization (placeholder; set this externally). t_init = 0 # Time spend on initialization (placeholder; set this externally).
estimated_time = 0.42
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.name = self.__class__.__name__ self.name = self.__class__.__name__
...@@ -224,6 +240,11 @@ class QuestionGroup(metaclass=OrderedClassMembers): ...@@ -224,6 +240,11 @@ class QuestionGroup(metaclass=OrderedClassMembers):
for gt in members: for gt in members:
self.items.append( (gt, 1) ) self.items.append( (gt, 1) )
self.items = [(I(question=self), w) for I, w in self.items] self.items = [(I(question=self), w) for I, w in self.items]
self.has_called_init_ = False
def init(self):
# Can be used to set resources relevant for this question instance.
pass
class Report(): class Report():
title = "report title" title = "report title"
...@@ -239,9 +260,13 @@ class Report(): ...@@ -239,9 +260,13 @@ class Report():
import time import time
qs = [] # Has to accumulate to new array otherwise the setup/evaluation steps cannot be run in sequence. qs = [] # Has to accumulate to new array otherwise the setup/evaluation steps cannot be run in sequence.
for k, (Q, w) in enumerate(self.questions): for k, (Q, w) in enumerate(self.questions):
# print(k, Q)
start = time.time() start = time.time()
q = (Q(working_directory=self.wdir), w) q = (Q(working_directory=self.wdir), w)
q[0].t_init = time.time() - start q[0].t_init = time.time() - start
# if time.time() -start > 0.2:
# raise Exception(Q, "Question takes to long to initialize. Use the init() function to set local variables instead")
# print(time.time()-start)
qs.append(q) qs.append(q)
self.questions = qs self.questions = qs
# self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions] # self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions]
...@@ -257,6 +282,7 @@ class Report(): ...@@ -257,6 +282,7 @@ class Report():
else: else:
print(s) print(s)
def set_payload(self, payloads, strict=False): def set_payload(self, payloads, strict=False):
for q, _ in self.questions: for q, _ in self.questions:
for item, _ in q.items: for item, _ in q.items:
...@@ -268,7 +294,9 @@ class Report(): ...@@ -268,7 +294,9 @@ class Report():
print(s) print(s)
else: else:
item._correct_answer_payload = payloads[q.name][item.name]['payload'] item._correct_answer_payload = payloads[q.name][item.name]['payload']
if "precomputed" in payloads[q.name][item.name]: item.estimated_time = payloads[q.name][item.name]['time']
q.estimated_time = payloads[q.name]['time']
if "precomputed" in payloads[q.name][item.name]: # Consider removing later.
item._precomputed_payload = payloads[q.name][item.name]['precomputed'] item._precomputed_payload = payloads[q.name][item.name]['precomputed']
self.payloads = payloads self.payloads = payloads
...@@ -297,3 +325,31 @@ def extract_numbers(txt): ...@@ -297,3 +325,31 @@ def extract_numbers(txt):
print(txt) print(txt)
raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all)) raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))
return all return all
class ActiveProgress():
def __init__(self, t, start=True, title="my progress bar"):
self.t = t
self._running = False
self.title = title
if start:
self.start()
def start(self):
self._running = True
self.thread = threading.Thread(target=self.run, args=(10,))
self.thread.start()
def terminate(self):
self._running = False
self.thread.join()
sys.stdout.flush()
def run(self, n):
dt = 0.1
n = int(np.round(self.t/dt))
for _ in tqdm.tqdm(range(n), file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100, bar_format='{l_bar}{bar}| [{elapsed}<{remaining}]'): #, unit_scale=dt, unit='seconds'):
if not self._running:
break
time.sleep(dt)
\ No newline at end of file
...@@ -2,7 +2,8 @@ import numpy as np ...@@ -2,7 +2,8 @@ import numpy as np
from tabulate import tabulate from tabulate import tabulate
from datetime import datetime from datetime import datetime
import pyfiglet import pyfiglet
from unitgrade import Hidden, myround, msum, mfloor from unitgrade import Hidden, myround, msum, mfloor, ActiveProgress
# import unitgrade
from unitgrade import __version__ from unitgrade import __version__
# from unitgrade.unitgrade import Hidden # from unitgrade.unitgrade import Hidden
...@@ -12,6 +13,10 @@ import inspect ...@@ -12,6 +13,10 @@ import inspect
import os import os
import argparse import argparse
import sys 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
#from threading import Thread # This import presents a problem for the minify-code compression tool.
parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example:
To run all tests in a report: To run all tests in a report:
...@@ -62,8 +67,8 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass ...@@ -62,8 +67,8 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass
unmute = args.unmute unmute = args.unmute
if passall is None: if passall is None:
passall = args.passall passall = args.passall
# print(passall)
results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute) 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)
if question is None: if question is None:
print("Provisional evaluation") print("Provisional evaluation")
...@@ -85,7 +90,10 @@ def upack(q): ...@@ -85,7 +90,10 @@ def upack(q):
h = np.asarray(h) h = np.asarray(h)
return h[:,0], h[:,1], h[:,2], return h[:,0], h[:,1], h[:,2],
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):
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):
from unitgrade.version import __version__ from unitgrade.version import __version__
now = datetime.now() now = datetime.now()
ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
...@@ -100,40 +108,71 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa ...@@ -100,40 +108,71 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
print(f"Loaded answers from: ", report.computed_answers_file, "\n") print(f"Loaded answers from: ", report.computed_answers_file, "\n")
table_data = [] table_data = []
nL = 80 nL = 80
t_start = time.time()
score = {} score = {}
for n, (q, w) in enumerate(report.questions): for n, (q, w) in enumerate(report.questions):
q_hidden = issubclass(q.__class__, Hidden) q_hidden = issubclass(q.__class__, Hidden)
# report.globals = q.globals
# q.globals = report.globals
if question is not None and n+1 != question: if question is not None and n+1 != question:
continue continue
# Don't use f format strings. # Don't use f format strings.
print(f"Question {n+1}: {q.title}" + (" (" + str( np.round(report.payloads[q.name].get('time', 0), 2) ) + " seconds)" if q.name in report.payloads else "" ) ) q_title_print = "Question %i: %s"%(n+1, q.title)
print("="*nL) print(q_title_print, end="")
# sys.stdout.flush()
q.possible = 0 q.possible = 0
q.obtained = 0 q.obtained = 0
q_ = {} # Gather score in this class. q_ = {} # Gather score in this class.
# Active progress bar.
for j, (item, iw) in enumerate(q.items): for j, (item, iw) in enumerate(q.items):
if qitem is not None and question is not None and item is not None and j+1 != qitem: if qitem is not None and question is not None and item is not None and j+1 != qitem:
continue continue
if not q.has_called_init_:
start = time.time()
cc = None
if show_progress_bar:
# cc.start()
cc = ActiveProgress(t=q.estimated_time, title=q_title_print)
from unitgrade import Capturing
#eval('from unitgrade import Capturing')
with eval('Capturing')(unmute=unmute): # Clunky import syntax is required bc. of minify issue.
q.init() # Initialize the question. Useful for sharing resources.
if show_progress_bar:
cc.terminate()
print(q_title_print, end="")
q.has_called_init_ = True
q_time =np.round( time.time()-start, 2)
print(" "* max(0,nL - len(q_title_print) ) + " (" + str(q_time) + " seconds)") # if q.name in report.payloads else "")
print("=" * nL)
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="")
ss = f"*** q{n+1}.{j+1}) {item.title}"
el = nL-4
if len(ss) < el:
ss += '.'*(el-len(ss))
hidden = issubclass(item.__class__, Hidden) hidden = issubclass(item.__class__, Hidden)
if not hidden: # if not hidden:
print(ss, end="") # print(ss, end="")
sys.stdout.flush() # sys.stdout.flush()
import time
start = time.time() start = time.time()
(current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent) (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)
q_[j] = {'w': iw, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title} q_[j] = {'w': iw, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title}
tsecs = np.round(time.time()-start, 2) 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="")
# q.possible += possible * iw
# q.obtained += current * iw
if not hidden: if not hidden:
ss = "PASS" if current == possible else "*** FAILED" ss = "PASS" if current == possible else "*** FAILED"
ss += " ("+ str(tsecs) + " seconds)" ss += " ("+ str(tsecs) + " seconds)"
...@@ -161,7 +200,14 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa ...@@ -161,7 +200,14 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
report.obtained = obtained report.obtained = obtained
now = datetime.now() now = datetime.now()
dt_string = now.strftime("%H:%M:%S") dt_string = now.strftime("%H:%M:%S")
print(f"Completed: "+ dt_string)
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) ]) table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])
results = {'total': (obtained, possible), 'details': score} results = {'total': (obtained, possible), 'details': score}
return results, table_data return results, table_data
__version__ = "0.1.2" __version__ = "0.1.3"
\ No newline at end of file \ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment