sapor 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. checksums.yaml +7 -0
  2. data/Area Class Diagram.dia +0 -0
  3. data/Area Class Diagram.png +0 -0
  4. data/Class Diagram.dia +0 -0
  5. data/Class Diagram.png +0 -0
  6. data/Example-Catalonia.md +361 -0
  7. data/Example-Flanders.md +486 -0
  8. data/Example-Greece.md +25 -0
  9. data/Example-Oslo.md +678 -0
  10. data/Example-UnitedKingdom-Referendum.md +132 -0
  11. data/Examples.md +15 -0
  12. data/LICENSE +674 -0
  13. data/README.md +103 -0
  14. data/Rakefile +18 -0
  15. data/Technical Documentation.md +14 -0
  16. data/bin/create_installation_package.sh +49 -0
  17. data/bin/install.sh +45 -0
  18. data/bin/sapor.rb +24 -0
  19. data/bin/sapor.sh +106 -0
  20. data/data/hu/hungary-2014.txt +1680 -0
  21. data/data/hu/hungary_2014_screen_scraper.rb +48 -0
  22. data/data/hu/hungary_2014_to_psv.rb +80 -0
  23. data/data/hu/index-2014.txt +106 -0
  24. data/data/pl/2015-gl-lis-okr.csv +42 -0
  25. data/data/pl/poland_2015_to_psv.rb +79 -0
  26. data/data/pl/poland_2015_to_psv_with_ko_and_rsw.rb +94 -0
  27. data/data/pl/poland_2015_to_psv_with_ko_konf_kp_l_and_zp.rb +100 -0
  28. data/data/pl/poland_2015_to_psv_with_ko_sld_and_wi.rb +92 -0
  29. data/data/pl/poland_2015_to_psv_with_sld.rb +84 -0
  30. data/data/pl/poland_2015_to_psv_with_sld_and_wi.rb +85 -0
  31. data/data/uk/inject_ukip_2015_as_brexit_2019_in_2017.rb +54 -0
  32. data/data/uk/united_kingdom_2015.txt +651 -0
  33. data/data/uk/united_kingdom_2015_to_psv.rb +104 -0
  34. data/data/uk/united_kingdom_2017.txt +651 -0
  35. data/data/uk/united_kingdom_2017_to_psv.rb +104 -0
  36. data/data/uk/united_kingdom_2017_to_psv_with_brexit_and_chuk.rb +113 -0
  37. data/data/uk/united_kingdom_2017_to_psv_with_tig.rb +111 -0
  38. data/lib/sapor.rb +150 -0
  39. data/lib/sapor/binomials_cache.rb +45 -0
  40. data/lib/sapor/combinations_distribution.rb +222 -0
  41. data/lib/sapor/denominators.rb +67 -0
  42. data/lib/sapor/dichotomies.rb +138 -0
  43. data/lib/sapor/dichotomy.rb +164 -0
  44. data/lib/sapor/first_past_the_post.rb +82 -0
  45. data/lib/sapor/largest_remainder.rb +118 -0
  46. data/lib/sapor/log4r_logger.rb +49 -0
  47. data/lib/sapor/log_facade.rb +40 -0
  48. data/lib/sapor/multi_district_leveled_proportional.rb +64 -0
  49. data/lib/sapor/multi_district_proportional.rb +123 -0
  50. data/lib/sapor/multi_district_variable_threshold_proportional.rb +128 -0
  51. data/lib/sapor/number_formatter.rb +45 -0
  52. data/lib/sapor/options.rb +73 -0
  53. data/lib/sapor/poll.rb +286 -0
  54. data/lib/sapor/polychotomy.rb +200 -0
  55. data/lib/sapor/pseudorandom_multirange_enumerator.rb +87 -0
  56. data/lib/sapor/referendum_polychotomy.rb +165 -0
  57. data/lib/sapor/regional_data/area.rb +82 -0
  58. data/lib/sapor/regional_data/austria.rb +84 -0
  59. data/lib/sapor/regional_data/belgium-brussels-2014.psv +46 -0
  60. data/lib/sapor/regional_data/belgium-brussels-20190526.psv +33 -0
  61. data/lib/sapor/regional_data/belgium-flanders-2014.psv +80 -0
  62. data/lib/sapor/regional_data/belgium-flanders-20190526.psv +74 -0
  63. data/lib/sapor/regional_data/belgium-wallonia-2014.psv +114 -0
  64. data/lib/sapor/regional_data/belgium-wallonia-20190526.psv +93 -0
  65. data/lib/sapor/regional_data/belgium.rb +97 -0
  66. data/lib/sapor/regional_data/belgium_brussels.rb +62 -0
  67. data/lib/sapor/regional_data/belgium_flanders.rb +64 -0
  68. data/lib/sapor/regional_data/belgium_wallonia.rb +63 -0
  69. data/lib/sapor/regional_data/catalonia-2012-2015.psv +100 -0
  70. data/lib/sapor/regional_data/catalonia-2012.psv +87 -0
  71. data/lib/sapor/regional_data/catalonia-2015-jxcat.psv +68 -0
  72. data/lib/sapor/regional_data/catalonia-2015-no-jxsi.psv +68 -0
  73. data/lib/sapor/regional_data/catalonia-2015.psv +63 -0
  74. data/lib/sapor/regional_data/catalonia-jxcat.rb +109 -0
  75. data/lib/sapor/regional_data/catalonia-no-jxsi.rb +96 -0
  76. data/lib/sapor/regional_data/catalonia.rb +96 -0
  77. data/lib/sapor/regional_data/denmark-20150618-with-e-and-p.psv +164 -0
  78. data/lib/sapor/regional_data/denmark-20150618-with-e.psv +153 -0
  79. data/lib/sapor/regional_data/denmark-20150618-with-p.psv +153 -0
  80. data/lib/sapor/regional_data/denmark-20150618.psv +142 -0
  81. data/lib/sapor/regional_data/denmark.rb +128 -0
  82. data/lib/sapor/regional_data/denmark_with_e.rb +128 -0
  83. data/lib/sapor/regional_data/denmark_with_e_and_p.rb +128 -0
  84. data/lib/sapor/regional_data/denmark_with_p.rb +128 -0
  85. data/lib/sapor/regional_data/estonia.rb +88 -0
  86. data/lib/sapor/regional_data/european-union-great-britain-20140522-brexit-chuk.psv +172 -0
  87. data/lib/sapor/regional_data/european-union-great-britain-20140522.psv +146 -0
  88. data/lib/sapor/regional_data/european-union-great-britain-20190523.psv +141 -0
  89. data/lib/sapor/regional_data/european-union-ireland-2014-ia-ri-sd.psv +64 -0
  90. data/lib/sapor/regional_data/european-union-ireland-2014-ia-sd.psv +60 -0
  91. data/lib/sapor/regional_data/european-union-ireland-2014-ia.psv +56 -0
  92. data/lib/sapor/regional_data/european-union-ireland-2014-sd.psv +56 -0
  93. data/lib/sapor/regional_data/european-union-ireland-2014.psv +50 -0
  94. data/lib/sapor/regional_data/european-union-ireland-20190524-ia.psv +58 -0
  95. data/lib/sapor/regional_data/european-union-ireland-20190524.psv +52 -0
  96. data/lib/sapor/regional_data/european_union_27_austria.rb +76 -0
  97. data/lib/sapor/regional_data/european_union_27_croatia.rb +81 -0
  98. data/lib/sapor/regional_data/european_union_27_denmark.rb +77 -0
  99. data/lib/sapor/regional_data/european_union_27_estonia.rb +74 -0
  100. data/lib/sapor/regional_data/european_union_27_finland.rb +74 -0
  101. data/lib/sapor/regional_data/european_union_27_ireland.rb +96 -0
  102. data/lib/sapor/regional_data/european_union_27_ireland_with_ia.rb +97 -0
  103. data/lib/sapor/regional_data/european_union_27_italy.rb +84 -0
  104. data/lib/sapor/regional_data/european_union_27_netherlands.rb +81 -0
  105. data/lib/sapor/regional_data/european_union_27_poland.rb +84 -0
  106. data/lib/sapor/regional_data/european_union_27_romania.rb +78 -0
  107. data/lib/sapor/regional_data/european_union_27_slovakia.rb +80 -0
  108. data/lib/sapor/regional_data/european_union_27_spain.rb +82 -0
  109. data/lib/sapor/regional_data/european_union_27_sweden.rb +76 -0
  110. data/lib/sapor/regional_data/european_union_austria.rb +76 -0
  111. data/lib/sapor/regional_data/european_union_bulgaria.rb +82 -0
  112. data/lib/sapor/regional_data/european_union_croatia.rb +81 -0
  113. data/lib/sapor/regional_data/european_union_cyprus.rb +72 -0
  114. data/lib/sapor/regional_data/european_union_czech_republic.rb +82 -0
  115. data/lib/sapor/regional_data/european_union_denmark.rb +77 -0
  116. data/lib/sapor/regional_data/european_union_estonia.rb +74 -0
  117. data/lib/sapor/regional_data/european_union_finland.rb +74 -0
  118. data/lib/sapor/regional_data/european_union_flanders.rb +74 -0
  119. data/lib/sapor/regional_data/european_union_france.rb +84 -0
  120. data/lib/sapor/regional_data/european_union_france_2019.rb +84 -0
  121. data/lib/sapor/regional_data/european_union_french_community_of_belgium.rb +73 -0
  122. data/lib/sapor/regional_data/european_union_germany.rb +86 -0
  123. data/lib/sapor/regional_data/european_union_great_britain.rb +98 -0
  124. data/lib/sapor/regional_data/european_union_greece.rb +77 -0
  125. data/lib/sapor/regional_data/european_union_hungary.rb +76 -0
  126. data/lib/sapor/regional_data/european_union_ireland.rb +96 -0
  127. data/lib/sapor/regional_data/european_union_ireland_with_ia.rb +97 -0
  128. data/lib/sapor/regional_data/european_union_italy.rb +84 -0
  129. data/lib/sapor/regional_data/european_union_latvia.rb +81 -0
  130. data/lib/sapor/regional_data/european_union_lithuania.rb +80 -0
  131. data/lib/sapor/regional_data/european_union_luxembourg.rb +75 -0
  132. data/lib/sapor/regional_data/european_union_malta.rb +71 -0
  133. data/lib/sapor/regional_data/european_union_netherlands.rb +81 -0
  134. data/lib/sapor/regional_data/european_union_northern_ireland.rb +75 -0
  135. data/lib/sapor/regional_data/european_union_poland.rb +84 -0
  136. data/lib/sapor/regional_data/european_union_portugal.rb +75 -0
  137. data/lib/sapor/regional_data/european_union_romania.rb +78 -0
  138. data/lib/sapor/regional_data/european_union_slovakia.rb +81 -0
  139. data/lib/sapor/regional_data/european_union_slovenia.rb +85 -0
  140. data/lib/sapor/regional_data/european_union_spain.rb +82 -0
  141. data/lib/sapor/regional_data/european_union_sweden.rb +76 -0
  142. data/lib/sapor/regional_data/finland-20150419-with-sin.psv +224 -0
  143. data/lib/sapor/regional_data/finland-20150419.psv +212 -0
  144. data/lib/sapor/regional_data/finland.rb +107 -0
  145. data/lib/sapor/regional_data/finland_with_sin.rb +107 -0
  146. data/lib/sapor/regional_data/flanders-2014.psv +96 -0
  147. data/lib/sapor/regional_data/flanders-20190526.psv +87 -0
  148. data/lib/sapor/regional_data/flanders.rb +115 -0
  149. data/lib/sapor/regional_data/france.rb +38 -0
  150. data/lib/sapor/regional_data/greece.rb +92 -0
  151. data/lib/sapor/regional_data/hungary-2014.psv +2104 -0
  152. data/lib/sapor/regional_data/hungary.rb +116 -0
  153. data/lib/sapor/regional_data/iceland-20161029-midflokkurinn.psv +94 -0
  154. data/lib/sapor/regional_data/iceland-20161029.psv +88 -0
  155. data/lib/sapor/regional_data/iceland-20171028.psv +85 -0
  156. data/lib/sapor/regional_data/iceland.rb +133 -0
  157. data/lib/sapor/regional_data/latvia-20141004-kpv-p-par.psv +109 -0
  158. data/lib/sapor/regional_data/latvia-20141004-kpv-par.psv +103 -0
  159. data/lib/sapor/regional_data/latvia-20141004-kpv.psv +97 -0
  160. data/lib/sapor/regional_data/latvia-20141004.psv +89 -0
  161. data/lib/sapor/regional_data/latvia.rb +112 -0
  162. data/lib/sapor/regional_data/latvia_kpv.rb +112 -0
  163. data/lib/sapor/regional_data/latvia_kpv_p_par.rb +112 -0
  164. data/lib/sapor/regional_data/latvia_kpv_par.rb +112 -0
  165. data/lib/sapor/regional_data/luxembourg-20131020.psv +76 -0
  166. data/lib/sapor/regional_data/luxembourg.rb +82 -0
  167. data/lib/sapor/regional_data/netherlands.rb +108 -0
  168. data/lib/sapor/regional_data/norway.rb +425 -0
  169. data/lib/sapor/regional_data/norwegian_municipality.rb +68 -0
  170. data/lib/sapor/regional_data/poland-20151025-with-ko-and-l-without-n-po-r-and-zl.psv +321 -0
  171. data/lib/sapor/regional_data/poland-20151025-with-ko-konf-kp-l-and-zp-without-k-k15-n-pis-po-psl-r-and-zl.psv +280 -0
  172. data/lib/sapor/regional_data/poland-20151025-with-ko-sld-and-wi-without-n-po-and-zl.psv +403 -0
  173. data/lib/sapor/regional_data/poland-20151025-with-sld-and-wi-without-zl.psv +444 -0
  174. data/lib/sapor/regional_data/poland-20151025-with-sld-without-zl.psv +403 -0
  175. data/lib/sapor/regional_data/poland-20151025.psv +403 -0
  176. data/lib/sapor/regional_data/poland.rb +125 -0
  177. data/lib/sapor/regional_data/poland_with_ko_and_l_without_n_po_r_and_zl.rb +122 -0
  178. data/lib/sapor/regional_data/poland_with_ko_konf_kp_l_and_zp_without_k_k15_n_pis_po_psl_r_and_zl.rb +123 -0
  179. data/lib/sapor/regional_data/poland_with_ko_sld_and_wi_without_n_po_and_zl.rb +125 -0
  180. data/lib/sapor/regional_data/poland_with_sld_and_wi_without_zl.rb +126 -0
  181. data/lib/sapor/regional_data/poland_with_sld_without_zl.rb +126 -0
  182. data/lib/sapor/regional_data/portugal-20151004-with-a-and-ch-without-paf.psv +438 -0
  183. data/lib/sapor/regional_data/portugal-20151004-with-a-and-il-without-paf.psv +438 -0
  184. data/lib/sapor/regional_data/portugal-20151004-with-a-ch-and-il-without-paf.psv +461 -0
  185. data/lib/sapor/regional_data/portugal-20151004-with-a-without-paf.psv +415 -0
  186. data/lib/sapor/regional_data/portugal-20151004-with-ch-and-il-without-paf.psv +438 -0
  187. data/lib/sapor/regional_data/portugal-20151004-without-paf.psv +392 -0
  188. data/lib/sapor/regional_data/portugal-20151004.psv +370 -0
  189. data/lib/sapor/regional_data/portugal.rb +101 -0
  190. data/lib/sapor/regional_data/portugal_with_a_and_ch_without_paf.rb +92 -0
  191. data/lib/sapor/regional_data/portugal_with_a_and_il_without_paf.rb +92 -0
  192. data/lib/sapor/regional_data/portugal_with_a_ch_and_il_without_paf.rb +92 -0
  193. data/lib/sapor/regional_data/portugal_with_a_without_paf.rb +92 -0
  194. data/lib/sapor/regional_data/portugal_with_ch_and_il_without_paf.rb +92 -0
  195. data/lib/sapor/regional_data/portugal_without_paf.rb +92 -0
  196. data/lib/sapor/regional_data/slovakia.rb +81 -0
  197. data/lib/sapor/regional_data/slovenia.rb +114 -0
  198. data/lib/sapor/regional_data/spain-20160626.psv +619 -0
  199. data/lib/sapor/regional_data/spain.rb +136 -0
  200. data/lib/sapor/regional_data/sweden.rb +92 -0
  201. data/lib/sapor/regional_data/sweden_20140914.rb +89 -0
  202. data/lib/sapor/regional_data/united_kingdom-2015.psv +4358 -0
  203. data/lib/sapor/regional_data/united_kingdom-20170608-brexit-chuk.psv +5154 -0
  204. data/lib/sapor/regional_data/united_kingdom-20170608-brexit.psv +4521 -0
  205. data/lib/sapor/regional_data/united_kingdom-20170608-tig.psv +4529 -0
  206. data/lib/sapor/regional_data/united_kingdom-20170608.psv +3894 -0
  207. data/lib/sapor/regional_data/united_kingdom.rb +94 -0
  208. data/lib/sapor/regional_data/united_kingdom_with_brexit.rb +110 -0
  209. data/lib/sapor/regional_data/united_kingdom_with_brexit_and_chuk.rb +111 -0
  210. data/lib/sapor/regional_data/united_kingdom_with_tig.rb +111 -0
  211. data/lib/sapor/regional_data/utopia.rb +66 -0
  212. data/lib/sapor/regional_data/wallonia-2014.psv +101 -0
  213. data/lib/sapor/regional_data/wallonia-20190526.psv +88 -0
  214. data/lib/sapor/regional_data/wallonia.rb +112 -0
  215. data/lib/sapor/representatives_polychotomy.rb +338 -0
  216. data/lib/sapor/single_district_proportional.rb +75 -0
  217. data/sapor.gemspec +35 -0
  218. data/spec/integration/area_spec.rb +28 -0
  219. data/spec/integration/poll_spec.rb +112 -0
  220. data/spec/integration/sample.poll +8 -0
  221. data/spec/spec_helper.rb +31 -0
  222. data/spec/unit/area_spec.rb +115 -0
  223. data/spec/unit/austria_spec.rb +76 -0
  224. data/spec/unit/belgium_brussels_spec.rb +58 -0
  225. data/spec/unit/belgium_flanders_spec.rb +62 -0
  226. data/spec/unit/belgium_spec.rb +26 -0
  227. data/spec/unit/belgium_wallonia_spec.rb +65 -0
  228. data/spec/unit/binomials_cache_spec.rb +34 -0
  229. data/spec/unit/catalonia_spec.rb +74 -0
  230. data/spec/unit/combinations_distribution_spec.rb +241 -0
  231. data/spec/unit/denmark_spec.rb +56 -0
  232. data/spec/unit/denmark_with_e_and_p_spec.rb +58 -0
  233. data/spec/unit/denmark_with_e_spec.rb +57 -0
  234. data/spec/unit/denmark_with_p_spec.rb +57 -0
  235. data/spec/unit/denominators_spec.rb +40 -0
  236. data/spec/unit/dichotomies_spec.rb +154 -0
  237. data/spec/unit/dichotomy_spec.rb +320 -0
  238. data/spec/unit/estonia_spec.rb +65 -0
  239. data/spec/unit/european_union_27_austria_spec.rb +61 -0
  240. data/spec/unit/european_union_27_croatia_spec.rb +60 -0
  241. data/spec/unit/european_union_27_denmark_spec.rb +62 -0
  242. data/spec/unit/european_union_27_estonia_spec.rb +94 -0
  243. data/spec/unit/european_union_27_finland_spec.rb +75 -0
  244. data/spec/unit/european_union_27_ireland_spec.rb +72 -0
  245. data/spec/unit/european_union_27_ireland_with_ia_spec.rb +74 -0
  246. data/spec/unit/european_union_27_italy_spec.rb +69 -0
  247. data/spec/unit/european_union_27_netherlands_spec.rb +81 -0
  248. data/spec/unit/european_union_27_poland_spec.rb +69 -0
  249. data/spec/unit/european_union_27_romania_spec.rb +67 -0
  250. data/spec/unit/european_union_27_slovakia_spec.rb +111 -0
  251. data/spec/unit/european_union_27_spain_spec.rb +130 -0
  252. data/spec/unit/european_union_27_sweden_spec.rb +89 -0
  253. data/spec/unit/european_union_austria_spec.rb +61 -0
  254. data/spec/unit/european_union_bulgaria_spec.rb +97 -0
  255. data/spec/unit/european_union_croatia_spec.rb +59 -0
  256. data/spec/unit/european_union_cyprus_spec.rb +65 -0
  257. data/spec/unit/european_union_czech_republic_spec.rb +125 -0
  258. data/spec/unit/european_union_denmark_spec.rb +61 -0
  259. data/spec/unit/european_union_estonia_spec.rb +93 -0
  260. data/spec/unit/european_union_finland_spec.rb +75 -0
  261. data/spec/unit/european_union_flanders_spec.rb +56 -0
  262. data/spec/unit/european_union_france_2019_spec.rb +73 -0
  263. data/spec/unit/european_union_france_spec.rb +73 -0
  264. data/spec/unit/european_union_french_community_of_belgium_spec.rb +61 -0
  265. data/spec/unit/european_union_germany_spec.rb +90 -0
  266. data/spec/unit/european_union_great_britain_spec.rb +87 -0
  267. data/spec/unit/european_union_greece_spec.rb +148 -0
  268. data/spec/unit/european_union_hungary_spec.rb +57 -0
  269. data/spec/unit/european_union_ireland_spec.rb +72 -0
  270. data/spec/unit/european_union_ireland_with_ia_spec.rb +74 -0
  271. data/spec/unit/european_union_italy_spec.rb +69 -0
  272. data/spec/unit/european_union_latvia_spec.rb +76 -0
  273. data/spec/unit/european_union_lithuania_spec.rb +68 -0
  274. data/spec/unit/european_union_luxembourg_spec.rb +63 -0
  275. data/spec/unit/european_union_malta_spec.rb +60 -0
  276. data/spec/unit/european_union_netherlands_spec.rb +81 -0
  277. data/spec/unit/european_union_northern_ireland_spec.rb +66 -0
  278. data/spec/unit/european_union_poland_spec.rb +69 -0
  279. data/spec/unit/european_union_portugal_spec.rb +77 -0
  280. data/spec/unit/european_union_romania_spec.rb +67 -0
  281. data/spec/unit/european_union_slovakia_spec.rb +111 -0
  282. data/spec/unit/european_union_slovenia_spec.rb +77 -0
  283. data/spec/unit/european_union_spain_spec.rb +129 -0
  284. data/spec/unit/european_union_sweden_spec.rb +89 -0
  285. data/spec/unit/finland_spec.rb +65 -0
  286. data/spec/unit/finland_with_sin_spec.rb +67 -0
  287. data/spec/unit/first_past_the_post_spec.rb +54 -0
  288. data/spec/unit/flanders_spec.rb +70 -0
  289. data/spec/unit/france_spec.rb +32 -0
  290. data/spec/unit/greece_spec.rb +118 -0
  291. data/spec/unit/hungary_spec.rb +132 -0
  292. data/spec/unit/iceland_spec.rb +57 -0
  293. data/spec/unit/largest_remainder_spec.rb +79 -0
  294. data/spec/unit/latvia_kpv_p_par_spec.rb +38 -0
  295. data/spec/unit/latvia_kpv_par_spec.rb +38 -0
  296. data/spec/unit/latvia_kpv_spec.rb +38 -0
  297. data/spec/unit/latvia_spec.rb +60 -0
  298. data/spec/unit/luxembourg_spec.rb +54 -0
  299. data/spec/unit/multi_district_leveled_proportional_spec.rb +49 -0
  300. data/spec/unit/multi_district_proportional_spec.rb +81 -0
  301. data/spec/unit/netherlands_spec.rb +107 -0
  302. data/spec/unit/norway_spec.rb +64 -0
  303. data/spec/unit/norwegian_municipality_spec.rb +89 -0
  304. data/spec/unit/number_formatter_spec.rb +173 -0
  305. data/spec/unit/poland_spec.rb +62 -0
  306. data/spec/unit/poland_with_ko_and_l_without_n_po_r_and_zl_spec.rb +60 -0
  307. data/spec/unit/poland_with_ko_konf_kp_l_and_zp_without_k_k15_n_pis_po_psl_r_and_zl_spec.rb +59 -0
  308. data/spec/unit/poland_with_ko_sld_and_wi_without_n_po_and_zl_spec.rb +62 -0
  309. data/spec/unit/poland_with_sld_and_wi_without_zl_spec.rb +63 -0
  310. data/spec/unit/poland_with_sld_without_zl_spec.rb +62 -0
  311. data/spec/unit/poll_spec.rb +110 -0
  312. data/spec/unit/portugal_spec.rb +66 -0
  313. data/spec/unit/portugal_with_a_and_ch_without_paf_spec.rb +68 -0
  314. data/spec/unit/portugal_with_a_and_il_without_paf_spec.rb +68 -0
  315. data/spec/unit/portugal_with_a_ch_and_il_without_paf_spec.rb +69 -0
  316. data/spec/unit/portugal_with_a_without_paf_spec.rb +67 -0
  317. data/spec/unit/portugal_with_ch_and_il_without_paf_spec.rb +68 -0
  318. data/spec/unit/portugal_without_paf_spec.rb +66 -0
  319. data/spec/unit/pseudorandom_multirange_enumerator_spec.rb +82 -0
  320. data/spec/unit/referendum_polychotomy_spec.rb +289 -0
  321. data/spec/unit/representatives_polychotomy_spec.rb +332 -0
  322. data/spec/unit/slovakia_spec.rb +99 -0
  323. data/spec/unit/slovenia_spec.rb +80 -0
  324. data/spec/unit/spain_spec.rb +101 -0
  325. data/spec/unit/sweden_20140914_spec.rb +112 -0
  326. data/spec/unit/sweden_spec.rb +113 -0
  327. data/spec/unit/united_kingdom_spec.rb +65 -0
  328. data/spec/unit/united_kingdom_with_brexit_and_chuk_spec.rb +67 -0
  329. data/spec/unit/united_kingdom_with_brexit_spec.rb +66 -0
  330. data/spec/unit/united_kingdom_with_tig_spec.rb +66 -0
  331. data/spec/unit/wallonia_spec.rb +70 -0
  332. metadata +490 -0
