#![allow(clippy::items_after_test_module)]
use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
use super::types::{HedonicGame, LiquidDemocracy, PreferenceProfile, WeightedVotingGame};
pub fn app(f: Expr, a: Expr) -> Expr {
Expr::App(Box::new(f), Box::new(a))
}
pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
app(app(f, a), b)
}
pub fn app3(f: Expr, a: Expr, b: Expr, c: Expr) -> Expr {
app(app2(f, a, b), c)
}
pub fn cst(s: &str) -> Expr {
Expr::Const(Name::str(s), vec![])
}
pub fn prop() -> Expr {
Expr::Sort(Level::zero())
}
pub fn type0() -> Expr {
Expr::Sort(Level::succ(Level::zero()))
}
pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
Expr::Pi(bi, Name::str(name), Box::new(dom), Box::new(body))
}
pub fn arrow(a: Expr, b: Expr) -> Expr {
pi(BinderInfo::Default, "_", a, b)
}
pub fn bvar(n: u32) -> Expr {
Expr::BVar(n)
}
pub fn nat_ty() -> Expr {
cst("Nat")
}
pub fn real_ty() -> Expr {
cst("Real")
}
pub fn bool_ty() -> Expr {
cst("Bool")
}
pub fn list_ty(elem: Expr) -> Expr {
app(cst("List"), elem)
}
pub fn preference_profile_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn social_welfare_function_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn social_choice_function_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn iia_ty() -> Expr {
arrow(prop(), prop())
}
pub fn pareto_optimality_ty() -> Expr {
arrow(prop(), prop())
}
pub fn non_dictatorship_ty() -> Expr {
arrow(prop(), prop())
}
pub fn condorcet_winner_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn strategy_proof_ty() -> Expr {
arrow(prop(), prop())
}
pub fn dictatorial_ty() -> Expr {
arrow(prop(), prop())
}
pub fn majority_rule_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn arrow_impossibility_ty() -> Expr {
arrow(
nat_ty(),
arrow(nat_ty(), arrow(prop(), arrow(prop(), prop()))),
)
}
pub fn gibbard_satterthwaite_ty() -> Expr {
arrow(
nat_ty(),
arrow(nat_ty(), arrow(prop(), arrow(prop(), prop()))),
)
}
pub fn may_theorem_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn condorcet_paradox_ty() -> Expr {
prop()
}
pub fn build_social_choice_env(env: &mut Environment) {
let axioms: &[(&str, Expr)] = &[
("PreferenceProfile", preference_profile_ty()),
("SocialWelfareFunction", social_welfare_function_ty()),
("SocialChoiceFunction", social_choice_function_ty()),
("IndependenceOfIrrelevantAlternatives", iia_ty()),
("ParetoOptimality", pareto_optimality_ty()),
("NonDictatorship", non_dictatorship_ty()),
("CondorcetWinner", condorcet_winner_ty()),
("StrategyProof", strategy_proof_ty()),
("Dictatorial", dictatorial_ty()),
("MajorityRule", majority_rule_ty()),
("arrow_impossibility", arrow_impossibility_ty()),
("gibbard_satterthwaite", gibbard_satterthwaite_ty()),
("may_theorem", may_theorem_ty()),
("condorcet_paradox", condorcet_paradox_ty()),
("BordaCount", arrow(nat_ty(), arrow(nat_ty(), nat_ty()))),
("PluralityRule", arrow(list_ty(nat_ty()), nat_ty())),
("ApprovalVoting", arrow(list_ty(bool_ty()), nat_ty())),
("RangeVoting", arrow(list_ty(real_ty()), nat_ty())),
("InstantRunoff", arrow(list_ty(list_ty(nat_ty())), nat_ty())),
(
"LiquidDemocracy",
arrow(nat_ty(), arrow(nat_ty(), nat_ty())),
),
("SchulzeMethod", arrow(list_ty(nat_ty()), nat_ty())),
(
"PairwiseMajority",
arrow(
nat_ty(),
arrow(nat_ty(), arrow(list_ty(list_ty(nat_ty())), bool_ty())),
),
),
];
for (name, ty) in axioms {
env.add(Declaration::Axiom {
name: Name::str(*name),
univ_params: vec![],
ty: ty.clone(),
})
.ok();
}
}
pub fn condorcet_winner(profile: &PreferenceProfile) -> Option<usize> {
'outer: for alt in 0..profile.n_alts {
for other in 0..profile.n_alts {
if alt == other {
continue;
}
if !profile.majority_beats(alt, other) {
continue 'outer;
}
}
return Some(alt);
}
None
}
pub fn condorcet_cycle(profile: &PreferenceProfile) -> Option<(usize, usize, usize)> {
let m = profile.n_alts;
for a in 0..m {
for b in 0..m {
if b == a {
continue;
}
if !profile.majority_beats(a, b) {
continue;
}
for c in 0..m {
if c == a || c == b {
continue;
}
if profile.majority_beats(b, c) && profile.majority_beats(c, a) {
return Some((a, b, c));
}
}
}
}
None
}
pub fn borda_count(profile: &PreferenceProfile) -> Vec<(usize, usize)> {
let mut scores = vec![0usize; profile.n_alts];
for voter in 0..profile.n_voters {
for (rank, &alt) in profile.rankings[voter].iter().enumerate() {
scores[alt] += profile.n_alts - 1 - rank;
}
}
let mut result: Vec<(usize, usize)> = scores.into_iter().enumerate().collect();
result.sort_by_key(|b| std::cmp::Reverse(b.1));
result
}
pub fn borda_winner(profile: &PreferenceProfile) -> usize {
borda_count(profile)[0].0
}
pub fn plurality_vote(profile: &PreferenceProfile) -> (usize, Vec<usize>) {
let mut counts = vec![0usize; profile.n_alts];
for voter in 0..profile.n_voters {
if !profile.rankings[voter].is_empty() {
counts[profile.rankings[voter][0]] += 1;
}
}
let winner = counts
.iter()
.enumerate()
.max_by_key(|(_, &c)| c)
.map(|(i, _)| i)
.unwrap_or(0);
(winner, counts)
}
pub fn instant_runoff(profile: &PreferenceProfile) -> usize {
let mut active: Vec<bool> = vec![true; profile.n_alts];
let mut n_active = profile.n_alts;
loop {
let mut counts = vec![0usize; profile.n_alts];
for voter in 0..profile.n_voters {
for &alt in &profile.rankings[voter] {
if active[alt] {
counts[alt] += 1;
break;
}
}
}
let total_votes: usize = counts.iter().sum();
for (alt, &count) in counts.iter().enumerate() {
if active[alt] && count * 2 > total_votes {
return alt;
}
}
if n_active <= 1 {
return active.iter().position(|&a| a).unwrap_or(0);
}
let min_votes = counts
.iter()
.enumerate()
.filter(|(i, _)| active[*i])
.map(|(_, &c)| c)
.min()
.unwrap_or(0);
for (alt, &count) in counts.iter().enumerate() {
if active[alt] && count == min_votes {
active[alt] = false;
n_active -= 1;
break;
}
}
}
}
pub fn approval_vote(approvals: &[Vec<bool>]) -> (usize, Vec<usize>) {
let n_alts = if approvals.is_empty() {
0
} else {
approvals[0].len()
};
let mut counts = vec![0usize; n_alts];
for voter_approvals in approvals {
for (alt, &approved) in voter_approvals.iter().enumerate() {
if approved {
counts[alt] += 1;
}
}
}
let winner = counts
.iter()
.enumerate()
.fold((0usize, 0usize), |(best_i, best_c), (i, &c)| {
if c > best_c {
(i, c)
} else {
(best_i, best_c)
}
})
.0;
(winner, counts)
}
pub fn range_vote(scores: &[Vec<f64>]) -> (usize, Vec<f64>) {
let n_alts = if scores.is_empty() {
0
} else {
scores[0].len()
};
let mut totals = vec![0.0f64; n_alts];
for voter_scores in scores {
for (alt, &score) in voter_scores.iter().enumerate() {
totals[alt] += score;
}
}
let winner = totals
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map(|(i, _)| i)
.unwrap_or(0);
(winner, totals)
}
pub fn pareto_dominates(profile: &PreferenceProfile, x: usize, y: usize) -> bool {
let all_weakly = (0..profile.n_voters).all(|v| !profile.prefers(v, y, x));
let some_strictly = (0..profile.n_voters).any(|v| profile.prefers(v, x, y));
all_weakly && some_strictly
}
pub fn pareto_optimal_set(profile: &PreferenceProfile) -> Vec<usize> {
(0..profile.n_alts)
.filter(|&x| (0..profile.n_alts).all(|y| y == x || !pareto_dominates(profile, y, x)))
.collect()
}
pub type SocialRanking = Vec<usize>;
pub fn borda_ranks_above(profile: &PreferenceProfile, x: usize, y: usize) -> bool {
let scores = borda_count(profile);
let score_x = scores
.iter()
.find(|(a, _)| *a == x)
.map(|(_, s)| *s)
.unwrap_or(0);
let score_y = scores
.iter()
.find(|(a, _)| *a == y)
.map(|(_, s)| *s)
.unwrap_or(0);
score_x > score_y
}
pub fn check_iia_violation(
profile1: &PreferenceProfile,
profile2: &PreferenceProfile,
x: usize,
y: usize,
) -> bool {
let agree = (0..profile1.n_voters.min(profile2.n_voters))
.all(|v| profile1.prefers(v, x, y) == profile2.prefers(v, x, y));
if !agree {
return false;
}
borda_ranks_above(profile1, x, y) != borda_ranks_above(profile2, x, y)
}
pub fn plurality_manipulation(profile: &PreferenceProfile, voter: usize) -> Option<usize> {
let (honest_winner, _) = plurality_vote(profile);
let voter_top = profile.rankings[voter][0];
if honest_winner == voter_top {
return None;
}
for &deceptive_top in &profile.rankings[voter] {
if deceptive_top == profile.rankings[voter][0] {
continue;
}
let mut modified_rankings = profile.rankings.clone();
let mut new_ranking: Vec<usize> = vec![deceptive_top];
new_ranking.extend(
profile.rankings[voter]
.iter()
.filter(|&&a| a != deceptive_top),
);
modified_rankings[voter] = new_ranking;
let modified_profile = PreferenceProfile::new(modified_rankings);
let (new_winner, _) = plurality_vote(&modified_profile);
if profile.prefers(voter, new_winner, honest_winner) {
return Some(new_winner);
}
}
None
}
pub fn majority_vote_two(votes_for_0: usize, votes_for_1: usize) -> Option<usize> {
use std::cmp::Ordering;
match votes_for_0.cmp(&votes_for_1) {
Ordering::Greater => Some(0),
Ordering::Less => Some(1),
Ordering::Equal => None,
}
}
pub fn schulze_winner(profile: &PreferenceProfile) -> usize {
let m = profile.n_alts;
let mut d = vec![vec![0usize; m]; m];
for i in 0..m {
for j in 0..m {
if i != j {
d[i][j] = profile.majority_margin(i, j);
}
}
}
let mut p = vec![vec![0usize; m]; m];
for i in 0..m {
for j in 0..m {
if i != j && d[i][j] > d[j][i] {
p[i][j] = d[i][j];
}
}
}
for k in 0..m {
for i in 0..m {
for j in 0..m {
if i != j && i != k && j != k {
p[i][j] = p[i][j].max(p[i][k].min(p[k][j]));
}
}
}
}
let mut wins = vec![0usize; m];
for i in 0..m {
for j in 0..m {
if i != j && p[i][j] > p[j][i] {
wins[i] += 1;
}
}
}
wins.iter()
.enumerate()
.max_by_key(|(_, &w)| w)
.map(|(i, _)| i)
.unwrap_or(0)
}
pub fn brier_score_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn spherical_score_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn log_score_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn proper_scoring_rule_ty() -> Expr {
arrow(prop(), prop())
}
pub fn strictly_proper_scoring_rule_ty() -> Expr {
arrow(prop(), prop())
}
pub fn scoring_rule_characterization_ty() -> Expr {
arrow(prop(), arrow(prop(), prop()))
}
pub fn judgment_aggregation_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn doctrinal_paradox_ty() -> Expr {
prop()
}
pub fn judgment_aggregation_impossibility_ty() -> Expr {
prop()
}
pub fn quota_rule_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn premise_based_procedure_ty() -> Expr {
arrow(prop(), prop())
}
pub fn conclusion_based_procedure_ty() -> Expr {
arrow(prop(), prop())
}
pub fn agm_revision_ty() -> Expr {
arrow(prop(), arrow(prop(), prop()))
}
pub fn agm_contraction_ty() -> Expr {
arrow(prop(), arrow(prop(), prop()))
}
pub fn belief_merge_operator_ty() -> Expr {
arrow(nat_ty(), arrow(prop(), prop()))
}
pub fn majority_merge_ty() -> Expr {
arrow(prop(), prop())
}
pub fn envy_free_allocation_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn efficient_allocation_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn top_trading_cycles_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn cake_cutting_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn proportional_allocation_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn envy_freeness_cake_cutting_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn deliberative_democracy_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn consensus_aggregation_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn hegselmann_krause_model_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn influence_propagation_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn median_rule_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn distance_rationalizability_ty() -> Expr {
arrow(prop(), prop())
}
pub fn probabilistic_social_choice_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn ssb_utility_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn lottery_dominance_ty() -> Expr {
arrow(prop(), arrow(prop(), prop()))
}
pub fn spatial_voting_model_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn median_voter_theorem_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn multi_dimensional_median_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn hedonic_game_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn stable_partition_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn core_coalition_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn nash_stable_partition_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn shapley_shubik_index_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn banzhaf_index_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn weighted_voting_game_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn condorcet_jury_theorem_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn truth_tracking_ty() -> Expr {
arrow(prop(), prop())
}
pub fn epistemic_democracy_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn transitive_delegation_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn delegation_graph_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn guruly_critique_ty() -> Expr {
prop()
}
pub fn manipulation_complexity_ty() -> Expr {
arrow(prop(), prop())
}
pub fn bribery_complexity_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn control_complexity_ty() -> Expr {
arrow(prop(), prop())
}
pub fn build_social_choice_env_extended(env: &mut Environment) {
let axioms: &[(&str, Expr)] = &[
("BrierScore", brier_score_ty()),
("SphericalScore", spherical_score_ty()),
("LogScore", log_score_ty()),
("ProperScoringRule", proper_scoring_rule_ty()),
(
"StrictlyProperScoringRule",
strictly_proper_scoring_rule_ty(),
),
(
"scoring_rule_characterization",
scoring_rule_characterization_ty(),
),
("JudgmentAggregation", judgment_aggregation_ty()),
("doctrinal_paradox", doctrinal_paradox_ty()),
(
"judgment_aggregation_impossibility",
judgment_aggregation_impossibility_ty(),
),
("QuotaRule", quota_rule_ty()),
("PremiseBasedProcedure", premise_based_procedure_ty()),
("ConclusionBasedProcedure", conclusion_based_procedure_ty()),
("AGMRevision", agm_revision_ty()),
("AGMContraction", agm_contraction_ty()),
("BeliefMergeOperator", belief_merge_operator_ty()),
("MajorityMerge", majority_merge_ty()),
("EnvyFreeAllocation", envy_free_allocation_ty()),
("EfficientAllocation", efficient_allocation_ty()),
("TopTradingCycles", top_trading_cycles_ty()),
("CakeCutting", cake_cutting_ty()),
("ProportionalAllocation", proportional_allocation_ty()),
("EnvyFreenessCakeCutting", envy_freeness_cake_cutting_ty()),
("DeliberativeDemocracy", deliberative_democracy_ty()),
("ConsensusAggregation", consensus_aggregation_ty()),
("HegselmannKrauseModel", hegselmann_krause_model_ty()),
("InfluencePropagation", influence_propagation_ty()),
("MedianRule", median_rule_ty()),
("DistanceRationalizability", distance_rationalizability_ty()),
(
"ProbabilisticSocialChoice",
probabilistic_social_choice_ty(),
),
("SSBUtility", ssb_utility_ty()),
("LotteryDominance", lottery_dominance_ty()),
("SpatialVotingModel", spatial_voting_model_ty()),
("median_voter_theorem", median_voter_theorem_ty()),
("MultiDimensionalMedian", multi_dimensional_median_ty()),
("HedonicGame", hedonic_game_ty()),
("StablePartition", stable_partition_ty()),
("CoreCoalition", core_coalition_ty()),
("NashStablePartition", nash_stable_partition_ty()),
("ShapleyShubikIndex", shapley_shubik_index_ty()),
("BanzhafIndex", banzhaf_index_ty()),
("WeightedVotingGame", weighted_voting_game_ty()),
("condorcet_jury_theorem", condorcet_jury_theorem_ty()),
("TruthTracking", truth_tracking_ty()),
("EpistemicDemocracy", epistemic_democracy_ty()),
("TransitiveDelegation", transitive_delegation_ty()),
("DelegationGraph", delegation_graph_ty()),
("guruly_critique", guruly_critique_ty()),
("ManipulationComplexity", manipulation_complexity_ty()),
("BriberyComplexity", bribery_complexity_ty()),
("ControlComplexity", control_complexity_ty()),
];
for (name, ty) in axioms {
env.add(Declaration::Axiom {
name: Name::str(*name),
univ_params: vec![],
ty: ty.clone(),
})
.ok();
}
}
pub(super) fn factorial(n: usize) -> f64 {
(1..=n).map(|i| i as f64).product()
}
pub(super) fn next_permutation(perm: &mut Vec<usize>) -> bool {
let n = perm.len();
if n < 2 {
return false;
}
let mut i = n - 1;
while i > 0 && perm[i - 1] >= perm[i] {
i -= 1;
}
if i == 0 {
return false;
}
let mut j = n - 1;
while perm[j] <= perm[i - 1] {
j -= 1;
}
perm.swap(i - 1, j);
perm[i..].reverse();
true
}
#[cfg(test)]
mod tests {
use super::*;
fn three_voter_profile() -> PreferenceProfile {
PreferenceProfile::new(vec![vec![0, 1, 2], vec![1, 2, 0], vec![2, 0, 1]])
}
fn clear_winner_profile() -> PreferenceProfile {
PreferenceProfile::new(vec![vec![0, 1, 2], vec![0, 2, 1], vec![1, 0, 2]])
}
#[test]
fn test_condorcet_paradox_detection() {
let profile = three_voter_profile();
assert!(profile.majority_beats(0, 1), "A should beat B");
assert!(profile.majority_beats(1, 2), "B should beat C");
assert!(profile.majority_beats(2, 0), "C should beat A");
assert_eq!(
condorcet_winner(&profile),
None,
"No Condorcet winner in paradox profile"
);
assert!(
condorcet_cycle(&profile).is_some(),
"Condorcet cycle should be detected"
);
}
#[test]
fn test_condorcet_winner() {
let profile = clear_winner_profile();
assert_eq!(
condorcet_winner(&profile),
Some(0),
"Alternative 0 should be Condorcet winner"
);
}
#[test]
fn test_borda_count() {
let profile = clear_winner_profile();
let scores = borda_count(&profile);
assert_eq!(scores[0].0, 0, "Alt 0 should be Borda winner");
assert_eq!(scores[0].1, 5, "Alt 0 Borda score should be 5");
assert_eq!(borda_winner(&profile), 0);
}
#[test]
fn test_plurality_vote() {
let profile = clear_winner_profile();
let (winner, counts) = plurality_vote(&profile);
assert_eq!(winner, 0, "Alt 0 wins plurality with 2 votes");
assert_eq!(counts[0], 2);
assert_eq!(counts[1], 1);
assert_eq!(counts[2], 0);
}
#[test]
fn test_instant_runoff() {
let profile = PreferenceProfile::new(vec![
vec![0, 1, 2],
vec![0, 1, 2],
vec![1, 2, 0],
vec![1, 2, 0],
vec![2, 0, 1],
]);
let winner = instant_runoff(&profile);
assert_eq!(winner, 0, "IRV winner should be alternative 0 (A)");
}
#[test]
fn test_approval_voting() {
let approvals = vec![
vec![true, true, false],
vec![false, true, true],
vec![true, false, true],
];
let (winner, counts) = approval_vote(&approvals);
assert_eq!(counts[0], 2);
assert_eq!(counts[1], 2);
assert_eq!(counts[2], 2);
assert_eq!(winner, 0);
}
#[test]
fn test_pareto_optimality() {
let profile = PreferenceProfile::new(vec![vec![0, 1, 2], vec![0, 1, 2]]);
assert!(pareto_dominates(&profile, 0, 1), "A Pareto-dominates B");
assert!(pareto_dominates(&profile, 0, 2), "A Pareto-dominates C");
assert!(pareto_dominates(&profile, 1, 2), "B Pareto-dominates C");
assert!(
!pareto_dominates(&profile, 1, 0),
"B does not Pareto-dominate A"
);
let opt_set = pareto_optimal_set(&profile);
assert_eq!(opt_set, vec![0], "Only A is Pareto-optimal");
}
#[test]
fn test_liquid_democracy() {
let delegation = vec![None, None, None, Some(0), Some(2)];
let direct_vote = vec![Some(0), Some(1), Some(0), None, None];
let ld = LiquidDemocracy::new(5, delegation, direct_vote);
assert_eq!(ld.effective_vote(0), Some(0));
assert_eq!(ld.effective_vote(3), Some(0));
assert_eq!(ld.effective_vote(4), Some(0));
let totals = ld.vote_totals(2);
assert_eq!(totals[0], 4, "Alt 0 should receive 4 effective votes");
assert_eq!(totals[1], 1, "Alt 1 should receive 1 effective vote");
assert_eq!(ld.winner(2), Some(0));
assert!(ld.find_cycle().is_none(), "No delegation cycles");
}
#[test]
fn test_liquid_democracy_cycle() {
let delegation = vec![Some(1), Some(2), Some(0)];
let direct_vote = vec![None, None, None];
let ld = LiquidDemocracy::new(3, delegation, direct_vote);
assert!(ld.find_cycle().is_some(), "Cycle should be detected");
assert_eq!(ld.effective_vote(0), None);
assert_eq!(ld.effective_vote(1), None);
}
}
pub fn sct_ext_arrow_impossibility_detailed_ty() -> Expr {
pi(
BinderInfo::Default,
"n",
nat_ty(),
pi(
BinderInfo::Default,
"m",
nat_ty(),
arrow(
app2(cst("NatGe"), bvar(1), cst("Two")),
arrow(
app2(cst("NatGe"), bvar(1), cst("Three")),
pi(
BinderInfo::Default,
"f",
app2(cst("SocialWelfareFunction"), bvar(3), bvar(2)),
arrow(
app(cst("IIA"), bvar(0)),
arrow(
app(cst("ParetoOptimal"), bvar(1)),
app(cst("Dictatorial"), bvar(2)),
),
),
),
),
),
),
)
}
pub fn sct_ext_decisive_set_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn sct_ext_pivot_lemma_ty() -> Expr {
pi(
BinderInfo::Default,
"f",
cst("SocialWelfareFunction"),
arrow(
app(cst("IIA"), bvar(0)),
arrow(
app(cst("ParetoOptimal"), bvar(1)),
app2(cst("ExistsDecisive"), bvar(2), bvar(2)),
),
),
)
}
pub fn sct_ext_field_expansion_lemma_ty() -> Expr {
prop()
}
pub fn sct_ext_sen_impossibility_ty() -> Expr {
pi(
BinderInfo::Default,
"n",
nat_ty(),
arrow(app(cst("NatGeTwo"), bvar(0)), arrow(prop(), prop())),
)
}
pub fn sct_ext_minimal_liberalism_ty() -> Expr {
arrow(cst("SocialChoiceFunction"), prop())
}
pub fn sct_ext_liberal_paradox_ty() -> Expr {
prop()
}
pub fn sct_ext_gibbard_satterthwaite_detailed_ty() -> Expr {
pi(
BinderInfo::Default,
"n",
nat_ty(),
pi(
BinderInfo::Default,
"m",
nat_ty(),
arrow(
app(cst("NatGeThree"), bvar(0)),
pi(
BinderInfo::Default,
"f",
app2(cst("SocialChoiceFunction"), bvar(2), bvar(1)),
arrow(
app(cst("IsSurjective"), bvar(0)),
arrow(
app(cst("IsStrategyProof"), bvar(1)),
app(cst("Dictatorial"), bvar(2)),
),
),
),
),
),
)
}
pub fn sct_ext_manipulable_voting_rule_ty() -> Expr {
arrow(cst("SocialChoiceFunction"), arrow(nat_ty(), prop()))
}
pub fn sct_ext_gibbard_randomized_ty() -> Expr {
pi(
BinderInfo::Default,
"f",
cst("ProbabilisticSocialChoice"),
arrow(
app(cst("IsStrategyProof"), bvar(0)),
app(cst("IsMixtureOfDictatorsAndDuos"), bvar(1)),
),
)
}
pub fn sct_ext_blacks_theorem_ty() -> Expr {
pi(
BinderInfo::Default,
"n",
nat_ty(),
arrow(
app(cst("AllSinglePeaked"), bvar(0)),
app2(cst("ExistsCondorcetWinner"), bvar(1), bvar(1)),
),
)
}
pub fn sct_ext_single_peaked_preference_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn sct_ext_median_voter_equilibrium_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn sct_ext_downs_median_voter_ty() -> Expr {
pi(
BinderInfo::Default,
"n",
nat_ty(),
arrow(
app(cst("IsOdd"), bvar(0)),
app2(
cst("Eq"),
app(cst("MedianPosition"), bvar(1)),
app(cst("CondorcetWinnerPos"), bvar(2)),
),
),
)
}
pub fn sct_ext_condorcet_consistency_ty() -> Expr {
arrow(cst("SocialChoiceFunction"), prop())
}
pub fn sct_ext_condorcet_loser_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn sct_ext_condorcet_efficiency_ty() -> Expr {
arrow(cst("SocialChoiceFunction"), arrow(real_ty(), prop()))
}
pub fn sct_ext_smith_set_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn sct_ext_schwartz_set_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), prop()))
}
pub fn sct_ext_smith_set_singleton_ty() -> Expr {
pi(
BinderInfo::Default,
"n",
nat_ty(),
pi(
BinderInfo::Default,
"m",
nat_ty(),
arrow(
app2(cst("CondorcetWinnerExists"), bvar(1), bvar(0)),
app2(cst("SmithSetIsSingleton"), bvar(2), bvar(1)),
),
),
)
}
pub fn sct_ext_kemeny_young_rule_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
}
pub fn sct_ext_kemeny_score_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
}
pub fn sct_ext_kemeny_condorcet_consistency_ty() -> Expr {
pi(
BinderInfo::Default,
"f",
cst("KemenyRule"),
app(cst("IsCondorcetConsistent"), bvar(0)),
)
}
pub fn sct_ext_kemeny_np_hard_ty() -> Expr {
prop()
}
pub fn sct_ext_borda_count_rule_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
}
pub fn sct_ext_borda_condorcet_loser_ty() -> Expr {
pi(
BinderInfo::Default,
"profile",
cst("PreferenceProfile"),
arrow(
app(cst("CondorcetLoserEx"), bvar(0)),
arrow(
cst("True"),
app2(
cst("Neq"),
app(cst("BordaWinner"), bvar(2)),
app(cst("CondorcetLoserVal"), bvar(2)),
),
),
),
)
}
pub fn sct_ext_borda_properties_ty() -> Expr {
prop()
}
pub fn sct_ext_borda_positive_resp_ty() -> Expr {
pi(
BinderInfo::Default,
"profile",
cst("PreferenceProfile"),
pi(
BinderInfo::Default,
"alt",
nat_ty(),
arrow(
app2(cst("BordaWinnerIs"), bvar(1), bvar(0)),
arrow(
app2(cst("MoreFirstPlaceVotes"), bvar(2), bvar(1)),
app2(cst("BordaWinnerIs"), bvar(3), bvar(2)),
),
),
),
)
}
pub fn sct_ext_dhondt_method_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
}
pub fn sct_ext_sainte_lague_method_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
}
pub fn sct_ext_hare_quota_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
}
pub fn sct_ext_droop_quota_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
}
pub fn sct_ext_pr_anonymity_ty() -> Expr {
prop()
}
pub fn sct_ext_largest_remainder_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
}
pub fn sct_ext_utilitarian_swf_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn sct_ext_rawlsian_swf_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn sct_ext_nash_swf_ty() -> Expr {
arrow(nat_ty(), arrow(real_ty(), prop()))
}
pub fn sct_ext_harsanyi_utilitarian_ty() -> Expr {
pi(
BinderInfo::Default,
"n",
nat_ty(),
arrow(
app(cst("HasVNMUtility"), bvar(0)),
app(cst("IsUtilitarian"), bvar(1)),
),
)
}
pub fn sct_ext_egalitarian_principle_ty() -> Expr {
pi(
BinderInfo::Default,
"n",
nat_ty(),
arrow(
app(cst("IsRawlsian"), bvar(0)),
app(cst("MaximisesMinUtility"), bvar(1)),
),
)
}
pub fn sct_ext_cake_cutting_protocol_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn sct_ext_moving_knife_protocol_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn sct_ext_sutherland_conway_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn sct_ext_brams_taylor_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn sct_ext_aziz_mackenzie_ty() -> Expr {
arrow(nat_ty(), prop())
}
pub fn sct_ext_envy_free_existence_ty() -> Expr {
pi(
BinderInfo::Default,
"n",
nat_ty(),
app2(
cst("Exists"),
cst("Protocol"),
app(cst("IsEnvyFree"), bvar(0)),
),
)
}
pub fn sct_ext_copeland_method_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
}
pub fn sct_ext_copeland_condorcet_ty() -> Expr {
pi(
BinderInfo::Default,
"f",
cst("CopelandRule"),
app(cst("IsCondorcetConsistent"), bvar(0)),
)
}
pub fn sct_ext_copeland_smith_criterion_ty() -> Expr {
prop()
}
pub fn sct_ext_approval_voting_axioms_ty() -> Expr {
prop()
}
pub fn sct_ext_approval_voting_neutrality_ty() -> Expr {
pi(
BinderInfo::Default,
"f",
cst("ApprovalRule"),
app(cst("IsNeutral"), bvar(0)),
)
}
pub fn sct_ext_approval_strategy_proof_ty() -> Expr {
pi(
BinderInfo::Default,
"f",
cst("ApprovalRule"),
app(cst("IsStrategyProofSincere"), bvar(0)),
)
}
pub fn sct_ext_irv_axioms_ty() -> Expr {
prop()
}
pub fn sct_ext_irv_violates_iia_ty() -> Expr {
pi(
BinderInfo::Default,
"f",
cst("IRVRule"),
arrow(app(cst("IIA"), bvar(0)), cst("False")),
)
}
pub fn sct_ext_irv_majority_criterion_ty() -> Expr {
pi(
BinderInfo::Default,
"f",
cst("IRVRule"),
app(cst("SatisfiesMajorityCriterion"), bvar(0)),
)
}
pub fn register_social_choice_extended(env: &mut Environment) -> Result<(), String> {
let axioms: &[(&str, Expr)] = &[
(
"arrow_impossibility_detailed",
sct_ext_arrow_impossibility_detailed_ty(),
),
("DecisiveSet", sct_ext_decisive_set_ty()),
("pivot_lemma", sct_ext_pivot_lemma_ty()),
("field_expansion_lemma", sct_ext_field_expansion_lemma_ty()),
("sen_impossibility", sct_ext_sen_impossibility_ty()),
("MinimalLiberalism", sct_ext_minimal_liberalism_ty()),
("liberal_paradox", sct_ext_liberal_paradox_ty()),
(
"gibbard_satterthwaite_detailed",
sct_ext_gibbard_satterthwaite_detailed_ty(),
),
(
"ManipulableVotingRule",
sct_ext_manipulable_voting_rule_ty(),
),
("gibbard_randomized", sct_ext_gibbard_randomized_ty()),
("blacks_theorem", sct_ext_blacks_theorem_ty()),
(
"SinglePeakedPreference",
sct_ext_single_peaked_preference_ty(),
),
(
"MedianVoterEquilibrium",
sct_ext_median_voter_equilibrium_ty(),
),
("downs_median_voter", sct_ext_downs_median_voter_ty()),
("CondorcetConsistency", sct_ext_condorcet_consistency_ty()),
("CondorcetLoser", sct_ext_condorcet_loser_ty()),
("CondorcetEfficiency", sct_ext_condorcet_efficiency_ty()),
("SmithSet", sct_ext_smith_set_ty()),
("SchwartzSet", sct_ext_schwartz_set_ty()),
("smith_set_singleton", sct_ext_smith_set_singleton_ty()),
("KemenyYoungRule", sct_ext_kemeny_young_rule_ty()),
("KemenyScore", sct_ext_kemeny_score_ty()),
(
"kemeny_condorcet_consistency",
sct_ext_kemeny_condorcet_consistency_ty(),
),
("kemeny_np_hard", sct_ext_kemeny_np_hard_ty()),
("BordaCountRule", sct_ext_borda_count_rule_ty()),
("borda_condorcet_loser", sct_ext_borda_condorcet_loser_ty()),
("borda_properties", sct_ext_borda_properties_ty()),
("borda_positive_resp", sct_ext_borda_positive_resp_ty()),
("DHondtMethod", sct_ext_dhondt_method_ty()),
("SainteLagueMethod", sct_ext_sainte_lague_method_ty()),
("HareQuota", sct_ext_hare_quota_ty()),
("DroopQuota", sct_ext_droop_quota_ty()),
(
"ProportionalRepresentationAnonymity",
sct_ext_pr_anonymity_ty(),
),
("LargestRemainder", sct_ext_largest_remainder_ty()),
("UtilitarianSWF", sct_ext_utilitarian_swf_ty()),
("RawlsianSWF", sct_ext_rawlsian_swf_ty()),
("NashSWF", sct_ext_nash_swf_ty()),
("harsanyi_utilitarian", sct_ext_harsanyi_utilitarian_ty()),
("egalitarian_principle", sct_ext_egalitarian_principle_ty()),
("CakeCuttingProtocol", sct_ext_cake_cutting_protocol_ty()),
("MovingKnifeProtocol", sct_ext_moving_knife_protocol_ty()),
("SutherlandConway", sct_ext_sutherland_conway_ty()),
("BramsTaylor", sct_ext_brams_taylor_ty()),
("AzizMackenzie", sct_ext_aziz_mackenzie_ty()),
("envy_free_existence", sct_ext_envy_free_existence_ty()),
("CopelandMethod", sct_ext_copeland_method_ty()),
("copeland_condorcet", sct_ext_copeland_condorcet_ty()),
(
"copeland_smith_criterion",
sct_ext_copeland_smith_criterion_ty(),
),
(
"approval_voting_axioms",
sct_ext_approval_voting_axioms_ty(),
),
(
"approval_voting_neutrality",
sct_ext_approval_voting_neutrality_ty(),
),
(
"approval_strategy_proof",
sct_ext_approval_strategy_proof_ty(),
),
("irv_axioms", sct_ext_irv_axioms_ty()),
("irv_violates_iia", sct_ext_irv_violates_iia_ty()),
(
"irv_majority_criterion",
sct_ext_irv_majority_criterion_ty(),
),
("NatGe", arrow(nat_ty(), arrow(nat_ty(), prop()))),
("NatGeTwo", arrow(nat_ty(), prop())),
("NatGeThree", arrow(nat_ty(), prop())),
("Two", nat_ty()),
("Three", nat_ty()),
("IIA", arrow(cst("SocialWelfareFunction"), prop())),
("ParetoOptimal", arrow(cst("SocialWelfareFunction"), prop())),
("Dictatorial", arrow(cst("SocialWelfareFunction"), prop())),
("IsSurjective", arrow(cst("SocialChoiceFunction"), prop())),
(
"IsStrategyProof",
arrow(cst("SocialChoiceFunction"), prop()),
),
(
"ExistsDecisive",
arrow(
cst("SocialWelfareFunction"),
arrow(cst("SocialWelfareFunction"), prop()),
),
),
("AllSinglePeaked", arrow(nat_ty(), prop())),
(
"ExistsCondorcetWinner",
arrow(nat_ty(), arrow(nat_ty(), prop())),
),
("IsOdd", arrow(nat_ty(), prop())),
("MedianPosition", arrow(nat_ty(), nat_ty())),
("CondorcetWinnerPos", arrow(nat_ty(), nat_ty())),
("Eq", arrow(nat_ty(), arrow(nat_ty(), prop()))),
(
"CondorcetWinnerExists",
arrow(nat_ty(), arrow(nat_ty(), prop())),
),
(
"SmithSetIsSingleton",
arrow(nat_ty(), arrow(nat_ty(), prop())),
),
("KemenyRule", type0()),
("IsCondorcetConsistent", arrow(type0(), prop())),
("CondorcetLoserEx", arrow(cst("PreferenceProfile"), prop())),
("BordaWinner", arrow(cst("PreferenceProfile"), nat_ty())),
(
"CondorcetLoserVal",
arrow(cst("PreferenceProfile"), nat_ty()),
),
("Neq", arrow(nat_ty(), arrow(nat_ty(), prop()))),
(
"BordaWinnerIs",
arrow(cst("PreferenceProfile"), arrow(nat_ty(), prop())),
),
(
"MoreFirstPlaceVotes",
arrow(cst("PreferenceProfile"), arrow(nat_ty(), prop())),
),
("HasVNMUtility", arrow(nat_ty(), prop())),
("IsUtilitarian", arrow(nat_ty(), prop())),
("IsRawlsian", arrow(nat_ty(), prop())),
("MaximisesMinUtility", arrow(nat_ty(), prop())),
("Protocol", type0()),
("IsEnvyFree", arrow(type0(), prop())),
("CopelandRule", type0()),
("ApprovalRule", type0()),
("IsNeutral", arrow(type0(), prop())),
("IsStrategyProofSincere", arrow(type0(), prop())),
("IRVRule", type0()),
("False", prop()),
("SatisfiesMajorityCriterion", arrow(type0(), prop())),
("ProbabilisticSocialChoiceFunc", type0()),
("IsMixtureOfDictatorsAndDuos", arrow(type0(), prop())),
("True", prop()),
];
for (name, ty) in axioms {
env.add(Declaration::Axiom {
name: Name::str(*name),
univ_params: vec![],
ty: ty.clone(),
})
.ok();
}
Ok(())
}
pub fn kemeny_score(profile: &PreferenceProfile, ranking: &[usize]) -> usize {
let mut score = 0usize;
let m = ranking.len();
for i in 0..m {
for j in (i + 1)..m {
let a = ranking[i];
let b = ranking[j];
score += profile.majority_margin(a, b);
}
}
score
}
pub fn kemeny_young_winner(profile: &PreferenceProfile) -> usize {
let m = profile.n_alts;
assert!(m <= 8, "Kemeny-Young brute force limited to 8 alternatives");
let mut perm: Vec<usize> = (0..m).collect();
let mut best_score = 0usize;
let mut best_winner = 0usize;
loop {
let score = kemeny_score(profile, &perm);
if score > best_score {
best_score = score;
best_winner = perm[0];
}
if !kemeny_next_perm(&mut perm) {
break;
}
}
best_winner
}
pub fn kemeny_next_perm(perm: &mut Vec<usize>) -> bool {
let n = perm.len();
if n < 2 {
return false;
}
let mut i = n - 1;
while i > 0 && perm[i - 1] >= perm[i] {
i -= 1;
}
if i == 0 {
return false;
}
let mut j = n - 1;
while perm[j] <= perm[i - 1] {
j -= 1;
}
perm.swap(i - 1, j);
perm[i..].reverse();
true
}
pub fn copeland_scores(profile: &PreferenceProfile) -> Vec<(usize, i64)> {
let m = profile.n_alts;
let mut scores = vec![0i64; m];
for a in 0..m {
for b in 0..m {
if a == b {
continue;
}
let margin_a = profile.majority_margin(a, b);
let margin_b = profile.majority_margin(b, a);
if margin_a > margin_b {
scores[a] += 1;
} else if margin_b > margin_a {
scores[a] -= 1;
}
}
}
let mut result: Vec<(usize, i64)> = scores.into_iter().enumerate().collect();
result.sort_by_key(|b| std::cmp::Reverse(b.1));
result
}
pub fn copeland_winner(profile: &PreferenceProfile) -> usize {
copeland_scores(profile)[0].0
}
pub fn smith_set(profile: &PreferenceProfile) -> Vec<usize> {
let m = profile.n_alts;
let beats: Vec<Vec<bool>> = (0..m)
.map(|i| {
(0..m)
.map(|j| i != j && profile.majority_margin(i, j) > profile.majority_margin(j, i))
.collect()
})
.collect();
let mut dom = beats.clone();
for k in 0..m {
for i in 0..m {
for j in 0..m {
if dom[i][k] && dom[k][j] {
dom[i][j] = true;
}
}
}
}
let mut smith: Vec<bool> = vec![true; m];
let mut changed = true;
while changed {
changed = false;
for i in 0..m {
if !smith[i] {
continue;
}
for j in 0..m {
if !smith[j] && dom[j][i] && !dom[i][j] {
smith[i] = false;
changed = true;
break;
}
}
}
}
(0..m).filter(|&i| smith[i]).collect()
}
pub fn is_single_peaked(profile: &PreferenceProfile, order: &[usize]) -> bool {
for voter in 0..profile.n_voters {
let top = profile.rankings[voter][0];
let top_pos = match order.iter().position(|&a| a == top) {
Some(p) => p,
None => return false,
};
for rank_a in 0..profile.n_alts {
for rank_b in (rank_a + 1)..profile.n_alts {
let a = profile.rankings[voter][rank_a];
let b = profile.rankings[voter][rank_b];
let pos_a = match order.iter().position(|&x| x == a) {
Some(p) => p,
None => return false,
};
let pos_b = match order.iter().position(|&x| x == b) {
Some(p) => p,
None => return false,
};
let dist_a = (pos_a as i64 - top_pos as i64).unsigned_abs() as usize;
let dist_b = (pos_b as i64 - top_pos as i64).unsigned_abs() as usize;
if dist_a > dist_b {
return false;
}
}
}
}
true
}
pub fn median_voter_position(profile: &PreferenceProfile, order: &[usize]) -> Option<usize> {
let mut top_positions: Vec<usize> = profile
.rankings
.iter()
.filter_map(|ranking| {
if ranking.is_empty() {
return None;
}
order.iter().position(|&a| a == ranking[0])
})
.collect();
if top_positions.is_empty() {
return None;
}
top_positions.sort();
let median_idx = top_positions[top_positions.len() / 2];
order.get(median_idx).copied()
}