Skip to content

Commit 37e9465

Browse files
committed
Add experimental "Cycle-cutting" method.
This is a DMTC, summable, cloneproof method with more strategy resistance than ordinary Condorcet methods. But not monotone, at least not yet.
1 parent 414f8fe commit 37e9465

File tree

8 files changed

+380
-7
lines changed

8 files changed

+380
-7
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ else()
4949
# warning as it falsely triggers on SpookyHash.
5050
# -flto may lead to further speedups, but linking with it enabled is way
5151
# too slow, so it's been omitted here.
52-
set(CMAKE_CXX_FLAGS "-gdwarf-4 -ggdb -Wall -Wextra -Wno-unused-parameter -Wno-implicit-fallthrough -Werror -march=native -mtune=native -fno-math-errno -fno-trapping-math -fno-signed-zeros -Wno-unknown-warning-option")
52+
set(CMAKE_CXX_FLAGS "-gdwarf-4 -ggdb -Wall -Wextra -Wno-unused-parameter -Wno-implicit-fallthrough -Werror -march=native -mtune=native -fno-math-errno -fno-trapping-math -fno-signed-zeros -Wno-unknown-warning-option -fopenmp")
5353
endif()
5454

5555
# g++: Disable the free-nonheap-object warning as it seems to be buggy.
@@ -120,6 +120,7 @@ add_library(qe_singlewinner_methods
120120
src/singlewinner/cardinal/cumulative.cc
121121
src/singlewinner/contingent/donation.cc
122122
src/singlewinner/desc_coalitions/desc_coalition.cc
123+
src/singlewinner/dmt/cyclecut.cc
123124
src/singlewinner/dmt/fpa_fpc.cc
124125
src/singlewinner/dmt/ifpp_like.cc
125126
src/singlewinner/dmt/strat_ifpp.cc

src/pairwise/matrix.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
#ifndef _VOTE_C_MATRIX
2-
#define _VOTE_C_MATRIX
1+
#pragma once
32

43
#include "common/ballots.h"
54
#include "tools/tools.h"
@@ -89,6 +88,4 @@ class condmat : public abstract_condmat {
8988
void set_type(pairwise_type type_in) {
9089
type = type_in;
9190
}
92-
};
93-
94-
#endif
91+
};

src/singlewinner/dmt/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
This directory contains methods intended to pass dominant mutual third candidate burial resistance. These could possibly be used in combination with Condorcet and Smith prefixes to produce monotone low-burial election methods.
1+
This directory contains methods intended to pass dominant mutual third
2+
candidate burial resistance. These could possibly be used in combination with
3+
Condorcet and Smith prefixes to produce monotone low-burial election methods.
4+
5+
Most are monotone; any non-monotone method will have comments saying as much.

src/singlewinner/dmt/all.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
#include "ifpp_like.h"
44
#include "fpa_fpc.h"
5+
#include "cyclecut.h"
56
#include "strat_ifpp.h"

