sapor 0.3.3

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 +81 -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,64 @@
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
+ # Class representing a multi-district proportional electoral system with a
23
+ # second round of leveling seats.
24
+ #
25
+ class MultiDistrictLeveledProportional
26
+ def initialize(last_election_result, last_detailed_election_result,
27
+ seat_distribution, leveling_seats, leveling_threshold,
28
+ denominators_class)
29
+ @proportional = MultiDistrictProportional.new(last_election_result,
30
+ last_detailed_election_result,
31
+ seat_distribution, denominators_class)
32
+ @leveling_seats = leveling_seats
33
+ @leveling_threshold = leveling_threshold
34
+ @denominators_class = denominators_class
35
+ end
36
+
37
+ def project(simulation)
38
+ result = @proportional.project(simulation)
39
+ threshold = @leveling_threshold * simulation.values.inject(:+)
40
+ quotients = []
41
+ simulation.each_pair do |choice, votes|
42
+ next if votes < threshold
43
+ denominators(result[choice]).each do |d|
44
+ quotients << [choice, votes.to_f / d]
45
+ end
46
+ end
47
+ seats = quotients.sort { |a, b| b.last <=> a.last }.map(&:first).slice(0, @leveling_seats)
48
+ seats.each do |seat|
49
+ if result.key?(seat)
50
+ result[seat] += 1
51
+ else
52
+ result[seat] = 1
53
+ end
54
+ end
55
+ result
56
+ end
57
+
58
+ private
59
+
60
+ def denominators(seats)
61
+ @denominators_class.get(seats + @leveling_seats).reverse_each.take(@leveling_seats)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,123 @@
1
+ #
2
+ # Statistical Analysis of Polling Results (SAPoR)
3
+ # Copyright (C) 2016 Filip van Laenen <f.a.vanlaenen@ieee.org>
4
+ #
5
+ # This file is part of SAPoR.
6
+ #
7
+ # SAPoR is free software: you can redistribute it and/or modify it under the
8
+ # terms of the GNU General Public License as published by the Free Software
9
+ # Foundation, either version 3 of the License, or (at your option) any later
10
+ # version.
11
+ #
12
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15
+ #
16
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
17
+ #
18
+
19
+ module Sapor
20
+ #
21
+ # Class representing a proportional electoral system with more than one
22
+ # district.
23
+ #
24
+ class MultiDistrictProportional
25
+ def initialize(last_election_result, last_detailed_election_result,
26
+ seat_distribution, denominators_class, threshold = 0,
27
+ national_threshold = 0)
28
+ @last_election_result = last_election_result
29
+ @last_detailed_election_result = last_detailed_election_result
30
+ @seat_distribution = seat_distribution
31
+ @denominators_class = denominators_class
32
+ @threshold = threshold
33
+ @national_threshold = national_threshold
34
+ end
35
+
36
+ def project(simulation)
37
+ allowed_parties = []
38
+ unless @national_threshold.zero?
39
+ threshold = @national_threshold * simulation.values.inject(:+)
40
+ simulation.each_pair do |choice, votes|
41
+ allowed_parties << choice if votes >= threshold
42
+ end
43
+ end
44
+ multiplicators = calculate_multiplicators(simulation)
45
+ result = create_empty_result(simulation)
46
+ @last_detailed_election_result.each_pair do |name, local_last_result|
47
+ no_of_seats = @seat_distribution[name]
48
+ seats = local_seats(no_of_seats, local_last_result, multiplicators,
49
+ allowed_parties)
50
+ add_seats_to_result(result, seats)
51
+ end
52
+ result
53
+ end
54
+
55
+ private
56
+
57
+ def add_seats_to_result(result, seats)
58
+ seats.each do |seat|
59
+ if result.key?(seat)
60
+ result[seat] += 1
61
+ else
62
+ result[seat] = 1
63
+ end
64
+ end
65
+ end
66
+
67
+ def calculate_multiplicators(simulation)
68
+ simulation_sum = simulation.values.inject(:+)
69
+ last_election_sum = @last_election_result.values.inject(:+)
70
+ multiplicators = {}
71
+ simulation.each_key do |choice|
72
+ new_fraction = simulation[choice].to_f / simulation_sum
73
+ last_fraction = @last_election_result[choice].to_f / last_election_sum
74
+ multiplicators[choice] = new_fraction / last_fraction
75
+ end
76
+ multiplicators
77
+ end
78
+
79
+ def create_empty_result(simulation)
80
+ result = {}
81
+ simulation.each_key do |choice|
82
+ result[choice] = 0
83
+ end
84
+ result[OTHER] = 0
85
+ result
86
+ end
87
+
88
+ def local_votes(local_last_result, multiplicators)
89
+ local_votes = {}
90
+ local_last_result.each_pair do |choice, votes|
91
+ local_votes[choice] = if multiplicators.key?(choice)
92
+ votes * multiplicators[choice]
93
+ else
94
+ votes
95
+ end
96
+ end
97
+ local_votes
98
+ end
99
+
100
+ def local_quotients(local_votes, local_threshold, no_of_seats,
101
+ allowed_parties)
102
+ local_quotients = []
103
+ local_votes.each_pair do |choice, new_value|
104
+ next unless allowed_parties.empty? || allowed_parties.include?(choice)
105
+ next if new_value < local_threshold
106
+ @denominators_class.get(no_of_seats).each do |d|
107
+ local_quotients << [choice, new_value.to_f / d]
108
+ end
109
+ end
110
+ local_quotients
111
+ end
112
+
113
+ def local_seats(no_of_seats, local_last_result, multiplicators,
114
+ allowed_parties)
115
+ local_votes = local_votes(local_last_result, multiplicators)
116
+ local_threshold = local_votes.values.inject(:+).to_f * @threshold
117
+ local_quotients = local_quotients(local_votes, local_threshold,
118
+ no_of_seats, allowed_parties)
119
+ sorted_quotients = local_quotients.sort { |a, b| b.last <=> a.last }
120
+ sorted_quotients.map(&:first).slice(0, no_of_seats)
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,128 @@
1
+ #
2
+ # Statistical Analysis of Polling Results (SAPoR)
3
+ # Copyright (C) 2016 Filip van Laenen <f.a.vanlaenen@ieee.org>
4
+ #
5
+ # This file is part of SAPoR.
6
+ #
7
+ # SAPoR is free software: you can redistribute it and/or modify it under the
8
+ # terms of the GNU General Public License as published by the Free Software
9
+ # Foundation, either version 3 of the License, or (at your option) any later
10
+ # version.
11
+ #
12
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15
+ #
16
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
17
+ #
18
+
19
+ module Sapor
20
+ #
21
+ # Class representing a proportional electoral system with more than one
22
+ # district.
23
+ #
24
+ class MultiDistrictVariableThresholdProportional
25
+ def initialize(last_election_result, last_detailed_election_result,
26
+ seat_distribution, denominators_class,
27
+ party_list_threshold, coalition_list_threshold,
28
+ coalition_lists, minority_lists)
29
+ @last_election_result = last_election_result
30
+ @last_detailed_election_result = last_detailed_election_result
31
+ @seat_distribution = seat_distribution
32
+ @denominators_class = denominators_class
33
+ @party_list_threshold = party_list_threshold
34
+ @coalition_list_threshold = coalition_list_threshold
35
+ @coalition_lists = coalition_lists
36
+ @minority_lists = minority_lists
37
+ end
38
+
39
+ def project(simulation)
40
+ allowed_parties = []
41
+ total_votes = simulation.values.inject(:+)
42
+ party_list_threshold = @party_list_threshold * total_votes
43
+ coalition_list_threshold = @coalition_list_threshold * total_votes
44
+ simulation.each_pair do |choice, votes|
45
+ next unless @minority_lists.include?(choice) ||
46
+ @coalition_lists.include?(choice) &&
47
+ votes >= coalition_list_threshold ||
48
+ !@coalition_lists.include?(choice) &&
49
+ votes >= party_list_threshold
50
+ allowed_parties << choice
51
+ end
52
+ multiplicators = calculate_multiplicators(simulation)
53
+ result = create_empty_result(simulation)
54
+ @last_detailed_election_result.each_pair do |name, local_last_result|
55
+ no_of_seats = @seat_distribution[name]
56
+ seats = local_seats(no_of_seats, local_last_result, multiplicators,
57
+ allowed_parties)
58
+ add_seats_to_result(result, seats)
59
+ end
60
+ result
61
+ end
62
+
63
+ private
64
+
65
+ def add_seats_to_result(result, seats)
66
+ seats.each do |seat|
67
+ if result.key?(seat)
68
+ result[seat] += 1
69
+ else
70
+ result[seat] = 1
71
+ end
72
+ end
73
+ end
74
+
75
+ def calculate_multiplicators(simulation)
76
+ simulation_sum = simulation.values.inject(:+)
77
+ last_election_sum = @last_election_result.values.inject(:+)
78
+ multiplicators = {}
79
+ simulation.each_key do |choice|
80
+ new_fraction = simulation[choice].to_f / simulation_sum
81
+ last_fraction = @last_election_result[choice].to_f / last_election_sum
82
+ multiplicators[choice] = new_fraction / last_fraction
83
+ end
84
+ multiplicators
85
+ end
86
+
87
+ def create_empty_result(simulation)
88
+ result = {}
89
+ simulation.each_key do |choice|
90
+ result[choice] = 0
91
+ end
92
+ result[OTHER] = 0
93
+ result
94
+ end
95
+
96
+ def local_votes(local_last_result, multiplicators)
97
+ local_votes = {}
98
+ local_last_result.each_pair do |choice, votes|
99
+ local_votes[choice] = if multiplicators.key?(choice)
100
+ votes * multiplicators[choice]
101
+ else
102
+ votes
103
+ end
104
+ end
105
+ local_votes
106
+ end
107
+
108
+ def local_quotients(local_votes, no_of_seats, allowed_parties)
109
+ local_quotients = []
110
+ local_votes.each_pair do |choice, new_value|
111
+ next unless allowed_parties.empty? || allowed_parties.include?(choice)
112
+ @denominators_class.get(no_of_seats).each do |d|
113
+ local_quotients << [choice, new_value.to_f / d]
114
+ end
115
+ end
116
+ local_quotients
117
+ end
118
+
119
+ def local_seats(no_of_seats, local_last_result, multiplicators,
120
+ allowed_parties)
121
+ local_votes = local_votes(local_last_result, multiplicators)
122
+ local_quotients = local_quotients(local_votes, no_of_seats,
123
+ allowed_parties)
124
+ sorted_quotients = local_quotients.sort { |a, b| b.last <=> a.last }
125
+ sorted_quotients.map(&:first).slice(0, no_of_seats)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,45 @@
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
+ # Module to format numbers.
23
+ #
24
+ module NumberFormatter
25
+ def three_digits_percentage(number)
26
+ if number >= 0.9995 || number < 0.000005
27
+ sprintf('%.0f', number * 100) + '%'
28
+ elsif number >= 0.09995
29
+ sprintf('%.1f', number * 100) + '%'
30
+ elsif number >= 0.009995
31
+ sprintf('%.2f', number * 100) + '%'
32
+ else
33
+ sprintf('%.3f', number * 100) + '%'
34
+ end
35
+ end
36
+
37
+ def six_char_percentage(number)
38
+ sprintf('%5.1f', number * 100) + '%'
39
+ end
40
+
41
+ def with_thousands_separator(integer)
42
+ integer.to_s.reverse.gsub(/...(?=.)/, '\&,').reverse
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,73 @@
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
+ #
23
+ # Represents a set of options.
24
+ #
25
+ class Options
26
+ DEFAULT_MAX_ERROR = 0.001
27
+ DEFAULT_MAX_POLYCHOTOMY_ITERATIONS = 2 ** 25
28
+ DEFAULT_MIN_DICHOTOMIES_ITERATIONS = 0
29
+ DEFAULT_MIN_POLYCHOTOMY_ITERATIONS = 2 ** 20
30
+
31
+ MAX_ERROR_KEY = 'emx'
32
+ MAX_POLYCHOTOMY_ITERATIONS_KEY = 'pmx'
33
+ MIN_DICHOTOMIES_ITERATIONS_KEY = 'dmn'
34
+ MIN_POLYCHOTOMY_ITERATIONS_KEY = 'pmn'
35
+
36
+ FLOAT_OPTIONS = [MAX_ERROR_KEY]
37
+ INTEGER_OPTIONS = [MAX_POLYCHOTOMY_ITERATIONS_KEY, MIN_DICHOTOMIES_ITERATIONS_KEY, MIN_POLYCHOTOMY_ITERATIONS_KEY]
38
+
39
+ def initialize(arguments)
40
+ @hash = { MAX_ERROR_KEY => DEFAULT_MAX_ERROR,
41
+ MAX_POLYCHOTOMY_ITERATIONS_KEY => DEFAULT_MAX_POLYCHOTOMY_ITERATIONS,
42
+ MIN_DICHOTOMIES_ITERATIONS_KEY => DEFAULT_MIN_DICHOTOMIES_ITERATIONS,
43
+ MIN_POLYCHOTOMY_ITERATIONS_KEY => DEFAULT_MIN_POLYCHOTOMY_ITERATIONS }
44
+ arguments.each do | argument |
45
+ key_value = argument[1..-1].split('=')
46
+ key = key_value.first
47
+ value = key_value.last
48
+ if INTEGER_OPTIONS.include?(key)
49
+ value = value.to_i
50
+ elsif FLOAT_OPTIONS.include?(key)
51
+ value = value.to_f
52
+ end
53
+ @hash[key] = value
54
+ end
55
+ end
56
+
57
+ def max_error
58
+ @hash[MAX_ERROR_KEY]
59
+ end
60
+
61
+ def max_polychotomy_iterations
62
+ @hash[MAX_POLYCHOTOMY_ITERATIONS_KEY]
63
+ end
64
+
65
+ def min_dichotomies_iterations
66
+ @hash[MIN_DICHOTOMIES_ITERATIONS_KEY]
67
+ end
68
+
69
+ def min_polychotomy_iterations
70
+ @hash[MIN_POLYCHOTOMY_ITERATIONS_KEY]
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,286 @@
1
+ #
2
+ # Statistical Analysis of Polling Results (SAPoR)
3
+ # Copyright (C) 2016 Filip van Laenen <f.a.vanlaenen@ieee.org>
4
+ #
5
+ # This file is part of SAPoR.
6
+ #
7
+ # SAPoR is free software: you can redistribute it and/or modify it under the
8
+ # terms of the GNU General Public License as published by the Free Software
9
+ # Foundation, either version 3 of the License, or (at your option) any later
10
+ # version.
11
+ #
12
+ # SAPoR is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15
+ #
16
+ # You can find a copy of the GNU General Public License in /doc/gpl.txt
17
+ #
18
+
19
+ require 'yaml'
20
+
21
+ module Sapor
22
+ OTHER = 'Other'.freeze
23
+
24
+ #
25
+ # Represents a poll.
26
+ #
27
+ class Poll
28
+ include NumberFormatter
29
+ attr_reader :area, :logger, :type
30
+
31
+ AREA_KEY = 'Area'.freeze
32
+
33
+ BELGIAN_AREAS = [BelgiumBrussels.instance, BelgiumFlanders.instance,
34
+ BelgiumWallonia.instance, Flanders.instance,
35
+ Wallonia.instance].freeze
36
+ CATALONIAN_AREAS = [Catalonia.instance,
37
+ CataloniaWithJuntsPerCatalunya.instance,
38
+ CataloniaWithoutJuntsPelSi.instance].freeze
39
+ DANISH_AREAS = [Denmark.instance, DenmarkWithE.instance,
40
+ DenmarkWithEAndP.instance, DenmarkWithP.instance].freeze
41
+ EUROPEAN_UNION_AREAS = [EuropeanUnion27Austria.instance,
42
+ EuropeanUnion27Croatia.instance,
43
+ EuropeanUnion27Denmark.instance,
44
+ EuropeanUnion27Estonia.instance,
45
+ EuropeanUnion27Finland.instance,
46
+ EuropeanUnion27Ireland.instance,
47
+ EuropeanUnion27IrelandWithIa.instance,
48
+ EuropeanUnion27Italy.instance,
49
+ EuropeanUnion27Netherlands.instance,
50
+ EuropeanUnion27Poland.instance,
51
+ EuropeanUnion27Romania.instance,
52
+ EuropeanUnion27Slovakia.instance,
53
+ EuropeanUnion27Spain.instance,
54
+ EuropeanUnion27Sweden.instance,
55
+ EuropeanUnionAustria.instance,
56
+ EuropeanUnionBulgaria.instance,
57
+ EuropeanUnionCroatia.instance,
58
+ EuropeanUnionCyprus.instance,
59
+ EuropeanUnionCzechRepublic.instance,
60
+ EuropeanUnionDenmark.instance,
61
+ EuropeanUnionEstonia.instance,
62
+ EuropeanUnionFinland.instance,
63
+ EuropeanUnionFlanders.instance,
64
+ EuropeanUnionFrance.instance,
65
+ EuropeanUnionFrance2019.instance,
66
+ EuropeanUnionFrenchCommunityOfBelgium.instance,
67
+ EuropeanUnionGermany.instance,
68
+ EuropeanUnionGreatBritain.instance,
69
+ EuropeanUnionGreece.instance,
70
+ EuropeanUnionHungary.instance,
71
+ EuropeanUnionIreland.instance,
72
+ EuropeanUnionIrelandWithIa.instance,
73
+ EuropeanUnionItaly.instance,
74
+ EuropeanUnionLatvia.instance,
75
+ EuropeanUnionLithuania.instance,
76
+ EuropeanUnionLuxembourg.instance,
77
+ EuropeanUnionMalta.instance,
78
+ EuropeanUnionNetherlands.instance,
79
+ EuropeanUnionNorthernIreland.instance,
80
+ EuropeanUnionPoland.instance,
81
+ EuropeanUnionPortugal.instance,
82
+ EuropeanUnionRomania.instance,
83
+ EuropeanUnionSlovakia.instance,
84
+ EuropeanUnionSlovenia.instance,
85
+ EuropeanUnionSpain.instance,
86
+ EuropeanUnionSweden.instance].freeze
87
+ LATVIAN_AREAS = [Latvia.instance, LatviaKpv.instance, \
88
+ LatviaKpvPar.instance, LatviaKpvPPar.instance].freeze
89
+ NORWEGIAN_AREAS = [Norway.instance, NorwegianMunicipality::BERGEN,
90
+ NorwegianMunicipality::OSLO,
91
+ NorwegianMunicipality::TRONDHEIM].freeze
92
+ POLISH_AREAS = [Poland.instance,
93
+ PolandWithKoAndLWithoutNPoRAndZl.instance,
94
+ PolandWithKoKonfKpLAndZpWithoutKK15NPisPoPslRAndZl.instance,
95
+ PolandWithKoSldAndWiWithoutNPoAndZl.instance,
96
+ PolandWithSldAndWiWithoutZL.instance,
97
+ PolandWithSldWithoutZL.instance].freeze
98
+ PORTUGUESE_AREAS = [Portugal.instance,
99
+ PortugalWithAAndChWithoutPaf.instance,
100
+ PortugalWithAAndIlWithoutPaf.instance,
101
+ PortugalWithAChAndIlWithoutPaf.instance,
102
+ PortugalWithAWithoutPaf.instance,
103
+ PortugalWithChAndIlWithoutPaf.instance,
104
+ PortugalWithoutPaf.instance].freeze
105
+ AREAS_MAP = {}
106
+ (BELGIAN_AREAS + CATALONIAN_AREAS + DANISH_AREAS + EUROPEAN_UNION_AREAS + \
107
+ LATVIAN_AREAS + NORWEGIAN_AREAS + POLISH_AREAS + PORTUGUESE_AREAS + \
108
+ [Austria.instance, Estonia.instance, Finland.instance,
109
+ FinlandWithSin.instance, France.instance, Greece.instance,
110
+ Hungary.instance, Iceland.instance, Luxembourg.instance,
111
+ Netherlands.instance, Slovakia.instance, Slovenia.instance,
112
+ Spain.instance, Sweden.instance, Sweden20140914.instance,
113
+ UnitedKingdom.instance, UnitedKingdomWithBrexit.instance,
114
+ UnitedKingdomWithBrexitAndChuk.instance, UnitedKingdomWithTig.instance,
115
+ Utopia.instance]).map do |area|
116
+ AREAS_MAP[area.area_code] = area
117
+ end
118
+
119
+ TYPE_KEY = 'Type'.freeze
120
+ REFERENDUM_TYPE_VALUE = 'Referendum'.freeze
121
+ ELECTION_TYPE_VALUE = 'Election'.freeze
122
+
123
+ DEFAULT_CONFIDENCE_LEVEL = 0.95
124
+
125
+ def initialize(filename, metadata, results)
126
+ @filename = filename
127
+ @logger = LogFacade.create_logger
128
+ @area_code = metadata.delete(AREA_KEY)
129
+ @area = Poll.lookup_area(@area_code)
130
+ @type = metadata.delete(TYPE_KEY)
131
+ @results = interpret(results)
132
+ end
133
+
134
+ def analyze(options)
135
+ analyze_as_dichotomies(options)
136
+ analyze_as_polychotomy(options)
137
+ @logger.info('Done.')
138
+ end
139
+
140
+ def continue_analysis(options)
141
+ @logger = LogFacade.create_logger
142
+ @logger.info('Continuing the analysis as a polychotomy...')
143
+ @area = Poll.lookup_area(@area_code)
144
+ @analysis.revive_volatile_fields(@logger)
145
+ analyze_until_convergence(options)
146
+ @logger.info('Done.')
147
+ end
148
+
149
+ def confidence_interval(choice, level = DEFAULT_CONFIDENCE_LEVEL)
150
+ @analysis.confidence_interval(choice, level) unless @analysis.nil?
151
+ end
152
+
153
+ def most_probable_fraction(choice)
154
+ @analysis.most_probable_fraction(choice) unless @analysis.nil?
155
+ end
156
+
157
+ def most_probable_value(choice)
158
+ @analysis.most_probable_value(choice) unless @analysis.nil?
159
+ end
160
+
161
+ def result(choice)
162
+ @results[choice]
163
+ end
164
+
165
+ def encode_with(coder)
166
+ (instance_variables - [:@area, :@logger]).each do |var|
167
+ var = var.to_s
168
+ coder[var.gsub('@', '')] = eval(var)
169
+ end
170
+ end
171
+
172
+ def self.lookup_area(area_code)
173
+ AREAS_MAP[area_code]
174
+ end
175
+
176
+ private
177
+
178
+ def self.line_to_hash(line, current, results)
179
+ if line.chomp.eql?('==')
180
+ current = results
181
+ else
182
+ elements = line.chomp.split('=')
183
+ current[elements.first] = elements.last
184
+ end
185
+ current
186
+ end
187
+
188
+ def self.as_hashes(lines)
189
+ metadata = {}
190
+ results = {}
191
+ current = metadata
192
+ lines.each do |line|
193
+ current = line_to_hash(line, current, results)
194
+ end
195
+ [metadata, results]
196
+ end
197
+
198
+ def self.from_lines(filename, lines)
199
+ hashes = as_hashes(lines)
200
+ metadata = hashes.first
201
+ results = hashes.last
202
+ new(filename, metadata, results)
203
+ end
204
+
205
+ def self.from_file(filename)
206
+ from_lines(filename, File.open(filename))
207
+ end
208
+
209
+ def interpret(results)
210
+ interpreted = {}
211
+ results.each_pair do |key, value|
212
+ interpreted[key] = value.to_i
213
+ end
214
+ interpreted
215
+ end
216
+
217
+ def population_size
218
+ @area.population_size
219
+ end
220
+
221
+ def referendum?
222
+ @type == REFERENDUM_TYPE_VALUE
223
+ end
224
+
225
+ def threshold
226
+ if referendum?
227
+ 0.5
228
+ else
229
+ @area.threshold
230
+ end
231
+ end
232
+
233
+ def analyze_until_convergence(options)
234
+ while should_continue_analysis?(@analysis, options)
235
+ @analysis.refine
236
+ @logger.info(@analysis.report)
237
+ @logger.info('Error estimate: ε ≤ ' +
238
+ three_digits_percentage(@analysis.error_estimate) +
239
+ '.')
240
+ @logger.info(@analysis.progress_report)
241
+ @analysis.write_outputs(@filename)
242
+ save_state
243
+ end
244
+ end
245
+
246
+ def should_continue_analysis?(analysis, options)
247
+ analysis.kind_of?(Dichotomies) \
248
+ && (options.min_dichotomies_iterations > analysis.size \
249
+ || analysis.error_estimate > options.max_error) \
250
+ || analysis.kind_of?(Polychotomy) \
251
+ && options.max_polychotomy_iterations > analysis.no_of_simulations \
252
+ && (options.min_polychotomy_iterations > analysis.no_of_simulations \
253
+ || analysis.error_estimate > options.max_error)
254
+ end
255
+
256
+ def analyze_as_dichotomies(options)
257
+ @logger.info('Analyzing as a set of dichotomies...')
258
+ @analysis = Dichotomies.new(@results, population_size, threshold)
259
+ analyze_until_convergence(options)
260
+ end
261
+
262
+ def analyze_as_polychotomy(options)
263
+ @logger.info('Analyzing as a polychotomy...')
264
+ if referendum?
265
+ @analysis = ReferendumPolychotomy.new(@results, @area, @analysis,
266
+ options.max_error)
267
+ else
268
+ @analysis = RepresentativesPolychotomy.new(@results, @area, @analysis,
269
+ options.max_error, @logger)
270
+ end
271
+ analyze_until_convergence(options)
272
+ end
273
+
274
+ def save_state
275
+ new_yaml_file = @filename.gsub('.poll', '-state-new.yaml')
276
+ open(new_yaml_file, 'w') do |file|
277
+ file.write(to_yaml)
278
+ end
279
+ yaml_file = @filename.gsub('.poll', '-state.yaml')
280
+ old_yaml_file = @filename.gsub('.poll', '-state-old.yaml')
281
+ File.delete(old_yaml_file) if File.exist?(old_yaml_file)
282
+ File.rename(yaml_file, old_yaml_file) if File.exist?(yaml_file)
283
+ File.rename(new_yaml_file, yaml_file)
284
+ end
285
+ end
286
+ end