Advertisement
Guest User

Untitled

a guest
Mar 20th, 2019
75
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.98 KB | None | 0 0
  1. from .graphtimer import CATEGORY10
  2.  
  3.  
  4. class MatPlotLib:
  5. def _graph_times(self, graph, data, domain, colors, error, fmt):
  6. for results, color in zip(data, colors):
  7. values = [v.value for v in results]
  8. if error:
  9. errors = zip(*[v.errors or [] for v in results])
  10. for error in errors:
  11. lower, upper = zip(*error)
  12. graph.fill_between(domain, upper, lower, facecolor=color, edgecolor=None, alpha=0.1)
  13. yield graph.plot(domain, values, fmt, color=color)[0]
  14.  
  15. def graph(self, graph, data, domain, *, functions=None, colors=CATEGORY10, title=None, legend=True, error=True,
  16. x_label='Input', y_label='Time [s]', fmt='-'):
  17. lines = list(self._graph_times(graph, data, domain, colors, error, fmt))
  18. if x_label is not None:
  19. graph.set_xlabel(x_label)
  20. if y_label is not None:
  21. graph.set_ylabel(y_label)
  22. if title is not None and hasattr(graph, 'set_title'):
  23. graph.set_title(title)
  24. if legend and functions is not None and hasattr(graph, 'legend'):
  25. graph.legend(lines, [fn.__name__ for fn in functions], loc=0)
  26. return lines
  27.  
  28. import timeit
  29.  
  30. SENTINAL = object()
  31.  
  32.  
  33. class MultiTimer:
  34. """Interface to timeit.Timer to ease timing over multiple functions."""
  35. def __init__(self, functions, timer=timeit.Timer):
  36. self.timer = timer
  37. self.functions = functions
  38.  
  39. def build_timer(self, fn, domain, stmt='fn(*args)', setup='pass', timer=SENTINAL, globals=SENTINAL,
  40. args_conv=SENTINAL):
  41. """Build a timeit.Timer"""
  42. if not isinstance(domain, tuple):
  43. domain = domain,
  44. if args_conv is not SENTINAL:
  45. domain = args_conv(*domain)
  46. if not isinstance(domain, tuple):
  47. domain = domain,
  48.  
  49. if globals is SENTINAL:
  50. globals = {}
  51. else:
  52. globals = globals.copy()
  53. globals.update({'fn': fn, 'args': domain})
  54.  
  55. # print(f'{self.timer}({stmt!r}, {setup!r}, {timer!r}, {globals!r})')
  56.  
  57. if timer is SENTINAL:
  58. timer = timeit.default_timer
  59.  
  60. return self.timer(stmt, setup, timer, globals=globals)
  61.  
  62. def build_timers(self, domain, *args, **kwargs):
  63. """Build multiple timers from various inputs and functions"""
  64. return [
  65. [
  66. self.build_timer(fn, dom, *args, **kwargs)
  67. for fn in self.functions
  68. ]
  69. for dom in domain
  70. ]
  71.  
  72. def _call(self, domain, repeat, call, *args, **kwargs):
  73. """Helper function to generate timing data."""
  74. if len(domain) == 0:
  75. raise ValueError('domain must have at least one argument.')
  76.  
  77. functions = self.build_timers(domain, *args, **kwargs)
  78. output = [[[] for _ in domain] for _ in functions[0]]
  79. for _ in range(repeat):
  80. for j, fns in enumerate(functions):
  81. for i, fn in enumerate(fns):
  82. output[i][j].append(call(fn))
  83. return output
  84.  
  85. def repeat(self, domain, repeat, number, *args, **kwargs):
  86. """Interface to timeit.Timer.repeat. `domain` is the values to pass to the functions."""
  87. return self._call(domain, repeat, lambda f: f.timeit(number), *args, **kwargs)
  88.  
  89. def timeit(self, domain, number, *args, **kwargs):
  90. """Interface to timeit.Timer.timeit. `domain` is the values to pass to the functions."""
  91. return [
  92. [value[0] for value in values]
  93. for values in self.repeat(domain, 1, number, *args, **kwargs)
  94. ]
  95.  
  96. def autorange(self, domain, *args, **kwargs):
  97. """Interface to timeit.Timer.autorange. `domain` is the values to pass to the functions."""
  98. return [
  99. [value[0] for value in values]
  100. for values in self._call(domain, 1, lambda f: f.autorange(), *args, **kwargs)
  101. ]
  102.  
  103.  
  104. class TimerNamespaceMeta(type):
  105. """Convenience class to ease creation of a MultiTimer."""
  106. def __new__(mcs, name, bases, attrs):
  107. if 'functions' in attrs:
  108. raise TypeError('FunctionTimers cannot define `functions`')
  109. if 'multi_timer' in attrs:
  110. raise TypeError('FunctionTimers cannot define `multi_timer`')
  111.  
  112. ret: TimerNamespace = super().__new__(mcs, name, bases, attrs)
  113. functions = [v for k, v in attrs.items() if k.startswith('test')]
  114. ret.functions = functions
  115. ret.multi_timer = ret.MULTI_TIMER(functions, ret.TIMER)
  116. return ret
  117.  
  118.  
  119. class TimerNamespace(metaclass=TimerNamespaceMeta):
  120. """Convenience class to ease creation of a MultiTimer."""
  121. TIMER = timeit.Timer
  122. MULTI_TIMER = MultiTimer
  123.  
  124. from .graph import MatPlotLib
  125.  
  126.  
  127. class Plotter:
  128. """Interface to the timer object. Returns objects made to ease usage."""
  129. def __init__(self, timer):
  130. self.timer = getattr(timer, 'multi_timer', timer)
  131.  
  132. def timeit(self, number, domain, *args, **kwargs):
  133. """Interface to self.timer.timeit. Returns a PlotValues."""
  134. return self.repeat(1, number, domain, *args, **kwargs).min(errors=None)
  135.  
  136. def repeat(self, repeat, number, domain, *args, **kwargs):
  137. """Interface to self.timer.repeat. Returns a PlotTimings."""
  138. return PlotTimings(
  139. self.timer.repeat(domain, repeat, number, *args, **kwargs),
  140. {
  141. 'functions': self.timer.functions,
  142. 'domain': domain
  143. }
  144. )
  145.  
  146.  
  147. class _DataSet:
  148. """Holds timeit values and defines statistical methods around them."""
  149. def __init__(self, values):
  150. self.values = sorted(values)
  151.  
  152. def quartile_indexes(self, outlier):
  153. """Generates the quartile indexes. Uses tukey's fences to remove outliers."""
  154. delta = (len(self.values) - 1) / 4
  155. quartiles = [int(round(delta * i)) for i in range(5)]
  156. if outlier is not None:
  157. if outlier < 0:
  158. raise ValueError("outlier should be non-negative.")
  159. iqr = outlier * (self.values[quartiles[3]] - self.values[quartiles[1]])
  160. low = self.values[quartiles[1]] - iqr
  161. high = self.values[quartiles[3]] + iqr
  162.  
  163. for i, v in enumerate(self.values):
  164. if v >= low:
  165. quartiles[0] = i
  166. break
  167.  
  168. for i, v in reversed(list(enumerate(self.values))):
  169. if v <= high:
  170. quartiles[4] = i
  171. break
  172. return tuple(quartiles)
  173.  
  174. def errors(self, errors, outlier):
  175. """Returns tuples containing the quartiles wanted."""
  176. if errors is None:
  177. return None
  178. quartiles = self.quartile_indexes(outlier)
  179. # Allow out of quartile error bars using -1 and 5.
  180. quartiles += (-1, 0)
  181. return [
  182. (
  183. self.values[quartiles[start]],
  184. self.values[quartiles[stop]]
  185. )
  186. for start, stop in errors
  187. ]
  188.  
  189. def quartile(self, quartile, outlier):
  190. """Return the value of the quartile provided."""
  191. quartiles = self.quartile_indexes(outlier)
  192. return self.values[quartiles[quartile]]
  193.  
  194. def mean(self, start, end, outlier):
  195. """Return the mean of the values over the quartiles specified."""
  196. quartiles = self.quartile_indexes(outlier)
  197. start = quartiles[start]
  198. end = quartiles[end]
  199. return sum(self.values[start:end + 1]) / (1 + end - start)
  200.  
  201.  
  202. class PlotTimings:
  203. """Thin interface over _DataSet"""
  204. def __init__(self, data, kwargs):
  205. self.data = [
  206. [_DataSet(results) for results in function_values]
  207. for function_values in data
  208. ]
  209. self.kwargs = kwargs
  210.  
  211. def quartile(self, quartile, *, errors=None, outlier=1.5):
  212. """Interface to _DataSet.quartile and errors. Returns a PlotValues."""
  213. return PlotValues(
  214. [
  215. [
  216. _DataValues(
  217. ds.quartile(quartile, outlier),
  218. ds.errors(errors, outlier)
  219. )
  220. for ds in function_values
  221. ]
  222. for function_values in self.data
  223. ],
  224. self.kwargs
  225. )
  226.  
  227. def min(self, *, errors=((-1, 3),), outlier=1.5):
  228. """Return the Q1 value and show the error from Q-1 Q3."""
  229. return self.quartile(0, errors=errors, outlier=outlier)
  230.  
  231. def max(self, *, errors=((1, 5),), outlier=1.5):
  232. """Return the Q4 value and show the error from Q1 Q5."""
  233. return self.quartile(4, errors=errors, outlier=outlier)
  234.  
  235. def mean(self, start=0, end=4, *, errors=((1, 3),), outlier=1.5):
  236. """Interface to _DataSet.mean and errors. Returns a PlotValues."""
  237. return PlotValues(
  238. [
  239. [
  240. _DataValues(
  241. ds.mean(start, end, outlier),
  242. ds.errors(errors, outlier)
  243. )
  244. for ds in function_values
  245. ]
  246. for function_values in self.data
  247. ],
  248. self.kwargs
  249. )
  250.  
  251.  
  252. class _DataValues:
  253. """Holds the wanted statistical data from the timings."""
  254. def __init__(self, value, errors):
  255. self.value = value
  256. self.errors = errors
  257.  
  258.  
  259. class PlotValues:
  260. """Thin interface to Graph.graph."""
  261. def __init__(self, data, kwargs):
  262. self.data = data
  263. self.kwargs = kwargs
  264.  
  265. def plot(self, graph, graph_lib=MatPlotLib, **kwargs):
  266. g = graph_lib()
  267. return g.graph(
  268. graph,
  269. self.data,
  270. self.kwargs.pop('domain'),
  271. functions=self.kwargs.pop('functions'),
  272. **kwargs
  273. )
  274.  
  275. import time
  276. import math
  277.  
  278. import matplotlib.pyplot as plt
  279. import numpy as np
  280.  
  281. from graphtimer import flat, Plotter, TimerNamespace
  282.  
  283.  
  284. class UnoptimisedRange(object):
  285. def __init__(self, size):
  286. self.size = size
  287.  
  288. def __getitem__(self, i):
  289. if i >= self.size:
  290. raise IndexError()
  291. return i
  292.  
  293.  
  294. class Peilonrayz(TimerNamespace):
  295. def test_comprehension(iterable):
  296. return [i for i in iterable]
  297.  
  298. def test_append(iterable):
  299. a = []
  300. append = a.append
  301. for i in iterable:
  302. append(i)
  303. return a
  304.  
  305.  
  306. SCALE = 10.
  307.  
  308.  
  309. class Graipher(TimerNamespace):
  310. def test_o_n(n):
  311. time.sleep(n / SCALE)
  312.  
  313. def test_o_n2(n):
  314. time.sleep(n ** 2 / SCALE)
  315.  
  316. def test_o_log(n):
  317. time.sleep(math.log(n + 1) / SCALE)
  318.  
  319. def test_o_exp(n):
  320. time.sleep((math.exp(n) - 1) / SCALE)
  321.  
  322. def test_o_nlog(n):
  323. time.sleep(n * math.log(n + 1) / SCALE)
  324.  
  325.  
  326. class Reverse(TimerNamespace):
  327. def test_orig(stri):
  328. output = ''
  329. length = len(stri)
  330. while length > 0:
  331. output += stri[-1]
  332. stri, length = (stri[0:length - 1], length - 1)
  333. return output
  334.  
  335. def test_g(s):
  336. return s[::-1]
  337.  
  338. def test_s(s):
  339. return ''.join(reversed(s))
  340.  
  341.  
  342. def main():
  343. # Reverse
  344. fig, axs = plt.subplots()
  345. axs.set_yscale('log')
  346. axs.set_xscale('log')
  347. (
  348. Plotter(Reverse)
  349. .repeat(10, 10, np.logspace(0, 5), args_conv=lambda i: ' '*int(i))
  350. .min()
  351. .plot(axs, title='Reverse', fmt='-o')
  352. )
  353. fig.show()
  354.  
  355. # Graipher
  356. fig, axs = plt.subplots()
  357. (
  358. Plotter(Graipher)
  359. .repeat(2, 1, [i / 10 for i in range(10)])
  360. .min()
  361. .plot(axs, title='Graipher', fmt='-o')
  362. )
  363. fig.show()
  364.  
  365. # Peilonrayz
  366. fig, axs = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True)
  367. p = Plotter(Peilonrayz)
  368. axis = [
  369. ('Range', {'args_conv': range}),
  370. ('List', {'args_conv': lambda i: list(range(i))}),
  371. ('Unoptimised', {'args_conv': UnoptimisedRange}),
  372. ]
  373. for graph, (title, kwargs) in zip(iter(flat(axs)), axis):
  374. (
  375. p.repeat(100, 5, list(range(0, 10001, 1000)), **kwargs)
  376. .min(errors=((-1, 3), (-1, 4)))
  377. .plot(graph, title=title)
  378. )
  379. fig.show()
  380.  
  381.  
  382. if __name__ == '__main__':
  383. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement