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.
 
 
 

434 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, 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 (using nilmdb.server.interval)
  198. ab = IntervalSet()
  199. for x in b:
  200. for i in (a & x):
  201. ab += i
  202. eq_(ab,c)
  203. # a & b == c (using nilmdb.utils.interval)
  204. eq_(IntervalSet(nilmdb.utils.interval.intersection(a,b)), c)
  205. # a \ b == d
  206. eq_(IntervalSet(nilmdb.utils.interval.set_difference(a,b)), d)
  207. # Intersection with intervals
  208. do_test(makeset("[---|---)[)"),
  209. makeset(" [------) "),
  210. makeset(" [-----) "), # intersection
  211. makeset("[-) [)")) # difference
  212. do_test(makeset("[---------)"),
  213. makeset(" [---) "),
  214. makeset(" [---) "), # intersection
  215. makeset("[) [----)")) # difference
  216. do_test(makeset(" [---) "),
  217. makeset("[---------)"),
  218. makeset(" [---) "), # intersection
  219. makeset(" ")) # difference
  220. do_test(makeset(" [-----)"),
  221. makeset(" [-----) "),
  222. makeset(" [--) "), # intersection
  223. makeset(" [--)")) # difference
  224. do_test(makeset(" [--) [--)"),
  225. makeset(" [------) "),
  226. makeset(" [-) [-) "), # intersection
  227. makeset(" [) [)")) # difference
  228. do_test(makeset(" [---)"),
  229. makeset(" [--) "),
  230. makeset(" "), # intersection
  231. makeset(" [---)")) # difference
  232. do_test(makeset(" [-|---)"),
  233. makeset(" [-----|-) "),
  234. makeset(" [----) "), # intersection
  235. makeset(" [)")) # difference
  236. do_test(makeset(" [-|-) "),
  237. makeset(" [-|--|--) "),
  238. makeset(" [---) "), # intersection
  239. makeset(" ")) # difference
  240. do_test(makeset("[-)[-)[-)[)"),
  241. makeset(" [) [|)[) "),
  242. makeset(" [) [) "), # intersection
  243. makeset("[) [-) [)[)")) # difference
  244. # Border cases -- will give different results if intervals are
  245. # half open or fully closed. In nilmdb, they are half open.
  246. do_test(makeset(" [---)"),
  247. makeset(" [----) "),
  248. makeset(" "), # intersection
  249. makeset(" [---)")) # difference
  250. do_test(makeset(" [----)[--)"),
  251. makeset("[-) [--) [)"),
  252. makeset(" [) [-) [)"), # intersection
  253. makeset(" [-) [-) ")) # difference
  254. # Set difference with bounds
  255. a = makeset(" [----)[--)")
  256. b = makeset("[-) [--) [)")
  257. c = makeset("[----) ")
  258. d = makeset(" [-) ")
  259. eq_(nilmdb.utils.interval.set_difference(
  260. a.intersection(list(c)[0]), b.intersection(list(c)[0])), d)
  261. # Fill out test coverage for non-subsets
  262. def diff2(a,b, subset):
  263. return nilmdb.utils.interval._interval_math_helper(
  264. a, b, (lambda a, b: b and not a), subset=subset)
  265. with assert_raises(nilmdb.utils.interval.IntervalError):
  266. list(diff2(a,b,True))
  267. list(diff2(a,b,False))
  268. # Empty second set
  269. eq_(nilmdb.utils.interval.set_difference(a, IntervalSet()), a)
  270. # Empty second set
  271. eq_(nilmdb.utils.interval.set_difference(a, IntervalSet()), a)
  272. class TestIntervalDB:
  273. def test_dbinterval(self):
  274. # Test DBInterval class
  275. i = DBInterval(100, 200, 100, 200, 10000, 20000)
  276. eq_(i.start, 100)
  277. eq_(i.end, 200)
  278. eq_(i.db_start, 100)
  279. eq_(i.db_end, 200)
  280. eq_(i.db_startpos, 10000)
  281. eq_(i.db_endpos, 20000)
  282. eq_(repr(i), repr(eval(repr(i))))
  283. # end before start
  284. with assert_raises(IntervalError):
  285. i = DBInterval(200, 100, 100, 200, 10000, 20000)
  286. # db_start too late
  287. with assert_raises(IntervalError):
  288. i = DBInterval(100, 200, 150, 200, 10000, 20000)
  289. # db_end too soon
  290. with assert_raises(IntervalError):
  291. i = DBInterval(100, 200, 100, 150, 10000, 20000)
  292. # actual start, end can be a subset
  293. a = DBInterval(150, 200, 100, 200, 10000, 20000)
  294. b = DBInterval(100, 150, 100, 200, 10000, 20000)
  295. c = DBInterval(150, 160, 100, 200, 10000, 20000)
  296. # Make a set of DBIntervals
  297. iseta = IntervalSet([a, b])
  298. isetc = IntervalSet(c)
  299. assert(iseta.intersects(a))
  300. assert(iseta.intersects(b))
  301. # Test subset
  302. with assert_raises(IntervalError):
  303. x = a.subset(150, 250)
  304. # Subset of those IntervalSets should still contain DBIntervals
  305. for i in IntervalSet(iseta.intersection(Interval(125,250))):
  306. assert(isinstance(i, DBInterval))
  307. class TestIntervalTree:
  308. def test_interval_tree(self):
  309. import random
  310. random.seed(1234)
  311. # make a set of 100 intervals
  312. iset = IntervalSet()
  313. j = 100
  314. for i in random.sample(range(j),j):
  315. interval = Interval(i, i+1)
  316. iset += interval
  317. render(iset, "Random Insertion")
  318. # remove about half of them
  319. for i in random.sample(range(j),j):
  320. if random.randint(0,1):
  321. iset -= Interval(i, i+1)
  322. # try removing an interval that doesn't exist
  323. with assert_raises(IntervalError):
  324. iset -= Interval(1234,5678)
  325. render(iset, "Random Insertion, deletion")
  326. # make a set of 100 intervals, inserted in order
  327. iset = IntervalSet()
  328. j = 100
  329. for i in range(j):
  330. interval = Interval(i, i+1)
  331. iset += interval
  332. render(iset, "In-order insertion")
  333. class TestIntervalSpeed:
  334. @unittest.skip("this is slow")
  335. def test_interval_speed(self):
  336. import yappi
  337. import time
  338. import random
  339. import math
  340. print()
  341. yappi.start()
  342. speeds = {}
  343. limit = 22 # was 20
  344. for j in [ 2**x for x in range(5,limit) ]:
  345. start = time.time()
  346. iset = IntervalSet()
  347. for i in random.sample(range(j),j):
  348. interval = Interval(i, i+1)
  349. iset += interval
  350. speed = (time.time() - start) * 1000000.0
  351. printf("%d: %g μs (%g μs each, O(n log n) ratio %g)\n",
  352. j,
  353. speed,
  354. speed/j,
  355. speed / (j*math.log(j))) # should be constant
  356. speeds[j] = speed
  357. yappi.stop()
  358. stats = yappi.get_func_stats()
  359. stats.sort("ttot")
  360. stats.print_all()