Skip to content

Commit c8b4996

Browse files
committed
Refactor resistant set, add indirect disqualification set.
The latter might later lead to a monotone resistant set analog or method.
1 parent f81633c commit c8b4996

File tree

7 files changed

+442
-37
lines changed

7 files changed

+442
-37
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ add_library(qe_singlewinner_methods
170170
src/singlewinner/random/randcand.cc
171171
src/singlewinner/sets/condorcet.cc
172172
src/singlewinner/sets/debug/venzke_landau.cc
173+
src/singlewinner/sets/disqset.cc
173174
src/singlewinner/sets/dmt.cc
174175
src/singlewinner/sets/inner_burial.cc
175176
src/singlewinner/sets/max_elements/det_sets.cc

src/main/monotonator.cc

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "tools/ballot_tools.h"
99
#include "common/ballots.h"
1010

11+
#include "singlewinner/dmt/all.h"
1112
#include "singlewinner/elimination/all.h"
1213
#include "singlewinner/experimental/all.h"
1314
#include "singlewinner/meta/all.h"
@@ -19,20 +20,23 @@
1920

2021
int main() {
2122

22-
int max_numvoters = 30, max_numcands = 4; // E.g.
23+
int max_numvoters = 30, max_numcands = 3; // E.g.
2324

2425
// TODO get seed from an entropy source, see quadelect proper
2526
std::shared_ptr<rng> rnd = std::make_shared<rng>(0);
2627

2728
auto method_tested =
28-
std::make_shared<disqelim>();
29-
//std::make_shared<instant_runoff_voting>(PT_WHOLE, true);
30-
/*std::make_shared<slash>(std::make_shared<rmr1>(RMR_DEFEATING),
31-
std::make_shared<ext_minmax>(CM_WV, false));*/
32-
//std::make_shared<rmr1>(RMR_TWO_WAY);
33-
/*std::make_shared<comma>(std::make_shared<inner_burial_set>(),
34-
std::make_shared<rmr1>(RMR_TWO_WAY));*/
35-
//std::make_shared<ext_minmax>(CM_WV, false);
29+
/* std::make_shared<loser_elimination>(
30+
std::make_shared<plurality>(PT_WHOLE),
31+
false, true, true);*/
32+
//std::make_shared<cycle_cutting>();
33+
//std::make_shared<instant_runoff_voting>(PT_WHOLE, true);
34+
/*std::make_shared<slash>(std::make_shared<rmr1>(RMR_DEFEATING),
35+
std::make_shared<ext_minmax>(CM_WV, false));*/
36+
//std::make_shared<rmr1>(RMR_TWO_WAY);
37+
//std::make_shared<comma>(std::make_shared<idisqualif_set>(),
38+
//std::make_shared<rmr1>(RMR_TWO_WAY));
39+
std::make_shared<idisqualif_set>();
3640

3741
std::shared_ptr<impartial> ballot_gen =
3842
std::make_shared<impartial>(false, false);

src/singlewinner/sets/all.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#pragma once
99

1010
#include "condorcet.h"
11+
#include "disqset.h"
1112
#include "dmt.h"
1213
#include "inner_burial.h"
1314
#include "mdd.h"

src/singlewinner/sets/disqset.cc

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
#include <cassert>
2+
#include <iostream>
3+
#include <stdexcept>
4+
5+
#include "disqset.h"
6+
#include "max_elements/smith.h"
7+
8+
// Recursively test the indirect disqualification condition for
9+
// chain_members.rbegin() ~> leaf:
10+
// Call the members last and leaf.
11+
// Go through each subelection, given by the membership set S:
12+
// If root is not in S, but both last and leaf are in it:
13+
// If fp{last} <= 1/|S|, then return (last doesn't disqualify
14+
// leaf).
15+
// If the whole chain_members list, *and* leaf is in S,
16+
// If the sum of first prefs of the chain_members list, does
17+
// not exceed k/|S|, then return (last doesn't
18+
// disqualify leaf through the root), where k is the
19+
// length of that list.
20+
// If we have gone through every applicable subelection without
21+
// aborting, then:
22+
// Set disqualified[leaf], and add leaf to the chain.
23+
// Recurse for every non-chain member.
24+
// Remove leaf from the chain and return.
25+
26+
// The chain must start with the root.
27+
28+
void idisqualif_set::explore_paths(
29+
const std::vector<bool> & hopefuls,
30+
const subelections & se,
31+
std::vector<size_t> & chain_members,
32+
std::vector<bool> & chain_members_bool,
33+
size_t leaf, std::vector<bool> & disqualified) const {
34+
35+
if (chain_members.empty()) {
36+
throw std::invalid_argument(
37+
"explore_paths: Chain must contain root!");
38+
}
39+
40+
size_t root = chain_members[0],
41+
last = *chain_members.rbegin();
42+
size_t num_subelections = se.hopeful_power_set.size();
43+
44+
// TODO: Check that this also verifies pairwise.
45+
46+
for (size_t se_idx = 0; se_idx < num_subelections; ++se_idx) {
47+
// If leaf isn't in it, then it's not interesting.
48+
if (!se.hopeful_power_set[se_idx][leaf]) {
49+
continue;
50+
}
51+
52+
/*if (se.num_remaining_candidates[se_idx] == 2) {
53+
std::cout << "debug, pairwise included" << std::endl;
54+
}*/
55+
56+
// If last and leaf are in it but root isn't, check
57+
// ordinary disqualification.
58+
59+
if (se.hopeful_power_set[se_idx][last] &&
60+
!se.hopeful_power_set[se_idx][root]) {
61+
// fp(last)_S <= numvoters/|S|, or for numerical stability,
62+
// |S| * fp(last)_S <= numvoters
63+
if (se.num_remaining_candidates[se_idx] *
64+
se.first_pref_scores[se_idx][last] <=
65+
se.num_remaining_voters[se_idx]) {
66+
// last doesn't disqualify leaf.
67+
return;
68+
}
69+
}
70+
71+
// Skip if not everybody in the current chain is in the
72+
// subelection.
73+
bool all_members = true;
74+
double combined_first_prefs = 0;
75+
for (size_t chain_cand : chain_members) {
76+
all_members &= se.hopeful_power_set[se_idx][chain_cand];
77+
combined_first_prefs += se.first_pref_scores[se_idx][chain_cand];
78+
79+
if (!all_members) {
80+
continue;
81+
}
82+
}
83+
if (!all_members) {
84+
continue;
85+
}
86+
87+
// If we fail to disqualify leaf through the root, abort.
88+
// combined_first_prefs <= numvoters * chain_members.size() / |S|
89+
// i.e. |S| * cfs <= numvoters * chain_members.size()
90+
if (se.num_remaining_candidates[se_idx] *
91+
combined_first_prefs <=
92+
se.num_remaining_voters[se_idx] * chain_members.size()) {
93+
// last doesn't disqualify leaf.
94+
return;
95+
}
96+
}
97+
98+
// Last does disqualify leaf!
99+
100+
// Set disqualification and add the leaf to the chain.
101+
disqualified[leaf] = true;
102+
chain_members.push_back(leaf);
103+
assert(!chain_members_bool[leaf]);
104+
chain_members_bool[leaf] = true;
105+
106+
// Recurse into every hopeful non-chain member.
107+
for (size_t candidate_leaf = 0; candidate_leaf < hopefuls.size();
108+
++candidate_leaf) {
109+
110+
if (!hopefuls[candidate_leaf] ||
111+
chain_members_bool[candidate_leaf]) {
112+
continue;
113+
}
114+
115+
explore_paths(hopefuls, se, chain_members,
116+
chain_members_bool, candidate_leaf,
117+
disqualified);
118+
}
119+
120+
// Remove the leaf member from the chain so that whoever is
121+
// calling us can try another branch.
122+
chain_members.pop_back();
123+
assert(chain_members_bool[leaf]);
124+
chain_members_bool[leaf] = false;
125+
}
126+
127+
std::vector<std::vector<bool> > idisqualif_set::explore_all_paths(
128+
const election_t & papers,
129+
const std::vector<bool> & hopefuls) const {
130+
131+
size_t numcands = hopefuls.size();
132+
133+
subelections se;
134+
se.count_subelections(papers, hopefuls, true);
135+
136+
std::vector<std::vector<bool> > disqualification_matrix(
137+
numcands, std::vector<bool>(numcands, false));
138+
139+
for (size_t root = 0; root < numcands; ++root) {
140+
if (!hopefuls[root]) {
141+
continue;
142+
}
143+
for (size_t leaf = 0; leaf < numcands; ++leaf) {
144+
if (!hopefuls[leaf] || root == leaf) {
145+
continue;
146+
}
147+
148+
std::vector<size_t> chain_members = {root};
149+
std::vector<bool> chain_members_bool(numcands, false);
150+
chain_members_bool[root] = true;
151+
152+
explore_paths(hopefuls, se, chain_members,
153+
chain_members_bool, leaf,
154+
disqualification_matrix[root]);
155+
}
156+
}
157+
158+
return disqualification_matrix;
159+
}
160+
161+
// Standard DFS search to detect a cycle.
162+
163+
bool idisqualif_set::has_cycle(
164+
const std::vector<std::vector<bool> > & disqualifications,
165+
std::vector<bool> & visited,
166+
size_t cur_cand) const {
167+
168+
size_t numcands = disqualifications.size();
169+
170+
visited[cur_cand] = true;
171+
172+
for (size_t next_candidate = 0; next_candidate < numcands;
173+
++next_candidate) {
174+
// Skip if there's no edge to next_candidate.
175+
if (!disqualifications[cur_cand][next_candidate]) {
176+
continue;
177+
}
178+
179+
if (visited[next_candidate]) {
180+
return true;
181+
} else {
182+
return has_cycle(disqualifications,
183+
visited, next_candidate);
184+
}
185+
}
186+
187+
return false;
188+
}
189+
190+
bool idisqualif_set::has_cycle(
191+
const std::vector<std::vector<bool> > &
192+
disqualifications) const {
193+
194+
size_t numcands = disqualifications.size();
195+
196+
for (size_t candidate = 0; candidate < numcands; ++candidate) {
197+
std::vector<bool> visited(numcands, false);
198+
199+
if (has_cycle(disqualifications, visited, candidate)) {
200+
return true;
201+
}
202+
}
203+
204+
return false;
205+
}
206+
207+
void idisqualif_set::print(const std::vector<std::vector<bool> > &
208+
disqualifications) const {
209+
210+
size_t numcands = disqualifications.size();
211+
212+
for (size_t i = 0; i < numcands; ++i) {
213+
for (size_t j = 0; j < numcands; ++j) {
214+
if (disqualifications[i][j]) {
215+
std::cout << (char)('A' + i) << " disqualifies "
216+
<< (char)('A'+j) << "\n";
217+
}
218+
}
219+
}
220+
}
221+
222+
std::pair<ordering, bool> idisqualif_set::elect_inner(
223+
const election_t & papers,
224+
const std::vector<bool> & hopefuls,
225+
int num_candidates, cache_map * cache, bool winner_only) const {
226+
227+
std::vector<std::vector<bool> > disq_matrix = explore_all_paths(
228+
papers, hopefuls);
229+
230+
if (has_cycle(disq_matrix)) {
231+
print(disq_matrix);
232+
throw std::runtime_error("idisqualif_set: Yowza! Cycle detected!");
233+
}
234+
235+
size_t numcands = hopefuls.size(); // handle sign-compare warning
236+
237+
// Turn the set vector into an ordering and return.
238+
ordering inner_set;
239+
240+
for (size_t cand = 0; cand < numcands; ++cand) {
241+
if (!hopefuls[cand]) {
242+
continue;
243+
}
244+
size_t defeated_by = 0;
245+
246+
for (size_t i = 0; i < numcands; ++i) {
247+
if (!hopefuls[i] || i == cand) {
248+
continue;
249+
}
250+
if (disq_matrix[i][cand]) {
251+
++defeated_by;
252+
}
253+
}
254+
255+
inner_set.insert(candscore(cand, numcands-defeated_by));
256+
}
257+
258+
return std::pair<ordering, bool>(inner_set, false);
259+
260+
/*
261+
// TODO, don't make it count, just do a blank matrix.
262+
condmat matrix(papers, num_candidates, CM_PAIRWISE_OPP);
263+
264+
for (size_t incumbent = 0; incumbent < numcands; ++incumbent) {
265+
for (size_t challenger = 0; challenger < numcands; ++challenger) {
266+
matrix.set(incumbent, challenger, 0);
267+
if (!hopefuls[incumbent]) { continue; }
268+
if (!hopefuls[challenger] || incumbent == challenger) { continue; }
269+
270+
if (disq_matrix[incumbent][challenger]) {
271+
matrix.set(incumbent, challenger, 1);
272+
if (disq_matrix[challenger][incumbent]) {
273+
throw std::runtime_error("Yowza! Cycle detected!");
274+
}
275+
}
276+
}
277+
}
278+
279+
return smith_set().pair_elect(matrix, hopefuls,
280+
cache, winner_only);
281+
*/
282+
}

0 commit comments

Comments
 (0)