src/singlewinner/dmt/cyclecut.cc

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#include "cyclecut.h"
2+
#include "../pairwise/simple_methods.h"
3+
4+
#include <iostream>
5+
6+
// BEWARE: This does not handle equal rank or truncation!
7+
// TODO: FIX LATER.
8+
subelect_count_t get_triple_counts(const election_t & scores,
9+
const std::vector<bool> & hopefuls) {
10+
11+
subelect_count_t counts;
12+
13+
std::vector<size_t> involved_candidates(3);
14+
15+
//std::cout << "Constructing triple counts...\n";
16+
17+
for (const ballot_group & bg: scores) {
18+
//std::cout << "New ballot, weight " << bg.get_weight() << "\n";
19+
ordering::const_iterator cursor_a, cursor_b, cursor_c,
20+
end = bg.contents.end();
21+
22+
for (cursor_a = bg.contents.begin(); cursor_a != end;
23+
++cursor_a) {
24+
25+
if (!hopefuls[cursor_a->get_candidate_num()]) {
26+
continue;
27+
}
28+
29+
for (cursor_b = cursor_a; cursor_b != end; ++cursor_b) {
30+
if (cursor_a == cursor_b) {
31+
continue;
32+
}
33+
if (!hopefuls[cursor_b->get_candidate_num()]) {
34+
continue;
35+
}
36+
if (cursor_a->get_score() == cursor_b->get_score()) {
37+
continue;
38+
}
39+
40+
for (cursor_c = cursor_b; cursor_c != end; ++cursor_c) {
41+
if (cursor_b == cursor_c) {
42+
continue;
43+
}
44+
if (!hopefuls[cursor_c->get_candidate_num()]) {
45+
continue;
46+
}
47+
if (cursor_b->get_score() == cursor_c->get_score()) {
48+
continue;
49+
}
50+
51+
involved_candidates[0] = cursor_a->get_candidate_num();
52+
involved_candidates[1] = cursor_b->get_candidate_num();
53+
involved_candidates[2] = cursor_c->get_candidate_num();
54+
/*std::cout << "Updating ";
55+
std::copy(involved_candidates.begin(), involved_candidates.end(),
56+
std::ostream_iterator<size_t>(std::cout, " "));
57+
std::cout << "\n";*/
58+
std::sort(involved_candidates.begin(),
59+
involved_candidates.end());
60+
61+
// Set a_idx to the index of the highest ranked candidate
62+
// in the involved candidates vector after sorting.
63+
// (Just go through the vector to find the index.
64+
// Quick and dirty.)
65+
size_t a_idx = 0;
66+
while (involved_candidates[a_idx] !=
67+
cursor_a->get_candidate_num()) {
68+
++a_idx;
69+
}
70+
71+
subelect_count_t::iterator sc_pos =
72+
counts.find(involved_candidates);
73+
74+
if (sc_pos == counts.end()) {
75+
std::vector<double> unit_count(3, 0);
76+
unit_count[a_idx] = bg.get_weight();
77+
78+
counts[involved_candidates] = unit_count;
79+
} else {
80+
sc_pos->second[a_idx] += bg.get_weight();
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
return counts;
88+
}
89+
90+
// cycle_fpa_fpc
91+
92+
cycle_fpa_fpc::cycle_fpa_fpc(const std::vector<size_t> & cycle_in,
93+
const subelect_count_t & subelect_count) {
94+
95+
cycle = cycle_in;
96+
std::vector<size_t> sorted_cycle = cycle;
97+
std::sort(sorted_cycle.begin(), sorted_cycle.end());
98+
99+
std::vector<double> first_prefs = subelect_count.find(
100+
sorted_cycle)->second;
101+
102+
// Now descramble the count-order so it's in cycle order
103+
// instead of sorted candidate order. (Yeah, this is ugly.)
104+
105+
std::vector<double> cycle_order_fpps(3);
106+
for (size_t i = 0; i < 3; ++i) {
107+
for (size_t j = 0; j < 3; ++j) {
108+
if (sorted_cycle[i] == cycle[j]) {
109+
cycle_order_fpps[j] = first_prefs[i];
110+
}
111+
}
112+
}
113+
114+
/*std::cout << "Adding cycle ";
115+
std::copy(cycle_in.begin(), cycle_in.end(), std::ostream_iterator<size_t>(std::cout, " "));
116+
std::cout << " with first prefs ";
117+
std::copy(cycle_order_fpps.begin(), cycle_order_fpps.end(), std::ostream_iterator<double>(std::cout, " "));
118+
std::cout << "\n";*/
119+
120+
fpA_fpC_score = cycle_order_fpps[0] - cycle_order_fpps[2];
121+
}
122+
123+
// Cycle-cutting
124+
125+
bool cycle_cutting::is_cycle(const condmat & matrix,
126+
const std::vector<size_t> & c) const {
127+
128+
return matrix.beats(c[0], c[1]) && matrix.beats(c[1], c[2]) &&
129+
matrix.beats(c[2], c[0]);
130+
131+
}
132+
133+
std::pair<ordering, bool> cycle_cutting::elect_inner(
134+
const election_t & papers,
135+
const std::vector<bool> & hopefuls,
136+
int num_candidates, cache_map * cache,
137+
bool winner_only) const {
138+
139+
size_t numcands = num_candidates; // HACK
140+
141+
// First get the Condorcet matrix and triple subelection count.
142+
condmat matrix(papers, num_candidates, CM_WV);
143+
subelect_count_t subelect_count = get_triple_counts(
144+
papers, hopefuls);
145+
146+
// non-neutrality test. HACK
147+
for (size_t a = 0; a < numcands; ++a) {
148+
for (size_t b = a+1; b < numcands; ++b) {
149+
if (matrix.get_magnitude(a, b) == matrix.get_magnitude(b, a)) {
150+
matrix.add(a, b, 1e-5);
151+
}
152+
}
153+
}
154+
155+
// Then determine what three-candidate cycles exist, and for each
156+
// of these cycles, insert the cycle, and the fpA-fpC score of the
157+
// first candidate in that cycle for the subelection containing
158+
// only the cycle members.
159+
160+
std::vector<size_t> cycle(3);
161+
std::vector<cycle_fpa_fpc> cycle_scores;
162+
163+
for (cycle[0] = 0; cycle[0] < numcands; ++cycle[0]) {
164+
if (!hopefuls[cycle[0]]) {
165+
continue;
166+
}
167+
168+
for (cycle[1] = 0; cycle[1] < numcands; ++cycle[1]) {
169+
if (cycle[0] == cycle[1] || !hopefuls[cycle[1]]) {
170+
continue;
171+
}
172+
173+
for (cycle[2] = 0; cycle[2] < numcands; ++cycle[2]) {
174+
if (cycle[0] == cycle[2] || cycle[1] == cycle[2] ||
175+
!hopefuls[cycle[2]]) {
176+
continue;
177+
}
178+
179+
/*std::cout << "Checking if this is a cycle: ";
180+
std::copy(cycle.begin(), cycle.end(), std::ostream_iterator<size_t>(std::cout, " "));
181+
std::cout << "\n";*/
182+
183+
// We only care about cycles.
184+
if (!is_cycle(matrix, cycle)) {
185+
continue;
186+
}
187+
188+
cycle_scores.push_back(cycle_fpa_fpc(
189+
cycle, subelect_count));
190+
}
191+
}
192+
}
193+
194+
// Sort the list in descending order, and for each member of the list,
195+
// check if there's still a cycle. If so, break the cycle at the edge
196+
// pointing to the fpA-fpC winner.
197+
198+
std::sort(cycle_scores.begin(), cycle_scores.end(), std::greater<>());
199+
200+
for (const cycle_fpa_fpc & cur_cycle: cycle_scores) {
201+
// Check if it's still a cycle, since earlier cycle-breaking
202+
// might have broken this cycle too.
203+
if (!is_cycle(matrix, cur_cycle.cycle)) {
204+
continue;
205+
}
206+
207+
// Break the cycle between the last and first candidate.
208+
matrix.set(cur_cycle.cycle[2], cur_cycle.cycle[0], 0);
209+
}
210+
211+
// Once all of that is done, hand the modified matrix to a Condorcet
212+
// method and return its outcome.
213+
214+
return ord_minmax(CM_WV).pair_elect(matrix,
215+
hopefuls, cache, winner_only);
216+
}

0 commit comments

Comments
 (0)