You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

418 lines
13 KiB

  1. # -*- coding: utf-8 -*-
  2. import nilmdb
  3. from nilmdb.utils.printf import *
  4. from nilmdb.utils import datetime_tz
  5. from nose.tools import *
  6. from nose.tools import assert_raises
  7. import itertools
  8. from nilmdb.utils.interval import IntervalError
  9. from nilmdb.server.interval import Interval, DBInterval, IntervalSet
  10. # so we can test them separately
  11. from nilmdb.utils.interval import Interval as UtilsInterval
  12. from testutil.helpers import *
  13. import unittest
  14. # set to False to skip live renders
  15. do_live_renders = False
  16. def render(iset, description = "", live = True):
  17. import testutil.renderdot as renderdot
  18. r = renderdot.RBTreeRenderer(iset.tree)
  19. return r.render(description, live and do_live_renders)
  20. def makeset(string):
  21. """Build an IntervalSet from a string, for testing purposes
  22. Each character is 1 second
  23. [ = interval start
  24. | = interval end + next start
  25. ] = interval end
  26. . = zero-width interval (identical start and end)
  27. anything else is ignored
  28. """
  29. iset = IntervalSet()
  30. for i, c in enumerate(string):
  31. day = i + 10000
  32. if (c == "["):
  33. start = day
  34. elif (c == "|"):
  35. iset += Interval(start, day)
  36. start = day
  37. elif (c == ")"):
  38. iset += Interval(start, day)
  39. del start
  40. elif (c == "."):
  41. iset += Interval(day, day)
  42. return iset
  43. class TestInterval:
  44. def test_client_interval(self):
  45. # Run interval tests against the Python version of Interval.
  46. global Interval
  47. NilmdbInterval = Interval
  48. Interval = UtilsInterval
  49. self.test_interval()
  50. self.test_interval_intersect()
  51. Interval = NilmdbInterval
  52. # Other helpers in nilmdb.utils.interval
  53. i = [ UtilsInterval(1,2), UtilsInterval(2,3), UtilsInterval(4,5) ]
  54. eq_(list(nilmdb.utils.interval.optimize(i)),
  55. [ UtilsInterval(1,3), UtilsInterval(4,5) ])
  56. eq_(UtilsInterval(1234567890123456, 1234567890654321).human_string(),
  57. "[ Fri, 13 Feb 2009 18:31:30.123456 -0500 -> " +
  58. "Fri, 13 Feb 2009 18:31:30.654321 -0500 ]")
  59. def test_interval(self):
  60. # Test Interval class
  61. os.environ['TZ'] = "America/New_York"
  62. datetime_tz._localtz = None
  63. (d1, d2, d3) = [ nilmdb.utils.time.parse_time(x)
  64. for x in [ "03/24/2012", "03/25/2012", "03/26/2012" ] ]
  65. # basic construction
  66. i = Interval(d1, d2)
  67. i = Interval(d1, d3)
  68. eq_(i.start, d1)
  69. eq_(i.end, d3)
  70. # assignment is allowed, but not verified
  71. i.start = d2
  72. #with assert_raises(IntervalError):
  73. # i.end = d1
  74. i.start = d1
  75. i.end = d2
  76. # end before start
  77. with assert_raises(IntervalError):
  78. i = Interval(d3, d1)
  79. # compare
  80. assert(Interval(d1, d2) == Interval(d1, d2))
  81. assert(Interval(d1, d2) < Interval(d1, d3))
  82. assert(Interval(d1, d3) > Interval(d1, d2))
  83. assert(Interval(d1, d2) < Interval(d2, d3))
  84. assert(Interval(d1, d3) < Interval(d2, d3))
  85. assert(Interval(d2, d2+1) > Interval(d1, d3))
  86. assert(Interval(d3, d3+1) == Interval(d3, d3+1))
  87. #with assert_raises(TypeError): # was AttributeError, that's wrong
  88. # x = (i == 123)
  89. # subset
  90. eq_(Interval(d1, d3).subset(d1, d2), Interval(d1, d2))
  91. with assert_raises(IntervalError):
  92. x = Interval(d2, d3).subset(d1, d2)
  93. # big integers, negative integers
  94. x = Interval(5000111222000000, 6000111222000000)
  95. eq_(str(x), "[5000111222000000 -> 6000111222000000)")
  96. x = Interval(-5000111222000000, -4000111222000000)
  97. eq_(str(x), "[-5000111222000000 -> -4000111222000000)")
  98. # misc
  99. i = Interval(d1, d2)
  100. eq_(repr(i), repr(eval(repr(i))))
  101. eq_(str(i), "[1332561600000000 -> 1332648000000000)")
  102. def test_interval_intersect(self):
  103. # Test Interval intersections
  104. dates = [ 100, 200, 300, 400 ]
  105. perm = list(itertools.permutations(dates, 2))
  106. prod = list(itertools.product(perm, perm))
  107. should_intersect = {
  108. False: [4, 5, 8, 20, 48, 56, 60, 96, 97, 100],
  109. True: [0, 1, 2, 12, 13, 14, 16, 17, 24, 25, 26, 28, 29,
  110. 32, 49, 50, 52, 53, 61, 62, 64, 65, 68, 98, 101, 104]
  111. }
  112. for i,((a,b),(c,d)) in enumerate(prod):
  113. try:
  114. i1 = Interval(a, b)
  115. i2 = Interval(c, d)
  116. eq_(i1.intersects(i2), i2.intersects(i1))
  117. in_(i, should_intersect[i1.intersects(i2)])
  118. except IntervalError:
  119. assert(i not in should_intersect[True] and
  120. i not in should_intersect[False])
  121. with assert_raises(TypeError):
  122. x = i1.intersects(1234)
  123. def test_intervalset_construct(self):
  124. # Test IntervalSet construction
  125. dates = [ 100, 200, 300, 400 ]
  126. a = Interval(dates[0], dates[1])
  127. b = Interval(dates[1], dates[2])
  128. c = Interval(dates[0], dates[2])
  129. d = Interval(dates[2], dates[3])
  130. iseta = IntervalSet(a)
  131. isetb = IntervalSet([a, b])
  132. isetc = IntervalSet([a])
  133. ne_(iseta, isetb)
  134. eq_(iseta, isetc)
  135. with assert_raises(TypeError):
  136. x = iseta != 3
  137. ne_(IntervalSet(a), IntervalSet(b))
  138. # Note that assignment makes a new reference (not a copy)
  139. isetd = IntervalSet(isetb)
  140. isete = isetd
  141. eq_(isetd, isetb)
  142. eq_(isetd, isete)
  143. isetd -= a
  144. ne_(isetd, isetb)
  145. eq_(isetd, isete)
  146. # test iterator
  147. for interval in iseta:
  148. pass
  149. # overlap
  150. with assert_raises(IntervalError):
  151. x = IntervalSet([a, b, c])
  152. # bad types
  153. with assert_raises(Exception):
  154. x = IntervalSet([1, 2])
  155. iset = IntervalSet(isetb) # test iterator
  156. eq_(iset, isetb)
  157. eq_(len(iset), 2)
  158. eq_(len(IntervalSet()), 0)
  159. # Test adding
  160. iset = IntervalSet(a)
  161. iset += IntervalSet(b)
  162. eq_(iset, IntervalSet([a, b]))
  163. iset = IntervalSet(a)
  164. iset += b
  165. eq_(iset, IntervalSet([a, b]))
  166. iset = IntervalSet(a)
  167. iset.iadd_nocheck(b)
  168. eq_(iset, IntervalSet([a, b]))
  169. iset = IntervalSet(a) + IntervalSet(b)
  170. eq_(iset, IntervalSet([a, b]))
  171. iset = IntervalSet(b) + a
  172. eq_(iset, IntervalSet([a, b]))
  173. # A set consisting of [0-1],[1-2] should match a set consisting of [0-2]
  174. eq_(IntervalSet([a,b]), IntervalSet([c]))
  175. # Etc
  176. ne_(IntervalSet([a,d]), IntervalSet([c]))
  177. ne_(IntervalSet([c]), IntervalSet([a,d]))
  178. ne_(IntervalSet([c,d]), IntervalSet([b,d]))
  179. # misc
  180. eq_(repr(iset), repr(eval(repr(iset))))
  181. eq_(str(iset),
  182. "[[100 -> 200), [200 -> 300)]")
  183. def test_intervalset_geniset(self):
  184. # Test basic iset construction
  185. eq_(makeset(" [----) "),
  186. makeset(" [-|--) "))
  187. eq_(makeset("[) [--) ") +
  188. makeset(" [) [--)"),
  189. makeset("[|) [-----)"))
  190. eq_(makeset(" [-------)"),
  191. makeset(" [-|-----|"))
  192. def test_intervalset_intersect_difference(self):
  193. # Test intersection (&)
  194. with assert_raises(TypeError): # was AttributeError
  195. x = makeset("[--)") & 1234
  196. def do_test(a, b, c, d):
  197. # a & b == c
  198. ab = IntervalSet()
  199. for x in b:
  200. for i in (a & x):
  201. ab += i
  202. eq_(ab,c)
  203. # a \ b == d
  204. eq_(IntervalSet(nilmdb.utils.interval.set_difference(a,b)), d)
  205. # Intersection with intervals
  206. do_test(makeset("[---|---)[)"),
  207. makeset(" [------) "),
  208. makeset(" [-----) "), # intersection
  209. makeset("[-) [)")) # difference
  210. do_test(makeset("[---------)"),
  211. makeset(" [---) "),
  212. makeset(" [---) "), # intersection
  213. makeset("[) [----)")) # difference
  214. do_test(makeset(" [---) "),
  215. makeset("[---------)"),
  216. makeset(" [---) "), # intersection
  217. makeset(" ")) # difference
  218. do_test(makeset(" [-----)"),
  219. makeset(" [-----) "),
  220. makeset(" [--) "), # intersection
  221. makeset(" [--)")) # difference
  222. do_test(makeset(" [--) [--)"),
  223. makeset(" [------) "),
  224. makeset(" [-) [-) "), # intersection
  225. makeset(" [) [)")) # difference
  226. do_test(makeset(" [---)"),
  227. makeset(" [--) "),
  228. makeset(" "), # intersection
  229. makeset(" [---)")) # difference
  230. do_test(makeset(" [-|---)"),
  231. makeset(" [-----|-) "),
  232. makeset(" [----) "), # intersection
  233. makeset(" [)")) # difference
  234. do_test(makeset(" [-|-) "),
  235. makeset(" [-|--|--) "),
  236. makeset(" [---) "), # intersection
  237. makeset(" ")) # difference
  238. do_test(makeset("[-)[-)[-)[)"),
  239. makeset(" [) [|)[) "),
  240. makeset(" [) [) "), # intersection
  241. makeset("[) [-) [)[)")) # difference
  242. # Border cases -- will give different results if intervals are
  243. # half open or fully closed. In nilmdb, they are half open.
  244. do_test(makeset(" [---)"),
  245. makeset(" [----) "),
  246. makeset(" "), # intersection
  247. makeset(" [---)")) # difference
  248. do_test(makeset(" [----)[--)"),
  249. makeset("[-) [--) [)"),
  250. makeset(" [) [-) [)"), # intersection
  251. makeset(" [-) [-) ")) # difference
  252. # Set difference with bounds
  253. a = makeset(" [----)[--)")
  254. b = makeset("[-) [--) [)")
  255. c = makeset("[----) ")
  256. d = makeset(" [-) ")
  257. eq_(nilmdb.utils.interval.set_difference(
  258. a.intersection(list(c)[0]), b.intersection(list(c)[0])), d)
  259. # Empty second set
  260. eq_(nilmdb.utils.interval.set_difference(a, IntervalSet()), a)
  261. class TestIntervalDB:
  262. def test_dbinterval(self):
  263. # Test DBInterval class
  264. i = DBInterval(100, 200, 100, 200, 10000, 20000)
  265. eq_(i.start, 100)
  266. eq_(i.end, 200)
  267. eq_(i.db_start, 100)
  268. eq_(i.db_end, 200)
  269. eq_(i.db_startpos, 10000)
  270. eq_(i.db_endpos, 20000)
  271. eq_(repr(i), repr(eval(repr(i))))
  272. # end before start
  273. with assert_raises(IntervalError):
  274. i = DBInterval(200, 100, 100, 200, 10000, 20000)
  275. # db_start too late
  276. with assert_raises(IntervalError):
  277. i = DBInterval(100, 200, 150, 200, 10000, 20000)
  278. # db_end too soon
  279. with assert_raises(IntervalError):
  280. i = DBInterval(100, 200, 100, 150, 10000, 20000)
  281. # actual start, end can be a subset
  282. a = DBInterval(150, 200, 100, 200, 10000, 20000)
  283. b = DBInterval(100, 150, 100, 200, 10000, 20000)
  284. c = DBInterval(150, 160, 100, 200, 10000, 20000)
  285. # Make a set of DBIntervals
  286. iseta = IntervalSet([a, b])
  287. isetc = IntervalSet(c)
  288. assert(iseta.intersects(a))
  289. assert(iseta.intersects(b))
  290. # Test subset
  291. with assert_raises(IntervalError):
  292. x = a.subset(150, 250)
  293. # Subset of those IntervalSets should still contain DBIntervals
  294. for i in IntervalSet(iseta.intersection(Interval(125,250))):
  295. assert(isinstance(i, DBInterval))
  296. class TestIntervalTree:
  297. def test_interval_tree(self):
  298. import random
  299. random.seed(1234)
  300. # make a set of 100 intervals
  301. iset = IntervalSet()
  302. j = 100
  303. for i in random.sample(xrange(j),j):
  304. interval = Interval(i, i+1)
  305. iset += interval
  306. render(iset, "Random Insertion")
  307. # remove about half of them
  308. for i in random.sample(xrange(j),j):
  309. if random.randint(0,1):
  310. iset -= Interval(i, i+1)
  311. # try removing an interval that doesn't exist
  312. with assert_raises(IntervalError):
  313. iset -= Interval(1234,5678)
  314. render(iset, "Random Insertion, deletion")
  315. # make a set of 100 intervals, inserted in order
  316. iset = IntervalSet()
  317. j = 100
  318. for i in xrange(j):
  319. interval = Interval(i, i+1)
  320. iset += interval
  321. render(iset, "In-order insertion")
  322. class TestIntervalSpeed:
  323. @unittest.skip("this is slow")
  324. def test_interval_speed(self):
  325. import yappi
  326. import time
  327. import random
  328. import math
  329. print
  330. yappi.start()
  331. speeds = {}
  332. limit = 22 # was 20
  333. for j in [ 2**x for x in range(5,limit) ]:
  334. start = time.time()
  335. iset = IntervalSet()
  336. for i in random.sample(xrange(j),j):
  337. interval = Interval(i, i+1)
  338. iset += interval
  339. speed = (time.time() - start) * 1000000.0
  340. printf("%d: %g μs (%g μs each, O(n log n) ratio %g)\n",
  341. j,
  342. speed,
  343. speed/j,
  344. speed / (j*math.log(j))) # should be constant
  345. speeds[j] = speed
  346. yappi.stop()
  347. yappi.print_stats(sort_type=yappi.SORTTYPE_TTOT, limit=10)