@@ -0,0 +1,200 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2016 Filip van Laenen <f.a.vanlaenen@ieee.org>
5
+ #
6
+ # This file is part of SAPoR.
7
+ #
8
+ # SAPoR is free software: you can redistribute it and/or modify it under the
9
+ # terms of the GNU General Public License as published by the Free Software
10
+ # Foundation, either version 3 of the License, or (at your option) any later
11
+ # version.
12
+ #
13
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
14
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16
+ #
17
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
18
+ #
19
+
20
+ module Sapor
21
+ #
22
+ # Superclass for polychotomies.
23
+ #
24
+ class Polychotomy
25
+ attr_reader :error_estimate, :no_of_data_points, :no_of_simulations
26
+
27
+ def initialize(results, area, dichotomies, max_error)
28
+ initialize_static_information(results, area, dichotomies, max_error)
29
+ initialize_calculation_information
30
+ end
31
+
32
+ def most_probable_fraction(key)
33
+ if @no_of_simulations == 0
34
+ nil
35
+ else
36
+ calculate_most_probable_fraction(key, @votes)
37
+ end
38
+ end
39
+
40
+ def most_probable_value(key)
41
+ if @no_of_simulations == 0
42
+ nil
43
+ else
44
+ @votes[key].most_probable_value
45
+ end
46
+ end
47
+
48
+ def range(choice)
49
+ @ranges[choice]
50
+ end
51
+
52
+ def space_size
53
+ @enum.size
54
+ end
55
+
56
+ def encode_with(coder)
57
+ (instance_variables - [:@area, :@enum, :@logger]).each do |var|
58
+ var = var.to_s
59
+ coder[var.gsub('@', '')] = eval(var)
60
+ end
61
+ end
62
+
63
+ def revive_volatile_fields(logger = nil)
64
+ @area = Poll.lookup_area(@area_code)
65
+ @enum = create_enumerator(@ranges)
66
+ @logger = logger
67
+ 1.upto(@no_of_data_points).each { |i| @enum.next() }
68
+ end
69
+
70
+ private
71
+
72
+ def calculate_most_probable_fraction(key, votes)
73
+ votes[key].most_probable_value.to_f / @area.population_size
74
+ end
75
+
76
+ def calculate_most_probable_rounded_fraction(key, votes)
77
+ votes[key].most_probable_rounded_fraction(@area.population_size)
78
+ end
79
+
80
+ def create_comparisons(choices)
81
+ comparisons = {}
82
+ choices.each do |a|
83
+ choices.each do |b|
84
+ comparisons[a + '>' + b] = 0.to_lf
85
+ end
86
+ end
87
+ comparisons
88
+ end
89
+
90
+ def create_enumerator(ranges)
91
+ range_sizes = ranges.values.map(&:size)
92
+ PseudoRandomMultiRangeEnumerator.new(range_sizes).each
93
+ end
94
+
95
+ def create_rankings(choices)
96
+ rankings = {}
97
+ choices.each do |choice|
98
+ 1.upto(choices.size).each do |i|
99
+ rankings[choice + '@' + (i - 1).to_s] = 0.to_lf
100
+ end
101
+ end
102
+ rankings
103
+ end
104
+
105
+ def extract_ranges_from_dichotomies(dichotomies, max_error)
106
+ ranges = {}
107
+ level = 1 - (max_error**2)
108
+ @choices.each do |choice|
109
+ unless choice == OTHER
110
+ ranges[choice] = dichotomies.confidence_interval_values(choice,
111
+ level).sort
112
+ end
113
+ end
114
+ ranges
115
+ end
116
+
117
+ def initialize_calculation_information
118
+ @enum = create_enumerator(@ranges)
119
+ @no_of_simulations = 0
120
+ @no_of_data_points = 0
121
+ @combinations_sum = 0.to_lf
122
+ @error_estimate = 1.0
123
+ @votes = create_new_votes_distributions
124
+ end
125
+
126
+ def initialize_static_information(results, area, dichotomies, max_error)
127
+ @results = results
128
+ @area = area
129
+ @area_code = area.area_code
130
+ @choices = results.keys
131
+ @comparisons = create_comparisons(@choices)
132
+ @rankings = create_rankings(@choices)
133
+ @ranges = extract_ranges_from_dichotomies(dichotomies, max_error)
134
+ end
135
+
136
+ def larger_than(a, b)
137
+ probability = @comparisons[a + '>' + b] / @combinations_sum
138
+ probability.mantissa * (10**probability.exponent)
139
+ end
140
+
141
+ def most_probable_rounded_fraction(key)
142
+ if @no_of_simulations == 0
143
+ nil
144
+ else
145
+ calculate_most_probable_rounded_fraction(key, @votes)
146
+ end
147
+ end
148
+
149
+ def next_data_point
150
+ indexes = @enum.next
151
+ data_point = {}
152
+ indexes.each_with_index do |ix, i|
153
+ data_point[@ranges.keys[i]] = @ranges.values[i][ix]
154
+ end
155
+ data_point[OTHER] = @area.population_size - data_point.values.inject(:+)
156
+ data_point
157
+ end
158
+
159
+ def ranking(choice, i)
160
+ probability = @rankings[choice + '@' + i.to_s] / @combinations_sum
161
+ probability.mantissa * (10**probability.exponent)
162
+ end
163
+
164
+ def result(choice)
165
+ @results[choice].to_f / @results.values.inject(:+)
166
+ end
167
+
168
+ def sort_choices_by_result
169
+ sorted_choices = @choices.reject { |choice| choice == OTHER }
170
+ sorted_choices.sort do |a, b|
171
+ comparison = result(b) <=> result(a)
172
+ if comparison == 0
173
+ a <=> b
174
+ else
175
+ comparison
176
+ end
177
+ end
178
+ end
179
+
180
+ def write_rankings(filename)
181
+ ci_filename = filename.gsub('.poll', '-polychotomy-rankings.psv')
182
+ File.open(ci_filename, 'w') do |file|
183
+ file.puts('Choice | ' + 0.upto(@choices.size - 1).map {|i| i + 1}.join(' | '))
184
+ @choices.each do | choice |
185
+ file.puts(choice + ' | ' + 0.upto(@choices.size - 1).map {|i| ranking(choice, i)}.join(' | '))
186
+ end
187
+ end
188
+ end
189
+
190
+ def write_state_summary(filename)
191
+ state_summary_filename = filename.gsub('.poll', '_state_summary.txt')
192
+ File.open(state_summary_filename, 'w') do |file|
193
+ file.puts("ErrorEstimate=#{@error_estimate}")
194
+ file.puts("NumberOfSimulations=#{@no_of_simulations}")
195
+ file.puts("NumberOfDataPoints=#{@no_of_data_points}")
196
+ file.puts("SpaceSize=#{space_size}")
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2016 Filip van Laenen <f.a.vanlaenen@ieee.org>
5
+ #
6
+ # This file is part of SAPoR.
7
+ #
8
+ # SAPoR is free software: you can redistribute it and/or modify it under the
9
+ # terms of the GNU General Public License as published by the Free Software
10
+ # Foundation, either version 3 of the License, or (at your option) any later
11
+ # version.
12
+ #
13
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
14
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16
+ #
17
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
18
+ #
19
+
20
+ require 'prime'
21
+
22
+ module Sapor
23
+ #
24
+ # A pseudo-random multi-range enumerator. It uses prime numbers to iterate
25
+ # in a pseudo-random way through the hyper-rectangle with dimensions
26
+ # provided as the constructor parameter. If a suitable set of primes can't
27
+ # be found, the constructor will raise an exception.
28
+ #
29
+ class PseudoRandomMultiRangeEnumerator
30
+ def initialize(dimensions)
31
+ @dimensions = dimensions
32
+ @incrementers = calculate_incrementers
33
+ end
34
+
35
+ def each
36
+ return enum_for { @dimensions.inject(:*) } unless block_given?
37
+ counters = Array.new(@dimensions.size, 0)
38
+ loop do
39
+ yield(counters.dup)
40
+ @incrementers.each_with_index do |incrementer, i|
41
+ counters[i] = (counters[i] + incrementer).modulo(@dimensions[i])
42
+ end
43
+ break if all_zeros?(counters)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def calculate_incrementers
50
+ incrementers = []
51
+ incrementer = 0
52
+ calculate_incrementer_indexes.each do |i|
53
+ incrementer = next_incrementer(incrementer)
54
+ if @dimensions[i] < incrementer
55
+ fail(ArgumentError, 'Could not construct suitable incrementers.')
56
+ end
57
+ incrementers[i] = incrementer
58
+ end
59
+ incrementers
60
+ end
61
+
62
+ def calculate_incrementer_indexes
63
+ @dimensions.each_with_index.sort.map(&:last)
64
+ end
65
+
66
+ def next_incrementer(incrementer)
67
+ incrementer += 1
68
+ while incrementer > 1 &&
69
+ (!prime?(incrementer) || !relative_prime?(incrementer))
70
+ incrementer += 1
71
+ end
72
+ incrementer
73
+ end
74
+
75
+ def prime?(number)
76
+ Prime.prime?(number)
77
+ end
78
+
79
+ def relative_prime?(incrementer)
80
+ @dimensions.map { |dimension| dimension.gcd(incrementer) }.max == 1
81
+ end
82
+
83
+ def all_zeros?(counters)
84
+ counters.max == 0
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,165 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Statistical Analysis of Polling Results (SAPoR)
4
+ # Copyright (C) 2016 Filip van Laenen <f.a.vanlaenen@ieee.org>
5
+ #
6
+ # This file is part of SAPoR.
7
+ #
8
+ # SAPoR is free software: you can redistribute it and/or modify it under the
9
+ # terms of the GNU General Public License as published by the Free Software
10
+ # Foundation, either version 3 of the License, or (at your option) any later
11
+ # version.
12
+ #
13
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
14
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16
+ #
17
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
18
+ #
19
+
20
+ module Sapor
21
+ #
22
+ # Represents a polychotomy for a referendum.
23
+ #
24
+ class ReferendumPolychotomy < Polychotomy
25
+ include NumberFormatter
26
+
27
+ def initialize(results, area, dichotomies, max_error)
28
+ super(results, area, dichotomies, max_error)
29
+ end
30
+
31
+ def refine
32
+ no_of_new_simulations = 0
33
+ new_votes = create_new_votes_distributions
34
+ while @no_of_data_points == 0 || no_of_new_simulations == 0 ||
35
+ no_of_new_simulations < @no_of_simulations
36
+ no_of_new_simulations += try_next_data_point(new_votes)
37
+ end
38
+ unless @no_of_simulations == 0
39
+ @error_estimate = calculate_error_estimate(new_votes)
40
+ end
41
+ @votes = merge_distributions(@votes, new_votes)
42
+ @no_of_simulations += no_of_new_simulations
43
+ end
44
+
45
+ def try_next_data_point(new_votes)
46
+ data_point = next_data_point
47
+ if data_point[OTHER] >= 0
48
+ simulate(new_votes, data_point)
49
+ new_simulation = 1
50
+ else
51
+ new_simulation = 0
52
+ end
53
+ @no_of_data_points += 1
54
+ new_simulation
55
+ end
56
+
57
+ def report
58
+ choice_lengths = @choices.map(&:length)
59
+ choice_lengths << 6
60
+ max_choice_width = choice_lengths.max
61
+ sorted_choices = sort_choices_by_result
62
+ choice_lines = sorted_choices.map.with_index do |choice, i|
63
+ next_choice = sorted_choices[i + 1]
64
+ create_choice_report_line(choice, i, next_choice, max_choice_width)
65
+ end
66
+ 'Most probable rounded fractions, fractions and 95% confidence' \
67
+ " intervals:\n" + 'Choice'.ljust(max_choice_width) +
68
+ ' Result MPRF MPF CI(95%) P(>↓) P(R↑) P(R)' \
69
+ " P(R↓)\n" + choice_lines.join("\n")
70
+ end
71
+
72
+ def progress_report
73
+ space_size_ratio = space_size.to_f / @no_of_data_points
74
+ if space_size_ratio > 10
75
+ space_size_ratio = with_thousands_separator(space_size_ratio.round)
76
+ else
77
+ space_size_ratio = space_size_ratio.round(1)
78
+ end
79
+ "#{with_thousands_separator(@no_of_simulations)} simulations out of " \
80
+ "#{with_thousands_separator(@no_of_data_points)} data" \
81
+ " points, 1 / #{space_size_ratio} of search space size" \
82
+ " (#{with_thousands_separator(space_size)})."
83
+ end
84
+
85
+ def write_outputs(filename)
86
+ write_rankings(filename)
87
+ end
88
+
89
+ private
90
+
91
+ def create_new_votes_distributions
92
+ votes = {}
93
+ @choices.each do |choice|
94
+ next if choice == OTHER
95
+ votes[choice] = CombinationsDistribution.new
96
+ @ranges[choice].each do |value|
97
+ votes[choice][value] = 0.to_lf
98
+ end
99
+ end
100
+ votes
101
+ end
102
+
103
+ def simulate(votes, data_point)
104
+ combinations = 1.to_lf
105
+ data_point.each do |choice, value|
106
+ combinations *= BinomialsCache.binomial(value, @results[choice])
107
+ end
108
+ @combinations_sum += combinations
109
+ data_point.each do |choice, value|
110
+ votes[choice][value] += combinations unless choice == OTHER
111
+ end
112
+ @choices.each do |a|
113
+ @choices.each do |b|
114
+ if data_point[a] > data_point[b]
115
+ @comparisons[a + '>' + b] += combinations
116
+ end
117
+ end
118
+ end
119
+ @choices.sort { |a, b| data_point[b] <=> data_point[a] }.each_with_index do |choice, i|
120
+ @rankings[choice + '@' + i.to_s] += combinations
121
+ end
122
+ end
123
+
124
+ def calculate_error_estimate(new_simulations)
125
+ error_estimate = 0
126
+ @choices.each do |choice|
127
+ next if choice == OTHER
128
+ mpv_new = calculate_most_probable_fraction(choice, new_simulations)
129
+ mpv_old = calculate_most_probable_fraction(choice, @votes)
130
+ mpv_delta = (mpv_new - mpv_old).abs
131
+ mprf_new = calculate_most_probable_rounded_fraction(choice, new_simulations)
132
+ mprf_old = calculate_most_probable_rounded_fraction(choice, @votes)
133
+ mprf_delta = (mprf_new - mprf_old).abs
134
+ error_estimate = [error_estimate, mpv_delta, mprf_delta].max
135
+ end
136
+ error_estimate
137
+ end
138
+
139
+ def merge_distributions(distributions1, distributions2)
140
+ merged_distributions = {}
141
+ @choices.each do |choice|
142
+ unless choice == OTHER
143
+ merged_distributions[choice] = distributions1[choice] + \
144
+ distributions2[choice]
145
+ end
146
+ end
147
+ merged_distributions
148
+ end
149
+
150
+ def create_choice_report_line(choice, i, next_choice, max_choice_width)
151
+ ci_values = @votes[choice].confidence_interval(0.95)
152
+ confidence_interval = ci_values.map { |x| x.to_f / @area.population_size }
153
+ choice.ljust(max_choice_width) + ' ' + \
154
+ six_char_percentage(result(choice)) + ' ' + \
155
+ six_char_percentage(most_probable_rounded_fraction(choice)) + ' ' + \
156
+ six_char_percentage(most_probable_fraction(choice)) + ' ' + \
157
+ six_char_percentage(confidence_interval.first) + '–' + \
158
+ six_char_percentage(confidence_interval.last) + ' ' + \
159
+ (next_choice.nil? ? ' ' : six_char_percentage(larger_than(choice, next_choice))) + ' ' + \
160
+ (i == 0 ? ' ' : six_char_percentage(ranking(choice, i - 1))) + ' ' + \
161
+ six_char_percentage(ranking(choice, i)) + \
162
+ (next_choice.nil? ? '' : ' ' + six_char_percentage(ranking(choice, i + 1)))
163
+ end
164
+ end
165
+ end