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.
 
 
 

439 lines
14 KiB

  1. # -*- coding: utf-8 -*-
  2. import nilmdb
  3. from nilmdb.utils.printf import *
  4. 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, d2))
  82. assert(Interval(d1, d2) >= Interval(d1, d2))
  83. assert(Interval(d1, d2) != Interval(d1, d3))
  84. assert(Interval(d1, d2) < Interval(d1, d3))
  85. assert(Interval(d1, d2) <= Interval(d1, d3))
  86. assert(Interval(d1, d3) > Interval(d1, d2))
  87. assert(Interval(d1, d3) >= Interval(d1, d2))
  88. assert(Interval(d1, d2) < Interval(d2, d3))
  89. assert(Interval(d1, d3) < Interval(d2, d3))
  90. assert(Interval(d2, d2+1) > Interval(d1, d3))
  91. assert(Interval(d3, d3+1) == Interval(d3, d3+1))
  92. #with assert_raises(TypeError): # was AttributeError, that's wrong
  93. # x = (i == 123)
  94. # subset
  95. eq_(Interval(d1, d3).subset(d1, d2), Interval(d1, d2))
  96. with assert_raises(IntervalError):
  97. x = Interval(d2, d3).subset(d1, d2)
  98. # big integers, negative integers
  99. x = Interval(5000111222000000, 6000111222000000)
  100. eq_(str(x), "[5000111222000000 -> 6000111222000000)")
  101. x = Interval(-5000111222000000, -4000111222000000)
  102. eq_(str(x), "[-5000111222000000 -> -4000111222000000)")
  103. # misc
  104. i = Interval(d1, d2)
  105. eq_(repr(i), repr(eval(repr(i))))
  106. eq_(str(i), "[1332561600000000 -> 1332648000000000)")
  107. def test_interval_intersect(self):
  108. # Test Interval intersections
  109. dates = [ 100, 200, 300, 400 ]
  110. perm = list(itertools.permutations(dates, 2))
  111. prod = list(itertools.product(perm, perm))
  112. should_intersect = {
  113. False: [4, 5, 8, 20, 48, 56, 60, 96, 97, 100],
  114. True: [0, 1, 2, 12, 13, 14, 16, 17, 24, 25, 26, 28, 29,
  115. 32, 49, 50, 52, 53, 61, 62, 64, 65, 68, 98, 101, 104]
  116. }
  117. for i,((a,b),(c,d)) in enumerate(prod):
  118. try:
  119. i1 = Interval(a, b)
  120. i2 = Interval(c, d)
  121. eq_(i1.intersects(i2), i2.intersects(i1))
  122. in_(i, should_intersect[i1.intersects(i2)])
  123. except IntervalError:
  124. assert(i not in should_intersect[True] and
  125. i not in should_intersect[False])
  126. with assert_raises(TypeError):
  127. x = i1.intersects(1234)
  128. def test_intervalset_construct(self):
  129. # Test IntervalSet construction
  130. dates = [ 100, 200, 300, 400 ]
  131. a = Interval(dates[0], dates[1])
  132. b = Interval(dates[1], dates[2])
  133. c = Interval(dates[0], dates[2])
  134. d = Interval(dates[2], dates[3])
  135. iseta = IntervalSet(a)
  136. isetb = IntervalSet([a, b])
  137. isetc = IntervalSet([a])
  138. ne_(iseta, isetb)
  139. eq_(iseta, isetc)
  140. with assert_raises(TypeError):
  141. x = iseta != 3
  142. ne_(IntervalSet(a), IntervalSet(b))
  143. # Note that assignment makes a new reference (not a copy)
  144. isetd = IntervalSet(isetb)
  145. isete = isetd
  146. eq_(isetd, isetb)
  147. eq_(isetd, isete)
  148. isetd -= a
  149. ne_(isetd, isetb)
  150. eq_(isetd, isete)
  151. # test iterator
  152. for interval in iseta:
  153. pass
  154. # overlap
  155. with assert_raises(IntervalError):
  156. x = IntervalSet([a, b, c])
  157. # bad types
  158. with assert_raises(Exception):
  159. x = IntervalSet([1, 2])
  160. iset = IntervalSet(isetb) # test iterator
  161. eq_(iset, isetb)
  162. eq_(len(iset), 2)
  163. eq_(len(IntervalSet()), 0)
  164. # Test adding
  165. iset = IntervalSet(a)
  166. iset += IntervalSet(b)
  167. eq_(iset, IntervalSet([a, b]))
  168. iset = IntervalSet(a)
  169. iset += b
  170. eq_(iset, IntervalSet([a, b]))
  171. iset = IntervalSet(a)
  172. iset.iadd_nocheck(b)
  173. eq_(iset, IntervalSet([a, b]))
  174. iset = IntervalSet(a) + IntervalSet(b)
  175. eq_(iset, IntervalSet([a, b]))
  176. iset = IntervalSet(b) + a
  177. eq_(iset, IntervalSet([a, b]))
  178. # A set consisting of [0-1],[1-2] should match a set consisting of [0-2]
  179. eq_(IntervalSet([a,b]), IntervalSet([c]))
  180. # Etc
  181. ne_(IntervalSet([a,d]), IntervalSet([c]))
  182. ne_(IntervalSet([c]), IntervalSet([a,d]))
  183. ne_(IntervalSet([c,d]), IntervalSet([b,d]))
  184. # misc
  185. eq_(repr(iset), repr(eval(repr(iset))))
  186. eq_(str(iset),
  187. "[[100 -> 200), [200 -> 300)]")
  188. def test_intervalset_geniset(self):
  189. # Test basic iset construction
  190. eq_(makeset(" [----) "),
  191. makeset(" [-|--) "))
  192. eq_(makeset("[) [--) ") +
  193. makeset(" [) [--)"),
  194. makeset("[|) [-----)"))
  195. eq_(makeset(" [-------)"),
  196. makeset(" [-|-----|"))
  197. def test_intervalset_intersect_difference(self):
  198. # Test intersection (&)
  199. with assert_raises(TypeError): # was AttributeError
  200. x = makeset("[--)") & 1234
  201. def do_test(a, b, c, d):
  202. # a & b == c (using nilmdb.server.interval)
  203. ab = IntervalSet()
  204. for x in b:
  205. for i in (a & x):
  206. ab += i
  207. eq_(ab,c)
  208. # a & b == c (using nilmdb.utils.interval)
  209. eq_(IntervalSet(nilmdb.utils.interval.intersection(a,b)), c)
  210. # a \ b == d
  211. eq_(IntervalSet(nilmdb.utils.interval.set_difference(a,b)), d)
  212. # Intersection with intervals
  213. do_test(makeset("[---|---)[)"),
  214. makeset(" [------) "),
  215. makeset(" [-----) "), # intersection
  216. makeset("[-) [)")) # difference
  217. do_test(makeset("[---------)"),
  218. makeset(" [---) "),
  219. makeset(" [---) "), # intersection
  220. makeset("[) [----)")) # difference
  221. do_test(makeset(" [---) "),
  222. makeset("[---------)"),
  223. makeset(" [---) "), # intersection
  224. makeset(" ")) # difference
  225. do_test(makeset(" [-----)"),
  226. makeset(" [-----) "),
  227. makeset(" [--) "), # intersection
  228. makeset(" [--)")) # difference
  229. do_test(makeset(" [--) [--)"),
  230. makeset(" [------) "),
  231. makeset(" [-) [-) "), # intersection
  232. makeset(" [) [)")) # difference
  233. do_test(makeset(" [---)"),
  234. makeset(" [--) "),
  235. makeset(" "), # intersection
  236. makeset(" [---)")) # difference
  237. do_test(makeset(" [-|---)"),
  238. makeset(" [-----|-) "),
  239. makeset(" [----) "), # intersection
  240. makeset(" [)")) # difference
  241. do_test(makeset(" [-|-) "),
  242. makeset(" [-|--|--) "),
  243. makeset(" [---) "), # intersection
  244. makeset(" ")) # difference
  245. do_test(makeset("[-)[-)[-)[)"),
  246. makeset(" [) [|)[) "),
  247. makeset(" [) [) "), # intersection
  248. makeset("[) [-) [)[)")) # difference
  249. # Border cases -- will give different results if intervals are
  250. # half open or fully closed. In nilmdb, they are half open.
  251. do_test(makeset(" [---)"),
  252. makeset(" [----) "),
  253. makeset(" "), # intersection
  254. makeset(" [---)")) # difference
  255. do_test(makeset(" [----)[--)"),
  256. makeset("[-) [--) [)"),
  257. makeset(" [) [-) [)"), # intersection
  258. makeset(" [-) [-) ")) # difference
  259. # Set difference with bounds
  260. a = makeset(" [----)[--)")
  261. b = makeset("[-) [--) [)")
  262. c = makeset("[----) ")
  263. d = makeset(" [-) ")
  264. eq_(nilmdb.utils.interval.set_difference(
  265. a.intersection(list(c)[0]), b.intersection(list(c)[0])), d)
  266. # Fill out test coverage for non-subsets
  267. def diff2(a,b, subset):
  268. return nilmdb.utils.interval._interval_math_helper(
  269. a, b, (lambda a, b: b and not a), subset=subset)
  270. with assert_raises(nilmdb.utils.interval.IntervalError):
  271. list(diff2(a,b,True))
  272. list(diff2(a,b,False))
  273. # Empty second set
  274. eq_(nilmdb.utils.interval.set_difference(a, IntervalSet()), a)
  275. # Empty second set
  276. eq_(nilmdb.utils.interval.set_difference(a, IntervalSet()), a)
  277. class TestIntervalDB:
  278. def test_dbinterval(self):
  279. # Test DBInterval class
  280. i = DBInterval(100, 200, 100, 200, 10000, 20000)
  281. eq_(i.start, 100)
  282. eq_(i.end, 200)
  283. eq_(i.db_start, 100)
  284. eq_(i.db_end, 200)
  285. eq_(i.db_startpos, 10000)
  286. eq_(i.db_endpos, 20000)
  287. eq_(repr(i), repr(eval(repr(i))))
  288. # end before start
  289. with assert_raises(IntervalError):
  290. i = DBInterval(200, 100, 100, 200, 10000, 20000)
  291. # db_start too late
  292. with assert_raises(IntervalError):
  293. i = DBInterval(100, 200, 150, 200, 10000, 20000)
  294. # db_end too soon
  295. with assert_raises(IntervalError):
  296. i = DBInterval(100, 200, 100, 150, 10000, 20000)
  297. # actual start, end can be a subset
  298. a = DBInterval(150, 200, 100, 200, 10000, 20000)
  299. b = DBInterval(100, 150, 100, 200, 10000, 20000)
  300. c = DBInterval(150, 160, 100, 200, 10000, 20000)
  301. # Make a set of DBIntervals
  302. iseta = IntervalSet([a, b])
  303. isetc = IntervalSet(c)
  304. assert(iseta.intersects(a))
  305. assert(iseta.intersects(b))
  306. # Test subset
  307. with assert_raises(IntervalError):
  308. x = a.subset(150, 250)
  309. # Subset of those IntervalSets should still contain DBIntervals
  310. for i in IntervalSet(iseta.intersection(Interval(125,250))):
  311. assert(isinstance(i, DBInterval))
  312. class TestIntervalTree:
  313. def test_interval_tree(self):
  314. import random
  315. random.seed(1234)
  316. # make a set of 100 intervals
  317. iset = IntervalSet()
  318. j = 100
  319. for i in random.sample(range(j),j):
  320. interval = Interval(i, i+1)
  321. iset += interval
  322. render(iset, "Random Insertion")
  323. # remove about half of them
  324. for i in random.sample(range(j),j):
  325. if random.randint(0,1):
  326. iset -= Interval(i, i+1)
  327. # try removing an interval that doesn't exist
  328. with assert_raises(IntervalError):
  329. iset -= Interval(1234,5678)
  330. render(iset, "Random Insertion, deletion")
  331. # make a set of 100 intervals, inserted in order
  332. iset = IntervalSet()
  333. j = 100
  334. for i in range(j):
  335. interval = Interval(i, i+1)
  336. iset += interval
  337. render(iset, "In-order insertion")
  338. class TestIntervalSpeed:
  339. @unittest.skip("this is slow")
  340. def test_interval_speed(self):
  341. import yappi
  342. import time
  343. import random
  344. import math
  345. print()
  346. yappi.start()
  347. speeds = {}
  348. limit = 22 # was 20
  349. for j in [ 2**x for x in range(5,limit) ]:
  350. start = time.time()
  351. iset = IntervalSet()
  352. for i in random.sample(range(j),j):
  353. interval = Interval(i, i+1)
  354. iset += interval
  355. speed = (time.time() - start) * 1000000.0
  356. printf("%d: %g μs (%g μs each, O(n log n) ratio %g)\n",
  357. j,
  358. speed,
  359. speed/j,
  360. speed / (j*math.log(j))) # should be constant
  361. speeds[j] = speed
  362. yappi.stop()
  363. stats = yappi.get_func_stats()
  364. stats.sort("ttot")
  365. stats.print_all()